Python数据分析基础之图与图表(1)

参考资料:
《Python数据分析基础》,作者[美]Clinton W. Brownley,译者陈光欣,中国工信出版集团,人民邮电出版社

  数据可视化主要旨在借助于图形化手段,清晰有效地传达与沟通信息。它可以使我们看到变量的分布和变量之间的关系,还可以检查建模过程中的假设。
  Python提供了若干种用于绘图的扩展包,包括matplotlib, pandas, ggplot和seaborn。
  这一节主要对matplotlib进行学习。
  有关matplotlib的相关函数及说明,可以参考我以前写的两篇博客:《【总结篇】Python matplotlib之使用函数绘制matplotlib的图表组成元素》《【总结篇】Python matplotlib之使用统计函数绘制简单图形》

使用matplotlib绘制条形图

  代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3

import matplotlib.pyplot as plt

plt.style.use('ggplot') # 使用ggplot样式表来模拟ggplot2风格的图形

# 为条形图准备数据
customers = ['ABC', 'DEF', 'GHI', 'JKL', 'MNO']
customers_index = range(len(customers))
sale_amounts = [127, 90, 201, 111, 232]

# 绘图
fig = plt.figure() # 创建了一个基础图
ax1 = fig.add_subplot(1, 1, 1) # 在基础图中创建1行1列的子图,并使用第1个也是唯一的一个子图
ax1.bar(customers_index, sale_amounts, align='center', color='darkblue') # 创建条形图
ax1.xaxis.set_ticks_position('bottom') # x刻度线位置
ax1.yaxis.set_ticks_position('left') # y刻度线位置
plt.xticks(customers_index, customers, rotation=0, fontsize='small') # 将刻度线标签更改为实际的客户名称
plt.xlabel('Customer Name') # 添加x轴标签
plt.ylabel('Sale Amount') # 添加y轴标签
plt.title('Sale Amount per Customer') # 添加图形标题
plt.savefig('bar_plot.png', dpi=400, bbox_inches='tight') # 保存统计图到当前文件夹
plt.show()

Tips:
ax1.bar(customers_index, sale_amounts, align=’center’, color=’darkblue’)
customer_index:设置条形图左侧在x轴上的坐标
sale_amounts:设置条形的高度
align=’center’:设置条形与标签中间对齐
color=’darkblue’:设置标签的颜色

Tips:
plt.xticks(customers_index, customers, rotation=0, fontsize=’small’)
rotation=0:刻度标签是水平的
fontsize=’small’:将刻度标签的字体设为小字体

Tips:
plt.savefig(‘bar_plot.png’, dpi=400, bbox_inches=’tight’)
dpi=400:设置图形分辨率
bbox_inches=’tight’:在保存图形时,将图形四周的空白部分去掉

  运行结果如下图所示。
在这里插入图片描述

使用matplotlib绘制直方图

  代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env python3

import numpy as np
import matplotlib.pyplot as plt

plt.style.use('ggplot')

# 为直方图准备数据
mu1, mu2, sigma = 100, 130, 15
x1 = mu1 + sigma * np.random.randn(10000) # 使用随机数生成器创建两个正态分布变量
x2 = mu2 + sigma * np.random.randn(10000)

# 绘图
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
n, bins, patches = ax1.hist(x1, bins=50, density=False, color='darkgreen') # 创建概率分布图
n, bins, patches = ax1.hist(x2, bins=50, density=False, color='orange', alpha=0.5)
ax1.xaxis.set_ticks_position('bottom')
ax1.yaxis.set_ticks_position('left')
plt.xlabel('Bins')
plt.ylabel('Number of Values in Bin')
fig.suptitle('Histograms', fontsize=14, fontweight='bold') # 为基础图添加一个居中的标题
ax1.set_title('Two Frequency Distributions') # 为子图添加一个居中的标题,位于基础图标题下面
plt.savefig('histogram.png', dpi=400, bbox_inches='tight')
plt.show()

Tips:
n, bins, patches = ax1.hist(x2, bins=50, density=False, color=’orange’, alpha=0.5)
bins=50:每个变量的值被分成50份
density=False:直方图显示的是频率分布,而不是概率密度
color=’darkgreen’:直方图颜色
alpha=0.5:透明度

  运行结果如下图所示。
在这里插入图片描述

使用matplotlib绘制折线图

  代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python3

from numpy.random import randn
import matplotlib.pyplot as plt

plt.style.use('ggplot')

# 为折线图准备数据
plot_data1 = randn(50).cumsum() # 使用randn创建绘图所用的随机数据
plot_data2 = randn(50).cumsum()
plot_data3 = randn(50).cumsum()
plot_data4 = randn(50).cumsum()

# 绘图
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
ax1.plot(plot_data1, marker=r'o', color=u'blue', linestyle='-', label='Blue Solid') # 创建4条折线
ax1.plot(plot_data2, marker=r'+', color=u'red', linestyle='--', label='Red Dashed')
ax1.plot(plot_data3, marker=r'*', color=u'green', linestyle='-.', label='Green Dash Dot')
ax1.plot(plot_data4, marker=r's', color=u'orange', linestyle=':', label='Orange Dotted')
ax1.xaxis.set_ticks_position('bottom')
ax1.yaxis.set_ticks_position('left')
ax1.set_title('Line Plots: Markers, Colors, and Linestyles')
plt.xlabel('Draw')
plt.ylabel('Random Number')
plt.legend(loc='best') # 创建图例,根据图中的空白部分将图例放在最合适的位置
plt.savefig('line_plot.png', dpi=400, bbox_inches='tight')
plt.show()

Tips:
ax1.plot(plot_data2, marker=r’+’, color=u’red’, linestyle=’–’, label=’Red Dashed’)
marker=r’+’:数据点类型
color=u’red’:颜色
linestyle=’–’:线型
label=’Red Dashed’:保证折线在图例中可以正确标记

  运行结果如下图所示。
在这里插入图片描述

使用matplotlib绘制散点图

  代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python3

import numpy as np
import matplotlib.pyplot as plt

plt.style.use('ggplot')

# 为散点图准备数据
x = np.arange(start=1, stop=15, step=1)
y_linear = x + 5 * np.random.randn(14)
y_quadratic = x**2 + 10 * np.random.randn(14)
fn_linear = np.poly1d(np.polyfit(x, y_linear, deg=1))
fn_quadratic = np.poly1d(np.polyfit(x, y_quadratic, deg=2))

# 绘图
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
ax1.plot(x, y_linear, 'bo', x, y_quadratic, 'go', x, fn_linear(x), 'b-',
x, fn_quadratic(x), 'g-', linewidth=2) # 创建带有两条回归曲线的散点图
ax1.xaxis.set_ticks_position('bottom')
ax1.yaxis.set_ticks_position('left')
ax1.set_title('Scatter Plots Regression Lines')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.xlim((min(x) - 1, max(x) + 1)) # 设置x轴范围
plt.ylim((min(y_quadratic) - 10, max(y_quadratic) + 10)) # 设置y轴范围
plt.savefig('scatter_plot.png', dpi=400, bbox_inches='tight')
plt.show()

Tips:
ax1.plot(x, y_linear, ‘bo’, x, y_quadratic, ‘go’, x, fn_linear(x), ‘b-‘, x, fn_quadratic(x), ‘g-‘, linewidth=2)
‘bo’:(x, y_linear)点是蓝色圆圈
‘go’:(x, y_quadratic)点是绿色圆圈
‘b-‘:(x, y_linear)点之间的线是一条蓝色实线
‘g-‘:(x, y_quadratic)点之间的线是一条绿色实线
linewidth=2:线的宽度

  运行结果如下图所示。
在这里插入图片描述

使用matplotlib绘制箱线图

  代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python3

import numpy as np
import matplotlib.pyplot as plt

plt.style.use('ggplot')

# 为箱线图准备数据
N = 500
normal = np.random.normal(loc=0.0, scale=1.0, size=N)
lognormal = np.random.lognormal(mean=0.0, sigma=1.0, size=N)
index_value = np.random.random_integers(low=0, high=N-1, size=N)
normal_sample = normal[index_value]
lognormal_sample = lognormal[index_value]
box_plot_data = [normal, normal_sample, lognormal, lognormal_sample]

# 绘图
fig = plt.figure()
ax1 = fig.add_subplot(1, 1, 1)
box_labels = ['normal', 'normal_sample', 'lognormal', 'lognormal_sample'] # 保存每个箱线图的标签
ax1.boxplot(box_plot_data, notch=False, sym='.', vert=True, whis=1.5, showmeans=True,
labels=box_labels) # 创建4个箱线图
ax1.xaxis.set_ticks_position('bottom')
ax1.yaxis.set_ticks_position('left')
ax1.set_title('Box Plots: Resampling of Two Distributions')
plt.xlabel('Distribution')
plt.ylabel('Value')
plt.savefig('box_plot.png', dpi=400, bbox_inches='tight')
plt.show()

Tips:
ax1.boxplot(box_plot_data, notch=False, sym=’.’, vert=True, whis=1.5, showmeans=True, labels=box_labels)
notch=False:箱体是矩形,而不是在中间收缩
sym=’.’:表示离群点使用圆点,而不是默认的+符号
vert=True:箱体是垂直的,不是水平的
whis=1.5:设定了直线从第一四分位数和第三四分位数延伸出的范围
showmeans=True:箱体在显示中位数的同时也显示均值
labels=box_labels:使用box_labels中的值来标记箱线图

  运行结果如下图所示。
在这里插入图片描述

Python数据分析基础之应用程序

参考资料:
《Python数据分析基础》,作者[美]Clinton W. Brownley,译者陈光欣,中国工信出版集团,人民邮电出版社

在一个大文件集合中查找一组项目

  当我们有大量历史数据的时候,要找到真正需要的数据是非常困难的。我们可以打开每个文件,找出需要的记录,并将其复制粘贴到一个新文件中。但这个过程既浪费时间,又容易出错。使用Python可以自动化地完成整个过程,既节省时间,又不会出错。
  首先,我们在合适的位置创建一个文件夹,命名为file_archive。然后,我们打开Excel,输入下图所示的数据,并将其命名为supplies_2012.csv保存在file_archive文件夹中。
在这里插入图片描述
  我们再打开Excel,如下图所示创建一个Excel工作簿,命名为supplies.xls,并保存在file_archive文件夹中。这个Excel工作簿中包含两个工作表:supplies_2013和supplies_2014。
在这里插入图片描述)在这里插入图片描述
  然后,我们将这个Excel工作簿另存为supplies.xlsx,同样保存在file_archive文件夹中。
在这里插入图片描述)在这里插入图片描述
  现在,在file_archive文件夹中应该有3个文件。
在这里插入图片描述
  我们还需要建立一个item_numbers_to_find.csv文件。我们要搜索的5个数值项目就是下表所示的5个数值。
在这里插入图片描述
  我们的搜索任务是,搜索file_archive文件夹,找出包含我们所需的数值项目的文件,当找到一个数值项目时,需要把包含这个项目的整行数据写入输出文件。
  Python脚本的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#!/usr/bin/env python3

import csv
import glob
import os
import sys
from datetime import date
from xlrd import open_workbook, xldate_as_tuple

item_numbers_file = sys.argv[1]
path_to_folder = sys.argv[2]
output_file = sys.argv[3]

item_numbers_to_find = []
with open(item_numbers_file, 'r', newline='') as item_numbers_csv_file:
filereader = csv.reader(item_numbers_csv_file)
for row in filereader:
item_numbers_to_find.append(row[0])
print(item_numbers_to_find)

filewriter = csv.writer(open(output_file, 'a', newline=''))
file_counter = 0
line_counter = 0
count_of_item_numbers = 0
for input_file in glob.glob(os.path.join(path_to_folder, '*.*')):
file_counter += 1
if input_file.split('.')[1] == 'csv':
with open(input_file, 'r', newline='') as csv_in_file:
filereader = csv.reader(csv_in_file)
header = next(filereader)
for row in filereader:
row_of_output = []
for column in range(len(header)):
if column == 3:
cell_value = str(row[column]).lstrip('$').replace(',', '').strip()
row_of_output.append(cell_value)
else:
cell_value = str(row[column]).strip()
row_of_output.append(cell_value)
row_of_output.append(os.path.basename(input_file))
if row[0] in item_numbers_to_find:
filewriter.writerow(row_of_output)
count_of_item_numbers += 1
line_counter += 1
elif input_file.split('.')[1] == 'xls' or input_file.split('.')[1] == 'xlsx':
workbook = open_workbook(input_file)
for worksheet in workbook.sheets():
try:
header = worksheet.row_values(0)
except IndexError:
pass
for row in range(1, worksheet.nrows):
row_of_output = []
for column in range(len(header)):
if worksheet.cell_type(row, column) == 3:
cell_value = xldate_as_tuple(worksheet.cell(row, column).value, workbook.datemode)
cell_value = str(date(*cell_value[0:3])).strip()
row_of_output.append(cell_value)
else:
cell_value = str(worksheet.cell_value(row, column)).strip()
row_of_output.append(cell_value)
row_of_output.append(os.path.basename(input_file))
row_of_output.append(worksheet.name)
if str(worksheet.cell(row, 0).value).split('.')[0].strip() in item_numbers_to_find:
filewriter.writerow(row_of_output)
count_of_item_numbers += 1
line_counter += 1

print('Number of files: ', file_counter)
print('Number of lines: ', line_counter)
print('Number of item numbers: ', count_of_item_numbers)

  这个脚本看似很长,但其实都是前面学过的内容,它只是将前面三章的内容综合了一下。我们在命令行窗口中运行这个脚本。
在这里插入图片描述)在这里插入图片描述

为CSV文件中数据的任意数目分类计算统计量

  我们创建一个CSV数据文件customer_category_history.csv,其中包含一个客户购买服务包的数据集。
在这里插入图片描述
  我们需要做的,是在这个数据集上执行计算,得到客户在他们购买的每个服务包类别上花费的总时间。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env python3

import csv
import sys
from datetime import date, datetime


def date_diff(date1, date2):
try:
diff = str(datetime.strptime(date1, '%m/%d/%Y') - datetime.strptime(date2, '%m/%d/%Y')).split()[0]
except:
diff = 0
if diff == '0:00:00':
diff = 0
return diff


input_file = sys.argv[1]
output_file = sys.argv[2]

packages = {}
previous_name = 'N/A'
previous_package = 'N/A'
previous_package_date = 'N/A'
first_row = True
today = date.today().strftime('%m/%d/%Y')
with open(input_file, 'r', newline='') as input_csv_file:
filereader = csv.reader(input_csv_file)
header = next(filereader)
for row in filereader:
current_name = row[0]
current_package = row[1]
current_package_date = row[3]
if current_name not in packages:
packages[current_name] = {}
if current_package not in packages[current_name]:
packages[current_name][current_package] = 0
if current_name != previous_name:
if first_row:
first_row = False
else:
diff = date_diff(today, previous_package_date)
if previous_package not in packages[previous_name]:
packages[previous_name][previous_package] = int(diff)
else:
packages[previous_name][previous_package] += int(diff)
else:
diff = date_diff(current_package_date, previous_package_date)
packages[previous_name][previous_package] += int(diff)
previous_name = current_name
previous_package = current_package
previous_package_date = current_package_date

header = ['Customer Name', 'Category', 'Total Time (in Days)']
with open(output_file, 'w', newline='') as output_csv_file:
filewriter = csv.writer(output_csv_file)
filewriter.writerow(header)
for customer_name, customer_name_value in packages.items():
for package_category, package_category_value in packages[customer_name].items():
row_of_output = []
print(customer_name, package_category, package_category_value)
row_of_output.append(customer_name)
row_of_output.append(package_category)
row_of_output.append(package_category_value)
filewriter.writerow(row_of_output)

  这个脚本使用了Python字典数据结构来组织和保存计算结果。实际上,这个脚本中使用的字典是嵌套的字典。外部字典的名称为packages,键为客户名称,与其对应的值是另一个字典,其中的键为服务包类别的名称,值为客户拥有这个服务包的天数。
  我们在命令行窗口中运行这个脚本。
在这里插入图片描述)在这里插入图片描述

为文本文件中数据的任意数目分类计算统计量

  文本文件(也称平面文件)也是商业中常用的文件类型。CSV文件实际上就是以逗号分隔的文本文件形式保存的。活动日志、错误日志和交易记录是商业数据保存在文本文件中的几个更常见的例子。
  接下来我们试着访问MySQL错误日志。

作者注:MySQL错误日志文件的路径为:C:\ProgramData\MySQL\MySQL Server 8.0\Data\LAPTOP-LNF22KFU.err

  我们选择以记事本方式打开该文件,可以看到错误日志中的信息。
在这里插入图片描述
  每个人的MySQL错误日志文件中的数据是不一样的。参考书上为我们提供了一个独立的、有代表性的MySQL错误日志文件,我们把它下载下来。
在这里插入图片描述
  接下来,我们需要做的就是通过Python脚本来进行错误消息的分析和计算。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/env python3

import sys

input_file = sys.argv[1]
output_file = sys.argv[2]

messages = {}
notes = []
with open(input_file, 'r', newline='') as text_file:
for row in text_file:
if '[Note]' in row:
row_list = row.split(' ', 4)
day = row_list[0].strip()
note = row_list[4].strip('\n').strip()
if note not in notes:
notes.append(note)
if day not in messages:
messages[day] = {}
if note not in messages[day]:
messages[day][note] = 1
else:
messages[day][note] += 1

filewriter = open(output_file, 'w', newline='')
header = ['Date']
header.extend(notes)
header = ','.join(map(str, header)) + '\n'
print(header)
filewriter.write(header)
for day, day_value in messages.items():
row_of_output = []
row_of_output.append(day)
for index in range(len(notes)):
if notes[index] in day_value.keys():
row_of_output.append(day_value[notes[index]])
else:
row_of_output.append(0)
output = ','.join(map(str, row_of_output)) + '\n'
print(output)
filewriter.write(output)
filewriter.close()

  我们在命令行窗口中运行这个脚本。
在这里插入图片描述)在这里插入图片描述

实验室寒假数据处理任务总结

想获取输入数据和源代码?点这里


处理一:将数据中所有信息有问题的那行信息删除。如样例中第4行数据,这一行数据只有3个元素,而其他行都有6个元素,所以删除第4行即可。再如最后一行第3个信息明显有问题,所以该行也是问题行,删除即可。将全部数据处理完之后,每行单个元素以逗号为分隔,写入文件test1。
输入数据样例

1
2
3
4
5
6
7
8
9
10
11
18 Jogging 102271561469000 -13.53 16.89 -6.4
18 Jogging 102271641608000 -5.75 16.89 -0.46
18 Jogging 102271681617000 -2.18 16.32 11.07
18 Jogging 3.36
18 Downstairs 103260201636000 -4.44 7.06 1.95
18 Downstairs 103260241614000 -3.87 7.55 3.3
18 Downstairs 103260321693000 -4.06 8.08 4.79
18 Downstairs 103260365577000 -6.32 8.66 4.94
18 Downstairs 103260403083000 -5.37 11.22 3.06
18 Downstairs 103260443305000 -5.79 9.92 2.53
6 Walking 0 0 0 3.214402

输出test1样例

1
2
3
4
5
6
7
8
9
18,Jogging,102271561469000,-13.53,16.89,-6.4
18,Jogging,102271641608000,-5.75,16.89,-0.46
18,Jogging,102271681617000,-2.18,16.32,11.07
18,Downstairs,103260201636000,-4.44,7.06,1.95
18,Downstairs,103260241614000,-3.87,7.55,3.3
18,Downstairs,103260321693000,-4.06,8.08,4.79
18,Downstairs,103260365577000,-6.32,8.66,4.94
18,Downstairs,103260403083000,-5.37,11.22,3.06
18,Downstairs,103260443305000,-5.79,9.92,2.53

我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3

import csv
import sys

input_file = sys.argv[1]
output_file = sys.argv[2]

with open(input_file, 'r', newline='') as csv_in_file:
with open(output_file, 'w', newline='') as csv_out_file:
filereader = csv.reader(csv_in_file, delimiter=' ')
filewriter = csv.writer(csv_out_file)
for row_list in filereader:
if len(row_list) == 6 and row_list[2] != '0':
filewriter.writerow(row_list)

  处理一是一个数据清洗的过程。输出文件的要求是“每行单个元素以逗号为分隔”,这不难让人想到要使用CSV逗号分隔值文件。首先对输入文件和输出文件进行打开操作,然后使用csv模块的reader()writer()函数分别创建了两个对象filereaderfilewriter。因为输入文件的每行的单个元素都以空格分隔,因此我们在创建对象filereader的时候,需要把reader()函数的delimiter参数设置为delimiter=' ',表示以空格作为分隔符。
  我们来分析一下数据样本。样本中有问题的数据行有两种,一种类似于上面输入数据样例的第4行数据,缺少数据元素;另一种类似于输入数据样例的最后一行数据,第3列的数据元素为0。针对第一类问题,我们可以通过判断列表长度的方法将其筛掉,即判断len(row_list)是否为6。而对于第二类问题,我们只需要判断row_list列表中索引为2的元素是否为'0'(注意这里的0是一个字符而并非一个数字)。对于满足条件的数据行,我们将其写入输出文件。


处理二:将test1数据中所有动作的数目统计出来,将动作数目打印到屏幕。然后将动作数目变为100的倍数,多余的删除,将数据行写入输出文件test2。比如统计出Jogging的数量为3021次,那么打印出3021后只往输出文件test2中写入3000条。
我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/usr/bin/env python3

import csv
import sys

input_file = sys.argv[1]
output_file = sys.argv[2]

movementsDict = {}
inputData = []
with open(input_file, 'r', newline='') as csv_in_file:
filereader = csv.reader(csv_in_file)
movementsList = []
for row_list in filereader:
movementsList.append(row_list[1])
for movement in movementsList:
if movement not in movementsDict:
movementsDict[movement] = 1
else:
movementsDict[movement] += 1
for movement, movementsDict[movement] in movementsDict.items():
print('Movement: %-15s' % movement, end='')
print('Amount: ' + str(movementsDict[movement]))
movementsDict[movement] = movementsDict[movement] // 100 * 100
inputData.append(movementsDict[movement])
csv_in_file.seek(0, 0)
with open(output_file, 'w', newline='') as csv_out_file:
filewriter = csv.writer(csv_out_file)
countWalking = 0
countJogging = 0
countUpstairs = 0
countDownstairs = 0
countStanding = 0
countSitting = 0
for row_list in filereader:
if row_list[1] == 'Walking':
if countWalking < inputData[0]:
filewriter.writerow(row_list)
countWalking += 1
elif row_list[1] == 'Jogging':
if countJogging < inputData[1]:
filewriter.writerow(row_list)
countJogging += 1
elif row_list[1] == 'Upstairs':
if countUpstairs < inputData[2]:
filewriter.writerow(row_list)
countUpstairs += 1
elif row_list[1] == 'Downstairs':
if countDownstairs < inputData[3]:
filewriter.writerow(row_list)
countDownstairs += 1
elif row_list[1] == 'Standing':
if countStanding < inputData[4]:
filewriter.writerow(row_list)
countStanding += 1
else:
if countSitting < inputData[5]:
filewriter.writerow(row_list)
countSitting += 1

  对于计数,我们可以应用Python的字典。这和我之前写过的统计文章中单次出现的次数的程序类似。首先建立一个空字典movementsDict和一个空列表movementsList。读取文件时,每个数据行是一个具有6个元素的列表,其中索引为1的元素就是我们要计数的动作。我们把索引为1的元素加入到movementsList中,然后对其中的元素通过字典来计数。为了以100为精确度,我们进行movementsDict[movement] = movementsDict[movement] // 100 * 100的运算。在写入输出文件的时候,我采用了每个动作分别计数的策略,每写入一行就令计数器+1。当计数器变量超过给定数值的时候,停止写入。


处理三:将test2中的数据读出,每行只取最后3列,每行数据中的单个元素以空格隔开,写入文件test3。
输出test3样例

1
2
3
-0.72 9.62 0.14982383
-4.02 11.03 3.445948
0.95 14.71 3.636633

我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3

import csv
import sys

input_file = sys.argv[1]
output_file = sys.argv[2]

my_columns = [3, 4, 5]
with open(input_file, 'r', newline='') as csv_in_file:
with open(output_file, 'w', newline='') as csv_out_file:
filereader = csv.reader(csv_in_file)
filewriter = csv.writer(csv_out_file, delimiter=' ')
for row_list in filereader:
row_list_output = []
for index_value in my_columns:
row_list_output.append(row_list[index_value])
filewriter.writerow(row_list_output)

  处理三还是比较简单的。输出文件只要求保留最后三列,即列索引值为3, 4, 5的三列。我们创建一个列表变量my_columns = [3, 4, 5],用来保存要保留的列索引值。输出文件还要求各个数据元素“以空格隔开”,所以我们在创建对象filewriter的时候,需要把writer()函数的delimiter参数设置为delimiter=' '。空列表row_list_output用来保存要输出的数据行。对于满足列索引值为3, 4, 5的数据元素,将其追加到列表row_list_output中,并将其写入输出文件。


处理四:将test3中的数据读出,每行数据的单个元素用空格隔开,数据行与数据行用逗号隔开,每20个一行写入文件finally中,保证每行不会出现不够或者多出来的情况。
输出finally单行样例

1
-0.72 9.62 0.14982383,-4.02 11.03 3.445948,0.95 14.71 3.636633,-3.57 5.75 -5.407278,-5.28 8.85 -9.615966,-1.14 15.02 -3.8681788,7.86 11.22 -1.879608,6.28 4.9 -2.3018389,0.95 7.06 -3.445948,-1.61 9.7 0.23154591,6.44 12.18 -0.7627395,5.83 12.07 -0.53119355,7.21 12.41 0.3405087,6.17 12.53 -6.701211,-1.08 17.54 -6.701211,-1.69 16.78 3.214402,-2.3 8.12 -3.486809,-2.91 0 -4.7535014,-2.91 0 -4.7535014,-4.44 1.84 -2.8330324

我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#!/usr/bin/env python3

import csv
import sys

input_file = sys.argv[1]
output_file = sys.argv[2]

with open(input_file, 'r', newline='') as csv_in_file:
with open(output_file, 'w', newline='') as csv_out_file:
filereader = csv.reader(csv_in_file)
filewriter = csv.writer(csv_out_file)
total = []
for row_list in filereader:
total.append(row_list[0])
outputList = []
a_list = []
a = 0
for data in total:
if a < 19:
a += 1
a_list.append(data)
else:
a_list.append(data)
outputList.append(a_list)
a = 0
a_list = []
for output_line in outputList:
filewriter.writerow(output_line)

  第13行代码创建了空列表total,用来保存所有的数据行。第16-17行则是把数据行添加到total中的过程。第18行创建了空列表outputList,用来把total中的数据每20个一组进行拆分。第19行创建的a_list是辅助拆分列表的列表变量。a则是一个计数器变量。
  遍历列表total,当a小于19时,a自加1,然后把data添加到a_list中。当a等于19时,再把data添加到a_list之后,a_list的长度为20,这时就需要把a_list添加到outputList中,然后将a归零,将a_list清空,继续下一轮拆分。拆分完成之后,逐行写入输出文件即可。

自学数据结构与算法——算法

  我正在学习的这门课程名叫“数据结构与算法”,这样看来,它们二者一定是有着某种关联。事实上,在学习数据结构的过程中,谈到“算法”是为了帮我们更好地理解数据结构。单说数据结构当然可以,在很短的时间内我们就可以学习完几种重要的数据结构。但是这样的话,我们学完后不会有什么感觉,也不知道数据结构有何作用。下面主要讨论一下有关算法的方方面面。

两种算法的比较

  在之前,无论是学习C语言也好,学习Python也好,学习Java也好,我们一定写过这样一个程序:1-100求和。这是一个再简单不过的程序,我们也很快能写出这样的代码:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(void) {
int i, sum = 0;
for (i = 1; i <= 100; i++) {
sum += i;
}
printf("%d\n", sum);
return 0;
}

  这个简单的程序就是一种算法。程序本身没有什么需要解释的地方,就是应用了一个for循环来对1-100这些数字进行累加。我相信这也是大多数人拿到这个题目的第一想法。
  相信很多人都听说过数学家高斯的一个故事。老师让所有的学生计算1+2+3+…+100的结果,这道题难倒了很多学生,但只有高斯很快地算出了结果。这让老师非常惊讶(可能老师也是通过1+2=3, 3+3=6, 6+4=10…这样的方法来计算的吧)。当被问到怎么样能如此快地计算出结果的时候,高斯解释道:

∵ sum = 1 + 2 + 3 + ... + 99 + 100
又∵ sum = 100 + 99 + 98 + ... + 2 + 1
∴ 2 × sum = 101 + 101 + 101 + ... + 101 = 100 × 101
∴ sum = 5050

  高斯用的方法相当于求等差数列前n项和的方法,用代码实现如下:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(void) {
int n = 100, sum = 0;
sum = (1 + n) * n / 2;
printf("%d\n", sum);
return 0;
}

  这个程序并没有用到循环。试想一下,如果我们要对1至1亿的所有数字进行求和(当然,这里的int要替换为长整型),如果用循环的方法,计算机要进行1亿次的循环累加,而应用高斯求和的方法,计算机运算出结果只是一瞬间的事。这大大节省了计算机运算的时间。

什么是算法

  前面说了这么多,我们似乎还是摸不清头脑,什么是算法呢?
  现在普遍认可的定义是:算法(Algorithm)是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。
  在算法的定义中提到了指令。指令能被人或机器等计算装置执行,它可以是计算机指令,也可以是我们平时的语言文字。为了解决某个或某类问题,需要把指令表示成一定的操作序列,操作序列包括一组操作,每一个操作都完成特定的功能,这就是算法。

算法的特性

  算法具有5个基本特性:输入,输出,有穷性,确定性,可行性。

输入

  算法具有零个,一个或多个输入。对于大多数算法来说,输入是必要的,但也有个别算法不需要输入任何参数。

输出

  算法至少有一个或多个输出,输出的形式可以是打印输出,也可以是返回一个或多个值等。

有穷性

  算法在执行有限的步骤之后,自动结束而不会出现无限循环,并且每一个步骤在可接受的时间内完成。这里的“有穷”并不是纯数学意义的,它更多是指在实际应用当中合理的、可以接受的“有边界”。

确定性

  算法的每一步骤都具有确定的含义,不会出现二义性。算法在一定条件下,只有一条执行路径,相同的输入只能有唯一的输出结果。算法的每个步骤被精确定义而无歧义。

可行性

  算法的每一步都必须是可行的,也就是说,每一步都能够通过执行有限次数完成。可行性意味着算法可以转换为程序上机运行,并得到正确的结果。

算法设计的要求

  算法设计有以下几个要求:正确性,可读性,健壮性,时间效率高和存储量低。

正确性

  一个好的算法,最起码得是正确的。如果连正确都谈不上,再谈别的要求也没有意义。
  算法的正确性是指算法至少应该具有输入、输出和加工处理无歧义性、能正确反映问题的需求,能够得到问题的正确答案。“正确”大体分为以下4个层次:
  1.算法程序没有语法错误。
  2.算法程序对于合法的输入数据能够产生满足要求的输出结果。
  3.算法程序对于非法的输入数据能够得出满足规格说明的结果。
  4.算法程序对于精心选择的,甚至刁难的测试数据都有满足要求的输出结果。
  一般情况下,我们把层次3作为一个算法是否正确的标准。

可读性

  算法设计的另一目的是为了便于阅读、理解和交流。可读性高有助于人们理解算法,晦涩难懂的算法往往隐含错误,不易被发现,并且难于调试和修改。
  我们写代码的目的,一方面是为了让计算机执行,另一个很重要的方面是为了便于他人阅读,让人理解和交流。自己将来也有可能阅读自己的代码,如果可读性不好的话,时间长了自己都不知道写了些什么。由此可见,可读性是算法(包括实现它的代码)好坏的很重要的标准。

健壮性

  一个好的算法应该能对输入数据不合法的情况做合适的处理。当输入数据不合法时,算法也能做出相关处理,而不是产生异常或莫名奇妙的结果。

时间效率高和存储量低

  好的算法还应该具备时间效率高和存储量低的特点。时间效率指的是算法的执行时间,执行时间短的算法效率高。存储量需求指的是算法在执行过程中需要的最大存储空间,主要指算法程序运行时所占用的内存或外部硬盘存储空间。设计算法应该尽量满足时间效率高和存储量低的需求。

算法效率的度量方法

  我们如何度量一个算法的执行时间呢?很简单,我们通过对算法的数据测试,利用计算机的计时功能,来计算不同算法的效率是高还是低。

事后统计方法

  事后统计方法主要是通过设计好的测试程序和数据,利用计算机计时器对不同算法编制的程序的运行时间进行比较,从而确定算法效率的高低。
  但是这种方法具有很大的缺陷。如果我们花费了大量的时间和精力,最后编制出算法之后发现它根本就是很糟糕的,就会造成“竹篮打水一场空”的尴尬。
  除此之外,算法的测试数据设计困难,而且程序运行时间也跟测试数据的规模大小密切相关。计算机的硬件、软件等环境因素有时也会掩盖算法本身的优劣。
  综上所述,我们一般不采纳事后统计方法。

事前分析估算方法

  事前分析估算方法是指在计算机程序编制前依据统计方法对算法进行估算。一个用高级程序语言编写的程序在计算机上运行时所消耗的时间取决于下列因素:
  1.算法采用的策略、方法。
  2.编译产生的代码质量。
  3.问题的输入规模。
  4.机器执行指令的速度。
  第1条当然是算法好坏的根本,第2条要由软件来支持,第4条要看硬件性能。抛开这些与计算机硬件、软件有关的因素,一个程序的运行时间,依赖于算法的好坏问题的输入规模。所谓问题输入规模,就是指输入量的多少。
  我们还是拿1-100求和这个问题举例子。

1
2
3
4
5
6
// 算法1:循环求和
int i, sum = 0; // 执行1次
for (i = 1; i <= 100; i++) { // 执行(n+1)次
sum += i; // 执行n次
}
printf("%d\n", sum); // 执行1次
1
2
3
4
// 算法2:高斯求和
int n = 100, sum = 0; // 执行1次
sum = (1 + n) * n / 2; // 执行1次
printf("%d\n", sum); // 执行1次

  循环求和算法执行了[1+(n+1)+n+1]次=(2n+3)次,而高斯求和算法执行了(1+1+1)次=3次。事实上,两个算法的第一条和最后一条语句是一样的,我们关注的代码其实是中间的部分。我们把循环看作一个整体,忽略头尾循环判断的开销,那么这两个算法其实就是n次与1次的差距。
  我们不关心编写程序使用了什么语言,也不关心这些程序将会在什么样的计算机中运行,我们只关心它所实现的算法。在分析程序的运行时间时,最重要的是把程序看成是独立于程序设计语言的算法或一系列步骤。
在这里插入图片描述
  由上图可以明显看出,随着n的不断增大,它们在时间效率上的差异也就越来越大。

函数的渐进增长

  给定两个函数f(n)和g(n),如果存在一个整数N,使得对于所有的n>N,f(n)总是比g(n)大,那么,我们说f(n)的增长渐进快于g(n)。也就是说,输入规模n在没有限制的情况下,只要超过一个数值N,这个函数就总是大于另一个函数,我们称函数是渐进增长的。
  判断一个算法的效率时,函数中的常数和其他次要项常常可以忽略,而更应该关注主项(最高阶项)的阶数。某个算法,随着n的增大,它会越来越优于另一算法,或者越来越差于另一算法。

时间复杂度

定义

  在进行算法分析时,语句总的执行次数T(n)是关于问题规模n的函数,进而分析T(n)随n的变化情况并确定T(n)的数量级。算法的时间复杂度,也就是算法的时间量度,记作:T(n)=O(f(n))。它表示随问题规模n的增大,算法执行时间的增长率和f(n)的增长率相同,称作算法的渐进时间复杂度,简称为时间复杂度。其中f(n)是问题规模n的某个函数。
  这样用大写O()来体现算法时间复杂度的记法,我们称之为大O记法。
  一般情况下,随着n的增大,T(n)增长最慢的算法为最优算法。

推导大O阶方法

  1.用常数1取代运行时间中的所有加法常数。
  2.在修改后的运行次数函数中,只保留最高阶项。
  3.如果最高阶项存在且不是1,则去除与这个项相乘的函数。
  经过上述3步运算,得到的结果就是大O阶。

常数阶

  我们还是以高斯求和算法为例。

1
2
3
int n = 100, sum = 0;           // 执行1次
sum = (1 + n) * n / 2; // 执行1次
printf("%d\n", sum); // 执行1次

  这个算法的运行次数函数是f(n)=3。根据推导大O阶的方法,第1步,我们把常数项3改为1。它没有最高阶项,所以这个算法的时间复杂度为O(1)。
  这种与问题的大小无关(n的多少),执行时间恒定的算法,我们称之为具有O(1)的时间复杂度,又叫常数阶。不管这个常数是多少,我们都记作O(1),括号里不能是其他任何数字。
  对于分支结构而言,无论是真还是假,执行的次数都是恒定的,不会随着n的变大而发生变化,所以单纯的分支结构(不包含在循环结构中),其时间复杂度也是O(1)。

线性阶

  下面这段代码,它的循环的时间复杂度为O(n),因为循环体中的代码执行了n次。

1
2
3
4
int i;
for (i = 0; i < n; i++) {
/* 时间复杂度为O(1)的程序步骤序列 */
}

对数阶

  我们再来看一段代码:

1
2
3
4
5
int count = 1;
while (count > n) {
count = count * 2;
/* 时间复杂度为O(1)的程序步骤序列 */
}

  由于每次count乘以2之后,就距离n更近了一分,也就是说,有多少个2相乘后大于n,则会退出循环。由$$2^x=n$$可得$$x=log_2n.$$所以这个循环的时间复杂度为O(logn)。

平方阶

  下面的例子是一个循环嵌套:

1
2
3
4
5
6
int i, j;
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++) {
/* 时间复杂度为O(1)的程序步骤序列 */
}
}

  刚才我们分析过,内循环的时间复杂度为O(n)。而对于外层的循环,其实就是内层的时间复杂度为O(n)的语句再循环n次。所以这段代码的时间复杂度为O(n^2^)。
  我们对上面的代码稍作修改:

1
2
3
4
5
6
int i, j;
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
/* 时间复杂度为O(1)的程序步骤序列 */
}
}

  在这段代码中,外循环的循环次数改为了m,那么时间复杂度就变为O(m×n)。
  我们可以总结得出:循环的时间复杂度=循环体的复杂度×该循环运行的次数
  我们再来看一个循环嵌套:

1
2
3
4
5
6
int i, j;
for (i = 0; i < n; i++) {
for (j = i; j < n; j++) {
/* 时间复杂度为O(1)的程序步骤序列 */
}
}

  当i=0时,内循环执行了n次;当i=1时,内循环执行了(n-1)次……当i=n-1时,内循环执行了一次。所以有总的执行次数为$$n+(n-1)+(n-2)+…+1=\frac{n(n+1)}{2}=\frac{n^2}{2}+\frac{n}{2}.$$
  用推导大O阶的方法,最终这段代码的时间复杂度为O(n^2^)。
  接下来我们来看一段相对复杂的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void function(int count) {
int j;
for (j = count; j < n; j++) {
/* 时间复杂度为O(1)的程序步骤序列 */
}
}

n++;
function(n);
int i, j;
for (i = 0; i < n; i++) {
function(i);
}
for (i = 0; i < n; i++) {
for (j = i; j < n; j++) {
/* 时间复杂度为O(1)的程序步骤序列 */
}
}

  函数function()的时间复杂度为O(n)。为了方便,我们把代码的下半部分单独拿出来,注释上每条语句的执行次数。

1
2
3
4
5
6
7
8
9
10
11
n++;                              // 执行次数为1
function(n); // 执行次数为n
int i, j;
for (i = 0; i < n; i++) { // 执行次数为n×n
function(i);
}
for (i = 0; i < n; i++) { // 执行次数为n(n+1)/2
for (j = i; j < n; j++) {
/* 时间复杂度为O(1)的程序步骤序列 */
}
}

  由上面的分析可得$$f(n)=1+n+n^2+\frac{n(n+1)}{2}=\frac{3}{2}n^2+\frac{3}{2}n+1.$$根据推导大O阶的方法,最终这段代码的时间复杂度也是O(n^2^)。

常见的时间复杂度

  常见的时间复杂度如下表所示。
执行次数函数|阶|非正式术语
——|——|——
12|O(1)|常数阶
2n+3|O(n)|线性阶
3n^2^+2n+1|O(n^2^)|平方阶
5log2n+20|O(logn)|对数阶
2n+3nlog2n+19|O(nlogn)|nlogn阶
6n^3^+2n^2^+3n+4|O(n^3^)|立方阶
2^n^|O(2^n^)|指数阶

  常用的时间复杂度所耗费的时间从小到大依次是:O(1)<O(logn)<O(n)<O(nlogn)<O(n^2^)<O(n^3^)<O(2^n^)<O(n!)<O(n^n^)。
  O(n^3^)之后的时间复杂度,过大的n都会使结果变得不现实。尤其是指数阶和阶乘阶等,除非n很小,否则就是噩梦般的运行时间。这种时间复杂度是不切实际的,我们一般不去讨论它。

最坏情况与平均情况

  比如说我们查找一个有n个随机数字数组中的某个数字,最好的情况是第一个数字就是我们要找的数字,那么算法的时间复杂度为O(1)。最坏的情况就是我们要找的数字在数组的最后一个位置上,那么算法的时间复杂度就是O(n)。这就像我们在日常生活中找东西,运气好的话我们可以一下找到,不会花费太长时间;运气不好的话,我们怎么也找不到,花费的时间也是相当长。
  最坏情况运行时间是一种保证,那就是运行时间将不会再长了。在应用中,这是一种最重要的需求,通常,除非特别指定,我们提到的运行时间都是最坏情况的运行时间。
  平均运行时间是所有情况中最有意义的,因为它是期望的运行时间。从概率的角度看,这个数字在每一个位置的可能性是相同的,所以平均的查找时间为n/2次后发现这个目标元素。
  一般在没有特殊说明的情况下,时间复杂度指最坏时间复杂度。

空间复杂度

  算法的空间复杂度通过计算算法所需的存储空间实现,算法空间复杂度的计算公式记作S(n)=O(f(n)),其中,n为问题的规模,f(n)为语句关于n所占存储空间的函数。
  我们在写代码时,可以用空间来换取时间。
  一般情况下,一个程序在机器上执行时,除了需要存储程序本身的指令、常数、变量和输入数据外,还需要存储对数据操作的存储单元。若输入数据所占空间只取决于问题本身,和算法无关,这样只需要分析该算法在实现时所需的辅助单元即可。若算法执行时所需的辅助空间相对于输入数据量而言是个常数,则称此算法为原地工作,空间复杂度为O(1)。

自学数据结构与算法——绪论

参考资料:《大话数据结构》,作者程杰,清华大学出版社

什么是数据结构

  数据结构是一门研究非数值计算的程序设计问题中的操作对象,以及它们之间的关系和操作等相关问题的学科。这里不得不提一个著名的公式:程序设计=数据结构+算法
  在说数据结构之前,我们应该先要了解什么是数据。数据是描述客观事物的符号,是计算机中可以操作的对象,是能被计算机识别,并输入给计算机处理的符号集合。数据包括整型、实型等数值类型,也包括字符、声音、图像、视频等非数值类型。也就是说,这里所说的数据其实就是符号,他们具备两个前提:可以输入到计算机中能被计算机程序处理
  数据元素是组成数据的、有一定意义的基本单位,在计算机中通常作为整体处理,也被称为记录。一个数据元素可以由若干个数据项组成。举个简单的例子,在人类中,人就是数据元素,而人的眼睛、耳朵、鼻子、手、脚等就是数据项,人的姓名、性别、年龄、联系方式等也可以是数据项。具体有哪些数据项,要视系统而决定。数据项是数据不可分割的最小单位
  数据对象是性质相同的数据元素的集合,是数据的子集。
在这里插入图片描述
  以上说的这么多都属于数据的范畴,接下来我们来说一下什么是结构。
  简单来说,结构就是关系。而严格来说,结构是指各个组成部分相互搭配和排列的方式。不同数据元素之间不是独立的,而是存在特定的关系。
  说了这么多,数据结构就是相互之间存在一种或多种特定关系的数据元素的集合。在计算机中,数据元素并不是孤立、杂乱无序的,而是具有内在联系的数据集合。数据元素之间存在的一种或多种特定关系,也就是数据的组织形式。

逻辑结构和物理结构

  数据结构分为逻辑结构物理结构
在这里插入图片描述
  逻辑结构是指数据对象中数据元素之间的相互关系,分为以下四种:
  1.集合结构:集合结构中的数据元素除了同属于一个集合外,它们之间没有其它关系。它类似于数学中的集合。如下图所示。
在这里插入图片描述
  2.线性结构:线性结构中的数据元素之间是一对一的关系。如下图所示。
在这里插入图片描述
  3.树形结构:树型结构中的数据元素之间存在一种一对多的层次关系。如下图所示。
在这里插入图片描述
  4.图形结构:图形结构的数据元素是多对多的关系。如下图所示。
在这里插入图片描述
  说完了逻辑结构,我们再来说一说物理结构(也叫存储结构)。物理结构是指数据的逻辑结构在计算机中的存储形式。它分为以下两种结构:
  1.顺序存储结构:是把数据元素存放在地址连续的存储单元里,其数据间的逻辑关系和物理关系是一致的。数组就是这样的顺序存储结构。顺序存储结构如下图所示。
在这里插入图片描述
  2.链式存储结构:是把数据元素存放在任意的存储单元里,这组存储单元可以是连续的,也可以是不连续的。数据元素的存储关系并不能反映其逻辑关系,因此需要用一个指针存放数据元素的地址,通过地址就可以找到相关联数据元素的位置。链式存储结构如下图所示。
在这里插入图片描述

抽象数据类型

  数据类型是指一组性质相同的值的集合及定义在此集合上的一些操作的总称。数据类型按照值的不同进行划分。在高级语言中,每个变量、常量和表达式都有各自的取值范围。类型用来说明变量或表达式的取值范围和所能进行的操作。
  在C语言中,按照取值的不同,数据类型可以分为两类。一类是原子类型,它是不可以再分解的基本类型,包括整型、实型、字符型等。另一类是结构类型,它由若干个类型组合而成,是可以再分解的,比如整型数组就是由若干个整型数据组成的。
  抽象数据类型(Abstract Data Type, ADT)是指一个数学模型及定义在该模型上的一组操作。抽象数据类型的定义仅取决于它的一组逻辑特性,而与其在计算机内部如何表示和实现无关。“抽象”的意义在于数据类型的数学抽象特性。事实上,抽象数据类型体现了程序设计中问题分解、抽象和信息隐藏的特性
  下面给出了描述抽象数据类型的标准格式。

1
2
3
4
5
6
7
8
9
10
11
12
ADT 抽象数据类型名
Data
数据元素之间逻辑关系的定义
Operation
操作1
初始条件
操作结果描述
操作2
……
操作n
……
endADT

Python数据分析基础之数据库(3)

查询一个表并将输出写入CSV文件

  数据表中有了数据之后,最常见的下一个步骤就是使用查询从表中取出一组数据进行分析。使用Python脚本可以从数据表中查询出一组特定记录。
  下面的代码会从Suppliers数据表中查询出Cost列中的值大于700.00的所有记录,并将这些记录所有列中的值输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env python3

import csv
import MySQLdb
import sys

# CSV输出文件的路径和文件名
output_file = sys.argv[1]

# 连接MySQL数据库
con = MySQLdb.connect(host='localhost', port=3306, db='my_suppliers', user='root', passwd='## your password ##')
c = con.cursor()

# 创建写文件的对象,并写入标题行
filewriter = csv.writer(open(output_file, 'w', newline=''), delimiter=',')
header = ['Supplier Name', 'Invoice Number', 'Part Number', 'Cost', 'Purchase Date']
filewriter.writerow(header)

# 查询Suppliers表,并将结果写入CSV输出文件
c.execute("""SELECT *
FROM Suppliers
WHERE Cost > 700.0;""")
rows = c.fetchall()
for row in rows:
filewriter.writerow(row)

  在命令行窗口中运行这个脚本,得到CSV输出文件。
在这里插入图片描述

更新表中记录

  更新表中记录的方法与之前的插入新记录的方法类似,只是SQL语句有所变化,将INSERT语句变为了UPDATE语句。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python3

import csv
import MySQLdb
import sys

# CSV输入文件的路径和文件名
input_file = sys.argv[1]

# 连接MySQL数据库
con = MySQLdb.connect(host='localhost', port=3306, db='my_suppliers', user='root', passwd='## your password ##')
c = con.cursor()

# 读取CSV文件并更新特定的行
file_reader = csv.reader(open(input_file, 'r', newline=''), delimiter=',')
header = next(file_reader, None)
for row in file_reader:
data = []
for column_index in range(len(header)):
data.append(str(row[column_index]).strip())
print(data)
c.execute("""UPDATE Suppliers SET Cost=%s, Purchase_Date=%s WHERE Supplier_Name=%s;""", data)
con.commit()

# 查询Suppliers表
c.execute("SELECT * FROM Suppliers")
rows = c.fetchall()
for row in rows:
output = []
for column_index in range(len(row)):
output.append(str(row[column_index]))
print(output)

  在UPDATE语句中,必须指定想要更新哪一条记录和哪一个列属性。在这里,我们想为一组特定的Supplier Names更新Cost值和Purchase Date值。UPDATE语句中需要几个值,就需要几个%s占位符表示出查询中的值的位置,CSV输入文件中数据的顺序也要同查询中属性的顺序一样。
  现在我们新建一个CSV输入文件:
在这里插入图片描述
  在命令行窗口中运行这个脚本,得到输出结果如下:
在这里插入图片描述
  输出结果的前两行展示了来自于CSV输入文件中除标题行之外的两行数据。在这两行下面,输出展示了执行更新之后从数据表中取出的12行数据,每行数据占一行。
  下面我们来确认一下MySQL数据表中与Supplier X和Supplier Y相关的8行数据是否已被更新。打开MySQL命令行客户端,输入命令SELECT * FROM Suppliers,然后按Enter键。
在这里插入图片描述
  下面的表格是更新后的数据表。这里我截取了本次和上一次的数据表,为的是有一个明显的对比。我们看到,Supplier X和Supplier Y相关的8行记录已经被更新为CSV输入文件中提供的数据。

Python数据分析基础之数据库(2)

  上一篇博客主要讲了使用Python的内置模块sqlite3来创建内存数据库,这篇博客以及之后的博客主要讲MySQL数据库。

准备工作

  我们需要安装Python的MySQLdb扩展包,在Python 2中是MySQL-python,在Python 3中是mysqlclient。我使用的是Anaconda,这个扩展包随着安装Anaconda的时候就捆绑安装好了。
  我们还需要安装MySQL。msi版安装教程可以参考我之前写的博客:《MySQL Community 8.0.19.0 msi版安装教程》,zip版安装教程请自行查阅。

在MySQL中创建新数据库

  打开MySQL 8.0 Command Line Client,输入root密码,进入MySQL。
在这里插入图片描述
  输入命令SHOW DATABASES;,然后按Enter键,我们可以看到,在MySQL数据库系统中已经有了4个数据库。这些数据库使MySQL数据库系统能够运行,并包含系统用户的权限信息。注意:在输入命令SHOW DATABASES;的时候,必须要输入分号
在这里插入图片描述
  接下来我们来创建自己的数据库。输入命令CREATE DATABASE my_suppliers;,然后按Enter键。我们可以再运行一次SHOW DATABASE;命令,这时我们可以看到我们刚刚创建的my_suppliers数据库。
在这里插入图片描述
  要使用my_suppliers数据库,必须先选择它。输入命令USE my_suppliers;,然后按Enter键。这样我们就已经选择了my_suppliers数据库。
在这里插入图片描述

  接下来我们创建一个数据表Suppliers。输入以下命令,然后按Enter键:

1
2
3
4
5
6
CREATE TABLE IF NOT EXISTS Suppliers
(Supplier_Name VARCHAR(20),
Invoice_Number VARCHAR(20),
Part_Number VARCHAR(20),
Cost FLOAT,
Purchase_Date DATE);

在这里插入图片描述

  我们来解释一下上面的命令。
  如果数据库中不存在数据表Suppliers,这个命令就创建数据表Suppliers。这个表有5个列。其中,前3个列是可变字符VARCHAR型字段。20表示为这个字段中的数据分配20个字符。如果输入这个字段的数据大于20个字符,那么数据将被截断。如果数据少于20个字符,那么这个字段就为数据分配一个更小的空间。
  第4列是一个浮点数FLOAT字段。浮点数字段保存浮点数近似值。在本例中,第4列包含的是货币值,所以可以用NUMERIC定点确定值类型字段替代FLOAT类型字段。不使用FLOAT,也可以使用NUMERIC(11, 2)。其中11是数值的精度,也就是为数值保存的数位总数(包括小数点后面的位数)。2是小数位数,即小数点后面的数位总数。
  第5列是一个日期DATE字段。DATE字段用来保存日期,形式为YYYY-MM-DD,没有时间部分。
  为了确保数据表创建正确,输入命令DESCRIBE Suppliers;,然后按Enter键。
在这里插入图片描述
  我们看到了一个表格,其中列出了创建的列的名称,每列的数据类型以及列中的值是否可以为NULL
  接下来我们创建一个新用户。输入命令CREATE USER 'username'@'localhost' IDENTIFIED BY 'secret_password';(请注意用要使用的用户名替换username,用自己的密码替换secret_password),然后按Enter键。
  输入以下两条命令,然后在每条命令后面按Enter键,来向新用户授于所有权限。同样地,用要使用的用户名替换下面的username

1
2
GRANT ALL PRIVILEGES ON my_suppliers.* TO 'username'@'localhost';
FLUSH PRIVILEGES;

  这样我们就可以同本地主机中的my_suppliers数据库中的Suppliers表进行交互了。
在这里插入图片描述

向表中插入新记录

  下面的Python脚本会将数据从CSV文件中插入到我们的数据表,然后展示表中的数据。我们还是以supplier_data.csv为例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!/usr/bin/env python3

import csv
import MySQLdb
import sys
from datetime import datetime, date

# CSV输入文件的路径和文件名
input_file = sys.argv[1]

# 连接MySQL数据库
con = MySQLdb.connect(host='localhost', port=3306, db='my_suppliers', user='root', passwd='## your password ##')
c = con.cursor()

# 向Suppliers表中插入数据
file_reader = csv.reader(open(input_file, 'r', newline=''))
header = next(file_reader)
for row in file_reader:
data = []
for column_index in range(len(header)):
if column_index < 4:
data.append(str(row[column_index]).lstrip('$').replace(',', '').strip())
else:
a_date = datetime.date(datetime.strptime(str(row[column_index]), '%m/%d/%Y'))
a_date = a_date.strftime('%Y-%m-%d')
data.append(a_date)
print(data)
c.execute("""INSERT INTO Suppliers VALUES (%s, %s, %s, %s, %s);""", data)
con.commit()
print("")

# 查询Suppliers表
c.execute("SELECT * FROM Suppliers")
rows = c.fetchall()
for row in rows:
row_list_output = []
for column_index in range(len(row)):
row_list_output.append(str(row[column_index]))
print(row_list_output)

  第12行代码使用MySQLdb模块的connect()方法连接my_suppliers,即前面我们创建的MySQL数据库。在连接时,我们需要指定一些通用参数。host是数据库所在的机器的主机名,在这里,MySQL服务器保存在我们的计算机上,所以hostlocalhostport是MySQL服务器的TCP/IP连接端口号,这里我们使用的端口号是默认的端口号3306。db是想要连接的数据库名称。user是进行数据库连接的用户的用户名,passwd即为密码。在这里我们作为“root”用户进行连接,使用的密码就是在安装MySQL服务器时创建的密码。我们此前新建了一个新用户,如果想使用新用户,我们只需要把用户名和密码相应地替换掉即可。
  我们在命令行窗口中运行这个脚本,得到输出结果。
在这里插入图片描述
  这个输出结果证明了数据被成功地加载到了Suppliers表中,并被成功读出。
  我们打开MySQL命令行客户端,输入命令SELECT * FROM Suppliers;,可以看到一个表格,其中列出了Suppliers数据表中所有的列以及每列中的12行数据。
在这里插入图片描述

MySQL Community 8.0.19.0 msi版安装教程

1.在MySQL官网中下载msi文件

下载地址:https://dev.mysql.com/downloads/windows/installer/8.0.html
在这里插入图片描述如上图所示,下载第二个即可。这里需要说明的一点是,虽然这里的MySQL Installer是32位,但会同时安装32位和64位二进制文件。

2.打开下载好的msi文件进行安装

2.1 选择Custom,点击Next。
在这里插入图片描述
2.2 将MySQL Server 8.0.19 - X64移动到右侧,如下图所示。点击Next。
在这里插入图片描述
2.3 点击Execute。
在这里插入图片描述
2.4 短暂等待后,点击Next。
在这里插入图片描述
2.5 点击Next。
在这里插入图片描述
2.6 点击Next。
在这里插入图片描述
2.7 点击Next。
在这里插入图片描述
2.8 点击Next。
在这里插入图片描述
2.9 设置root密码,root密码一定要牢记!!!记性不好的可以把它存在手机的备忘录里哦~设置完成后点击Next。
在这里插入图片描述
2.10 点击Next。
在这里插入图片描述
2.11 点击Execute。
在这里插入图片描述
短暂等待之后,这七个白色的小圆圈都变成绿色了,如下图所示。点击Finish。
在这里插入图片描述
2.12 点击Next。
在这里插入图片描述
2.13 点击Finish完成安装。
在这里插入图片描述

3.检验是否安装成功

打开开始菜单,我们会发现MySQL文件夹,如下图所示。
在这里插入图片描述

打开MySQL 8.0 Command Line Client,输入此前设置的root密码,如下图所示。这样即为安装成功。
在这里插入图片描述

Python数据分析基础之数据库(1)

  SQL(Structured Query Language),表示结构化查询语言,是一组应用广泛的与数据库交互的命令。要学习如何使用Python同数据库交互,首先我们要有一个数据库,并且数据库中要有一张充满数据的表。有两种资源可供我们选择:一是Python的内置模块sqlite3,它可以创建内存数据库,我们不用下载安装专门的数据库软件;二是MySQL、PostgreSQL或Oracle这样的常用数据库系统。

使用sqlite3模块创建数据库

  首先,我们使用sqlite3模块直接在Python代码中创建一个内存数据库以及充满了数据的表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env python3

import sqlite3

# 创建SQLite3内存数据库
# 创建带有4个属性的sales表
con = sqlite3.connect(':memory:')
query = """CREATE TABLE sales
(customer VARCHAR(20),
product VARCHAR(40),
amount FLOAT,
date DATE);"""
con.execute(query)
con.commit()

# 在表中插入几行数据
data = [('Richard Lucas', 'Notepad', 2.50, '2014-01-02'),
('Jenny Kim', 'Binder', 4.15, '2014-01-15'),
('Svetlana Crow', 'Printer', 155.75, '2014-02-03'),
('Stephen Randolph', 'Computer', 679.40, '2014-02-20')]
statement = "INSERT INTO sales VALUES(?, ?, ?, ?)"
con.executemany(statement, data)
con.commit()

# 查询sales表
cursor = con.execute("SELECT * FROM sales")
rows = cursor.fetchall()

# 计算查询结果中行的数量
row_counter = 0
for row in rows:
print(row)
row_counter += 1
print('Number of rows: %d' % (row_counter))

  第3行代码导入了sqlite3模块。为了使用这个模块,首先必须创建一个代表数据库的连接对象。第7行代码创建了连接对象con来代表数据库。专用名称':memory:'在内存中创建了一个数据库。如果想要数据库持久化,就需要提供另外的字符串。
  第8-12行代码使用三重双引号创建了一个多行字符串,并将这个字符串赋给变量query。这个字符串是一个SQL命令,可以在数据库中创建一个名为sales的表。sales表有4个属性:customer, product, amountdatecustomerproduct都是变长字符型字段,最大字符数分别为20和40。amount是一个浮点数型字段。date是一个日期型字段。
  第13行代码使用连接对象的execute()方法执行包含在变量query中的SQL命令,在内存数据库中创建sales表。第14行代码使用连接对象的commit()方法将修改提交(也就是保存)到数据库。对数据库做出修改时必须使用commit()方法保存修改。
  第21行代码与第8行代码类似,创建了一个字符串并赋给变量statement。这行代码中的字符串是另一个SQL命令,INSERT语句可以将data中的数据行插入sales表。?在这里用作占位符,表示想在SQL命令中使用的值。然后,在连接对象的execute()executemany()方法中,需要提供一个包含4个值的元组,元组中的值会按位置替换到SQL命令中。
  第26行代码使用连接对象的execute()方法运行一条SQL命令,并将命令结果赋给一个光标变量cursor。第27行代码执行了fetchall()方法,返回第26行代码中的SQL命令的数据行的列表。每行数据都是一个元组,所以rows是一个元组列表。在这种情况下,sales表中包含4行数据,SQL命令从sales表中选择所有的行,所以rows是一个含有4个元组的列表。
  运行结果如下:

1
2
3
4
5
('Richard Lucas', 'Notepad', 2.5, '2014-01-02')
('Jenny Kim', 'Binder', 4.15, '2014-01-15')
('Svetlana Crow', 'Printer', 155.75, '2014-02-03')
('Stephen Randolph', 'Computer', 679.4, '2014-02-20')
Number of rows: 4

向表中插入新记录

  下面的这个脚本将创建一个数据表,向表中插入CSV文件中的数据,然后展示表中的数据。这里使用的CSV文件是前面用到过的supplier_data.csv文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#!/usr/bin/env python3

import csv
import sqlite3
import sys

# CSV输入文件的路径和文件名
input_file = sys.argv[1]

# 创建SQLite3内存数据库
# 创建带有5个属性的Suppliers表
con = sqlite3.connect('E:\\python_pycharm\\Python数据分析基础\\第4章 数据库\\Suppliers.db')
c = con.cursor()
create_table = """CREATE TABLE IF NOT EXISTS Suppliers
(Supplier_Name VARCHAR(20),
Invoice_Number VARCHAR(20),
Part_Number VARCHAR(20),
Cost FLOAT,
Purchase_Date DATE);"""
c.execute(create_table)
con.commit()

# 读取CSV文件
# 向Suppliers表中插入数据
file_reader = csv.reader(open(input_file, 'r'), delimiter=',')
header = next(file_reader, None)
for row in file_reader:
data = []
for column_index in range(len(header)):
data.append(row[column_index])
print(data)
c.execute("INSERT INTO Suppliers VALUES (?, ?, ?, ?, ?);", data)
con.commit()
print('')

# 查询Suppliers表
output = c.execute("SELECT * FROM Suppliers")
rows = output.fetchall()
for row in rows:
output = []
for column_index in range(len(row)):
output.append(str(row[column_index]))
print(output)

  第12行代码为本地数据库Suppliers.db创建连接。这是一个持久化数据库,即使重启计算机,这个数据库也不会被删除。
  第25-33行代码从CSV输入文件中读取要加载到数据库中的数据,并对输入文件中的每行数据执行一条SQL语句,将数据插入到数据库的表中。第32行代码将每行数据加载到数据表中。第33行代码使用commit()方法将修改提交到数据库。
  第37-43行代码从数据表Suppliers中选择所有数据,并将输出打印到命令行窗口。第37-38行代码执行一条SQL语句,从Suppliers表中选择所有数据,并将output中的所有行读入变量rows
  在命令行窗口中运行这个脚本,得到输出结果如下:
在这里插入图片描述
  输出的第一部分是从CSV文件中解析出的数据行,输出的第二部分是同样的行,但它是从sqlite数据表中提取出来的。

更新表中记录

  我们可以使用CSV输入文件向数据表中添加新行,也可以使用从CSV输入文件中读取数据的技术来更新表中已有的行。在这种情况下,SQL语句有所改变,从INSERT语句变成了UPDATE语句。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env python3

import csv
import sqlite3
import sys

# CSV输入文件的路径和文件名
input_file = sys.argv[1]

# 创建SQLite3内存数据库
# 创建带有4个属性的sales表
con = sqlite3.connect(':memory:')
query = """CREATE TABLE IF NOT EXISTS sales
(customer VARCHAR(20),
product VARCHAR(40),
amount FLOAT,
date DATE);"""
con.execute(query)
con.commit()

# 向表中插入几行数据
data = [('Richard Lucas', 'Notepad', 2.50, '2014-01-02'),
('Jenny Kim', 'Binder', 4.15, '2014-01-15'),
('Svetlana Crow', 'Printer', 155.75, '2014-02-03'),
('Stephen Randolph', 'Computer', 679.40, '2014-02-20')]
for tuple in data:
print(tuple)
statement = "INSERT INTO sales VALUES(?, ?, ?, ?)"
con.executemany(statement, data)
con.commit()

# 读取CSV文件并更新特定的行
file_reader = csv.reader(open(input_file, 'r'), delimiter=',')
header = next(file_reader, None)
for row in file_reader:
data = []
for column_index in range(len(header)):
data.append(row[column_index])
print(data)
con.execute("UPDATE sales SET amount=?, date=? WHERE customer=?;", data)
con.commit()

# 查询sales表
cursor = con.execute("SELECT * FROM sales")
rows = cursor.fetchall()
for row in rows:
output = []
for column_index in range(len(row)):
output.append(str(row[column_index]))
print(output)

  第40行代码,UPDATE语句代替了原来的INSERT语句。在UPDATE语句中,我们必须指定想更新哪一条记录和哪一个列属性。在这里,我们想为一组特定的customer更新amount值和date值。像前面的示例一样,UPDATE语句也需要占位符表示查询中的值的位置,CSV输入文件中数据的顺序也要同查询中属性的顺序一样。
  我们新建一个CSV输入文件:
在这里插入图片描述
  在命令行窗口中运行这个脚本,得到下面的输出结果。
在这里插入图片描述
  这个输出首先展示了数据库中初始的4行数据,接下来2行是要更新到数据库中的2行数据。在这2行下面,输出还展示了执行更新之后从数据表中取出的4行数据。

Python数据分析基础之Excel文件(6)

  这一篇博客主要讲一下处理多个工作簿。
  之前我们已经创建了sales_2013.xlsx工作簿。在这里,我们再创建两个新的工作簿sales_2014.xlsx和sales_2015.xlsx,并且修改其中的工作表名称和日期数据。
在这里插入图片描述)在这里插入图片描述)在这里插入图片描述)在这里插入图片描述)在这里插入图片描述)在这里插入图片描述

工作表计数以及每个工作表中的行列计数

  如果想知道一个文件夹中工作簿的数量,每个工作簿中工作表的数量,以及每个工作表中的行与列的数量,我们可以编写下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3

import glob
import os
import sys
from xlrd import open_workbook

input_directory = sys.argv[1]

workbook_counter = 0
for input_file in glob.glob(os.path.join(input_directory, '*.xlsx')):
workbook = open_workbook(input_file)
print('Workbook: %s' % os.path.basename(input_file))
print('Number of worksheets: %d' % workbook.nsheets)
for worksheet in workbook.sheets():
print('Worksheet name: ', worksheet.name, '\tRows: ', worksheet.nrows, '\tColumns: ', worksheet.ncols)
workbook_counter += 1
print('Number of Excel workbooks: %d' % (workbook_counter))

  这和之前的内省单个工作簿的代码非常相似。我们在命令行窗口中运行这个脚本,得到下面的输出结果。
在这里插入图片描述

从多个工作簿中连接数据

  使用Python可以将多个工作簿中所有工作表的数据垂直连接成一个输出文件。

1.基础Python

  代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/usr/bin/env python3

import glob
import os
import sys
from datetime import date
from xlrd import open_workbook, xldate_as_tuple
from xlwt import Workbook

input_folder = sys.argv[1]
output_file = sys.argv[2]

output_workbook = Workbook()
output_worksheet = output_workbook.add_sheet('all_data_all_workbooks', cell_overwrite_ok=True)
data = []
first_worksheet = True
for input_file in glob.glob(os.path.join(input_folder, '*.xlsx')):
print(os.path.basename(input_file))
with open_workbook(input_file) as workbook:
for worksheet in workbook.sheets():
if first_worksheet:
header_row = worksheet.row_values(0)
data.append(header_row)
first_worksheet = False
for row_index in range(1, worksheet.nrows):
row_list = []
for column_index in range(worksheet.ncols):
cell_value = worksheet.cell_value(row_index, column_index)
cell_type = worksheet.cell_type(row_index, column_index)
if cell_type == 3:
date_cell = xldate_as_tuple(cell_value, workbook.datemode)
date_cell = date(*date_cell[0:3]).strftime('%m/%d/%Y')
row_list.append(date_cell)
else:
row_list.append(cell_value)
data.append(row_list)
for list_index, output_list in enumerate(data):
for element_index, element in enumerate(output_list):
output_worksheet.write(list_index, element_index, element)
output_workbook.save(output_file)

作者注:在第14行代码中,参考书上的代码并没有’cell_overwrite_ok=True’这一参数,这是我在写代码的时候报错后自己加上去的。
报错信息如下:
Traceback (most recent call last):
File “E:\python_pycharm\Python数据分析基础\第3章 Excel文件\13excel_concat_data_from_multiple_workbook.py”, line 39, in
output_worksheet.write(list_index, element_index, element)
File “D:\Anaconda3\lib\site-packages\xlwt\Worksheet.py”, line 1088, in write
self.row(r).write(c, label, style)
File “D:\Anaconda3\lib\site-packages\xlwt\Row.py”, line 235, in write
StrCell(self.idx, col, style_index, self.parent_wb.add_str(label))
File “D:\Anaconda3\lib\site-packages\xlwt\Row.py”, line 154, in insert_cell
raise Exception(msg)
Exception: Attempt to overwrite cell: sheetname=’all_data_all_workbooks’ rowx=0 colx=0
查阅有关资料后得知,对一个单元格重复操作会引发ReturnsError,这个时候只要在打开文件的时候加上’cell_overwrite_ok=True’即可解决。这应该是书本上的错误。

  在命令行窗口中运行这个代码,得到输出文件。
在这里插入图片描述)在这里插入图片描述

2.pandas

  pandas模块提供了concat()函数来连接数据框。如果想把数据框一个一个地垂直堆叠起来,需要设置参数axis=0;如果想把数据框一个一个地平行连接起来,需要设置参数axis=1。如果我们需要基于某个关键字列连接数据框,可以使用pandas模块的merge()函数。
  使用pandas模块的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python3

import pandas as pd
import glob
import os
import sys

input_path = sys.argv[1]
output_file = sys.argv[2]

all_workbooks = glob.glob(os.path.join(input_path, '*.xlsx'))
data_frames = []
for workbook in all_workbooks:
all_worksheets = pd.read_excel(workbook, sheet_name=None, index_col=None)
for worksheet_name, data in all_worksheets.items():
data_frames.append(data)
all_data_concatenated = pd.concat(data_frames, axis=0, ignore_index=True)
writer = pd.ExcelWriter(output_file)
all_data_concatenated.to_excel(writer, sheet_name='all_data_all_workbooks', index=False)
writer.save()

  在命令行窗口中运行这个脚本,得到输出文件。
在这里插入图片描述

为每个工作簿和工作表计算总数和均值

基础Python

  代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/env python3

import glob
import os
import sys
from datetime import date
from xlrd import open_workbook, xldate_as_tuple
from xlwt import Workbook

input_folder = sys.argv[1]
output_file = sys.argv[2]

output_workbook = Workbook()
output_worksheet = output_workbook.add_sheet('sums_and_averages')
all_data = []
sales_column_index = 3
header = ['workbook', 'worksheet', 'worksheet_total', 'worksheet_average', 'workbook_total', 'workbook_average']
all_data.append(header)
for input_file in glob.glob(os.path.join(input_folder, '*.xlsx')):
with open_workbook(input_file) as workbook:
list_of_totals = []
list_of_numbers = []
workbook_output = []
for worksheet in workbook.sheets():
total_sales = 0
number_of_sales = 0
worksheet_list = []
worksheet_list.append(os.path.basename(input_file))
worksheet_list.append(worksheet.name)
for row_index in range(1, worksheet.nrows):
try:
total_sales += float(str(worksheet.cell_value(row_index, sales_column_index))
.strip('$').replace(',', ''))
number_of_sales += 1
except:
total_sales += 0
number_of_sales += 0
average_sales = '%.2f' % (total_sales / number_of_sales)
worksheet_list.append(total_sales)
worksheet_list.append(float(average_sales))
list_of_totals.append(total_sales)
list_of_numbers.append(float(number_of_sales))
workbook_output.append(worksheet_list)
workbook_total = sum(list_of_totals)
workbook_average = sum(list_of_totals) / sum(list_of_numbers)
for list_element in workbook_output:
list_element.append(workbook_total)
list_element.append(workbook_average)
all_data.extend(workbook_output)

for list_index, output_list in enumerate(all_data):
for element_index, element in enumerate(output_list):
output_worksheet.write(list_index, element_index, element)
output_workbook.save(output_file)

  第21-23行分别创建了3个空列表,list_of_totals用来保存工作簿中所有工作表的销售额总计,list_of_numbers用来保存工作簿的所有工作表中用来计算总销售额的销售额数据个数,workbook_output用来保存要写入输出文件的所有输出列表。
  第27行代码创建了列表worksheet_list,用来保存要保留的所有工作表的信息。
  第28-29行代码将工作簿名称和工作表名称追加到worksheet_list中。同样地,第39-40行代码将销售额总计和均值追加到worksheet_list中,第43行代码将worksheet_list追加到workbook_output中,在工作簿级别保存信息。
  第41-42行代码将工作表的销售额总计和销售额数据个数分别追加到list_of_totalslist_of_numbers中,这样我们可以对所有工作表保存这些值。
  当获得了所有要为工作簿保留的信息之后,就将这些列表扩展到all_data中。这里使用extend()而不是append(),以使workbook_output中的每个列表都会成为all_data中的一个独立元素。在处理完所有工作簿之后,all_data是一个具有9个元素的列表,每个元素都是一个列表。否则,如果使用append()all_data中就会只有3个元素,每个元素都是一个列表的列表。
  在命令行窗口中运行这个脚本,得到输出文件。
在这里插入图片描述