【问题标题】:Better way to recalculate pandas dataframe fields depending on other fields根据其他字段重新计算熊猫数据框字段的更好方法
【发布时间】:2017-08-13 10:28:14
【问题描述】:

我是 python pandas 的新手。 我想估计一段时间内的流入支付价值,考虑到一段时间内的费用和增长。 我只使用了一笔付款(流入)进行测试。 有时,fee2 可以应用到周期 n-t。即不是整个时期,n.

我像下面那样做,只是想知道是否有更好的方法来重新计算值而不循环?

电子表格中的示例:

Python 代码:

import pandas as pd
import numpy as np

def getCashFlows():
   term = 2
   growthRate = (1+0.06)**(1/12) - 1
   df = pd.DataFrame(list(range(1,term*12+1)), columns=['t'])
   df['Value_t_1'] = 0
   df['Inflow1']=0
   df['growth']=0
   df['ValuePlusGrowth'] = 0
   df['fee1']=0
   df['fee2']=30
   df['Value_t']=0

   df.set_value(0, 'Inflow1', 10000)

   for i in range(0,term*12):
      df['Value_t_1'] = df['Value_t'].shift()
      df['Value_t_1'].fillna(0,inplace=True)

      df['growth'] = (df['Value_t_1'] + df['Inflow1'])*growthRate
      df['ValuePlusGrowth'] = df['Value_t_1']+df['Inflow1']+df['growth']
      df['fee1']=df['ValuePlusGrowth']*0.5/100
      df['Value_t'] = df['ValuePlusGrowth'] - df['fee1'] - df['fee2']
   return df

【问题讨论】:

  • 只需去掉 def & for 并运行其余代码。循环是隐式的。你有“for i”但没有“i”,所以你所做的只是重复完全相同的代码 24 次而不是一次,它每次都在整个 df 上运行
  • @JohnE - 实际上并不完全。最初我也是这么想的,但请注意第一行中的shift()。每个循环都期待 Value_t。在这样做的过程中,OP 实际上是在 24 个周期内累积这些值。

标签: python python-3.x pandas numpy


【解决方案1】:

真正需要的唯一初始输入是inflow 的初始值。根据行索引,其他一切都可以简化为重复一定次数的操作。数据框中的某些列实际上只是常量。

这里有一个解决方案,它阐明了计算数据帧的每一行所需的操作:

import pandas as pd

class GrowthTracker(object):

    def __init__(self, n_iter):

        self.colnames = ['Value_t_1', 'growth', 'ValuePlusGrowth', 'fee1', 'Value_t']
        self.data = None
        self.fee1_mult = 0.5/100
        self.fee2 = (0,0,0,0,30)
        self.growthRate = (1+0.06)**(1/12) - 1
        self.n_iter = n_iter
        self.ops = pd.Series([1, # Value_t_1
                              self.growthRate, # growth
                              (1 + self.growthRate), # ValuePlusGrowth
                              (1 + self.growthRate) * self.fee1_mult, # fee1
                              (1 + self.growthRate) * (1 - self.fee1_mult) # Value_t
                             ])

    def update(self, t, n, df=None):
        row = self.ops.mul(t).subtract(self.fee2)
        tmp = pd.concat([df, row], axis = 1, ignore_index=True)
        if n < self.n_iter: 
            self.data = self.update(row.iloc[-1], n+1, tmp)
            return self.data
        else:
            tmp.iloc[0,0] = 0 # remove the initial 10000 from Value_t_1
            self.data = tmp.T
            self.data.columns = self.colnames
            return self.data

现在只需设置初始值,实例化GrowthTracker 对象和update()

total_iter = 23

tracker = GrowthTracker(n_iter=total_iter)

inflow = 10000
start_index = 0

tracker.update(t=inflow, n=start_index)

tracker.data

      Value_t_1     growth  ValuePlusGrowth       fee1      Value_t
0      0.000000  48.675506     10048.675506  50.243378  9968.432128
1   9968.432128  48.521847     10016.953976  50.084770  9936.869206
2   9936.869206  48.368213      9985.237419  49.926187  9905.311232
3   9905.311232  48.214603      9953.525835  49.767629  9873.758206
4   9873.758206  48.061017      9921.819223  49.609096  9842.210127
5   9842.210127  47.907455      9890.117583  49.450588  9810.666995
6   9810.666995  47.753918      9858.420912  49.292105  9779.128808
7   9779.128808  47.600404      9826.729212  49.133646  9747.595566
8   9747.595566  47.446914      9795.042480  48.975212  9716.067268
9   9716.067268  47.293449      9763.360716  48.816804  9684.543913
10  9684.543913  47.140007      9731.683920  48.658420  9653.025500
11  9653.025500  46.986590      9700.012090  48.500060  9621.512030
12  9621.512030  46.833196      9668.345226  48.341726  9590.003500
13  9590.003500  46.679827      9636.683327  48.183417  9558.499910
14  9558.499910  46.526482      9605.026392  48.025132  9527.001260
15  9527.001260  46.373160      9573.374420  47.866872  9495.507548
16  9495.507548  46.219863      9541.727411  47.708637  9464.018774
17  9464.018774  46.066590      9510.085364  47.550427  9432.534937
18  9432.534937  45.913341      9478.448278  47.392241  9401.056037
19  9401.056037  45.760116      9446.816152  47.234081  9369.582072
20  9369.582072  45.606915      9415.188986  47.075945  9338.113041
21  9338.113041  45.453737      9383.566779  46.917834  9306.648945
22  9306.648945  45.300584      9351.949529  46.759748  9275.189781
23  9275.189781  45.147455      9320.337237  46.601686  9243.735551

我发现将这一切表达为一个类更容易,但只需在类外部定义变量然后运行update() 函数就足够简单了。

更新
下面是这个解决方案背后的更多解释:

初始数据框df 大部分为空。唯一完全非零的列是 t,它从未使用过,fee2,它是一个常量 (fee2 = 30)。 df 的全部剩余部分以零值开始,Inflow1 中的第一个单元格除外 - 它的第一个值是 10000,其余值为零。

这意味着,就我们需要完成的计算而言,我们可以将“感兴趣的矩阵”限制在 Value_t_1growthValuePlusGrowthfee1Value_t 列。

我们可以将第一个 Inflow1 值视为种子 - 其他所有内容都只是对数字 10000 执行的一串操作。 (事实上​​,我们实际上并不需要 Inflow1 作为一个字段,因为它的所有其他值在整个计算过程中都保持为零。)

在您的循环中,您最初使用其他列的值更新了列。这是有道理的,我可能也会这样做——看起来整洁而高效。然而,回想一下,每一次更新实际上只是一个数学字符串,可以追溯到最初的10000。写出每个列更新的实际操作,而不是使用其他列名,显示了如何简化每个更新操作。

首先,一些速记符号:

t = Value_t from previous row (in case of the first row, Value_t = Inflow1 = 10000)
t1 = Value_t_1 
g = growth
inf = Inflow1 
vpg = ValuePlusGrowth
gr = growthRate # gr is a constant: (1+0.06)**(1/12) - 1
f1X = 0.5/100
new_t = Value_t for current row

我们从t = 10000 开始。其他的都是对t的一些操作。

每个值都可以表示为我们需要乘以t 以获得所需的值(有一个例外,我稍后会介绍)。比如:

df['Value_t_1'] = df['Value_t'].shift()
df['Value_t_1'].fillna(0,inplace=True)

# equivalent to:
t1 = 1 * t # recall t is the shifted Value_t from the previous row

请记住,我们只需将种子值 t 放入一次,然后只需对种子进行操作即可填充所有 df。这意味着循环中的操作可以表示为“需要乘以 t 才能获得正确的列值的项”。所以虽然我们已经证明了t1 = 1 * t,但考虑t1 = 1对我们来说更有用——最终我们将乘以t,但该等式的右侧表示t1与@987654354的关系@。

然后:

t1 = 1

下一步:

# Inflow1 is always 0, except for its initial value which we capture in initial t, so:
df['growth'] = (df['Value_t_1'] + df['Inflow1'])*growthRate
# becomes:
g = t1 * gr 
# with t1 = 1
g = gr

# we know t1 = 1, and inf is never used as a multiplier, so:
df['ValuePlusGrowth'] = df['Value_t_1']+df['Inflow1']+df['growth']
# becomes:
vpg = 1 + g = 1 + gr

df['fee1']=df['ValuePlusGrowth']*0.5/100
# becomes:
fee1 = vpg * f1X = (1 + gr) * f1X

# we'll ignore subtracting fee2 for now, see notes below.
df['Value_t'] = df['ValuePlusGrowth'] - df['fee1'] - df['fee2']
# becomes:
new_t = vpg - fee1 = (1 + gr) - ((1 + gr) * f1X) = (1 + gr) * (1 - f1X)

ops = (t1, g, vpg, fee1, new_t)

现在,对于每一行,我们对每一列都有一组更新操作ops。鉴于我们有上一行的t,我们可以为每一行填充值:

new_row = t * ops

我们仍然需要从new_t 中减去fee2,这并不完全适合在此之前的一系列乘法运算。但我们可以坚持我们的矢量化公式并定义:

fee2 = (0,0,0,0,30)

在每个new_row 之后,我们从new_row 向量中减去fee2 向量,这实际上只是根据需要从new_t 中减去fee2

new_row = t * ops - fee2

此时,我们只需要一个以t = 10000 开头的函数,并在前一行的基础上继续执行new_row 公式,直到我们达到所需的迭代次数。我选择了递归策略来执行此操作,并在每个递归步骤中将每个 new_row 保存到一个数据框中。

最后,由于我通过设置t = 10000 而不是Inflow1 = 10000 有点滥用了您的原始符号,这意味着第一个t1 值被错误地设置为10000。在update() 函数结束时,我们将第一个t1 值设置回0

【讨论】:

  • 感谢@andrew_reece,但我在努力运行代码,我修复了其他简单的错误,例如在引用一些变量之前缺少 self...但是递归调用 update(self, t, n, df=无),更新(row.iloc[-1],n+1,self.n_iter,tmp),有一个额外的值(row.iloc[-1])我无法匹配
  • 很抱歉,在我转换为基于类的方法之前,我的环境中存在一些变量,并且在发布之前我没有清除所有要测试的东西。我的错。现在更新了,我已经从一个新的内核开始了这段代码,并确认一切都按照发布的那样工作。
  • 非常感谢...花了更长的时间,因为我想确保我理解它...
  • 不客气,我真的很喜欢这个挑战。我添加了一些解释来阐明我的实现,请参阅更新的解决方案。
猜你喜欢
  • 2020-07-02
  • 2012-05-11
  • 2023-03-30
  • 2020-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-16
  • 1970-01-01
相关资源
最近更新 更多