第1章 準備工作
第2章 Python語法基礎,IPython和Jupyter
第3章 Python的數(shù)據(jù)結(jié)構(gòu)、函數(shù)和文件
第4章 NumPy基礎:數(shù)組和矢量計算
第5章 pandas入門
第6章 數(shù)據(jù)加載、存儲與文件格式
第7章 數(shù)據(jù)清洗和準備
第8章 數(shù)據(jù)規(guī)整:聚合、合并和重塑
第9章 繪圖和可視化
第10章 數(shù)據(jù)聚合與分組運算
第11章 時間序列
第12章 pandas高級應用
第13章 Python建模庫介紹
第14章 數(shù)據(jù)分析案例
附錄A NumPy高級應用
附錄B 更多關(guān)于IPython的內(nèi)容(完)
訪問數(shù)據(jù)是使用本書所介紹的這些工具的第一步。我會著重介紹pandas的數(shù)據(jù)輸入與輸出,雖然別的庫中也有不少以此為目的的工具。
輸入輸出通??梢詣澐譃閹讉€大類:讀取文本文件和其他更高效的磁盤存儲格式,加載數(shù)據(jù)庫中的數(shù)據(jù),利用Web API操作網(wǎng)絡資源。
6.1 讀寫文本格式的數(shù)據(jù)
pandas提供了一些用于將表格型數(shù)據(jù)讀取為DataFrame對象的函數(shù)。表6-1對它們進行了總結(jié),其中read_csv和read_table可能會是你今后用得最多的。

我將大致介紹一下這些函數(shù)在將文本數(shù)據(jù)轉(zhuǎn)換為DataFrame時所用到的一些技術(shù)。這些函數(shù)的選項可以劃分為以下幾個大類:
- 索引:將一個或多個列當做返回的DataFrame處理,以及是否從文件、用戶獲取列名。
- 類型推斷和數(shù)據(jù)轉(zhuǎn)換:包括用戶定義值的轉(zhuǎn)換、和自定義的缺失值標記列表等。
- 日期解析:包括組合功能,比如將分散在多個列中的日期時間信息組合成結(jié)果中的單個列。
- 迭代:支持對大文件進行逐塊迭代。
- 不規(guī)整數(shù)據(jù)問題:跳過一些行、頁腳、注釋或其他一些不重要的東西(比如由成千上萬個逗號隔開的數(shù)值數(shù)據(jù))。
因為工作中實際碰到的數(shù)據(jù)可能十分混亂,一些數(shù)據(jù)加載函數(shù)(尤其是read_csv)的選項逐漸變得復雜起來。面對不同的參數(shù),感到頭痛很正常(read_csv有超過50個參數(shù))。pandas文檔有這些參數(shù)的例子,如果你感到閱讀某個文件很難,可以通過相似的足夠多的例子找到正確的參數(shù)。
其中一些函數(shù),比如pandas.read_csv,有類型推斷功能,因為列數(shù)據(jù)的類型不屬于數(shù)據(jù)類型。也就是說,你不需要指定列的類型到底是數(shù)值、整數(shù)、布爾值,還是字符串。其它的數(shù)據(jù)格式,如HDF5、Feather和msgpack,會在格式中存儲數(shù)據(jù)類型。
日期和其他自定義類型的處理需要多花點工夫才行。首先我們來看一個以逗號分隔的(CSV)文本文件:
In [8]: !cat examples/ex1.csv
a,b,c,d,message
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
筆記:這里,我用的是Unix的cat shell命令將文件的原始內(nèi)容打印到屏幕上。如果你用的是Windows,你可以使用type達到同樣的效果。
由于該文件以逗號分隔,所以我們可以使用read_csv將其讀入一個DataFrame:
In [9]: df = pd.read_csv('examples/ex1.csv')
In [10]: df
Out[10]:
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,并指定分隔符:
In [11]: pd.read_table('examples/ex1.csv', sep=',')
Out[11]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
并不是所有文件都有標題行。看看下面這個文件:
In [12]: !cat examples/ex2.csv
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
讀入該文件的辦法有兩個。你可以讓pandas為其分配默認的列名,也可以自己定義列名:
In [13]: pd.read_csv('examples/ex2.csv', header=None)
Out[13]:
0 1 2 3 4
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
In [14]: pd.read_csv('examples/ex2.csv', names=['a', 'b', 'c', 'd', 'message'])
Out[14]:
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參數(shù)指定"message":
In [15]: names = ['a', 'b', 'c', 'd', 'message']
In [16]: pd.read_csv('examples/ex2.csv', names=names, index_col='message')
Out[16]:
a b c d
message
hello 1 2 3 4
world 5 6 7 8
foo 9 10 11 12
如果希望將多個列做成一個層次化索引,只需傳入由列編號或列名組成的列表即可:
In [17]: !cat 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
In [18]: parsed = pd.read_csv('examples/csv_mindex.csv',
....: index_col=['key1', 'key2'])
In [19]: parsed
Out[19]:
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
有些情況下,有些表格可能不是用固定的分隔符去分隔字段的(比如空白符或其它模式)??纯聪旅孢@個文本文件:
In [20]: list(open('examples/ex3.txt'))
Out[20]:
[' 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']
雖然可以手動對數(shù)據(jù)進行規(guī)整,這里的字段是被數(shù)量不同的空白字符間隔開的。這種情況下,你可以傳遞一個正則表達式作為read_table的分隔符。可以用正則表達式表達為\s+,于是有:
In [21]: result = pd.read_table('examples/ex3.txt', sep='\s+')
In [22]: result
Out[22]:
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
這里,由于列名比數(shù)據(jù)行的數(shù)量少,所以read_table推斷第一列應該是DataFrame的索引。
這些解析器函數(shù)還有許多參數(shù)可以幫助你處理各種各樣的異形文件格式(表6-2列出了一些)。比如說,你可以用skiprows跳過文件的第一行、第三行和第四行:
In [23]: !cat 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
In [24]: pd.read_csv('examples/ex4.csv', skiprows=[0, 2, 3])
Out[24]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
缺失值處理是文件解析任務中的一個重要組成部分。缺失數(shù)據(jù)經(jīng)常是要么沒有(空字符串),要么用某個標記值表示。默認情況下,pandas會用一組經(jīng)常出現(xiàn)的標記值進行識別,比如NA及NULL:
In [25]: !cat 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
In [26]: result = pd.read_csv('examples/ex5.csv')
In [27]: result
Out[27]:
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
In [28]: pd.isnull(result)
Out[28]:
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可以用一個列表或集合的字符串表示缺失值:
In [29]: result = pd.read_csv('examples/ex5.csv', na_values=['NULL'])
In [30]: result
Out[30]:
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標記值:
In [31]: sentinels = {'message': ['foo', 'NA'], 'something': ['two']}
In [32]: pd.read_csv('examples/ex5.csv', na_values=sentinels)
Out[32]:
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常用的選項。



逐塊讀取文本文件
在處理很大的文件時,或找出大文件中的參數(shù)集以便于后續(xù)處理時,你可能只想讀取文件的一小部分或逐塊對文件進行迭代。
在看大文件之前,我們先設置pandas顯示地更緊些:
In [33]: pd.options.display.max_rows = 10
然后有:
In [34]: result = pd.read_csv('examples/ex6.csv')
In [35]: result
Out[35]:
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]
If you want to only read a small
如果只想讀取幾行(避免讀取整個文件),通過nrows進行指定即可:
In [36]: pd.read_csv('examples/ex6.csv', nrows=5)
Out[36]:
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(行數(shù)):
In [874]: chunker = pd.read_csv('ch06/ex6.csv', chunksize=1000)
In [875]: chunker
Out[875]: <pandas.io.parsers.TextParser at 0x8398150>
read_csv所返回的這個TextParser對象使你可以根據(jù)chunksize對文件進行逐塊迭代。比如說,我們可以迭代處理ex6.csv,將值計數(shù)聚合到"key"列中,如下所示:
chunker = pd.read_csv('examples/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)
然后有:
In [40]: tot[:10]
Out[40]:
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方法,它使你可以讀取任意大小的塊。
將數(shù)據(jù)寫出到文本格式
數(shù)據(jù)也可以被輸出為分隔符格式的文本。我們再來看看之前讀過的一個CSV文件:
In [41]: data = pd.read_csv('examples/ex5.csv')
In [42]: data
Out[42]:
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方法,我們可以將數(shù)據(jù)寫到一個以逗號分隔的文件中:
In [43]: data.to_csv('examples/out.csv')
In [44]: !cat examples/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,所以僅僅是打印出文本結(jié)果而已):
In [45]: import sys
In [46]: 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
缺失值在輸出結(jié)果中會被表示為空字符串。你可能希望將其表示為別的標記值:
In [47]: 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
如果沒有設置其他選項,則會寫出行和列的標簽。當然,它們也都可以被禁用:
In [48]: 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
此外,你還可以只寫出一部分的列,并以你指定的順序排列:
In [49]: 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方法:
In [50]: dates = pd.date_range('1/1/2000', periods=7)
In [51]: ts = pd.Series(np.arange(7), index=dates)
In [52]: ts.to_csv('examples/tseries.csv')
In [53]: !cat examples/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
處理分隔符格式
大部分存儲在磁盤上的表格型數(shù)據(jù)都能用pandas.read_table進行加載。然而,有時還是需要做一些手工處理。由于接收到含有畸形行的文件而使read_table出毛病的情況并不少見。為了說明這些基本工具,看看下面這個簡單的CSV文件:
In [54]: !cat examples/ex7.csv
"a","b","c"
"1","2","3"
"1","2","3"
對于任何單字符分隔符文件,可以直接使用Python內(nèi)置的csv模塊。將任意已打開的文件或文件型的對象傳給csv.reader:
import csv
f = open('examples/ex7.csv')
reader = csv.reader(f)
對這個reader進行迭代將會為每行產(chǎn)生一個元組(并移除了所有的引號):對這個reader進行迭代將會為每行產(chǎn)生一個元組(并移除了所有的引號):
In [56]: for line in reader:
....: print(line)
['a', 'b', 'c']
['1', '2', '3']
['1', '2', '3']
現(xiàn)在,為了使數(shù)據(jù)格式合乎要求,你需要對其做一些整理工作。我們一步一步來做。首先,讀取文件到一個多行的列表中:
In [57]: with open('examples/ex7.csv') as f:
....: lines = list(csv.reader(f))
然后,我們將這些行分為標題行和數(shù)據(jù)行:
In [58]: header, values = lines[0], lines[1:]
然后,我們可以用字典構(gòu)造式和zip(*values),后者將行轉(zhuǎn)置為列,創(chuàng)建數(shù)據(jù)列的字典:
In [59]: data_dict = {h: v for h, v in zip(header, zip(*values))}
In [60]: data_dict
Out[60]: {'a': ('1', '1'), 'b': ('2', '2'), 'c': ('3', '3')}
CSV文件的形式有很多。只需定義csv.Dialect的一個子類即可定義出新格式(如專門的分隔符、字符串引用約定、行結(jié)束符等):
class my_dialect(csv.Dialect):
lineterminator = '\n'
delimiter = ';'
quotechar = '"'
quoting = csv.QUOTE_MINIMAL
reader = csv.reader(f, dialect=my_dialect)
各個CSV語支的參數(shù)也可以用關(guān)鍵字的形式提供給csv.reader,而無需定義子類:
reader = csv.reader(f, delimiter='|')
可用的選項(csv.Dialect的屬性)及其功能如表6-3所示。

筆記:對于那些使用復雜分隔符或多字符分隔符的文件,csv模塊就無能為力了。這種情況下,你就只能使用字符串的split方法或正則表達式方法re.split進行行拆分和其他整理工作了。
要手工輸出分隔符文件,你可以使用csv.writer。它接受一個已打開且可寫的文件對象以及跟csv.reader相同的那些語支和格式化選項:
with open('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'))
JSON數(shù)據(jù)
JSON(JavaScript Object Notation的簡稱)已經(jīng)成為通過HTTP請求在Web瀏覽器和其他應用程序之間發(fā)送數(shù)據(jù)的標準格式之一。它是一種比表格型文本格式(如CSV)靈活得多的數(shù)據(jù)格式。下面是一個例子:
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代碼?;绢愋陀袑ο螅ㄗ值洌?、數(shù)組(列表)、字符串、數(shù)值、布爾值以及null。對象中所有的鍵都必須是字符串。許多Python庫都可以讀寫JSON數(shù)據(jù)。我將使用json,因為它是構(gòu)建于Python標準庫中的。通過json.loads即可將JSON字符串轉(zhuǎn)換成Python形式:
In [62]: import json
In [63]: result = json.loads(obj)
In [64]: result
Out[64]:
{'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對象轉(zhuǎn)換成JSON格式:
In [65]: asjson = json.dumps(result)
如何將(一個或一組)JSON對象轉(zhuǎn)換為DataFrame或其他便于分析的數(shù)據(jù)結(jié)構(gòu)就由你決定了。最簡單方便的方式是:向DataFrame構(gòu)造器傳入一個字典的列表(就是原先的JSON對象),并選取數(shù)據(jù)字段的子集:
In [66]: siblings = pd.DataFrame(result['siblings'], columns=['name', 'age'])
In [67]: siblings
Out[67]:
name age
0 Scott 30
1 Katie 38
pandas.read_json可以自動將特別格式的JSON數(shù)據(jù)集轉(zhuǎn)換為Series或DataFrame。例如:
In [68]: !cat 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數(shù)組中的每個對象是表格中的一行:
In [69]: data = pd.read_json('examples/example.json')
In [70]: data
Out[70]:
a b c
0 1 2 3
1 4 5 6
2 7 8 9
第7章中關(guān)于USDA Food Database的那個例子進一步講解了JSON數(shù)據(jù)的讀取和處理(包括嵌套記錄)。
如果你需要將數(shù)據(jù)從pandas輸出到JSON,可以使用to_json方法:
In [71]: 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}}
In [72]: print(data.to_json(orient='records'))
[{"a":1,"b":2,"c":3},{"a":4,"b":5,"c":6},{"a":7,"b":8,"c":9}]
XML和HTML:Web信息收集
Python有許多可以讀寫常見的HTML和XML格式數(shù)據(jù)的庫,包括lxml、Beautiful Soup和html5lib。lxml的速度比較快,但其它的庫處理有誤的HTML或XML文件更好。
pandas有一個內(nèi)置的功能,read_html,它可以使用lxml和Beautiful Soup自動將HTML文件中的表格解析為DataFrame對象。為了進行展示,我從美國聯(lián)邦存款保險公司下載了一個HTML文件(pandas文檔中也使用過),它記錄了銀行倒閉的情況。首先,你需要安裝read_html用到的庫:
conda install lxml
pip install beautifulsoup4 html5lib
如果你用的不是conda,可以使用pip install lxml。
pandas.read_html有一些選項,默認條件下,它會搜索、嘗試解析<table>標簽內(nèi)的的表格數(shù)據(jù)。結(jié)果是一個列表的DataFrame對象:
In [73]: tables = pd.read_html('examples/fdic_failed_bank_list.html')
In [74]: len(tables)
Out[74]: 1
In [75]: failures = tables[0]
In [76]: failures.head()
Out[76]:
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插入了一個換行符\。
這里,我們可以做一些數(shù)據(jù)清洗和分析(后面章節(jié)會進一步講解),比如計算按年份計算倒閉的銀行數(shù):
In [77]: close_timestamps = pd.to_datetime(failures['Closing Date'])
In [78]: close_timestamps.dt.year.value_counts()
Out[78]:
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)是另一種常見的支持分層、嵌套數(shù)據(jù)以及元數(shù)據(jù)的結(jié)構(gòu)化數(shù)據(jù)格式。本書所使用的這些文件實際上來自于一個很大的XML文檔。
前面,我介紹了pandas.read_html函數(shù),它可以使用lxml或Beautiful Soup從HTML解析數(shù)據(jù)。XML和HTML的結(jié)構(gòu)很相似,但XML更為通用。這里,我會用一個例子演示如何利用lxml從XML格式解析數(shù)據(jù)。
紐約大都會運輸署發(fā)布了一些有關(guān)其公交和列車服務的數(shù)據(jù)資料(http://www.mta.info/developers/download.html)。這里,我們將看看包含在一組XML文件中的運行情況數(shù)據(jù)。每項列車或公交服務都有各自的文件(如Metro-North Railroad的文件是Performance_MNR.xml),其中每條XML記錄就是一條月度數(shù)據(jù),如下所示:
<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文件的根節(jié)點的引用:
from lxml import objectify
path = 'datasets/mta_perf/Performance_MNR.xml'
parsed = objectify.parse(open(path))
root = parsed.getroot()
root.INDICATOR返回一個用于產(chǎn)生各個<INDICATOR>XML元素的生成器。對于每條記錄,我們可以用標記名(如YTD_ACTUAL)和數(shù)據(jù)值填充一個字典(排除幾個標記):
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)
最后,將這組字典轉(zhuǎn)換為一個DataFrame:
In [81]: perf = pd.DataFrame(data)
In [82]: perf.head()
Out[82]:
Empty DataFrame
Columns: []
Index: []
XML數(shù)據(jù)可以比本例復雜得多。每個標記都可以有元數(shù)據(jù)??纯聪旅孢@個HTML的鏈接標簽(它也算是一段有效的XML):
from io import StringIO
tag = '<a
root = objectify.parse(StringIO(tag)).getroot()
現(xiàn)在就可以訪問標簽或鏈接文本中的任何字段了(如href):
In [84]: root
Out[84]: <Element a at 0x7f6b15817748>
In [85]: root.get('href')
Out[85]: 'http://www.google.com'
In [86]: root.text
Out[86]: 'Google'
6.2 二進制數(shù)據(jù)格式
實現(xiàn)數(shù)據(jù)的高效二進制格式存儲最簡單的辦法之一是使用Python內(nèi)置的pickle序列化。pandas對象都有一個用于將數(shù)據(jù)以pickle格式保存到磁盤上的to_pickle方法:
In [87]: frame = pd.read_csv('examples/ex1.csv')
In [88]: frame
Out[88]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
In [89]: frame.to_pickle('examples/frame_pickle')
你可以通過pickle直接讀取被pickle化的數(shù)據(jù),或是使用更為方便的pandas.read_pickle:
In [90]: pd.read_pickle('examples/frame_pickle')
Out[90]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
注意:pickle僅建議用于短期存儲格式。其原因是很難保證該格式永遠是穩(wěn)定的;今天pickle的對象可能無法被后續(xù)版本的庫unpickle出來。雖然我盡力保證這種事情不會發(fā)生在pandas中,但是今后的某個時候說不定還是得“打破”該pickle格式。
pandas內(nèi)置支持兩個二進制數(shù)據(jù)格式:HDF5和MessagePack。下一節(jié),我會給出幾個HDF5的例子,但我建議你嘗試下不同的文件格式,看看它們的速度以及是否適合你的分析工作。pandas或NumPy數(shù)據(jù)的其它存儲格式有:
- bcolz:一種可壓縮的列存儲二進制格式,基于Blosc壓縮庫。
- Feather:我與R語言社區(qū)的Hadley Wickham設計的一種跨語言的列存儲文件格式。Feather使用了Apache Arrow的列式內(nèi)存格式。
使用HDF5格式
HDF5是一種存儲大規(guī)??茖W數(shù)組數(shù)據(jù)的非常好的文件格式。它可以被作為C標準庫,帶有許多語言的接口,如Java、Python和MATLAB等。HDF5中的HDF指的是層次型數(shù)據(jù)格式(hierarchical data format)。每個HDF5文件都含有一個文件系統(tǒng)式的節(jié)點結(jié)構(gòu),它使你能夠存儲多個數(shù)據(jù)集并支持元數(shù)據(jù)。與其他簡單格式相比,HDF5支持多種壓縮器的即時壓縮,還能更高效地存儲重復模式數(shù)據(jù)。對于那些非常大的無法直接放入內(nèi)存的數(shù)據(jù)集,HDF5就是不錯的選擇,因為它可以高效地分塊讀寫。
雖然可以用PyTables或h5py庫直接訪問HDF5文件,pandas提供了更為高級的接口,可以簡化存儲Series和DataFrame對象。HDFStore類可以像字典一樣,處理低級的細節(jié):
In [92]: frame = pd.DataFrame({'a': np.random.randn(100)})
In [93]: store = pd.HDFStore('mydata.h5')
In [94]: store['obj1'] = frame
In [95]: store['obj1_col'] = frame['a']
In [96]: store
Out[96]:
<class 'pandas.io.pytables.HDFStore'>
File path: mydata.h5
/obj1 frame (shape->[100,1])
/obj1_col series (shape->[100])
/obj2 frame_table (typ->appendable,nrows->100,ncols->1,indexers->
[index])
/obj3 frame_table (typ->appendable,nrows->100,ncols->1,indexers->
[index])
HDF5文件中的對象可以通過與字典一樣的API進行獲?。?/p>
In [97]: store['obj1']
Out[97]:
a
0 -0.204708
1 0.478943
2 -0.519439
3 -0.555730
4 1.965781
.. ...
95 0.795253
96 0.118110
97 -0.748532
98 0.584970
99 0.152677
[100 rows x 1 columns]
HDFStore支持兩種存儲模式,'fixed'和'table'。后者通常會更慢,但是支持使用特殊語法進行查詢操作:
In [98]: store.put('obj2', frame, format='table')
In [99]: store.select('obj2', where=['index >= 10 and index <= 15'])
Out[99]:
a
10 1.007189
11 -1.296221
12 0.274992
13 0.228913
14 1.352917
15 0.886429
In [100]: store.close()
put是store['obj2'] = frame方法的顯示版本,允許我們設置其它的選項,比如格式。
pandas.read_hdf函數(shù)可以快捷使用這些工具:
In [101]: frame.to_hdf('mydata.h5', 'obj3', format='table')
In [102]: pd.read_hdf('mydata.h5', 'obj3', where=['index < 5'])
Out[102]:
a
0 -0.204708
1 0.478943
2 -0.519439
3 -0.555730
4 1.965781
筆記:如果你要處理的數(shù)據(jù)位于遠程服務器,比如Amazon S3或HDFS,使用專門為分布式存儲(比如Apache Parquet)的二進制格式也許更加合適。Python的Parquet和其它存儲格式還在不斷的發(fā)展之中,所以這本書中沒有涉及。
如果需要本地處理海量數(shù)據(jù),我建議你好好研究一下PyTables和h5py,看看它們能滿足你的哪些需求。。由于許多數(shù)據(jù)分析問題都是IO密集型(而不是CPU密集型),利用HDF5這樣的工具能顯著提升應用程序的效率。
注意:HDF5不是數(shù)據(jù)庫。它最適合用作“一次寫多次讀”的數(shù)據(jù)集。雖然數(shù)據(jù)可以在任何時候被添加到文件中,但如果同時發(fā)生多個寫操作,文件就可能會被破壞。
讀取Microsoft Excel文件
pandas的ExcelFile類或pandas.read_excel函數(shù)支持讀取存儲在Excel 2003(或更高版本)中的表格型數(shù)據(jù)。這兩個工具分別使用擴展包xlrd和openpyxl讀取XLS和XLSX文件。你可以用pip或conda安裝它們。
要使用ExcelFile,通過傳遞xls或xlsx路徑創(chuàng)建一個實例:
In [104]: xlsx = pd.ExcelFile('examples/ex1.xlsx')
存儲在表單中的數(shù)據(jù)可以read_excel讀取到DataFrame(原書這里寫的是用parse解析,但代碼中用的是read_excel,是個筆誤:只換了代碼,沒有改文字):
In [105]: pd.read_excel(xlsx, 'Sheet1')
Out[105]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
如果要讀取一個文件中的多個表單,創(chuàng)建ExcelFile會更快,但你也可以將文件名傳遞到pandas.read_excel:
In [106]: frame = pd.read_excel('examples/ex1.xlsx', 'Sheet1')
In [107]: frame
Out[107]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
如果要將pandas數(shù)據(jù)寫入為Excel格式,你必須首先創(chuàng)建一個ExcelWriter,然后使用pandas對象的to_excel方法將數(shù)據(jù)寫入到其中:
In [108]: writer = pd.ExcelWriter('examples/ex2.xlsx')
In [109]: frame.to_excel(writer, 'Sheet1')
In [110]: writer.save()
你還可以不使用ExcelWriter,而是傳遞文件的路徑到to_excel:
In [111]: frame.to_excel('examples/ex2.xlsx')
6.3 Web APIs交互
許多網(wǎng)站都有一些通過JSON或其他格式提供數(shù)據(jù)的公共API。通過Python訪問這些API的辦法有不少。一個簡單易用的辦法(推薦)是requests包(http://docs.python-requests.org)。
為了搜索最新的30個GitHub上的pandas主題,我們可以發(fā)一個HTTP GET請求,使用requests擴展庫:
In [113]: import requests
In [114]: url = 'https://api.github.com/repos/pandas-dev/pandas/issues'
In [115]: resp = requests.get(url)
In [116]: resp
Out[116]: <Response [200]>
響應對象的json方法會返回一個包含被解析過的JSON字典,加載到一個Python對象中:
In [117]: data = resp.json()
In [118]: data[0]['title']
Out[118]: 'Period does not round down for frequencies less that 1 hour'
data中的每個元素都是一個包含所有GitHub主題頁數(shù)據(jù)(不包含評論)的字典。我們可以直接傳遞數(shù)據(jù)到DataFrame,并提取感興趣的字段:
In [119]: issues = pd.DataFrame(data, columns=['number', 'title',
.....: 'labels', 'state'])
In [120]: issues
Out[120]:
number title \
0 17666 Period does not round down for frequencies les...
1 17665 DOC: improve docstring of function where
2 17664 COMPAT: skip 32-bit test on int repr
3 17662 implement Delegator class
4 17654 BUG: Fix series rename called with str alterin...
.. ... ...
25 17603 BUG: Correctly localize naive datetime strings...
26 17599 core.dtypes.generic --> cython
27 17596 Merge cdate_range functionality into bdate_range
28 17587 Time Grouper bug fix when applied for list gro...
29 17583 BUG: fix tz-aware DatetimeIndex + TimedeltaInd...
labels state
0 [] open
1 [{'id': 134699, 'url': 'https://api.github.com... open
2 [{'id': 563047854, 'url': 'https://api.github.... open
3 [] open
4 [{'id': 76811, 'url': 'https://api.github.com/... open
.. ... ...
25 [{'id': 76811, 'url': 'https://api.github.com/... open
26 [{'id': 49094459, 'url': 'https://api.github.c... open
27 [{'id': 35818298, 'url': 'https://api.github.c... open
28 [{'id': 233160, 'url': 'https://api.github.com... open
29 [{'id': 76811, 'url': 'https://api.github.com/... open
[30 rows x 4 columns]
花費一些精力,你就可以創(chuàng)建一些更高級的常見的Web API的接口,返回DataFrame對象,方便進行分析。
6.4 數(shù)據(jù)庫交互
在商業(yè)場景下,大多數(shù)數(shù)據(jù)可能不是存儲在文本或Excel文件中?;赟QL的關(guān)系型數(shù)據(jù)庫(如SQL Server、PostgreSQL和MySQL等)使用非常廣泛,其它一些數(shù)據(jù)庫也很流行。數(shù)據(jù)庫的選擇通常取決于性能、數(shù)據(jù)完整性以及應用程序的伸縮性需求。
將數(shù)據(jù)從SQL加載到DataFrame的過程很簡單,此外pandas還有一些能夠簡化該過程的函數(shù)。例如,我將使用SQLite數(shù)據(jù)庫(通過Python內(nèi)置的sqlite3驅(qū)動器):
In [121]: import sqlite3
In [122]: query = """
.....: CREATE TABLE test
.....: (a VARCHAR(20), b VARCHAR(20),
.....: c REAL, d INTEGER
.....: );"""
In [123]: con = sqlite3.connect('mydata.sqlite')
In [124]: con.execute(query)
Out[124]: <sqlite3.Cursor at 0x7f6b12a50f10>
In [125]: con.commit()
然后插入幾行數(shù)據(jù):
In [126]: data = [('Atlanta', 'Georgia', 1.25, 6),
.....: ('Tallahassee', 'Florida', 2.6, 3),
.....: ('Sacramento', 'California', 1.7, 5)]
In [127]: stmt = "INSERT INTO test VALUES(?, ?, ?, ?)"
In [128]: con.executemany(stmt, data)
Out[128]: <sqlite3.Cursor at 0x7f6b15c66ce0>
從表中選取數(shù)據(jù)時,大部分Python SQL驅(qū)動器(PyODBC、psycopg2、MySQLdb、pymssql等)都會返回一個元組列表:
In [130]: cursor = con.execute('select * from test')
In [131]: rows = cursor.fetchall()
In [132]: rows
Out[132]:
[('Atlanta', 'Georgia', 1.25, 6),
('Tallahassee', 'Florida', 2.6, 3),
('Sacramento', 'California', 1.7, 5)]
你可以將這個元組列表傳給DataFrame構(gòu)造器,但還需要列名(位于光標的description屬性中):
In [133]: cursor.description
Out[133]:
(('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))
In [134]: pd.DataFrame(rows, columns=[x[0] for x in cursor.description])
Out[134]:
a b c d
0 Atlanta Georgia 1.25 6
1 Tallahassee Florida 2.60 3
2 Sacramento California 1.70 5
這種數(shù)據(jù)規(guī)整操作相當多,你肯定不想每查一次數(shù)據(jù)庫就重寫一次。SQLAlchemy項目是一個流行的Python SQL工具,它抽象出了SQL數(shù)據(jù)庫中的許多常見差異。pandas有一個read_sql函數(shù),可以讓你輕松的從SQLAlchemy連接讀取數(shù)據(jù)。這里,我們用SQLAlchemy連接SQLite數(shù)據(jù)庫,并從之前創(chuàng)建的表讀取數(shù)據(jù):
In [135]: import sqlalchemy as sqla
In [136]: db = sqla.create_engine('sqlite:///mydata.sqlite')
In [137]: pd.read_sql('select * from test', db)
Out[137]:
a b c d
0 Atlanta Georgia 1.25 6
1 Tallahassee Florida 2.60 3
2 Sacramento California 1.70 5
6.5 總結(jié)
訪問數(shù)據(jù)通常是數(shù)據(jù)分析的第一步。在本章中,我們已經(jīng)學了一些有用的工具。在接下來的章節(jié)中,我們將深入研究數(shù)據(jù)規(guī)整、數(shù)據(jù)可視化、時間序列分析和其它主題。
第1章 準備工作
第2章 Python語法基礎,IPython和Jupyter
第3章 Python的數(shù)據(jù)結(jié)構(gòu)、函數(shù)和文件
第4章 NumPy基礎:數(shù)組和矢量計算
第5章 pandas入門
第6章 數(shù)據(jù)加載、存儲與文件格式
第7章 數(shù)據(jù)清洗和準備
第8章 數(shù)據(jù)規(guī)整:聚合、合并和重塑
第9章 繪圖和可視化
第10章 數(shù)據(jù)聚合與分組運算
第11章 時間序列
第12章 pandas高級應用
第13章 Python建模庫介紹
第14章 數(shù)據(jù)分析案例
附錄A NumPy高級應用
附錄B 更多關(guān)于IPython的內(nèi)容(完)