Pandas
引入
前面一篇文章我們介紹了numpy,但numpy的特長并不是在于資料處理,而是在它能非常友善地實作科學計算,是以我們日常對資料進行處理時用的numpy情況并不是很多,我們需要處理的資料一般都是帶有列标簽和index索引的,而numpy并不支援這些,這時我們就需要pandas上場啦!
WHAT?
Pandas是基于Numpy建構的庫,在資料處理方面可以把它了解為numpy加強版,同時Pandas也是一項開源項目 。不同于numpy的是,pandas擁有種資料結構:Series和DataFrame:

下面我們就來生成一個簡單的series對象來友善了解:
In [1]: from pandas import Series,DataFrame
In [2]: import pandas as pd
In [3]: data = Series([1,2,3,4],index = ['a','b','c','d'])
In [4]: data
Out[4]:
a 1
b 2
c 3
d 4
dtype: int64
Series是一種類似一維數組的資料結構,由一組資料和與之相關的index組成,這個結構一看似乎與dict字典差不多,我們知道字典是一種無序的資料結構,而pandas中的Series的資料結構不一樣,它相當于定長有序的字典,并且它的index和value之間是獨立的,兩者的索引還是有差別的,Series的index是可變的,而dict字典的key值是不可變的。
下面照例生成一個簡單的DataFrame對象:
In [8]: data = {'a':[1,2,3],'b':['we','you','they'],'c':['btc','eos','ae']}
In [9]: df = DataFrame(data)
In [10]: df
Out[10]:
a b c
0 1 we btc
1 2 you eos
2 3 they ae
DataFrame這種資料結構我們可以把它看作是一張二維表,DataFrame長得跟我們平時使用的Excel表格差不多,DataFrame的橫行稱為columns,豎列和Series一樣稱為index,DataFrame每一列可以是不同類型的值集合,是以DataFrame你也可以把它視為不同資料類型同一index的Series集合。
WHY?
科學計算方面numpy是優勢,但在資料處理方面DataFrame就更勝一籌了,事實上DataFrame已經覆寫了一部分的資料操作了,對于資料挖掘來說,工作可大概分為讀取資料-資料清洗-分析模組化-結果展示:
先說說讀取資料,Pandas提供強大的IO讀取工具,csv格式、Excel檔案、資料庫等都可以非常簡便地讀取,對于大資料,pandas也支援大檔案的分塊讀取;
接下來就是資料清洗,面對資料集,我們遇到最多的情況就是存在缺失值,Pandas把各種類型資料類型的缺失值統一稱為NaN(這裡要多說幾句,None==None這個結果是true,但np.nan==np.nan這個結果是false,NaN在官方文檔中定義的是float類型,有關于NaN和None的差別以及使用,有位部落客已經做好整理:None vs NaN),Pandas提供許多友善快捷的方法來處理這些缺失值NaN。
最重要的分析模組化階段,Pandas自動且明确的資料對齊特性,非常友善地使新的對象可以正确地與一組标簽對齊,有了這個特性,Pandas就可以非常友善地将資料集進行拆分-重組操作。
最後就是結果展示階段了,我們都知道Matplotlib是個資料視圖化的好工具,Pandas與Matplotlib搭配,不用複雜的代碼,就可以生成多種多樣的資料視圖。
HOW?
Series
Series的兩種生成方式:
In [19]: data = Series([222,'btc',234,'eos'])
In [20]: data
Out[20]:
0 222
1 btc
2 234
3 eos
dtype: object
雖然我們在生成的時候沒有設定index值,但Series還是會自動幫我們生成index,這種方式生成的Series結構跟list清單差不多,可以把這種形式的Series了解為豎起來的list清單。
In [21]: data = Series([1,2,3,4],index = ['a','b','c','d'])
In [22]: data
Out[22]:
a 1
b 2
c 3
d 4
dtype: int64
這種形式的Series可以了解為numpy的array外面披了一件index的馬甲,是以array的相關操作,Series同樣也是支援的。結構非常相似的dict字典同樣也是可以轉化為Series格式的:
In [29]: dic = {'a':1,'b':2,'c':'as'}
In [30]: dicSeries = Series(dic)
檢視Series的相關資訊:
In [32]: data.index
Out[32]: Index(['a', 'b', 'c', 'd'], dtype='object')
In [33]: data.values
Out[33]: array([1, 2, 3, 4], dtype=int64)
In [35]: 'a' in data #in方法預設判斷的是index值
Out[35]: True
Series的NaN生成:
In [46]: index1 = [ 'a','b','c','d']
In [47]: dic = {'b':1,'c':1,'d':1}
In [48]: data2 = Series(dic,index=index1)
In [49]: data2
Out[49]:
a NaN
b 1.0
c 1.0
d 1.0
dtype: float64
從這裡我們可以看出Series的生成依據的是index值,index‘a’在字典dic的key中并不存在,Series自然也找不到’a’的對應value值,這種情況下Pandas就會自動生成NaN(not a number)來填補缺失值,這裡還有個有趣的現象,原本dtype是int類型,生成NaN後就變成了float類型了,因為NaN的官方定義就是float類型。
NaN的相關查詢:
In [58]: data2.isnull()
Out[58]:
a True
b False
c False
d False
dtype: bool
In [59]: data2.notnull()
Out[59]:
a False
b True
c True
d True
dtype: bool
In [60]: data2[data2.isnull()==True] #嵌套查詢NaN
Out[60]:
a NaN
dtype: float64
In [64]: data2.count() #統計非NaN個數
Out[64]: 3
切記切記,查詢NaN值切記不要使用np.nan==np.nan這種形式來作為判斷條件,結果永遠是False,==是用作值判斷的,而NaN并沒有值,如果你不想使用上方的判斷方法,你可以使用is作為判斷方法,is是對象引用判斷,np.nan is np.nan,結果就是你要的True。
Series自動對齊:
In [72]: data1
Out[72]:
a 1
asd 1
b 1
dtype: int64
In [73]: data
Out[73]:
a 1
b 2
c 3
d 4
dtype: int64
In [74]: data+data1
Out[74]:
a 2.0
asd NaN
b 3.0
c NaN
d NaN
dtype: float64
從上面兩個Series中不難看出各自的index所處位置并不完全相同,這時Series的自動對齊特性就發揮作用了,在算術運算中,Series會自動尋找比對的index值進行運算,如果index不存在比對則自動賦予NaN,值得注意的是,任何數+NaN=NaN,你可以把NaN了解為吸收一切的黑洞。
Series的name屬性:
In [84]: data.index.name = 'abc'
In [85]: data.name = 'test'
In [86]: data
Out[86]:
abc
a 1
b 2
c 3
d 4
Name: test, dtype: int64
Series對象本身及其索引index都有一個name屬性,name屬性主要發揮作用是在DataFrame中,當我們把一個Series對象放進DataFrame中,新的列将根據我們的name屬性對該列進行命名,如果我們沒有給Series命名,DataFrame則會自動幫我們命名為0。
DataFrame
DataFrame的生成:
In [87]: data = {'name': ['BTC', 'ETH', 'EOS'], 'price':[50000, 4000, 150]}
In [88]: data = DataFrame(data)
In [89]: data
Out[89]:
name price
0 BTC 50000
1 ETH 4000
2 EOS 150
DataFrame的生成與Series差不多,你可以自己指定index,也可不指定,DataFrame會自動幫你補上。
檢視DataFrame的相關資訊:
In [95]: data.index
Out[95]: RangeIndex(start=0, stop=3, step=1)
In [96]: data.values
Out[96]:
array([['BTC', 50000],
['ETH', 4000],
['EOS', 150]], dtype=object)
In [97]: data.columns #DataFrame的列标簽
Out[97]: Index(['name', 'price'], dtype='object')
DataFrame的索引:
In [92]: data.name
Out[92]:
0 BTC
1 ETH
2 EOS
Name: name, dtype: object
In [93]: data['name']
Out[93]:
0 BTC
1 ETH
2 EOS
Name: name, dtype: object
In [94]: data.iloc[1] #loc['name']查詢的是行标簽
Out[94]:
name ETH
price 4000
Name: 1, dtype: object
其實行索引,除了iloc,loc還有個ix,ix既可以進行行标簽索引,也可以進行行号索引,但這也大大增加了它的不确定性,有時會出現一些奇怪的問題,是以pandas在0.20.0版本的時候就把ix給棄用了。
DataFrame的常用操作
簡單地增加行、列:
In [105]: data['type'] = 'token' #增加列
In [106]: data
Out[106]:
name price type
0 BTC 50000 token
1 ETH 4000 token
2 EOS 150 token
In [109]: data.loc['3'] = ['ae',200,'token'] #增加行
In [110]: data
Out[110]:
name price type
0 BTC 50000 token
1 ETH 4000 token
2 EOS 150 token
3 ae 200 token
删除行、列操作:
In [117]: del data['type'] #删除列
In [118]: data
Out[118]:
name price
0 BTC 50000
1 ETH 4000
2 EOS 150
3 ae 200
In [120]: data.drop([2]) #删除行
Out[120]:
name price
0 BTC 50000
1 ETH 4000
3 ae 200
In [121]: data
Out[121]:
name price
0 BTC 50000
1 ETH 4000
2 EOS 150
3 ae 200
這裡需要注意的是,使用drop()方法傳回的是Copy而不是視圖,要想真正在原資料裡删除行,就要設定inplace=True:
In [125]: data.drop([2],inplace=True)
In [126]: data
Out[126]:
name price
0 BTC 50000
1 ETH 4000
3 ae 200
設定某一列為index:
In [131]: data.set_index(['name'],inplace=True)
In [132]: data
Out[132]:
price
name
BTC 50000
ETH 4000
ae 200
In [133]: data.reset_index(inplace=True) #将index傳回回dataframe中
In [134]: data
Out[134]:
name price
0 BTC 50000
1 ETH 4000
2 ae 200
處理缺失值:
In [149]: data
Out[149]:
name price
0 BTC 50000.0
1 ETH 4000.0
2 ae 200.0
3 eos NaN
In [150]: data.dropna() #丢棄含有缺失值的行
Out[150]:
name price
0 BTC 50000.0
1 ETH 4000.0
2 ae 200.0
In [151]: data.fillna(0) #填充缺失值資料為0
Out[151]:
name price
0 BTC 50000.0
1 ETH 4000.0
2 ae 200.0
3 eos 0.0
還是需要注意:這些方法傳回的是copy而不是視圖,如果想在原資料上改變,别忘了inplace=True。
資料合并:
In [160]: data
Out[160]:
name price
0 BTC 50000.0
1 ETH 4000.0
2 ae 200.0
3 eos NaN
In [161]: data1
Out[161]:
name other
0 BTC 50000
1 BTC 4000
2 EOS 150
In [162]: pd.merge(data,data1,on='name',how='left') #以name為key進行左連接配接
Out[162]:
name price other
0 BTC 50000.0 50000.0
1 BTC 50000.0 4000.0
2 ETH 4000.0 NaN
3 ae 200.0 NaN
4 eos NaN NaN
平時進行資料合并操作,更多的會出一種情況,那就是出現重複值,DataFrame也為我們提供了簡便的方法:
data.drop_duplicates(inplace=True)
資料的簡單儲存與讀取:
In [165]: data.to_csv('test.csv')
In [166]: pd.read_csv('test.csv')
Out[166]:
Unnamed: 0 name price
0 0 BTC 50000.0
1 1 ETH 4000.0
2 2 ae 200.0
3 3 eos NaN
為什麼會出現這種情況呢,從頭看到尾的同學可能就看出來了,增加第三行時,我用的是loc[‘3’]行标簽來增加的,而read_csv方法是預設index是從0開始增長的,此時隻需要我們設定下index參數就ok了:
In [167]: data.to_csv('test.csv',index=None) #不儲存行索引
In [168]: pd.read_csv('test.csv')
Out[168]:
name price
0 BTC 50000.0
1 ETH 4000.0
2 ae 200.0
3 eos NaN
其他的還有header參數, 這些參數都是我們在儲存資料時需要注意的。