【问题标题】:How to get various combinations of categories in a categorical variable and at the same time aggregate it?如何在分类变量中获取各种类别组合并同时聚合它?
【发布时间】:2021-01-20 14:47:33
【问题描述】:

我正在尝试为三列中的数据获取各种组合,在这样做的同时,我还想聚合(求和)这些值。

我的数据如下所示,下面是我的示例输出:

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

Dim1Dim2Dim3 是分类变量,Spend 是值/指标。我们需要在分类变量的所有可能组合上找到Spend 的总数,这部分我可以使用itertools.combinations() 来实现。现在,不仅是三列,我们还可以获得任意数量的此类变量的组合,例如 Dim1、Dim2、Dim3 .. Dim 30 等等。

我的问题是我无法聚合相同的值,例如,在第 12 行中,对于类别 ZSpend 值,我们正在执行 Z 出现的所有值中的 sum()在主数据中,因此值为 1000。我们如何实现聚合?


可重复的数据:

data = pd.DataFrame({'Dim1': ['A', 'A', 'B', 'B'],
 'Dim2': ['X', 'Y', 'X', 'Y'],
 'Dim3': ['Z', 'Z', 'Z', 'Z'],
 'Spend': [100, 200, 300, 400]})

【问题讨论】:

  • 我认为不使用某种批处理就不能直接处理 30 个维度。假设有 30 个非单一维度,每个维度至少有 2 个类别,那么至少需要生成 3^30(3=2+1,其中 1 代表 NaN)或超过 200 万亿行。这对您来说可能是个问题吗?
  • @BillHuang 没错!但这不是现在的问题。我正在尝试找到一种方法来获得正确的总和

标签: pandas dataframe itertools cartesian-product


【解决方案1】:

摘要

你有正确的想法使用itertools.combinations()。进一步的关键步骤:

  1. itertools.combinations() 应用于要汇总的每个可能的维数(从1n_dim-1)。即itertools.combinations(range(1, 1+n_dim), i),代表i in range(1, 1+n_dim)
  2. 使用df.groupby(by=column_combinations).sum() 自动获取类组合的结果。

代码

程序由 3 个逻辑部分组成。

  1. 从各个维度按类聚合。这部分基本上和你做的一样,只是通过DFS的方法重新设计,减少了正在处理的数据总量。当有数百万行要处理时,这可能很有用。后面的步骤也是基于这个中间数据集而不是原始数据集计算的。
  2. 一个生成器,用于循环遍历摘要 1 中提到的维度组合,无需显式枚举。
  3. 执行摘要 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', '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

【讨论】:

  • 感谢您的解释。这适用于较小的数据集,尚未对较大的数据进行测试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-28
  • 1970-01-01
  • 1970-01-01
  • 2013-09-08
  • 1970-01-01
相关资源
最近更新 更多