问题描述
我试图在三列中获得各种数据组合,同时,我还想对这些值进行汇总(求和)。
我的数据如下所示,下面是我的示例输出:
Dim1 Dim2 Dim3 Spend
A X Z 100
A Y Z 200
B X Z 300
B Y Z 400
样本输出:
Dim 1 Dim 2 Dim 3 Spend
A NaN NaN 300
A X NaN 100
A Y NaN 200
A NaN Z 300
B NaN NaN 700
B X NaN 300
B Y NaN 400
B NaN Z 700
NaN X Z 400
NaN Y Z 600
NaN NaN Z 1000
NaN X NaN 400
NaN Y NaN 600
A X Z 100
A Y Z 200
B X Z 300
B Y Z 400
Dim1
,Dim2
,Dim3
是分类变量,而Spend
是值/度量。我们需要在分类变量的所有可能组合上找到总计Spend
,而我可以使用itertools.combinations()
来实现这一部分。现在,不仅对于三列,我们还可以获得任意数量的此类变量的组合,例如Dim1,Dim2,Dim3 .... Dim 30等。
我的问题是我无法在同一行上进行汇总,例如,在第12行中,对于类别Spend
的{{1}}值,我们正在执行所有Z
Z出现在主数据中的值,即1000。我们如何实现聚合的值?
可复制的数据:
sum()
解决方法
摘要
使用itertools.combinations()
的想法正确。其他关键步骤:
- 将
itertools.combinations()
应用于所有可能要汇总的尺寸(从1
到n_dim-1
)。即itertools.combinations(range(1,1+n_dim),i)
,代表i in range(1,1+n_dim)
。 - 使用
df.groupby(by=column_combinations).sum()
从类的组合中自动获取结果。
代码
程序包含3个逻辑部分。
- 从各个维度按类进行汇总。这部分基本上与您所做的相等,但是通过DFS方法进行了重新设计,以减少要处理的数据总量。当有数百万行要处理时,这很有用。随后的步骤也基于此中间数据集而不是原始数据集进行计算。
- 一个生成器,可以循环进行摘要1中提到的维组合,而无需显式枚举。
- 执行摘要2中提到的分组计算,并输出可以在程序末尾连接的结果数据帧列表。
警告:请务必在生产中测试性能和内存问题。
import pandas as pd
import numpy as np
import itertools
df = pd.DataFrame(
{'Dim1': ['A','A','B','B'],'Dim2': ['X','Y','X','Y'],'Dim3': ['Z','Z','Z'],'Spend': [100,200,300,400]
}
)
# constants: column names and dimensions
n_dim = 3
dim_cols = [f"Dim{i}" for i in range(1,n_dim + 1)]
cols = dim_cols + ["Spend"]
# 1. compute sums with every dimension
def dfs(df,ls_out,dim_now=1,ls_classes=[]):
# termination condition (every dimension has been traversed)
if dim_now == n_dim + 1:
# perform aggregation
sum = df["Spend"].sum()
ls_classes.append(sum)
ls_out.append(ls_classes)
return
# proceed
col = f"Dim{dim_now}"
# get categories
classes = df[col].unique()
classes.sort()
for c in classes:
# recurse next dimension with subset data
dfs(df[df[col] == c],dim_now=dim_now + 1,ls_classes=ls_classes + [c])
ls_out = [] # the output container
dfs(df,ls_out)
# convert to dataframe
df_every_dim = pd.DataFrame(data=ls_out,columns=df.columns)
del ls_out
print(df_every_dim)
# 2. generate combinations of groupby-dimensions
def multinomial_combinations(n_dim):
for i in range(1,1+n_dim):
for tup in itertools.combinations(range(1,i):
yield tup
print("Check multinomial_combinations(4):")
for i in multinomial_combinations(4):
print(i)
# 3. Sum based on from df_every_dim
def aggr_by_dims(df,by_dims):
# guard
if not (0 < len(by_dims) < n_dim):
raise ValueError(f"Wrong n_dim={n_dim},len(by_dims)={len(by_dims)}")
# by-columns
by_cols = [f"Dim{i}" for i in by_dims]
# groupby-sum
df_grouped = df.groupby(by=by_cols).sum().reset_index()
# create none-columns (cannot be empty here)
arr = np.ones(n_dim+1,dtype=int)
arr[list(by_dims)] = 0
for i in range(1,1+n_dim):
if arr[i] == 1:
df_grouped[f"Dim{i}"] = None # or np.nan as you wish
# reorder columns
return df_grouped[cols]
print("\nCheck aggr_by_dims(df_every_dim,[1,3]):")
print(aggr_by_dims(df_every_dim,3]))
# combine 2. and 3.
ls = []
for by_dims in multinomial_combinations(n_dim):
if len(by_dims) < n_dim:
df_grouped = aggr_by_dims(df_every_dim,by_dims)
ls.append(df_grouped)
# no none-dimensions
ls.append(df_every_dim)
# final result
df_ans = pd.concat(ls,axis=0)
df_ans.reset_index(drop=True,inplace=True)
print(df_ans)
输出
(省略了中间输出)
Dim1 Dim2 Dim3 Spend
0 A None None 300
1 B None None 700
2 None X None 400
3 None Y None 600
4 None None Z 1000
5 A X None 100
6 A Y None 200
7 B X None 300
8 B Y None 400
9 A None Z 300
10 B None Z 700
11 None X Z 400
12 None Y Z 600
13 A X Z 100
14 A Y Z 200
15 B X Z 300
16 B Y Z 400