【问题标题】:Pandas add new second level column to column multiindex based on other columnsPandas 基于其他列向列多索引添加新的二级列
【发布时间】:2017-10-06 17:23:33
【问题描述】:

我有一个列多索引的 DataFrame:

System   A                B
Trial    Exp1    Exp2     Exp1    Exp2
1        NaN     1        2       3
2        4       5        NaN     NaN
3        6       NaN      7       8

结果对于每个系统 (A, B) 和每个测量值 (1, 2, 3 in index),Exp1 的结果总是优于 Exp2。因此,我想为每个系统生成第三列,将其命名为Final,只要可用,它就应该采用Exp1,否则默认为Exp2。想要的结果是

System   A                       B
Trial    Exp1    Exp2    Final   Exp1    Exp2    Final
1        NaN     1       1       2       3       2
2        4       5       4       NaN     NaN     NaN
3        6       NaN     6       7       8       7

最好的方法是什么?

我尝试在列上使用groupby:

grp = df.groupby(level=0, axis=1)

并且正在考虑使用transform 或apply 结合assign 来实现它。但是我无法找到一种可行的或有效的方法。具体来说,出于效率原因,我避免使用原生 python for 循环(否则问题很简单)。

【问题讨论】:

    标签: pandas dataframe multi-index


    【解决方案1】:

    使用stack 进行整形,使用fillna 添加列,然后通过unstack 使用swaplevel + sort_index 进行整形:

    df = df.stack(level=0)
    df['Final'] = df['Exp1'].fillna(df['Exp1'])
    df = df.unstack().swaplevel(0,1,axis=1).sort_index(axis=1)
    print (df)
    System    A               B           
    Trial  Exp1 Exp2 Final Exp1 Exp2 Final
    1       NaN  1.0   NaN  2.0  3.0   2.0
    2       4.0  5.0   4.0  NaN  NaN   NaN
    3       6.0  NaN   6.0  7.0  8.0   7.0
    

    另一个使用xs 选择DataFrames 的解决方案,由combine_first 创建新的DataFrame,但缺少第二级-由MultiIndex.from_product 和最后一个concat 添加DataFrames 一起:

    a = df.xs('Exp1', axis=1, level=1)
    b = df.xs('Exp2', axis=1, level=1)
    df1 =  a.combine_first(b)
    df1.columns = pd.MultiIndex.from_product([df1.columns, ['Final']])
    df = pd.concat([df, df1], axis=1).sort_index(axis=1)
    print (df)
    System    A               B           
    Trial  Exp1 Exp2 Final Exp1 Exp2 Final
    1       NaN  1.0   1.0  2.0  3.0   2.0
    2       4.0  5.0   4.0  NaN  NaN   NaN
    3       6.0  NaN   6.0  7.0  8.0   7.0
    

    与rename类似的解决方案:

    a = df.xs('Exp1', axis=1, level=1, drop_level=False)
    b = df.xs('Exp2', axis=1, level=1, drop_level=False)
    df1 = a.rename(columns={'Exp1':'Final'}).combine_first(b.rename(columns={'Exp2':'Final'}))
    df = pd.concat([df, df1], axis=1).sort_index(axis=1)
    print (df)
    System    A               B           
    Trial  Exp1 Exp2 Final Exp1 Exp2 Final
    1       NaN  1.0   1.0  2.0  3.0   2.0
    2       4.0  5.0   4.0  NaN  NaN   NaN
    3       6.0  NaN   6.0  7.0  8.0   7.0
    

    【讨论】:

    • 这真是天才!谢谢!
    • 很高兴能帮上忙,我在第二个添加解释。
    【解决方案2】:
    • stack 与您的第一级列索引 stack(0) 将 ['Exp1', 'Exp2'] 留在列索引中
    • 使用lambda 函数,该函数在assign 调用中应用于整个数据帧。
    • 最后,unstack、swaplevel、sort_index 将其清理干净,并将所有东西放在它所属的地方。

    f = lambda x: x.Exp1.fillna(x.Exp2)
    df.stack(0).assign(Final=f).unstack() \
        .swaplevel(0, 1, 1).sort_index(1)
    
         A               B           
      Exp1 Exp2 Final Exp1 Exp2 Final
    1  NaN  1.0   1.0  2.0  3.0   2.0
    2  4.0  5.0   4.0  NaN  NaN   NaN
    3  6.0  NaN   6.0  7.0  8.0   7.0
    

    另一个使用xs的概念

    d1 = df.xs('Exp1', 1, 1).fillna(df.xs('Exp2', 1, 1))
    d1.columns = [d1.columns, ['Final'] * len(d1.columns)]
    pd.concat([df, d1], axis=1).sort_index(1)
    
    
         A               B           
      Exp1 Exp2 Final Exp1 Exp2 Final
    1  NaN  1.0   1.0  2.0  3.0   2.0
    2  4.0  5.0   4.0  NaN  NaN   NaN
    3  6.0  NaN   6.0  7.0  8.0   7.0
    

    【讨论】:

    • 谢谢!我不得不选择一个答案……对不起。我非常喜欢你的 assign() !真的很感激。
    【解决方案3】:

    感觉不是超级理想,但试试这个:

    for system in df.columns.levels[0]:
        df[(system, 'final')] = df[(system, 'Exp1')].fillna(df[(system, 'Exp2')])
    

    【讨论】:

    • 谢谢@Steven,虽然我忘了提到我可以使用循环来完成这个。但我试图明确回避for 语句,因为它超出了 Pandas 的目的......
    • 不要忘记 sort_index,因为新列将附加到最后。
    猜你喜欢
    • 2021-10-15
    • 1970-01-01
    • 2018-06-10
    • 2017-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-30
    相关资源
    最近更新 更多