【问题标题】:Explode index level of DataFrame展开 DataFrame 的索引级别
【发布时间】:2020-10-28 01:10:07
【问题描述】:

我有一个具有多索引的数据框,其中一个级别的值代表该级别的所有其他值。例如(下面的代码示例):

         D
A B   C   
x a   y  0
  b   y  1
  all z  2

这里all 是表示该级别所有其他值的简写,因此数据框实际上表示:

       D
A B C   
x a y  0
  b y  1
  a z  2
  b z  2

这也是我想要得到的形式。对于在该索引级别中包含all 的每一行,该行在索引级别中的每个其他值都重复。如果它是一列,我可以将每次出现的all 替换为其他值的列表,然后使用DataFrame.explode

所以我考虑重置该索引级别,将所有出现的 all 替换为其他值的列表,然后将 explode 该列替换为索引:

level_values = sorted(set(df.index.unique('B')) - {'all'})
tmp = df.reset_index('B')
mask = df.index.get_level_values('B') == 'all'
col_index = list(tmp.columns).index('B')
for i in np.argwhere(mask).ravel():
    tmp.iat[i, col_index] = level_values
result = tmp.explode('B').set_index('B', append=True)

然而,这似乎效率很低,代码也不是很清楚。现在索引级别的顺序也错误(我的实际数据框有三个以上的索引级别,所以我不能使用swaplevel 重新排序)。

所以我想知道是否有更简洁的方法来分解这些 all 值?


生成示例数据帧的代码:

import numpy as np
import pandas as pd

df = pd.DataFrame(
    data=[[0], [1], [2]],
    index=pd.MultiIndex.from_arrays(
        [['x', 'x', 'x'], ['a', 'b', 'all'], ['y', 'y', 'z']],
        names=['A', 'B', 'C']
    ),
    columns=['D']
)

expected = pd.DataFrame(
    data=[[0], [1], [2], [2]],
    index=pd.MultiIndex.from_arrays(
        [['x', 'x', 'x', 'x'], ['a', 'b', 'a', 'b'], ['y', 'y', 'z', 'z']],
        names=['A', 'B', 'C']
    ),
    columns=['D']
)

【问题讨论】:

    标签: python pandas


    【解决方案1】:
    def fn(x):
        l, rv = [], []
        for v in x:
            if v == 'all':
                rv.append(l[:])
                l = []
            else:
                l.append(v)
                rv.append(v)
        return rv
    
    
    df2 = pd.DataFrame(zip(*df.index)).T.assign(D=df['D'].values)
    df2 = df2.apply(fn).explode(1).rename(columns={0:'A', 1:'B', 2:'C'}).set_index(keys=['A', 'B', 'C'])
    
    print(df2)
    

    打印:

           D
    A B C   
    x a y  0
      b y  1
      a z  2
      b z  2
    

    【讨论】:

      【解决方案2】:

      这是一个范围很窄的函数,可能只适用于这个特定的问题。如果你分享更多数据会很有帮助,其他人可能也有更好的东西:

      def explode_index(df):
      
          #get index where 'all' exists
          for ent in df.index :
              if 'all' in ent:
                  val = ent.index('all') 
      
          #get index name
          expr_var = df.index.get_level_values(1).name
      
          #create query expression
          expr = f"{expr_var}=='all'"
      
          #filter with the query expression
          expr_outcome = df.query(expr)
      
          #get values except 'all'
          others = df.index.get_level_values(expr_var).drop('all').tolist()
      
          #get the before and after :
          tail = list(expr_outcome.index[0])[val+1:]
          head = list(expr_outcome.index[0])[:val]
      
          #create combo  of head,others, tail
          from itertools import product
          add_index = list(product(head,others,tail))
      
          #create new index
          newindex = (df.index.drop(expr_outcome.index)
                     .join(pd.Index(add_index, names=df.index.names),how='outer')
                      )
      
      
          #create new df with reindex
          res = df.reindex(newindex).sort_index()
      
          #assign value to null rows :
          res.loc[add_index] = expr_outcome.iloc[0].item()
          res = res.astype('uint8')
      
          return res
      
      explode_index(df)
      
                 D
      A   B   C   
      x   a   y   0
              z   2
          b   y   1
              z   2
      

      【讨论】:

        【解决方案3】:

        我经常遇到这种情况,但找不到比重置索引和使用 explode 更好的解决方案。

        您可以通过重置整个索引并使用map 来消除一些复杂性,这非常有效。

        # Remember index order before resetting
        idx_names = df.index.names
        df = df.reset_index()
        
        # Build a map to replace "all" with the other unique values
        all_map = {"all": sorted(set(df["B"].unique()) - {'all'})}
        
        # map gives NaNs for non-matched entries, but we can just fill those back in
        df["B"] = df["B"].map(all_map).fillna(df["B"])
        
        # After mapping, explode does the work for us and we can reset the original index
        df.explode("B").set_index(idx_names)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-07-08
          • 2021-07-20
          • 2015-07-19
          • 2019-01-26
          • 2013-08-14
          • 1970-01-01
          • 2018-10-15
          • 2020-06-05
          相关资源
          最近更新 更多