【问题标题】:Unpack the list element of DataFrame解包 DataFrame 的列表元素
【发布时间】:2016-06-28 13:47:05
【问题描述】:

我有这个 df:

l1 = ['a', 'b', 'c']
l2 = ['x', ['y1', 'y2', 'y3'], 'z']
df = pd.DataFrame(list(zip(l1, l2)), columns = ['l1', 'l2'])

结果:

  l1            l2
0  a             x
1  b  [y1, y2, y3]
2  c             z

我需要的是在 l2 中解压内部列表并将相应的值传播到 l1 中,如下所示:

  l1  l2
0  a   x
1  b  y1
2  b  y2
3  b  y3
4  c   z

这样做的正确方法是什么? 谢谢。

【问题讨论】:

    标签: python list pandas unpack


    【解决方案1】:

    您可以将嵌套列表推导与 itertools.zip_longest 一起使用。

    import pandas as pd
    
    from itertools import zip_longest
    
    l1 = ['a', 'b', 'c']
    l2 = ['x', ['y1', 'y2', 'y3'], 'z']
    
    expanded = [(left, right) for outer in zip(l1, l2) 
                              for left, right in zip_longest(*outer, fillvalue=outer[0])]
    
    pd.DataFrame(expanded)
    

    结果是……

       0   1
    0  a   x
    1  b  y1
    2  b  y2
    3  b  y3
    4  c   z
    

    对我来说,这是一个列表组合太长的边界。还假设l1 中没有列表并且将进行填充。

    【讨论】:

      【解决方案2】:

      我认为您可以将numpy.repeat 用于str.len 的长度重复值和chain 的嵌套lists 的平面值:

      from  itertools import chain
      
      df1 = pd.DataFrame({
              "l1": np.repeat(df.l1.values, df.l2.str.len()),
              "l2": list(chain.from_iterable(df.l2))})
      print (df1)
        l1  l2
      0  a   x
      1  b  y1
      2  b  y2
      3  b  y3
      4  c   z
      

      时间安排:

      #[100000 rows x 2 columns]
      np.random.seed(10)
      N = 100000
      l1 = ['a', 'b', 'c']
      l1 = np.random.choice(l1, N)
      l2 = [list(tuple(string.ascii_letters[:np.random.randint(1, 10)])) for _ in np.arange(N)]
      df = pd.DataFrame({"l1":l1, "l2":l2})
      df.l2 = df.l2.apply(lambda x: x if len(x) !=1 else x[0])
      #print (df)
      
      
      In [91]: %timeit (pd.DataFrame([(left, right) for outer in zip(l1, l2) for left, right in zip_longest(*outer, fillvalue=outer[0])]))
      1 loop, best of 3: 242 ms per loop
      
      In [92]: %timeit (pd.DataFrame({ "l1": np.repeat(df.l1.values, df.l2.str.len()), "l2": list(chain.from_iterable(df.l2))}))
      10 loops, best of 3: 84.6 ms per loop
      

      结论:

      numpy.repeat 比 3 times 在更大的 df 中的解决方案更快。

      编辑:

      与循环版本相比,df 需要更小,因为非常慢:

      #[1000 rows x 2 columns]
      np.random.seed(10)
      N = 1000
      l1 = ['a', 'b', 'c']
      l1 = np.random.choice(l1, N)
      l2 = [list(tuple(string.ascii_letters[:np.random.randint(1, 10)])) for _ in np.arange(N)]
      df = pd.DataFrame({"l1":l1, "l2":l2})
      df.l2 = df.l2.apply(lambda x: x if len(x) !=1 else x[0])
      #print (df)
      
      def alexey(df):
          df2 = pd.DataFrame(columns=df.columns,index=df.index)[0:0]
      
          for idx in df.index:
              new_row = df.loc[idx, :].copy()
              for res in df.ix[idx, 'l2']:
                  new_row.set_value('l2', res)
                  df2.loc[len(df2)] = new_row
          return df2
      
      print (alexey(df))
      
      In [20]: %timeit (alexey(df))
      1 loop, best of 3: 11.4 s per loop
      
      In [21]: %timeit pd.DataFrame([(left, right) for outer in zip(l1, l2) for left, right in zip_longest(*outer, fillvalue=outer[0])])
      100 loops, best of 3: 2.57 ms per loop
      
      In [22]: %timeit pd.DataFrame({ "l1": np.repeat(df.l1.values, df.l2.str.len()), "l2": list(chain.from_iterable(df.l2))})
      The slowest run took 4.42 times longer than the fastest. This could mean that an intermediate result is being cached.
      1000 loops, best of 3: 1.41 ms per loop
      

      【讨论】:

      • 我可以让你权衡一下我的回答吗here我回答晚了。我也在路上,没有笔记本电脑,无法运行任何代码?
      • 不幸的是我在打电话,所以无法测试。但我得到了投票。
      【解决方案3】:

      蛮力,遍历数据框:

      for idx in df.index:
          # This transforms the item in "l2" into an iterable list
          item = df.loc[idx, "l2"] if isinstance(df.loc[idx, "l2"], (list, tuple)) else [df.loc[idx, "l2"]]
          for element in item:
              print(df.loc[idx, "l1"], element)
      

      返回

      a x
      b y1
      b y2
      b y3
      c z
      

      【讨论】:

        【解决方案4】:

        对于列数不固定的 DataFrame,我现在执行以下操作:

        l1 = ['a', 'b', 'c']
        l2 = ['x', ['y1', 'y2', 'y3'], 'z']
        df = pd.DataFrame(list(zip(l1, l2)), columns = ['l1', 'l2'])
        

        自 pandas 0.25.0 以来,有一个内置的 explode 方法,它完全可以做到这一点,保留索引:

        df.explode('l2')
        

        结果:

          l1  l2
        0  a   x
        1  b  y1
        1  b  y2
        1  b  y3
        2  c   z
        

        如果需要刷新索引:

        df.explode('l2').reset_index(drop=True)
        

        结果:

          l1  l2
        0  a   x
        1  b  y1
        2  b  y2
        3  b  y3
        4  c   z
        

        旧答案:

        df2 = pd.DataFrame(columns=df.columns,index=df.index)[0:0]
        
        for idx in df.index:
            new_row = df.loc[idx, :].copy()
            for res in df.ix[idx, 'l2']:
                new_row.set_value('l2', res)
                df2.loc[len(df2)] = new_row
        

        它有效,但这看起来很像暴力破解。

        【讨论】:

        • 但我认为它会很慢,因为循环:(
        • 修复了代码,你能检查一下时间吗?是的,我想它也很慢。也许循环可以以某种方式优化(我不是专家)
        • 循环非常慢,在 pandas 或 numpy 中最好避免它。请检查时间,如果我的回答有帮助,请不要忘记accept。谢谢。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-10-31
        • 2018-09-02
        • 1970-01-01
        • 2015-07-28
        • 1970-01-01
        相关资源
        最近更新 更多