【问题标题】:Pandas - How to flatten a hierarchical index in columnsPandas - 如何展平列中的分层索引
【发布时间】:2013-01-08 14:09:17
【问题描述】:

我有一个在轴 1(列)中具有层次索引的数据框(来自 groupby.agg 操作):

     USAF   WBAN  year  month  day  s_PC  s_CL  s_CD  s_CNT  tempf       
                                     sum   sum   sum    sum   amax   amin
0  702730  26451  1993      1    1     1     0    12     13  30.92  24.98
1  702730  26451  1993      1    2     0     0    13     13  32.00  24.98
2  702730  26451  1993      1    3     1    10     2     13  23.00   6.98
3  702730  26451  1993      1    4     1     0    12     13  10.04   3.92
4  702730  26451  1993      1    5     3     0    10     13  19.94  10.94

我想将其展平,使其看起来像这样(名称并不重要 - 我可以重命名):

     USAF   WBAN  year  month  day  s_PC  s_CL  s_CD  s_CNT  tempf_amax  tmpf_amin   
0  702730  26451  1993      1    1     1     0    12     13  30.92          24.98
1  702730  26451  1993      1    2     0     0    13     13  32.00          24.98
2  702730  26451  1993      1    3     1    10     2     13  23.00          6.98
3  702730  26451  1993      1    4     1     0    12     13  10.04          3.92
4  702730  26451  1993      1    5     3     0    10     13  19.94          10.94

我该怎么做? (我尝试了很多,但没有成功。)

根据建议,这里是dict形式的头部

{('USAF', ''): {0: '702730',
  1: '702730',
  2: '702730',
  3: '702730',
  4: '702730'},
 ('WBAN', ''): {0: '26451', 1: '26451', 2: '26451', 3: '26451', 4: '26451'},
 ('day', ''): {0: 1, 1: 2, 2: 3, 3: 4, 4: 5},
 ('month', ''): {0: 1, 1: 1, 2: 1, 3: 1, 4: 1},
 ('s_CD', 'sum'): {0: 12.0, 1: 13.0, 2: 2.0, 3: 12.0, 4: 10.0},
 ('s_CL', 'sum'): {0: 0.0, 1: 0.0, 2: 10.0, 3: 0.0, 4: 0.0},
 ('s_CNT', 'sum'): {0: 13.0, 1: 13.0, 2: 13.0, 3: 13.0, 4: 13.0},
 ('s_PC', 'sum'): {0: 1.0, 1: 0.0, 2: 1.0, 3: 1.0, 4: 3.0},
 ('tempf', 'amax'): {0: 30.920000000000002,
  1: 32.0,
  2: 23.0,
  3: 10.039999999999999,
  4: 19.939999999999998},
 ('tempf', 'amin'): {0: 24.98,
  1: 24.98,
  2: 6.9799999999999969,
  3: 3.9199999999999982,
  4: 10.940000000000001},
 ('year', ''): {0: 1993, 1: 1993, 2: 1993, 3: 1993, 4: 1993}}

【问题讨论】:

  • 您可以添加df[:5].to_dict() 的输出作为示例,供其他人在您的数据集中读取吗?
  • 有一个suggestion on the pandas issue tracker 来实现这个的专用方法。
  • @joelostblom 实际上已经实现(pandas 0.24.0 及更高版本)。我发布了an answer,但基本上现在你可以做dat.columns = dat.columns.to_flat_index()。内置熊猫功能。
  • 对我有用的解决方案是df.reset_index(drop=True, inplace=True) drop=True 是关键部分。

标签: python pandas dataframe


【解决方案1】:

我认为最简单的方法是将列设置为顶级:

df.columns = df.columns.get_level_values(0)

注意:如果 to 级别有名字,你也可以通过这个来访问它,而不是 0。

.

如果您想将/join 您的 MultiIndex 合并为一个索引(假设您的列中只有字符串条目) 您可以:

df.columns = [' '.join(col).strip() for col in df.columns.values]

注意:当没有第二个索引时,我们必须strip 空格。

In [11]: [' '.join(col).strip() for col in df.columns.values]
Out[11]: 
['USAF',
 'WBAN',
 'day',
 'month',
 's_CD sum',
 's_CL sum',
 's_CNT sum',
 's_PC sum',
 'tempf amax',
 'tempf amin',
 'year']

【讨论】:

  • df.reset_index(inplace=True) 可能是另一种解决方案。
  • 一个小评论...如果您想将 _ 用于合并列多级...您可以使用此... df.columns = ['_'.join(col).strip( ) 用于 df.columns.values 中的 col]
  • 小幅修改,仅保留加入的列的下划线:['_'.join(col).rstrip('_') for col in df.columns.values]
  • 效果很好,如果您只想要第二列,请使用:df.columns = [col[1] for col in df.columns.values]
  • 如果你想使用sum s_CD而不是s_CD sum,可以使用df.columns = ['_'.join(col).rstrip('_') for col in [c[::-1] for c in df.columns.values]]
【解决方案2】:

此线程上的所有当前答案一定有点过时了。从 pandas 0.24.0 版开始,.to_flat_index() 可以满足您的需求。

来自熊猫的own documentation

MultiIndex.to_flat_index()

将 MultiIndex 转换为包含级别值的元组索引。

其文档中的一个简单示例:

import pandas as pd
print(pd.__version__) # '0.23.4'
index = pd.MultiIndex.from_product(
        [['foo', 'bar'], ['baz', 'qux']],
        names=['a', 'b'])

print(index)
# MultiIndex(levels=[['bar', 'foo'], ['baz', 'qux']],
#           codes=[[1, 1, 0, 0], [0, 1, 0, 1]],
#           names=['a', 'b'])

申请to_flat_index():

index.to_flat_index()
# Index([('foo', 'baz'), ('foo', 'qux'), ('bar', 'baz'), ('bar', 'qux')], dtype='object')

用它来替换现有的pandas

如何在dat 上使用它的示例,这是一个带有MultiIndex 列的DataFrame:

dat = df.loc[:,['name','workshop_period','class_size']].groupby(['name','workshop_period']).describe()
print(dat.columns)
# MultiIndex(levels=[['class_size'], ['count', 'mean', 'std', 'min', '25%', '50%', '75%', 'max']],
#            codes=[[0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 2, 3, 4, 5, 6, 7]])

dat.columns = dat.columns.to_flat_index()
print(dat.columns)
# Index([('class_size', 'count'),  ('class_size', 'mean'),
#     ('class_size', 'std'),   ('class_size', 'min'),
#     ('class_size', '25%'),   ('class_size', '50%'),
#     ('class_size', '75%'),   ('class_size', 'max')],
#  dtype='object')

就地展平和重命名

可能值得注意的是,如何将它与简单的列表理解(感谢@Skippy 和@mmann1123)结合起来以加入元素,这样您的结果列名就是简单的字符串,由例如下划线分隔:

dat.columns = ["_".join(a) for a in dat.columns.to_flat_index()]

【讨论】:

  • 也许值得加入元组的元素,否则你最终会得到疯狂的名字
  • @mmann1123 确实如此。 FWIW:dat.columns = ["_".join(a) for a in dat.columns.to_flat_index()].
  • 仅供参考,相反的是:df.columns = pd.MultiIndex.from_tuples(df.columns)。这会将扁平元组转换回 MultiIndex。
【解决方案3】:
pd.DataFrame(df.to_records()) # multiindex become columns and new index is integers only

【讨论】:

  • 这可行,但留下难以以编程方式访问且不可查询的列名
  • 这不适用于最新版本的 pandas。它适用于 0.18 但不适用于 0.20(截至目前的最新版本)
  • @dmeu 保留列名 pd.DataFrame(df.to_records(), columns=df.index.names + list(df.columns))
  • 它为我保留列名作为元组,并保留我使用的索引:pd.DataFrame(df_volume.to_records(), index=df_volume.index).drop('index', axis=1)
【解决方案4】:

Andy Hayden 的回答肯定是最简单的方法——如果你想避免重复的列标签,你需要稍微调整一下

In [34]: df
Out[34]: 
     USAF   WBAN  day  month  s_CD  s_CL  s_CNT  s_PC  tempf         year
                               sum   sum    sum   sum   amax   amin      
0  702730  26451    1      1    12     0     13     1  30.92  24.98  1993
1  702730  26451    2      1    13     0     13     0  32.00  24.98  1993
2  702730  26451    3      1     2    10     13     1  23.00   6.98  1993
3  702730  26451    4      1    12     0     13     1  10.04   3.92  1993
4  702730  26451    5      1    10     0     13     3  19.94  10.94  1993


In [35]: mi = df.columns

In [36]: mi
Out[36]: 
MultiIndex
[(USAF, ), (WBAN, ), (day, ), (month, ), (s_CD, sum), (s_CL, sum), (s_CNT, sum), (s_PC, sum), (tempf, amax), (tempf, amin), (year, )]


In [37]: mi.tolist()
Out[37]: 
[('USAF', ''),
 ('WBAN', ''),
 ('day', ''),
 ('month', ''),
 ('s_CD', 'sum'),
 ('s_CL', 'sum'),
 ('s_CNT', 'sum'),
 ('s_PC', 'sum'),
 ('tempf', 'amax'),
 ('tempf', 'amin'),
 ('year', '')]

In [38]: ind = pd.Index([e[0] + e[1] for e in mi.tolist()])

In [39]: ind
Out[39]: Index([USAF, WBAN, day, month, s_CDsum, s_CLsum, s_CNTsum, s_PCsum, tempfamax, tempfamin, year], dtype=object)

In [40]: df.columns = ind




In [46]: df
Out[46]: 
     USAF   WBAN  day  month  s_CDsum  s_CLsum  s_CNTsum  s_PCsum  tempfamax  tempfamin  \
0  702730  26451    1      1       12        0        13        1      30.92      24.98   
1  702730  26451    2      1       13        0        13        0      32.00      24.98   
2  702730  26451    3      1        2       10        13        1      23.00       6.98   
3  702730  26451    4      1       12        0        13        1      10.04       3.92   
4  702730  26451    5      1       10        0        13        3      19.94      10.94   




   year  
0  1993  
1  1993  
2  1993  
3  1993  
4  1993

【讨论】:

    【解决方案5】:
    df.columns = ['_'.join(tup).rstrip('_') for tup in df.columns.values]
    

    【讨论】:

      【解决方案6】:

      如果你想保留多索引第二级的任何聚合信息,你可以试试这个:

      In [1]: new_cols = [''.join(t) for t in df.columns]
      Out[1]:
      ['USAF',
       'WBAN',
       'day',
       'month',
       's_CDsum',
       's_CLsum',
       's_CNTsum',
       's_PCsum',
       'tempfamax',
       'tempfamin',
       'year']
      
      In [2]: df.columns = new_cols
      

      【讨论】:

      • new_cols 未定义。
      【解决方案7】:

      对我来说最简单和最直观的解决方案是使用 get_level_values 组合列名。当您对同一列执行多个聚合时,这可以防止列名重复:

      level_one = df.columns.get_level_values(0).astype(str)
      level_two = df.columns.get_level_values(1).astype(str)
      df.columns = level_one + level_two
      

      如果您想在列之间使用分隔符,您可以这样做。这将返回与 Seiji Armstrong 对已接受答案的评论相同的内容,该答案仅包括具有两个索引级别的值的列的下划线:

      level_one = df.columns.get_level_values(0).astype(str)
      level_two = df.columns.get_level_values(1).astype(str)
      column_separator = ['_' if x != '' else '' for x in level_two]
      df.columns = level_one + column_separator + level_two
      

      我知道这与上面 Andy Hayden 的出色答案的作用相同,但我认为这种方式更直观,更容易记住(所以我不必一直引用这个线程),尤其是对于新手熊猫用户。

      在您可能有 3 列级别的情况下,此方法也更具可扩展性。

      level_one = df.columns.get_level_values(0).astype(str)
      level_two = df.columns.get_level_values(1).astype(str)
      level_three = df.columns.get_level_values(2).astype(str)
      df.columns = level_one + level_two + level_three
      

      【讨论】:

        【解决方案8】:

        最pythonic的方法是使用map函数。

        df.columns = df.columns.map(' '.join).str.strip()
        

        输出print(df.columns):

        Index(['USAF', 'WBAN', 'day', 'month', 's_CD sum', 's_CL sum', 's_CNT sum',
               's_PC sum', 'tempf amax', 'tempf amin', 'year'],
              dtype='object')
        

        使用带有 f 字符串的 Python 3.6+ 更新:

        df.columns = [f'{f} {s}' if s != '' else f'{f}' 
                      for f, s in df.columns]
        
        print(df.columns)
        

        输出:

        Index(['USAF', 'WBAN', 'day', 'month', 's_CD sum', 's_CL sum', 's_CNT sum',
               's_PC sum', 'tempf amax', 'tempf amin', 'year'],
              dtype='object')
        

        【讨论】:

          【解决方案9】:

          阅读所有答案后,我想出了这个:

          def __my_flatten_cols(self, how="_".join, reset_index=True):
              how = (lambda iter: list(iter)[-1]) if how == "last" else how
              self.columns = [how(filter(None, map(str, levels))) for levels in self.columns.values] \
                              if isinstance(self.columns, pd.MultiIndex) else self.columns
              return self.reset_index() if reset_index else self
          pd.DataFrame.my_flatten_cols = __my_flatten_cols
          

          用法:

          给定一个数据框:

          df = pd.DataFrame({"grouper": ["x","x","y","y"], "val1": [0,2,4,6], 2: [1,3,5,7]}, columns=["grouper", "val1", 2])
          
            grouper  val1  2
          0       x     0  1
          1       x     2  3
          2       y     4  5
          3       y     6  7
          
          • 单一聚合方法:结果变量命名为与源相同

            df.groupby(by="grouper").agg("min").my_flatten_cols()
            
            • df.groupby(by="grouper",相同as_index=False).agg(...).reset_index()
            • ----- before -----
                         val1  2
                grouper         
              
              ------ after -----
                grouper  val1  2
              0       x     0  1
              1       y     4  5
              
          • 单个源变量,多个聚合:结果变量以统计数据命名

            df.groupby(by="grouper").agg({"val1": [min,max]}).my_flatten_cols("last")
            
            • a = df.groupby(..).agg(..); a.columns = a.columns.droplevel(0); a.reset_index()
            • ----- before -----
                          val1    
                         min max
                grouper         
              
              ------ after -----
                grouper  min  max
              0       x    0    2
              1       y    4    6
              
          • 多个变量,多个聚合:生成的变量名为 (varname)_(statname)

            df.groupby(by="grouper").agg({"val1": min, 2:[sum, "size"]}).my_flatten_cols()
            # you can combine the names in other ways too, e.g. use a different delimiter:
            #df.groupby(by="grouper").agg({"val1": min, 2:[sum, "size"]}).my_flatten_cols(" ".join)
            
            • 在后台运行a.columns = ["_".join(filter(None, map(str, levels))) for levels in a.columns.values](因为agg() 的这种形式会在列上产生MultiIndex)。
            • 如果您没有my_flatten_cols 帮助程序,输入@Seigi 建议的解决方案可能会更容易:a.columns = ["_".join(t).rstrip("_") for t in a.columns.values],在这种情况下它的工作方式类似(但如果您在列上有数字标签,则会失败) )
            • 要处理列上的数字标签,您可以使用@jxstanford and @Nolan Conaway (a.columns = ["_".join(tuple(map(str, t))).rstrip("_") for t in a.columns.values]) 建议的解决方案,但我不明白为什么需要调用tuple(),我相信rstrip() 只是如果某些列具有像 ("colname", "") 这样的描述符(如果您在尝试修复 .columns 之前 reset_index() 可能会发生这种情况)
            • ----- before -----
                         val1           2     
                         min       sum    size
                grouper              
              
              ------ after -----
                grouper  val1_min  2_sum  2_size
              0       x         0      4       2
              1       y         4     12       2
              
          • 您想手动命名结果变量:(这是deprecated since pandas 0.20.0no adequate alternative as of 0.23

            df.groupby(by="grouper").agg({"val1": {"sum_of_val1": "sum", "count_of_val1": "count"},
                                               2: {"sum_of_2":    "sum", "count_of_2":    "count"}}).my_flatten_cols("last")
            
            • Other 建议 include:手动设置列:res.columns = ['A_sum', 'B_sum', 'count'].join()ing 多个 groupby 语句。
            • ----- before -----
                                 val1                      2         
                        count_of_val1 sum_of_val1 count_of_2 sum_of_2
                grouper                                              
              
              ------ after -----
                grouper  count_of_val1  sum_of_val1  count_of_2  sum_of_2
              0       x              2            2           2         4
              1       y              2           10           2        12
              

          辅助函数处理的案例

          • 级别名称可以是非字符串,例如Index pandas DataFrame by column numbers, when column names are integers,所以我们必须转换为map(str, ..)
          • 他们也可以是空的,所以我们必须filter(None, ..)
          • 对于单级列(即除 MultiIndex 之外的任何内容),columns.values 返回名称(str,而不是元组)
          • 根据您使用.agg() 的方式,您可能需要为一列保留最底部的标签或连接多个标签
          • (因为我是 pandas 的新手?)我经常希望 reset_index() 能够以常规方式使用分组列,所以默认情况下会这样做

          【讨论】:

          • 非常好的答案,你能解释一下'["".join(tuple(map(str, t))).rstrip("") 的工作吗for t in a.columns.values]',在此先感谢
          • @Vineet 我更新了我的帖子,表明我提到了 sn-p 表明它与我的解决方案具有相似的效果。如果您想详细了解为什么需要tuple(),您可能想对 jxstanford 的帖子发表评论。否则,检查提供的示例中的.columns.values 可能会有所帮助:[('val1', 'min'), (2, 'sum'), (2, 'size')]。 1) for t in a.columns.values 在列上循环,第二列 t == (2, 'sum'); 2)map(str, t)str()应用于每个“级别”,产生('2', 'sum'); 3) "_".join(('2','sum')) 结果为“2_sum”,
          【解决方案10】:

          处理多层次和混合类型的通用解决方案:

          df.columns = ['_'.join(tuple(map(str, t))) for t in df.columns.values]
          

          【讨论】:

          • 如果还有非分层列:df.columns = ['_'.join(tuple(map(str, t))).rstrip('_') for t in df.columns.values]
          • 谢谢。找了很久。因为我的多级索引包含整数值。它解决了我的问题:)
          【解决方案11】:

          又一个简短的,仅使用 pandas 方法:

          df.columns = df.columns.to_flat_index().str.join('_')
          

          作为输出的产量:

              USAF_  WBAN_  day_  month_  ...  s_PC_sum  tempf_amax  tempf_amin  year_
          0  702730  26451     1       1  ...       1.0       30.92       24.98   1993
          1  702730  26451     2       1  ...       0.0       32.00       24.98   1993
          2  702730  26451     3       1  ...       1.0       23.00        6.98   1993
          3  702730  26451     4       1  ...       1.0       10.04        3.92   1993
          4  702730  26451     5       1  ...       3.0       19.94       10.94   1993
          

          您会注意到不属于 MultiIndex 的列的尾随下划线。你提到你不关心这个名字,所以这可能对你有用。在我自己的类似用例中,所有列都有两个级别,所以这个简单的命令创建了很好的名称。

          【讨论】:

            【解决方案12】:

            可能有点晚了,但如果您不担心重复的列名:

            df.columns = df.columns.tolist()
            

            【讨论】:

            • 对我来说,这会将列的名称更改为类似元组的名称:(year, )(tempf, amax)
            【解决方案13】:

            如果您想在级别之间的名称中使用分隔符,此功能效果很好。

            def flattenHierarchicalCol(col,sep = '_'):
                if not type(col) is tuple:
                    return col
                else:
                    new_col = ''
                    for leveli,level in enumerate(col):
                        if not level == '':
                            if not leveli == 0:
                                new_col += sep
                            new_col += level
                    return new_col
            
            df.columns = df.columns.map(flattenHierarchicalCol)
            

            【讨论】:

            • 我喜欢。忽略列不分层的情况,这可以简化很多:df.columns = ["_".join(filter(None, c)) for c in df.columns]
            【解决方案14】:

            在@jxstanford 和@tvt173 之后,我编写了一个快速函数,无论字符串/int 列名如何,都可以解决问题:

            def flatten_cols(df):
                df.columns = [
                    '_'.join(tuple(map(str, t))).rstrip('_') 
                    for t in df.columns.values
                    ]
                return df
            

            【讨论】:

              【解决方案15】:

              我将分享一个对我有用的直截了当的方法。

              [" ".join([str(elem) for elem in tup]) for tup in df.columns.tolist()]
              #df = df.reset_index() if needed
              

              【讨论】:

                【解决方案16】:

                要在其他 DataFrame 方法链中展平 MultiIndex,请定义如下函数:

                def flatten_index(df):
                  df_copy = df.copy()
                  df_copy.columns = ['_'.join(col).rstrip('_') for col in df_copy.columns.values]
                  return df_copy.reset_index()
                

                然后在 groupbyagg 之后,但在链中的任何其他方法之前,使用 pipe method 将此函数应用到 DataFrame 方法链中:

                my_df \
                  .groupby('group') \
                  .agg({'value': ['count']}) \
                  .pipe(flatten_index) \
                  .sort_values('value_count')
                

                【讨论】:

                  【解决方案17】:

                  你也可以这样做。将df 视为您的数据框并假设一个两级索引(如您的示例中的情况)

                  df.columns = [(df.columns[i][0])+'_'+(datadf_pos4.columns[i][1]) for i in range(len(df.columns))]
                  

                  【讨论】:

                    【解决方案18】:

                    另一个简单的例程。

                    def flatten_columns(df, sep='.'):
                        def _remove_empty(column_name):
                            return tuple(element for element in column_name if element)
                        def _join(column_name):
                            return sep.join(column_name)
                    
                        new_columns = [_join(_remove_empty(column)) for column in df.columns.values]
                        df.columns = new_columns
                    

                    【讨论】:

                    • 不错的选择,在代码末尾使用return df
                    • @Marukox,请注意 python 中的 pandas 数据帧是可变的。您可以对它们进行操作而无需复制/返回。这也是这个函数没有返回语句的原因。
                    猜你喜欢
                    • 1970-01-01
                    • 2022-07-12
                    • 1970-01-01
                    • 2021-02-03
                    相关资源
                    最近更新 更多