我不知道groupby
+ transform
操作可以接受哪些功能。通常,我最终只能进行猜测,测试,还原,直到某些方法可行为止,但是我认为应该有一种系统的方法来确定解决方案是否可行。
这是一个最小的例子。首先,将groupby
和apply
与set
一起使用:
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
的好处是避免了这样的显式映射。我哪里出错了?
答案 0 :(得分:10)
首先,我认为使用这些功能可能会很直观,因为它们可能非常有意义。
在您的第一个结果中,您实际上并不是在尝试转换您的值,而是汇总(这将按照您的预期方式工作)。
但是进入代码后,transform
文档在暗示这一点上很有启发性
返回与组块大小相同或可广播到组块大小的结果。
完成时
df.groupby(['a', 'b'])['type'].transform(some_func)
您实际上是在使用pd.Series
函数将每个组中的每个some_func
对象转换为一个新对象。但事实是,此新对象的大小应与组 OR 相同,并且可以广播到块的大小。
因此,如果您使用tuple
或list
变换系列,则基本上是在变换对象
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
(列)时起作用:
“展开”:类似列表的结果将变成列。
“ reduce”:如果可能,返回一个Series而不是像列表一样展开 结果。这与“展开”相反。
“广播”:结果将以DataFrame的原始形状进行广播,原始索引和列将保留。
答案 1 :(得分:3)
转换的结果仅限于某些类型。 [例如,不能为list
,set
,Series
等-这不正确,谢谢@RafaelC的评论] 我认为这没有记录,但是在检查groupby.py
和series.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行)无需进一步检查即可通过此类型。因此,我相信这种行为将在以后的版本中保留。