类别具有冗余名称的Pandas群

brvekthn  于 2023-01-28  发布在  其他
关注(0)|答案(6)|浏览(151)

我在使用pandas groupby处理分类数据时遇到了一些问题。理论上,它应该是超级高效的:你是通过整数而不是字符串来分组和索引的。但它坚持认为,当按多个类别分组时,* 类别的每一种组合 * 都必须考虑在内。
我有时候使用分类,即使常见字符串的密度很低,因为这些字符串很长,这样可以节省内存/提高性能。有时候每列有数千个分类。当按3列分组时,pandas强制我们保存1000^3个组的结果。
我的问题:有没有一种方便的方法可以将groupby与类别一起使用,同时避免这种不愉快的行为?我不想寻找以下任何解决方案:

  • 通过numpy重新创建所有功能。
  • 不断转换为groupby之前的字符串/代码,稍后恢复为类别。
  • 从组列生成元组列,然后按元组列分组。

我希望有一种方法可以修改这个特殊的pandas特性。下面是一个简单的例子。我希望在输出中有12个类别,而不是4个类别。

import pandas as pd

group_cols = ['Group1', 'Group2', 'Group3']

df = pd.DataFrame([['A', 'B', 'C', 54.34],
                   ['A', 'B', 'D', 61.34],
                   ['B', 'A', 'C', 514.5],
                   ['B', 'A', 'A', 765.4],
                   ['A', 'B', 'D', 765.4]],
                  columns=(group_cols+['Value']))

for col in group_cols:
    df[col] = df[col].astype('category')

df.groupby(group_cols, as_index=False).sum()

Group1  Group2  Group3  Value
#   A   A   A   NaN
#   A   A   C   NaN
#   A   A   D   NaN
#   A   B   A   NaN
#   A   B   C   54.34
#   A   B   D   826.74
#   B   A   A   765.40
#   B   A   C   514.50
#   B   A   D   NaN
#   B   B   A   NaN
#   B   B   C   NaN
#   B   B   D   NaN
    • 奖金更新**

Pandas开发团队没有很好地解决这个问题(cf github.com/pandas-dev/pandas/issues/17594)。因此,我希望得到解决以下问题的回复:
1.参考panda源代码,为什么在groupby操作中对分类数据的处理不同?
1.为什么当前的实现是首选?我知道这是主观的,但我很难找到这个问题的答案。当前的行为在许多情况下是禁止的,没有繁琐的,潜在的昂贵的工作区。
1.有没有一个干净的解决方案来覆盖panda在groupby操作中对分类数据的处理?注意3条不可行的路线(下降到numpy;转换为代码/从代码转换;通过元组列创建和分组)。我更喜欢一个"Pandas兼容"的解决方案,以最小化/避免其他Pandas分类功能的损失。
1.Pandas开发团队的回应,支持并澄清现有的治疗。还有,为什么考虑所有的类别组合不能配置为布尔参数?

    • 奖金更新#2**

需要说明的是,我并不期待上面4个问题的答案,我想问的主要问题是,是否有可能或者建议重写pandas库方法,以便以一种便于groupby/set_index操作的方式来处理类别。

rbl8hiat

rbl8hiat1#

从Pandas 0.23.0开始,groupby method现在可以接受一个参数observed,如果将其设置为True(默认为False),则可以修复此问题。以下代码与问题中的代码完全相同,只是添加了observed=True

import pandas as pd

group_cols = ['Group1', 'Group2', 'Group3']

df = pd.DataFrame([['A', 'B', 'C', 54.34],
                   ['A', 'B', 'D', 61.34],
                   ['B', 'A', 'C', 514.5],
                   ['B', 'A', 'A', 765.4],
                   ['A', 'B', 'D', 765.4]],
                  columns=(group_cols+['Value']))

for col in group_cols:
    df[col] = df[col].astype('category')

df.groupby(group_cols, as_index=False, observed=True).sum()

4xrmg8kj

4xrmg8kj2#

我找到了一个很好的解决方案。我会用一个更好的解释来编辑我的帖子。但同时,这个方案对你来说好吗?

import pandas as pd

group_cols = ['Group1', 'Group2', 'Group3']

df = pd.DataFrame([['A', 'B', 'C', 54.34],
                   ['A', 'B', 'D', 61.34],
                   ['B', 'A', 'C', 514.5],
                   ['B', 'A', 'A', 765.4],
                   ['A', 'B', 'D', 765.4]],
                  columns=(group_cols+['Value']))
for col in group_cols:
    df[col] = df[col].astype('category')

result = df.groupby([df[col].values.codes for col in group_cols]).sum()
result = result.reset_index()
level_to_column_name = {f"level_{i}":col for i,col in enumerate(group_cols)}
result = result.rename(columns=level_to_column_name)
for col in group_cols:
    result[col] = pd.Categorical.from_codes(result[col].values, categories=df[col].values.categories)
result

所以这个问题的答案感觉更像是一个适当的编程,而不是一个普通的Pandas问题。所有分类序列都是一堆索引到分类名称中的数字。我对这些底层数字执行了groupby操作,因为它们不存在与分类列相同的问题。完成此操作后,我必须重命名列。然后我使用from_代码构造函数来创建有效地将整数列表转换回分类列。

Group1  Group2  Group3  Value
A       B       C       54.34
A       B       D       826.74
B       A       A       765.40
B       A       C       514.50

我知道这不是你的答案,但我已经把我的解决方案变成了一个小函数,为将来遇到这个问题的人服务。

def categorical_groupby(df,group_cols,agg_fuction="sum"):
    "Does a groupby on a number of categorical columns"
    result = df.groupby([df[col].values.codes for col in group_cols]).agg(agg_fuction)
    result = result.reset_index()
    level_to_column_name = {f"level_{i}":col for i,col in enumerate(group_cols)}
    result = result.rename(columns=level_to_column_name)
    for col in group_cols:
        result[col] = pd.Categorical.from_codes(result[col].values, categories=df[col].values.categories)
    return result

这样称呼它:

df.pipe(categorical_groupby,group_cols)
iqjalb3h

iqjalb3h3#

我发现这种行为类似于分类数据的操作部分中记录的行为。
特别是,类似于

In [121]: cats2 = pd.Categorical(["a","a","b","b"], categories=["a","b","c"])

In [122]: df2 = pd.DataFrame({"cats":cats2,"B":["c","d","c","d"], "values":[1,2,3,4]})

In [123]: df2.groupby(["cats","B"]).mean()
Out[123]: 
        values
cats B        
a    c     1.0
     d     2.0
b    c     3.0
     d     4.0
c    c     NaN
     d     NaN

描述Seriesgroupby中相关行为的其他一些词。在本节的最后还有一个透视表示例。
除了Series. min()、Series. max()和Series. mode()之外,分类数据还可以执行以下操作:
Series. value_counts()等Series方法将使用所有类别,即使某些类别不存在于数据中:
Groupby还将显示"未使用"的类别:
这些词和例子引用自分类数据。

2ul0zpep

2ul0zpep4#

这里有很多问题需要回答。
让我们从理解什么是“类别”开始...

分类数据类型的定义

引用Pandas文件中的“分类数据”:
分类是Pandas数据类型,对应于统计中的分类变量:一个变量,它只能取有限的,通常是固定的,可能值的数量(***类别;例如性别、社会阶层、血型、国家归属、观察时间或通过李克特量表的等级。
这里我想重点谈两点:
1.分类值作为统计变量的定义:
基本上,这意味着我们必须从统计学的Angular 来看待它们,而不是从“常规”编程的Angular 来看待它们。也就是说,它们不是“枚举”。统计分类变量有特定的操作和用例,你可以在wikipedia中阅读更多关于它们的信息。
在第二点之后,我将详细讨论这一点。
1.类别是R中的级别:
如果我们了解R水平和因子,我们就能更好地理解分类。
我对R了解不多,但我发现this source简单而足够,下面引用一个有趣的例子:

When a factor is first created, all of its levels are stored along with the factor, and if subsets of the factor are extracted, they will retain all of the original levels. This can create problems when constructing model matrices and may or may not be useful when displaying the data using, say, the table function. As an example, consider a random sample from the letters vector, which is part of the base R distribution.

> lets = sample(letters,size=100,replace=TRUE)
> lets = factor(lets)
> table(lets[1:5])

a b c d e f g h i j k l m n o p q r s t u v w x y z
1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 0 1

Even though only five of the levels were actually represented, the table function shows the frequencies for all of the levels of the original factors. To change this, we can simply use another call to factor

> table(factor(lets[1:5]))

a k q s z
1 1 1 1 1

基本上,这告诉我们,显示/使用所有类别,即使它们是不需要的,并不罕见,实际上,这是默认的行为!
这是由于分类变量在统计学中的常见使用情况,几乎在所有情况下,你***都***关心所有的分类,即使它们没有被使用,以Pandas函数cut为例。
我希望你现在已经明白了为什么Pandas会有这种行为。
分类变量上的## GroupBy
至于为什么groupby考虑所有类别的组合:我不能肯定,但我最好的猜测是,基于对源代码的快速审查(和你提到的github问题),他们认为分类变量的groupby是它们之间的相互作用。因此,它应该考虑所有的对/元组(像笛卡尔乘积)。AFAIK,当你试图做像ANOVA这样的事情时,这很有帮助。
这也意味着,在这种情况下,您不能用通常的类似SQL的术语来理解它。

解决方案?

好吧,但如果你不想这样呢?
据我所知,并考虑到我昨晚花了一个晚上在Pandas源代码中跟踪这个,你不能“禁用”它,它在每个关键步骤都是硬编码的。
然而,由于groupby的工作方式,实际的“扩展”只有在需要时才会发生,例如,在组上调用sum或试图打印它们时。
因此,您可以执行以下任一操作以仅获取所需的组:

df.groupby(group_cols).indices
#{('A', 'B', 'C'): array([0]),
# ('A', 'B', 'D'): array([1, 4]),
# ('B', 'A', 'A'): array([3]),
# ('B', 'A', 'C'): array([2])}

df.groupby(group_cols).groups
#{('A', 'B', 'C'): Int64Index([0], dtype='int64'),
# ('A', 'B', 'D'): Int64Index([1, 4], dtype='int64'),
# ('B', 'A', 'A'): Int64Index([3], dtype='int64'),
# ('B', 'A', 'C'): Int64Index([2], dtype='int64')}

# an example
for g in df.groupby(group_cols).groups:
    print(g, grt.get_group(g).sum()[0])
#('A', 'B', 'C') 54.34
#('A', 'B', 'D') 826.74
#('B', 'A', 'A') 765.4
#('B', 'A', 'C') 514.5

我知道这对你来说是不可能的,但我有99%的把握,没有直接的方法来做到这一点。
我同意应该有一个布尔变量来禁用这种行为,并使用“常规的”类似SQL的变量。

wpcxdonn

wpcxdonn5#

我在调试类似的东西时发现了这个帖子。非常好的帖子,而且我真的很喜欢包含边界条件!
下面是实现初始目标的代码:

r = df.groupby(group_cols, as_index=False).agg({'Value': 'sum'})

r.columns = ['_'.join(col).strip('_') for col in r.columns]

这种解决方案的缺点是,它会导致一个分层的列索引,您可能希望将其扁平化(特别是在您有多个统计数据的情况下)。
我不知道为什么示例方法:

df.groupby(group_cols).sum() 
df.groupby(group_cols).mean()
df.groupby(group_cols).stdev()

使用分类变量的所有唯一组合,而.agg()方法:

df.groupby(group_cols).agg(['count', 'sum', 'mean', 'std'])

忽略未使用的组级别组合。这似乎不一致。很高兴我们可以使用.agg()方法,而不必担心笛卡尔组合爆炸。
此外,我认为与笛卡尔积相比,唯一基数计数低得多是很常见的。想想所有数据包含“州”、“县”、“邮编”等列的情况...这些都是嵌套变量,许多数据集都有高度嵌套的变量。
在我们的例子中,分组变量的笛卡尔积和自然出现的组合之间的差异超过1000 x(起始数据集超过1,000,000行)。
因此,我会投票赞成将observed=True作为默认行为。

nwlqm0z1

nwlqm0z16#

使用分类数据类型的主要优点是:

  • 内存效率。数据存储为整数代码,其大小比字符串小,类别类型与对象类型或int类型数据相比,需要更少的内存来存储相同数量的数据。
  • 更快的处理。分类数据操作(如group by)通常比对象或int类型数据上的等效操作更快,因为它们可以在整数代码上执行,而整数代码比字符串更高效。

缺点是:

  • 按输出分组:groupby的输出是非常混乱的。根据你的分类值生成了一个lof的nan。
  • 同样的问题适用于滤波。
  • 类别类型连接问题:类别类型链接到值的字典,所以当你连接或合并时,你将有麻烦并且类别数据类型丢失。

您可以从这篇文章中获得更深入的信息:https://medium.com/gitconnected/pandas-category-type-pros-and-cons-1bcac1bdea71

相关问题