一、CSV文件
逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。纯文本意味着该文件是一个字符序列,不含必须像二进制数字那样被解读的数据。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符。通常,所有记录都有完全相同的字段序列。通常都是纯文本文件。建议使用记事本来打开编辑,或双击直接用Excel打开编辑。
(一)CSV文件用法
CSV是一种通用的、相对简单的文件格式,被用户、商业和科学广泛应用。最广泛的应用是在程序之间转移表格数据,而这些程序本身是在不兼容的格式上进行操作的(往往是私有的和/或无规范的格式)。因为大量程序都支持某种CSV变体,至少是作为一种可选择的输入/输出格式。
例如,有一批新生名单的Excel表文档,在将新生数据导入教学管理系统。最简单的方式是,将Excel数据导出为“CSV”,然后将导出的CSV文件导入到教学管理系统。
“CSV”并不是一种单一的、定义明确的格式(尽管RFC 4180有一个被通常使用的定义)。因此在实践中,术语“CSV”泛指具有以下特征的任何文件:
- 纯文本,使用某个字符集,比如ASCII、Unicode、EBCDIC或GBK;
- 由记录组成(典型的是每行一条记录);
- 每条记录被分隔符分隔为字段(典型分隔符有逗号、分号或制表符;有时分隔符可以包括可选的空格);
- 每条记录都有同样的字段序列。
在这些常规的约束条件下,存在着许多CSV变体,故CSV文件并不完全互通。然而,这些变异非常小,并且有许多应用程序允许用户预览文件(这是可行的,因为它是纯文本),然后指定分隔符、转义规则等。如果一个特定CSV文件的变异过大,超出了特定接收程序的支持范围,那么可行的做法往往是人工检查并编辑文件,或通过简单的程序来修复问题。因此在实践中,CSV文件还是非常方便的。
本文讲的CSV文件是指逗号分隔值的纯文本(Excel样式),编码:Windows系统为GBK,UNIX(Linux)一般为UTF-8。注:Win10记事本默认为UTF-8,Win7记事本默认为ANSI(GBK),MS Excel导出的csv文档是ANSI(GBK)。
(二)CSV文件规则
- 开头是不留空,以行为单位。
- 可含或不含列名,含列名则居文件第一行。
- 一行数据不跨行,无空行。
- 以半角逗号(即“,”)作分隔符,列为空也要表达其存在。
- 列内容如存在半角引号(即"),替换成半角双引号("")转义,即用半角引号(即"")将该字段值包含起来。
- 文件读写时引号,逗号操作规则互逆。
- 内码格式不限,可为 ASCII、Unicode、ANSI、GBK或者其他。
- 不支持数字(数字以数值字符串表示)
- 不支持特殊字符
二、CSV模块
CSV模块是Python的标准模块,不需要安装就可使用。
(一) CSV模块的读方法(函数)
csv模块中的reader类和 DictReader类用于读取文件中的数据。
1. reader()
reader()语法格式如下:
csv.reader(csvfile, dialect='excel', **fmtparams)
其中:csvfile是支持迭代(Iterator)的对象,可以是文件(file)对象或者列表(list)对象;dialect是编码样式,默认为Excel的样式,也就是使用逗号“,”分隔符;fmtparams是格式化参数,用来覆盖之前 dialect 对象指定的编码样式。一般用于处理没有标题(列名)的csv文件。
2. DictReader
DictReader语法格式如下:
csv.DictReader(csvfile, fieldnames=None, restkey=None, restval=None, dialect='excel',
*args, **kwds)
其中:csvfile是csv文件对象;fieldnames 是一个序列,如果省略fieldnames,则文件对象f 第一行中的值将用作标题(列名)。不管标题是如何确定的,字典会保留它们的原始顺序。
如果一行的列多于标题列,则将剩余数据放入列表中并使用 restkey 指定的列名(默认为 None )存储。如果非空白行的列少于标题列,则缺少的值为 filled-in,值为 restval(默认为 None )。
所有其他可选或关键词参数都传递给底层reader 对象。
DictReader将二维表的每行信息映射到字典(dict),其键为对应的标题。
(二) CSV模块的写方法(函数)
csv 模块中的writer类和DictWriter类用于将数据写入CSV文件中。
1. writer()
writer()的语法格式如下:
csv.writer(csvfile, dialect='excel', **fmtparams)
其中:csvfile是支持迭代(Iterator)的对象,可以是文件(file)对象或者列表(list)对象;dialect是编码样式,默认为Excel的样式,也就是使用逗号“,”分隔符;fmtparams是格式化参数,用来覆盖之前 dialect 对象指定的编码样式。
2. DictWriter()
csv.DictWriter(csvfile, fieldnames=None, restval =None, extrasaction ='raise', dialect='excel',
*args, **kwds)
其中:csvfile是csv文件对象;fieldnames 是一个序列,如果省略fieldnames,则文件对象f 第一行中的值将用作标题(列名)。不管标题是如何确定的,字典会保留它们的原始顺序。restval(可选)当如果字典缺少标题中的列, 则指定要写入的值。extrasaction(可选)如果在标题中找不到列, 则可选的extrasaction参数指示要执行的操作。如果将其设置为引发ValueError, 则会抛出异常。dialect (可选)是编码样式,默认为Excel的样式,也就是使用逗号“,”分隔符。
csv.DictWriter提供了两种写入CSV的方法,writeheader()和writerows()。
writeheader()方法仅将标题写入csv文件的第一行。
语法如下:
writeheader()
writerows()方法只写所有行, 但在每一行中, 只写值(不写键)。
语法如下:
writerows(mydict)
三、用csv模块处理二维数据应用实例
表1是杭州市2022年10月份天气情况表,请用Python编程,将数据保存为csv文档,并读取数据编程绘制温度曲线。提示:可用turtle绘图。
表1 杭州市2022年10月份天气情况表
注:数据来自杭州历史气温_天气网
表中含有五列数据:日期、最高气温、最低气温、天气和风向,日期为2022年10月1日~2022年10月31日。这是带标题的二维数据,可以用Python的列表元素列表表示,也可以用Python的字典元素列表表示。相对于二维表格(如表1)转换为列表元素列表比较简单,转换为字典元素列表则相对要复杂些,用csv模块可以很方便地解决列表元素列表与字典元素列表间的转换。
(一) 用列表元素列表表示
用列表作为列表元素表示二维表时,数据的标题列表作为列表的第一个元素,每行数据都以值的列表表示,例如表1可以用下面的列表元素列表表示:
lst = [['日期','最高气温','最低气温','天气','风向'],
['2022-10-01 星期六','34℃','25℃','雾','东风 1级'],
['2022-10-02 星期日','37℃','26℃','晴','东南风 1级'],
['2022-10-03 星期一','38℃','24℃','晴','南风 1级'],
['2022-10-04 星期二','26℃','18℃','阴','北风 3级'],
['2022-10-05 星期三','20℃','15℃','多云','东北风 2级'],
['2022-10-06 星期四','16℃','14℃','小雨','北风 3级'],
['2022-10-07 星期五','15℃','14℃','小雨','北风 2级'],
['2022-10-08 星期六','17℃','15℃','小雨','北风 2级'],
['2022-10-09 星期日','19℃','10℃','多云','西北风 2级'],
['2022-10-10 星期一','19℃','9℃','晴','西北风 2级'],
['2022-10-11 星期二','19℃','12℃','多云','北风 2级'],
['2022-10-12 星期三','22℃','11℃','多云','东北风 1级'],
['2022-10-13 星期四','22℃','14℃','多云','东风 1级'],
['2022-10-14 星期五','24℃','15℃','多云','北风 2级'],
['2022-10-15 星期六','24℃','16℃','晴','东北风 2级'],
['2022-10-16 星期日','24℃','15℃','雾','北风 2级'],
['2022-10-17 星期一','21℃','10℃','雾','北风 2级'],
['2022-10-18 星期二','19℃','10℃','多云','北风 2级'],
['2022-10-19 星期三','20℃','11℃','晴','东风 2级'],
['2022-10-20 星期四','22℃','12℃','多云','东风 2级'],
['2022-10-21 星期五','26℃','16℃','多云','东风 2级'],
['2022-10-22 星期六','26℃','17℃','雾','北风 2级'],
['2022-10-23 星期日','23℃','11℃','雾','东北风 3级'],
['2022-10-24 星期一','22℃','13℃','多云','东风 2级'],
['2022-10-25 星期二','21℃','15℃','多云','东风 3级'],
['2022-10-26 星期三','20℃','16℃','阴','北风 1级'],
['2022-10-27 星期四','19℃','17℃','小雨','东北风 1级'],
['2022-10-28 星期五','18℃','14℃','小雨','东北风 2级'],
['2022-10-29 星期六','21℃','14℃','多云','东北风 2级'],
['2022-10-30 星期日','21℃','14℃','雾','东北风 1级'],
['2022-10-31 星期一','22℃','13℃','多云','西北风 2级']]
(二) 用字典元素列表表示
用字典作为列表元素表示二维表时,列标题作为键,每行数据对应列作为值的字典表示,例如表1可以用下面的字典元素列表表示:
dic = [{'日期': '2022-10-01 星期六', '最高气温': '34℃', '最低气温': '25℃', '天气': '雾', '风向': '东风 1级'},
{'日期': '2022-10-02 星期日', '最高气温': '37℃', '最低气温': '26℃', '天气': '晴', '风向': '东南风 1级'},
{'日期': '2022-10-03 星期一', '最高气温': '38℃', '最低气温': '24℃', '天气': '晴', '风向': '南风 1级'},
{'日期': '2022-10-04 星期二', '最高气温': '26℃', '最低气温': '18℃', '天气': '阴', '风向': '北风 3级'},
{'日期': '2022-10-05 星期三', '最高气温': '20℃', '最低气温': '15℃', '天气': '多云', '风向': '东北风 2级'},
{'日期': '2022-10-06 星期四', '最高气温': '16℃', '最低气温': '14℃', '天气': '小雨', '风向': '北风 3级'},
{'日期': '2022-10-07 星期五', '最高气温': '15℃', '最低气温': '14℃', '天气': '小雨', '风向': '北风 2级'},
{'日期': '2022-10-08 星期六', '最高气温': '17℃', '最低气温': '15℃', '天气': '小雨', '风向': '北风 2级'},
{'日期': '2022-10-09 星期日', '最高气温': '19℃', '最低气温': '10℃', '天气': '多云', '风向': '西北风 2级'},
{'日期': '2022-10-10 星期一', '最高气温': '19℃', '最低气温': '9℃', '天气': '晴', '风向': '西北风 2级'},
{'日期': '2022-10-11 星期二', '最高气温': '19℃', '最低气温': '12℃', '天气': '多云', '风向': '北风 2级'},
{'日期': '2022-10-12 星期三', '最高气温': '22℃', '最低气温': '11℃', '天气': '多云', '风向': '东北风 1级'},
{'日期': '2022-10-13 星期四', '最高气温': '22℃', '最低气温': '14℃', '天气': '多云', '风向': '东风 1级'},
{'日期': '2022-10-14 星期五', '最高气温': '24℃', '最低气温': '15℃', '天气': '多云', '风向': '北风 2级'},
{'日期': '2022-10-15 星期六', '最高气温': '24℃', '最低气温': '16℃', '天气': '晴', '风向': '东北风 2级'},
{'日期': '2022-10-16 星期日', '最高气温': '24℃', '最低气温': '15℃', '天气': '雾', '风向': '北风 2级'},
{'日期': '2022-10-17 星期一', '最高气温': '21℃', '最低气温': '10℃', '天气': '雾', '风向': '北风 2级'},
{'日期': '2022-10-18 星期二', '最高气温': '19℃', '最低气温': '10℃', '天气': '多云', '风向': '北风 2级'},
{'日期': '2022-10-19 星期三', '最高气温': '20℃', '最低气温': '11℃', '天气': '晴', '风向': '东风 2级'},
{'日期': '2022-10-20 星期四', '最高气温': '22℃', '最低气温': '12℃', '天气': '多云', '风向': '东风 2级'},
{'日期': '2022-10-21 星期五', '最高气温': '26℃', '最低气温': '16℃', '天气': '多云', '风向': '东风 2级'},
{'日期': '2022-10-22 星期六', '最高气温': '26℃', '最低气温': '17℃', '天气': '雾', '风向': '北风 2级'},
{'日期': '2022-10-23 星期日', '最高气温': '23℃', '最低气温': '11℃', '天气': '雾', '风向': '东北风 3级'},
{'日期': '2022-10-24 星期一', '最高气温': '22℃', '最低气温': '13℃', '天气': '多云', '风向': '东风 2级'},
{'日期': '2022-10-25 星期二', '最高气温': '21℃', '最低气温': '15℃', '天气': '多云', '风向': '东风 3级'},
{'日期': '2022-10-26 星期三', '最高气温': '20℃', '最低气温': '16℃', '天气': '阴', '风向': '北风 1级'},
{'日期': '2022-10-27 星期四', '最高气温': '19℃', '最低气温': '17℃', '天气': '小雨', '风向': '东北风 1级'},
{'日期': '2022-10-28 星期五', '最高气温': '18℃', '最低气温': '14℃', '天气': '小雨', '风向': '东北风 2级'},
{'日期': '2022-10-29 星期六', '最高气温': '21℃', '最低气温': '14℃', '天气': '多云', '风向': '东北风 2级'},
{'日期': '2022-10-30 星期日', '最高气温': '21℃', '最低气温': '14℃', '天气': '雾', '风向': '东北风 1级'},
{'日期': '2022-10-31 星期一', '最高气温': '22℃', '最低气温': '13℃', '天气': '多云', '风向': '西北风 2级'}]
(三) 二维数据写csv文件
有标题的csv文档与无标题的csv文档的区别是:有标题的csv文档的第一行是标题,第二行开始才是数据;无标题的csv文档第一行开始就是数据。
例1:将表1数据按有标题二维数据格式写到csv文件中,文档名为“d:\10月杭州天气.csv”
import csv
# 将数据保存为有标题(第一行为标题)的csv文档
lst = [['日期','最高气温','最低气温','天气','风向'],
['2022-10-01 星期六','34℃','25℃','雾','东风 1级'],
['2022-10-02 星期日','37℃','26℃','晴','东南风 1级'],
['2022-10-03 星期一','38℃','24℃','晴','南风 1级'],
['2022-10-04 星期二','26℃','18℃','阴','北风 3级'],
['2022-10-05 星期三','20℃','15℃','多云','东北风 2级'],
['2022-10-06 星期四','16℃','14℃','小雨','北风 3级'],
['2022-10-07 星期五','15℃','14℃','小雨','北风 2级'],
['2022-10-08 星期六','17℃','15℃','小雨','北风 2级'],
['2022-10-09 星期日','19℃','10℃','多云','西北风 2级'],
['2022-10-10 星期一','19℃','9℃','晴','西北风 2级'],
['2022-10-11 星期二','19℃','12℃','多云','北风 2级'],
['2022-10-12 星期三','22℃','11℃','多云','东北风 1级'],
['2022-10-13 星期四','22℃','14℃','多云','东风 1级'],
['2022-10-14 星期五','24℃','15℃','多云','北风 2级'],
['2022-10-15 星期六','24℃','16℃','晴','东北风 2级'],
['2022-10-16 星期日','24℃','15℃','雾','北风 2级'],
['2022-10-17 星期一','21℃','10℃','雾','北风 2级'],
['2022-10-18 星期二','19℃','10℃','多云','北风 2级'],
['2022-10-19 星期三','20℃','11℃','晴','东风 2级'],
['2022-10-20 星期四','22℃','12℃','多云','东风 2级'],
['2022-10-21 星期五','26℃','16℃','多云','东风 2级'],
['2022-10-22 星期六','26℃','17℃','雾','北风 2级'],
['2022-10-23 星期日','23℃','11℃','雾','东北风 3级'],
['2022-10-24 星期一','22℃','13℃','多云','东风 2级'],
['2022-10-25 星期二','21℃','15℃','多云','东风 3级'],
['2022-10-26 星期三','20℃','16℃','阴','北风 1级'],
['2022-10-27 星期四','19℃','17℃','小雨','东北风 1级'],
['2022-10-28 星期五','18℃','14℃','小雨','东北风 2级'],
['2022-10-29 星期六','21℃','14℃','多云','东北风 2级'],
['2022-10-30 星期日','21℃','14℃','雾','东北风 1级'],
['2022-10-31 星期一','22℃','13℃','多云','西北风 2级']]
with open(r'd:/10月杭州天气.csv','w',newline='') as f:
csv.writer(f).writerows(lst)
程序执行结果如下:
图1 例1执行结果
(四) 读有标题csv文档为字典元素列表
读取有标题的csv文档时,如以列表方式读,则处理时要丢弃第一行标题,获取数据按位置(索引值)获取,即按行、列获取某天的某个天气信息。如以字典方式读,则处理时第一行成为标题,获取数据按列标题获取,即可按条件、用列标题获取某天的某个天气信息。
如:
date = '2022-10-05'
print([i['天气'] for i in dic if i['日期'][:10] == date][0])
结果为:多云
例2:读取例1保存10月杭州市天气情况“d:\10月杭州天气.csv”,绘制全月的最高温度和最低温度变化曲线,并在最高温度曲线上标注“天气”信息。
import csv
import turtle as tl
_maxt=[] # 保存'最高温度'数据(数值)
_mint=[] # 保存'最低温度'数据(数值)
with open(r'd:/10月杭州天气.csv','r') as f: # 读数据,将标题转化为每列数据的“键”
reader = csv.DictReader(f) # 加fieldnames=head参数,则head为标题,
dic_lst = list(reader) # 第一行为数据,否则第一行为标题
print(dic_lst)
for i in dic_lst: # 第i行数据
_maxt.append(int(i['最高气温'][:-1])) # '最高气温'列是某日最高温度,去掉℃转数值
_mint.append(int(i['最低气温'][:-1])) # '最低气温'列是某日最低温度,去掉℃转数值
tl.pu()
for i in range(31): # 绘制日期文字(1日~31日)
tl.goto(-300+i*20,-230)
tl.write(str(i+1)+'\n日',align='center')
for i in range(5,40,5): # 间隔5℃绘制温度文字(5~35℃)
tl.goto(-305,-208+i*10)
tl.write(str(i))
tl.goto(-305,-208+40*10)
tl.write('℃') # 绘制温度坐标单位:℃
tl.goto(-310,-200)
tl.pd()
tl.fd(620) # 绘制水平线(水平坐标线)
tl.lt(90)
for i in range(31,0,-1): # 绘制31个日期刻度
tl.pu()
tl.goto(-300+(i-1)*20,-200)
tl.pd()
tl.fd(5)
tl.pu()
tl.goto(-310,-200)
tl.pd()
tl.fd(410) # 绘制垂直线(垂直坐标线)
tl.rt(90)
for i in range(40,0,-1): # 绘制40个温度刻度
tl.pu()
tl.goto(-310,-200+i*10)
tl.pd()
tl.fd(5)
tl.pu()
tl.goto(-300,-200+_maxt[0]*10) # 移动到1日最高温度位置
tl.pd()
tl.color('red') # 最高温度曲线用红色绘制
for i,t in enumerate(_maxt): # 根据日期求水平、最高温度求垂直坐标
tl.goto(-300+i*20,-200+t*10)
tl.dot(6) # 加小圆点
tl.write(dic_lst[i]['天气'],align='center') # 绘制天气信息(文字)
tl.pu()
tl.goto(-300,-200+_mint[0]*10) # 移动到1日最低温度位置
tl.color('blue') # 最低温度曲线用蓝色绘制
tl.pd()
for i,t in enumerate(_mint): # 根据日期求水平、最低温度求垂直坐标
tl.goto(-300+i*20,-200+t*10)
tl.dot(6) # 加小圆点
tl.ht() # 隐藏LOGO形状
程序执行结果如图2所示。
图2 例2执行结果(2022年10月最高温度和最低温度变化曲线)