1. 程式人生 > >學機器學習,不會資料處理怎麼行?—— 二、Pandas詳解

學機器學習,不會資料處理怎麼行?—— 二、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還有很多內容如資料透視表、多層索引的使用、資料清洗等,將在後面學到時再做補充