【翻譯】《利用Python進行數據分析·第2版》第5章(中)pandas入門

作者:SeanCheney Python愛好者社區專欄作者

簡書專欄:jianshu.com/u/130f76596

公眾號:Python愛好者社區

前文傳送門:

【翻譯】《利用Python進行數據分析·第2版》第1章 準備工作

【翻譯】《利用Python進行數據分析·第2版》第2章(上)Python語法基礎,IPython和Jupyter

【翻譯】《利用Python進行數據分析·第2版》第2章(中)Python語法基礎,IPython和Jupyter

【翻譯】《利用Python進行數據分析·第2版》第2章(下)Python語法基礎,IPython和Jupyter

【翻譯】《利用Python進行數據分析·第2版》第3章(上)Python的數據結構、函數和文件

【翻譯】《利用Python進行數據分析·第2版》第3章(中)Python的數據結構、函數和文件

【翻譯】《利用Python進行數據分析·第2版》第3章(下)Python的數據結構、函數和文件

【翻譯】《利用Python進行數據分析·第2版》第4章(上)NumPy基礎:數組和矢量計算

【翻譯】《利用Python進行數據分析·第2版》第4章(中)NumPy基礎:數組和矢量計算

【翻譯】《利用Python進行數據分析·第2版》第4章(下)NumPy基礎:數組和矢量計算

【翻譯】《利用Python進行數據分析·第2版》第5章(上)pandas入門

5.2 基本功能

本節中,我將介紹操作Series和DataFrame中的數據的基本手段。後續章節將更加深入地挖掘pandas在數據分析和處理方面的功能。本書不是pandas庫的詳盡文檔,主要關注的是最重要的功能,那些不大常用的內容(也就是那些更深奧的內容)就交給你自己去摸索吧。

重新索引

pandas對象的一個重要方法是reindex,其作用是創建一個新對象,它的數據符合新的索引。看下面的例子:

In [91]: obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=[d, b, a, c])In [92]: objOut[92]: d 4.5b 7.2a -5.3c 3.6dtype: float64

用該Series的reindex將會根據新索引進行重排。如果某個索引值當前不存在,就引入缺失值:

In [93]: obj2 = obj.reindex([a, b, c, d, e])In [94]: obj2Out[94]: a -5.3b 7.2c 3.6d 4.5e NaNdtype: float64

對於時間序列這樣的有序數據,重新索引時可能需要做一些插值處理。method選項即可達到此目的,例如,使用ffill可以實現前向值填充:

In [95]: obj3 = pd.Series([blue, purple, yellow], index=[0, 2, 4])In [96]: obj3Out[96]: 0 blue2 purple4 yellowdtype: objectIn [97]: obj3.reindex(range(6), method=ffill)Out[97]: 0 blue1 blue2 purple3 purple4 yellow5 yellowdtype: object

藉助DataFrame,reindex可以修改(行)索引和列。只傳遞一個序列時,會重新索引結果的行:

In [98]: frame = pd.DataFrame(np.arange(9).reshape((3, 3)), ....: index=[a, c, d], ....: columns=[Ohio, Texas, California])In [99]: frameOut[99]: Ohio Texas Californiaa 0 1 2c 3 4 5d 6 7 8In [100]: frame2 = frame.reindex([a, b, c, d])In [101]: frame2Out[101]: Ohio Texas Californiaa 0.0 1.0 2.0b NaN NaN NaNc 3.0 4.0 5.0d 6.0 7.0 8.0

列可以用columns關鍵字重新索引:

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

表5-3列出了reindex函數的各參數及說明。

丟棄指定軸上的項

丟棄某條軸上的一個或多個項很簡單,只要有一個索引數組或列表即可。由於需要執行一些數據整理和集合邏輯,所以drop方法返回的是一個在指定軸上刪除了指定值的新對象:

In [105]: obj = pd.Series(np.arange(5.), index=[a, b, c, d, e])In [106]: objOut[106]: a 0.0b 1.0c 2.0d 3.0e 4.0dtype: float64In [107]: new_obj = obj.drop(c)In [108]: new_objOut[108]: a 0.0b 1.0d 3.0e 4.0dtype: float64In [109]: obj.drop([d, c])Out[109]: a 0.0b 1.0e 4.0dtype: float64

對於DataFrame,可以刪除任意軸上的索引值。為了演示,先新建一個DataFrame例子:

In [110]: data = pd.DataFrame(np.arange(16).reshape((4, 4)), .....: index=[Ohio, Colorado, Utah, New York], .....: columns=[one, two, three, four])In [111]: dataOut[111]: one two three fourOhio 0 1 2 3Colorado 4 5 6 7Utah 8 9 10 11New York 12 13 14 15

用標籤序列調用drop會從行標籤(axis 0)刪除值:

In [112]: data.drop([Colorado, Ohio])Out[112]: one two three fourUtah 8 9 10 11New York 12 13 14 15

通過傳遞axis=1或axis=columns可以刪除列的值:

In [113]: data.drop(two, axis=1)Out[113]: one three fourOhio 0 2 3Colorado 4 6 7Utah 8 10 11New York 12 14 15In [114]: data.drop([two, four], axis=columns)Out[114]: one threeOhio 0 2Colorado 4 6Utah 8 10New York 12 14

許多函數,如drop,會修改Series或DataFrame的大小或形狀,可以就地修改對象,不會返回新的對象:

In [115]: obj.drop(c, inplace=True)In [116]: objOut[116]: a 0.0b 1.0d 3.0e 4.0dtype: float64

小心使用inplace,它會銷毀所有被刪除的數據。

索引、選取和過濾

Series索引(obj[...])的工作方式類似於NumPy數組的索引,只不過Series的索引值不只是整數。下面是幾個例子:

In [117]: obj = pd.Series(np.arange(4.), index=[a, b, c, d])In [118]: objOut[118]: a 0.0b 1.0c 2.0d 3.0dtype: float64In [119]: obj[b]Out[119]: 1.0In [120]: obj[1]Out[120]: 1.0In [121]: obj[2:4]Out[121]: c 2.0d 3.0dtype: float64In [122]: obj[[b, a, d]]Out[122]:b 1.0a 0.0d 3.0dtype: float64In [123]: obj[[1, 3]]Out[123]: b 1.0d 3.0dtype: float64In [124]: obj[obj < 2]Out[124]: a 0.0b 1.0dtype: float64

利用標籤的切片運算與普通的Python切片運算不同,其末端是包含的:

In [125]: obj[b:c]Out[125]:b 1.0c 2.0dtype: float64

用切片可以對Series的相應部分進行設置:

In [126]: obj[b:c] = 5In [127]: objOut[127]: a 0.0b 5.0c 5.0d 3.0dtype: float64

用一個值或序列對DataFrame進行索引其實就是獲取一個或多個列:

In [128]: data = pd.DataFrame(np.arange(16).reshape((4, 4)), .....: index=[Ohio, Colorado, Utah, New York], .....: columns=[one, two, three, four])In [129]: dataOut[129]: one two three fourOhio 0 1 2 3Colorado 4 5 6 7Utah 8 9 10 11New York 12 13 14 15In [130]: data[two]Out[130]: Ohio 1Colorado 5Utah 9New York 13Name: two, dtype: int64In [131]: data[[three, one]]Out[131]: three oneOhio 2 0Colorado 6 4Utah 10 8New York 14 12

這種索引方式有幾個特殊的情況。首先通過切片或布爾型數組選取數據:

In [132]: data[:2]Out[132]: one two three fourOhio 0 1 2 3Colorado 4 5 6 7In [133]: data[data[three] > 5]Out[133]: one two three fourColorado 4 5 6 7Utah 8 9 10 11New York 12 13 14 15

選取行的語法data[:2]十分方便。向[ ]傳遞單一的元素或列表,就可選擇列。

另一種用法是通過布爾型DataFrame(比如下面這個由標量比較運算得出的)進行索引:

In [134]: data < 5Out[134]: one two three fourOhio True True True TrueColorado True False False FalseUtah False False False FalseNew York False False False FalseIn [135]: data[data < 5] = 0In [136]: dataOut[136]: one two three fourOhio 0 0 0 0Colorado 0 5 6 7Utah 8 9 10 11New York 12 13 14 15

這使得DataFrame的語法與NumPy二維數組的語法很像。

用loc和iloc進行選取

對於DataFrame的行的標籤索引,我引入了特殊的標籤運算符loc和iloc。它們可以讓你用類似NumPy的標記,使用軸標籤(loc)或整數索引(iloc),從DataFrame選擇行和列的子集。

作為一個初步示例,讓我們通過標籤選擇一行和多列:

In [137]: data.loc[Colorado, [two, three]]Out[137]: two 5three 6Name: Colorado, dtype: int64

然後用iloc和整數進行選取:

In [138]: data.iloc[2, [3, 0, 1]]Out[138]: four 11one 8two 9Name: Utah, dtype: int64In [139]: data.iloc[2]Out[139]: one 8two 9three 10four 11Name: Utah, dtype: int64In [140]: data.iloc[[1, 2], [3, 0, 1]]Out[140]: four one twoColorado 7 0 5Utah 11 8 9

這兩個索引函數也適用於一個標籤或多個標籤的切片:

In [141]: data.loc[:Utah, two]Out[141]: Ohio 0Colorado 5Utah 9Name: two, dtype: int64In [142]: data.iloc[:, :3][data.three > 5]Out[142]: one two threeColorado 0 5 6Utah 8 9 10New York 12 13 14

所以,在pandas中,有多個方法可以選取和重新組合數據。對於DataFrame,表5-4進行了總結。後面會看到,還有更多的方法進行層級化索引。

筆記:在一開始設計pandas時,我覺得用frame[:, col]選取列過於繁瑣(也容易出錯),因為列的選擇是非常常見的操作。我做了些取捨,將花式索引的功能(標籤和整數)放到了ix運算符中。在實踐中,這會導致許多邊緣情況,數據的軸標籤是整數,所以pandas團隊決定創造loc和iloc運算符分別處理嚴格基於標籤和整數的索引。

ix運算符仍然可用,但並不推薦。

表5-4 DataFrame的索引選項

整數索引

處理整數索引的pandas對象常常難住新手,因為它與Python內置的列表和元組的索引語法不同。例如,你可能不認為下面的代碼會出錯:

ser = pd.Series(np.arange(3.))serser[-1]

這裡,pandas可以勉強進行整數索引,但是會導致小bug。我們有包含0,1,2的索引,但是引入用戶想要的東西(基於標籤或位置的索引)很難:

In [144]: serOut[144]: 0 0.01 1.02 2.0dtype: float64

另外,對於非整數索引,不會產生歧義:

In [145]: ser2 = pd.Series(np.arange(3.), index=[a, b, c])In [146]: ser2[-1]Out[146]: 2.0

為了進行統一,如果軸索引含有整數,數據選取總會使用標籤。為了更準確,請使用loc(標籤)或iloc(整數):

In [147]: ser[:1]Out[147]: 0 0.0dtype: float64In [148]: ser.loc[:1]Out[148]: 0 0.01 1.0dtype: float64In [149]: ser.iloc[:1]Out[149]: 0 0.0dtype: float64

算術運算和數據對齊

pandas最重要的一個功能是,它可以對不同索引的對象進行算術運算。在將對象相加時,如果存在不同的索引對,則結果的索引就是該索引對的並集。對於有資料庫經驗的用戶,這就像在索引標籤上進行自動外連接。看一個簡單的例子:

In [150]: s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=[a, c, d, e])In [151]: s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1], .....: index=[a, c, e, f, g])In [152]: s1Out[152]: a 7.3c -2.5d 3.4e 1.5dtype: float64In [153]: s2Out[153]: a -2.1c 3.6e -1.5f 4.0g 3.1dtype: float64

將它們相加就會產生:

In [154]: s1 + s2Out[154]: a 5.2c 1.1d NaNe 0.0f NaNg NaNdtype: float64

自動的數據對齊操作在不重疊的索引處引入了NA值。缺失值會在算術運算過程中傳播。

對於DataFrame,對齊操作會同時發生在行和列上:

In [155]: df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list(bcd), .....: index=[Ohio, Texas, Colorado])In [156]: df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list(bde), .....: index=[Utah, Ohio, Texas, Oregon])In [157]: df1Out[157]: b c dOhio 0.0 1.0 2.0Texas 3.0 4.0 5.0Colorado 6.0 7.0 8.0In [158]: df2Out[158]: b d eUtah 0.0 1.0 2.0Ohio 3.0 4.0 5.0Texas 6.0 7.0 8.0Oregon 9.0 10.0 11.0

把它們相加後將會返回一個新的DataFrame,其索引和列為原來那兩個DataFrame的並集:

In [159]: df1 + df2Out[159]: b c d eColorado NaN NaN NaN NaNOhio 3.0 NaN 6.0 NaNOregon NaN NaN NaN NaNTexas 9.0 NaN 12.0 NaNUtah NaN NaN NaN NaN

因為c和e列均不在兩個DataFrame對象中,在結果中以預設值呈現。行也是同樣。

如果DataFrame對象相加,沒有共用的列或行標籤,結果都會是空:

In [160]: df1 = pd.DataFrame({A: [1, 2]})In [161]: df2 = pd.DataFrame({B: [3, 4]})In [162]: df1Out[162]: A0 11 2In [163]: df2Out[163]: B0 31 4In [164]: df1 - df2Out[164]: A B0 NaN NaN1 NaN NaN

在算術方法中填充值

在對不同索引的對象進行算術運算時,你可能希望當一個對象中某個軸標籤在另一個對象中找不到時填充一個特殊值(比如0):

In [165]: df1 = pd.DataFrame(np.arange(12.).reshape((3, 4)), .....: columns=list(abcd))In [166]: df2 = pd.DataFrame(np.arange(20.).reshape((4, 5)), .....: columns=list(abcde))In [167]: df2.loc[1, b] = np.nanIn [168]: df1Out[168]: a b c d0 0.0 1.0 2.0 3.01 4.0 5.0 6.0 7.02 8.0 9.0 10.0 11.0In [169]: df2Out[169]: a b c d e0 0.0 1.0 2.0 3.0 4.01 5.0 NaN 7.0 8.0 9.02 10.0 11.0 12.0 13.0 14.03 15.0 16.0 17.0 18.0 19.0

將它們相加時,沒有重疊的位置就會產生NA值:

In [170]: df1 + df2Out[170]: a b c d e0 0.0 2.0 4.0 6.0 NaN1 9.0 NaN 13.0 15.0 NaN2 18.0 20.0 22.0 24.0 NaN3 NaN NaN NaN NaN NaN使用df1的add方法,傳入df2以及一個fill_value參數:```pythonIn [171]: df1.add(df2, fill_value=0)Out[171]: a b c d e0 0.0 2.0 4.0 6.0 4.01 9.0 5.0 13.0 15.0 9.02 18.0 20.0 22.0 24.0 14.03 15.0 16.0 17.0 18.0 19.0

表5-5列出了Series和DataFrame的算術方法。它們每個都有一個副本,以字母r開頭,它會翻轉參數。因此這兩個語句是等價的:

In [172]: 1 / df1Out[172]: a b c d0 inf 1.000000 0.500000 0.3333331 0.250000 0.200000 0.166667 0.1428572 0.125000 0.111111 0.100000 0.090909In [173]: df1.rdiv(1)Out[173]: a b c d0 inf 1.000000 0.500000 0.3333331 0.250000 0.200000 0.166667 0.1428572 0.125000 0.111111 0.100000 0.090909

表5-5 靈活的算術方法

與此類似,在對Series或DataFrame重新索引時,也可以指定一個填充值:

In [174]: df1.reindex(columns=df2.columns, fill_value=0)Out[174]: a b c d e0 0.0 1.0 2.0 3.0 01 4.0 5.0 6.0 7.0 02 8.0 9.0 10.0 11.0 0

推薦閱讀:

Python數據分析(一)
走出迷茫——數據分析實踐計劃
零基礎學習Python數據分析:科學計算庫NumPy(2)
Python爬蟲入門 | 5 爬取小豬短租租房信息
人工智慧能給運營商市場帶來哪些改變

TAG:Python | pandas包 | 數據分析 |