Pandas: 新的列值基于匹配的多级列的条件

Pandas: New column value based on the matching multi-level column's conditions

我有以下包含多级列的数据框

In [1]: data = {('A', '10'):[1,3,0,1],
                ('A', '20'):[3,2,0,0],
                ('A', '30'):[0,0,3,0],
                ('B', '10'):[3,0,0,0],
                ('B', '20'):[0,5,0,0],
                ('B', '30'):[0,0,1,0],
                ('C', '10'):[0,0,0,2],
                ('C', '20'):[1,0,0,0],
                ('C', '30'):[0,0,0,0]
                }
        df = pd.DataFrame(data)
        df
Out[1]:
   A        B        C
  10 20 30 10 20 30 10 20 30
0  1  3  0  3  0  0  0  1  0
1  3  2  0  0  5  0  0  0  0
2  0  0  3  0  0  1  0  0  0
3  1  0  0  0  0  0  2  0  0

在新列中 results 我想 return 包含每个子集(即第二级列)的最大值的组合列名称

我想要的输出应该如下所示

Out[2]:
   A        B        C
  10 20 30 10 20 30 10 20 30      results
0  1  3  0  3  0  0  0  1  0  A20&B10&C20
1  3  2  0  0  5  0  0  0  0      A10&B20
2  0  0  3  0  0  1  0  0  0      A30&B30
3  1  0  0  0  0  0  2  0  0      A10&C10

比如第一行:

对于第 'A' 列,最大值在第 '20' 列下 & 对于 'B' 列,'10' 下只有 1 个值 & 对于 'C' 列,它也只是 '20' 下的一个值 & 所以结果将是 A20&B10&C20

编辑:将results栏中的“+”替换为“&”,显然我被误解了,你们认为我需要求和而我需要用分隔符分隔的列名

编辑2: 由于某些原因,下面@A.B 提供的解决方案对我不起作用。尽管它在他这边工作,并且针对 google colab.

上的示例数据

不知何故 .idxmax(skipna = True) 导致 ValueError: No axis named 1 for object type Series

我找到了一个解决方法,即在此步骤之前转置数据,然后再将其转置回去。

map_res = lambda x:  ",".join(list(filter(None,['' if isinstance(x[a], float) else (x[a][0]+x[a][1]) for a in x.keys()])))

df['results'] = df.replace(0, np.nan)\
                  .T\  # Transpose here
                  .groupby(level=0)\  # Remove (axis=1) from here
                  .idxmax(skipna = True)\
                  .T\  # Transpose back here
                  .apply(map_res,axis=1)

我仍然想知道为什么没有转置它就不能工作?

尝试:

df["results"] = df.groupby(level=0, axis=1).max().sum(1)
print(df)

打印:

   A        B        C       results
  10 20 30 10 20 30 10 20 30        
0  1  3  0  3  0  0  0  1  0       7
1  3  2  0  0  5  0  0  0  0       8
2  0  0  3  0  0  1  0  0  0       4
3  1  0  0  0  0  0  2  0  0       3
  • 按级别 0 和轴=1 分组

  • 您使用 idxmax 将最大子级索引作为元组获取(同时跳过 NaN)。

  • 将函数应用于行 (axix-1) 以连接名称

  • 在函数(应用于行)中,迭代 keys/columns 并连接列级别。将 Nan(类型为 'float')替换为空字符串并稍后过滤它们。

如果您最初有 NaN 并让它们保留,则不需要 df.replace(0, np.nan)。

map_res = lambda x:  ",".join(list(filter(None,['' if isinstance(x[a], float) else (x[a][0]+x[a][1]) for a in x.keys()])))

df['results'] = df.replace(0, np.nan)\
                  .groupby(level=0, axis=1)\
                  .idxmax(skipna = True)\
                  .apply(map_res,axis=1)

这是输出

    A       B           C               results
10  20  30  10  20  30  10  20  30  
0   1   3   0   3   0   0   0   1   0   A20,B10,C20
1   3   2   0   0   5   0   0   0   0   A10,B20
2   0   0   3   0   0   1   0   0   0   A30,B30
3   1   0   0   0   0   0   2   0   0   A10,C10

想法是将 0 替换为 NaN,因此如果使用 DataFrame.stack all rows with NaNs are removed. Then get indices by DataFrameGroupBy.idxmax,则将第二个和第三个元组值映射为 map 并将 join 聚合到新的每个索引的列 - 第一级:

df['results'] = (df.replace(0, np.nan)
                   .stack([0,1])
                   .groupby(level=[0,1])
                   .idxmax()
                   .map(lambda x: f'{x[1]}{x[2]}')
                   .groupby(level=0)
                   .agg('&'.join))
print (df)
   A        B        C            results
  10 20 30 10 20 30 10 20 30             
0  1  3  0  3  0  0  0  1  0  A20&B10&C20
1  3  2  0  0  5  0  0  0  0      A10&B20
2  0  0  3  0  0  1  0  0  0      A30&B30
3  1  0  0  0  0  0  2  0  0      A10&C10