《利用Python進行數據分析·第2版》第6章 數據載入、存儲與文件格式
來自專欄 Python程序員
作者:SeanCheney Python愛好者社區--專欄作者
來源:簡書
第1章 準備工作
第2章 Python語法基礎,IPython和Jupyter Notebooks 第3章 Python的數據結構、函數和文件 第4章 NumPy基礎:數組和矢量計算 第5章 pandas入門第6章 數據載入、存儲與文件格式
第7章 數據清洗和準備 第8章 數據規整:聚合、合併和重塑 第9章 繪圖和可視化 第10章 數據聚合與分組運算 第11章 時間序列 第12章 pandas高級應用 第13章 Python建模庫介紹第14章 數據分析案例附錄A NumPy高級應用附錄B 更多關於IPython的內容(完)
訪問數據是使用本書所介紹的這些工具的第一步。我會著重介紹pandas的數據輸入與輸出,雖然別的庫中也有不少以此為目的的工具。
輸入輸出通常可以劃分為幾個大類:讀取文本文件和其他更高效的磁碟存儲格式,載入資料庫中的數據,利用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)文本文件:
In [8]: !cat examples/ex1.csva,b,c,d,message1,2,3,4,hello5,6,7,8,world9,10,11,12,foo
筆記:這裡,我用的是Unix的cat shell命令將文件的原始內容列印到屏幕上。如果你用的是Windows,你可以使用type達到同樣的效果。
由於該文件以逗號分隔,所以我們可以使用read_csv將其讀入一個DataFrame:
In [9]: df = pd.read_csv(examples/ex1.csv)In [10]: dfOut[10]: a b c d message0 1 2 3 4 hello1 5 6 7 8 world2 9 10 11 12 foo
我們還可以使用read_table,並指定分隔符:
In [11]: pd.read_table(examples/ex1.csv, sep=,)Out[11]: a b c d message0 1 2 3 4 hello1 5 6 7 8 world2 9 10 11 12 foo
並不是所有文件都有標題行。看看下面這個文件:
In [12]: !cat examples/ex2.csv1,2,3,4,hello5,6,7,8,world9,10,11,12,foo
讀入該文件的辦法有兩個。你可以讓pandas為其分配默認的列名,也可以自己定義列名:
In [13]: pd.read_csv(examples/ex2.csv, header=None)Out[13]: 0 1 2 3 40 1 2 3 4 hello1 5 6 7 8 world2 9 10 11 12 fooIn [14]: pd.read_csv(examples/ex2.csv, names=[a, b, c, d, message])Out[14]: a b c d message0 1 2 3 4 hello1 5 6 7 8 world2 9 10 11 12 foo
假設你希望將message列做成DataFrame的索引。你可以明確表示要將該列放到索引4的位置上,也可以通過index_col參數指定"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 dmessage hello 1 2 3 4world 5 6 7 8foo 9 10 11 12
如果希望將多個列做成一個層次化索引,只需傳入由列編號或列名組成的列表即可:
In [17]: !cat examples/csv_mindex.csvkey1,key2,value1,value2one,a,1,2one,b,3,4one,c,5,6one,d,7,8two,a,9,10two,b,11,12two,c,13,14two,d,15,16In [18]: parsed = pd.read_csv(examples/csv_mindex.csv, ....: index_col=[key1, key2])In [19]: parsedOut[19]: value1 value2key1 key2 one a 1 2 b 3 4 c 5 6 d 7 8two a 9 10 b 11 12 c 13 14 d 15 16
有些情況下,有些表格可能不是用固定的分隔符去分隔欄位的(比如空白符或其他模式)。有些表格可能不是用固定的分隔符去分隔欄位的(比如空白符或其他模式來分隔欄位)。看看下面這個文本文件:
In [20]: list(open(examples/ex3.txt))Out[20]: [ 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的分隔符。可以用正則表達式表達為s+,於是有有:
In [21]: result = pd.read_table(examples/ex3.txt, sep=s+)In [22]: resultOut[22]: A B Caaa -0.264438 -1.026059 -0.619500bbb 0.927272 0.302904 -0.032399ccc -0.264273 -0.386314 -0.217601ddd -0.871858 -0.348382 1.100491
這裡,由於列名比數據行的數量少,所以read_table推斷第一列應該是DataFrame的索引。這裡,由於列名比數據行的數量少,所以read_table推斷第一列應該是DataFrame的索引。
這些解析器函數還有許多參數可以幫助你處理各種各樣的異形文件格式(表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,hello5,6,7,8,world9,10,11,12,fooIn [24]: pd.read_csv(examples/ex4.csv, skiprows=[0, 2, 3])Out[24]: a b c d message0 1 2 3 4 hello1 5 6 7 8 world2 9 10 11 12 foo
缺失值處理是文件解析任務中的一個重要組成部分。缺失數據經常是要麼沒有(空字元串),要麼用某個標記值表示。默認情況下,pandas會用一組經常出現的標記值進行識別,比如NA及NULL:
In [25]: !cat examples/ex5.csvsomething,a,b,c,d,messageone,1,2,3,4,NAtwo,5,6,,8,worldthree,9,10,11,12,fooIn [26]: result = pd.read_csv(examples/ex5.csv)In [27]: resultOut[27]: something a b c d message0 one 1 2 3.0 4 NaN1 two 5 6 NaN 8 world2 three 9 10 11.0 12 fooIn [28]: pd.isnull(result)Out[28]: something a b c d message0 False False False False False True1 False False False True False False2 False False False False False False
na_values可以用一個列表或集合的字元串表示缺失值:
In [29]: result = pd.read_csv(examples/ex5.csv, na_values=[NULL])In [30]: resultOut[30]: something a b c d message0 one 1 2 3.0 4 NaN1 two 5 6 NaN 8 world2 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 message0 one 1 2 3.0 4 NaN1 NaN 5 6 NaN 8 world2 three 9 10 11.0 12 NaN
表6-2列出了pandas.read_csv和pandas.read_table常用的選項。
逐塊讀取文本文件
在處理很大的文件時,或找出大文件中的參數集以便於後續處理時,你可能只想讀取文件的一小部分或逐塊對文件進行迭代。
在看大文件之前,我們先設置pandas顯示地更緊些:
In [33]: pd.options.display.max_rows = 10
然後有:
In [34]: result = pd.read_csv(examples/ex6.csv)In [35]: resultOut[35]: one two three four key0 0.467976 -0.038649 -0.295344 -1.824726 L1 -0.358893 1.404453 0.704965 -0.200638 B2 -0.501840 0.659254 -0.421691 -0.057688 G3 0.204886 1.074134 1.388361 -0.982404 R4 0.354628 -0.133116 0.283763 -0.837063 Q... ... ... ... ... ..9995 2.311896 -0.417070 -1.409599 -0.515821 L9996 -0.479893 -0.650419 0.745152 -0.646038 E9997 0.523331 0.787112 0.486066 1.093156 K9998 -0.362559 0.598894 -1.843201 0.887292 G9999 -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 key0 0.467976 -0.038649 -0.295344 -1.824726 L1 -0.358893 1.404453 0.704965 -0.200638 B2 -0.501840 0.659254 -0.421691 -0.057688 G3 0.204886 1.074134 1.388361 -0.982404 R4 0.354628 -0.133116 0.283763 -0.837063 Q
要逐塊讀取文件,可以指定chunksize(行數):
In [874]: chunker = pd.read_csv(ch06/ex6.csv, chunksize=1000)In [875]: chunkerOut[875]: <pandas.io.parsers.TextParser at 0x8398150>
read_csv所返回的這個TextParser對象使你可以根據chunksize對文件進行逐塊迭代。比如說,我們可以迭代處理ex6.csv,將值計數聚合到"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.0X 364.0L 346.0O 343.0Q 340.0M 338.0J 337.0F 335.0K 334.0H 330.0dtype: float64
TextParser還有一個get_chunk方法,它使你可以讀取任意大小的塊。
將數據寫出到文本格式
數據也可以被輸出為分隔符格式的文本。我們再來看看之前讀過的一個CSV文件:
In [41]: data = pd.read_csv(examples/ex5.csv)In [42]: dataOut[42]: something a b c d message0 one 1 2 3.0 4 NaN1 two 5 6 NaN 8 world2 three 9 10 11.0 12 foo
利用DataFrame的to_csv方法,我們可以將數據寫到一個以逗號分隔的文件中:
In [43]: data.to_csv(examples/out.csv)In [44]: !cat examples/out.csv,something,a,b,c,d,message0,one,1,2,3.0,4,1,two,5,6,,8,world2,three,9,10,11.0,12,foo
當然,還可以使用其他分隔符(由於這裡直接寫出到sys.stdout,所以僅僅是列印出文本結果而已):
In [45]: import sysIn [46]: data.to_csv(sys.stdout, sep=|)|something|a|b|c|d|message0|one|1|2|3.0|4|1|two|5|6||8|world2|three|9|10|11.0|12|foo
缺失值在輸出結果中會被表示為空字元串。你可能希望將其表示為別的標記值:
In [47]: data.to_csv(sys.stdout, na_rep=NULL),something,a,b,c,d,message0,one,1,2,3.0,4,NULL1,two,5,6,NULL,8,world2,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,worldthree,9,10,11.0,12,foo
此外,你還可以只寫出一部分的列,並以你指定的順序排列:
In [49]: data.to_csv(sys.stdout, index=False, columns=[a, b, c])a,b,c1,2,3.05,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.csv2000-01-01,02000-01-02,12000-01-03,22000-01-04,32000-01-05,42000-01-06,52000-01-07,6
處理分隔符格式
大部分存儲在磁碟上的表格型數據都能用pandas.read_table進行載入。然而,有時還是需要做一些手工處理。由於接收到含有畸形行的文件而使read_table出毛病的情況並不少見。為了說明這些基本工具,看看下面這個簡單的CSV文件:
In [54]: !cat examples/ex7.csv"a","b","c""1","2","3""1","2","3"
對於任何單字元分隔符文件,可以直接使用Python內置的csv模塊。將任意已打開的文件或文件型的對象傳給csv.reader:
import csvf = open(examples/ex7.csv)reader = csv.reader(f)
對這個reader進行迭代將會為每行產生一個元組(並移除了所有的引號):對這個reader進行迭代將會為每行產生一個元組(並移除了所有的引號):
In [56]: for line in reader: ....: print(line)[a, b, c][1, 2, 3][1, 2, 3]
現在,為了使數據格式合乎要求,你需要對其做一些整理工作。我們一步一步來做。首先,讀取文件到一個多行的列表中:
In [57]: with open(examples/ex7.csv) as f: ....: lines = list(csv.reader(f))
然後,我們將這些行分為標題行和數據行:
In [58]: header, values = lines[0], lines[1:]
然後,我們可以用字典構造式和zip(*values),後者將行轉置為列,創建數據列的字典:
In [59]: data_dict = {h: v for h, v in zip(header, zip(*values))}In [60]: data_dictOut[60]: {a: (1, 1), b: (2, 2), c: (3, 3)}
CSV文件的形式有很多。只需定義csv.Dialect的一個子類即可定義出新格式(如專門的分隔符、字元串引用約定、行結束符等):
class my_dialect(csv.Dialect): lineterminator =
delimiter = ; quotechar = " quoting = csv.QUOTE_MINIMALreader = csv.reader(f, dialect=my_dialect)
各個CSV語支的參數也可以關鍵字的形式提供給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數據
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形式:```pythonIn [62]: import jsonIn [63]: result = json.loads(obj)In [64]: resultOut[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對象轉換成JSON格式:
In [65]: asjson = json.dumps(result)
如何將(一個或一組)JSON對象轉換為DataFrame或其他便於分析的數據結構就由你決定了。最簡單方便的方式是:向DataFrame構造器傳入一個字典的列表(就是原先的JSON對象),並選取數據欄位的子集:
In [66]: siblings = pd.DataFrame(result[siblings], columns=[name, age])In [67]: siblingsOut[67]: name age0 Scott 301 Katie 38
pandas.read_json可以自動將特別格式的JSON數據集轉換為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數組中的每個對象是表格中的一行:
In [69]: data = pd.read_json(examples/example.json)In [70]: dataOut[70]: a b c0 1 2 31 4 5 62 7 8 9
第7章中關於USDA Food Database的那個例子進一步講解了JSON數據的讀取和處理(包括嵌套記錄)。
如果你需要將數據從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格式數據的庫,包括lxml、Beautiful Soup和html5lib。lxml的速度比較快,但其它的庫處理有誤的HTML或XML文件更好。
pandas有一個內置的功能,read_html,它可以使用lxml和Beautiful Soup自動將HTML文件中的表格解析為DataFrame對象。為了進行展示,我從美國聯邦存款保險公司下載了一個HTML文件(pandas文檔中也使用過),它記錄了銀行倒閉的情況。首先,你需要安裝read_html用到的庫:
conda install lxmlpip install beautifulsoup4 html5lib
如果你用的不是conda,可以使用pip install lxml
。
pandas.read_html有一些選項,默認條件下,它會搜索、嘗試解析<table>標籤內的的表格數據。結果是一個列表的DataFrame對象:
In [73]: tables = pd.read_html(examples/fdic_failed_bank_list.html)In [74]: len(tables)Out[74]: 1In [75]: failures = tables[0]In [76]: failures.head()Out[76]: Bank Name City ST CERT 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 Todays 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插入了一個換行符。
這裡,我們可以做一些數據清洗和分析(後面章節會進一步講解),比如計算按年份計算倒閉的銀行數:
In [77]: close_timestamps = pd.to_datetime(failures[Closing Date])In [78]: close_timestamps.dt.year.value_counts()Out[78]: 2010 1572009 1402011 922012 512008 25 ... 2004 42001 42007 32003 32000 2Name: Closing Date, Length: 15, dtype: int64
利用lxml.objectify解析XML
XML(Extensible Markup Language)是另一種常見的支持分層、嵌套數據以及元數據的結構化數據格式。本書所使用的這些文件實際上來自於一個很大的XML文檔。
前面,我介紹了pandas.read_html函數,它可以使用lxml或Beautiful Soup從HTML解析數據。XML和HTML的結構很相似,danXML更為通用。這裡,我會用一個例子演示如何利用lxml從XML格式解析數據。
紐約大都會運輸署發布了一些有關其公交和列車服務的數據資料(http://www.mta.info/developers/download.html)。這裡,我們將看看包含在一組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 objectifypath = examples/mta_perf/Performance_MNR.xmlparsed = 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.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:
In [81]: perf = pd.DataFrame(data)In [82]: perf.head()Out[82]:Empty DataFrameColumns: []Index: []
XML數據可以比本例複雜得多。每個標記都可以有元數據。看看下面這個HTML的鏈接標籤(它也算是一段有效的XML):
from io import StringIOtag = <a href="http://www.google.com">Google</a>root = objectify.parse(StringIO(tag)).getroot()
現在就可以訪問標籤或鏈接文本中的任何欄位了(如href):
In [84]: rootOut[84]: <Element a at 0x7f6b15817748>In [85]: root.get(href)Out[85]: http://www.google.comIn [86]: root.textOut[86]: Google
6.2 二進位數據格式
實現數據的高效二進位格式存儲最簡單的辦法之一是使用Python內置的pickle序列化。pandas對象都有一個用於將數據以pickle格式保存到磁碟上的to_pickle方法:
In [87]: frame = pd.read_csv(examples/ex1.csv)In [88]: frameOut[88]: a b c d message0 1 2 3 4 hello1 5 6 7 8 world2 9 10 11 12 fooIn [89]: frame.to_pickle(examples/frame_pickle)
你可以通過pickle直接讀取被pickle化的數據,或是使用更為方便的pandas.read_pickle:
In [90]: pd.read_pickle(examples/frame_pickle)Out[90]: a b c d message0 1 2 3 4 hello1 5 6 7 8 world2 9 10 11 12 foo
注意:pickle僅建議用於短期存儲格式。其原因是很難保證該格式永遠是穩定的;今天pickle的對象可能無法被後續版本的庫unpickle出來。雖然我儘力保證這種事情不會發生在pandas中,但是今後的某個時候說不定還是得「打破」該pickle格式。
pandas內置支持兩個二進位數據格式:HDF5和MessagePack。下一節,我會給出幾個HDF5的例子,但我建議你嘗試下不同的文件格式,看看它們的速度以及是否適合你的分析工作。pandas或NumPy數據的其它存儲格式有:
- bcolz:一種可壓縮的列存儲二進位格式,基於Blosc壓縮庫。
- Feather:我與R語言社區的Hadley Wickham設計的一種跨語言的列存儲文件格式。Feather使用了Apache Arrow的列式內存格式。
使用HDF5格式
HDF5是一種存儲大規模科學數組數據的非常好的文件格式。它可以被作為C庫,帶有許多語言的介面,如Java、Python和MATLAB等。HDF5中的HDF指的是層次型數據格式(hierarchical data format)。每個HDF5文件都含有一個文件系統式的節點結構,它使你能夠存儲多個數據集並支持元數據。與其他簡單格式相比,HDF5支持多種壓縮器的即時壓縮,還能更高效地存儲重複模式數據。對於那些非常大的無法直接放入內存的數據集,HDF5就是不錯的選擇,因為它可以高效地分塊讀寫。
雖然可以用PyTables或h5py庫直接訪問HDF5文件,pandas提供了更為高級的介面,可以簡化存儲Series和DataFrame對象。HDFStore類可以像字典一樣,處理低級的細節:
In [92]: frame = pd.DataFrame({a: np.random.randn(100)})In [93]: store = pd.HDFStore(mydata.h5)In [94]: store[obj1] = frameIn [95]: store[obj1_col] = frame[a]In [96]: storeOut[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進行獲取:
In [97]: store[obj1]Out[97]: a0 -0.2047081 0.4789432 -0.5194393 -0.5557304 1.965781.. ...95 0.79525396 0.11811097 -0.74853298 0.58497099 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]: a10 1.00718911 -1.29622112 0.27499213 0.22891314 1.35291715 0.886429In [100]: store.close()
put是store[obj2] = frame方法的顯示版本,允許我們設置其它的選項,比如格式。
pandas.read_hdf函數可以快捷使用這些工具:
In [101]: frame.to_hdf(mydata.h5, obj3, format=table)In [102]: pd.read_hdf(mydata.h5, obj3, where=[index < 5])Out[102]: a0 -0.2047081 0.4789432 -0.5194393 -0.5557304 1.965781
筆記:如果你要處理的數據位於遠程伺服器,比如Amazon S3或HDFS,使用專門為分散式存儲(比如Apache Parquet)的二進位格式也許更加合適。Python的Parquet和其它存儲格式還在不斷的發展之中,所以這本書中沒有涉及。
如果需要本地處理海量數據,我建議你好好研究一下PyTables和h5py,看看它們能滿足你的哪些需求。。由於許多數據分析問題都是IO密集型(而不是CPU密集型),利用HDF5這樣的工具能顯著提升應用程序的效率。
注意:HDF5不是資料庫。它最適合用作「一次寫多次讀」的數據集。雖然數據可以在任何時候被添加到文件中,但如果同時發生多個寫操作,文件就可能會被破壞。
讀取Microsoft Excel文件
pandas的ExcelFile類或pandas.read_excel函數支持讀取存儲在Excel 2003(或更高版本)中的表格型數據。這兩個工具分別使用擴展包xlrd和openpyxl讀取XLS和XLSX文件。你可以用pip或conda安裝它們。
要使用ExcelFile,通過傳遞xls或xlsx路徑創建一個實例:
In [104]: xlsx = pd.ExcelFile(examples/ex1.xlsx)
存儲在表單中的數據可以read_excel讀取到DataFrame(原書這裡寫的是用parse解析,但代碼中用的是read_excel,是個筆誤:只換了代碼,沒有改文字):
In [105]: pd.read_excel(xlsx, Sheet1)Out[105]: a b c d message0 1 2 3 4 hello1 5 6 7 8 world2 9 10 11 12 foo
如果要讀取一個文件中的多個表單,創建ExcelFile會更快,但你也可以將文件名傳遞到pandas.read_excel:
In [106]: frame = pd.read_excel(examples/ex1.xlsx, Sheet1)In [107]: frameOut[107]: a b c d message0 1 2 3 4 hello1 5 6 7 8 world2 9 10 11 12 foo
如果要將pandas數據寫入為Excel格式,你必須首先創建一個ExcelWriter,然後使用pandas對象的to_excel方法將數據寫入到其中:
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交互
許多網站都有一些通過JSON或其他格式提供數據的公共API。通過Python訪問這些API的辦法有不少。一個簡單易用的辦法(推薦)是requests包(http://docs.python-requests.org)。
為了搜索最新的30個GitHub上的pandas主題,我們可以發一個HTTP GET請求,使用requests擴展庫:
In [113]: import requestsIn [114]: url = https://api.github.com/repos/pandas-dev/pandas/issuesIn [115]: resp = requests.get(url)In [116]: respOut[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主題頁數據(不包含評論)的字典。我們可以直接傳遞數據到DataFrame,並提取感興趣的欄位:
In [119]: issues = pd.DataFrame(data, columns=[number, title, .....: labels, state])In [120]: issuesOut[120]: number title 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 class4 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]
花費一些精力,你就可以創建一些更高級的常見的Web API的介面,返回DataFrame對象,方便進行分析。
6.4 資料庫交互
在商業場景下,大多數數據可能不是存儲在文本或Excel文件中。基於SQL的關係型資料庫(如SQL Server、PostgreSQL和MySQL等)使用非常廣泛,其它一些資料庫也很流行。資料庫的選擇通常取決於性能、數據完整性以及應用程序的伸縮性需求。
將數據從SQL載入到DataFrame的過程很簡單,此外pandas還有一些能夠簡化該過程的函數。例如,我將使用SQLite資料庫(通過Python內置的sqlite3驅動器):
In [121]: import sqlite3In [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()
然後插入幾行數據:
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>
從表中選取數據時,大部分Python SQL驅動器(PyODBC、psycopg2、MySQLdb、pymssql等)都會返回一個元組列表:
In [130]: cursor = con.execute(select * from test)In [131]: rows = cursor.fetchall()In [132]: rowsOut[132]: [(Atlanta, Georgia, 1.25, 6), (Tallahassee, Florida, 2.6, 3), (Sacramento, California, 1.7, 5)]
你可以將這個元組列表傳給DataFrame構造器,但還需要列名(位於游標的description屬性中):
In [133]: cursor.descriptionOut[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 d0 Atlanta Georgia 1.25 61 Tallahassee Florida 2.60 32 Sacramento California 1.70 5
這種數據規整操作相當多,你肯定不想每查一次資料庫就重寫一次。SQLAlchemy項目是一個流行的Python SQL工具,它抽象出了SQL資料庫中的許多常見差異。pandas有一個read_sql函數,可以讓你輕鬆的從SQLAlchemy連接讀取數據。這裡,我們用SQLAlchemy連接SQLite資料庫,並從之前創建的表讀取數據:
In [135]: import sqlalchemy as sqlaIn [136]: db = sqla.create_engine(sqlite:///mydata.sqlite)In [137]: pd.read_sql(select * from test, db)Out[137]: a b c d0 Atlanta Georgia 1.25 61 Tallahassee Florida 2.60 32 Sacramento California 1.70 5
6.5 總結
訪問數據通常是數據分析的第一步。在本章中,我們已經學了一些有用的工具。在接下來的章節中,我們將深入研究數據規整、數據可視化、時間序列分析和其它主題。
推薦閱讀:
※Python下使用matplotlib庫時,如何與LaTeX結合起來?
※【Python3網路爬蟲開發實戰】1.4.1-MySQL的安裝
※通過雲計算平台製作簡易文字識別腳本
※六步使用Python寫一個小小的自動化項目監控客網站全實現
※Python出現ValueError: need more than 1 value to unpack 的原因是什麼?