資料分析三劍客之pandas
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]: a1 b2 c3 d4 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]: abc 01webtc 12youeos 23theyae
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]: 0222 1btc 2234 3eos 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]: a1 b2 c3 d4 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]: aNaN b1.0 c1.0 d1.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]: aTrue bFalse cFalse dFalse dtype: bool In [59]: data2.notnull() Out[59]: aFalse bTrue cTrue dTrue dtype: bool In [60]: data2[data2.isnull()==True]#巢狀查詢NaN Out[60]: aNaN 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]: a1 asd1 b1 dtype: int64 In [73]: data Out[73]: a1 b2 c3 d4 dtype: int64 In [74]: data+data1 Out[74]: a2.0 asdNaN b3.0 cNaN dNaN 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 a1 b2 c3 d4 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]: nameprice 0BTC50000 1ETH4000 2EOS150
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]: 0BTC 1ETH 2EOS Name: name, dtype: object In [93]: data['name'] Out[93]: 0BTC 1ETH 2EOS Name: name, dtype: object In [94]: data.iloc[1]#loc['name']查詢的是行標籤 Out[94]: nameETH price4000 Name: 1, dtype: object
其實行索引,除了iloc,loc還有個 ix , ix 既可以進行 行標籤索引 ,也可以進行 行號索引 ,但這也大大增加了它的不確定性,有時會出現一些奇怪的問題,所以pandas在0.20.0版本的時候就把ix給棄用了。
DataFrame的常用操作
簡單地增加行、列:
In [105]: data['type'] = 'token'#增加列 In [106]: data Out[106]: namepricetype 0BTC50000token 1ETH4000token 2EOS150token In [109]: data.loc['3'] = ['ae',200,'token']#增加行 In [110]: data Out[110]: namepricetype 0BTC50000token 1ETH4000token 2EOS150token 3ae200token
刪除行、列操作:
In [117]: del data['type']#刪除列 In [118]: data Out[118]: nameprice 0BTC50000 1ETH4000 2EOS150 3ae200 In [120]: data.drop([2])#刪除行 Out[120]: nameprice 0BTC50000 1ETH4000 3ae200 In [121]: data Out[121]: nameprice 0BTC50000 1ETH4000 2EOS150 3ae200
這裡需要注意的是,使用 drop()方法 返回的是 Copy 而不是 檢視 ,要想真正在原資料裡刪除行,就要設定 inplace=True :
In [125]: data.drop([2],inplace=True) In [126]: data Out[126]: nameprice 0BTC50000 1ETH4000 3ae200
設定某一列為index:
In [131]: data.set_index(['name'],inplace=True) In [132]: data Out[132]: price name BTC50000 ETH4000 ae200 In [133]: data.reset_index(inplace=True)#將index返回回dataframe中 In [134]: data Out[134]: nameprice 0BTC50000 1ETH4000 2ae200
處理缺失值:
In [149]: data Out[149]: nameprice 0BTC50000.0 1ETH4000.0 2ae200.0 3eosNaN In [150]: data.dropna()#丟棄含有缺失值的行 Out[150]: nameprice 0BTC50000.0 1ETH4000.0 2ae200.0 In [151]: data.fillna(0)#填充缺失值資料為0 Out[151]: nameprice 0BTC50000.0 1ETH4000.0 2ae200.0 3eos0.0
還是需要注意: 這些方法返回的是copy而不是檢視,如果想在原資料上改變,別忘了 inplace=True 。
資料合併:
In [160]: data Out[160]: nameprice 0BTC50000.0 1ETH4000.0 2ae200.0 3eosNaN In [161]: data1 Out[161]: nameother 0BTC50000 1BTC4000 2EOS150 In [162]: pd.merge(data,data1,on='name',how='left')#以name為key進行左連線 Out[162]: namepriceother 0BTC50000.050000.0 1BTC50000.04000.0 2ETH4000.0NaN 3ae200.0NaN 4eosNaNNaN
平時進行資料合併操作,更多的會出一種情況,那就是出現 重複值 ,DataFrame也為我們提供了簡便的方法:
data.drop_duplicates(inplace=True)
資料的簡單儲存與讀取:
In [165]: data.to_csv('test.csv') In [166]: pd.read_csv('test.csv') Out[166]: Unnamed: 0 nameprice 00BTC50000.0 11ETH4000.0 22ae200.0 33eosNaN
為什麼會出現這種情況呢,從頭看到尾的同學可能就看出來了,增加第三行時,我用的是 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]: nameprice 0BTC50000.0 1ETH4000.0 2ae200.0 3eosNaN
其他的還有 header 引數, 這些引數都是我們在儲存資料時需要注意的。