學機器學習,不會資料處理怎麼行?—— 二、Pandas詳解
在上篇文章學機器學習,不會資料處理怎麼行?—— 一、NumPy詳解中,介紹了NumPy的一些基本內容,以及使用方法,在這篇文章中,將接著介紹另一模組——Pandas。(本文所用程式碼在這裡)
Pandas資料結構介紹
大家應該都聽過表結構,但是,如果讓你自己來實現這麼一個結構,並且能對其進行資料處理,能實現嗎?我相信,大部分人都能做出來,但是不一定能做的很好。而Python中的一個模組pandas給我們提供了一個很好的資料結構,它包括了序列Series和資料框DataFrame。pandas是基於NumPy陣列構建的,特別是基於陣列的函式和不使用for迴圈的資料處理,讓以Numpy為中心的應用變得更加簡單。
Series
建立方式
Series是一種類似於一維陣列的物件,它由一組資料(各種NumPy資料型別)以及一組與之相關的資料標籤(即索引)組成,其建立主要有三種方式
1)通過一維陣列建立序列
import numpy as np import pandas as pd arr1 = np.arange(10) s1 = pd.Series(arr1)
通過type函式可檢視arr1與s1的型別分別為,numpy.ndarray 和 pandas.core.series.Series。
3)通過字典的方式建立
dic1 = {'a': 1,'b': 2,'c': 3,'d': 4,'e': 50} s2 = pd.Series(dic1)
通過type函式可檢視dic1與s2的型別分別為,dict 和 pandas.core.series.Series。
3)通過DataFrame中的某一行或者某一列建立序列
這一步將在下面講DataFrame的時候再補充
Series索引
我們上面在建立Series序列時,可以輸出檢視我們建立的Series長什麼樣,發現有兩列,第二列的資料跟我們輸入的是一樣的,那第一列是什麼呢?那就是索引。Series的字串表現形式為:索引在左邊,值在右邊。如果我們沒有為資料指定索引,就會自動建立一個0到N-1(N為資料的長度)的整數型索引。
我們可以通過 Series 的 values 和 index 屬性獲取其陣列表示形式和索引物件。
obj = pd.Series([6, 9, -8, 1]) obj.values obj.index
我們也能修改其索引
obj.index = ['a','b','c','d']
講了那麼多,知道這是索引了,但它有什麼用呢?
1.我們可以通過索引值或索引標籤進行資料的獲取
obj['a'] obj[3] obj[[0,2,3]] obj[0:2] obj['a':'c']
注:若通過索引標籤獲取資料的話,末端標籤對應的值也將返回!!!
2.自動化對齊
如果有兩個序列,需要對這兩個序列進行算術運算,這時索引的存在就體現的它的價值了—自動化對齊.
obj1 = pd.Series(np.array([10,15,20,30,55,80]),index = ['a','b','c','d','e','f']) obj2 = pd.Series(np.array([12,11,13,15,14,16]),index = ['a','c','g','b','d','f']) obj1+obj2 obj1*obj2
我們會發現,計算後的序列號中,g和e對應的值為NaN,這是因為obj1中沒有g索引,obj2中沒有e索引,所以資料的運算會產生兩個缺失值NaN。
DataFrame
DataFrame是一個表格型的資料結構,它含有一組有序的列,每列可以是不同的值型別(數值、字串、布林值等)。DataFrame既有行索引也有列索引,它可以被看做由Series組成的字典。
建立方式
1)通過二維陣列建立
arr2 = np.array(np.arange(12).reshape(4,3))
df1 = pd.DataFrame(arr2)
2)通過字典的方式建立
該方式可通過字典列表和巢狀字典實現,如果將巢狀字典傳給DataFrame,pandas就會被解釋為:外層字典的鍵作為列,內層鍵則作為行索引
#字典列表 dic2 = {'a': [1, 2, 3, 4],'b': [5, 6, 7, 8],'c': [9, 10, 11, 12],'d': [13, 14, 15, 16]} df2 = pd.DataFrame(dic2) #巢狀字典 dic3 = {'one': {'a': 1,'b': 2,'c': 3,'d':4}, 'two': {'a': 5,'b': 6,'c': 7,'d': 8}, 'three':{'a': 9,'b': 10,'c': 11,'d':12}} df3 = pd.DataFrame(dic3)
3)通過資料框的方式建立
df4 = df3[['one','three']] s3 = df4['one']
DataFrame索引
DataFrame的索引與Series的索引大體上是一樣的,不過DataFrame有兩個索引,分別是列索引和行索引,感覺看起來就跟excel差不多了。具體的實現方式可通過下面的部分來了解。
利用pandas進行資料處理
pandas可以通過布林索引有針對的選取原資料的子集、指定行、指定列等,同時我們可以通過 pd.read_csv()來匯入資料集,因為暫時找不到資料集,就從別人的程式碼裡複製一些過來了
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)
選擇資料
簡單篩選
我們可以使用以下函式來查詢資料的前幾行或後幾行(預設5行)
frame.head()
frame.tail()
也可以使用行列索引來簡單地查詢指定的行和列
frame['state'] frame[0:3]
loc
當然,上面的方式只是比較簡單的操作,複雜些的,我們可以通過 loc 來選取資料(: 表示所有行)
frame.loc[:,['state','year']]
iloc
另外,我們也可以使用iloc,通過位置選擇在不同情況下所需要的資料,可進行選取某個資料、連續或者跨行選擇等操作
frame.iloc[0:2,0:2]
ix
除此之外還有一種操作方式,使用 混合選擇 ix ,我這裡的表可能無法很好的體現,如果將行索引改為字元就可以看出來了
frame.ix[3:5,['year']]
發現輸入這行程式碼執行之後發出一條警告
DeprecationWarning: .ix is deprecated. Please use .loc for label based indexing or .iloc for positional indexing
emmmmm。。想想以後主要還是用 loc 和 iloc 好了。
通過判斷的篩選
除了上面講的之外,我們還可以通過判斷指令進行選擇,即通過約束某項條件然後選擇出當前所有資料。
frame[frame['year']>2001]
資料修改
講完了資料選擇,既然選擇出了資料,我們可能會想去修改資料,那麼,怎麼修改呢?
根據位置
我們可以利用索引或者標籤確定需要修改的位置
frame.iloc[0,2]=1.6 frame.loc[1,'pop']=2
根據條件
如果我們想將資料中'pop'欄小於1.7的'year'改為1999,那麼我們可以進行如下操作
frame.year[frame['pop']<1.7]=1999
按行或按列設定
我們還可以對行列進行批處理
frame['pop']=2.6
新增和刪除資料
上面講的都是資料的修改,再來講講資料的新增和刪除
#資料新增 #方法一 frame['A'] = np.nan #方法二 frame['B'] = pd.Series([1, 2, 3, 4, 5, 6]) #資料刪除 frame.drop('B',axis = 1)
注:
- 對於方法二,長度必須對齊,如果行索不為預設則需指定索引
dates = pd.date_range('20130101', periods=6) df = pd.DataFrame(np.arange(24).reshape((6,4)),index=dates, columns=['A','B','C','D']) df['E'] = pd.Series([1, 2, 3, 4, 5, 6],index=pd.date_range('20130101',periods=6))
- 對於資料刪除,axis取值 0表示刪除行索引 1表示刪除列索引 預設為0
統計分析
pandas模組為我們提供了非常多的描述性統計分析的指標函式,如總和、均值、最小值、最大值等,跟對應的NumPy陣列方法相比,它們都是基於沒有缺失資料的假設而構建的,下圖中列舉了大部分函式
這裡有幾點需要注意
- descirbe函式只能針對序列或者資料框,一維陣列是沒有這個方法的。
- 使用sum時,NaN值會自動被排除,除非整個切片都是NaN,通過skipna選項可以禁用該功能。
- 呼叫sum時,返回的時含有列的和的Series,需要傳入axis的值才會進行行運算
當我們要對一列資料進行分析時,我們可以將要分析的內容封裝成一個函式,方便呼叫
def stats(x): return pd.Series([x.count(),x.min(),x.idxmin(), x.quantile(.25),x.median(), x.quantile(.75),x.mean(), x.max(),x.idxmax(), x.mad(),x.var(), x.std(),x.skew(),x.kurt()], index = ['Count','Min','Whicn_Min', 'Q1','Median','Q3','Mean', 'Max','Which_Max','Mad', 'Var','Std','Skew','Kurt'])
有時或許會需要對一個數據框進行分析,那該如何操作?是一列一列不斷地取出來然後呼叫函式嗎?這個方法也不是不可行,但面對眾多資料時該怎麼辦?一個一個呼叫怕不是要弄到明年,在這裡我們可以使用apply函式
d1 = pd.Series(2*np.random.normal(size = 100)+3) d2 = np.random.f(2,4,size = 100) d3 = np.random.randint(1,100,size = 100) df = pd.DataFrame(np.array([d1,d2,d3]).T,columns=['x1','x2','x3']) df.head() df.apply(stats)
就這樣很簡單的建立了數值型資料的統計性描述。但如果是離散型資料呢?就不能用這個統計口徑了,我們需要統計離散變數的觀測數、唯一值個數、眾數水平及個數。你只需要使用describe方法就可以實現這樣的統計了。
缺失值處理
現實生活中的資料是非常雜亂的,其中缺失值也是非常常見的,對於缺失值的存在可能會影響到後期的資料分析或挖掘工作,那麼我們該如何處理這些缺失值呢?常用的有三大類方法,即刪除法、填補法和插值法。
刪除法:當資料中的某個變數大部分值都是缺失值,可以考慮刪除改變數;當缺失值是隨機分佈的,且缺失的數量並不是很多是,也可以刪除這些缺失的觀測。
替補法:對於連續型變數,如果變數的分佈近似或就是正態分佈的話,可以用均值替代那些缺失值;如果變數是有偏的,可以使用中位數來代替那些缺失值;對於離散型變數,我們一般用眾數去替換那些存在缺失的觀測。
插補法:插補法是基於蒙特卡洛模擬法,結合線性模型、廣義線性模型、決策樹等方法計算出來的預測值替換缺失值。
刪除法
這裡僅講述刪除法和替補法,先來看刪除法,先建立一個6X4的矩陣資料並且把兩個位置置為空
dates = pd.date_range('20130101', periods=6) df = pd.DataFrame(np.arange(24).reshape((6,4)),index=dates, columns=['A','B','C','D']) df.iloc[0,1] = np.nan df.iloc[1,2] = np.nan
我們可以結合sum函式和isnull函式來檢測資料中含有多少缺失值
for i in df.columns: print(sum(pd.isnull(df[i])))
也可以通過any來判斷是否存在缺失值
np.any(df.isnull()) == True
刪除直接dropna就行了
df.dropna( axis=0, # 0: 對行進行操作; 1: 對列進行操作 預設為0 how='any' # 'any': 只要存在 NaN 就 drop 掉; 'all': 必須全部是 NaN 才 drop 預設為'any' )
填補法
簡單粗暴的辦法就是把所有的NaN用0代替
df.fillna(value=0)
稍微好一點的方法,使用常量填充不同的列
df.fillna({'B': 3,'C': 4})
還算好的方法,採用前項填充或後向填充
df.fillna(method = 'ffill') #用前一個觀測值填充 df.fillna(method = 'bfill') #用後一個觀測值填充
較好的方法,用均值或中位數填充各自的列
df.fillna(df.median())
df.fillna(df.mean())
很顯然,在使用填充法時,相對於常數填充或前項、後項填充,使用各列的眾數、均值或中位數填充要更加合理一點,這也是工作中常用的一個快捷手段。
注:使用fillna,dropna時,需要新增引數 inplace = True,如df.fillna(df.median(),inplace = True),以確認修改,否則實際的資料並不會有改動。
資料合併
concat
pandas 處理多組資料的時候往往會要用到資料的合併處理,使用 concat 是一種基本的合併方式.而且concat中有很多引數可以調整,合併成你想要的資料形式.
先建立資料集
df1 = pd.DataFrame(np.ones((3,4))*0, columns=['a','b','c','d']) df2 = pd.DataFrame(np.ones((3,4))*1, columns=['a','b','c','d']) df3 = pd.DataFrame(np.ones((3,4))*2, columns=['a','b','c','d'])
concat合併
pd.concat([df1, df2, df3], axis=0) #縱向合併 pd.concat([df1, df2, df3], axis=1) #橫向合併
這裡觀察下縱向合併的輸出,會發現index的值為0,1,2,0,1,2,0,1,2,大部分情況下,這肯定不是我們想要的,我們可以通過一下方法來重置index
pd.concat([df1, df2, df3], axis=0, ignore_index=True)
pandas中的資料結構是支援類似SQL的部分操作方式的,如新增新行新列、多表連線、聚合什麼的。pandas有個join引數,用來定義連線方式的,預設為outer,此方式是依照column來做縱向合併有相同的column上下合併在一起,其他的獨自成列,沒有的值用NaN填充
df1 = pd.DataFrame(np.ones((3,4))*0, columns=['a','b','c','d'], index=[1,2,3]) df2 = pd.DataFrame(np.ones((3,4))*1, columns=['b','c','d','e'], index=[2,3,4]) res = pd.concat([df1, df2], axis=0, join='outer')#縱向"外"合併df1與df2
引數:
- axis 表示行或列,0為行,1為列
- join 'inner':內連線,會產生NaN的列丟棄 ‘outer’:外連線,上面介紹過了
另外,還有join_axes也可以決定連線方式
pd.concat([df1, df2], axis=1, join_axes=[df1.index])
pd.concat([df1, df2], axis=1, join_axes=[df2.index])
執行上面兩行程式碼,就會發現有所不同,第一行是以df1的index為依據,將df2中具有相同索引的行對應拼接上去,第二行同樣的道理。
此外,還有append操作,該操作類似與axis=0,join='outer'時的操作,不過要注意的是append只有縱向合併。
merge
pandas中的合併操作除了concat之外,還有merge操作,主要用於兩組有key column的資料,統一索引的資料. 通常也被用在Database的處理當中.
看個簡單的,依據一組Key合併
left = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'], 'A': ['A0', 'A1', 'A2', 'A3'], 'B': ['B0', 'B1', 'B2', 'B3']}) right = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3'], 'C': ['C0', 'C1', 'C2', 'C3'], 'D': ['D0', 'D1', 'D2', 'D3']}) pd.merge(left, right, on='key')
稍微難一點就是依據兩組Key合併,合併時有四種方法 how=['left','right','outer','inner'],預設為how='inner'
left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'], 'key2': ['K0', 'K1', 'K0', 'K1'], 'A': ['A0', 'A1', 'A2', 'A3'], 'B': ['B0', 'B1', 'B2', 'B3']}) right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'], 'key2': ['K0', 'K0', 'K0', 'K0'], 'C': ['C0', 'C1', 'C2', 'C3'], 'D': ['D0', 'D1', 'D2', 'D3']}) res = pd.merge(left, right, on=['key1', 'key2'], how='inner') print(res) res = pd.merge(left, right, on=['key1', 'key2'], how='outer') print(res) res = pd.merge(left, right, on=['key1', 'key2'], how='left') print(res) res = pd.merge(left, right, on=['key1', 'key2'], how='right') print(res)
都試一試然後輸出就知道是如何操作的了。
上面講的是以列索引為標準進行合併,當然,我們還可以以index為標準進行合併
left = pd.DataFrame({'A': ['A0', 'A1', 'A2'], 'B': ['B0', 'B1', 'B2']}, index=['K0', 'K1', 'K2']) right = pd.DataFrame({'C': ['C0', 'C2', 'C3'], 'D': ['D0', 'D2', 'D3']}, index=['K0', 'K2', 'K3']) res = pd.merge(left, right, left_index=True, right_index=True, how='outer') print(res) res = pd.merge(left, right, left_index=True, right_index=True, how='inner') print(res)
聚合和分組
pandas提供了一個靈活高效的groupby功能,它使你能以一種自然的方式對資料集進行切片、切塊、摘要等操作。根據一個或多個鍵(可以是函式、陣列或DataFrame列名)拆分pandas物件。計算分組摘要統計,如計數、平均值、標準差,或使用者自定義函式。
根據列索引分組
df = pd.DataFrame({'key1':['a', 'a', 'b', 'b', 'a'], 'key2':['one', 'two', 'one', 'two', 'one'], 'data1':np.random.randn(5), 'data2':np.random.randn(5)}) df['data1'].groupby(df['key1']).mean()
這裡我們按照key1將資料分組,然後計算分組後的data1的平均值。
我們也可以一次傳入多個數組
df['data1'].groupby([df['key1'], df['key2']]).mean()
此外,還可以將列名用作分組
df.groupby('key1').mean() df.groupby(['key1', 'key2']).mean()
注:這裡在執行df.groupby('key1').mean()時,結果中沒有key2列。這是因為df['key2']不是數值資料,所以被從結果中排除了。預設情況下,所有數值列都會被聚合,雖然有時可能會被過濾為一個子集。
分組後可進行迭代
for name, group in df.groupby('key1'): print(name) print(group)
對於多重鍵的情況
for (k1, k2), group in df.groupby(['key1', 'key2']): print (k1, k2) print (group)
上面進行的group操作返回的型別都是Series,如果我們想返回DataFrame型別的結果,那麼我們可以進行如下操作
df[['data2']].groupby([df['key1']])
根據行型別進行分組
groupby預設是在axis=0上進行分組的,通過設定也可以在其他任何軸上進行分組。拿上面例子中的df來說,我們可以根據行型別(dtype)對列進行分組
dict(list(df.groupby(df.dtypes,axis = 1)))
這方面還有蠻多內容,不過都還沒接觸過,只能暫時講到這裡了,如果要了解更多,可以訪問這篇部落格 ,我在後面也會逐漸完善這些方面的內容。
結尾
講了這麼多,相信你對pandas肯定有了一定的瞭解了,但由於個人接觸不多,就暫時介紹到這裡,pandas還有很多內容如資料透視表、多層索引的使用、資料清洗等,將在後面學到時再做補充