Pandas Dataframe GroupBy Agg - LAMBDA - 单个值转到预先存在的或新的列表和预先存在的列表融合

Pandas Dataframe GroupBy Agg - LAMBDA - single values go to preexisting or new lists and preexisting lists fusion

我有这个要按键分组的 DataFrame:

df = pd.DataFrame({
                   'key': ['1', '1', '1', '2', '2', '3', '3', '4', '4', '5'],
                   'data1': [['A', 'B', 'C'], 'D', 'P', 'E', ['F', 'G', 'H'], ['I', 'J'], ['K', 'L'], 'M', 'N', 'O']
                   'data2': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
                 })
df

我想做groupby key和sum data2,这部分没问题。 但是关于data1,我想:

  1. 如果列表还不存在:
    • 当键没有被复制时,单个值不会改变
    • 分配给一个键的单个值被合并到一个新列表中
  2. 如果列表已经存在:
    • 附加其他单个值
    • 其他列表值附加到它

生成的 DataFrame 应该是:

dfgood = pd.DataFrame({
                   'key': ['1', '2', '3', '4', '5'],
                   'data1': [['A', 'B', 'C', 'D', 'P'], ['F', 'G', 'H', 'E'], ['I', 'J', 'K', 'L'], ['M', 'N'], 'O']
                   'data2': [6, 9, 13, 17, 10]
                 })
dfgood

事实上,我并不真正关心 data1 值在列表中的顺序,它也可以是将它们放在一起的任何结构,甚至是带分隔符的字符串或集合,如果它更容易制作的话走你认为最好的路。

我想到了两个解决方案:

  1. 往那边走:
dfgood = df.groupby('key', as_index=False).agg({
            'data1' : lambda x: x.iloc[0].append(x.iloc[1]) if type(x.iloc[0])==list else list(x),
            'data2' : sum,
            })
dfgood

由于 x.iloc[1] 中的 index out of range,它不起作用。 我也尝试过,因为 data1 在 :

问题的另一个组中是这样组织的
dfgood = df.groupby('key', as_index=False).agg({
            'data1' : lambda g: g.iloc[0] if len(g) == 1 else list(g)),
            'data2' : sum,
            })
dfgood

但它是根据预先存在的列表或值创建新列表,而不是将数据附加到现有列表。

  1. 另一种方法,但我认为它更复杂,应该有更好或更快的解决方案:
    • 使用 apply
    • 将 data1 列表和单个值转换为单独的系列
    • 使用 wide_to_long 为每个键保留单个值,
    • 然后分组应用:
dfgood = df.groupby('key', as_index=False).agg({
            'data1' : lambda g: g.iloc[0] if len(g) == 1 else list(g)),
            'data2' : sum,
            })
dfgood

我认为我的问题是我不知道如何正确使用 lambda,所以我尝试了一些愚蠢的事情,例如上一个示例中的 x.iloc[1]。看了很多关于lambdas的教程,但脑子里还是很模糊

您可以 explode 获取单独的行,然后在注意屏蔽 data2 中的重复值(以避免对重复值求和)后再次与 groupby+agg 聚合:

(df.explode('data1')
   .assign(data2=lambda d: d['data2'].mask(d.duplicated(['key', 'data2']), 0))
   .groupby('key')
   .agg({'data1': list, 'data2': 'sum'})
)

输出:

               data1  data2
key                        
1    [A, B, C, D, P]      6
2       [E, F, G, H]      9
3       [I, J, K, L]     13
4             [M, N]     17
5                [O]     10

列表与标量存在问题组合,可能的解决方案是先创建标量列表,然后在 groupby.agg 中将它们展平:

dfgood = (df.assign(data1 = df['data1'].apply(lambda y: y if isinstance(y, list) else [y]))
            .groupby('key', as_index=False).agg({
            'data1' : lambda x: [z for y in x for z in y],
            'data2' : sum,
            })
            )
print (dfgood)
  key            data1  data2
0   1  [A, B, C, D, P]      6
1   2     [E, F, G, H]      9
2   3     [I, J, K, L]     13
3   4           [M, N]     17
4   5              [O]     10

另一个想法是使用 flatten 函数来展平列表,而不是字符串:

#
def flatten(foo):
    for x in foo:
        if hasattr(x, '__iter__') and not isinstance(x, str):
            for y in flatten(x):
                yield y
        else:
            yield x

dfgood = (df.groupby('key', as_index=False).agg({
            'data1' : lambda x: list(flatten(x)),
            'data2' : sum}))