【问题标题】:Pandas column of lists, create a row for each list elementPandas 列表列,为每个列表元素创建一行
【发布时间】:2015-01-31 13:10:13
【问题描述】:

我有一个数据框,其中一些单元格包含多个值的列表。而不是存储多个 单元格中的值,我想扩展数据框,以便列表中的每个项目都有自己的行(在所有其他列中具有相同的值)。所以如果我有:

import pandas as pd
import numpy as np

df = pd.DataFrame(
    {'trial_num': [1, 2, 3, 1, 2, 3],
     'subject': [1, 1, 1, 2, 2, 2],
     'samples': [list(np.random.randn(3).round(2)) for i in range(6)]
    }
)

df
Out[10]: 
                 samples  subject  trial_num
0    [0.57, -0.83, 1.44]        1          1
1    [-0.01, 1.13, 0.36]        1          2
2   [1.18, -1.46, -0.94]        1          3
3  [-0.08, -4.22, -2.05]        2          1
4     [0.72, 0.79, 0.53]        2          2
5    [0.4, -0.32, -0.13]        2          3

如何转换为长格式,例如:

   subject  trial_num  sample  sample_num
0        1          1    0.57           0
1        1          1   -0.83           1
2        1          1    1.44           2
3        1          2   -0.01           0
4        1          2    1.13           1
5        1          2    0.36           2
6        1          3    1.18           0
# etc.

索引不重要,设置existing就可以了 列作为索引,最终排序不是 很重要。

【问题讨论】:

  • 从 pandas 0.25 开始你也可以使用df.explode('samples') 来解决这个问题。 explode目前只支持爆炸一列。

标签: python pandas list


【解决方案1】:

比我预期的要长一点:

>>> df
                samples  subject  trial_num
0  [-0.07, -2.9, -2.44]        1          1
1   [-1.52, -0.35, 0.1]        1          2
2  [-0.17, 0.57, -0.65]        1          3
3  [-0.82, -1.06, 0.47]        2          1
4   [0.79, 1.35, -0.09]        2          2
5   [1.17, 1.14, -1.79]        2          3
>>>
>>> s = df.apply(lambda x: pd.Series(x['samples']),axis=1).stack().reset_index(level=1, drop=True)
>>> s.name = 'sample'
>>>
>>> df.drop('samples', axis=1).join(s)
   subject  trial_num  sample
0        1          1   -0.07
0        1          1   -2.90
0        1          1   -2.44
1        1          2   -1.52
1        1          2   -0.35
1        1          2    0.10
2        1          3   -0.17
2        1          3    0.57
2        1          3   -0.65
3        2          1   -0.82
3        2          1   -1.06
3        2          1    0.47
4        2          2    0.79
4        2          2    1.35
4        2          2   -0.09
5        2          3    1.17
5        2          3    1.14
5        2          3   -1.79

如果需要顺序索引,可以将reset_index(drop=True) 应用于结果。

更新

>>> res = df.set_index(['subject', 'trial_num'])['samples'].apply(pd.Series).stack()
>>> res = res.reset_index()
>>> res.columns = ['subject','trial_num','sample_num','sample']
>>> res
    subject  trial_num  sample_num  sample
0         1          1           0    1.89
1         1          1           1   -2.92
2         1          1           2    0.34
3         1          2           0    0.85
4         1          2           1    0.24
5         1          2           2    0.72
6         1          3           0   -0.96
7         1          3           1   -2.72
8         1          3           2   -0.11
9         2          1           0   -1.33
10        2          1           1    3.13
11        2          1           2   -0.65
12        2          2           0    0.10
13        2          2           1    0.65
14        2          2           2    0.15
15        2          3           0    0.64
16        2          3           1   -0.10
17        2          3           2   -0.76

【讨论】:

  • 谢谢,即使是申请将每个项目都放在自己的专栏中的第一步也是一个巨大的帮助。我能够想出一个稍微不同的方法来做到这一点,但仍然涉及一些相当多的步骤。显然,这在 Pandas 中并不简单!
  • 很好的答案。您可以通过将df.apply(lambda x: pd.Series(x['samples']),axis=1) 替换为df.samples.apply(pd.Series) 来缩短它。
  • 读者注意:这严重受到性能问题的影响。请参阅here 以获得使用 numpy 的性能更高的解决方案。
  • 所有行的样本数不一样怎么办?
  • @SarahData 使用df.explode() 如图here.
【解决方案2】:

为了更好地理解 Roman Pekar 的解决方案,我想出了自己的解决方案,它使用 melt 来避免一些令人困惑的堆叠和索引重置。我不能说这显然是一个更清晰的解决方案:

items_as_cols = df.apply(lambda x: pd.Series(x['samples']), axis=1)
# Keep original df index as a column so it's retained after melt
items_as_cols['orig_index'] = items_as_cols.index

melted_items = pd.melt(items_as_cols, id_vars='orig_index', 
                       var_name='sample_num', value_name='sample')
melted_items.set_index('orig_index', inplace=True)

df.merge(melted_items, left_index=True, right_index=True)

输出(显然我们现在可以删除原始样本列):

                 samples  subject  trial_num sample_num  sample
0    [1.84, 1.05, -0.66]        1          1          0    1.84
0    [1.84, 1.05, -0.66]        1          1          1    1.05
0    [1.84, 1.05, -0.66]        1          1          2   -0.66
1    [-0.24, -0.9, 0.65]        1          2          0   -0.24
1    [-0.24, -0.9, 0.65]        1          2          1   -0.90
1    [-0.24, -0.9, 0.65]        1          2          2    0.65
2    [1.15, -0.87, -1.1]        1          3          0    1.15
2    [1.15, -0.87, -1.1]        1          3          1   -0.87
2    [1.15, -0.87, -1.1]        1          3          2   -1.10
3   [-0.8, -0.62, -0.68]        2          1          0   -0.80
3   [-0.8, -0.62, -0.68]        2          1          1   -0.62
3   [-0.8, -0.62, -0.68]        2          1          2   -0.68
4    [0.91, -0.47, 1.43]        2          2          0    0.91
4    [0.91, -0.47, 1.43]        2          2          1   -0.47
4    [0.91, -0.47, 1.43]        2          2          2    1.43
5  [-1.14, -0.24, -0.91]        2          3          0   -1.14
5  [-1.14, -0.24, -0.91]        2          3          1   -0.24
5  [-1.14, -0.24, -0.91]        2          3          2   -0.91

【讨论】:

    【解决方案3】:

    您也可以为此使用pd.concatpd.melt

    >>> objs = [df, pd.DataFrame(df['samples'].tolist())]
    >>> pd.concat(objs, axis=1).drop('samples', axis=1)
       subject  trial_num     0     1     2
    0        1          1 -0.49 -1.00  0.44
    1        1          2 -0.28  1.48  2.01
    2        1          3 -0.52 -1.84  0.02
    3        2          1  1.23 -1.36 -1.06
    4        2          2  0.54  0.18  0.51
    5        2          3 -2.18 -0.13 -1.35
    >>> pd.melt(_, var_name='sample_num', value_name='sample', 
    ...         value_vars=[0, 1, 2], id_vars=['subject', 'trial_num'])
        subject  trial_num sample_num  sample
    0         1          1          0   -0.49
    1         1          2          0   -0.28
    2         1          3          0   -0.52
    3         2          1          0    1.23
    4         2          2          0    0.54
    5         2          3          0   -2.18
    6         1          1          1   -1.00
    7         1          2          1    1.48
    8         1          3          1   -1.84
    9         2          1          1   -1.36
    10        2          2          1    0.18
    11        2          3          1   -0.13
    12        1          1          2    0.44
    13        1          2          2    2.01
    14        1          3          2    0.02
    15        2          1          2   -1.06
    16        2          2          2    0.51
    17        2          3          2   -1.35
    

    最后,如果需要,您可以根据前三列进行排序。

    【讨论】:

    • 这只有在您事先知道列表的长度和/或它们都具有相同长度的情况下才有效?
    【解决方案4】:

    对于那些寻找避免手动列命名的 Roman Pekar 答案版本的人:

    column_to_explode = 'samples'
    res = (df
           .set_index([x for x in df.columns if x != column_to_explode])[column_to_explode]
           .apply(pd.Series)
           .stack()
           .reset_index())
    res = res.rename(columns={
              res.columns[-2]:'exploded_{}_index'.format(column_to_explode),
              res.columns[-1]: '{}_exploded'.format(column_to_explode)})
    

    【讨论】:

      【解决方案5】:

      更新:以下解决方案对旧版 Pandas 很有帮助,因为 DataFrame.explode() 不可用。从 Pandas 0.25.0 开始,您可以简单地使用 DataFrame.explode()


      lst_col = 'samples'
      
      r = pd.DataFrame({
            col:np.repeat(df[col].values, df[lst_col].str.len())
            for col in df.columns.drop(lst_col)}
          ).assign(**{lst_col:np.concatenate(df[lst_col].values)})[df.columns]
      

      结果:

      In [103]: r
      Out[103]:
          samples  subject  trial_num
      0      0.10        1          1
      1     -0.20        1          1
      2      0.05        1          1
      3      0.25        1          2
      4      1.32        1          2
      5     -0.17        1          2
      6      0.64        1          3
      7     -0.22        1          3
      8     -0.71        1          3
      9     -0.03        2          1
      10    -0.65        2          1
      11     0.76        2          1
      12     1.77        2          2
      13     0.89        2          2
      14     0.65        2          2
      15    -0.98        2          3
      16     0.65        2          3
      17    -0.30        2          3
      

      PShere you may find a bit more generic solution


      更新: 一些解释:IMO 理解这段代码的最简单方法是尝试逐步执行它:

      在下一行中,我们在一列中重复了N 次中的值,其中N - 是相应列表的长度:

      In [10]: np.repeat(df['trial_num'].values, df[lst_col].str.len())
      Out[10]: array([1, 1, 1, 2, 2, 2, 3, 3, 3, 1, 1, 1, 2, 2, 2, 3, 3, 3], dtype=int64)
      

      这可以推广到所有列,包含标量值:

      In [11]: pd.DataFrame({
          ...:           col:np.repeat(df[col].values, df[lst_col].str.len())
          ...:           for col in df.columns.drop(lst_col)}
          ...:         )
      Out[11]:
          trial_num  subject
      0           1        1
      1           1        1
      2           1        1
      3           2        1
      4           2        1
      5           2        1
      6           3        1
      ..        ...      ...
      11          1        2
      12          2        2
      13          2        2
      14          2        2
      15          3        2
      16          3        2
      17          3        2
      
      [18 rows x 2 columns]
      

      使用np.concatenate(),我们可以展平list 列(samples)中的所有值并获得一维向量:

      In [12]: np.concatenate(df[lst_col].values)
      Out[12]: array([-1.04, -0.58, -1.32,  0.82, -0.59, -0.34,  0.25,  2.09,  0.12,  0.83, -0.88,  0.68,  0.55, -0.56,  0.65, -0.04,  0.36, -0.31])
      

      将所有这些放在一起:

      In [13]: pd.DataFrame({
          ...:           col:np.repeat(df[col].values, df[lst_col].str.len())
          ...:           for col in df.columns.drop(lst_col)}
          ...:         ).assign(**{lst_col:np.concatenate(df[lst_col].values)})
      Out[13]:
          trial_num  subject  samples
      0           1        1    -1.04
      1           1        1    -0.58
      2           1        1    -1.32
      3           2        1     0.82
      4           2        1    -0.59
      5           2        1    -0.34
      6           3        1     0.25
      ..        ...      ...      ...
      11          1        2     0.68
      12          2        2     0.55
      13          2        2    -0.56
      14          2        2     0.65
      15          3        2    -0.04
      16          3        2     0.36
      17          3        2    -0.31
      
      [18 rows x 3 columns]
      

      使用pd.DataFrame()[df.columns] 将保证我们按原始顺序选择列...

      【讨论】:

      • 这应该是公认的答案。与此相比,当前接受的答案要慢得多。
      • 我不知道如何解决这个问题:TypeError: Cannot cast array data from dtype('float64') to dtype('int64') based on the rule 'safe'
      • 这是唯一一个对我有用的答案,在整整一小时的 Stacks 搜索中找到的 10 多个答案。谢谢MaxU?
      • 请注意,这会完全删除 lst_col 中具有空列表的行;要保留这些行并使用np.nan 填充它们的lst_col,您可以在使用此方法之前执行df[lst_col] = df[lst_col].apply(lambda x: x if len(x) > 0 else [np.nan])。显然.mask 不会返回列表,因此.apply
      • 这是一个很好的答案,应该被接受。虽然,这是一个黑魔法级别的答案,但我希望能对这些步骤的实际作用做出一些解释。
      【解决方案6】:

      我发现最简单的方法是:

      1. samples 列转换为DataFrame
      2. 加入原df
      3. 熔化

      此处显示:

          df.samples.apply(lambda x: pd.Series(x)).join(df).\
      melt(['subject','trial_num'],[0,1,2],var_name='sample')
      
              subject  trial_num sample  value
          0         1          1      0  -0.24
          1         1          2      0   0.14
          2         1          3      0  -0.67
          3         2          1      0  -1.52
          4         2          2      0  -0.00
          5         2          3      0  -1.73
          6         1          1      1  -0.70
          7         1          2      1  -0.70
          8         1          3      1  -0.29
          9         2          1      1  -0.70
          10        2          2      1  -0.72
          11        2          3      1   1.30
          12        1          1      2  -0.55
          13        1          2      2   0.10
          14        1          3      2  -0.44
          15        2          1      2   0.13
          16        2          2      2  -1.44
          17        2          3      2   0.73
      

      值得注意的是,这可能只是因为每个试验都有相同数量的样本 (3)。对于不同样本量的试验,可能需要更聪明的方法。

      【讨论】:

        【解决方案7】:

        答案很晚,但我想补充一下:

        使用 vanilla Python 的快速解决方案,它还处理 OP 示例中的 sample_num 列。在我自己的包含超过 1000 万行的大型数据集和包含 2800 万行的结果中,这只需要大约 38 秒。接受的解决方案完全因数据量而崩溃,并导致我的系统上有一个memory error,它有 128GB 的​​ RAM。

        df = df.reset_index(drop=True)
        lstcol = df.lstcol.values
        lstcollist = []
        indexlist = []
        countlist = []
        for ii in range(len(lstcol)):
            lstcollist.extend(lstcol[ii])
            indexlist.extend([ii]*len(lstcol[ii]))
            countlist.extend([jj for jj in range(len(lstcol[ii]))])
        df = pd.merge(df.drop("lstcol",axis=1),pd.DataFrame({"lstcol":lstcollist,"lstcol_num":countlist},
        index=indexlist),left_index=True,right_index=True).reset_index(drop=True)
        

        【讨论】:

          【解决方案8】:

          熊猫 >= 0.25

          Series 和 DataFrame 方法定义了一个 .explode() 方法,该方法将列表分解为单独的行。请参阅Exploding a list-like column 上的文档部分。

          df = pd.DataFrame({
              'var1': [['a', 'b', 'c'], ['d', 'e',], [], np.nan], 
              'var2': [1, 2, 3, 4]
          })
          df
                  var1  var2
          0  [a, b, c]     1
          1     [d, e]     2
          2         []     3
          3        NaN     4
          
          df.explode('var1')
          
            var1  var2
          0    a     1
          0    b     1
          0    c     1
          1    d     2
          1    e     2
          2  NaN     3  # empty list converted to NaN
          3  NaN     4  # NaN entry preserved as-is
          
          # to reset the index to be monotonically increasing...
          df.explode('var1').reset_index(drop=True)
          
            var1  var2
          0    a     1
          1    b     1
          2    c     1
          3    d     2
          4    e     2
          5  NaN     3
          6  NaN     4
          

          请注意,这还可以适当地处理列表和标量的混合列,以及空列表和 NaN(这是基于 repeat 的解决方案的一个缺点)。

          但是,您应该注意 explode 仅适用于单个列(目前)。

          P.S.:如果你想分解一列字符串,你需要先在分隔符上分割,然后使用explode。看到这个(非常)related answer by me.

          【讨论】:

          • 最后是 Pandas 的 explode()!
          • 终于!脑洞大开!上面@MaxU 给出了很好的答案,但这让事情变得更加简单。
          • 这是当前熊猫的正确答案
          • 这应该是正确的答案——如此简单,如此优雅,如此熊猫。
          【解决方案9】:
          import pandas as pd
          df = pd.DataFrame([{'Product': 'Coke', 'Prices': [100,123,101,105,99,94,98]},{'Product': 'Pepsi', 'Prices': [101,104,104,101,99,99,99]}])
          print(df)
          df = df.assign(Prices=df.Prices.str.split(',')).explode('Prices')
          print(df)
          

          在 pandas >=0.25 版本中试试这个

          【讨论】:

          • 不需要.str.split(','),因为Prices 已经是一个列表。
          【解决方案10】:

          也很晚了,但是如果您没有 pandas >=0.25 版本,那么 Karvy1 的回答对我来说效果很好:https://stackoverflow.com/a/52511166/10740287

          对于上面的例子,你可以写:

          data = [(row.subject, row.trial_num, sample) for row in df.itertuples() for sample in row.samples]
          data = pd.DataFrame(data, columns=['subject', 'trial_num', 'samples'])
          

          速度测试:

          %timeit data = pd.DataFrame([(row.subject, row.trial_num, sample) for row in df.itertuples() for sample in row.samples], columns=['subject', 'trial_num', 'samples'])
          

          每个循环 1.33 ms ± 74.8 µs(平均值 ± 标准偏差,7 次运行,每次 1000 个循环)

          %timeit data = df.set_index(['subject', 'trial_num'])['samples'].apply(pd.Series).stack().reset_index()
          

          每个循环 4.9 ms ± 189 µs(平均值 ± 标准偏差,7 次运行,每次 100 个循环)

          %timeit data = pd.DataFrame({col:np.repeat(df[col].values, df['samples'].str.len())for col in df.columns.drop('samples')}).assign(**{'samples':np.concatenate(df['samples'].values)})
          

          每个循环 1.38 毫秒 ± 25 微秒(平均值 ± 标准偏差,7 次运行,每次 1000 次循环)

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-10-22
            • 1970-01-01
            • 2023-03-11
            • 2021-05-04
            相关资源
            最近更新 更多