输入输出通常可以划分为几个大类:读取文本文件和其他更高效的磁盘存储格式,加载数据库中的数据,利用Web API操作网络资源。
6.1 读写文本格式的数据
pandas提供了一些用于将表格型数据读取为DataFrame对象的函数。表6-1对它们进行了总结,其中read_csv和read_table可能会是你今后用得最多的。
表6-1 pandas中的解析函数我将大致介绍一下这些函数在将文本数据转换为DataFrame时所用到的一些技术。这些函数的选项可以划分为以下几个大类:
- 索引:将一个或多个列当做返回的DataFrame处理,以及是否从文件、用户获取列名。
- 类型推断和数据转换:包括用户定义值的转换、和自定义的缺失值标记列表等。
- 日期解析:包括组合功能,比如将分散在多个列中的日期时间信息组合成结果中的单个列。
- 迭代:支持对大文件进行逐块迭代。
- 不规整数据问题:跳过一些行、页脚、注释或其他一些不重要的东西(比如由成千上万个逗号隔开的数值数据)。
因为工作中实际碰到的数据可能十分混乱,一些数据加载函数(尤其是read_csv)的选项逐渐变得复杂起来。面对不同的参数,感到头痛很正常(read_csv有超过50个参数)。pandas文档有这些参数的例子,如果你感到阅读某个文件很难,可以通过相似的足够多的例子找到正确的参数。
其中一些函数,比如pandas.read_csv,有类型推断功能,因为列数据的类型不属于数据类型。也就是说,你不需要指定列的类型到底是数值、整数、布尔值,还是字符串。其它的数据格式,如HDF5、Feather和msgpack,会在格式中存储数据类型。
日期和其他自定义类型的处理需要多花点工夫才行。首先我们来看一个以逗号分隔的(CSV)文本文件:
import pandas as pd
basic_address = '../examples/'
df = pd.read_csv(basic_address + 'ex1.csv')
print(df)
print('------我们也可以用read_table来指定分隔符---------')
# 我们也可以用read_table来指定分隔符:
df = pd.read_table('../examples/ex1.csv', sep=',')
print(df)
print('------一个文件不会总是有header row(页首行),考虑下面的文件:-------------')
# 一个文件不会总是有header row(页首行),考虑下面的文件:
df = pd.read_csv(basic_address + 'ex2.csv', header=None)
print(df)
df = pd.read_csv('../examples/ex2.csv', names=['a', 'b', 'c', 'd', 'message'])
print(df)
print('---------如果想要从多列从构建一个hierarchical index(阶层型索引),传入一个包含列名的list:---------')
# 如果想要从多列从构建一个hierarchical index(阶层型索引),传入一个包含列名的list:
parsed = pd.read_csv(basic_address +'csv_mindex.csv',
index_col=['key1', 'key2'])
print(parsed)
因为列名比行的数量少,所以read_table推测第一列应该是dataframe的index。
这个解析器功能有很多其他参数能帮你解决遇到文件格式异常的问题(可以见之后的表格)。比如,我们要跳过第一、三、四行,使用skiprows:
pd.read_csv(basic_address + 'ex4.csv')
Out[256]:
# hey!
a b c d message
# just wanted to make things more difficult for... NaN NaN NaN NaN
# who reads CSV files with computers anyway? NaN NaN NaN
1 2 3 4 hello
5 6 7 8 world
9 10 11 12 foo
pd.read_csv(basic_address + 'ex4.csv',skiprows=[0,2,3])
Out[258]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
对于缺失值,pandas使用一些sentinel value(标记值)来代表,比如NA和NULL:
result = pd.read_csv(basic_address + 'ex5.csv')
result
Out[260]:
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)
Out[261]:
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选项能把我们传入的字符识别为NA,导入必须是list:
result = pd.read_csv(basic_address+'ex5.csv', na_values=['NULL'])
result
Out[263]:
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']}
# 把message列中的foo和NA识别为NA,把something列中的two识别为NA
pd.read_csv(basic_address + 'ex5.csv', na_values=sentinels)
Out[264]:
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
面是pandas.read_csv和pandas.read_table中一些常用的选项:
img1 Reading Text Files in Pieces(读取一部分文本)
于一些比较大的文件,我们想要一次读取一小部分,或者每次迭代一小部分。在我们看一个比较大的文件前,先设置一下pandas中显示的数量:
pd.options.display.max_rows = 10 #设置显示数量
result = pd.read_csv(basic_address + 'ex6.csv')
result
Out[267]:
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
如果只是想要读取前几行(不读取整个文件),指定一下nrows:
pd.read_csv(basic_address + 'ex6.csv', nrows=5)
Out[268]:
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(basic_address + 'ex6.csv', chunksize=1000)
chunker
Out[272]: <pandas.io.parsers.TextFileReader at 0x2018631b048>
pandas返回的TextParser object能让我们根据chunksize每次迭代文件的一部分。比如,我们想要迭代ex6.csv, 计算key列的值的综合:
chunker = pd.read_csv(basic_address + 'ex6.csv', chunksize=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]
Out[276]:
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格式:
n [281]: data = pd.read_csv(basic_address + 'ex5.csv')
data
Out[282]:
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
data.to_csv(basic_address + 'out.csv')
其他一些分隔符也可以使用(使用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/tseries.csv')
3 处理分隔符格式
对于大部分磁盘中的表格型数据,用pandas.read_table就能解决。不过,有时候一些人工的处理也是需要的。
当然,有时候,一些格式不正确的行能会把read_table绊倒。为了展示一些基本用法,这里先考虑一个小的CSV文件:
对于单个字符的分隔符,可以使用python内建的csv方法。只要给csv.reader一个打开的文件即可:
import csv
f = open(basic_address + 'ex7.csv')
reader = csv.reader(f)
迭代这个reader:
for line in reader:
print(line)
['a', 'b', 'c']
['1', '2', '3']
['1', '2', '3']
接下来,我们可以根据自己的需要来处理数据。一步步来,首先,把文件读取成一个list of lines:
with open(basic_address + 'ex7.csv') as f:
lines = list(csv.reader(f))
把lines分成header line和data lines:
header, values = lines[0], lines[1:]
然后我们可以用一个字典表达式来构造一个有列的字典,以及用zip(*values)反转行为列:
data_dict = {h: v for h, v in zip(header, zip(*values))}
data_dict
Out[303]: {'a': ('1', '1'), 'b': ('2', '2'), 'c': ('3', '3')}
header
Out[304]: ['a', 'b', 'c']
print([x for x in zip(*values)])
[('1', '1'), ('2', '2'), ('3', '3')]
CSV有很多功能。我们可以定义一个新的分隔符格式,比如字符串的引号,行结束时的回车,这里我们利用csv.Dialect来构造一个子类:
class my_dialect(csv.Dialect):
lineterminator = '\n'
delimiter = ';'
quotechar = '"'
quoting = csv.QUOTE_MINIMAL
f = open(basic_address + 'ex7.csv')
reader = csv.reader(f, dialect=my_dialect)
当然,也可以设定一个分隔符参数给csv.reader,而不用单独定义一个子类:
reader = csv.reader(f, delimiter='|')
for line in reader:
print(line)
f.close()
['a,"b","c"']
['1,"2","3"']
['1,"2","3"']
img
笔记:对于那些使用复杂分隔符或多字符分隔符的文件,csv模块就无能为力了。这种情况下,你就只能使用字符串的split方法或正则表达式方法re.split进行行拆分和其他整理工作了。
要手工输出分隔符文件,你可以使用csv.writer。它接受一个已打开且可写的文件对象以及跟csv.reader相同的那些语支和格式化选项:
对于一些更复杂的文件,比如用多种字符来做分隔符,就不能知网用csv模块来处理了。这种情况下,要先做string的split,或者用re.split
写入的话,可以用csv.write。它可以写入与csv.reader中设定一样的文件:
with open('../examples/mydata.csv', 'w') as f:
writer = csv.writer(f, dialect=my_dialect)
writer.writerow(('one', 'two', 'three'))
writer.writerow(('1', '2', '3'))
writer.writerow(('4', '5', '6'))
writer.writerow(('7', '8', '9'))
4 JSON Data
JSON (short for JavaScript Object Notation)已经是发送HTTP请求的标准数据格式了。这种格式比起表个性的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"]}]
}
"""
JSON是很接近python代码的,除了他的缺失值为null和一些其他的要求。基本的类型是object(dicts), array(lists), strings, numbers, booleans, and nulls. 所以的key必须是string。有很多读取JSON的库,这里用json,它也是python内建的库。把JSON string变为python格式,用json.loads:
import json
result = json.loads(obj)
result
Out[314]:
{'name': 'Wes',
'pet': None,
'places_lived': ['United States', 'Spain', 'Germany'],
'siblings': [{'age': 30, 'name': 'Scott', 'pets': ['Zeus', 'Zuko']},
{'age': 38, 'name': 'Katie', 'pets': ['Sixes', 'Stache', 'Cisco']}]}
使用json.dumps,可以把python object转换为JSON:
asjson = json.dumps(result)
如何把JSON转变为DataFrame或其他一些结构呢。可以把a list of dicts(JSON object)传给DataFrame constructor而且可以自己指定传入的部分:
siblings = pd.DataFrame(result['siblings'], columns=['name', 'age'])
siblings
Out[315]:
name age
0 Scott 30
1 Katie 38
pandas.read_json可以自动把JSON数据转变为series或DataFrame:
pandas.read_json假设JSON数组中的每一个Object,是表格中的一行:
data = pd.read_json(basic_address + 'example.json')
data
a b c
0 1 2 3
1 4 5 6
2 7 8 9
如果想要输出结果为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}}
orient='records'
表示输出的数据结构是 列->值 的形式:
records : list like [{column -> value}, ... , {column -> value}]
5 XML and HTML: Web Scraping (网络爬取)
python有很多包用来读取和写入HTML和XML格式。比如:lxml, Beautiful Soup, html5lib。其中lxml比较快,其他一些包则能更好的处理一些复杂的HTML和XML文件。
pandas有一个内建的函数,叫read_html, 这个函数利用lxml和Beautiful Soup这样的包来自动解析HTML,变为DataFrame。
pandas.read_html函数有很多额外选项,但是默认会搜索并试图解析含有<tagble>
tag的表格型数据。结果是a list of dataframe:
tables = pd.read_html('../examples/fdic_failed_bank_list.html')
failures = tables[0]
failures.head()
Out[326]:
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
这里我们做一些数据清洗和分析,比如按年计算bank failure的数量:
failures = tables[0]
failures.head()
Out[96]:
Bank Name City ST CERT Acquiring Institution Closing Date Updated Date
0 Allied Bank Mulberry AR 91 Today's Bank September 23, 2016 November 17, 2016
1 The Woodbury Banking Company Woodbury GA 11297 United Bank August 19, 2016 November 17, 2016
2 First CornerStone Bank King of Prussia PA 35312 First-Citizens Bank & Trust Company May 6, 2016 September 6, 2016
3 Trust Company Bank Memphis TN 9956 The Bank of Fayette County April 29, 2016 September 6, 2016
4 North Milwaukee State Bank Milwaukee WI 20364 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()
Out[329]:
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
利用lxml.objectify解析XML
XML(eXtensible Markup Language)是另一种常见的数据格式,支持阶层型、嵌套的数据。这里我们演示如何用lxml来解析一个XML格式文件。
纽约都会交通局发布了巴士和地铁的时间表。每一个地跌或巴士都有一个不同的文件(比如Performance_NNR.xml对应Metro-North Railroad)
这里,我们将看看包含在一组XML文件中的运行情况数据。每项列车或公交服务都有各自的文件(如Metro-North Railroad的文件是Performance_MNR.xml),其中每条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 = data_address + 'mta_perf/Performance_MNR.xml'
parsed = objectify.parse(open(path))
root = parsed.getroot()
root.INDICATOR 返回一个生成器,每次调用能生成一个<INDICATOR>
XML元素。每一个记录,我们产生一个dict,tag name(比如YTD_ACTUAL)作为字典的key:
data = []
skip_fields = ['PARENT_SEQ', 'INDICATOR_SEQ',
'DESIRED_CHANGE', 'DECIMAL_PLACES']
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)
然后我们把这个dict变为DataFrame:
perf = pd.DataFrame(data)
perf.head()
AGENCY_NAME CATEGORY DESCRIPTION FREQUENCY INDICATOR_NAME INDICATOR_UNIT MONTHLY_ACTUAL MONTHLY_TARGET PERIOD_MONTH PERIOD_YEAR YTD_ACTUAL YTD_TARGET
0 Metro-North Railroad Service Indicators Percent of commuter trains that arrive at thei... M On-Time Performance (West of Hudson) % 96.9 95 1 2008 96.9 95
1 Metro-North Railroad Service Indicators Percent of commuter trains that arrive at thei... M On-Time Performance (West of Hudson) % 95 95 2 2008 96 95
2 Metro-North Railroad Service Indicators Percent of commuter trains that arrive at thei... M On-Time Performance (West of Hudson) % 96.9 95 3 2008 96.3 95
3 Metro-North Railroad Service Indicators Percent of commuter trains that arrive at thei... M On-Time Performance (West of Hudson) % 98.3 95 4 2008 96.8 95
4 Metro-North Railroad Service Indicators Percent of commuter trains that arrive at thei... M On-Time Performance (West of Hudson) % 95.8 95 5 2008 96.6 95
XML数据能得到比这个例子更复杂的情况。每个tag都有数据。比如一个而HTML链接,也是一个有效的XML:
from io import StringIO
tag = '<a href="http://www.google.com">Google</a>'
root = objectify.parse(StringIO(tag)).getroot()
我们可以访问任何区域的tag(比如href):
from io import StringIO
tag = '<a href="http://www.google.com">Google</a>'
root = objectify.parse(StringIO(tag)).getroot()
root
Out[339]: <Element a at 0x2018a0a7a88>
root.get('href')
Out[340]: 'http://www.google.com'
root.text
Out[341]: 'Google'
网友评论