【问题标题】:Splitting a List inside a Pandas DataFrame在 Pandas DataFrame 中拆分列表
【发布时间】:2015-04-11 02:47:14
【问题描述】:

我有一个包含许多列的 csv 文件。使用 pandas,我将这个 csv 文件读入一个数据框,并有一个日期时间索引和五六列其他列。

其中一列是时间戳列表(下面带有索引的示例)

CreateDate     TimeStamps
4/1/11         [Timestamp('2012-02-29 00:00:00'), Timestamp('2012-03-31 00:00:00'), Timestamp('2012-04-25 00:00:00'), Timestamp('2012-06-30 00:00:00')]
4/2/11         [Timestamp('2014-01-31 00:00:00')]
6/8/11         [Timestamp('2012-08-31 00:00:00'), Timestamp('2012-09-30 00:00:00'), Timestamp('2012-11-07 00:00:00'), Timestamp('2013-01-10 00:00:00'), Timestamp('2013-07-25 00:00:00')]

我想做的是将时间戳列转换为列出的每个时间戳的单独行。例如,对于第 1 行,它将转换为 4 行,第 2 行将转换为 1 行。我意识到我需要重置索引才能做到这一点,这很好。

我尝试过的一切最终都进入了左侧字段(获取值并在 pandas 之外创建一个列表等)

任何建议表示赞赏。

【问题讨论】:

  • 您如何创建数据框以便在 TimeStamps 列中获得列表?
  • 这就是将 csv 发送给我的方式。它是来自 quickbooks 导出的数据文件。

标签: python csv pandas


【解决方案1】:

如果您想留在纯 pandas 中,您可以输入一个棘手的 groupbyapply,如果您不计算列重命名,它们最终会归结为一个内衬。

In [1]: import pandas as pd

In [2]: d = {'date': ['4/1/11', '4/2/11'], 'ts': [[pd.Timestamp('2012-02-29 00:00:00'), pd.Timestamp('2012-03-31 00:00:00'), pd.Timestamp('2012-04-25 00:00:00'), pd.Timestamp('2012-06-30 00:00:00')], [pd.Timestamp('2014-01-31 00:00:00')]]}

In [3]: df = pd.DataFrame(d)

In [4]: df.head()
Out[4]: 
     date                                                 ts
0  4/1/11  [2012-02-29 00:00:00, 2012-03-31 00:00:00, 201...
1  4/2/11                              [2014-01-31 00:00:00]

In [5]: df_new = df.groupby('date').ts.apply(lambda x: pd.DataFrame(x.values[0])).reset_index().drop('level_1', axis = 1)

In [6]: df_new.columns = ['date','ts']

In [7]: df_new.head()
Out[7]: 
     date         ts
0  4/1/11 2012-02-29
1  4/1/11 2012-03-31
2  4/1/11 2012-04-25
3  4/1/11 2012-06-30
4  4/2/11 2014-01-31

由于目标是获取列的值(在本例中为日期)并为您打算从列表中创建的多行的所有值重复它,因此考虑 pandas 索引很有用。

我们希望日期成为新行的单一索引,因此我们使用groupby 将所需的行值放入索引中。然后在该操作中,我只想为这个日期拆分这个列表,这就是apply 将为我们做的事情。

我正在传递apply 一个熊猫Series,它由一个列表组成,但我可以通过.values[0] 访问该列表,它将Series 的唯一行推送到具有单个条目的数组。

要将列表转换为一组将传递回索引日期的行,我可以将其设为DataFrame。这会导致拾取额外索引的惩罚,但我们最终放弃了它。我们可以将其设为索引本身,但这会排除重复值。

一旦将其传回,我就有了一个多索引,但我可以通过reset_index 将其强制转换为我们想要的行格式。然后我们只需删除不需要的索引。

这听起来很复杂,但实际上我们只是利用 pandas 函数的自然行为来避免显式迭代或循环。

在速度方面,这往往相当不错,因为它依赖于apply,任何适用于apply 的并行化技巧都可以在这里工作。

如果您希望它对多个日期具有鲁棒性,每个日期都有一个嵌套列表:

df_new = df.groupby('date').ts.apply(lambda x: pd.DataFrame([item for sublist in x.values for item in sublist]))

此时一个衬里变得越来越密集,您可能应该投入到一个函数中。

【讨论】:

    【解决方案2】:

    我的做法是将列表拆分为单独的列,然后 melted 将每个时间戳放在单独的行中。

    In [48]: df = pd.DataFrame([[1,2,[1,2,4]],[4,5,[1,3]],],columns=['a','b','TimeStamp'])
        ...: df
    Out[48]: 
       a  b  TimeStamp
    0  1  2  [1, 2, 4]
    1  4  5     [1, 3]
    

    您可以将列转换为列表,然后再转换回DataFrame 以将其拆分为列:

    In [53]: TScolumns = pd.DataFrame(df.TimeStamp.tolist(), )
        ...: TScolumns
    Out[53]: 
       0  1   2
    0  1  2   4
    1  1  3 NaN
    

    然后将其拼接到原始数据帧上

    In [90]: df = df.drop('TimeStamp',axis=1)
    In [58]: split = pd.concat([df, TScolumns], axis=1)
        ...: split
    Out[58]: 
       a  b  0  1   2
    0  1  2  1  2   4
    1  4  5  1  3 NaN
    

    最后,使用melt 把它变成你想要的形状:

    In [89]: pd.melt(split, id_vars=['a', 'b'], value_name='TimeStamp')
    Out[89]: 
       a  b variable  TimeStamp
    0  1  2        0          1
    1  4  5        0          1
    2  1  2        1          2
    3  4  5        1          3
    4  1  2        2          4
    5  4  5        2        NaN
    

    【讨论】:

      【解决方案3】:

      这感觉不是很 Python,但它有效(前提是你的 createDate 是唯一的!)

      Apply 只会返回比使用 groupby 获得的行数更多的行,因此我们将人为地使用 groupby(即 groupby 一列唯一值,因此每个组都是一行)。

      def splitRows(x):
      
          # Extract the actual list of time-stamps. 
          theList = x.TimeStamps.iloc[0]
      
          # Each row will be a dictionary in this list.
          listOfNewRows = list()
      
          # Iterate over items in list of timestamps, 
          # putting each one in a dictionary to later convert to a row, 
          # then adding the dictionary to a list. 
      
          for i in theList:
              newRow = dict()
              newRow['CreateDate'] = x.CreateDate.iloc[0]
              newRow['TimeStamps'] = i
              listOfNewRows.append(newRow)
      
          # Now convert these dictionaries into rows in a new dataframe and return it. 
          return pd.DataFrame(listOfNewRows)
      
      
      df.groupby('CreateDate', as_index = False, group_keys = False).apply(splitRows)
      

      跟进:如果 CreateDate 不是唯一的,您只需将索引重置为新列并按此分组。

      【讨论】:

      • 除非我读错了(并且执行错误),否则这将返回一个数据帧,其中的行只是一个计数器。 CreateDate 是正确的,但时间戳行只是一个计数器“i”。当我尝试这个时,我得到 Timestamps 列是从 1 到 x 的数字序列(x 是我有多少行)
      • 你是对的,我很抱歉——当我搬到 SO 时添加了一个错字。 “newRow['TimeStamps'] = i” 应为“newRow['TimeStamps'] = theList[i]”。立即编辑。
      • 或者迭代器应该在列表上方,如现在所示。
      • 谢谢...今晚会看看它
      • 当我向 theList 行添加一个“split”时,这有效。我将该行更改为 theList = x.TimeStamps.iloc[0].split()。谢谢。
      【解决方案4】:

      一种更新的方法是使用explode (documentation)

      import pandas as pd
      
      d = {'date': ['4/1/11', '4/2/11'], 'ts': [[pd.Timestamp('2012-02-29 00:00:00'), pd.Timestamp('2012-03-31 00:00:00'), pd.Timestamp('2012-04-25 00:00:00'), pd.Timestamp('2012-06-30 00:00:00')], [pd.Timestamp('2014-01-31 00:00:00')]]}
      
      test_df = pd.DataFrame(d)
      
      result_df = test_df.explode('ts')
      
      result_df.head()
      

      输出

          date    ts
      0   4/1/11  2012-02-29
      0   4/1/11  2012-03-31
      0   4/1/11  2012-04-25
      0   4/1/11  2012-06-30
      1   4/2/11  2014-01-31
      

      【讨论】:

        【解决方案5】:

        从性能角度来看,这可能不是最好的方法,但您仍然可以利用 itertools 包:

        from pandas import DataFrame, Timestamp
        import itertools
        
        d = {'date': ['4/1/11', '4/2/11'], 'ts': [[Timestamp('2012-02-29 00:00:00'), Timestamp('2012-03-31 00:00:00'), Timestamp('2012-04-25 00:00:00'), Timestamp('2012-06-30 00:00:00')], [Timestamp('2014-01-31 00:00:00')]]}
        df = DataFrame(d)
        
        res = df.to_dict()
        data = []
        for x in res['date'].keys():
          data.append(itertools.izip_longest([res['date'][x]], res['ts'][x], fillvalue=res['date'][x]))
        
        new_data = list(itertools.chain.from_iterable(data))
        df2 = DataFrame(new_data, columns=['date', 'timestamp'])
        print df2
        

        将打印:

             date  timestamp
        0  4/1/11 2012-02-29
        1  4/1/11 2012-03-31
        2  4/1/11 2012-04-25
        3  4/1/11 2012-06-30
        4  4/2/11 2014-01-31
        

        【讨论】:

          猜你喜欢
          • 2018-04-07
          • 2018-05-28
          • 2016-02-17
          • 2021-10-04
          • 1970-01-01
          • 1970-01-01
          • 2019-05-18
          相关资源
          最近更新 更多