ch6_01 Pandas 資料載入、儲存&檔案格式
阿新 • • 發佈:2018-12-19
- 輸入輸出通常分為以下幾大類:讀取文字檔案和其他的更高效的磁碟儲存格式、載入資料庫中的資料、利用Web API操作網路資源
6.1讀寫文字格式的資料
- pandas提供了一些用於將表格型資料讀取為DataFrame物件的函式。下表進行了總結,其中用的比較多的就是read_csv和read_table
import pandas as pd
df = pd.read_csv('data/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 |
- 雖然是.csv檔案,但是可以使用read_table來讀取,但是需要指定分隔符為“,”
pd.read_table('data/ex1.csv',sep=',')
d:\program filles\python\lib\site-packages\ipykernel_launcher.py:1: ParserWarning: Falling back to the 'python' engine because the separator encoded in utf-8 is > 1 char long, and the 'c' engine does not support such separators; you can avoid this warning by specifying engine='python'. """Entry point for launching an IPython kernel.
a,b,c,d,message | |
---|---|
0 | 1,2,3,4,hello |
1 | 5,6,7,8,world |
2 | 9,10,11,12,foo |
- 並不是所有的檔案都有標題行,讀入檔案時可以使用預設的列名,也可以自己定義
pd.read_csv('data/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('data/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 |
- 如果想要明確的將該列放到索引4的位置上可以通過index_col指定message
names = ['a','b','c','d','message']
pd.read_csv('data/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 |
- 如果想要將多個列做成一個層次化索引,只需傳入由列編號或列名組成的列表即可
parsed = pd.read_csv('data/csv_mindex.csv',index_col=['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 |
- 還有一些檔案含有不需要的資料,可以使用skiprows引數 跳過指定的行
result = pd.read_csv('data/ex4.csv',skiprows=[0,2,3])
result
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:
result = pd.read_csv('data/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('data/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']}
pd.read_csv('data/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 |
- read_csv和read_table 一些常用的引數
逐塊讀取文字檔案
- 在處理很大的檔案時,或找出大檔案中的引數集以便於後續處理時,你可能只想讀取檔案的一小部分或逐塊對檔案進行迭代。
- 在看大檔案之前,需要先設定pandas的一些引數
pd.options.display.max_rows = 8
result = pd.read_csv('data/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 |
... | ... | ... | ... | ... | ... |
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 × 5 columns
- 如果之想讀取幾行,可以通過nrows進行指定即可:
pd.read_csv('data/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('data/examples/ex6.csv',chunksize=1000)
chunker
<pandas.io.parsers.TextFileReader at 0xed5ed0>
- read_csv所返回的這個TextParser物件使你可以根據chunksize對檔案進行逐塊迭代。比如說,我們可以迭代處理ex6.csv,將值計數聚合到"key"列中,如下所示:
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]
E 368.0
X 364.0
L 346.0
O 343.0
...
J 337.0
F 335.0
K 334.0
H 330.0
Length: 10, dtype: float64
將資料寫出到文字格式
- 資料也可以被輸出為分隔符格式的文字
data = pd.read_csv('data/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 |
data.to_csv('data/out.csv')# 也可以傳入一個sep引數,指定分隔符
- 缺失值在輸出結果中會被表示為空字串。也可將其表示為別的標記值
import sys
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','c','b'])
a,c,b
1,3.0,2
5,,6
9,11.0,10
- Series也有一個to_csv 的方法
import numpy as np
dates = pd.date_range('1/1/2000',periods=7)
ts = pd.Series(np.arange(7),index = dates)
ts.to_csv('data/tseries.csv')
pd.read_csv('data/tseries.csv')
2000-01-01 | 0 | |
---|---|---|
0 | 2000-01-02 | 1 |
1 | 2000-01-03 | 2 |
2 | 2000-01-04 | 3 |
3 | 2000-01-05 | 4 |
4 | 2000-01-06 | 5 |
5 | 2000-01-07 | 6 |
處理分隔符格式
- 大部分儲存在磁碟上的表格型資料都可以使用pandas.read_csv進行載入。但有時候需要做一些手工處理
import pandas as pd
import csv
f = open("data/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("data/examples/ex7.csv") as f:
lines = list(csv.reader(f))
#然後將這些行分為標題行和資料行
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
with open("data/examples/ex7.csv") as f:
reader = csv.reader(f,dialect=my_dialect)
print(reader)
<_csv.reader object at 0x07025DB0>
- 各個CSV語支的引數也可以用關鍵字的形式提供給csv.reader,而無需定義子類:
with open("data/examples/ex7.csv") as f:
reader = csv.reader(f,delimiter='|')
print(reader)
<_csv.reader object at 0x07025D30>
-
可用的選項(csv.Dialect的屬性)及其功能如表6-3所示
-
要手工輸出分隔符檔案,你可以使用csv.writer。它接受一個已開啟且可寫的檔案物件以及跟csv.reader相同的那些語支和格式化選項:
with open('data/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形式:
import json
result = json.loads(obj)
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)
asjson
'{"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物件轉換為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。預設選項假設JSON陣列中的每個物件是表格中的一行:
data = pd.read_json('data/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}]
XML和HTML:Web資訊收集
- Python有許多可以讀寫常見的HTML和XML格式資料的庫,包括lxml、Beautiful Soup和html5lib。lxml的速度比較快,但其它的庫處理有誤的HTML或XML檔案更好。pandas有一個內建的功能,read_html,它可以使用lxml和Beautiful Soup自動將HTML檔案中的表格解析為DataFrame物件
import pandas as pd
tables = pd.read_html('data/examples/fdic_failed_bank_list.html')
len(tables)
1
failures = tables[0]
failures.head()
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 |
- 這裡可以先做一些資料清洗和統計
close_timestamps = pd.to_datetime(failures['Closing Date'])
close_timestamps.dt.year.value_counts()
2010 157
2009 140
2011 92
2012 51
2008 25
2013 24
2014 18
2002 11
2015 8
2016 5
2004 4
2001 4
2007 3
2003 3
2000 2
Name: Closing Date, dtype: int64
利用lxml.objectify解析XML
- XML(Extensible Markup Language)是另一種常見的支援分層、巢狀資料以及元資料的結構化資料格式。介紹了pandas.read_html函式,XML和HTML的結構很相似,但XML更為通用。
from lxml import objectify
#先用lxml.objectify解析該檔案,然後通過getroot得到該XML檔案的根節點的引用
path = 'data/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.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 | 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 |
from io import StringIO
tag = '<a href="http://www.google.com">Google</a>'
root = objectify.parse(StringIO(tag)).getroot()
#接下來就可以訪問標籤或者連結文字中的任何欄位
root
<Element a at 0xcac8a0>
root.get('href')
'http://www.google.com'
root.text
'Google'