Pandas: построение сводной таблицы с подытогами на каждом уровне мультииндекса

Рассмотрим следующий дейта-фрейм:

a       b       c      Sce1     Sce2    Sce3    Sce4    Sce5    Sc6
Animal  Ground  Dog    0.0      0.9     0.5     0.0     0.3     0.4  
Animal  Ground  Cat    0.6      0.5     0.3     0.5     1.0     0.2 
Animal  Air     Eagle  1.0      0.1     0.1     0.6     0.9     0.1 
Animal  Air     Owl    0.3      0.1     0.5     0.3     0.5     0.9     
Object  Metal   Car    0.3      0.3     0.8     0.6     0.5     0.6 
Object  Metal   Bike   0.5      0.1     0.4     0.7     0.4     0.2 
Object  Wood    Chair  0.9      0.6     0.1     0.9     0.2     0.8 
Object  Wood    Table  0.9      0.6     0.6     0.1     0.9     0.7 

Нужно получить сводный отчет с подытогами:

                     Sce1  Sce2  Sce3  Sce4  Sce5  Sc6
a      b      c                                       
Animal Air    Eagle   1.0   0.1   0.1   0.6   0.9  0.1
              Owl     0.3   0.1   0.5   0.3   0.5  0.9
              Total   1.3   0.2   0.6   0.9   1.4  1.0
       Ground Cat     0.6   0.5   0.3   0.5   1.0  0.2
              Dog     0.0   0.9   0.5   0.0   0.3  0.4
              Total   0.6   1.4   0.8   0.5   1.3  0.6
       Total  Total   1.9   1.6   1.4   1.4   2.7  1.6
Object Metal  Bike    0.5   0.1   0.4   0.7   0.4  0.2
              Car     0.3   0.3   0.8   0.6   0.5  0.6
              Total   0.8   0.4   1.2   1.3   0.9  0.8
       Wood   Chair   0.9   0.6   0.1   0.9   0.2  0.8
              Table   0.9   0.6   0.6   0.1   0.9  0.7
              Total   1.8   1.2   0.7   1.0   1.1  1.5
       Total  Total   2.6   1.6   1.9   2.3   2.0  2.3
Total  Total  Total   4.5   3.2   3.3   3.7   4.7  3.9

Добиться такого результата можно с помощью следующего паттерна:

fields = ['a', 'b', 'c']
pd.concat([
        df.assign(
            **{x: '' for x in fields[i:]}
        ).groupby(fields).sum() for i in range(1, 4)
    ]).sort_index()

fields — список с уровнями индекса.

С помощью df.assign создаем несколько дейта-фреймов (по одному на уровень индекса) с колонками итогов. Для этого:

  • проходимся по списку полей, формируя словарь параметров;
  • группируем данные по уровням (fields);
  • применяем функцию аггрегации (sum).

Затем объединяем отдельные дейта-фреймы, созданные df.assign, с помощью pd.concat в один финальный дейта-фрейм и сортируем индекс.

Результат:

                     Sce1  Sce2  Sce3  Sce4  Sce5  Sc6
a      b      c                                       
Animal                1.9   1.6   1.4   1.4   2.7  1.6
       Air            1.3   0.2   0.6   0.9   1.4  1.0
              Eagle   1.0   0.1   0.1   0.6   0.9  0.1
              Owl     0.3   0.1   0.5   0.3   0.5  0.9
       Ground         0.6   1.4   0.8   0.5   1.3  0.6
              Cat     0.6   0.5   0.3   0.5   1.0  0.2
              Dog     0.0   0.9   0.5   0.0   0.3  0.4
Object                2.6   1.6   1.9   2.3   2.0  2.3
       Metal          0.8   0.4   1.2   1.3   0.9  0.8
              Bike    0.5   0.1   0.4   0.7   0.4  0.2
              Car     0.3   0.3   0.8   0.6   0.5  0.6
       Wood           1.8   1.2   0.7   1.0   1.1  1.5
              Chair   0.9   0.6   0.1   0.9   0.2  0.8
              Table   0.9   0.6   0.6   0.1   0.9  0.7

Обратите внимание: для колонки подытогов опущена метка. Это сделано для упрощения примера. Метку можно добавить, изменив строку:

**{x: '' for x in fields[i:]}

на

**{x: 'total' for x in fields[i:]}

Но в таком случае понадобится позаботиться о том, чтобы строки с подытогами находились в нужной позиции, т. к. сортировка индекса с помощью sort_index() переместит total в позицию, соответствующую лексикографическому порядку.


За пример спасибо piRSquared.