根据特定的 cumsum 值拆分数据框
Split a dataframe based on a specifc cumsum value
我有一个解决方案,但它看起来很麻烦,我想知道是否有更好的方法来实现我想要的。我需要完成两件事:
- 根据特定的 cumsum 值将一个数据帧拆分为两个数据帧。
- 如果需要拆分一行以满足 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_cs
和 prev_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}
我有一个解决方案,但它看起来很麻烦,我想知道是否有更好的方法来实现我想要的。我需要完成两件事:
- 根据特定的 cumsum 值将一个数据帧拆分为两个数据帧。
- 如果需要拆分一行以满足 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_cs
和 prev_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}