熊猫改变了列表的不一致行为

时间:2019-09-01 06:47:34

标签: python pandas transform pandas-groupby

我有一些示例代码可以正常工作:

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]

我曾考虑过可变性/不可变性(列表/元组),但是对于集合/冻结集,它是一致的。

问题是为什么它会以这种方式工作?

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]