啃書:《利用python進行資料分析》第五章——pandas入門(一)

語言: CN / TW / HK

通過前面幾個章節的鋪墊,我們對於資料處理也有了一點了解。後續主要首選是Pandas,它包含了使資料清洗和分析工作更快更簡單的資料結構和操作工具。pandas經常會和其他工具一起並行使用,如上章學習到的numpy和scipy,分析庫statsmodels和scikit-learn,和資料視覺化庫matplotlib。pandas是基於numpy建立的,特別是對基於陣列的函式和不使用for迴圈的資料處理。

雖然pandas使用了很多的numpy編碼風格,但是二者最大的不同是pandas專門為處理表格和混雜資料設計的框架。而numpy更加適合處理統一的數值陣列資料。

本章中,我將使用以下約定引入pandas:

python In [1]: import pandas as pd 因為Series和DataFrame用的次數非常多,所以將其引入本地名稱空間中會更方便:

python In [2]: from pandas import Series, DataFrame

5.1pandas的資料結構

要使用pandas,你首先就得熟悉它的兩個主要資料結構:Series和DataFrame。這兩個基本數可以解決大多數的處理問題了,所以我們要了解清楚他們的細節。

Series

Series是一種類似一維陣列的物件,它是由一組資料(各種numpy的資料型別)以及一組與之相關的資料標籤(索引)組成。

```python In [11]: obj = pd.Series([4, 7, -5, 3])

In [12]: obj Out[12]: 0 4 1 7 2 -5 3 3 dtype: int64 ``` Series的表現形式是左邊是索引,右邊是資料值。這裡我們並沒有對資料進行特殊索引設定,所以是預設從0開始的索引標號。你也可以通過Series的values和index屬性對陣列內容進行訪問:

```python In [13]: obj.values Out[13]: array([ 4, 7, -5, 3])

In [14]: obj.index # like range(4) Out[14]: RangeIndex(start=0, stop=4, step=1) ``` 通常來說,我們希望Series可以對各個資料進行標記:

```python In [15]: obj2 = pd.Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c'])

In [16]: obj2 Out[16]: d 4 b 7 a -5 c 3 dtype: int64

In [17]: obj2.index Out[17]: Index(['d', 'b', 'a', 'c'], dtype='object') ``` 與普通NumPy陣列相比,你可以通過索引的方式選取Series中的單個或一組值:

```python In [18]: obj2['a'] Out[18]: -5

In [19]: obj2['d'] = 6

In [20]: obj2[['c', 'a', 'd']] Out[20]: c 3 a -5 d 6 dtype: int64 ``` 當然你也可以通過上章節所學的numpy的相關知識對Series進行資料運算:

```python In [21]: obj2[obj2 > 0] Out[21]: d 6 b 7 c 3 dtype: int64

In [22]: obj2 * 2 Out[22]: d 12 b 14 a -10 c 6 dtype: int64

In [23]: np.exp(obj2) Out[23]: d 403.428793 b 1096.633158 a 0.006738 c 20.085537 dtype: float64 ``` 如果資料被存放在一個Python字典中,也可以直接通過這個字典來建立Series:

```python In [26]: sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}

In [27]: obj3 = pd.Series(sdata)

In [28]: obj3 Out[28]: Ohio 35000 Oregon 16000 Texas 71000 Utah 5000 dtype: int64 ``` 如果只傳入一個字典,則結果Series中的索引就是原字典的鍵(有序排列)。你可以傳入排好序的字典的鍵以改變順序:

```python In [29]: states = ['California', 'Ohio', 'Oregon', 'Texas']

In [30]: obj4 = pd.Series(sdata, index=states)

In [31]: obj4 Out[31]: California NaN Ohio 35000.0 Oregon 16000.0 Texas 71000.0 dtype: float64 `` 這個例子中,sdata和states索引相匹配的才會存入新生成的物件中,由於states中的California找不到對應值,所以為NaN。而Utah`並不在states中,所以也不存在。

這種NaN在pandas中被稱為損失值或NA值。pandas有函式isnull和notnull進行檢測陣列中是否含有損失值:

```python In [32]: pd.isnull(obj4) Out[32]: California True Ohio False Oregon False Texas False dtype: bool

In [33]: pd.notnull(obj4) Out[33]: California False Ohio True Oregon True Texas True dtype: bool ``` 當然細心的同學可能發現了Series很重要的功能就是資料對齊,資料能夠很整齊的排列,這對我們資料分析有很大幫助。Series物件本身和其索引都有一個屬性name,該屬性和pandas的其他功能密切相關:

```python In [38]: obj4.name = 'population'

In [39]: obj4.index.name = 'state'

In [40]: obj4 Out[40]: state California NaN Ohio 35000.0 Oregon 16000.0 Texas 71000.0 Name: population, dtype: float64 ``` 同時Series的索引可以通過賦值就地修改:

```python In [41]: obj Out[41]: 0 4 1 7 2 -5 3 3 dtype: int64

In [42]: obj.index = ['Bob', 'Steve', 'Jeff', 'Ryan']

In [43]: obj Out[43]: Bob 4 Steve 7 Jeff -5 Ryan 3 dtype: int64 ```

DataFrame

DataFrame是一個表格型資料結構,它含有一組有序列,每列可以識不同型別值。DataFrame既有行索引,也有列索引,它可以被看成是Series組成的字典。DataFrame中的資料是以一個或多個二維塊存放的,而不是列表,字典或者其他的一維陣列的集合。關於其內部實現,這超出了本章的範圍,所以有興趣的小夥伴可以自行查閱瞭解細節。

學一個新的資料結構首先就是要學會它如何構造。有很多方式都可以構造DataFrame,最常用的方式就是利用包含等長度列表或Numpy陣列的字典來形成DataFrame:

python data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada', 'Nevada'], 'year': [2000, 2001, 2002, 2001, 2002, 2003], 'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]} frame = pd.DataFrame(data) 結果DataFrame會自動加上索引(跟Series一樣):

python In [24]:frame Out[24]: state year pop 0 Ohio 2000 1.5 1 Ohio 2001 1.7 2 Ohio 2002 3.6 3 Nevada 2001 2.4 4 Nevada 2002 2.9 5 Nevada 2003 3.2 如果你使用的是Jupyter notebook,pandas DataFrame物件會以對瀏覽器友好的HTML表格的方式呈現。 對於特別大的DataFrame,head方法會選取前五行:

python In [26]:frame.head() Out[27]: state year pop 0 Ohio 2000 1.5 1 Ohio 2001 1.7 2 Ohio 2002 3.6 3 Nevada 2001 2.4 4 Nevada 2002 2.9 如果你想要自定義列序,你需要制定好序列順序:

python In [27]:pd.DataFrame(data, columns=['year', 'state', 'pop']) Out[28]: year state pop 0 2000 Ohio 1.5 1 2001 Ohio 1.7 2 2002 Ohio 3.6 3 2001 Nevada 2.4 4 2002 Nevada 2.9 5 2003 Nevada 3.2 如果傳入的列在資料中找不到,就會在結果中產生缺失值:

```python In [28]:pd.DataFrame(data, columns=['year', 'state', 'pop', 'debt'], ....: index=['one', 'two', 'three', 'four', ....: 'five', 'six'])

Out[29]: year state pop debt one 2000 Ohio 1.5 NaN two 2001 Ohio 1.7 NaN three 2002 Ohio 3.6 NaN four 2001 Nevada 2.4 NaN five 2002 Nevada 2.9 NaN six 2003 Nevada 3.2 NaN ``` 通過類似字典標記的方式或屬性的方式,可以將DataFrame的列獲取為一個Series:

```python In [36]:frame2['year'] Out[33]: one 2000 two 2001 three 2002 four 2001 five 2002 six 2003 Name: year, dtype: int64

In [36]:frame2.state Out[34]: one Ohio two Ohio three Ohio four Nevada five Nevada six Nevada Name: state, dtype: object ``` 行也可以通過位置或名稱方式進行獲取,比如用到loc屬性:

python In [36]:frame2.loc['three'] Out[36]: year 2002 state Ohio pop 3.6 debt NaN Name: three, dtype: object 列可以通過賦值的方式進行修改。例如,我們可以給那個空的"debt"列賦上一個標量值或一組值:

```python In [37]: frame2['debt'] = 16

In [38]:frame2 Out[38]: year state pop debt one 2000 Ohio 1.5 16 two 2001 Ohio 1.7 16 three 2002 Ohio 3.6 16 four 2001 Nevada 2.4 16 five 2002 Nevada 2.9 16 six 2003 Nevada 3.2 16

In [39]: import numpy as np

In [40]: frame2['debt'] = np.arange(6.)

In [41]: frame2 Out[41]: year state pop debt one 2000 Ohio 1.5 0.0 two 2001 Ohio 1.7 1.0 three 2002 Ohio 3.6 2.0 four 2001 Nevada 2.4 3.0 five 2002 Nevada 2.9 4.0 six 2003 Nevada 3.2 5.0 ``` 將列表或陣列賦值給某個列時,其長度必須跟DataFrame的長度相匹配。如果賦值的是一個Series,就會精確匹配DataFrame的索引,所有的空位都將被填上缺失值:

```python In [42]: val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five'])

In [43]: frame2['debt'] = val

In [44]: frame2 Out[44]: year state pop debt one 2000 Ohio 1.5 NaN two 2001 Ohio 1.7 -1.2 three 2002 Ohio 3.6 NaN four 2001 Nevada 2.4 -1.5 five 2002 Nevada 2.9 -1.7 six 2003 Nevada 3.2 NaN ``` 為不存在的列賦值會創建出一個新列。關鍵字del用於刪除列。作為del的例子,我先新增一個新的布林值的列,state是否為'Ohio':

```python In [45]: frame2['ear'] = frame2.state == 'Ohio'

In [46]: frame2 Out[46]: year state pop debt ear one 2000 Ohio 1.5 NaN True two 2001 Ohio 1.7 -1.2 True three 2002 Ohio 3.6 NaN True four 2001 Nevada 2.4 -1.5 False five 2002 Nevada 2.9 -1.7 False six 2003 Nevada 3.2 NaN False ``` ~~注意這裡不能用frame2.eastern建立新的列。~~

del方法可以用來刪除這列:

```python In [47]: del frame2['ear']

In [48]: frame2.columns Out[48]: Index(['year', 'state', 'pop', 'debt'], dtype='object') ``` 注意:通過索引方式返回的列只是相應資料的檢視而已,並不是副本。因此,對返回的Series所做的任何就地修改全都會反映到源DataFrame上。通過Series的copy方法即可指定複製列。

另一種常見的資料形式是巢狀字典:

python In [49]: pop = {'Nevada': {2001: 2.4, 2002: 2.9}, ....: 'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}} 如果巢狀字典傳給DataFrame,pandas就會被解釋為:外層字典的鍵作為列,內層鍵則作為行索引:

```python In [50]: frame3 = pd.DataFrame(pop)

In [51]: frame3

Out[52]: Nevada Ohio 2000 NaN 1.5 2001 2.4 1.7 2002 2.9 3.6 ``` 你也可以使用類似NumPy陣列的方法,對DataFrame進行轉置(交換行和列):

python In [53]: frame3.T Out[53]: 2000 2001 2002 Nevada NaN 2.4 2.9 Ohio 1.5 1.7 3.6

索引物件

pandas的索引物件負責管理軸標籤和其他的元資料(例如軸名稱)。構建Series或DataFrame時,所用到的任何陣列或其他序列的標籤都會被轉換為Index:

```python In [76]: obj = pd.Series(range(3), index=['a', 'b', 'c'])

In [77]: index = obj.index

In [78]: index Out[78]: Index(['a', 'b', 'c'], dtype='object')

In [79]: index[1:] Out[79]: Index(['b', 'c'], dtype='object') ``` Index物件是不可變的,因此使用者不能對其進行修改。不可變可以使Index物件在多個數據結構之間安全共享:

```python In [80]: labels = pd.Index(np.arange(3))

In [81]: labels Out[81]: Int64Index([0, 1, 2], dtype='int64')

In [82]: obj2 = pd.Series([1.5, -2.5, 0], index=labels)

In [83]: obj2 Out[83]: 0 1.5 1 -2.5 2 0.0 dtype: float64

In [84]: obj2.index is labels Out[84]: True ``` 下圖表5-2列出了索引常見的方法和屬性

圖片.png

5.2基本功能

基礎概念介紹完畢了,接下來要對資料操作的基本手段進行一個解決,本文並不是羅列所有的pandas庫,所以只展示一些常用函式,如果要深入學習,可以自己仔細閱讀相關文件進一步學習。

重建索引

reindex是pandas物件的重要方法,該方法用於建立一個符合新索引的新物件:

```python In [91]: obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b', 'a', 'c'])

In [92]: obj Out[92]: d 4.5 b 7.2 a -5.3 c 3.6 dtype: float64 ``` 用該Series的reindex將會根據新索引進行重排。如果某個索引值當前不存在,就引入缺失值:

```python In [93]: obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e'])

In [94]: obj2 Out[94]: a -5.3 b 7.2 c 3.6 d 4.5 e NaN dtype: float64 ``` 對於順序結構的資料,比如遞增函式,在重建索引的時候會需要進行插值和填充。method方法可以允許我們對其插入,例如ffill方法,會在值前插入:

```python In [95]: obj3 = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])

In [96]: obj3 Out[96]: 0 blue 2 purple 4 yellow dtype: object

In [97]: obj3.reindex(range(6), method='ffill') Out[97]: 0 blue 1 blue 2 purple 3 purple 4 yellow 5 yellow dtype: object ``` 在DataFrame中,reindex仍然可以更改行與列的索引值。只需要傳遞一個序列,結果就會重置:

```python In [98]: frame = pd.DataFrame(np.arange(9).reshape((3, 3)), ....: index=['a', 'c', 'd'], ....: columns=['Ohio', 'Texas', 'California'])

In [99]: frame Out[99]: Ohio Texas California a 0 1 2 c 3 4 5 d 6 7 8

In [100]: frame2 = frame.reindex(['a', 'b', 'c', 'd'])

In [101]: frame2 Out[101]: Ohio Texas California a 0.0 1.0 2.0 b NaN NaN NaN c 3.0 4.0 5.0 d 6.0 7.0 8.0 ``` 列可以用columns關鍵字重新索引:

```python In [102]: states = ['Texas', 'Utah', 'California']

In [103]: frame.reindex(columns=states) Out[103]: Texas Utah California a 1 NaN 2 c 4 NaN 5 d 7 NaN 8 ```

軸向上刪除條目

如果此時你已經擁有了索引陣列或不含條目的列表,在軸向上刪除一個或更多的條目就很容易,但這樣需要一些資料操作和集合邏輯,drop方法會返回一個含有指示值或軸向上刪除值的新物件:

```python In [105]: obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])

In [106]: obj Out[106]: a 0.0 b 1.0 c 2.0 d 3.0 e 4.0 dtype: float64

In [107]: new_obj = obj.drop('c')

In [108]: new_obj Out[108]: a 0.0 b 1.0 d 3.0 e 4.0 dtype: float64

In [109]: obj.drop(['d', 'c']) Out[109]: a 0.0 b 1.0 e 4.0 dtype: float64 ``` 在DataFrame中,有類似的屬性方法,具體就不再演示了,大家可以自己嘗試。不過要小心使用drop函式中的inplace屬性,小心使用inplace,它會銷燬所有被刪除的資料。‘

索引、選取和過濾

Series的索引工作方式類似於Numpy,只不過Series有更豐富的索引型別:

```python In [117]: obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])

In [118]: obj Out[118]: a 0.0 b 1.0 c 2.0 d 3.0 dtype: float64

In [119]: obj['b'] Out[119]: 1.0

In [120]: obj[1] Out[120]: 1.0

In [121]: obj[2:4] Out[121]: c 2.0 d 3.0 dtype: float64

In [122]: obj[['b', 'a', 'd']] Out[122]: b 1.0 a 0.0 d 3.0 dtype: float64

In [123]: obj[[1, 3]] Out[123]: b 1.0 d 3.0 dtype: float64

In [124]: obj[obj < 2] Out[124]: a 0.0 b 1.0 dtype: float64 ``` DataFrame的操作方法類似Series。同樣可以通過上述方法進行選擇。這使得DataFrame的語法與NumPy二維陣列的語法很像。

用loc和iloc進行選取

對於DataFrame的行標籤,pandas引入了loc和iloc,它們可以讓使用者能夠通過類似與numpy的標記方式,使用軸標籤(loc)或整數標籤(iloc),從DataFrame選擇行和列的子集。以下作一個初步示例,我們來通過標籤選擇一行或多列:

python In [137]: data.loc['Colorado', ['two', 'three']] Out[137]: two 5 three 6 Name: Colorado, dtype: int64 然後用iloc和整數進行選取,數字就代表行與列:

```python In [138]: data.iloc[2, [3, 0, 1]] Out[138]: four 11 one 8 two 9 Name: Utah, dtype: int64

In [139]: data.iloc[2] Out[139]: one 8 two 9 three 10 four 11 Name: Utah, dtype: int64

In [140]: data.iloc[[1, 2], [3, 0, 1]] Out[140]: four one two Colorado 7 0 5 Utah 11 8 9 ``` 這兩個索引函式也適用於一個標籤或多個標籤的切片。所以,在pandas中,有多個方法可以選取和重新組合資料。對於DataFrame,圖5-1進行了總結。後面會看到,還有更多的方法進行層級化索引。

圖片.png

圖5-1 DataFrame的常用索引選項

由於內容太多這裡分一篇!!