Pandas知識點-詳解行列級批處理函數apply
在Pandas中,DataFrame和Series等對象需要執行批量處理操作時,可以借用apply()函數來實作。
apply()的核心功能是實作“批量”排程處理,至于批量做什麼,由使用者傳入的函數決定(自定義或現成的函數)。函數傳遞給apply(),apply()會幫使用者在DataFrame和Series等對象中(按行或按列)批量執行傳入的函數。
先看一個例子:
# coding=utf-8
import pandas as pd
df = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [2, 4, 6], 'Col-3': [9, 8, 7], 'Col-4': [3, 6, 9]},
index=['A', 'B', 'C'])
print(df)
df_new = df.apply(lambda x: x-1)
print('-' * 30, '\n', df_new, sep='')
Col-1 Col-2 Col-3 Col-4
A 1 2 9 3
B 3 4 8 6
C 5 6 7 9
------------------------------
Col-1 Col-2 Col-3 Col-4
A 0 1 8 2
B 2 3 7 5
C 4 5 6 8
從這個例子可以看出,apply的使用非常簡便且優雅,一行代碼就對DataFrame中的所有資料都執行了一遍傳入的匿名函數。
apply用法和參數介紹
apply(self, func, axis=0, raw=False, result_type=None, args=(), **kwds):
func: 應用于每一列或每一行的函數,這個函數可以是Python内置函數、Pandas或其他庫中的函數、自定義函數、匿名函數。
axis: 設定批處理函數按列還是按行應用,0或index表示按列應用函數,1或columns表示按行應用函數,預設值為0。
raw: 設定将列/行作為Series對象傳遞給函數,還是作為ndarray對象傳遞給函數。raw是bool類型,預設為False。
False: 将列/行作為Series對象傳遞給函數。
True: 将列/行作為ndarray對象傳遞給函數。apply中的func函數将接收ndarray對象,如果應用numpy中的函數,這樣可以提升性能。
result_type: 當axis=1時,設定傳回結果的類型和樣式,支援{'expand', 'reduce', 'broadcast', None}四種類型,預設為None。
expand: 清單式的結果将被轉化為列。
reduce: 如果可能的話,傳回一個Series,而不是傳回清單式的結果。這與expand相反。
broadcast: 結果将被廣播成DataFrame的原始形狀,DataFrame的原始索引和列将被保留。
None: 結果取決于應用函數的傳回值,清單式的結果将以Series形式傳回,如果應用函數傳回Series将會擴充到列。
args: 傳給應用函數func的位置參數,args接收的資料類型為元組,如果隻有一個位置參數要注意加逗号。
**kwds: 如果func中有關鍵字參數,可以傳給**kwds。
raw和result_type通常不需要自己設定,保持預設即可。
下面依次介紹apply()函數的各種用法。
傳入不同類型的函數
import numpy as np
df = pd.DataFrame({'Col-1': [1, 3, 5], 'Col-2': [2, 4, 6], 'Col-3': [9, 8, 7], 'Col-4': [3, 6, 9]},
index=['A', 'B', 'C'])
print(df)
df1 = df.apply(max) # python内置函數
print('-' * 30, '\n', df1, sep='')
df2 = df.apply(np.mean) # numpy中的函數
print('-' * 30, '\n', df2, sep='')
df3 = df.apply(pd.DataFrame.min) # pandas中的方法
print('-' * 30, '\n', df3, sep='')
Col-1 Col-2 Col-3 Col-4
A 1 2 9 3
B 3 4 8 6
C 5 6 7 9
------------------------------
Col-1 5
Col-2 6
Col-3 9
Col-4 9
dtype: int64
------------------------------
Col-1 3.0
Col-2 4.0
Col-3 8.0
Col-4 6.0
dtype: float64
------------------------------
Col-1 1
Col-2 2
Col-3 7
Col-4 3
dtype: int64
def make_ok(s):
return pd.Series(['{}ok'.format(d) for d in s])
df4 = df.apply(make_ok) # 自定義函數
print('-' * 30, '\n', df4, sep='')
------------------------------
Col-1 Col-2 Col-3 Col-4
0 1ok 2ok 9ok 3ok
1 3ok 4ok 8ok 6ok
2 5ok 6ok 7ok 9ok
設定按行還是按列
def make_ok(s):
if isinstance(s, pd.Series):
if s.name in df.columns:
return pd.Series(['{}ok-列'.format(d) for d in s])
else:
return pd.Series(['{}ok-行'.format(d) for d in s])
else:
return '{}ok'.format(s)
df5 = df.apply(make_ok, axis=0) # 按列處理
print('-' * 30, '\n', df5, sep='')
df6 = df.apply(make_ok, axis=1) # 按行處理
print('-' * 30, '\n', df6, sep='')
------------------------------
Col-1 Col-2 Col-3 Col-4
0 1ok-列 2ok-列 9ok-列 3ok-列
1 3ok-列 4ok-列 8ok-列 6ok-列
2 5ok-列 6ok-列 7ok-列 9ok-列
------------------------------
0 1 2 3
A 1ok-行 2ok-行 9ok-行 3ok-行
B 3ok-行 4ok-行 8ok-行 6ok-行
C 5ok-行 6ok-行 7ok-行 9ok-行
這裡推演一下按列和按行的過程,當axis參數為0或index時,按列從DataFrame中取資料,如上面的例子先取到第一列Col-1:[1, 3, 5],将第一列傳給函數func,然後取第二列Col-2:[2, 4, 6]…以此類推。當axis參數為1或columns時,按行從DataFrame中取資料,如上面的例子先取到第一行A:[1, 2, 9, 3],将第一行傳遞給函數func執行後取第二行,以此類推。
同時,當按列應用函數時,DataFrame的index變成了預設索引(0開始的自然數),當按行應用函數時,DataFrame的columns變成了預設索引,如果要保持與原DataFrame一樣,則需要重新設定。
函數func的參數
def yes_or_no(s, answer):
if answer != 'yes' and answer != 'no':
answer = 'yes'
if isinstance(s, pd.Series):
return pd.Series(['{}-{}'.format(d, answer) for d in s])
else:
return '{}-{}'.format(s, answer)
df7 = df.apply(yes_or_no, args=('yes',))
df7.index = ['A', 'B', 'C']
print('-' * 30, '\n', df7, sep='')
df8 = df.apply(yes_or_no, args=('no',))
print('-' * 30, '\n', df8, sep='')
df9 = df.apply(yes_or_no, args=(0,))
print('-' * 30, '\n', df9, sep='')
------------------------------
Col-1 Col-2 Col-3 Col-4
A 1-yes 2-yes 9-yes 3-yes
B 3-yes 4-yes 8-yes 6-yes
C 5-yes 6-yes 7-yes 9-yes
------------------------------
Col-1 Col-2 Col-3 Col-4
0 1-no 2-no 9-no 3-no
1 3-no 4-no 8-no 6-no
2 5-no 6-no 7-no 9-no
------------------------------
Col-1 Col-2 Col-3 Col-4
0 1-yes 2-yes 9-yes 3-yes
1 3-yes 4-yes 8-yes 6-yes
2 5-yes 6-yes 7-yes 9-yes
在apply()中,func函數的第一個參數預設會傳入Series對象,這就是前面說的“将列/行作為Series對象傳遞給函數”,是以函數func至少要有一個參數,這個參數相當于類方法中的self,不需要在args中傳值。如果func沒有參數,則不能在apply中使用。
如果func的參數多于一個,則多出來的參數通過args傳遞,args接收一個元組,args裡隻有一個值時,需要加上逗号。如果func中有關鍵字參數,可以傳到apply中**kwds的位置。
傳入多個函數進行聚合
df10 = df.apply([np.max, np.min])
print('-' * 40, '\n', df10, sep='')
df11 = df.apply({'Col-1': np.mean, 'Col-2': np.min})
print('-' * 40, '\n', df11, sep='')
df12 = df.apply({'Col-1': [np.mean, np.median], 'Col-2': [np.min, np.mean]})
print('-' * 40, '\n', df12, sep='')
----------------------------------------
Col-1 Col-2 Col-3 Col-4
amax 5 6 9 9
amin 1 2 7 3
----------------------------------------
Col-1 3.0
Col-2 2.0
dtype: float64
----------------------------------------
Col-1 Col-2
mean 3.0 4.0
median 3.0 NaN
amin NaN 2.0
當在apply中傳入多個函數時,傳回的結果被聚合成一個新的DataFrame或Series,作用類似于pandas中的聚合函數DataFrame.agg()。[後續文章會介紹agg()]
通過函數名字元串調用函數
df13 = df.apply('mean', axis=1)
print('-' * 30, '\n', df13, sep='')
df14 = df.apply(['mean', 'min'], axis=1)
print('-' * 30, '\n', df14, sep='')
------------------------------
A 3.75
B 5.25
C 6.75
dtype: float64
------------------------------
mean min
A 3.75 1.0
B 5.25 3.0
C 6.75 5.0
apply()支援函數名用字元串傳給func,調用函數。
修改DataFrame本身
df15 = df.copy()
# 讀取df的一列,将處理結果添加到原df中,增加一列
df15['Col-x'] = df15['Col-1'].apply(make_ok)
print('-' * 40, '\n', df15, sep='')
# 讀取df的一行,将處理結果添加到原df中,增加一行
df15.loc['Z'] = df15.loc['A'].apply(yes_or_no, args=('yes',))
print('-' * 40, '\n', df15, sep='')
----------------------------------------
Col-1 Col-2 Col-3 Col-4 Col-x
A 1 2 9 3 1ok
B 3 4 8 6 3ok
C 5 6 7 9 5ok
----------------------------------------
Col-1 Col-2 Col-3 Col-4 Col-x
A 1 2 9 3 1ok
B 3 4 8 6 3ok
C 5 6 7 9 5ok
Z 1-yes 2-yes 9-yes 3-yes 1ok-yes
DataFrame增加一列或一行可以直接指派,修改一個DataFrame後将結果指派給本身,這樣相當于修改了原始DataFrame。
Series使用apply
s0 = df['Col-2'].apply(make_ok)
print('-' * 20, '\n', s0, sep='')
s = pd.Series(range(5), index=[alpha for alpha in 'abcde'])
print('-' * 20, '\n', s, sep='')
s1 = s.apply(make_ok)
print('-' * 20, '\n', s1, sep='')
--------------------
A 2ok
B 4ok
C 6ok
Name: Col-2, dtype: object
--------------------
a 0
b 1
c 2
d 3
e 4
dtype: int64
--------------------
a 0ok
b 1ok
c 2ok
d 3ok
e 4ok
dtype: object
DataFrame中的一行或一列都是一個Series,是以用DataFrame的列或行調用apply()就相當于Series調用apply()。
在DataFrame中,apply()将行/列作為Series傳給func函數,在Series中,apply()将Series中的每一個值傳給func函數。對于這兩種情況,func接受的參數類型完全不一樣,是以使用時一定要注意func函數的參數類型,否則可能不适用。
s2 = s.apply(np.mean)
print('-' * 20, '\n', s2, sep='')
s3 = np.mean(s)
print('-' * 20, '\n', s3, sep='')
--------------------
a 0.0
b 1.0
c 2.0
d 3.0
e 4.0
dtype: float64
--------------------
2.0