Pandas - 如何在列中展平分层索引

Ros*_*s R 266 python dataframe pandas

我有一个数据框,在第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
Run Code Online (Sandbox Code Playgroud)

我想扁平它,所以它看起来像这样(名字不重要 - 我可以重命名):

     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
Run Code Online (Sandbox Code Playgroud)

我该怎么做呢?(我已经尝试了很多,但无济于事.)

根据一个建议,这里是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}}
Run Code Online (Sandbox Code Playgroud)

And*_*den 391

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

df.columns = df.columns.get_level_values(0)
Run Code Online (Sandbox Code Playgroud)

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

.

如果你想将join你的MultiIndex 组合成一个索引(假设你的列中只有字符串条目),你可以:

df.columns = [' '.join(col).strip() for col in df.columns.values]
Run Code Online (Sandbox Code Playgroud)

注意: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']
Run Code Online (Sandbox Code Playgroud)

  • 仅对连接的cols维护下划线的微小修改:`['_'.join(col).rstrip('_')for col in df.columns.values]` (24认同)
  • *df.reset_index(inplace = True)*可能是另一种解决方案. (11认同)
  • 一个小评论...如果你想使用_作为组合列多级...你可以使用这个... df.columns = ['_'.join(col).strip()用于df.columns中的col.值] (5认同)
  • 如果你想使用“sum s_CD”而不是“s_CD sum”,可以对 [c[::-1 中的 col] 执行“df.columns = ['_'.join(col).rstrip('_')” ] for c in df.columns.values]]`。 (2认同)

小智 75

pd.DataFrame(df.to_records()) # multiindex become columns and new index is integers only
Run Code Online (Sandbox Code Playgroud)

  • 这可行,但留下了很难以编程方式访问且不可查询的列名 (3认同)
  • @ TH22适用于0.23.3版本 (3认同)
  • @dmeu **保留列名称** `pd.DataFrame(df.to_records(), columns=df.index.names + list(df.columns))` (2认同)

The*_*eke 38

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
Run Code Online (Sandbox Code Playgroud)

  • 感谢Theodros!这是处理所有情况的唯一正确解决方案! (2认同)

onl*_*tom 29

该线程上的所有当前答案都必须已过时。从pandas0.24.0版开始,.to_flat_index()您需要做什么。

从熊猫自己的文档中

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'])
Run Code Online (Sandbox Code Playgroud)

申请to_flat_index()

index.to_flat_index()
# Index([('foo', 'baz'), ('foo', 'qux'), ('bar', 'baz'), ('bar', 'qux')], dtype='object')
Run Code Online (Sandbox Code Playgroud)

用它来替换现有的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')
Run Code Online (Sandbox Code Playgroud)

  • 也许值得加入元组的元素,否则你最终会得到疯狂的名字 (5认同)
  • @mmann1123 确实如此。FWIW:`dat.columns = ["_".join(a) for a in dat.columns.to_flat_index()]`。 (2认同)
  • 仅供参考,相反的是:`df.columns = pd.MultiIndex.from_tuples(df.columns)`。这会将展平的元组转换回 MultiIndex。 (2认同)

bod*_*y11 16

对我来说,最简单、最直观的解决方案是使用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
Run Code Online (Sandbox Code Playgroud)

如果您想在列之间使用分隔符,您可以这样做。这将返回与 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
Run Code Online (Sandbox Code Playgroud)

我知道这与上面安迪·海登 (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
Run Code Online (Sandbox Code Playgroud)


tvt*_*173 15

df.columns = ['_'.join(tup).rstrip('_') for tup in df.columns.values]
Run Code Online (Sandbox Code Playgroud)


Zel*_*ny7 13

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

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
Run Code Online (Sandbox Code Playgroud)


Sco*_*ton 8

使用map函数的最pythonic方法。

df.columns = df.columns.map(' '.join).str.strip()
Run Code Online (Sandbox Code Playgroud)

输出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')
Run Code Online (Sandbox Code Playgroud)

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

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

print(df.columns)
Run Code Online (Sandbox Code Playgroud)

输出:

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


小智 5

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

df.columns = df.columns.tolist()
Run Code Online (Sandbox Code Playgroud)


jxs*_*ord 5

处理多个级别和混合类型的常规解决方案:

df.columns = ['_'.join(tuple(map(str, t))) for t in df.columns.values]
Run Code Online (Sandbox Code Playgroud)


Nic*_*lay 5

阅读完所有答案后,我想到了:

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
Run Code Online (Sandbox Code Playgroud)

用法:

给定一个数据框:

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
Run Code Online (Sandbox Code Playgroud)
  • 单一聚合方法与源名称相同的结果变量:

    df.groupby(by="grouper").agg("min").my_flatten_cols()
    
    Run Code Online (Sandbox Code Playgroud)
    • 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
      
      Run Code Online (Sandbox Code Playgroud)
  • 单源变量,多个聚合以统计信息命名的结果变量:

    df.groupby(by="grouper").agg({"val1": [min,max]}).my_flatten_cols("last")
    
    Run Code Online (Sandbox Code Playgroud)
    • 与相同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
      
      Run Code Online (Sandbox Code Playgroud)
  • 多个变量,多个聚合:名为(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)
    
    Run Code Online (Sandbox Code Playgroud)
    • 在后台运行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 Conawaya.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
      
      Run Code Online (Sandbox Code Playgroud)
  • 要手动命名结果变量:(这是因为大熊猫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")
    
    Run Code Online (Sandbox Code Playgroud)
    • 其他建议包括:手动设置列: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
      
      Run Code Online (Sandbox Code Playgroud)

助手功能处理的案件

  • 级别名称可以是非字符串,例如,当列名称是整数时按列号使用Index Pandas DataFrame,因此我们必须使用map(str, ..)
  • 他们也可以是空的,所以我们必须 filter(None, ..)
  • 对于单级列(即,除MultiIndex之外的任何其他列),columns.values返回名称(str,不是元组)
  • 根据您的使用方式,.agg()您可能需要保留一列的最底部标签或连接多个标签
  • (因为我是熊猫新手?),我希望reset_index()能够以常规方式使用“分组依据”列,因此默认情况下会这样做


ode*_*dbd 5

另一个简短的,仅使用熊猫方法:

df.columns = df.columns.to_flat_index().str.join('_')
Run Code Online (Sandbox Code Playgroud)

产量作为输出:

    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
Run Code Online (Sandbox Code Playgroud)

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