天天看點

Pandas知識點-詳解行列級批處理函數apply

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