使用 Pandas 以更有效的方式在后续行之间应用函数
Posted
技术标签:
【中文标题】使用 Pandas 以更有效的方式在后续行之间应用函数【英文标题】:Apply function between subsequent rows in more efficient way with Pandas 【发布时间】:2017-07-18 04:35:09 【问题描述】:我有一个数据框df
,其定义如下:
import numpy as np
import pandas as pd
dic = 'A':['1A','1A','3C','3C','3C','7M','7M','7M'],'B':[10,15,49,75,35,33,45,65],'C':[11,56,32,78,45,89,15,14],'D':[111,0,np.nan,np.nan,np.nan,np.nan,np.nan,np.nan],'E':[0,222,np.nan,np.nan,np.nan,np.nan,np.nan,np.nan]
df = pd.DataFrame(dic)
我的目标是在 A
列中具有相同项目的行之间执行一些计算。
函数是这样定义的(但可以是任何东西):
def fun(a,b,c,d):
out = a*c + b/2 + d*b
return out
这样操作的结果将按照以下规则存储在D列和E列中:
# Fill column D
for j in range(0,len(df)-1):
if df['A'].iloc[j]==df['A'].iloc[j+1] and pd.isnull(df['D'].iloc[j]):
df['D'].iloc[j] = fun(df['B'].iloc[j],df['B'].iloc[j],df['B'].iloc[j+1],df['B'].iloc[j+1])
# Fill column E
for j in reversed(range(1,len(df))):
if df['A'].iloc[j-1]==df['A'].iloc[j] and pd.isnull(df['E'].iloc[j]):
df['E'].iloc[j] = fun(df['B'].iloc[j],df['B'].iloc[j],df['B'].iloc[j-1],df['B'].iloc[j-1])
两个循环非常相似,但第二个循环是从最后一个元素循环到数据帧的第一个元素。 我的代码工作正常,结果应该是这样的:
# Before # # After #
A B C D E A B C D E
0 1A 10 11 111 0 0 1A 10 11 111.0 0.0
1 1A 15 56 0 222 1 1A 15 56 0.0 222.0
2 3C 49 32 NaN NaN 2 3C 49 32 7374.5 NaN
3 3C 75 78 NaN NaN 3 3C 75 78 5287.5 7387.5
4 3C 35 45 NaN NaN 4 3C 35 45 NaN 5267.5
5 7M 33 89 NaN NaN 5 7M 33 89 2986.5 NaN
6 7M 45 15 NaN NaN 6 7M 45 15 5872.5 2992.5
7 7M 65 14 NaN NaN 7 7M 65 14 NaN 5882.5
您能否使用 Pandas 库中的一些内置函数改进此类代码以使其更高效?我想有一些更优雅的方式来实现我的结果。
注意:第一行和第二行已经有值(111 0
和0 222
),所以不能由函数计算!
【问题讨论】:
尝试使用Series.diff @user2476373 感谢您的评论 :) 您能否提供有关如何使用它以及为什么它适合我的问题的更多详细信息? 【参考方案1】:您可以先按 A 的值分组,然后应用矢量化函数:
def fun(a,b,c,d):
out = a*c + b/2 + d*b
return out
def apply_func(df):
mask = pd.isnull(df['D'][:-1])
df['D'][:-1][mask] = fun(df['B'][:-1].values, df['B'][:-1].values,
df['B'][1:].values, df['B'][1:].values)
mask = pd.isnull(df['E'][1:])
df['E'][1:][mask] = fun(df['B'][1:].values, df['B'][1:].values,
df['B'][:-1].values, df['B'][:-1].values)
return df
然后:
df = df.groupby('A').apply(apply_func).reset_index(drop=True)
A B C D E
0 1A 10 11 305.0 NaN
1 1A 15 56 NaN 307.5
2 3C 49 32 7374.5 NaN
3 3C 75 78 5287.5 7387.5
4 3C 35 45 NaN 5267.5
5 7M 33 89 2986.5 NaN
6 7M 45 15 5872.5 2992.5
7 7M 65 14 NaN 5882.5
【讨论】:
我还注意到答案不正确,因为第一行和第二行在字典中定义了固定值 (111 0, 0 222)...而您的答案正在修改结果 @FedericoGentile :查看您的示例 DataFrame。 D 列和 E 列只有 NaN 值。我看到您的“之前”和“之后”数据帧之间存在不一致,所以我只是将该函数应用于“之前”数据帧。如果 D 和 E 列已定义值,则该函数不应影响它们。【参考方案2】:你可以使用np.where和dataframe.shift()
np.where 像 if 语句一样工作datafrmae.shift() - 使用可选的时间频率按所需的周期数移动索引
df['D']=np.where(df.A.shift(-1)==df.A,func(df['B'],df['B'],df.B.shift(-1),df.B.shift(-1)),np.NaN)
【讨论】:
感谢您的回答; df['E'] 呢? 我还注意到答案不正确,因为第一行和第二行在字典中定义了固定值 (111 0, 0 222)...而您的答案正在修改结果 我能够修正你的答案,但 df['E'] 仍然丢失【参考方案3】:为了解决我的问题,我定义了另一个函数,将fun
作为输入
def fun2(df,s):
X= fun(df.B,df.C,df.B.shift(s),df.C.shift(s))
return X
D
和 E
列可以这样填充:
df2['D']=np.where((df2.A.shift(-1)==df2.A) & (df2.D.isnull()==True),fun2(df2,-1),df2.D)
df2['E']=np.where((df2.A.shift(1)==df2.A) & (df2.E.shift(1).isnull()==True),fun2(df2,+1),df2.E)
注意:虽然更紧凑,但这种方法可能会更慢
【讨论】:
以上是关于使用 Pandas 以更有效的方式在后续行之间应用函数的主要内容,如果未能解决你的问题,请参考以下文章