Python数据分析01-argv函数与读取或导出csv文件

前言

这篇笔记的主要参考书是《Python数据分析基础》([美] 克林顿·布朗利(Clinton,W.,Brownley) 著,陈光欣 译),封面如下:

作者在Github上已经放了书中的源代码与数据文件

sys.argv[]函数

sys.argv[]是用来获取命令行输入的参数的,argv的是argument variable的缩写。这个变量其实是一个List列表,其中参数和参数之间空格区分,sys.argv[0]表示代码本身文件路径,而sys.argv[1]表示获取的参数。现在看使用案例。

首选我们在桌面上新建一个test.py文件,输入内容如下所示:

1
2
3
import sys
result = sys.argv[0]
print(result)

现在运行这段代码,如下所示:

1
2
3
C:\Users\20161111>cd Desktop
C:\Users\20161111\Desktop>python test.py
test.py

从上面的结果可以看出来,运行test.py文件返回了一个test.py字符,这是因为sys.argv[0]这个参数本身指的就是这个程序本身,这个程序就是test.py文件。现在更改一下test.py代码,将第2行的result = sys.argv[0]改为result = sys.argv[1:],如下所示:

1
2
3
import sys
result = sys.argv[1:]
print(result)

再次运行,如下所示:

1
2
3
4
5
C:\Users\20161111\Desktop>python test.py Hello, Python
['Hello,', 'Python']
C:\Users\20161111\Desktop>python test.py Hello,Python
['Hello,Python']

从上面看到,当我们输入python test.py Hello, Python时(请注意,Hello,Python之间有一个空格),sys.argv[1:]就会接收输入的参数,并且以空格来进行区分,当用空格进行区分,这就是2个参数,这个列表中就有2个参数。当我们直接输入Hello,python(中间没有空格)时,就是一个参数,这个列表中就只有一个元素。

使用sys.argv[]来读取文件的代码如下所示:

在桌面上新建一个文本文档test_text,再新建一个test.pyPython文件,如下所示:

1
2
3
4
5
6
7
8
9
import sys
input_file = sys.argv[1]
print("Output: ")
filereader = open(input_file, 'r', newline= '')
for row in filereader:
print("{}".format(row.strip()))
filereader.close()

运行结果如下所示:

1
2
3
4
5
6
7
C:\Users\20161111\Desktop>python test.py test_text.txt
Output:
这是第1行;
这是第2行;
这是第3行;
这是第4行;
这是第5行;

还有一种代码,就是使用with,如下所示:

1
2
3
4
5
6
7
8
9
import sys
input_file = sys.argv[1]
print("Output: ")
with open(input_file, 'r', newline= '') as filereader:
for row in filereader:
print("{}".format(row.strip()))
filereader.close()

读取多个文本文件glob

在实际应用过程中,有可能要同时读取多个文件。读取多个文件的实现方式之一就是在命令行中将包含输入文件目录的路径名写在Python脚本之后,如果要使用这种方法,就需要os模块与glob模块,那么完整的代码如下所示,这段代码位于桌面,命令为directory_test.py,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python3
from math import exp, log, sqrt
import re
from datetime import date, time, datetime, timedelta
from operator import itemgetter
import glob
import os
import sys
print("Output: ")
input_file = sys.argv[1]
for input_file in glob.glob(os.path.join(input_file, '*.txt')):
with open(input_file, 'r', newline='') as filereader:
for row in filereader:
print("{}".format(row.strip()))

使用os模块可以使用其提供的与路径相关的函数,例如os.path.join函数可以将一个或多个路径成分连接在一起,在上面的代码中,

glob模块可以找出与特定模式相匹配的所有路径名。它可以将某目录下面跟通配符模式相同的文件放到一个列表中,有了这个函数中,我们想于生成所有文件的列表就不需要使用for循环遍历目录了,而是直接使用glob.glob(path + pattern)的方式进行获取,可以看一下面的案例:

1
2
>>> print(glob.glob(r'.\*.py'))
['.\\12csv_reader_add_header_row.py', '.\\8csv_reader_counts_for_multiple_files.py', '.\\pandas_add_header_row.py']

这段代码表示的内容就是,搜索当前目录下的所有.py文件,并显示出来。

另外一个类似的函数则是iglob,此函数则是一次只检索一个文件,如下所示:

1
2
3
4
5
6
7
>>> f = glob.iglob(r'.\*.py')
>>> for py in f:
... print(py)
...
.\12csv_reader_add_header_row.py
.\8csv_reader_counts_for_multiple_files.py
.\pandas_add_header_row.py

os模块和glob模块组合在一起可以找出符合特定模式的某个文件夹下的所有文件。在这段代码的for循环中使用了os.path.join函数和glob.glob函数来找出符合特定模式的某个文件夹下面的所有文件。指向这个文件夹的路径包含在变量input_file中,这个变量需要在命令行中提供。os.path.join函数将这个文件夹路径和这个文件夹中所有符合特定模式的文件名连接起来,这种特定模式可以由glob.glob函数扩展。
这段代码使用是的模式*.txt来匹配由.txt结尾的所有文件名。

现在来演示一下这个案例。

在桌面上新建一个文件夹,命令为test_directory,在此目录下新建一个文本文件,命名为test_1.txt,输入以下内容:

1
2
3
4
5
6
7
8
This
text
comes
from
a
different
text
file.

在此目录新建第二个文本文件,命名为test_2.txt,输入以下内容:

1
2
3
4
5
这是第2个txt文件;
这是第2个txt文件;
这是第2个txt文件;
这是第2个txt文件;
这是第2个txt文件;

现在运行directory_test.py代码,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
C:\Users\20161111\Desktop>python directory_test.py "test_directory"
Output:
This
text
comes
from
a
different
text
file.
这是第2个txt文件;
这是第2个txt文件;
这是第2个txt文件;
这是第2个txt文件;
这是第2个txt文件;

读取csv文件

csv文件的全称为comma-separated value,即逗号分隔值,这是一种常用的数据存储格式。在Python中专门的csv模块,但这里先用最原始的python代码来实现一下。

非csv模块读取csv文件

在桌面上新建一个名为read_csv.py的Python文件,输入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3
import sys
input_file = sys.argv[1]
output_file = sys.argv[2]
with open(input_file, 'r', newline = "") as filereader:
with open(output_file, 'w', newline = "") as filewriter:
header = filereader.readline() # Read first line as header
header = header.strip()
header_list = header.split(',')
print(header_list)
filewriter.wirte(','.join(map(str,header_list)) + '\n')
for row in filereader:
row = row.strip()
row_list = row.split(',')
print(row_list)
filewriter.write(','.join(map(str, row_list)) + '\n')

在桌面上新建一个test_csv.csv文件,输入以下内容:

1
2
3
header1,header2
test1,test2
123,345

运行结果如下所示:

1
2
3
4
C:\Users\20161111\Desktop>python read_csv.py test_csv.csv "test_out.csv"
['header1', 'header2']
['test1', 'test2']
['123', '345']

现在桌面上就有了一个输出文件,名称为test_out.csv

使用pandas模块读取csv文件

pandas是一个开源的,BSD许可的库,为Python编程语言提供高性能,易于使用的数据结构和数据分析工具。pandas可以用于读取csv文件,在下面的这段代码中,会读取csv文件,然后输出读取的内容,在桌上上新建pandas_test.py文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python3
import sys
import pandas as pd
input_file = sys.argv[1]
output_file = sys.argv[2]
data_frame = pd.read_csv(input_file)
print(data_frame)
data_frame.to_csv(output_file, index=False)

运行结果如下所示:

1
2
3
4
C:\Users\20161111\Desktop>python pandas_test.py test_csv.csv "pandas_test.csv"
header1 header2
0 test1 test2
1 123 345

此时在桌面上生成一个叫pandas_test.csv的文件。

使用csv模块读取csv文件

python内置的csv模块可以读取csv文件,现在在桌上新建一个名为csv_module.py的文件,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3
import sys
import csv
input_file = sys.argv[1]
out_file = sys.argv[2]
with open(input_file, 'r', newline = '') as csv_in_file:
with open(out_file, 'w', newline = '') as csv_out_file:
filereader = csv.reader(csv_in_file, delimiter = ',') # delimiter是指默认分割符,如果导入的文件是csv,其实是不用这个参数的
filewriter = csv.writer(csv_out_file, delimiter = ',')
for row_list in filereader:
print(row_list)
filewriter.writerow(row_list)

现在运行一下这个文件,如下所示:

1
2
3
4
C:\Users\20161111\Desktop>python csv_module.py test_csv.csv "csv_module_test.csv"
['header1', 'header2']
['test1', 'test2']
['123', '345']

现在在桌上上生成了一个名为csv_module_test.csv的csv文件。

读取特定的行

数据处理过程中通常会有一项操作是要把满足某个条件的某行数据给挑出来,通常有这几种方法:

  • 行中的值满足某个条件;
  • 行中的值属于某个集合
  • 行中的值匹配于某个模式(正则表达式)

满足某条件行的通用代码结构

现在看一下以下的这段虚拟代码:

1
2
3
4
5
for row in filereader:
***if value in row meets some business rule or set of rules:***
do something
else:
do something else

这段代码描述的是在输入文件中筛选出特定行的通用代码结构。

在平时写代码过程中只需要修改***中的代码,以使脚本满足具体需要即可。

行中的值满足某个条件

有数据分析的实现情况中,有的时候有这种需要:当行中的值满足一个具体条件时需要保留些行。例如,我们可能希望在数据集中保留那些成本高于某个阈值的行,或者希望保留所有购买日期在一个具体日期之前的行。

在这种情况下,我们就可以检验行中的值是否满足具体的条件,然后筛选出满足条件的行。现在我们来描述这么一个案例:

保留某个供应商名字为Suuplier Z的行,或者是成本大于600的行,并将结果写入某个文件中。

使用csv模块某列-索引方法

在csv文中选取特定列的一种方法就是使用列的索引值来提取。例如,如果想提取数据的第1列和最后1列,就使用row[0]row[-1]来提取。

在下面的这个案例中,我们只想提取供应商姓名和成本这两列,就要使用索引值来聚会这两列。

现在我们在桌面上新建一个名为select_meets_condition.py的文件,输入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python3
import csv
import sys
input_file = sys.argv[1]
output_file = sys.argv[2]
my_columns = [0, 3]
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)
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)

现在新建一个csv文件,这个文件就是我们要使用数据文件,把它命名为supplier_data_unnecessary_header_footer,内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
I don't care about this row,,,,
I don't care about this row,,,,
I don't care about this row,,,,
Supplier Name,Invoice Number,Part Number,Cost,Purchase Date
Supplier X,001-1001,2341,$500.00,1/20/14
Supplier X,001-1001,2341,$500.00,1/20/14
Supplier X,001-1001,5467,$750.00,1/20/14
Supplier X,001-1001,5467,$750.00,1/20/14
Supplier X,50-9501,7009,$250.00,1/30/14
Supplier X,50-9501,7009,$250.00,1/30/14
Supplier X,50-9501,6650,$125.00,2/3/14
Supplier X,50-9501,6650,$125.00,2/3/14
Supplier X,920-4803,3321,$615.00,2/3/14
Supplier X,920-4803,3321,$615.00,2/10/14
Supplier X,920-4803,3321,$615.00,2/17/14
Supplier X,920-4803,3321,$615.00,2/24/14
I don't want this row either,,,,
I don't want this row either,,,,
I don't want this row either,,,,

用Excel打开就是下面的这个样子:

现在解释一下源代码:

  1. 在代码中我们使用了my_columns = [0, 3]这个变量,0与3就是我们要提取的列的位置,也就是对应的是Supplier NameCost这两列。
  2. 代码中有2个for循环,对于输入的文件,每一行都要执行这些代码。在第1个for循环中,创建了一个空列表变量row_list_output。这个变量保存的是,每行中需要保留的值。
  3. for index_value in my_columns这个语句是第2个for循环语句,在my_cloumns中的各个索引值之间进行迭代。
  4. row_list_output.append(row_list[index_value]):使用每行中my_columns索引位置的值添加到row_list_output这个列表中。
  5. 现在描述一下这段代码对于第一次外部for循环的运行过程:当读取了文件的第一行后。index_value的值为0,随后,appendrow[3],也就是csv文件中对应的成本那一列加入到row_list_output
  6. 再往下运行,filewriter.writerow(row_list_output)将提取的这个值写入输出文件。

现在运行这段代码,如下所示:

1
C:\Users\20161111\Desktop>python select_meets_condition.py supplier_data_unnecessary_header_footer.csv "output.csv" #其实不加引号也行

现在在桌面上就出现了一个新的csv文件,名称为output.csv,打开这个文件,如下所示:

使用pandas模块挑出某列-索引方法

使用pandas模块来选取某列的代码如下所示,代码保存在桌面上,命名为pandas_column_by_index.py

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python3
import pandas as pd
import sys
input_file = sys.argv[1]
output_file = sys.argv[2]
data_frame = pd.read_csv(input_file)
data_frame_column_by_index = data_frame.iloc[:, [0, 3]]
data_frame_column_by_index.to_csv(output_file, index = False)

运行如下所示:

1
C:\Users\20161111\Desktop>python pandas_column_by_index.py supplier_data_unnecessary_header_footer.csv pandas_output.csv

这里用到了iloc函数,这个函数的功能是:基于索引位置来选择数据集,那么代码中的[:0, 3]表示的就是选取第0列与第3列。

ilocloc函数的用法

iloc的用法可以通过代码来演示一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import numpy as np
import pandas as pd
#创建一个Dataframe
data=pd.DataFrame(np.arange(16).reshape(4,4),index=list('abcd'),columns=list('ABCD'))
data
data.loc['a'] #取索引为'a'的行
data.iloc[0] # 取第一行数据,索引为'a'的行就是第一行,所以结果相同
data.loc[:,['A']] #取'A'列所有行,多取几列格式为 data.loc[:,['A','B']]
data.iloc[:,[0]] #取第0列所有行,多取几列格式为 data.iloc[:,[0,1]]
data.loc[['a','b'],['A','B']] #提取index为'a','b',列名为'A','B'中的数据
data.iloc[[0,1],[0,1]] #提取第0、1行,第0、1列中的数据
data.loc[:,:] #取A,B,C,D列的所有行
data.iloc[:,:] #取第0,1,2,3列的所有行
data.loc[data['A']==0] #提取data数据(筛选条件: A列中数字为0所在的行数据)

运行结果如下所示:

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
>>> data
A B C D
a 0 1 2 3
b 4 5 6 7
c 8 9 10 11
d 12 13 14 15
>>> data.loc['a'] #取索引为'a'的行
A 0
B 1
C 2
D 3
Name: a, dtype: int32
>>> data.iloc[0] # 取第一行数据,索引为'a'的行就是第一行,所以结果相同
A 0
B 1
C 2
D 3
Name: a, dtype: int32
>>> data.loc[:,['A']] #取'A'列所有行,多取几列格式为 data.loc[:,['A','B']]
A
a 0
b 4
c 8
d 12
>>> data.iloc[:,[0]] #取第0列所有行,多取几列格式为 data.iloc[:,[0,1]]
A
a 0
b 4
c 8
d 12
>>> data.loc[['a','b'],['A','B']] #提取index为'a','b',列名为'A','B'中的数据
A B
a 0 1
b 4 5
>>> data.iloc[[0,1],[0,1]] #提取第0、1行,第0、1列中的数据
A B
a 0 1
b 4 5
>>> data.loc[:,:] #取A,B,C,D列的所有行
A B C D
a 0 1 2 3
b 4 5 6 7
c 8 9 10 11
d 12 13 14 15
>>> data.iloc[:,:] #取第0,1,2,3列的所有行
A B C D
a 0 1 2 3
b 4 5 6 7
c 8 9 10 11
d 12 13 14 15
>>> data.loc[data['A']==0] #提取data数据(筛选条件: A列中数字为0所在的行数据)
A B C D
a 0 1 2 3

基于标题选取列

选取特定的列也可以使用列标题来选取,尤其是当处理的文件中有相同的标题,但是列不同的时候就可以采用这种方法。在这个案例中,还以前面的那个csv文件说明一下,现在只需要保留发票号码列和购买日期列,我们采用列标题的方式来选取,输入以下代码,命名为reader_column_by_name.py,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python3
import csv
import sys
input_file = sys.argv[1]
output_file = sys.argv[2]
my_columns = ['Invoice Number','Purchase Date']
my_columns_index = []
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)
header = next(filereader, None)
for index_value in range(len(header)):
if header[index_value] in my_columns:
my_columns_index.append(index_value)
filewriter.writerow(my_columns)
for row_list in filereader:
row_list_output = []
for index_value in my_columns_index:
row_list_output.append(row_list[index_value])
filewriter.writerow(row_list_output)

解释一下代码(略去前面已经提到的内容):

  1. header = next(filereader, None):这里使用了next函数,它的功能是返回迭代器的下一项。next函数的用法是next(iterator[, default]),其中iterator是一个迭代器,而default则是一个可选对象,也就是说,在没有下一个元素返回时,就返回该默认值,如果不设置,又没有下一个元素时,就会触发StopIteration异常。

这段代码是《Python数据分析基础》中的代码,根据书后面的csv文件,它的前两行是其它数据,使用上面的代码无法正确读取(只会生成标题,而没有内容),因此需要手工将前面的数据删除,形成如下内容才行:

现在运行代码,如下所示:

1
C:\Users\20161111\Desktop>python reader_column_by_name.py supplier_data_unnecessary_header_footer.csv index_output.csv

此时就会在桌面上生成一个名为index_output.csv的csv文件,打开如下所示:

使用pandas按标题行进行读取

要使用pandas来读取数据,需要输入以下代码(命名为pandas_column_by_name.py):

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python3
import pandas as pd
import sys
input_file = sys.argv[1]
output_file = sys.argv[2]
data_frame = pd.read_csv(input_file)
data_frame_column_by_name = data_frame.loc[:, ['Invoice Number','Purchase Date']]
data_frame_column_by_name.to_csv(output_file, index = False)

需要注意的是,这个也要删除原始csv文件的前两行。

常规方法选取连续的行

在前面的原始csv表格中,我们可以发现,前两行的内容是I don’t care about this line,后三行的内容是I don’t want this line either,这些内容不是我们想要的,我们只想要中间的那些内容,如下所示:

因此下面演示一下如何选取中间的内容,输入以下代码,命名为11csv_reader_select_contiguous_rows.py,如下所示:

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]
row_counter = 0
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)
for row in filereader:
if row_counter >=3 and row_counter <= 15:
filewriter.writerow([value.strip() for value in row])
print(row_counter, [value.strip() for value in row])
row_counter += 1

运行代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
C:\Users\20161111\Desktop>python 11csv_reader_select_contiguous_rows.py supplier_data_unnecessary_header_footer.csv 11.csv
3 ['Supplier X', '001-1001', '5467', '$750.00', '1/20/14']
4 ['Supplier X', '001-1001', '5467', '$750.00', '1/20/14']
5 ['Supplier X', '50-9501', '7009', '$250.00', '1/30/14']
6 ['Supplier X', '50-9501', '7009', '$250.00', '1/30/14']
7 ['Supplier X', '50-9501', '6650', '$125.00', '2002/3/14']
8 ['Supplier X', '50-9501', '6650', '$125.00', '2002/3/14']
9 ['Supplier X', '920-4803', '3321', '$615.00', '2002/3/14']
10 ['Supplier X', '920-4803', '3321', '$615.00', '2002/10/14']
11 ['Supplier X', '920-4803', '3321', '$615.00', '2/17/14']
12 ['Supplier X', '920-4803', '3321', '$615.00', '2/24/14']
13 ["I don't want this row either", '', '', '', '']
14 ["I don't want this row either", '', '', '', '']
15 ["I don't want this row either", '', '', '', '']

此时就在桌面上生成了一个名为11.csv的csv文件。

添加标题行

当电子表格中没有标题行 的时候,就需要添加列标题。此时还以前面的ccsv文件为例说明一下,删除前后几行,菾下面的样式:

将文保存为supplier_data_no_header_row.csv格式。

使用常规方式添加标题行

在桌面上新建一个名为12csv_reader_add_header_row.py的Python文件,输入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/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)
header_list = ['Supplier Name', 'Invoice Number','Part Number',\
'Cost', 'Purchase Date']
filewriter.writerow(header_list)
for row in filereader:
filewriter.writerow(row)

运行代码,如下所示:

1
C:\Users\20161111\Desktop>python 12csv_reader_add_header_row.py supplier_data_no_header_row.csv add_header.csv

在桌面上就生成了add_header.csv的新文件,打开如下所示:

使用pandas包添加列标题

pandas包中的read_csv函数可以直接指定输入文件不包含标题行,并哦可以提供一个列标题列表。如果要给一个没有标题行的数据集添加标题行,此文件命名为pandas_add_header_row.py,实现方式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
import pandas as pd
import sys
input_file = sys.argv[1]
output_file = sys.argv[2]
header_list = ['Supplier Name', 'Invoice Number', 'Part Number', \
'Cost', 'Purchase Date']
data_frame = pd.read_csv(input_file, header=None, names= header_list)
data_frame.to_csv(output_file, index=False)

运行代码如下所示:

1
C:\Users\20161111\Desktop>python pandas_add_header_row.py supplier_data_no_header_row.csv pandas_header.csv

这里需要注意一下最后一行data_frame.to_csv(output_file, index=False)里面的index=False,如果设置为True,则会在数据集的前面再添加1列,这1列是索引,如下所示:

读取多个csv文件

数据处理过程中常常会遇到同时处理多个csv文件的情况。

创建3个演示csv文件

在桌面上创建第1个csv文件(这个文件也可以从作者的Github上下载),命名为sales_january_2014.csv文件内容如下所示:

创建第2个csv文件,命名为sales_february_2014.csv,内容如下所示:

创建第3个csv文件,命名为sales_march_2014.csv,内容如下所示:

文件计数与文件中的行列计数

这一部分是要计算一下每个文件中行与列的数量,代码文件为8csv_reader_counts_for_multiple_files.py,代码如下所示:

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 glob
import os
import sys
input_path = sys.argv[1]
file_counter = 0
for input_file in glob.glob(os.path.join(input_path, 'sales_*')):
row_counter = 1
with open(input_file, 'r', newline = '') as csv_in_file:
filereader = csv.reader(csv_in_file)
header = next(filereader, None)
for row in filereader:
row_counter += 1
print('{0!s}: \t{1:d} rows \t{2:d} columns'.format(os.path.basename(input_file), row_counter, len(header)))
file_counter += 1
print('Number of files: {0:d}'.format(file_counter))

运行结果如下所示:

1
2
3
4
5
C:\Users\20161111\Desktop>python 8csv_reader_counts_for_multiple_files.py "C:\Users\20161111\Desktop"
sales_february_2014.csv: 7 rows 5 columns
sales_january_2014.csv: 7 rows 5 columns
sales_march_2014.csv: 7 rows 5 columns
Number of files: 3

代码解释:

  1. for input_file in glob.glob(os.path.join(input_path, 'sales_*')):这里使用了glob模块中的glob函数,这个模块可以匹配某个特定模式的所有路径名,在这段代码中,搜索的模式是sales_*。这个模式表示要搜索所有文件名以sale_形状并且下划线后面可以是任意字符的文件。由于我们创建了3个csv文件,所以应该知道使用这段代码可以识别出这3个文件,它们的文件名都是以sales_形状的。如果我们要检索某个目录下的所有csv文件,则要将sales_*改为*.csv
  2. os.path.basename(path):这是返回path的基本文件名。即如果pathC:\Users\Clinton\Desktop\my_input_file.csv,那么os.path.basename(path)则返回的是是my_input_file.csv

合并多个文件

有的时候需要将多个csv文件合并为一个csv文件,现在有几种合并方式。

常规合并方式

先看常规方式来合并多个csv文件,输入以下代码,命名为9csv_reader_concat_rows_from_multiple_files.py,如下所示:

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
#!/usr/bin/env python3
import csv
import sys
import glob
import os
input_path = sys.argv[1]
output_file = sys.argv[2]
first_file = True
for input_file in glob.glob(os.path.join(input_path,'sales_*')):
print(os.path.basename(input_file))
with open(input_file, 'r', newline='') as csv_in_file:
with open(output_file, 'a', newline='') as csv_out_file:
filereader = csv.reader(csv_in_file)
filewriter = csv.writer(csv_out_file)
if first_file:
for row in filereader:
filewriter.writerow(row)
first_file = False
else:
header = next(filereader, None)
for row in filereader:
filewriter(row)

运行结果如下所示:

1
2
3
4
C:\Users\20161111\Desktop>python 9csv_reader_concat_rows_from_multiple_files.py "C:\Users\20161111\Desktop" merge.csv
sales_february_2014.csv
sales_january_2014.csv
sales_march_2014.csv

此时在桌面上生成了一名为merge.csv的文件,打开后,如下所示:

使用pandas包合并

pandas包可以直接将多个csv文件合并为一个csv文件。基本过程就是将每个输入文件读取到pandas的数据框中,将所有数据框追加到一个数据框列表,然后使用concat函数将所有数据框连接成一个数据框。concat函数可以使用axis函数来设置合并数据框的试,其中axis=0表示从头到尾垂直合并,axis=1表示并排地合并。

使用pandas包合并多个csv文件的过程如下所示(文件命名为pandas_concat_rows_from_multiple_files.py):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/env python3
import pandas as pd
import sys
import glob
import os
input_path = sys.argv[1]
output_file = sys.argv[2]
all_files = glob.glob(os.path.join(input_path, 'sales_*'))
all_data_frames = []
for file in all_files:
data_frame = pd.read_csv(file, index_col = None)
all_data_frames.append(data_frame)
data_frame_concat = pd.concat(all_data_frames, axis = 0, ignore_index = True)
data_frame_concat.to_csv(output_file, index = False)

运行如下所示:

1
C:\Users\20161111\Desktop>python pandas_concat_rows_from_multiple_files.py "C:\Users\20161111\Desktop" pandas_merge.csv

计算每个文件值中的总和与均值

在数据处理过程中,有的时候需要计算多个csv文件中的一些统计量,例如均值和总和。现在以前面的3个csv文件为例说明一下如何计算。

常规计算方式

现在使用Python的常规方式来计算前面3个csv文件某列的总和和均值。代码如下所示(代码名称为10csv_reader_sum_average_from_multiple_files.py):

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
#!/usr/bin/env python3
import csv
import sys
import glob
import os
input_path = sys.argv[1]
output_file = sys.argv[2]
output_header_list = ['file_name', 'total_sales', 'average_sales']
csv_out_file = open(output_file, 'a', newline='')
filewriter = csv.writer(csv_out_file)
filewriter.writerow(output_header_list)
for input_file in glob.glob(os.path.join(input_path, 'sales_*')):
with open(input_file, 'r', newline = '') as csv_in_file:
filereader = csv.reader(csv_in_file)
output_list = []
output_list.append(os.path.basename(input_file))
header = next(filereader,None)
total_sales = 0.0
number_of_sales = 0.0
for row in filereader:
sale_amount = row[3]
total_sales += float(str(sale_amount).strip('$').replace(',', ''))
number_of_sales += 1
average_sales = '{0:.2f}'.format(total_sales / number_of_sales)
output_list.append(total_sales)
output_list.append(average_sales)
filewriter.writerow(output_list)
csv_out_file.close()

运行结果如下所示:

1
C:\Users\20161111\Desktop>python 10csv_reader_sum_average_from_multiple_files.py "C:\Users\20161111\Desktop" add_col.csv

此时就会在桌面上创建一个add_col.csv文件,打开后如下所示:

pandas计算

pandas包中提供了各种统计函数,例如summean。下面的代码功能是对多个文件中的某一列计算这两个统计量(总和与均值),并将每个输入文件的计算结果写入输出文件,代码保存文件为pandas_sum_average_from_multiple_files.py,如下所示:

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
#!/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_files = glob.glob(os.path.join(input_path, 'sales_*'))
all_data_frames = []
for input_file in all_files:
data_frame = pd.read_csv(input_file, index_col=None)
total_sales = pd.DataFrame([float(str(value).strip('$').replace(',', '')) \
for value in data_frame.loc[:, 'Sale Amount']]).sum()
average_sales = pd.DataFrame([float(str(value).strip('$').replace(',', '')) \
for value in data_frame.loc[:, 'Sale Amount']]).mean()
data = {'file_name': os.path.basename(input_file),
'total_sales': total_sales,
'average_sales': average_sales}
all_data_frames.append(pd.DataFrame(data, \
columns=['file_name', 'total_sales', 'average_sales']))
data_frames_concat = pd.concat(all_data_frames, axis=0, ignore_index=True)
data_frames_concat.to_csv(output_file, index=False)

运行结果如下所示:

1
2
PS D:\netdisk\pyton_data> python .\pandas_read_and_write_excel.py sales_2013.xlsx pandas_out.xlsx
PS D:\netdisk\pyton_data>

参考资料

  1. sys.argv是什么?
  2. 克林顿·布朗利(Clinton,W.,Brownley) 著,陈光欣 译. Python数据分析基础[M]. 2017.
  3. Panadas中文文档
  4. Pandas中loc和iloc函数用法详解(源码+实例)
  5. 《Python数据分析基础》中源代码与数据文件