我有一些示例代码可以正常工作:
import pandas as pd
df = pd.DataFrame(data={'label': ['a', 'b', 'b', 'c'], 'wave': [1, 2, 3, 4], 'y': [0,0,0,0]})
df['new'] = df.groupby(['label'])[['wave']].transform(tuple)
结果是:
label wave y new
0 a 1 0 (1,)
1 b 2 0 (2, 3)
2 b 3 0 (2, 3)
3 c 4 0 (4,)
它可以类似地工作,如果我在转换中使用tuple
而不是set, frozenset, dict
,但是如果我使用list
,则会得到完全出乎意料的结果:
df['new'] = df.groupby(['label'])[['wave']].transform(list)
label wave y new
0 a 1 0 1
1 b 2 0 2
2 b 3 0 3
3 c 4 0 4
有一种变通方法以获得预期结果:
df['new'] = df.groupby(['label'])[['wave']].transform(tuple)['wave'].apply(list)
label wave y new
0 a 1 0 [1]
1 b 2 0 [2, 3]
2 b 3 0 [2, 3]
3 c 4 0 [4]
我曾考虑过可变性/不可变性(列表/元组),但是对于集合/冻结集,它是一致的。
问题是为什么它会以这种方式工作?
答案 0 :(得分:6)
我之前也遇到过类似的问题。我认为潜在的问题是,当列表中的元素数量与组中的记录数量匹配时,它将尝试解压缩列表,以便列表中的每个元素都映射到组中的记录。
例如,这将导致列表解包,因为列表的len与每个组的长度匹配:
df.groupby(['label'])[['wave']].transform(lambda x: list(x))
wave
0 1
1 2
2 3
3 4
但是,如果列表的长度与每个组的长度都不相同,则您将获得所需的行为:
df.groupby(['label'])[['wave']].transform(lambda x: list(x)+[0])
wave
0 [1, 0]
1 [2, 3, 0]
2 [2, 3, 0]
3 [4, 0]
我认为这是列表拆包功能的副作用。
答案 1 :(得分:3)
我认为这是熊猫中的虫子。您可以在their github页上打开票证吗?
起初我以为可能是因为list
只是作为.transform
的参数而没有正确处理,但是如果我这样做的话:
def create_list(obj):
print(type(obj))
return obj.to_list()
df.groupby(['label'])[['wave']].transform(create_list)
我得到同样的意外结果。但是,如果使用agg
方法,它将直接起作用:
df.groupby(['label'])['wave'].agg(list)
Out[179]:
label
a [1]
b [2, 3]
c [4]
Name: wave, dtype: object
我无法想象这是预期的行为。
顺便说一句。我还发现,如果将元组应用于分组的序列和分组的数据框,则会显示不同的行为。例如。如果将transform
应用于序列而不是DataFrame,则结果也不是包含列表的序列,而是包含ints
的序列(记住[['wave']]
会创建一个单列数据帧transform(tuple)
确实返回了元组):
df.groupby(['label'])['wave'].transform(tuple)
Out[177]:
0 1
1 2
2 3
3 4
Name: wave, dtype: int64
如果我再次使用agg
而不是transform
进行此操作,它将同时适用于['wave']
和[['wave']]
我在ubuntu X86_64系统上使用的是0.25.0版进行测试。
答案 2 :(得分:3)
由于DataFrames
主要用于处理2D数据,包括数组而不是标量值可能会偶然遇到这样的警告。
pd.DataFrame.trasnform
最初是在.agg
之上实现的:
# pandas/core/generic.py
@Appender(_shared_docs["transform"] % dict(axis="", **_shared_doc_kwargs))
def transform(self, func, *args, **kwargs):
result = self.agg(func, *args, **kwargs)
if is_scalar(result) or len(result) != len(self):
raise ValueError("transforms cannot produce " "aggregated results")
return result
但是,transform
总是返回一个数据帧,该数据帧必须与self长度相同,本质上就是输入。
在.agg
上执行DataFrame
函数时,它可以正常工作:
df.groupby('label')['wave'].agg(list)
label
a [1]
b [2, 3]
c [4]
Name: wave, dtype: object
当transform
尝试返回相同长度的Series
时,就会出现此问题。
在转换groupby
的一个切片self
元素然后再次进行串联的过程中,列表被解压缩为与@Allen相同的索引长度。
但是,当它们不对齐时,就不要拆开包装:
df.groupby(['label'])[['wave']].transform(lambda x: list(x) + [1])
wave
0 [1, 1]
1 [2, 3, 1]
2 [2, 3, 1]
3 [4, 1]
解决此问题的方法可能是避免使用transform
:
df = pd.DataFrame(data={'label': ['a', 'b', 'b', 'c'], 'wave': [1, 2, 3, 4], 'y': [0,0,0,0]})
df = df.merge(df.groupby('label')['wave'].agg(list).rename('new'), on='label')
df
label wave y new
0 a 1 0 [1]
1 b 2 0 [2, 3]
2 b 3 0 [2, 3]
3 c 4 0 [4]
答案 3 :(得分:1)
建议的答案不再适用于 Pandas 1.2.4。这是一个解决方法:
df.groupby(['label'])[['wave']].transform(lambda x: [list(x) + [1]]*len(x))
其背后的想法与其他答案中的解释相同(例如@Allen 的答案)。因此,这里的解决方案是将函数包装到另一个列表中,并重复与组长度相同的数量,以便pandas变换解包时,每一行都得到内部列表。
输出:
wave
0 [1, 1]
1 [2, 3, 1]
2 [2, 3, 1]
3 [4, 1]