Pandas-高級操作知識點總結

本文的Pandas知識點包括:
1、合并數據集
2、重塑和軸向旋轉
3、數據轉換
4、數據聚合

1、合并數據集

Pandas中合并數據集有多種方式,這里我們來逐一介紹

1.1 數據庫風格合并

數據庫風格的合并指根據索引或某一列的值是否相等進行合并的方式,在pandas中,這種合并使用merge以及join函數實現。
先來看下面的例子:

df1 = pd.DataFrame({'key':['b','b','a','c','a','a','b'],'data1':range(7)})
df2 = pd.DataFrame({'key':['a','b','d'],'data2':range(3)})
pd.merge(df1,df2)

如果merge函數只指定了兩個DataFrame,它會自動搜索兩個DataFrame中相同的列索引,即key,當然,這可以進行指定,下面的語句和上面是等價的:

pd.merge(df1,df2,on='key')

當兩個DataFrame沒有相同的列索引時,我們可以指定鏈接的列:

#如果兩個DataFrame的列名不同,可以分別指定
df3 = pd.DataFrame({'lkey':['b','b','a','c','a','a','b'],'data1':range(7)})
df4 = pd.DataFrame({'rkey':['a','b','d'],'data2':range(3)})
pd.merge(df3,df4,left_on='lkey',right_on='rkey')

同時我們可以看到,merge默認情況下采用的是內鏈接方式,當然我們可以通過how參數指定鏈接方式:

pd.merge(df1,df2,how='outer')

要根據多個鍵進行合并,傳入一組由列名組成的列表即可:

left = pd.DataFrame({'key1':['foo','foo','bar'],'key2':['one','two','one'],'lval':[1,2,3]})
right = pd.DataFrame({'key1':['foo','foo','bar','bar'],'key2':['one','one','one','two'],'rval':[4,5,6,7]})
pd.merge(left,right,on=['key1','key2'],how='outer')

上面兩個表有兩列重復的列,如果只根據一列進行合并,則會多出一列重復列,重復列名的處理我們一般使用merge的suffixes屬性,可以幫我們指定重復列合并后的列名:

pd.merge(left,right,on='key1',suffixes=('_left','_right'))

上面的on、left_on、right_on都是根據列值進行合并的,如果我們想用索引進行合并,使用left_index 或者 right_index屬性:

left1 = pd.DataFrame({'key':['a','b','a','a','b','c'],'value':range(6)})
right1 = pd.DataFrame({'group_val':[3.5,7]},index=['a','b'])
pd.merge(left1,right1,left_on='key',right_index=True)

對于層次化索引的數據,我們必須以列表的形式指明用作合并鍵的多個列:

lefth = pd.DataFrame({'key1':['Ohio','Ohio','Ohio','Nevada','Nevada'],
                     'key2':[2000,2001,2002,2001,2002],
                     'data':np.arange(5.0)})
righth = pd.DataFrame(np.arange(12).reshape((6,2)),
                      index=[['Nevada','Nevada','Ohio','Ohio','Ohio','Ohio'],[2001,2000,2000,2000,2001,2002]],
                     columns=['event1','event2'])
pd.merge(lefth,righth,left_on=['key1','key2'],right_index=True)

如果單純想根據索引進行合并,使用join方法會更加簡單:

left2 = pd.DataFrame([[1.0,2.0],[3.0,4.0],[5.0,6.0]],index = ['a','c','e'],columns=['Ohio','Nevada'])
right2 = pd.DataFrame([[7.0,8.0],[9.0,10.0],[11.0,12.0],[13.0,14.0]],index = ['b','c','d','e'],columns=['Missouri','Alabama'])
left2.join(right2,how='outer')

1.2 軸向鏈接

pandas的軸向鏈接指的是根據某一個軸向來拼接數據,類似于列表的合并。concat函數,默認在軸0上工作,我們先來看一個Series的例子:

s1 = pd.Series([0,1],index=['a','b'])
s2 = pd.Series([2,3,4],index=['c','d','e'])
s3 = pd.Series([5,6],index=['f','g'])
pd.concat([s1,s2,s3])

#輸出
a    0
b    1
c    2
d    3
e    4
f    5
g    6
dtype: int64

pd.concat([s1,s2,s3],axis=1)

#輸出
    0   1   2
a   0.0 NaN NaN
b   1.0 NaN NaN
c   NaN 2.0 NaN
d   NaN 3.0 NaN
e   NaN 4.0 NaN
f   NaN NaN 5.0
g   NaN NaN 6.0

在上面的情況下,參與連接的片段在結果中區分不開,假設你想要在連接軸上創建一個層次化索引,我們可以額使用keys參數:

result = pd.concat([s1,s1,s3],keys=['one','two','three'])
result

#輸出
one    a    0
      b    1
two    a    0
      b    1
three  f    5
      g    6
dtype: int64

如果是沿著axis=1進行軸向合并,keys會變為列索引:

pd.concat([s1,s1,s3],keys=['one','two','three'],axis=1)

上面的邏輯同樣適用于DataFrame的軸向合并:

df1 = pd.DataFrame(np.arange(6).reshape((3,2)),index=['a','b','c'],columns=['one','two'])
df2 = pd.DataFrame(5 + np.arange(4).reshape((2,2)),index=['a','c'],columns=['three','four'])

pd.concat([df1,df2],axis=1,keys=['level1','level2'])

#下面的操作會得到與上面同樣的效果
pd.concat({"level1":df1,'level2':df2},axis=1)

使用ignore_index參數可以不保留軸上的索引,產生一組新的索引:

df1 = pd.DataFrame(np.arange(6).reshape((3,2)),index=[1,2,3],columns=['one','two'])
df2 = pd.DataFrame(5 + np.arange(4).reshape((2,2)),index=[1,2],columns=['three','four'])
pd.concat([df1,df2],ignore_index = True)

2、重塑和軸向旋轉

在重塑和軸向旋轉中,有兩個重要的函數,二者互為逆操作:
stack:將數據的列旋轉為行
unstack:將數據的行旋轉為列
先來看下面的例子:

data = pd.DataFrame(np.arange(6).reshape((2,3)),index=pd.Index(['Ohio','Colorado'],name='state'),
                    columns=pd.Index(['one','two','three'],name='number'))
result = data.stack()
result

我們使用unstack()將數據的列旋轉為行,默認是最里層的行索引:

result.unstack()

默認unstack是將最里層的行索引旋轉為列索引,不過我們可以指定unstack的層級,unstack之后作為旋轉軸的級別將會成為結果中的最低級別,當然,我們也可以根據名字指定要旋轉的索引,下面兩句代碼是等價的:

result.unstack(0)
result.unstack('state')

如果不是所有的級別都能在分組中找到的話,unstack操作可能會產生缺失數據:

s1 = pd.Series([0,1,2,3],index=['a','b','c','d'])
s2 = pd.Series([4,5,6],index=['c','d','e'])
data2 = pd.concat([s1,s2],keys=['one','two'])
data2.unstack()

stack操作默認會過濾掉缺失值,不過可以使用dropna參數選擇不過濾缺失值:

data2.unstack().stack()
#輸出
one  a    0.0
     b    1.0
     c    2.0
     d    3.0
two  c    4.0
     d    5.0
     e    6.0
dtype: float64

data2.unstack().stack(dropna=False)
#輸出
one  a    0.0
     b    1.0
     c    2.0
     d    3.0
     e    NaN
two  a    NaN
     b    NaN
     c    4.0
     d    5.0
     e    6.0
dtype: float64

3、數據轉換

3.1 移除重復數據

移除重復數據,使用drop_duplicates方法,該方法默認判斷全部列,不過我們也可以根據指定列進行去重.

data = pd.DataFrame({'k1':['one']*3 + ['two'] * 4,'k2':[1,1,2,3,3,4,4]})
data.drop_duplicates()
#輸出
<bound method DataFrame.drop_duplicates of     k1  k2
0  one   1
1  one   1
2  one   2
3  two   3
4  two   3
5  two   4
6  two   4>

data.drop_duplicates(['k2'])
#輸出
    k1  k2
0   one 1
2   one 2
3   two 3
5   two 4

默認對于重復數據,系統會保留第一項,即keep參數的默認值為first,不過我們也可以保留最后一項,只需將keep參數設置為last即可:

data.drop_duplicates(['k2'],keep='last')
#輸出
    k1  k2
1   one 1
2   one 2
4   two 3
6   two 4

3.2 map函數

在對數據集進行轉換時,你可能希望根據數組、Series或者DataFrame列中的值來實現該轉換工作,我們來看看下面的肉類數據的處理:

data = pd.DataFrame({'food':['bacon','pulled pork','bacon',
                             'Pastrami','corned beef','Bacon','pastrami','honey ham','nova lox'],
                    'ounces':[4,3,12,6,7.5,8,3,5,6]})
meat_to_animal = {
    'bacon':'pig',
    'pulled pork':'pig',
    'pastrami':'cow',
    'corned beef':'cow',
    'honey ham':'pig',
    'nova lox':'salmon'
}
#Series的map方法接受一個函數或含有映射關系的字典對象,對元素進行相應的轉換
data['animal']=data['food'].map(str.lower).map(meat_to_animal)
data

使用下面的代碼是等價的:

data['animal'] = data['food'].map(lambda x: meat_to_animal[x.lower()])

3.3 replace值替換

使用replace方法進行值替換,返回一個新的對象。如果希望對不同的值進行不同的替換,傳入一個由替換關系組成的列表或者字典即可:

data = pd.Series([1,-999,2,-999,-1000,3])
data.replace(-999,np.nan)
#輸出
0       1
1    -999
2       2
3    -999
4   -1000
5       3
dtype: int64

data.replace([-999,-1000],[np.nan,0])
#輸出
0    1.0
1    NaN
2    2.0
3    NaN
4    0.0
5    3.0
dtype: float64

3.4 離散化和面元劃分

根據區間對數據進行劃分,使用cut函數,比如我們想根據年齡區間對人群進行劃分,從而得到不同年齡段的人數統計:

ages = [20,22,25,27,21,23,37,31,61,45,41,32]
bins = [18,25,35,60,100]
cats = pd.cut(ages,bins)
cats
#輸出
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]

cut函數返回一個特殊的Categorical對象,可以通過codes來查看個數據的組別編號:

cats.codes
#輸出
array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)

使用value_counts可以實現分組計數:

pd.value_counts(cats)
#輸出
(18, 25]     5
(35, 60]     3
(25, 35]     3
(60, 100]    1
dtype: int64

上面是前開后閉區間,如果想要變為前閉后開區間,只需要設置right=False參數:

cats = pd.cut(ages,bins,right=False)
pd.value_counts(cats)
#輸出
[25, 35)     4
[18, 25)     4
[35, 60)     3
[60, 100)    1
dtype: int64

也可以設置自己的面元名稱,將labels選項設為一個列表或者數組即可:

group_names = ['Youth','YoungAdult','MiddleAged','Senior']
pd.cut(ages,bins,labels=group_names)
#輸出
[Youth, Youth, Youth, YoungAdult, Youth, ..., YoungAdult, Senior, MiddleAged, MiddleAged, YoungAdult]
Length: 12
Categories (4, object): [MiddleAged < Senior < YoungAdult < Youth]

如果向cut傳入的不是面元邊界而是面元的數量,則會根據數據的最大值和最小值自動計算等長面元,比如下面的例子將均勻分布的數據分為四組:

data = np.random.rand(20)
pd.cut(data,4,precision=2)

pandas還提供了一個對數據進行劃分的函數:qcut。qcut基于樣本分位數對數據進行面元劃分,可以自定義分位數,也可以傳入一個數量(會自動計算分位數):

data = np.random.randn(1000)
cats = pd.qcut(data,4)
pd.value_counts(cats)

#輸出
(0.701, 3.451]     250
(0.0727, 0.701]    250
(-0.57, 0.0727]    250
(-3.84, -0.57]     250
dtype: int64

pd.value_counts(pd.qcut(data,[0,0.1,0.5,0.9,1]))
#輸出
(0.0727, 1.338]     400
(-1.247, 0.0727]    400
(1.338, 3.451]      100
(-3.84, -1.247]     100
dtype: int64

3.5 排列和隨機采樣

利用numpy.random.permutation函數可以輕松實現對Series或者DataFrame的列的排列工作,通過需要排列的軸的長度調用permutation,可產生一個表示新順序的整數數組,最后使用pandas的take函數返回指定大小的數據即可實現采樣。

df = pd.DataFrame(np.arange(5*4).reshape(5,4))
sampler = np.random.permutation(5)
df.take(sampler[:3])

4、數據聚合

4.1 數據分組

pandas中的數據分組使用groupby方法,返回的是一個GroupBy對象,對分組之后的數據,我們可以使用一些聚合函數進行聚合,比如求平均值mean:

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)
})
groupd = df['data1'].groupby(df['key1'])
groupd
#<pandas.core.groupby.SeriesGroupBy object at 0x118814dd8>
groupd.mean()

#輸出
key1
a    0.697500
b   -0.068161
Name: data1, dtype: float64

上面是進行分組的一個簡單的例子,我們可以根據多種數據格式進行數據分組,下面就一一整理一下:

Series

means = df['data1'].groupby([df['key1'],df['key2']]).mean()
means
#輸出
key1  key2
a     one     0.543936
      two     1.004630
b     one     0.219453
      two    -0.355776
Name: data1, dtype: float64

合適長度的數組
分組鍵可以是任何適當長度的數組,數組中每一個元素的值代表相應下標的記錄的分組鍵:

states = np.array(['Ohio','Nevada','Nevada','Ohio','Ohio'])
years = np.array([2005,2005,2006,2005,2006])
df['data1'].groupby([states,years]).mean()

列名

df.groupby('key1').mean()
df.groupby(['key1','key2']).mean()

你可能已經注意到了,在執行df.groupby('key1').mean()的結果中,結果并沒有key2這一列,這是因為key2這一列不是數值數據,所以從結果中排除了,默認情況下,所有的數值列都會被聚合,雖然有時可能會被過濾為一個子集。

map

#我們還可以根據map來進行分組
people = pd.DataFrame(np.random.randn(5,5),columns=['a','b','c','d','e'],index=['Joe','Steve','Wes','Jim','Travis'])
mapping = {'a':'red','b':'red','c':'blue','d':'blue','e':'red','f':'orange'}
people.groupby(mapping,axis=1).sum()

Python函數
假如你想根據人名的長度進行分組,雖然可以求取一個字符串長度數組,其實僅僅傳入len函數就可以了:

people.groupby(len).sum()

索引級別

columns = pd.MultiIndex.from_arrays([['US','US','US','JP','JP'],[1,3,5,1,3]],names=['city','tenor'])
hier_df = pd.DataFrame(np.random.randn(4,5),columns=columns)
hier_df.groupby(level='city',axis=1).count()

分組之后產生一個GroupBy對象,這個對象支持迭代,是一個由(分組名,數據塊)組成的二元組:

for name,group in df.groupby('key1'):
    print(name)
    print(group)

groupby默認是在axis=0上分組的,不過我們也可以在axis=1上分組,比如根據列的數據類型進行分組:

for name,group in df.groupby(df.dtypes,axis=1):
    print(name)
    print(group)

4.2 數據聚合操作

特定聚合函數
我們可以像之前一樣使用一些特定的聚合函數,比如sum,mean等等,但是同時也可以使用自定義的聚合函數,只需將其傳入agg方法中即可:

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)
})
def peak_to_peak(arr):
    return arr.max()-arr.min()
grouped = df.groupby('key1')
grouped.agg(peak_to_peak)


關于agg還有更多的功能,我們使用小費數據(下載地址:http://pan.baidu.com/s/1bpGW3Av 密碼:2p9v),我們讀入數據,并計算小費率一列:

tips = pd.read_csv('data/ch08/tips.csv')
tips['tip_pct'] = tips['tip'] / tips['total_bill']
tips.head(10)

可以同時使用多個聚合函數,此時得到的DataFrame的列就會以相應的函數命名:

grouped = tips.groupby(['sex','smoker'])
grouped_pct = grouped['tip_pct']
grouped_pct.agg(['mean','std',peak_to_peak])

你如果不想接受這些自動給出的列名,你可以用(name,function)的方法指定你的列名:

grouped_pct.agg([('foo','mean'),('bar',np.std)])

假如你想要對不同的列應用不同的函數,具體的辦法是向agg傳入一個從列名映射到函數的字典:

grouped.agg({'tip':[np.max,'min'],'size':'sum'})

如果你想以無索引的方式返回聚合數據,可是設置as_index=False:

tips.groupby(['sex','smoker'],as_index=False).mean()

transform函數
transform會將一個函數運用到各個分組,然后將結果放置到適當的位置上。如果個分組產生的是一個標量值,則該值將會被廣播出去,如果分組產生的是一個相同大小的數組,則會根據下標放置到適當的位置上。

people = pd.DataFrame(np.random.randn(5,5),columns=['a','b','c','d','e'],index=['Joe','Steve','Wes','Jim','Travis'])
key = ['one','two','one','two','one']
people.groupby(key).transform(np.mean)

可以看到,在上面的例子中,分組產生了一個標量,即分組的平均值,然后transform將這個值映射到對應的位置上,現在DataFrame中每個位置上的數據都是對應組別的平均值。假設我們希望從各組中減去平均值,可以用下面的方法實現:

def demean(arr):
    return arr - arr.mean()
demeaned = people.groupby(key).transform(demean)
demeaned

apply函數
同agg一樣,transform也是有嚴格條件的函數,傳入的函數只能產生兩種結果:要么產生一個可以廣播的標量值,如np.mean,要么產生一個相同大小的結果數組.最一般化的GroupBy方法是apply,apply將會待處理的對象拆分成多個片段,然后對各片段調用傳入的函數,最后嘗試將各片段組合到一起.

def top(df,n=5,column='tip_pct'):
    return df.sort_values(by=column)[-n:]
tips.groupby('smoker').apply(top)

如果傳入apply的方法里有可變參數的話,我們可以自定義這些參數的值:

tips.groupby(['smoker','day']).apply(top,n=1,column='total_bill')

從上面的例子可以看出,分組鍵會跟原始對象的索引共同構成結果對象中的層次化索引。將group_keys=False傳入groupby即可禁止該效果:

tips.groupby(['smoker'],group_keys=False).apply(top)

4.3 數據透視表

透視表是各種電子表格程序和其他數據分析軟件中一種常見的數據匯總工具,它根據一個或多個鍵對數據進行聚合,并根據行和列傷的分組鍵將數據分配到各個矩形區域中。
考慮我們的小費數據集,我們想聚合tip_pct和size,想根據day進行分組,將smoker放到列上,將day放到行上:

tips.pivot_table(['tip_pct','size'],index=['sex','day'],columns='smoker')

如果想增加匯總統計列,可以增加margins=True參數:

tips.pivot_table(['tip_pct','size'],index=['sex','day'],columns='smoker',margins=True)

如果想使用其他聚合函數,將其傳入aggfunc即可,例如使用count或len可以得到有關分組大小的交叉表:

tips.pivot_table('tip_pct',index=['sex','smoker'],columns='day',aggfunc=len,margins=True)

可以使用fill_value填充缺失值:

tips.pivot_table('size',index=['time','sex','smoker'],columns='day',aggfunc=sum,fill_value=0)
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容