1. 程式人生 > >資料基礎---《利用Python進行資料分析·第2版》第5章 pandas入門

資料基礎---《利用Python進行資料分析·第2版》第5章 pandas入門

之前自己對於numpy和pandas是要用的時候東學一點西一點,直到看到《利用Python進行資料分析·第2版》,覺得只看這一篇就夠了。非常感謝原博主的翻譯和分享。

pandas是本書後續內容的首選庫。它含有使資料清洗和分析工作變得更快更簡單的資料結構和操作工具。pandas經常和其它工具一同使用,如數值計算工具NumPy和SciPy,分析庫statsmodels和scikit-learn,和資料視覺化庫matplotlib。pandas是基於NumPy陣列構建的,特別是基於陣列的函式和不使用for迴圈的資料處理。

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

自從2010年pandas開源以來,pandas逐漸成長為一個非常大的庫,應用於許多真實案例。開發者社群已經有了800個獨立的貢獻者,他們在解決日常資料問題的同時為這個專案提供貢獻。

在本書後續部分中,我將使用下面這樣的pandas引入約定:

import pandas as pd

因此,只要你在程式碼中看到pd.,就得想到這是pandas。因為Series和DataFrame用的次數非常多,所以將其引入本地名稱空間中會更方便:

from pandas import Series, DataFrame

5.1 pandas的資料結構介紹

要使用pandas,你首先就得熟悉它的兩個主要資料結構:Series和DataFrame。雖然它們並不能解決所有問題,但它們為大多數應用提供了一種可靠的、易於使用的基礎。
(DataFrame可以看作是Series是堆疊)

Series

Series是一種類似於一維陣列的物件,它由一組資料(各種NumPy資料型別)以及一組與之相關的資料標籤(即索引)組成。僅由一組資料即可產生最簡單的Series:
numpy中的array也有索引,但是array索引只能是整數型,而且不會顯示的打印出來;但是Series和DataFrame的索引可以是字元型,也能顯示打印出來。

obj=pd.Series([4, 7, -5, 3])
obj
0    4
1    7
2   -5
3    3
dtype: int64

Series的字串表現形式為:索引在左邊,值在右邊。由於我們沒有為資料指定索引,於是會自動建立一個0到N-1(N為資料的長度)的整數型索引。你可以通過Series 的values和index屬性獲取其陣列表示形式和索引物件:

obj.index
RangeIndex(start=0, stop=4, step=1)

可以看到Index是一種資料物件,Series的索引、DataFrame的索引和列名都是Index資料物件。上面我們看到的是RangeIndex資料物件。

obj.values
array([ 4,  7, -5,  3], dtype=int64)

通常,我們希望所建立的Series帶有一個可以對各個資料點進行標記的索引:因為用字串命名更容易與業務的含義對應

obj2=pd.Series([4, 7, -5, 3],index=['d','b','a','c'])
obj2
d    4
b    7
a   -5
c    3
dtype: int64
obj2.index
Index(['d', 'b', 'a', 'c'], dtype='object')

與普通NumPy陣列相比,你可以通過索引的方式選取Series中的單個或一組值:

obj2['a']
-5
obj2['d']=6
obj2
d    6
b    7
a   -5
c    3
dtype: int64

從上面可以看到對Series索引取數跟numpy中一樣,也是獲得一個檢視,因而對對應位置的賦值會改變原Series物件,而不是返回一個新的物件。

obj2[['c', 'a', 'd']]
c    3
a   -5
d    6
dtype: int64

[‘c’, ‘a’, ‘d’]是索引列表,即使它包含的是字串而不是整數。

obj2[['c', 'a', 'd']]=1
obj2
d    1
b    7
a    1
c    1
dtype: int64
obj2[['c', 'a', 'd']]['a']=0
obj2
d    1
b    7
a    1
c    1
dtype: int64
obj2[['c', 'a', 'd']]['a']
1

可以看到不管在numpy中還是在pandas中,不管是普通索引還是花式索引(我理解的花式索引就是通過陣列來索引),不管是對一個軸進行索引還是對多個軸同時索引,其實獲得的都是檢視,對檢視進行賦值都會改變原物件的值。但是一次索引之後緊接著二次索引,我們仍能獲取物件的元素,此時對這些元素位置進行賦值不會改變原物件。該博主說它總是將資料複製到新陣列中,既然是新的陣列,當然就不會對原資料產生影響。

使用NumPy函式或類似NumPy的運算(如根據布林型陣列進行過濾、標量乘法、應用數學函式等)都會保留索引值的連結:

obj2[obj2 > 1]
b    7
dtype: int64

上面雖然只有一個元素,但仍然是一個Series物件。obj2 > 1其實得到是一個布林型索引,布林型索引的使用和整數型索引和字元型索引是一樣的。在numpy中和pd.DataFrame中也是同樣適用的。

obj2 * 2
d     2
b    14
a     2
c     2
dtype: int64
obj2
d    1
b    7
a    1
c    1
dtype: int64
np.exp(obj2)
d       2.718282
b    1096.633158
a       2.718282
c       2.718282
dtype: float64
obj2
d    1
b    7
a    1
c    1
dtype: int64

可以看到在pandas中與在numpy中一樣,對Series的函式操作是元素級的,並且與賦值不同,函式操作不會對原資料物件產生改變

還可以將Series看成是一個定長的有序字典,因為它是索引值到資料值的一個對映。它可以用在許多原本需要字典引數的函式中:索引相當於鍵

'b' in obj2
True
'e' in obj2
False

如果資料被存放在一個Python字典中,也可以直接通過這個字典來建立Series:

sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
obj3=pd.Series(sdata)
obj3
Ohio      35000
Oregon    16000
Texas     71000
Utah       5000
dtype: int64

如果只傳入一個字典,則結果Series中的索引就是原字典的鍵(有序排列)。你可以傳入排好序的字典的鍵以改變順序,值的對應關係不會變,變的前後順序,如果索引不在原字典中,對應的值就為空:

states = ['California', 'Ohio', 'Oregon', 'Texas']
obj4=pd.Series(sdata,index=states)
obj4
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
dtype: float64

在這個例子中,sdata中跟states索引相匹配的那3個值會被找出來並放到相應的位置上,但由於"California"所對應的sdata值找不到,所以其結果就為NaN(即“非數字”(not a number),在pandas中,它用於表示缺失或NA值)。因為‘Utah’不在states中,它被從結果中除去。

我將使用缺失(missing)或NA表示缺失資料。pandas的isnull和notnull函式可用於檢測缺失資料:

pd.isnull(obj4)
California     True
Ohio          False
Oregon        False
Texas         False
dtype: bool
pd.notnull(obj4)
California    False
Ohio           True
Oregon         True
Texas          True
dtype: bool

可以看到,得到的是bool值的series。Series也有類似的例項方法:

obj4.isnull()
California     True
Ohio          False
Oregon        False
Texas         False
dtype: bool

我將在第7章詳細講解如何處理缺失資料。

對於許多應用而言,Series最重要的一個功能是,它會根據運算的索引標籤自動對齊資料:

obj3
Ohio      35000
Oregon    16000
Texas     71000
Utah       5000
dtype: int64
obj4
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
dtype: float64
obj3+obj4
California         NaN
Ohio           70000.0
Oregon         32000.0
Texas         142000.0
Utah               NaN
dtype: float64

首先會將兩者的索引合併,然後將對應索引位置相加,如果某一方面有缺失值,則結果中對應位置的值也是缺失值。
資料對齊功能將在後面詳細講解。如果你使用過資料庫,你可以認為是類似join的操作。

Series物件本身及其索引都有一個name屬性,該屬性跟pandas其他的關鍵功能關係非常密切:

obj4.name = 'population'
obj4.index.name = 'state'
obj4
state
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
Name: population, dtype: float64

Series的索引可以通過賦值的方式就地修改:

obj
0    4
1    7
2   -5
3    3
dtype: int64
obj.index=['Bob', 'Steve', 'Jeff', 'Ryan']
obj
Bob      4
Steve    7
Jeff    -5
Ryan     3
dtype: int64

DataFrame

DataFrame是一個表格型的資料結構,它含有一組有序的列,每列可以是不同的值型別(數值、字串、布林值等)。DataFrame既有行索引也有列索引,它可以被看做由Series組成的字典(共用同一個索引)。DataFrame中的資料是以一個或多個二維塊存放的(而不是列表、字典或別的一維資料結構)。有關DataFrame內部的技術細節遠遠超出了本書所討論的範圍。

筆記:雖然DataFrame是以二維結構儲存資料的,但你仍然可以輕鬆地將其表示為更高維度的資料(層次化索引的表格型結構,這是pandas中許多高階資料處理功能的關鍵要素,我們會在第8章討論這個問題)。

建DataFrame的辦法有很多,最常用的一種是直接傳入一個由等長列表或NumPy陣列組成的字典:

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)
frame
pop state year
0 1.5 Ohio 2000
1 1.7 Ohio 2001
2 3.6 Ohio 2002
3 2.4 Nevada 2001
4 2.9 Nevada 2002
5 3.2 Nevada 2003

如果你使用的是Jupyter notebook,pandas DataFrame物件會以對瀏覽器友好的HTML表格的方式呈現。

對於特別大的DataFrame,head方法會選取前五行:

frame.head()
pop state year
0 1.5 Ohio 2000
1 1.7 Ohio 2001
2 3.6 Ohio 2002
3 2.4 Nevada 2001
4 2.9 Nevada 2002

如果指定了列序列,則DataFrame的列就會按照指定順序進行排列:

pd.DataFrame(data,columns=['year', 'state', 'pop'])
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

如果傳入的列在資料中找不到,就會在結果中產生缺失值:

frame2=pd.DataFrame(data,columns=['year', 'state', 'pop', 'debt'],index=['one', 'two', 'three', 'four','five', 'six'])
frame2
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
frame2.columns
Index(['year', 'state', 'pop', 'debt'], dtype='object')
frame2.index
Index(['one', 'two', 'three', 'four', 'five', 'six'], dtype='object')

可以看到df.index和df.columns是一樣的資料物件Index。很多性質和用法,在index上操作方法和在columns上是一樣的。

通過類似字典標記的方式或屬性的方式,可以將DataFrame的列獲取為一個Series:

frame2['state']
one        Ohio
two        Ohio
three      Ohio
four     Nevada
five     Nevada
six      Nevada
Name: state, dtype: object
frame2.year
one      2000
two      2001
three    2002
four     2001
five     2002
six      2003
Name: year, dtype: int64

筆記:IPython提供了類似屬性的訪問(即frame2.year)和tab補全。
frame2[column]適用於任何列的名,但是frame2.column只有在列名是一個合理的Python變數名時才適用。

注意,返回的Series擁有原DataFrame相同的索引,且其name屬性也已經被相應地設定好了。可以看到從DataFrame中取出來的Series比單純生成的Series資訊會更豐富一點。

行也可以通過位置或名稱的方式進行獲取,比如用loc屬性(稍後將對此進行詳細講解):

frame2.loc['three']
year     2002
state    Ohio
pop       3.6
debt      NaN
Name: three, dtype: object

可以看到,loc或iloc是通過索引來操作資料的,也可以同時在[]中加入行索引和列索引,可以單獨用行索引取一行確不能通過.loc單獨取一列。

列可以通過賦值的方式進行修改。例如,我們可以給那個空的"debt"列賦上一個標量值或一組值:

frame2['debt']=16.5
frame2
year state pop debt
one 2000 Ohio 1.5 16.5
two 2001 Ohio 1.7 16.5
three 2002 Ohio 3.6 16.5
four 2001 Nevada 2.4 16.5
five 2002 Nevada 2.9 16.5
six 2003 Nevada 3.2 16.5
frame2['debt'] = np.arange(6.)
frame2
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的索引,所有的空位都將被填上缺失值。在我看來Series和DataFrame沒有太大差別

val=pd.Series([-1.2, -1.5, -1.7],index=['two', 'four', 'five'])
frame2['debt']=val
frame2
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

為不存在的列賦值會創建出一個新列。所以即使不用merge、john、concat、這些操作我們也能生成新的列。
關鍵字del用於刪除列。
作為del的例子,我先新增一個新的布林值的列,state是否為’Ohio’:

frame2['eastern']= frame2.state=='Ohio'
frame2
year state pop debt eastern
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方法可以用來刪除這列:

del frame2['eastern']
frame2
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

注意:通過索引方式返回的列只是相應資料的檢視而已,並不是副本。因此,對返回的Series所做的任何就地修改全都會反映到源DataFrame上。通過Series的copy方法即可指定複製列。

另一種常見的資料形式是巢狀字典:
如果巢狀字典傳給DataFrame,pandas就會被解釋為:外層字典的鍵作為列,內層鍵則作為行索引,而且會將內層索引補齊:

pop = {'Nevada': {2001: 2.4, 2002: 2.9}, 'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}
frame3=pd.DataFrame(pop)
frame3
Nevada Ohio
2000 NaN 1.5
2001 2.4 1.7
2002 2.9 3.6

你也可以使用類似NumPy陣列的方法,對DataFrame進行轉置(交換行和列):

frame3.T
2000 2001 2002
Nevada NaN 2.4 2.9
Ohio 1.5 1.7 3.6
frame3.T.index
Index(['Nevada', 'Ohio'], dtype='object')

內層字典的鍵會被合併、排序以形成最終的索引。如果明確指定了索引,則不會這樣,指明瞭索引還是按之前Series和DataFrame一樣,會用索引來找對應的值,以索引為準,而不是源資料,找不到的為空:

import pandas as pd
pop = {'Nevada': {2001: 2.4, 2002: 2.9},'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}
pd.DataFrame(pop, index=pd.Index([2001, 2002, 2003]))
Nevada Ohio
2001 2.4 1.7
2002 2.9 3.6
2003 NaN NaN

在series中直接用index=[2000, 2002, 2001],原博主在DataFrame中也是直接index=[2000, 2002, 2001],但不知道我為什麼出錯,可能是python版本的問題。這裡改為index=pd.Index([2001, 2002, 2003])

由Series組成的字典差不多也是一樣的用法:

pdata = {'Ohio': frame3['Ohio'][:-1], 'Nevada': frame3['Nevada'][:2]}
pdata
{'Nevada': 2000    NaN
 2001    2.4
 Name: Nevada, dtype: float64, 'Ohio': 2000    1.5
 2001    1.7
 Name: Ohio, dtype: float64}
pd.DataFrame(pdata)
Nevada Ohio
2000 NaN 1.5
2001 2.4 1.7

表5-1列出了DataFrame建構函式所能接受的各種資料。

表5-1:可以輸入給 DataFrame構造器的資料
型別 說明
二維ndarray 資料矩陣,還可以傳入行標和列標
由陣列、列表或元組組成的字典 每個序列會變成 Dataframe的一列。所有序列的長度必須相同
NumPy的結構化/記錄陣列 類似於“由陣列組成的字典”
由 Series組成的字典 每個 Series會成為一列。如果沒有顯式指定索引,則各 Series的索引會被合併成結果的行索引
由字典組成的字典 各內層字典會成為一列。鍵會被合併成結果的行索引,跟“由 Series組成的字典”的情況一樣
字典或 Series的列表 各項將會成為 Dataframe的一行。字典鍵或 Series索引的並集將會成為 Data frame的列標
由列表或元組組成的列表 類似於“二維 ndarray
另一個 Dataframe 該 Data frame的索引將會被沿用,除非顯式指定了其他索引
NumPy的 Masked Array 類似於“二維 ndarray”的情況,只是掩碼值在結果Dataframe會變成NA缺失值

如果設定了DataFrame的index和columns的name屬性,則這些資訊也會被顯示出來:

frame3.index.name='year'
frame3.columns.name='state'
frame3
state Nevada Ohio
year
2000 NaN 1.5
2001 2.4 1.7
2002 2.9 3.6

跟Series一樣,values屬性也會以二維ndarray的形式返回DataFrame中的資料:

frame3.values
array([[nan, 1.5],
       [2.4, 1.7],
       [2.9, 3.6]])

如果DataFrame各列的資料型別不同,則值陣列的dtype就會選用能相容所有列的資料型別:

frame2.values
array([[2000, 'Ohio', 1.5, nan],
       [2001, 'Ohio', 1.7, nan],
       [2002, 'Ohio', 3.6, nan],
       [2001, 'Nevada', 2.4, nan],
       [2002, 'Nevada', 2.9, nan],
       [2003, 'Nevada', 3.2, nan]], dtype=object)

索引物件

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

obj = pd.Series(range(3), index=['a', 'b', 'c'])
index = obj.index
index
Index(['a', 'b', 'c'], dtype='object')
index[1:]
Index(['b', 'c'], dtype='object')

Index物件是不可變的,因此使用者不能對其進行修改:

index[1]='d'
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-18-8be6e68dba2d> in <module>()
----> 1 index[1]='d'


c:\users\qingt\miniconda2\envs\python35\lib\site-packages\pandas\core\indexes\base.py in __setitem__(self, key, value)
   2063 
   2064     def __setitem__(self, key, value):
-> 2065         raise TypeError("Index does not support mutable operations")
   2066 
   2067     def __getitem__(self, key):


TypeError: Index does not support mutable operations

不可變可以使Index物件在多個數據結構之間安全共享

import numpy as np
labels=pd.Index(np.arange(3))#主動生成index物件
labels
Int64Index([0, 1, 2], dtype='int64')
obj2 = pd.Series([1.5, -2.5, 0], index=labels)#可以直接賦一個列表,但這樣更保險,不會出錯
obj2
0    1.5
1   -2.5
2    0.0
dtype: float64
obj2.index is labels
True

注意:雖然使用者不需要經常使用Index的功能,但是因為一些操作會生成包含被索引化的資料,理解它們的工作原理是很重要的。

除了類似於陣列,Index的功能也類似一個固定大小的集合:

frame3
state Nevada Ohio
year
2000 NaN 1.5
2001 2.4 1.7
2002 2.9 3.6
frame3.columns
Index(['Nevada', 'Ohio'], dtype='object', name='state')
'Ohio' in frame3.columns
True
2003 in frame3.index
False

與python的集合不同,pandas的Index可以包含重複的標籤:

dup_labels=pd.Index(pd.Index(['foo', 'foo', 'bar', 'bar']))
dup_labels
Index(['foo', 'foo', 'bar', 'bar'], dtype='object')

選擇重複的標籤,會顯示所有的結果。

每個索引都有一些方法和屬性,它們可用於設定邏輯並回答有關該索引所包含的資料的常見問題。表5-2列出了這些函式。

表5-2:ndex的方法和屬性
方法 說明
append 連線另一個index物件,產生一個新的index
difference 計算差集,並得到一個index
intersection 計算交集
union 計算並集
isin 計算一個指示各值是否都包含在引數集合中的布林型陣列
delete 刪除索引處的元素,並得到新的 Index
drop 刪除傳入的值,並得到新的ndex
Insert 將元素插入到索引處,並得到新的index
is_monotonic 當各元素均大於等於前一個元素時,返回True
is_unique 當index沒有重複值時,返回True
unique 計算index中唯一值的陣列

5.2 基本功能

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

重新索引

pandas物件的一個重要方法是reindex,其作用是建立一個新物件,它的資料符合新的索引。看下面的例子:

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

用該Series的reindex將會根據新索引進行重排。如果某個索引值當前不存在,就引入缺失值;對原資料不產生影響,因為生成的是一個新的物件:

obj2=obj.reindex(['a', 'b', 'c', 'd', 'e'])
obj2
a   -5.3
b    7.2
c    3.6
d    4.5
e    NaN
dtype: float64

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

obj3=pd.Series(['blue', 'purple', 'yellow'],index=[0, 2, 4])
obj3
0      blue
2    purple
4    yellow
dtype: object
obj3.reindex(range(6),method='ffill')
0      blue
1      blue
2    purple
3    purple
4    yellow
5    yellow
dtype: object

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

frame=pd.DataFrame(np.arange(9).reshape(3,3),index=['a', 'c', 'd'],columns=['Ohio', 'Texas', 'California'])
frame
Ohio Texas California
a 0 1 2
c 3 4 5
d 6 7 8
frame2=frame.reindex(['a', 'b', 'c', 'd'])
frame2
Ohio Texas California