Flatten DataFrame с многоиндексными столбцами

Я бы хотел конвертировать Pandas DataFrame, который был получен из сводной таблицы в представление строки, как показано ниже.

Вот где я нахожусь:

import pandas as pd
import numpy as np
df = pd.DataFrame({
    'goods': ['a', 'a', 'b', 'b', 'b'],
    'stock': [5, 10, 30, 40, 10],
    'category': ['c1', 'c2', 'c1', 'c2', 'c1'],
    'date': pd.to_datetime(['2014-01-01', '2014-02-01', '2014-01-06', '2014-02-09', '2014-03-09'])
})
# we don't care about year in this example
df['month'] = df['date'].map(lambda x: x.month)
piv = df.pivot_table(["stock"], "month", ["goods", "category"], aggfunc="sum")
piv = piv.reindex(np.arange(piv.index[0], piv.index[-1] + 1))
piv = piv.ffill(axis=0)
piv = piv.fillna(0)
print piv

что приводит к

stock            
goods        a       b    
category    c1  c2  c1  c2
month                     
1            5   0  30   0
2            5  10  30  40
3            5  10  10  40

И здесь я хочу добраться.

goods category month stock
    a       c1     1     5
    a       c1     2     0
    a       c1     3     0
    a       c2     1     0
    a       c2     2    10
    a       c2     3     0
    b       c1     1    30
    b       c1     2     0
    b       c1     3    10
    b       c2     1     0
    b       c2     2    40
    b       c2     3     0

Ранее, я использовал

piv = piv.stack()
piv = piv.reset_index()
print piv

чтобы избавиться от мультииндекса, но это приводит к этому, потому что я теперь поворачиваюсь на двух столбцах (["goods", "category"]):

      month category stock    
goods                    a   b
0         1       c1     5  30
1         1       c2     0   0
2         2       c1     5  30
3         2       c2    10  40
4         3       c1     5  10
5         3       c2    10  40

Кто-нибудь знает, как я могу избавиться от мультииндекса в столбце и получить результат в DataFrame примерного формата?

Ответ 1

>>> piv.unstack().reset_index().drop('level_0', axis=1)
   goods category  month   0
0      a       c1      1   5
1      a       c1      2   5
2      a       c1      3   5
3      a       c2      1   0
4      a       c2      2  10
5      a       c2      3  10
6      b       c1      1  30
7      b       c1      2  30
8      b       c1      3  10
9      b       c2      1   0
10     b       c2      2  40
11     b       c2      3  40

тогда вам нужно только изменить имя последнего столбца от 0 до stock.

Ответ 2

Мне кажется, что melt (aka univot) очень близок к тому, что вы хотите сделать:

In [11]: pd.melt(piv)
Out[11]:
      NaN goods category  value
0   stock     a       c1      5
1   stock     a       c1      5
2   stock     a       c1      5
3   stock     a       c2      0
4   stock     a       c2     10
5   stock     a       c2     10
6   stock     b       c1     30
7   stock     b       c1     30
8   stock     b       c1     10
9   stock     b       c2      0
10  stock     b       c2     40
11  stock     b       c2     40

Там находится столбец-жулик (запас), который появляется здесь, что заголовок столбца является постоянным в piv. Если мы сначала откачем, расплав работает OOTB:

In [12]: piv.columns = piv.columns.droplevel(0)

In [13]: pd.melt(piv)
Out[13]:
   goods category  value
0      a       c1      5
1      a       c1      5
2      a       c1      5
3      a       c2      0
4      a       c2     10
5      a       c2     10
6      b       c1     30
7      b       c1     30
8      b       c1     10
9      b       c2      0
10     b       c2     40
11     b       c2     40

Изменить: вышеописанное фактически снижает индекс, вам нужно сделать столбец с reset_index:

In [21]: pd.melt(piv.reset_index(), id_vars=['month'], value_name='stock')
Out[21]:
    month goods category  stock
0       1     a       c1      5
1       2     a       c1      5
2       3     a       c1      5
3       1     a       c2      0
4       2     a       c2     10
5       3     a       c2     10
6       1     b       c1     30
7       2     b       c1     30
8       3     b       c1     10
9       1     b       c2      0
10      2     b       c2     40
11      3     b       c2     40

Ответ 3

Я знаю, что на этот вопрос уже был дан ответ, но для моей проблемы многоиндексных столбцов набора данных предоставленное решение было неэффективным. Поэтому здесь я выкладываю другое решение для разворачивания многоиндексных столбцов с помощью панд.

Вот проблема, которая у меня была:

enter image description here

Как видно, фрейм данных состоит из 3 многоиндексных и двухуровневых многоиндексных столбцов.

Желаемый формат данных:

enter image description here

Когда я попробовал параметры, указанные выше, функция pd.melt не позволяла иметь более одного столбца в атрибуте var_name. Поэтому каждый раз, когда я пытался расплавиться, я терял какой-то атрибут со своего стола.

Решение, которое я нашел, состояло в том, чтобы применить функцию двойного стека к моему фрейму данных.

Перед кодированием стоит заметить, что желаемое имя var_name для моего столбца непивотированной таблицы было "Populacao residente em domicilios speculares ocupados" (см. Код ниже). Поэтому для всех моих записей значений они должны быть сложены во вновь созданном новом столбце var_name.

Вот фрагмент кода:

import pandas as pd

# reading my table

df = pd.read_excel(r'my_table.xls', sep=',', header=[2,3], encoding='latin3', 
               index_col=[0,1,2], na_values=['-', ' ', '*'], squeeze=True).fillna(0)

df.index.names = ['COD_MUNIC_7', 'NOME_MUN', 'TIPO']
df.columns.names = ['sexo', 'faixa_etaria']


df.head()


# making the stacking:

df = pd.DataFrame(pd.Series(df.stack(level=0).stack(), name='Populacao residente em domicilios particulares ocupados')).reset_index()


df.head()

Другое решение, которое я нашел, состояло в том, чтобы сначала применить функцию стекирования к фрейму данных, а затем применить расплав.

Вот альтернативный код:

df = df.stack('faixa_etaria').reset_index().melt(id_vars=['COD_MUNIC_7', 'NOME_MUN','TIPO', 'faixa_etaria'],
                  value_vars=['Homens', 'Mulheres'],
                  value_name='Populacao residente em domicilios particulares ocupados', 
                  var_name='sexo')

df.head()

Искренне Ваш,

Филипп Рискалла Лил