我在使用pandas groupby分类数据时遇到问题。从理论上讲,它应该是超级高效的:您通过整数而不是字符串进行分组和索引。但它坚持认为,在按多个类别进行分组时,必须考虑 的每个组合
。即使普通字符串密度较低,我有时会使用类别,因为这些字符串很长,可以节省内存/提高性能。有时每列中有数千个类别。当按3列分组时,pandas
会强制我们保存1000 ^ 3组的结果。
我的问题:是否有方便的方法将groupby
与类别一起使用,同时避免这种不良行为?我不是在寻找任何这些解决方案:
numpy
重新创建所有功能。groupby
之前继续转换为字符串/代码,稍后再恢复为类别。我希望有一种方法可以修改这个特定的pandas
特质。一个简单的例子如下。而不是我想要的4个类别,我最终得到12。
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)。因此,我正在寻找解决以下任何问题的回复:
Bounty update#2
要明确的是,我没有期待上述所有4个问题的答案。我要问的主要问题是,覆盖pandas
库方法是否可行或不可取,以便以便于groupby
/ set_index
操作的方式处理类别。
答案 0 :(得分:7)
由于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()
答案 1 :(得分: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')
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问题。在引擎盖下,所有分类系列只是一组数字,索引到类别的名称。我对这些基础数字进行了分组,因为它们与分类列没有相同的问题。这样做后,我不得不重命名列。然后我使用from_codes构造函数创建有效的将整数列表转换回分类列。
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)
答案 2 :(得分:4)
我发现行为类似于Categorical Data的操作部分中记录的行为。
特别类似于
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
描述Series
和groupby
中相关行为的其他一些词语。该部分的末尾还有一个数据透视表示例。
除了Series.min(),Series.max()和Series.mode()之外,还有以下内容 可以使用分类数据进行操作:
Series.value_counts()等系列方法将使用所有类别, 即使数据中没有某些类别:
Groupby还会显示“未使用”的类别:
单词和示例来自Categorical Data。
答案 3 :(得分:3)
这里有一个很多的问题需要解答
让我们首先了解“类别”是什么......
从pandas docs引用“分类数据”:
分类是一种熊猫数据类型, 对应于统计中的分类变量 :一个变量,它只能采用有限的,通常是固定的数量值( 类别; R 中的级别)。例如性别,社会阶层,血型,国家隶属关系,观察时间或通过李克特量表评级。
我想在此重点关注两点:
将分类定义为统计变量:
基本上,这意味着我们必须从统计的角度来看待它们,而不是“常规”编程。即它们不是“枚举”。统计分类变量具有特定的操作和用例,您可以在wikipedia中阅读更多相关信息
在第二点之后我会详细谈论这个。
类别是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为例。
我希望在这一点上你明白为什么这种行为存在于熊猫中。
为什么groupby
会考虑所有类别的组合:我不能肯定地说,但基于对源代码(以及您提到的github问题)的快速回顾,我的最佳猜测是他们认为分类变量groupby
与interaction之间存在ANOVA。因此,它应该考虑所有对/元组(如笛卡尔积)。 AFAIK,当您尝试执行{{3}}之类的操作时,这会有很大帮助
这也意味着在这种情况下你不能用通常的类似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。
答案 4 :(得分:0)
我在调试类似内容时找到了这篇文章。很好的帖子,我真的很喜欢包含边界条件!
这是完成最初目标的代码:
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()方法,而不必担心笛卡尔组合爆炸。
此外,我认为与基数乘积相比,唯一基数少得多的情况很常见。考虑一下所有数据具有诸如“州”,“县”,“邮政编码”之类的列的情况……这些都是嵌套变量,并且那里的许多数据集都具有高度嵌套的变量。
在我们的案例中,分组变量的笛卡尔积与自然出现的组合之间的差超过1000x(并且起始数据集超过1,000,000行)。
因此,我本来会投票赞成将Observed = True设为默认行为。