根据特定的 cumsum 值拆分数据框

Split a dataframe based on a specifc cumsum value

我有一个解决方案,但它看起来很麻烦,我想知道是否有更好的方法来实现我想要的。我需要完成两件事:

  1. 根据特定的 cumsum 值将一个数据帧拆分为两个数据帧。
  2. 如果需要拆分一行以满足 cumsum 条件,则必须发生这种情况。

举例说明千言万语;我有以下数据框:

import pandas as pd

max_order_value = 2500
df = pd.DataFrame({'Age': [30, 20, 22, 40, 32, 28, 39],
                   'vol': [165, 70, 120, 80, 180, 172, 150],
                   'price': [4.6, 8.3, 9.0, 3.3, 1.8, 9.5, 2.2],
                   }, index=['A', 'B', 'C', 'D', 'E',
                            'F', 'G']
                  )
df["eurvol"] = df.vol * df.price
df["eurvol_cs"] = df.eurvol.cumsum()
df["prev_cs"] = df["eurvol_cs"].shift(fill_value=0)
print(df)

请注意,最后三列不在我的原始数据框中,我需要计算它们。

   Age  vol  price  eurvol  eurvol_cs  prev_cs
A   30  165    4.6   759.0      759.0      0.0
B   20   70    8.3   581.0     1340.0    759.0
C   22  120    9.0  1080.0     2420.0   1340.0
D   40   80    3.3   264.0     2684.0   2420.0
E   32  180    1.8   324.0     3008.0   2684.0
F   28  172    9.5  1634.0     4642.0   3008.0
G   39  150    2.2   330.0     4972.0   4642.0

现在,我需要将它们基本上分成两个数据帧。 df1 将保留所有行,直到第 eurvol_cs 列(欧元交易量累计)等于 2500 (max_order_value)。另一个数据框 df2 将保存之后的所有行。请注意,在这种情况下,这意味着行 D 将部分位于 df1 中,部分位于 df2.

我从 df2:

开始
#create new df with only remaining orders
df2 = df[df["eurvol_cs"] > max_order_value].copy()

#make sure we save the price of the last order (D) and calculate how much of the volume we have used
used_volume_of_last_row = ((max_order_value-df2["prev_cs"].iloc[0]) / df2["price"].iloc[0])

#Recalculate the new volume, eurvol for (D) and new cumsum for the df
df2["vol"].iloc[0] = df2["vol"].iloc[0] - used_volume_of_last_row
df2["eurvol"].iloc[0] = df2["vol"].iloc[0] * df2["price"].iloc[0]
df2["eurvol_cs"] = df2["eurvol"].cumsum()
print(df2.head())
#    Age         vol  price  eurvol  eurvol_cs  prev_cs
# D   40   55.757576    3.3   184.0      184.0   2420.0
# E   32  180.000000    1.8   324.0      508.0   2684.0
# F   28  172.000000    9.5  1634.0     2142.0   3008.0
# G   39  150.000000    2.2   330.0     2472.0   4642.0

到目前为止一切顺利,但有点难看,尤其是因为我必须重新计算第一行 (D) 的特定字段。

df1:

df1 = df[df["prev_cs"] < 2500].copy()
df1["vol"].iloc[-1] = used_volume_of_last_row
df1["eurvol"] = df1["vol"] * df1["price"]
df1["eurvol_cs"] = df1["eurvol"].cumsum()
print(df1.head())
#    Age         vol  price  eurvol  eurvol_cs  prev_cs
# A   30  165.000000    4.6   759.0      759.0      0.0
# B   20   70.000000    8.3   581.0     1340.0    759.0
# C   22  120.000000    9.0  1080.0     2420.0   1340.0
# D   40   24.242424    3.3    80.0     2500.0   2420.0

#df_first_order is now correct, so we can calculate average price:
avg_price = max_order_value/df1["vol"].sum()
print(avg_price)
# 6.592089492608869

如您所见,总的来说它是有效的。但是,这超过了 15 个 LoC。我希望有人能阐明如何以不同的方式完成这项工作。请注意,整个代码块被执行了数百万次(它是在另一个数据帧上 apply-ed 的函数的一部分)。因此,性能很重要,但不是特别重要。我只是觉得我做事不正确。

编辑: 在上面睡了一夜之后,我想它可能并不完全清楚我想要什么。我希望我的原始数据框(年龄、体积、价格)像这样拆分:

数据帧 1:

    Age         vol  price  eurvol  
 A   30  165.000000    4.6   759.0 
 B   20   70.000000    8.3   581.0
 C   22  120.000000    9.0  1080.0
 D   40   24.242424    3.3    80.0

数据帧 2:

    Age         vol  price  eurvol  
 D   40   55.757576    3.3   184.0
 E   32  180.000000    1.8   324.0
 F   28  172.000000    9.5  1634.0
 G   39  150.000000    2.2   330.0

eurvol_csprev_cs 本身在生成的数据框中不是必需的,但它们也不需要删除。

  • 计算您记下的列
  • 找到 cumsum() 超过幻数 2500
  • 的行
  • 在该行上制作 vol 一个 list 这是将 cumsum() 限制为幻数的拆分
  • 使用 explode()
  • 扩展列表
  • 再次计算导出的数字并重新使用split列来识别它是哪个目标DF
  • 最终生成目标 DF 作为 dict
df = pd.DataFrame({'Age': [30, 20, 22, 40, 32, 28, 39],
                   'vol': [165, 70, 120, 80, 180, 172, 150],
                   'price': [4.6, 8.3, 9.0, 3.3, 1.8, 9.5, 2.2],
                   }, index=['A', 'B', 'C', 'D', 'E',
                            'F', 'G']
                  )
magicv = 2500

df = (df.assign(eurvol=df.vol*df.price,
         eurvol_cs=lambda dfa: dfa.eurvol.cumsum(),
           # find row where cumsum goes above magic number
         split=lambda dfa: dfa.eurvol_cs.gt(magicv) & dfa.eurvol_cs.shift().lt(magicv),
           # split vol on row where it goes above magic number into a list
          vol=lambda dfa: np.where(dfa.split, 
                                   dfa.apply(lambda r: [r.vol-((r.eurvol_cs-magicv)/r.price),
                                                             (r.eurvol_cs-magicv)/r.price], axis=1), 
                                   dfa.vol),
         )
 # explode list
 .explode("vol")
 # recalc and group DF
 .assign(eurvol=lambda dfa: dfa.vol*dfa.price,
         split=lambda dfa: dfa.eurvol.cumsum().gt(magicv),
        )
 .drop(columns="eurvol_cs")
)

# finally a dict of multiple dataframes
dfs = {f"df_{i+1}":df.loc[df.split.eq(v), [c for c in df.columns if c!="split"]] for i,v in enumerate(df.split.unique())}

输出字典

{'df_1':    Age        vol  price  eurvol
 A   30        165    4.6   759.0
 B   20         70    8.3   581.0
 C   22        120    9.0  1080.0
 D   40  24.242424    3.3    80.0,
 'df_2':    Age        vol  price  eurvol
 D   40  55.757576    3.3   184.0
 E   32        180    1.8   324.0
 F   28        172    9.5  1634.0
 G   39        150    2.2   330.0}