如何展平列中的层次索引

2024-12-02 08:42:00
admin
原创
153
摘要:问题描述:我有一个数据框,其中在轴 1(列)有一个分层索引(来自groupby.agg操作): USAF WBAN year month day s_PC s_CL s_CD s_CNT tempf ...

问题描述:

我有一个数据框,其中在轴 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 形式的 head

{('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}}

解决方案 1:

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

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

注意:如果目标级别有名称,您也可以通过此名称来访问它,而不是 0。

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

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']

解决方案 2:

此线程上的所有当前答案肯定都有些过时了。从pandas0.24.0 版开始,就.to_flat_index()可以满足您的需要。

来自熊猫自己的文档:

MultiIndex.to_flat_index()

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

来自其文档的一个简单示例:

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以下是如何在带有列的 DataFrame上使用它的示例MultiIndex

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()]

解决方案 3:

pd.DataFrame(df.to_records()) # multiindex become columns and new index is integers only

解决方案 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  \n0  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:

还有另一个简短的方法,仅使用 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 的列的尾部有下划线。您提到您不关心名称,因此这可能对您有用。在我自己类似的用例中,所有列都有两个级别,因此这个简单的命令创建了不错的名称。

解决方案 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:

如果您想保留多索引第二级的任何聚合信息,您可以尝试以下操作:

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

解决方案 9:

实现此目的最具 Python 风格的方法是使用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')

使用 Python 3.6+ 和 f 字符串进行更新:

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')

解决方案 10:

读完所有的答案后,我得出了以下结论:

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] \n                    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**`)`或**.reset_index()**相同`.agg(...)`
+ ```
----- 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 和@Nolan Conaway建议的解决方案(`a.columns = ["_".join(tuple(map(str, t))).rstrip("_") for t in a.columns.values]`),但我不明白为什么`tuple()`需要调用,我相信`rstrip()`只有当某些列具有类似的描述符时才需要(如果您在尝试修复之前`("colname", "")`发生这种情况)`reset_index()``.columns`
+ ```
----- 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
* **您想手动命名结果变量:(**自 pandas 0.20.0 版本起,此功能已被弃用,并且自 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")

    + 其他建议包括:手动设置列:`res.columns = ['A_sum', 'B_sum', 'count']`或`.join()`多个`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
    

辅助函数处理的情况

  • 级别名称可以是非字符串,例如按列号索引 pandas DataFrame,当列名称为整数时,我们必须使用map(str, ..)

  • 它们也可以是空的,所以我们必须filter(None, ..)

  • 对于单级列(即除 MultiIndex 之外的任何内容),columns.values返回名称(str而不是元组)

  • 根据您的使用方式,.agg()您可能需要保留列的最底部标签或连接多个标签

  • (因为我刚接触 Pandas?)很多时候,我希望reset_index()能够以常规方式处理分组列,所以它默认这样做

解决方案 11:

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

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

解决方案 12:

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

df.columns = df.columns.tolist()

解决方案 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)

解决方案 14:

按照@jxstanford 和@tvt173 的步骤,我编写了一个快速函数,它可以解决问题,而不管字符串/整数列名如何:

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()

然后使用该pipe方法在 DataFrame 方法链中应用此函数,位于链中任何其他方法之后但之前groupbyagg

my_df \n  .groupby('group') \n  .agg({'value': ['count']}) \n  .pipe(flatten_index) \n  .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

解决方案 19:

我采用了 monkey patch 方法,否则与其他创建函数的答案类似,但我想要一个内联方法。

def flatten_columns(self):
    """Monkey patchable function onto pandas dataframes to flatten multiindex column names from tuples. Especially useful
    with plotly.

    pd.DataFrame.flatten_columns = flatten_columns

    """
    df = self.copy()
    df.columns = [
        '_'.join([str(x)
                  for x in [y for y in item
                            if y]]) if not isinstance(item, str) else item
        for item in df.columns
    ]
    return df

然后我就pd.Dataframe.flatten_columns = flatten_columns

现在我可以这样做:


df.groupby('C')[['A',B']].agg(['mean','count']).flatten_columns()

说实话,作为 Pandas 的内置方法,这会很不错。

解决方案 20:

如果您的任何级别不是字符串类型并且您收到错误,这对我很有用:

df.columns = ["_".join([str(column) for column in columns]) for columns in df.columns]

解决方案 21:

我发现自己有同样的问题,即如何在之后展平列名agg,但我也想将聚合类型保留为行名。

为此,您可以使用stack()。结果是列名称是平面的,但聚合类型也得以保留。现在您可以安全地导出到 csv,例如。

在此处输入图片描述

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用