【问题标题】:Dataframe: select different index for each columns数据框:为每列选择不同的索引
【发布时间】:2019-01-18 09:51:32
【问题描述】:

假设我有以下 pandas DataFrame:

从熊猫导入数据框 从 numpy 导入范围

lst = [ range(10), range(11,21), range(21,31) ]
df = DataFrame(lst).T.set_index(arange(0.1, 1.1, 0.1))

     0   1   2
0.1  0  11  21
0.2  1  12  22
0.3  2  13  23
0.4  3  14  24
0.5  4  15  25
0.6  5  16  26
0.7  6  17  27
0.8  7  18  28
0.9  8  19  29
1.0  9  20  30

我想使用不同的索引范围选择每一列。

以列为例:

  • 0:我希望只有索引为 0.6 到 0.9 的行
  • 1:我希望只有索引为 0.2 到 0.3 的行
  • 2:我希望只有索引为 0.1 到 0.3 的行

所以我的DataFrame应该是这样的:

       0     1     2
0.1  NaN   NaN  21.0
0.2  NaN  12.0  22.0
0.6  5.0   NaN   NaN
0.7  6.0   NaN   NaN
0.8  7.0   NaN   NaN
0.9  8.0   NaN   NaN

我目前的解决方案是这个:

idx = array([ [0.6, 0.9], [0.2, 0.3], [0.1, 0.3] ])
df2 = DataFrame((df[col][i[0]:i[1]] for i, col in zip(idx, df))).T

也许不是更好的解决方案。

感谢大家的回答。

比较 我编写了一个脚本来对每个答案进行基准测试。 基准分为两部分:

  • STD:仅适用于标准索引 (0,1,2,3,...) 的答案
  • GEN:适用于通用索引的答案

    from numpy import arange, array, linspace
    from numpy.random import rand, randint
    from pandas import DataFrame
    from timeit import Timer
    
    # yellowhat
    def yellowhat(df, idx):
        df2 = DataFrame((df[col][i[0]:i[1]] for i, col in zip(idx, df))).T
        return df2
    
    # user3483203
    def user3483203(df, idx):
        from numpy import arange
        r = arange(df.shape[0])[:, None]
        m = (idx[:,0] <= r) & (idx[:,1] > r)
        df2 = df.mask(~m).dropna(how='all')
        return df2
    
    def user3483203_2(df, idx):
        from numpy import zeros, bool8, arange
        def foo(a, idx):
            out = zeros(a, dtype=bool8)
            for (i, j), k in zip(idx, arange(a[1])):
                out[i:j, k] = True
            return out
        df2 = df.mask(~foo(df.shape, idx)).dropna(how='all')
        return df2
    
    def user3483203_mod(df, idx):
        r = df.index.values[:,None]
        m = (r >= idx[:,0]) & (r <= idx[:,1])
        df2 = df.mask(~m).dropna(how='all')
        return df2
    
    #
    def GeorgeLPerkins(df, idx):
        from pandas import DataFrame
        dct = {i : row for i, row in enumerate(idx)}
        df2 = DataFrame(columns = df.columns, index=df.index)
        for k in dct:
            df2[k] = df[k][dct[k][0] : dct[k][1]]
        return df2
    
    #
    def piRSquared(df, idx):
        tups = sorted([(i, j) for j, args in enumerate(idx) for i in range(*args)])
        df2 = df.stack().loc[tups].unstack()
        return df2
    
    #
    def sacul(df, idx):
        from pandas import concat
        df2 = concat([df[col].iloc[range(*idx[i])] for i,col in enumerate(df.columns)],axis=1)
        return df2
    
    def sacul_2(df, idx):
        df2 = df.apply(lambda x: x.iloc[range(*idx[df.columns.get_loc(x.name)])])
        return df2
    
    # Benchmark Index STD
    nRow, nCol = 1000, 500
    df = DataFrame(rand(nRow, nCol))
    
    idx = df.index[randint(nRow, size=(nCol, 2))].values
    idx.sort(axis=1)
    
    print('STD')
    for func in [yellowhat, GeorgeLPerkins, user3483203, user3483203_2, user3483203_mod, piRSquared, sacul, sacul_2]:
        nmFunc = func.__name__
        print(nmFunc)
        t = Timer("%s(df, idx)"%nmFunc, "from __main__ import df, idx, %s"%nmFunc).timeit(10)
        print(' %8.2f sec'%t)
        print('')
    
    # Benchmark Index GEN
    idx = linspace(0, 1, nRow)
    df = DataFrame(rand(nRow, nCol)).set_index(idx)
    
    idx = idx[randint(nRow, size=(nCol, 2))]
    idx.sort(axis=1)
    
    print('GEN')
    for func in [yellowhat, GeorgeLPerkins, user3483203_mod]:
        nmFunc = func.__name__
        print(nmFunc)
        t = Timer("%s(df, idx)"%nmFunc, "from __main__ import df, idx, %s"%nmFunc).timeit(10)
        print(' %8.2f sec'%t)
        print('')
    

这些是我机器上的结果:

STD
yellowhat
     4.56 sec
GeorgeLPerkins
    26.10 sec
user3483203
     0.56 sec
user3483203_2
     0.57 sec
user3483203_mod
     0.63 sec
piRSquared
    31.84 
sacul
     6.50 
sacul_2
     7.15 sec

GEN
yellowhat
     5.13 
GeorgeLPerkins
    27.07 
user3483203_mod
     0.52 sec

感谢大家的回答。

【问题讨论】:

    标签: python pandas


    【解决方案1】:

    更新:我向 another question 询问了如何对这个问题的方法进行矢量化处理,@Divakar 发布了一个可以在此处应用的 excellent answer

    r = np.arange(df.shape[0])[:, None]
    m = (idx[:,0] <= r) & (idx[:,1] > r)
    df.mask(~m).dropna(how='all')
    
         0     1     2
    1  NaN   NaN  22.0
    2  NaN  13.0  23.0
    6  6.0   NaN   NaN
    7  7.0   NaN   NaN
    8  8.0   NaN   NaN
    

    旧的非矢量化方法

    这种方法使用底层的numpy 数组来创建掩码:

    def foo(a, idx):
        out = np.zeros(a, dtype=np.bool8)
        for (i, j), k in zip(idx, np.arange(a[1])):
            out[i:j, k] = True
        return out
    
    df.mask(~foo(df.shape, idx)).dropna(how='all')
    

    输出:

         0     1     2
    1  NaN   NaN  22.0
    2  NaN  13.0  23.0
    6  6.0   NaN   NaN
    7  7.0   NaN   NaN
    8  8.0   NaN   NaN
    

    【讨论】:

    • 使用out = np.zeros(a, dtype=np.bool8) 然后out[i:k, k] = True
    • @piRSquared 我问了另一个问题,看看如何对其进行矢量化,Divakar 当然有一个很好的解决方案。
    • 这很好。但是,广播会耗尽时间复杂度和内存。有时循环会更好。
    • 但既然我已经说过了,我需要备份它......待续。
    • 这个解决方案看起来很有趣,但如果索引不是standard 像 1,2,3,... 我用更通用的索引更新我的问题。谢谢
    【解决方案2】:

    我不确定这是否真的比你所拥有的更好,但你可以遍历你的列,使用*index 解压缩到一个范围中,然后连接生成的数据帧:

    pd.concat([df[col].iloc[range(*index[i])] for i,col in enumerate(df.columns)],axis=1)
    
         0     1     2
    1  NaN   NaN  22.0
    2  NaN  13.0  23.0
    6  6.0   NaN   NaN
    7  7.0   NaN   NaN
    8  8.0   NaN   NaN
    

    或者另一种方式,应用:使用每列的索引号来索引您的索引列表,使用df.columns.get_loc(x.name)

    df.apply(lambda x: x.iloc[range(*index[df.columns.get_loc(x.name)])])
    
         0     1     2
    1  NaN   NaN  22.0
    2  NaN  13.0  23.0
    6  6.0   NaN   NaN
    7  7.0   NaN   NaN
    8  8.0   NaN   NaN
    

    【讨论】:

      【解决方案3】:

      stack 然后选择 loc

      tups = sorted([(i, j) for j, args in enumerate(index) for i in range(*args)])
      df.stack().loc[tups].unstack()
      
           0     1     2
      1  NaN   NaN  22.0
      2  NaN  13.0  23.0
      6  6.0   NaN   NaN
      7  7.0   NaN   NaN
      8  8.0   NaN   NaN
      

      构造新系列然后解栈

      pd.Series({
          (i, j): df.at[i, j] for j, args in enumerate(index) for i in range(*args)
      }).unstack()
      
           0     1     2
      1  NaN   NaN  22.0
      2  NaN  13.0  23.0
      6  6.0   NaN   NaN
      7  7.0   NaN   NaN
      8  8.0   NaN   NaN
      

      【讨论】:

        【解决方案4】:

        您可以使用字典并执行以下操作,而不是将您的“索引”作为列表:

        import pandas as pd
        
        lst = [ range(10), range(11,21), range(21,31) ]
        df = pd.DataFrame(lst).T
        dict = {0:[6,9], 1:[2,3], 2:[1,3]}
        
        df2 = pd.DataFrame(columns = df.columns, index=df.index)
        
        for k in dict:
            df2[k] = df[k][dict[k][0]:dict[k][1]+1]
        
        print(df2)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-04-27
          • 2020-08-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-10-16
          • 1970-01-01
          • 2021-10-10
          相关资源
          最近更新 更多