【问题标题】:Expand pandas DataFrame column into multiple rows将 pandas DataFrame 列展开为多行
【发布时间】:2016-11-07 06:51:43
【问题描述】:

如果我有一个DataFrame 这样:

pd.DataFrame( {"name" : "John", 
               "days" : [[1, 3, 5, 7]]
              })

给出这个结构:

           days  name
0  [1, 3, 5, 7]  John

如何展开成如下?

   days  name
0     1  John
1     3  John
2     5  John
3     7  John

【问题讨论】:

  • 我不完全明白你为什么要这样做?是因为你有一个这样的字典,你想把它变成一个数据框吗?而且,在name 列中,您希望始终保持相同的值吗?

标签: python pandas


【解决方案1】:

您可以使用df.itertuples 遍历每一行,并使用列表推导将数据重塑为所需的形式:

import pandas as pd

df = pd.DataFrame( {"name" : ["John", "Eric"], 
               "days" : [[1, 3, 5, 7], [2,4]]})
result = pd.DataFrame([(d, tup.name) for tup in df.itertuples() for d in tup.days])
print(result)

产量

   0     1
0  1  John
1  3  John
2  5  John
3  7  John
4  2  Eric
5  4  Eric

Divakar's solutionusing_repeat,最快:

In [48]: %timeit using_repeat(df)
1000 loops, best of 3: 834 µs per loop

In [5]: %timeit using_itertuples(df)
100 loops, best of 3: 3.43 ms per loop

In [7]: %timeit using_apply(df)
1 loop, best of 3: 379 ms per loop

In [8]: %timeit using_append(df)
1 loop, best of 3: 3.59 s per loop

这是用于上述基准测试的设置:

import numpy as np
import pandas as pd

N = 10**3
df = pd.DataFrame( {"name" : np.random.choice(list('ABCD'), size=N), 
                    "days" : [np.random.randint(10, size=np.random.randint(5))
                              for i in range(N)]})

def using_itertuples(df):
    return  pd.DataFrame([(d, tup.name) for tup in df.itertuples() for d in tup.days])

def using_repeat(df):
    lens = [len(item) for item in df['days']]
    return pd.DataFrame( {"name" : np.repeat(df['name'].values,lens), 
                          "days" : np.concatenate(df['days'].values)})

def using_apply(df):
    return (df.apply(lambda x: pd.Series(x.days), axis=1)
            .stack()
            .reset_index(level=1, drop=1)
            .to_frame('day')
            .join(df['name']))

def using_append(df):
    df2 = pd.DataFrame(columns = df.columns)
    for i,r in df.iterrows():
        for e in r.days:
            new_r = r.copy()
            new_r.days = e
            df2 = df2.append(new_r)
    return df2

【讨论】:

  • 打扰您了,因为我刚刚修改了我的以将 np.concatenate 替换为 np.hstack。好像速度有点快。你介意用它更新时间吗? :)
  • @Divakar:我很惊讶。这是一个相当大的进步!
  • 感谢更新!好吧,我很惊讶地看到 np.hstack 比 np.concatenate 快,因为我的印象是所有这些 hstack 和 vstack 都是从 np.concatenate 派生的。也许有了熊猫,它正在做优化?不确定!
  • np.concatenate(df['days'].values) 似乎比np.hstack(df['days']) 还要快。
  • 啊,这很有趣,我想有点道理。所以通过 obj dtype 的 NumPy 数组的路线比较好!嗯。
【解决方案2】:

自熊猫 0.25 以来的新功能,您可以使用函数 explode()

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html

import pandas as pd
df = pd.DataFrame( {"name" : "John", 
               "days" : [[1, 3, 5, 7]]})

print(df.explode('days'))

打印

   name days
0  John    1
0  John    3
0  John    5
0  John    7

【讨论】:

    【解决方案3】:

    这是 NumPy 的一些东西 -

    lens = [len(item) for item in df['days']]
    df_out = pd.DataFrame( {"name" : np.repeat(df['name'].values,lens), 
                   "days" : np.hstack(df['days'])
                  })
    

    正如@unutbu's solution 中指出的那样,np.concatenate(df['days'].values) 会比np.hstack(df['days']) 快。

    它使用循环理解来提取每个 'days' 元素的长度,这在运行时必须是最小的。

    示例运行 -

    >>> df
               days  name
    0  [1, 3, 5, 7]  John
    1        [2, 4]  Eric
    >>> lens = [len(item) for item in df['days']]
    >>> pd.DataFrame( {"name" : np.repeat(df['name'].values,lens), 
    ...                "days" : np.hstack(df['days'])
    ...               })
       days  name
    0     1  John
    1     3  John
    2     5  John
    3     7  John
    4     2  Eric
    5     4  Eric
    

    【讨论】:

      【解决方案4】:

      “原生”pandas 解决方案 - 我们将列拆分为一系列,然后根据索引重新加入:

      import pandas as pd #import
      x2 = x.days.apply(lambda x: pd.Series(x)).unstack() #make an unstackeded series, x2
      x.drop('days', axis = 1).join(pd.DataFrame(x2.reset_index(level=0, drop=True))) #drop the days column, join to the x2 series
      

      【讨论】:

        【解决方案5】:

        另一种解决方案:

        In [139]: (df.apply(lambda x: pd.Series(x.days), axis=1)
           .....:    .stack()
           .....:    .reset_index(level=1, drop=1)
           .....:    .to_frame('day')
           .....:    .join(df['name'])
           .....: )
        Out[139]:
           day  name
        0    1  John
        0    3  John
        0    5  John
        0    7  John
        

        【讨论】:

        • 绝妙的答案!稍微不同的需求:当数组像 [val, None, None, val2, None ...] 并且它们的大小不同时,可以修改什么以将 NaN 值保留在框架中?
        • 自我回复:首先用一些占位符替换Nones,这样熊猫就不会吓坏了。然后删除reset_index。在堆栈之前移动连接,并可选择在堆栈之前将名称字段设置为索引以避免重复。然后用 NaN 替换占位符。
        【解决方案6】:

        大概是这样的:

        df2 = pd.DataFrame(columns = df.columns)
        for i,r in df.iterrows():
            for e in r.days:
                new_r = r.copy()
                new_r.days = e
                df2 = df2.append(new_r)
        df2
        

        【讨论】:

          【解决方案7】:

          感谢Divakar's solution,将其编写为一个包装函数来展平列,处理np.nan 和具有多列的DataFrames

          def flatten_column(df, column_name):
               repeat_lens = [len(item) if item is not np.nan else 1 for item in df[column_name]]
               df_columns = list(df.columns)
               df_columns.remove(column_name)
               expanded_df = pd.DataFrame(np.repeat(df.drop(column_name, axis=1).values, repeat_lens, axis=0), columns=df_columns)
               flat_column_values = np.hstack(df[column_name].values)
               expanded_df[column_name] = flat_column_values
               expanded_df[column_name].replace('nan', np.nan, inplace=True)
               return expanded_df
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-05-30
            • 2020-03-30
            • 2021-06-06
            • 2016-11-17
            • 1970-01-01
            • 2021-05-07
            • 2018-12-11
            相关资源
            最近更新 更多