【问题标题】:Merge pandas DataFrame columns starting with the same letters合并以相同字母开头的 pandas DataFrame 列
【发布时间】:2019-10-22 13:36:12
【问题描述】:

假设我有一个DataFrame

>>> df = pd.DataFrame({'a1':[1,2],'a2':[3,4],'b1':[5,6],'b2':[7,8],'c':[9,0]})
>>> df
   a1  a2  b1  b2  c
0   1   3   5   7  9
1   2   4   6   8  0
>>> 

我想合并(可能不是合并,而是连接)名称的第一个字母相等的列,例如a1a2 等等......但正如我们所见,有一个@987654325 @ 列本身没有任何其他类似的列,因此我希望它们不要抛出错误,而是将NaNs 添加到它们。

我想以一种将宽 DataFrame 更改为长 DataFrame 的方式进行合并,基本上就像从宽到长的修改一样。

我已经有了解决问题的方法,但唯一的问题是它的效率非常低,我想要一个更高效、更快的解决方案(不像我的:P),我目前有一个for 循环和一个try except(呃,听起来已经很糟糕了)代码如:

>>> df2 = pd.DataFrame()
>>> for i in df.columns.str[:1].unique():
    try:
        df2[i] = df[[x for x in df.columns if x[:1] == i]].values.flatten()
    except:
        l = df[[x for x in df.columns if x[:1] == i]].values.flatten().tolist()
        df2[i] = l + [pd.np.nan] * (len(df2) - len(l))


>>> df2
   a  b    c
0  1  5  9.0
1  3  7  0.0
2  2  6  NaN
3  4  8  NaN
>>> 

我想用更好的代码获得同样的结果。

【问题讨论】:

  • 为什么c=0。与新数据帧中的 (a, b) = (3, 7) 对齐,而在原始数据帧中它们之间没有连接?
  • @GZ0 我的回答给出了准确的输出。 3,7 水平第二,0 垂直第二。
  • 我知道。我只是质疑输出是否真的需要。在我看来,对齐 c=0 会更合理。与 (a, b) = (2, 6)

标签: python pandas dataframe merge


【解决方案1】:

我推荐melt,然后是pivot。要解决重复问题,您需要以 cumcounted 列为轴。

u = df.melt()
u['variable'] = u['variable'].str[0]  # extract the first letter
u.assign(count=u.groupby('variable').cumcount()).pivot('count', 'variable', 'value')

variable    a    b    c
count                  
0         1.0  5.0  9.0
1         2.0  6.0  0.0
2         3.0  7.0  NaN
3         4.0  8.0  NaN

这可以重写为,

u = df.melt()
u['variable'] = [x[0] for x in u['variable']]
u.insert(0, 'count', u.groupby('variable').cumcount())

u.pivot(*u)

variable    a    b    c
count                  
0         1.0  5.0  9.0
1         2.0  6.0  0.0
2         3.0  7.0  NaN
3         4.0  8.0  NaN

如果性能很重要,这里是pd.concat 的替代方案:

from operator import itemgetter

pd.concat({
    k: pd.Series(g.values.ravel()) 
    for k, g in df.groupby(operator.itemgetter(0), axis=1)
}, axis=1)

   a  b    c
0  1  5  9.0
1  3  7  0.0
2  2  6  NaN
3  4  8  NaN

【讨论】:

  • 输出与 OP 的预期略有不同,感谢@GZ0 的评论。但我想这并不重要。
  • @QuangHoang 是的,我认为相对顺序对 OP 来说并不重要,但他们可能想澄清一下。
【解决方案2】:

使用字典理解:

df = pd.DataFrame({i: pd.Series(x.to_numpy().ravel()) 
                      for i, x in df.groupby(lambda x: x[0], axis=1)})
print (df)
   a  b    c
0  1  5  9.0
1  3  7  0.0
2  2  6  NaN
3  4  8  NaN

【讨论】:

    【解决方案3】:

    此解决方案给出了与 cs95 类似的答案,并且速度快了两到三倍。

    grouping = df.columns.map(lambda s: int(s[1:]) if len(s) > 1 else 1)
    df.columns = df.columns.str[0]   # Make a copy if the original dataframe needs to be retained
    result = pd.concat((g for _, g in df.groupby(grouping, axis=1)), 
                       axis=0, ignore_index=True, sort=False)
    

    输出

        a   b   c
    0   1   5   9.0
    1   2   6   0.0
    2   3   7   NaN
    3   4   8   NaN
    

    【讨论】:

    • 哇,是最快的
    • 好吧,jezrael 用毫秒击败了你:P
    • 谢谢。在我的测试环境中,即使我复制了原始数据,我的解决方案也比他的稍快。如果我不制作副本,它会快 50%。
    • 你可能想看看我在问题陈述下的评论,看看你是否想添加一些关于所需输出的说明。
    • @U9-Forward - 答案错误,检查第一列 - 顺序不同。 1,2,3,4,但需要1,3,2,4
    【解决方案4】:

    pd.concatpd.meltpd.groupby 一起使用:

    pd.concat([d.T.melt(value_name=k)[k] for k, d in df.groupby(df.columns.str[0], 1)], 1)
    

    输出:

       a  b    c
    0  1  5  9.0
    1  3  7  0.0
    2  2  6  NaN
    3  4  8  NaN
    

    【讨论】:

      【解决方案5】:

      使用renamegroupby.apply

      df = (df.rename(columns = dict(zip(df.columns, df.columns.str[:1])))
              .groupby(level=0, axis=1, group_keys=False)
              .apply(lambda x: pd.DataFrame(x.values.flat, columns=np.unique(x.columns))))
      
      print(df)
         a  b    c
      0  1  5  9.0
      1  3  7  0.0
      2  2  6  NaN
      3  4  8  NaN
      

      【讨论】:

        【解决方案6】:

        我知道这不如使用 melt ,但由于它是单行的,如果您确实需要更快的解决方案,请尝试 cs95 的解决方案

        df.groupby(df.columns.str[0],1).agg(lambda x : x.tolist()).sum().apply(pd.Series).T
        Out[391]: 
             a    b    c
        0  1.0  5.0  9.0
        1  3.0  7.0  0.0
        2  2.0  6.0  NaN
        3  4.0  8.0  NaN
        

        【讨论】:

          【解决方案7】:

          我们可以试试 groupby 列 (axis=1):

          def f(g,a):
              ret = g.stack().reset_index(drop=True)
              ret.name = a
              return ret
          
          pd.concat( (f(g,a) for a,g in df.groupby(df.columns.str[0], axis=1)), axis=1)
          

          输出:

              a   b   c
          0   1   5   9.0
          1   3   7   0.0
          2   2   6   NaN
          3   4   8   NaN
          

          【讨论】:

            猜你喜欢
            • 2014-01-18
            • 2014-09-28
            • 2021-11-02
            • 2020-01-02
            • 1970-01-01
            • 2021-10-12
            • 2020-12-28
            • 2016-02-19
            • 2017-09-02
            相关资源
            最近更新 更多