如何在列中平铺分层索引

我有一个在轴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

我怎么做呢?(我尝试了很多,但都无济于事。)

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

{('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}}
436027 次浏览

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

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

注意:如果to级别有名称,您也可以通过this访问它,而不是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']

如果你想保留multiindex第二层的任何聚合信息,你可以试试这个:

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

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

如果您想在级别之间的名称中使用分隔符,这个函数可以很好地工作。

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)
pd.DataFrame(df.to_records()) # multiindex become columns and new index is integers only

你也可以这样做。考虑df作为你的数据帧,并假设有一个两级索引(就像你的例子中那样)

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

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

df.columns = df.columns.tolist()
df.columns = ['_'.join(tup).rstrip('_') for tup in df.columns.values]

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

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

在@jxstanford和@tvt173之后,我写了一个快速函数,无论字符串/int列名如何,它都应该做到这一点:

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

在读完所有的答案后,我想到了这个:

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
  • 单一聚合法:结果变量名为和source一样:

    df.groupby(by="grouper").agg("min").my_flatten_cols()
    
    • df.groupby(by="grouper", as_index = False).agg(...).reset_index ()相同
    • ----- before -----
      val1 2
      石斑鱼
      
      
      ------在-----之后
      石斑鱼val1 2
      0 x 0 1
      1 y 4 5
      < /代码> < / pre > < /李>
      李< / ul > < / >
      
    • 单个源变量,多个聚合:结果变量

      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
        分钟马克斯
        石斑鱼
        
        
        ------在-----之后
        石斑鱼最小最大值
        0 x 0 2
        1 y 4 6
        < /代码> < / pre > < /李>
        李< / ul > < / >
        
      • 多个变量,多个聚合:结果变量名为(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()调用,而且我相信只有当一些列有("colname", "")这样的描述符时才需要rstrip()(如果你在试图修复.columns之前reset_index()就会发生这种情况)
        • ----- before -----
          val1            2
          最小和尺寸
          石斑鱼
          
          
          ------在-----之后
          石斑鱼val1_min 2_sum 2_size
          0 x 0 4 2
          1 y 4 12
          < /代码> < / pre > < /李>
          李< / ul > < / >
          
        • 你需要手动命名结果变量:(这是自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()ing多个groupby语句。
          • ----- before -----
            val1                       2
            Count_of_val1 sum_of_val1 count_of_2 sum_of_2
            石斑鱼
            
            
            ------在-----之后
            石斑鱼count_of_val1 sum_of_val1 count_of_2 sum_of_2
            0 × 2 2 2 4
            1 y 2 10 2 12
            < /代码> < / pre > < /李>
            李< / ul > < / >
            

          由helper函数处理的情况

          • 关卡名称可以是非字符串,例如当列名为整数时,按列号索引pandas DataFrame,因此我们必须使用map(str, ..)进行转换
          • 它们也可以是空的,所以我们必须filter(None, ..)
          • 对于单层列(即除MultiIndex之外的任何列),columns.values返回名称(str,而不是元组)
          • 根据你如何使用.agg(),你可能需要为一列保留最底部的标签或连接多个标签
          • (因为我是熊猫新手?)通常情况下,我希望reset_index()能够以常规方式与group-by列一起工作,因此它默认这样做

最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 string更新:

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

我将分享一个对我有效的简单方法。

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

为了在其他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 \
.groupby('group') \
.agg({'value': ['count']}) \
.pipe(flatten_index) \
.sort_values('value_count')

这个帖子上的所有答案都有点过时了。在pandas 0.24.0版本中,.to_flat_index()完成了你所需要的。

来自panda的自己的文档:

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列的数据帧:

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

对我来说,最简单和最直观的解决方案是使用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

我知道这和上面安迪·海登的答案是一样的,但我认为这种方式更直观,更容易记住(所以我不需要一直引用这个线程),特别是对于熊猫新手用户。

在您可能有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

另一个简单的程序。

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

还有一个简短的,只使用熊猫的方法:

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的列,后面有下划线。你提到过你不关心名字,所以这可能对你有用。在我自己类似的用例中,所有列都有两层,因此这个简单的命令创建了漂亮的名称。

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

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

enter image description here