【问题标题】:How to flatten/concatenate multiple columns with similar information based on one index column in pandas?如何根据熊猫中的一个索引列展平/连接具有相似信息的多列?
【发布时间】:2018-07-14 04:28:43
【问题描述】:

我有一个关于将数据框从一行中的多列中展平或折叠的问题,其中包含有关键的信息,每行具有相同的键列和适当的数据。假设一个数据框是这样的:

df = pd.DataFrame({'CODE': ['AA', 'BB', 'CC'],
              'START_1': ['1990-01-01', '2000-01-01', '2005-01-01'],
              'END_1': ['1990-02-14', '2000-03-01', '2005-12-31'],
              'MEANING_1': ['SOMETHING', 'OR', 'OTHER'],
              'START_2': ['1990-02-15', None, '2006-01-01'],
              'END_2': ['1990-06-14', None, '2006-12-31'],
              'MEANING_2': ['ELSE', None, 'ANOTHER']})
  CODE     START_1       END_1  MEANING_1     START_2       END_2 MEANING_2
0   AA  1990-01-01  1990-02-14  SOMETHING  1990-02-15  1990-06-14      ELSE
1   BB  2000-01-01  2000-03-01         OR        None        None      None
2   CC  2005-01-01  2005-12-31      OTHER  2006-01-01  2006-12-31   ANOTHER

我需要把它变成这样的形式:

  CODE       START         END    MEANING
0   AA  1990-01-01  1990-02-14  SOMETHING
1   AA  1990-02-15  1990-06-14       ELSE
2   BB  2000-01-01  2000-03-01         OR
3   CC  2005-01-01  2005-12-31      OTHER
4   CC  2006-01-01  2006-12-31    ANOTHER

我有一个解决方案如下:

df_a = df[['CODE', 'START_1', 'END_1', 'MEANING_1']]
df_b = df[['CODE', 'START_2', 'END_2', 'MEANING_2']]
df_a = df_a.rename(index=str, columns={'CODE': 'CODE',
                                'START_1': 'START',
                                'END_1': 'END',
                                'MEANING_1': 'MEANING'})
df_b = df_b.rename(index=str, columns={'CODE': 'CODE',
                                'START_2': 'START',
                                'END_2': 'END',
                                'MEANING_2': 'MEANING'})
df = pd.concat([df_a, df_b], ignore_index=True)
df = df.dropna(axis=0, how='any')

这会产生所需的结果。当然,如果您有超过 2 个需要折叠的列组(我的真实代码中实际上有 6 个),这似乎不是很pythonic,并且显然不理想。我已经检查了groupby()melt()stack() 方法,但还没有真正发现它们非常有用。任何建议将不胜感激。

【问题讨论】:

  • 实际上有一些方便的函数,称为 lreshape 和 wide_to_long,但它们都没有得到很好的支持。 lreshape 仍然是“实验性的”,wide_to_long 的参数名称很糟糕,并且有非常具体的输入要求。

标签: python pandas


【解决方案1】:

这就是melt 将实现的目标

df1=df.melt('CODE')

df1[['New','New2']]=df1.variable.str.split('_',expand=True)
df1.set_index(['CODE','New2','New']).value.unstack()
Out[492]: 
New               END    MEANING       START
CODE New2                                   
AA   1     1990-02-14  SOMETHING  1990-01-01
     2     1990-06-14       ELSE  1990-02-15
BB   1     2000-03-01         OR  2000-01-01
     2           None       None        None
CC   1     2005-12-31      OTHER  2005-01-01
     2     2006-12-31    ANOTHER  2006-01-01

【讨论】:

  • 文太棒了!我忘记了这个方法,我见过 MaxU 也使用这种方法。 +1
  • @ScottBoston 是的,但是 pd_wide to Long 是解决这类问题的正确方法,我还记得我是从 Ted Petrou 那里学来的 :-)
【解决方案2】:

使用pd.wide_to_long:

pd.wide_to_long(df, stubnames=['END', 'MEANING', 'START'],
                i='CODE', j='Number', sep='_', suffix='*')

输出:

                    END    MEANING       START
CODE Number                                   
AA   1       1990-02-14  SOMETHING  1990-01-01
BB   1       2000-03-01         OR  2000-01-01
CC   1       2005-12-31      OTHER  2005-01-01
AA   2       1990-06-14       ELSE  1990-02-15
BB   2             None       None        None
CC   2       2006-12-31    ANOTHER  2006-01-01

然后,如果您愿意,我们可以删除 Number 列/索引和 dropna,例如df.reset_index().drop('Number', 1).

【讨论】:

  • 赞成,从来不知道这存在,我还要添加df.reset_index().drop('Number', 1)
  • @jp_data_analysis 很棒的补充!谢谢你。是的,pd.wide_to_long 非常适合“同时”融化的情况。
  • 这就是我在 pandas 文档中寻找的内容。它似乎在引擎盖下使用了融化,所以我很接近但无法让它工作。你知道我会如何用 melt 完成同样的事情吗?
  • @DrPiranoid 添加融化方式 :-)
【解决方案3】:

这也应该有效。

# the following line get rid of _x suffix 
df = df.set_index("CODE")
df.columns = pd.Index(map(lambda x : str(x)[:-2], df.columns)
pd.concat([df.iloc[:, range(len(df.columns))[i::2]] for i in range(2)])

去除后缀的方法取自Remove last two characters from column names of all the columns in Dataframe - Pandas

应该很容易将该方法扩展到每组 2 列以上。像OP一样说6。

pd.concat([df.iloc[:, range(len(df.columns))[i::6]] for i in range(6)])

【讨论】:

  • @jp_data_analysis 现在修改为更通用。
【解决方案4】:

这是另一种方式:

df.columns = [i[0] for i in df.columns.str.split('_')]
df = df.T
cond = df.index.duplicated()
concat_df = pd.concat([df[~cond],df[cond]],axis=1).T
sort_df = concat_df.sort_values('START').iloc[:-1]
sort_df.CO = sort_df.CO.ffill()

【讨论】:

    【解决方案5】:

    这是一种方法。这和你的逻辑差不多,我只是优化了一点,清理了代码,所以你只需要维护common_colsvar_colsdata_count

    common_cols = ['CODE']
    var_cols = ['START', 'END', 'MEANING']
    data_count = 2
    
    dfs = {i: df[common_cols + [k+'_'+str(int(i)) for k in var_cols]].\
              rename(columns=lambda x: x.split('_')[0]) for i in range(1, data_count+1)}
    
    pd.concat(list(dfs.values()), ignore_index=True)
    
    #   CODE       START         END    MEANING
    # 0   AA  1990-01-01  1990-02-14  SOMETHING
    # 1   BB  2000-01-01  2000-03-01         OR
    # 2   CC  2005-01-01  2005-12-31      OTHER
    # 3   AA  1990-02-15  1990-06-14       ELSE
    # 4   BB        None        None       None
    # 5   CC  2006-01-01  2006-12-31    ANOTHER
    

    【讨论】:

    • 这个解决方案对我有用!我很惊讶没有适合该任务的 pandas 方法。我也很好奇我怎么能以不同的方式表达我的问题。我用一个例子来问什么是相当明显的,但是如果没有正确的词汇,寻找解决方案是非常困难的。
    • @DrPiranoid,我实际上认为你的问题措辞很好。可重现的代码帮助我们轻松测试解决方案。
    猜你喜欢
    • 1970-01-01
    • 2020-12-03
    • 2020-02-21
    • 1970-01-01
    • 2020-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-24
    相关资源
    最近更新 更多