Pandas groupby应用特定功能进行转换

时间:2018-06-28 09:39:38

标签: python pandas dataframe pandas-groupby

我不知道groupby + transform操作可以接受哪些功能。通常,我最终只能进行猜测,测试,还原,直到某些方法可行为止,但是我认为应该有一种系统的方法来确定解决方案是否可行。

这是一个最小的例子。首先,将groupbyapplyset一起使用:

df = pd.DataFrame({'a': [1,2,3,1,2,3,3], 'b':[1,2,3,1,2,3,3], 'type':[1,0,1,0,1,0,1]})

g = df.groupby(['a', 'b'])['type'].apply(set)

print(g)

a  b
1  1    {0, 1}
2  2    {0, 1}
3  3    {0, 1}

这很好,但是我希望在原始数据帧的新列中按组计算结果set。因此,我尝试使用transform

df['g'] = df.groupby(['a', 'b'])['type'].transform(set)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
---> 23 df['g'] = df.groupby(['a', 'b'])['type'].transform(set)

TypeError: int() argument must be a string, a bytes-like object or a number, not 'set'

这是我在Pandas v0.19.0中看到的错误。在v0.23.0中,我看到了TypeError: 'set' type is unordered。当然,我可以映射一个专门定义的索引来实现我的结果:

g = df.groupby(['a', 'b'])['type'].apply(set)
df['g'] = df.set_index(['a', 'b']).index.map(g.get)

print(df)

   a  b  type       g
0  1  1     1  {0, 1}
1  2  2     0  {0, 1}
2  3  3     1  {0, 1}
3  1  1     0  {0, 1}
4  2  2     1  {0, 1}
5  3  3     0  {0, 1}
6  3  3     1  {0, 1}

但是我认为transform的好处是避免了这样的显式映射。我哪里出错了?

2 个答案:

答案 0 :(得分:10)

首先,我认为使用这些功能可能会很直观,因为它们可能非常有意义。

在您的第一个结果中,您实际上并不是在尝试转换您的值,而是汇总(这将按照您的预期方式工作)。

但是进入代码后,transform文档在暗示这一点上很有启发性

  

返回与组块大小相同或可广播到组块大小的结果。

完成时

df.groupby(['a', 'b'])['type'].transform(some_func)

您实际上是在使用pd.Series函数将每个组中的每个some_func对象转换为一个新对象。但事实是,此新对象的大小应与组 OR 相同,并且可以广播到块的大小。

因此,如果您使用tuplelist变换系列,则基本上是在变换对象

0    1
1    2
2    3
dtype: int64

进入

[1,2,3]

但是请注意,这些值现在被分配回到它们各自的索引,这就是为什么您看不到transform操作的区别。现在,具有.iloc[0]的{​​{1}}值的行将具有来自转换列表的pd.Series值(这同样适用于元组)等。请注意 ordering size 在这里很重要,因为否则您可能会弄乱您的组,并且转换将不起作用(这正是[1,2,3][0]不适合使用的功能的原因,因为情况)。


引用文字的第二部分说“可以广播到组块的大小”。

这意味着您还可以将set转换为可以在所有行中使用的对象。例如

pd.Series

会工作。为什么?即使df.groupby(['a', 'b'])['type'].transform(lambda k: 50) 不可迭代,也可以通过在初始50的所有位置重复使用此值来广播


您为什么pd.Series使用set?

因为apply方法在结果中没有 size 的约束。它实际上具有三种不同的结果类型,并且可以推断您是想要扩展结果还是 expand reduce broadcast 。注意,您不能减少进行转换*

  

默认情况下(apply),从应用函数的返回类型推断出最终的返回类型。   result_type:{“展开”,“减少”,“广播”,“无”,默认为“无”   这些仅在result_type=None(列)时起作用:

     
      
  1. “展开”:类似列表的结果将变成列。

  2.   
  3. “ reduce”:如果可能,返回一个Series而不是像列表一样展开   结果。这与“展开”相反。

  4.   
  5. “广播”:结果将以DataFrame的原始形状进行广播,原始索引和列将保留。

  6.   

答案 1 :(得分:3)

转换的结果仅限于某些类型。 [例如,不能为listsetSeries等-这不正确,谢谢@RafaelC的评论] 我认为这没有记录,但是在检查groupby.pyseries.py的源代码时,您会发现这些类型限制。

groupby documentation

  

transform方法返回一个索引的对象与被分组的对象相同(相同大小)。转换函数必须:

     
      
  • 返回与组块大小相同或可广播到组块大小的结果(例如,标量,grouped.transform(lambda x:x.iloc [- 1]))。
  •   
  • 在组块上逐列操作。使用chunk.apply将转换应用于第一组块。

  •   
  • 不对组块执行就地操作。组块应视为不可变的,对组块的更改可能会产生意外的结果。例如,使用fillna时,inplace必须为False(grouped.transform(lambda x:x.fillna(inplace = False)))。

  •   
  • (可选)在整个组块上运行。如果支持,则从第二个块开始使用快速路径。

  •   

免责声明:我遇到了其他错误(pandas版本0.23.1):

df['g'] = df.groupby(['a', 'b'])['type'].transform(set)
File "***/lib/python3.6/site-packages/pandas/core/groupby/groupby.py", line 3661, in transform
s = klass(res, indexer)        s = klass(res, indexer)
File "***/lib/python3.6/site-packages/pandas/core/series.py", line 242, in __init__
"".format(data.__class__.__name__))
TypeError: 'set' type is unordered

更新

将组转换成集合后,pandas无法将其广播到Series,因为它是无序的(并且尺寸与组块不同)。如果我们将其强制放入列表中,它将变成与组块相同的大小,并且每行仅获得一个值。答案是将其包装在某个容器中,这样对象的结果大小将变为1,然后pandas将能够广播它:

df['g'] = df.groupby(['a', 'b'])['type'].transform(lambda x: np.array(set(x)))
print(df)

   a  b  type       g
0  1  1     1  {0, 1}
1  2  2     0  {0, 1}
2  3  3     1  {0, 1}
3  1  1     0  {0, 1}
4  2  2     1  {0, 1}
5  3  3     0  {0, 1}
6  3  3     1  {0, 1}

为什么我选择np.array作为容器?因为series.py(第205:206行)无需进一步检查即可通过此类型。因此,我相信这种行为将在以后的版本中保留。