在上篇文章學機器學習,不會數據處理怎么行?—— 一、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還有很多內容如數據透視表、多層索引的使用、數據清洗等,將在后面學到時再做補充