当在应用中也计算出前一个值时,Pandas 中是否有办法在 dataframe.apply 中使用前一行的值?
- 2025-01-20 09:07:00
- admin 原创
- 104
问题描述:
我有以下数据框:
Index_Date A B C D
================================
2015-01-31 10 10 Nan 10
2015-02-01 2 3 Nan 22
2015-02-02 10 60 Nan 280
2015-02-03 10 100 Nan 250
要求:
Index_Date A B C D
================================
2015-01-31 10 10 10 10
2015-02-01 2 3 23 22
2015-02-02 10 60 290 280
2015-02-03 10 100 3000 250
Column C
是2015-01-31
通过取得出value
的D
。
然后我需要使用并value
乘以并添加。C
`2015-01-31value
A2015-02-01
B`
我尝试过apply
和一个shift
使用一个,if else
但这给出了一个关键错误。
解决方案 1:
首先,创建派生值:
df.loc[0, 'C'] = df.loc[0, 'D']
然后遍历剩余的行并填充计算的值:
for i in range(1, len(df)):
df.loc[i, 'C'] = df.loc[i-1, 'C'] * df.loc[i, 'A'] + df.loc[i, 'B']
Index_Date A B C D
0 2015-01-31 10 10 10 10
1 2015-02-01 2 3 23 22
2 2015-02-02 10 60 290 280
解决方案 2:
给定一列数字:
lst = []
cols = ['A']
for a in range(100, 105):
lst.append([a])
df = pd.DataFrame(lst, columns=cols, index=range(5))
df
A
0 100
1 101
2 102
3 103
4 104
您可以使用以下方式引用上一行shift
:
df['Change'] = df.A - df.A.shift(1)
df
A Change
0 100 NaN
1 101 1.0
2 102 1.0
3 103 1.0
4 104 1.0
fill_value
您可以使用参数填充缺失值
df['Change'] = df.A - df.A.shift(1, fill_value=df.A[0]) # fills in the missing value e.g. 100<br>
df
A Change
0 100 0.0
1 101 1.0
2 102 1.0
3 103 1.0
4 104 1.0
解决方案 3:
numba
对于不可矢量化的递归计算,numba
使用 JIT 编译并与较低级别对象一起工作,通常可以大大提高性能。您只需定义一个常规for
循环并使用装饰器@njit
或(对于旧版本)@jit(nopython=True)
:
对于合理大小的数据框,与常规循环相比,这可以使性能提高约 30 倍for
:
from numba import jit
@jit(nopython=True)
def calculator_nb(a, b, d):
res = np.empty(d.shape)
res[0] = d[0]
for i in range(1, res.shape[0]):
res[i] = res[i-1] * a[i] + b[i]
return res
df['C'] = calculator_nb(*df[list('ABD')].values.T)
n = 10**5
df = pd.concat([df]*n, ignore_index=True)
# benchmarking on Python 3.6.0, Pandas 0.19.2, NumPy 1.11.3, Numba 0.30.1
# calculator() is same as calculator_nb() but without @jit decorator
%timeit calculator_nb(*df[list('ABD')].values.T) # 14.1 ms per loop
%timeit calculator(*df[list('ABD')].values.T) # 444 ms per loop
解决方案 4:
在 numpy 数组上应用递归函数将比当前答案更快。
df = pd.DataFrame(np.repeat(np.arange(2, 6),3).reshape(4,3), columns=['A', 'B', 'D'])
new = [df.D.values[0]]
for i in range(1, len(df.index)):
new.append(new[i-1]*df.A.values[i]+df.B.values[i])
df['C'] = new
输出
A B D C
0 1 1 1 1
1 2 2 2 4
2 3 3 3 15
3 4 4 4 64
4 5 5 5 325
解决方案 5:
虽然这个问题已经有一段时间了,但我还是会发布我的答案,希望它能对别人有所帮助。
免责声明:我知道这个解决方案不是标准的,但我认为它效果很好。
import pandas as pd
import numpy as np
data = np.array([[10, 2, 10, 10],
[10, 3, 60, 100],
[np.nan] * 4,
[10, 22, 280, 250]]).T
idx = pd.date_range('20150131', end='20150203')
df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx)
df
A B C D
=================================
2015-01-31 10 10 NaN 10
2015-02-01 2 3 NaN 22
2015-02-02 10 60 NaN 280
2015-02-03 10 100 NaN 250
def calculate(mul, add):
global value
value = value * mul + add
return value
value = df.loc['2015-01-31', 'D']
df.loc['2015-01-31', 'C'] = value
df.loc['2015-02-01':, 'C'] = df.loc['2015-02-01':].apply(lambda row: calculate(*row[['A', 'B']]), axis=1)
df
A B C D
=================================
2015-01-31 10 10 10 10
2015-02-01 2 3 23 22
2015-02-02 10 60 290 280
2015-02-03 10 100 3000 250
所以基本上我们使用apply
来自 pandas 并借助全局变量来跟踪之前计算的值。
循环的时间比较for
:
data = np.random.random(size=(1000, 4))
idx = pd.date_range('20150131', end='20171026')
df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx)
df.C = np.nan
df.loc['2015-01-31', 'C'] = df.loc['2015-01-31', 'D']
%%timeit
for i in df.loc['2015-02-01':].index.date:
df.loc[i, 'C'] = df.loc[(i - pd.DateOffset(days=1)).date(), 'C'] * df.loc[i, 'A'] + df.loc[i, 'B']
每循环 3.2 秒 ± 114 毫秒(7 次运行的平均值 ± 标准差,每次 1 个循环)
data = np.random.random(size=(1000, 4))
idx = pd.date_range('20150131', end='20171026')
df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx)
df.C = np.nan
def calculate(mul, add):
global value
value = value * mul + add
return value
value = df.loc['2015-01-31', 'D']
df.loc['2015-01-31', 'C'] = value
%%timeit
df.loc['2015-02-01':, 'C'] = df.loc['2015-02-01':].apply(lambda row: calculate(*row[['A', 'B']]), axis=1)
每循环 1.82 秒 ± 64.4 毫秒(7 次运行的平均值 ± 标准差,每次 1 个循环)
因此平均速度快 0.57 倍。
解决方案 6:
这是一个老问题,但下面的解决方案(没有 for 循环)可能会有所帮助:
def new_fun(df):
prev_value = df.iloc[0]["C"]
def func2(row):
# non local variable ==> will use pre_value from the new_fun function
nonlocal prev_value
new_value = prev_value * row['A'] + row['B']
prev_value = row['C']
return new_value
# This line might throw a SettingWithCopyWarning warning
df.iloc[1:]["C"] = df.iloc[1:].apply(func2, axis=1)
return df
df = new_fun(df)
解决方案 7:
一般来说,避免显式循环的关键是在 rowindex-1==rowindex 上连接(合并)数据框的 2 个实例。
然后,您将拥有一个包含 r 行和 r-1 行的大数据框,您可以从中执行 df.apply() 函数。
然而创建大型数据集的开销可能会抵消并行处理的好处......
扫码咨询,免费领取项目管理大礼包!