如何在分类变量中获取类别的各种组合并同时对其进行聚合?
How to get various combinations of categories in a categorical variable and at the same time aggregate it?
我正在尝试获取三列数据的各种组合,同时我还想聚合(求和)这些值。
我的数据如下所示,下面是我的示例输出:
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
是 value/metric。我们需要找到分类变量的所有可能组合的总数 Spend
,这部分我可以使用 itertools.combinations()
来实现。现在,不仅是三列,我们还可以获得任意数量的此类变量的组合,例如 Dim1、Dim2、Dim3 .. Dim 30 等等。
我的问题是我无法聚合相同的内容,例如,在第 12 行中,对于类别 Z
的 Spend
值,我们正在执行 sum()
Z 出现在主要数据中的所有值,因此值为 1000。我们如何实现聚合的那个值?
可重现的数据:
data = pd.DataFrame({'Dim1': ['A', 'A', 'B', 'B'],
'Dim2': ['X', 'Y', 'X', 'Y'],
'Dim3': ['Z', 'Z', 'Z', 'Z'],
'Spend': [100, 200, 300, 400]})
摘要
您使用 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()
自动从classes的组合中得到结果。
代码
该程序由 3 个逻辑部分组成。
- 从每个维度按 class 聚合。这部分基本上和你做的一样,只是re-designed通过一个DFS的方法来减少处理的数据总量。当有数百万行要处理时,这会很有用。后面的步骤也是基于这个中间数据集而不是原始数据集计算的。
- 一个生成器,用于循环遍历摘要 1 中提到的维度组合,并且没有显式枚举。
- 执行摘要 2 中提到的 group-by 计算并输出结果数据帧列表,可以在程序末尾连接这些数据帧。
警告:务必在生产使用中测试性能和内存问题。
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', '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], ls_out,
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, 1+n_dim), 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, [1, 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
我正在尝试获取三列数据的各种组合,同时我还想聚合(求和)这些值。
我的数据如下所示,下面是我的示例输出:
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
是 value/metric。我们需要找到分类变量的所有可能组合的总数 Spend
,这部分我可以使用 itertools.combinations()
来实现。现在,不仅是三列,我们还可以获得任意数量的此类变量的组合,例如 Dim1、Dim2、Dim3 .. Dim 30 等等。
我的问题是我无法聚合相同的内容,例如,在第 12 行中,对于类别 Z
的 Spend
值,我们正在执行 sum()
Z 出现在主要数据中的所有值,因此值为 1000。我们如何实现聚合的那个值?
可重现的数据:
data = pd.DataFrame({'Dim1': ['A', 'A', 'B', 'B'],
'Dim2': ['X', 'Y', 'X', 'Y'],
'Dim3': ['Z', 'Z', 'Z', 'Z'],
'Spend': [100, 200, 300, 400]})
摘要
您使用 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()
自动从classes的组合中得到结果。
代码
该程序由 3 个逻辑部分组成。
- 从每个维度按 class 聚合。这部分基本上和你做的一样,只是re-designed通过一个DFS的方法来减少处理的数据总量。当有数百万行要处理时,这会很有用。后面的步骤也是基于这个中间数据集而不是原始数据集计算的。
- 一个生成器,用于循环遍历摘要 1 中提到的维度组合,并且没有显式枚举。
- 执行摘要 2 中提到的 group-by 计算并输出结果数据帧列表,可以在程序末尾连接这些数据帧。
警告:务必在生产使用中测试性能和内存问题。
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', '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], ls_out,
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, 1+n_dim), 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, [1, 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