输⼊输出通常可以划分为⼏个⼤类:读取⽂本⽂件和其他更⾼效的磁盘存储格式,加载数据库中的数据,利⽤Web API操作⽹络资源。
一、读写文本格式的数据
pandas提供了⼀些⽤于将表格型数据读取为DataFrame对象的函数。
表6-1对它们进⾏了总结,其中read_csv和read_table可能会是今后⽤得最多的。
表6-1 pandas中的解析函数
这些函数的选项可以划分为以下⼏个⼤类:
索引:将⼀个或多个列当做返回的DataFrame处理,以及是否从⽂件、⽤户获取列名。
类型推断和数据转换:包括⽤户定义值的转换、和自定义的缺失值标记列表等。
⽇期解析:包括组合功能,⽐如将分散在多个列中的⽇期时间信息组合成结果中的单个列。
迭代:⽀持对⼤⽂件进⾏逐块迭代。
不规整数据问题:跳过⼀些⾏、⻚脚、注释或其他⼀些不重要的东⻄(⽐如由成千上万个逗号隔开的数值数据)。
因⼯作中实际碰到的数据可能⼗分混乱,⼀些数据加载函数(尤其是read_csv)的选项逐渐变得复杂起来。⾯对不同的参数,感到头痛很正常(read_csv有超过50个参数)。pandas⽂档有这些参数的例⼦,如果阅读某个⽂件很难,可以通过相似的⾜够多的例⼦找到正确的参数。
其中⼀些函数,⽐如pandas.read_csv,有类型推断功能,因为列数据的类型不属于数据类型。也就是说,你不需要指定列的类型到底是数值、整数、布尔值,还是字符串。其它的数据格式,如HDF5、Feather和msgpack,会在格式中存储数据类型。
⽇期和其他⾃定义类型的处理需要多花点⼯夫才⾏。⾸先来看⼀个以逗号分隔的(CSV)⽂本⽂件:
!type ex1.csv # 输出如下:(Unix or Linux命令是:!cat ex1.csv)
a,b,c,d,message
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
该⽂件以逗号分隔,可以使⽤read_csv将其读⼊⼀个DataFrame:
df = pd.read_csv('examples/ex1.csv')
df # 输出如下:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
还可以使⽤read_table,并指定分隔符:
pd.read_table('examples/ex1.csv', sep=',') # 输出如下:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
并不是所有⽂件都有标题⾏。看看下⾯这个⽂件:
!type examples\\ex2.csv # 输出如下:
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
读⼊该⽂件的办法有两个。你可以让pandas为其分配默认的列名,也可以⾃⼰定义列名:
pd.read_csv('examples/ex2.csv', header=None) # 默认列名
0 1 2 3 4
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
pd.read_csv('examples/ex2.csv', names=['a', 'b', 'c', 'd', 'message']) # 指定列名
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
将message列做成DataFrame的索引。明确表示要将该列放到索引4的位置上,也可以通过index_col参数指定"message":
names = ['a', 'b', 'c', 'd', 'message']
pd.read_csv('examples/ex2.csv', names=names, index_col='message')
a b c d
message
hello 1 2 3 4
world 5 6 7 8
foo 9 10 11 12
如果希望将多个列做成⼀个层次化索引,只需传⼊由列编号或列名组成的列表即可:
!type examples\\csv_mindex.csv # 原文件内容如下:
key1,key2,value1,value2
one,a,1,2
one,b,3,4
one,c,5,6
one,d,7,8
two,a,9,10
two,b,11,12
two,c,13,14
two,d,15,16
parsed = pd.read_csv('examples/csv_mindex.csv', index_col=['key1', 'key2']) # 打开时指定key1,key2为索引
parsed # 输出如下:
value1 value2
key1 key2
one a 1 2
b 3 4
c 5 6
d 7 8
two a 9 10
b 11 12
c 13 14
d 15 16
有些表格可能不是⽤固定的分隔符去分隔字段的(⽐如空⽩符或其他模式来分隔字段)。看看下⾯这个⽂本⽂件:
list(open('examples/ex3.txt')) # 输出如下:
[' A B C\n',
'aaa -0.264438 -1.026059 -0.619500\n',
'bbb 0.927272 0.302904 -0.032399\n',
'ccc -0.264273 -0.386314 -0.217601\n',
'ddd -0.871858 -0.348382 1.100491\n']
虽然可以⼿动对数据进⾏规整,这⾥的字段是被数量不同的空⽩字符间隔开的。这种情况下,传递⼀个正则表达式作为read_table的分隔符。可以⽤正则表达式为\s+,于是有:
result = pd.read_table('examples/ex3.txt', sep='\s+')
result # 输出如下:
A B C
aaa -0.264438 -1.026059 -0.619500
bbb 0.927272 0.302904 -0.032399
ccc -0.264273 -0.386314 -0.217601
ddd -0.871858 -0.348382 1.100491
这⾥,由于列名⽐数据⾏的数量少,所以read_table推断第⼀列应该是DataFrame的索引。这些解析器函数还有许多参数可以帮助你处理各种各样的异形⽂件格式(表6-2列出了⼀些)。⽐如说,你可以⽤skiprows跳过⽂件的第⼀⾏、第三⾏和第四⾏:
!type examples\\ex4.csv # 文件内容原文如下:
# hey!
a,b,c,d,message
# just wanted to make things more difficult for you
# who reads CSV files with computers, anyway?
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
pd.read_csv('examples/ex4.csv', skiprows=[0, 2, 3]) # 跳过第1、3、4行
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
缺失值处理是⽂件解析任务中的⼀个重要组成部分。缺失数据经常是要么没有(空字符串),要么⽤某个标记值表示。默认情况下,pandas会⽤⼀组经常出现的标记值进⾏识别,⽐如NA及NULL:
!type examples\\ex5.csv # 原文件内容如下:
something,a,b,c,d,message
one,1,2,3,4,NA
two,5,6,,8,world
three,9,10,11,12,foo
result = pd.read_csv('examples/ex5.csv')
result # 输出如下:
something a b c d message
0 one 1 2 3.0 4 NaN
1 two 5 6 NaN 8 world
2 three 9 10 11.0 12 foo
pd.isnull(result) # 输出如下:
something a b c d message
0 False False False False False True
1 False False False True False False
2 False False False False False False
na_values可以⽤⼀个列表或集合的字符串表示缺失值:
result = pd.read_csv('examples/ex5.csv', na_values=['NULL'])
result # 输出如下:
something a b c d message
0 one 1 2 3.0 4 NaN
1 two 5 6 NaN 8 world
2 three 9 10 11.0 12 foo
字典的各列可以使⽤不同的NA标记值:
sentinels = {'message': ['foo', 'NA'], 'something': ['two']} # 设置了标记值的,都会被改为NaN
pd.read_csv('examples/ex5.csv', na_values=sentinels) # 输出如下:
something a b c d message
0 one 1 2 3.0 4 NaN
1 NaN 5 6 NaN 8 world
2 three 9 10 11.0 12 NaN
表6-2列出了pandas.read_csv和pandas.read_table常⽤的选项
1、逐块读取文本文件
在处理很⼤的⽂件时,或找出⼤⽂件中的参数集以便于后续处理时,你可能只想读取⽂件的⼀⼩部分或逐块对⽂件进⾏迭代。
在看⼤⽂件之前,我们先设置pandas显示的最大行数:
pd.options.display.max_rows = 10
然后打开文件:
result = pd.read_csv('examples/ex6.csv')
result # 输出如下:
one two three four key
0 0.467976 -0.038649 -0.295344 -1.824726 L
1 -0.358893 1.404453 0.704965 -0.200638 B
2 -0.501840 0.659254 -0.421691 -0.057688 G
3 0.204886 1.074134 1.388361 -0.982404 R
4 0.354628 -0.133116 0.283763 -0.837063 Q
... ... ... ... ... ..
9995 2.311896 -0.417070 -1.409599 -0.515821 L
9996 -0.479893 -0.650419 0.745152 -0.646038 E
9997 0.523331 0.787112 0.486066 1.093156 K
9998 -0.362559 0.598894 -1.843201 0.887292 G
9999 -0.096376 -1.012999 -0.657431 -0.573315 0
[10000 rows x 5 columns]
如果只想读取⼏⾏(避免读取整个⽂件),通过nrows进⾏指定即可:
pd.read_csv('examples/ex6.csv', nrows=5)
one two three four key
0 0.467976 -0.038649 -0.295344 -1.824726 L
1 -0.358893 1.404453 0.704965 -0.200638 B
2 -0.501840 0.659254 -0.421691 -0.057688 G
3 0.204886 1.074134 1.388361 -0.982404 R
4 0.354628 -0.133116 0.283763 -0.837063 Q
要逐块读取⽂件,可以指定chunksize(⾏数):
chunker = pd.read_csv('examples/ex6.csv', chunksize=1000) # 一次读取1000行
chunker # 输出:<pandas.io.parsers.TextFileReader at 0x1ffcad757b8>
read_csv所返回的这个TextParser对象使你可以根据chunksize对⽂件进⾏逐块迭代。⽐如说,我们可以迭代处理ex6.csv,将值计数聚合到"key"列中,如下所示:
chunker = pd.read_csv('examples/ex6.csv', chunksize=1000) # chunksize=1000,指定一次读取1000行
tot = pd.Series([])
for piece in chunker:
tot = tot.add(piece['key'].value_counts(), fill_value=0)
tot = tot.sort_values(ascending=False)
tot[:10] # 前10行输出如下:
E 368.0
X 364.0
L 346.0
O 343.0
Q 340.0
M 338.0
J 337.0
F 335.0
K 334.0
H 330.0
dtype: float64
TextParser还有⼀个get_chunk⽅法,它使你可以读取任意⼤⼩的块。
2、将数据写出到⽂本格式
数据也可以被输出为分隔符格式的⽂本。再来看看之前读过的⼀个CSV⽂件:
data = pd.read_csv('examples/ex5.csv')
data # 输出如下
something a b c d message
0 one 1 2 3.0 4 NaN
1 two 5 6 NaN 8 world
2 three 9 10 11.0 12 foo
利⽤DataFrame的to_csv⽅法,我们可以将数据写到⼀个以逗号分隔的⽂件中:
data.to_csv('examples/my_out.csv')
!type examples\\my_out.csv # 查看结果
,something,a,b,c,d,message
0,one,1,2,3.0,4,
1,two,5,6,,8,world
2,three,9,10,11.0,12,foo
还可以使⽤其他分隔符(由于这⾥直接写出到sys.stdout,打印出⽂本结果):
import sys
data.to_csv(sys.stdout, sep=':') # 输出结果如下:
:something:a:b:c:d:message
0:one:1:2:3.0:4:
1:two:5:6::8:world
2:three:9:10:11.0:12:foo
缺失值在输出结果中会被表示为空字符串。你可能希望将其表示为别的标记值:
data.to_csv(sys.stdout, na_rep='NULL') # 设置缺省值的输出结果
,something,a,b,c,d,message
0,one,1,2,3.0,4,NULL
1,two,5,6,NULL,8,world
2,three,9,10,11.0,12,foo
如果没有设置其他选项,则会写出⾏和列的标签。当然,它们也都可以被禁⽤:
data.to_csv(sys.stdout, index=False, header=False) # 禁用行和列标签
one,1,2,3.0,4,
two,5,6,,8,world
three,9,10,11.0,12,foo
此外,你还可以只写出⼀部分的列,并以你指定的顺序排列:
data.to_csv(sys.stdout, index=False, columns=['a', 'b', 'c']) # 输出指定的列,并按指定的顺序排列
a,b,c
1,2,3.0
5,6,
9,10,11.0
Series也有⼀个to_csv⽅法:
dates = pd.date_range('1/1/2000', periods=7)
ts = pd.Series(np.arange(7), index=dates)
ts.to_csv('examples/my_tseries.csv')
!type examples\\my_tseries.csv # 结果输出如下:
2000-01-01,0
2000-01-02,1
2000-01-03,2
2000-01-04,3
2000-01-05,4
2000-01-06,5
2000-01-07,6
3、处理分隔符格式
pandas.read_table可加载大部分表格型数据,然⽽,有时还是需要做⼀些⼿⼯处理。由于接收到含有畸形⾏的⽂件⽽使read_table出⽑病的情况并不少⻅。为了说明这些基本⼯具,看看下⾯这个简单的CSV⽂件:
!type examples\\ex7.csv # 原文件内容如下:
"a","b","c"
"1","2","3"
"1","2","3"
对于任何单字符分隔符⽂件,可以直接使⽤Python内置的csv模块。将任意已打开的⽂件或⽂件型的对象传给csv.reader:
import csv
f = open('examples/ex7.csv')
reader = csv.reader(f)
对这个reader进⾏迭代将会为每⾏产⽣⼀个元组(并移除了所有的引号):
for line in reader:
print(line) # 输出如下:
['a', 'b', 'c']
['1', '2', '3']
['1', '2', '3']
为了使数据格式合乎要求,你需要对其做⼀些整理⼯作。我们⼀步⼀步来做。
⾸先,读取⽂件到⼀个多⾏的列表中:
with open('examples/ex7.csv') as f:
lines = list(csv.reader(f)) # 将reader对象转换为列表
然后,我们将这些⾏分为标题⾏和数据⾏:
header, values = lines[0], lines[1:]
然后,我们可以⽤字典构造式和zip(*values),后者将⾏转置为列,创建数据列的字典:
data_dict = {h:v for h,v in zip(header, zip(*values))}
data_dict # 输出:{'a': ('1', '1'), 'b': ('2', '2'), 'c': ('3', '3')}
CSV⽂件的形式有很多。只需定义csv.Dialect的⼀个⼦类即可定义出新格式(如专⻔的分隔符、字符串引⽤约定、⾏结束符等):
class my_dialect(csv.Dialect):
lineterminator = '\n'
delimiter = ';'
quotechar = '"'
quoting = csv.QUOTE_MINIMAL
reader = csv.reader(f, dialect=my_dialect)
各个CSV语⽀的参数也可以关键字的形式提供给csv.reader,⽽⽆需定义⼦类:
reader = csv.reader(f, delimiter='|')
可⽤的选项(csv.Dialect的属性)及其功能如表6-3所示(CSV语支选项)
使⽤复杂分隔符或多字符分隔符的⽂件,csv模块就⽆能为⼒了。这种情况下,就只能使⽤字符串的split⽅法或正则表达式⽅法re.split进⾏⾏拆分和其他整理⼯作。
要⼿⼯输出分隔符⽂件,可使⽤csv.writer。它接受⼀个已打开且可写的⽂件对象以及跟csv.reader相同的那些语⽀和格式化选项:
with open('examples/mydata.csv', 'w') as f: # 写入到文件mydata.csv
writer = csv.writer(f, dialect=my_dialect) # 创建csv文件的写入对象,用对象去调用写入方法
writer.writerow(('one', 'two', 'three'))
writer.writerow(('1', '2', '3'))
writer.writerow(('4', '5', '6'))
writer.writerow(('7', '8', '9'))
4、JSON数据
JSON(JavaScript Object Notation的简称)已经成为通过HTTP请求在Web浏览器和其他应⽤程序之间发送数据的标准格式之⼀。它是⼀种⽐表格型⽂本格式(如CSV)灵活得多的数据格式。下⾯是⼀个例⼦:
obj = """
{"name": "Wes",
"places_lived": ["United States", "Spain", "Germany"],
"pet": null,
"siblings": [{"name": "Scott", "age": 30, "pets": ["Zeus", "Zuko"]},
{"name": "Katie", "age": 38, "pets": ["Sixes", "Stache", "Cisco"]}]
}"""
除其空值null和一些其他的细微差别(如列表末尾不允许存在多余的逗号)之外,JSON非常接近于有效的Python代码。基本类型有对象(字典)、数组(列表)、字符串、数值、布尔值以及null。对象中所有的键都必须是字符串。许多Python库都可以读写JSON数据。我将使用json,因为它是构建于Python标准库中的。通过json.loads即可将JSON字符串转换成Python形式:
import json
result = json.loads(obj) # JSON字符串转换成Python形式
result # 输出如下:
{'name': 'Wes',
'places_lived': ['United States', 'Spain', 'Germany'],
'pet': None,
'siblings': [{'name': 'Scott', 'age': 30, 'pets': ['Zeus', 'Zuko']},
{'name': 'Katie', 'age': 38, 'pets': ['Sixes', 'Stache', 'Cisco']}]}
json.dumps则将Python对象转换成JSON格式:
asjson = json.dumps(result)
如何将(⼀个或⼀组)JSON对象转换为DataFrame或其他便于分析的数据结构就由需求来定。最简单⽅便的⽅式是:向DataFrame构造器传⼊⼀个字典的列表(就是原先的JSON对象),并选取数据字段的⼦集:
siblings = pd.DataFrame(result['siblings'], columns=['name', 'age']) # 根据需要提取数据
siblings # 输出如下:
name age
0 Scott 30
1 Katie 38
pandas.read_json可以⾃动将特别格式的JSON数据集转换为Series或DataFrame。例如:
!type examples\\example.json # 原文件内容如下
[{"a": 1, "b": 2, "c": 3},
{"a": 4, "b": 5, "c": 6},
{"a": 7, "b": 8, "c": 9}]
pandas.read_json的默认选项假设JSON数组中的每个对象是表格中的⼀⾏:
data = pd.read_json('examples/example.json')
data # 输出如下:
a b c
0 1 2 3
1 4 5 6
2 7 8 9
要将数据从pandas输出到JSON,可以使⽤to_json⽅法:
print(data.to_json()) # 输出如下:(以字典方式写入)
{"a":{"0":1,"1":4,"2":7},"b":{"0":2,"1":5,"2":8},"c":{"0":3,"1":6,"2":9}}
print(data.to_json(orient='records')) # 输出如下:(以字典列表方式写入)
[{"a":1,"b":2,"c":3},{"a":4,"b":5,"c":6},{"a":7,"b":8,"c":9}]
5、XML和HTML:Web信息收集
Python有许多可以读写常⻅的HTML和XML格式数据的库,包括lxml、Beautiful Soup和html5lib。lxml的速度⽐较快,但其它的库处理有误的HTML或XML⽂件更好。
pandas有⼀个内置的功能,read_html,它可以使⽤lxml和Beautiful Soup⾃动将HTML⽂件中的表格解析为DataFrame对象。为进一步实验,先安装read_html要用到的库:
conda install lxml
pip install beautifulsoup4 html5lib
不用conda,可以使⽤pip install lxml
pandas.read_html有⼀些选项,默认条件下,它会搜索、尝试解析<table>标签内的的表格数据。结果是⼀个列表的DataFrame对象:
tables = pd.read_html('examples/fdic_failed_bank_list.html')
len(tables) # 输出:1
failures = tables[0]
failures.head() # 输出如下:
Bank Name City ST CERT \
0 Allied Bank Mulberry AR 91
1 The Woodbury Banking Company Woodbury GA 11297
2 First CornerStone Bank King of Prussia PA 35312
3 Trust Company Bank Memphis TN 9956
4 North Milwaukee State Bank Milwaukee WI 20364
Acquiring Institution Closing Date Updated Date
0 Today's Bank September 23, 2016 November 17, 2016
1 United Bank August 19, 2016 November 17, 2016
2 First-Citizens Bank & Trust Company May 6, 2016 September 6, 2016
3 The Bank of Fayette County April 29, 2016 September 6, 2016
4 First-Citizens Bank & Trust Company March 11, 2016 June 16, 2016
因为failures有许多列,pandas插⼊了⼀个换⾏符 \ 。
这⾥,我们可以做⼀些数据清洗和分析,⽐如计算按年份计算倒闭的银⾏数:
close_timestamps = pd.to_datetime(failures['Closing Date'])
close_timestamps.dt.year.value_counts() # 输出如下:
2010 157
2009 140
2011 92
2012 51
2008 25
...
2004 4
2001 4
2007 3
2003 3
2000 2
Name: Closing Date, Length: 15, dtype: int64
6、利⽤lxml.objectify解析XML
XML(Extensible Markup Language)是另⼀种常⻅的⽀持分层、嵌套数据以及元数据的结构化数据格式。
XML和HTML的结构很相似,但XML更为通⽤。这⾥,⽤⼀个例⼦演示如何利⽤lxml从XML格式解析数据。
现有一个xml文件Performance_MNR.xml,内容如下所示:
<INDICATOR>
<INDICATOR_SEQ>373889</INDICATOR_SEQ>
<PARENT_SEQ></PARENT_SEQ>
<AGENCY_NAME>Metro-North Railroad</AGENCY_NAME>
<INDICATOR_NAME>Escalator Availability</INDICATOR_NAME>
<DESCRIPTION>Percent of the time that escalators are operational
systemwide. The availability rate is based on physical observations performed
the morning of regular business days only. This is a new indicator the agency
began reporting in 2009.</DESCRIPTION>
<PERIOD_YEAR>2011</PERIOD_YEAR>
<PERIOD_MONTH>12</PERIOD_MONTH>
<CATEGORY>Service Indicators</CATEGORY>
<FREQUENCY>M</FREQUENCY>
<DESIRED_CHANGE>U</DESIRED_CHANGE>
<INDICATOR_UNIT>%</INDICATOR_UNIT>
<DECIMAL_PLACES>1</DECIMAL_PLACES>
<YTD_TARGET>97.00</YTD_TARGET>
<YTD_ACTUAL></YTD_ACTUAL>
<MONTHLY_TARGET>97.00</MONTHLY_TARGET>
<MONTHLY_ACTUAL></MONTHLY_ACTUAL>
</INDICATOR>
我们先⽤lxml.objectify解析该⽂件,然后通过getroot得到该XML⽂件的根节点的引⽤:
from lxml import objectify
path = 'examples/mta_perf/Performance_MNR.xml'
parsed = objectify.parse(open(path))
root = parsed.getroot()
root.INDICATOR返回⼀个⽤于产⽣各个<INDICATOR>XML元素的⽣成器。对于每条记录,我们可以⽤标记名(如YTD_ACTUAL)和数据值填充⼀个字典(排除⼏个标记):
data = []
skip_fields = ['PARENT_SEQ', 'INDICATOR_SEQ', 'DESIRED_CHANGE', 'DECIMAL_PLACES']
for elt in root: # 原代码:for elt in root.INDICATOR:
el_data = {}
for child in elt.getchildren():
if child.tag in skip_fields:
continue
el_data[child.tag] = child.pyval
data.append(el_data)
最后,将这组字典转换为⼀个DataFrame:
perf = pd.DataFrame(data)
perf.head() # 输出如下:
AGENCY_NAME CATEGORY \
0 Metro-North Railroad Service Indicators
DESCRIPTION FREQUENCY \
0 Percent of the time that escalators are operat... M
INDICATOR_NAME ... MONTHLY_TARGET PERIOD_MONTH \
0 Escalator Availability ... 97.0 12
PERIOD_YEAR YTD_ACTUAL YTD_TARGET
0 2011 97.0
[1 rows x 12 columns]
XML数据可以复杂得多。每个标记都可以有元数据。看看这个HTML的链接标签(它也算是⼀段有效的XML):
from io import StringIO
tag = '<a href="http://www.google.com">Google</a>'
root = objectify.parse(StringIO(tag)).getroot()
现在就可以访问标签或链接⽂本中的任何字段了(如href):
root # 输出:<Element a at 0x23eada549c8>
root.get('href') # 输出:'http://www.google.com'
root.text # 输出:'Google'
二、⼆进制数据格式
1、pandas.read_pickle,pandas.to_pickle
实现数据的⾼效⼆进制格式存储最简单的办法之⼀是使⽤Python内置的pickle序列化。
pandas对象有⼀个⽤于将数据以pickle格式保存到磁盘上的to_pickle⽅法:
frame = pd.read_csv('examples/ex1.csv')
frame # 输出如下:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
frame.to_pickle('examples/frame_pickle')
你可以通过pickle直接读取被pickle化的数据,或是使⽤更为⽅便的pandas.read_pickle:
pd.read_pickle('examples/my_frame_pickle') # 输出如下:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
注意:pickle仅建议用于短期存储格式。因为该格式不是永远是稳定的;今天pickle的对象可能无法被后续版本的库unpickle出来。在pandas中,在今后的某个时候说不定还是得“打破”该pickle格式。
pandas内置⽀持两个⼆进制数据格式:HDF5和MessagePack。
pandas或NumPy数据的其它存储格式有:
bcolz:⼀种可压缩的列存储⼆进制格式,基于Blosc压缩库。
Feather:作者与R语⾔社区的Hadley Wickham设计的⼀种跨语⾔的列存储⽂件格式。Feather使⽤了Apache Arrow的列式内存格式。
2、使⽤HDF5格式
HDF5是⼀种存储⼤规模科学数组数据的⾮常好的⽂件格式。它可以被作为C库,带有许多语⾔的接⼝,如Java、Python和MATLAB等。HDF5中的HDF指的是层次型数据格式(hierarchical data format)。每个HDF5⽂件都含有⼀个⽂件系统式的节点结构,它使你能够存储多个数据集并⽀持元数据。与其他简单格式相⽐,HDF5⽀持多种压缩器的即时压缩,还能更⾼效地存储重复模式数据。对于那些⾮常⼤的⽆法直接放⼊内存的数据集,HDF5就是不错的选择,因为它可以⾼效地分块读写。
⽤PyTables或h5py库可直接访问HDF5⽂件,pandas提供了更为⾼级的接⼝,可以简化存储Series和DataFrame对象。HDFStore类可以像字典⼀样,处理低级的细节:
frame = pd.DataFrame({'a': np.random.randn(100)})
store = pd.HDFStore('mydata.h5') # 在当前工作目录下会生成文件mydata.h5
store['obj1'] = frame # 数据存入文件,列名是'a'
store['obj1_col'] = frame['a'] # 数据存入文件,无列名
store # 输出如下:
<class 'pandas.io.pytables.HDFStore'>
File path: mydata.h5
HDF5⽂件中的对象可以通过与字典⼀样的API进⾏获取:
store['obj1'] # 输出如下:
a
0 -0.887456
1 0.986413
2 0.840563
3 -0.017308
4 0.286415
.. ...
95 0.544270
96 0.331259
97 0.585923
98 1.170877
99 0.791187
[100 rows x 1 columns]
HDFStore⽀持两种存储模式,'fixed'和'table'。后者会更慢,但⽀持使⽤特殊语法进⾏查询操作:
store.put('obj2', frame, format='table') # 将frame中的数据以obj2为列名以table形式存入store所指向的文件中(mydata.h5)
store.select('obj2', where=['index >= 10 and index <=15']) # 读取指定的行,输出如下:
a
10 -0.238414
11 -0.755672
12 -0.309194
13 0.612262
14 0.187334
15 1.103517
store.close() # 关闭文件
put是store['obj2'] = frame⽅法的显示版本,允许我们设置其它的选项,⽐如格式。
pandas.read_hdf函数可以快捷使⽤这些⼯具:
frame.to_hdf('mydata.h5', 'obj3', format='table') # 将frame中的数据以obj3为列名以table形式存入未打开的mydata.h5文件中
pd.read_hdf('mydata.h5', 'obj3', where=['index < 5']) # 如果文件未关闭,读取会失败
注意:如果你要处理的数据位于远程服务器,⽐如AmazonS3或HDFS,使⽤专⻔为分布式存储(⽐如ApacheParquet)的⼆进制格式也许更加合适。
如果需要本地处理海量数据,建议先研究⼀下PyTables和h5py,看看能满⾜哪些需求。由于许多数据分析问题都是IO密集型(⽽不是CPU密集型),利⽤HDF5这样的⼯具能显著提升应⽤程序的效率。
注意:HDF5不是数据库。它最适合⽤作“⼀次写多次读”的数据集。虽然数据可以在任何时候被添加到⽂件中,但如果同时发⽣多个写操作,⽂件就可能会被破坏。
3、读取Microsoft Excel⽂件
pandas的ExcelFile类或pandas.read_excel函数⽀持读取存储在Excel 2003(或更⾼版本)中的表格型数据。这两个⼯具分别使⽤扩展包xlrd和openpyxl读取XLS和XLSX⽂件。可以⽤pip或conda安装它们。
要使⽤ExcelFile,通过传递xls或xlsx路径创建⼀个实例:
xlsx = pd.ExcelFile('examples/ex1.xlsx') # 创建一个ExcelFile类实例
存储在表单中的数据可以read_excel读取到DataFrame
pd.read_excel(xlsx, 'Sheet1') # 输出如下:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
要读取⼀个⽂件中的多个表单,创建ExcelFile会更快,也可以将⽂件名传递到pandas.read_excel:
frame = pd.read_excel('examples/ex1.xlsx', 'Sheet1') # 不创建实例,直接读取
frame # 输出如下:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
如果要将pandas数据写⼊为Excel格式,你必须⾸先创建⼀个ExcelWriter,然后使⽤pandas对象的to_excel⽅法将数据写⼊到其中:
writer = pd.ExcelWriter('examples/ex2.xlsx') # 创建文件句柄
frame.to_excel(writer, 'Sheet1') # 将frame写入writer文件
writer.save() # 保存
你还可以不使⽤ExcelWriter,⽽是传递⽂件的路径到to_excel:
frame.to_excel('examples/ex2.xlsx')
三、Web APIs交互
许多⽹站都有⼀些通过JSON或其他格式提供数据的公共API。通过Python访问这些API的办法有不少。⼀个简单易⽤的办法是requests包。
为了搜索最新的30个GitHub上的pandas主题,我们可以发⼀个HTTP GET请求,使⽤requests扩展库:
import requests
url = 'https://api.github.com/repos/pandas-dev/pandas/issues'
resp = requests.get(url)
resp # 输出: <Response [200]>,输出200表示请求成功
响应对象的json⽅法会返回⼀个包含被解析过的JSON字典,加载到⼀个Python对象中:
data = resp.json()
data[0]['title'] # 输出:'DOC: Use a standard header for all rst files'
data中的每个元素都是⼀个包含所有GitHub主题⻚数据(不包含评论)的字典。我们可以直接传递数据到DataFrame,并提取感兴趣的字段:
issues = pd.DataFrame(data, columns=['number', 'title', 'labels', 'state'])
issues # 输出如下:
number title \
0 24086 DOC: Use a standard header for all rst files
1 24085 DataFrames don't handle input arrays of dtype ...
2 24084 PERF: consolidate imports inside parse_time_st...
3 24082 Handle utc and box parameters for to_datetime
4 24079 DataFrame.fillna() fails with categorical colu...
.. ... ...
25 24047 Add test for rdivmod on EA array (GH23287)
26 24046 API: capabilities of df.set_index
27 24043 ENH: Support datetime.time objects with tzinfo...
28 24036 Clarify how to cite pandas in scientific papers
29 24034 ENH: Add columns argument to read_feather() (#...
labels state
0 [] open
1 [] open
2 [{'id': 8935311, 'node_id': 'MDU6TGFiZWw4OTM1M... open
3 [] open
4 [] open
.. ... ...
25 [{'id': 849023693, 'node_id': 'MDU6TGFiZWw4NDk... open
26 [{'id': 35818298, 'node_id': 'MDU6TGFiZWwzNTgx... open
27 [{'id': 76812, 'node_id': 'MDU6TGFiZWw3NjgxMg=... open
28 [{'id': 134699, 'node_id': 'MDU6TGFiZWwxMzQ2OT... open
29 [{'id': 2301354, 'node_id': 'MDU6TGFiZWwyMzAxM... open
[30 rows x 4 columns]
通过一些操作,你就可以创建⼀些更⾼级的常⻅的Web API的接⼝,返回DataFrame对象,⽅便进⾏分析。
四、数据库交互
在商业场景下,⼤多数数据可能不是存储在⽂本或Excel⽂件中。基于SQL的关系型数据库(如SQL Server、PostgreSQL和MySQL等)使⽤⾮常⼴泛,其它⼀些数据库也很流⾏。数据库的选择通常取决于性能、数据完整性以及应⽤程序的伸缩性需求。
将数据从SQL加载到DataFrame的过程很简单,此外pandas还有⼀些能够简化该过程的函数。
例如,使⽤SQLite数据库(通过Python内置的sqlite3驱动器):
import sqlite3
query = """
CREATE TABLE test
(a VARCHAR(20), b VARCHAR(20),
c REAL, d INTEGER);"""
con = sqlite3.connect('mydata.sqlite') # 创建数据库,会在当前工作目录生成mydata.sqlite文件
con.execute(query) # 输出:<sqlite3.Cursor at 0x2918b215b90>,执行命令query
con.commit() # 提交命令并执行,在数据库中创建表格
然后插⼊⼏⾏数据:
data = [('Atlanta', 'Georgia', 1.25, 6),
('Tallahassee', 'Florida', 2.6, 3),
('Sacramento', 'California', 1.7, 5)]
stmt = "INSERT INTO test VALUES(?, ?, ?, ?)" # 创建数据库test的插入命令
con.executemany(stmt, data) # 输出:<sqlite3.Cursor at 0x2918d1cd960>
con.commit() # 提交命令并执行
从表中选取数据时,⼤部分Python SQL驱动器(PyODBC、psycopg2、MySQLdb、pymssql等)都会返回⼀个元组列表:
cursor = con.execute('select * from test') # 执行查询命令并将结果返回给cursor变量
rows = cursor.fetchall() # 接收返回结果集
rows # 输出如下:
[('Atlanta', 'Georgia', 1.25, 6),
('Tallahassee', 'Florida', 2.6, 3),
('Sacramento', 'California', 1.7, 5)]
你可以将这个元组列表传给DataFrame构造器,但还需要列名(位于游标(cursor)的description属性中):
cursor.description # 输出如下:(查看游标的列名)
(('a', None, None, None, None, None, None),
('b', None, None, None, None, None, None),
('c', None, None, None, None, None, None),
('d', None, None, None, None, None, None))
pd.DataFrame(rows, columns = [x[0] for x in cursor.description]) # 输出如下:
a b c d
0 Atlanta Georgia 1.25 6
1 Tallahassee Florida 2.60 3
2 Sacramento California 1.70 5
这种数据规整操作相当多,每查询一次就重写一次很麻烦。SQLAlchemy项⽬是⼀个流⾏的Python SQL⼯具,它抽象出了SQL数据库中的许多常⻅差异。pandas有⼀个read_sql函数,可以让你轻松的从SQLAlchemy连接读取数据。这⾥,我们⽤SQLAlchemy连接SQLite数据库,并从之前创建的表读取数据:
import sqlalchemy as sqla # 使用sqlalchemy
db = sqla.create_engine('sqlite:///mydata.sqlite') # 创建引擎,指向数据库
pd.read_sql('select * from test', db) # 根据命令从指定数据库中获取数据
a b c d
0 Atlanta Georgia 1.25 6
1 Tallahassee Florida 2.60 3
2 Sacramento California 1.70 5