Сообщить об ошибке.

Методы .stack() и .unstack() объекта DataFrame в pandas

Сведение иерархических уровней меток столбцов/строк

Содержание:


Обзор DataFrame.stack() и DataFrame.unstack()

С методом DataFrame.pivot() тесно связаны методы DataFrame.stack() и DataFrame.unstack(), которые доступны для Series и DataFrame. Эти методы предназначены для совместной работы с объектами MultiIndex.

  • метод DataFrame.stack(): "сводит" уровень (возможно, иерархический) меток столбцов, возвращая a DataFrame с новым, самым внутренним уровнем меток строк.

  • метод DataFrame.unstack(): (обратная операция DataFrame.stack()) "сводит" уровень индекса строки (возможно, иерархический) к оси столбца, создавая измененный DataFrame с новым, самым внутренним уровнем меток столбцов.

tuples = [
   ["bar", "bar", "baz", "baz", "foo", "foo", "qux", "qux"],
   ["one", "two", "one", "two", "one", "two", "one", "two"],
]

>>> import pandas as pd
>>> import numpy as np
>>> index = pd.MultiIndex.from_arrays(tuples, names=["first", "second"])
>>> df = pd.DataFrame(np.random.randn(8, 2), index=index, columns=["A", "B"])
>>> df2 = df[:4]
>>> df2
df2
                     A         B
first second                    
bar   one    -0.325765 -1.455506
      two    -0.909177  0.081546
baz   one     0.128316 -0.435166
      two    -0.308656  2.402403

Метод DataFrame.stack() "сжимает" уровень в столбцах DataFrame, и получает одно из следующих значений:

  • Series, в случае Index в столбцах.
  • DataFrame, в случае MultiIndex в столбцах.

Если столбцы имеют MultiIndex, то можно выбрать, какой уровень складывать. Уровень с накоплением становится новым самым низким уровнем в MultiIndex по столбцам:

>>> stacked = df2.stack(future_stack=True)
>>> stacked
first  second   
bar    one     A   -0.325765
               B   -1.455506
       two     A   -0.909177
               B    0.081546
baz    one     A    0.128316
               B   -0.435166
       two     A   -0.308656
               B    2.402403
dtype: float64

С “многоуровневым” DataFrame или Series (имеющим MultiIndex в качестве индекса строк) обратной операцией DataFrame.stack() является DataFrame.unstack(), которая по умолчанию разархивирует последний уровень:

>>> stacked.unstack()
                     A         B
first second                    
bar   one    -0.325765 -1.455506
      two    -0.909177  0.081546
baz   one     0.128316 -0.435166
      two    -0.308656  2.402403

>>> stacked.unstack(1)
second        one       two
first                      
bar   A -0.325765 -0.909177
      B -1.455506  0.081546
baz   A  0.128316 -0.308656
      B -0.435166  2.402403

>>> stacked.unstack(0)
first          bar       baz
second                      
one    A -0.325765  0.128316
       B -1.455506 -0.435166
two    A -0.909177 -0.308656
       B  0.081546  2.402403

Если индексы имеют имена, то можно использовать имена меток уровней вместо номеров уровней:

>>> stacked.unstack("second")
second        one       two
first                      
bar   A -0.325765 -0.909177
      B -1.455506  0.081546
baz   A  0.128316 -0.308656
      B -0.435166  2.402403

Обратите внимание, что методы DataFrame.stack() и DataFrame.unstack() неявно сортируют задействованные уровни индекса. Следовательно, вызов DataFrame.stack(), а затем DataFrame.unstack(), или наоборот, приведет к отсортированной копии исходного DataFrame или Series:

>>> index = pd.MultiIndex.from_product([[2, 1], ["a", "b"]])
>>> df = pd.DataFrame(np.random.randn(4), index=index, columns=["A"])
>>> df
            A
2 a  0.109137
  b  0.104417
1 a -0.427419
  b -0.976336

>>> all(df.unstack().stack(future_stack=True) == df.sort_index())
# True

Несколько уровней

columns = pd.MultiIndex.from_tuples(
    [
        ("A", "cat", "long"),
        ("B", "cat", "long"),
        ("A", "dog", "short"),
        ("B", "dog", "short"),
    ],
    names=["exp", "animal", "hair_length"],
)

>>> df = pd.DataFrame(np.random.randn(4, 4), columns=columns)
>>> df
exp                 A         B         A         B
animal            cat       cat       dog       dog
hair_length      long      long     short     short
0            0.613395  0.069411 -0.843289 -0.935644
1           -0.556056  0.321497  0.742493 -1.329095
2           -0.828012 -0.477574 -0.208956  0.124899
3            1.674502 -0.127979 -0.204078  0.676785

>>> df.stack(level=["animal", "hair_length"], future_stack=True)
exp                          A         B
  animal hair_length                    
0 cat    long         0.613395  0.069411
  dog    short       -0.843289 -0.935644
1 cat    long        -0.556056  0.321497
  dog    short        0.742493 -1.329095
2 cat    long        -0.828012 -0.477574
  dog    short       -0.208956  0.124899
3 cat    long         1.674502 -0.127979
  dog    short       -0.204078  0.676785

Список уровней может содержать либо имена, либо номера уровней, но не их сочетание.

# предыдущий пример эквивалентен:
>>> df.stack(level=[1, 2], future_stack=True)
exp                          A         B
  animal hair_length                    
0 cat    long         0.613395  0.069411
  dog    short       -0.843289 -0.935644
1 cat    long        -0.556056  0.321497
  dog    short        0.742493 -1.329095
2 cat    long        -0.828012 -0.477574
  dog    short       -0.208956  0.124899
3 cat    long         1.674502 -0.127979
  dog    short       -0.204078  0.676785

Пропущенные данные

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

columns = pd.MultiIndex.from_tuples(
    [
        ("A", "cat"),
        ("B", "dog"),
        ("B", "cat"),
        ("A", "dog"),
    ],
    names=["exp", "animal"],
)

index = pd.MultiIndex.from_product(
    [("bar", "baz", "foo", "qux"), ("one", "two")], names=["first", "second"]
)

>>> df = pd.DataFrame(np.random.randn(8, 4), index=index, columns=columns)
>>> df3 = df.iloc[[0, 1, 4, 7], [1, 2]]
>>> df3
exp                  B          
animal             dog       cat
first second                    
bar   one    -0.232130 -0.496571
      two     0.184100 -0.553932
foo   one     0.603123 -0.321162
qux   two    -0.626404 -0.434028

Отсутствующее значение может быть заполнено определенным значением с помощью аргумента fill_value.

>>> df3.unstack(fill_value=-1e9)
exp                B                                          
animal           dog                         cat              
second           one           two           one           two
first                                                         
bar    -2.321301e-01  1.840997e-01 -4.965706e-01 -5.539317e-01
foo     6.031231e-01 -1.000000e+09 -3.211621e-01 -1.000000e+09
qux    -1.000000e+09 -6.264035e-01 -1.000000e+09 -4.340277e-01

DataFrame.stack(level=-1, dropna=no_default, sort=no_default, future_stack=False):

Метод DataFrame.stack() "сводит" уровень меток столбцов (возможно, иерархический) к оси индекса строк, возвращая измененный DataFrame с новым, самым внутренним уровнем меток строк.

Возвращает измененный DataFrame или Series, имеющий многоуровневый индекс с одним или несколькими новыми внутренними уровнями по сравнению с текущим DataFrame. Новые самые внутренние уровни создаются путем поворота столбцов текущего DataFrame:

  • если столбцы имеют один уровень, то выходные данные представляют собой Series;
  • если столбцы имеют несколько уровней, то новый уровень/уровни индекса берутся из заданных уровней, а результатом является DataFrame.

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

Принимаемые аргументы

  • level=-1 - номер уровня (уровней как список значений) столбца для складывания к оси индекса. Можно передавать имя метки уровня.
  • dropna=True - следует ли удалять строки в результирующем DataFrame/Series с пропущенными значениями NA. Наложение уровня столбца на ось индекса может создать комбинации значений индекса и столбца, которые отсутствуют в исходном DataFrame.
  • sort=True - следует ли сортировать уровни результирующего MultiIndex.
  • future_stack=False - следует ли использовать новую реализацию, которая заменит текущую реализацию в pandas 3.0. При значении True dropna и sort не влияют на результат и должны оставаться неопределенными.

Примеры использования DataFrame.stack()

Визуализация работы метода `DataFrame.stack()` в pandas

Одноуровневые столбцы

>>> import pandas as pd
df_single_level_cols = pd.DataFrame([[0, 1], [2, 3]],
                                    index=['cat', 'dog'],
                                    columns=['weight', 'height'])

>>> df_single_level_cols
     weight  height
cat       0       1
dog       2       3

Сведение DataFrame с осью столбца одного уровня, возвращает Series:

>>> df_single_level_cols.stack(future_stack=True)
cat  weight    0
     height    1
dog  weight    2
     height    3
dtype: int64

Многоуровневые столбцы: простой случай

df_multi_level_cols1multicol1 = pd.MultiIndex.from_tuples([('weight', 'kg'),
                                       ('weight', 'pounds')])

df_multi_level_cols1 = pd.DataFrame([[1, 2], [2, 4]],
                                    index=['cat', 'dog'],
                                    columns=multicol1)

>>> df_multi_level_cols1
    weight       
        kg pounds
cat      1      2
dog      2      4

Объединение DataFrame с многоуровневой осью столбца:

>>> df_multi_level_cols1.stack(future_stack=True)
            weight
cat kg           1
    pounds       2
dog kg           2
    pounds       4

Отсутствующие значения NA

multicol2 = pd.MultiIndex.from_tuples([('weight', 'kg'),
                                       ('height', 'm')])

df_multi_level_cols2 = pd.DataFrame([[1.0, 2.0], [3.0, 4.0]],
                                    index=['cat', 'dog'],
                                    columns=multicol2)

>>> df_multi_level_cols2
    weight height
        kg      m
cat    1.0    2.0
dog    3.0    4.0

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

>>> df_multi_level_cols2.stack(future_stack=True)
        weight  height
cat kg     1.0     NaN
    m      NaN     2.0
dog kg     3.0     NaN
    m      NaN     4.0

Указание уровня(ей) для сведения

Первый аргумент ``level определяет, какой уровень или уровни складываются:

>>> df_multi_level_cols2.stack(0, future_stack=True)
             kg    m
cat weight  1.0  NaN
    height  NaN  2.0
dog weight  3.0  NaN
    height  NaN  4.0

>>> df_multi_level_cols2.stack([0, 1], future_stack=True)
cat  weight  kg    1.0
     height  m     2.0
dog  weight  kg    3.0
     height  m     4.0
dtype: float64

Удаление пропущенных значений

df_multi_level_cols3 = pd.DataFrame([[None, 1.0], [2.0, 3.0]],
                                    index=['cat', 'dog'],
                                    columns=multicol2)

>>> df_multi_level_cols3
    weight height
        kg      m
cat    NaN    1.0
dog    2.0    3.0

Обратите внимание, что строки, в которых отсутствуют все значения, по умолчанию удаляются, но этим поведением можно управлять с помощью ключевого аргумента dropna:

>>> df_multi_level_cols3.stack(dropna=False)
        weight  height
cat kg     NaN     NaN
    m      NaN     1.0
dog kg     2.0     NaN
    m      NaN     3.0

>>> df_multi_level_cols3.stack(dropna=True)
        weight  height
cat m      NaN     1.0
dog kg     2.0     NaN
    m      NaN     3.0

DataFrame.unstack(level=-1, fill_value=None, sort=True):

Метод DataFrame.unstack() - обратная операция DataFrame.stack(). "Сводит" уровень индекса строки (возможно, иерархический) к оси столбца, создавая измененный DataFrame с новым, самым внутренним уровнем меток столбцов.

Метод возвращает DataFrame, имеющий новый уровень меток столбцов, самый внутренний уровень которого состоит из меток сводного индекса.

Если индекс не является MultiIndex, то результатом будет Series (аналог стека, когда столбцы не являются многоиндексными).

Принимаемые аргументы

  • level=-1 - номер уровня (уровней как список значений) индекса для извлечения, можно передавать имя метки уровня.
  • fill_value=None - заменяет NaN этим значением, если при извлечении из стека значения отсутствуют.
  • sort=True - следует ли сортировать уровни результирующего MultiIndex.

Примеры использования DataFrame.unstack()

Визуализация работы метода `DataFrame.unstack()` в pandas

>>> import pandas as pd
index = pd.MultiIndex.from_tuples([('one', 'a'), ('one', 'b'),
                                   ('two', 'a'), ('two', 'b')])

>>> s = pd.Series(np.arange(1.0, 5.0), index=index)
>>> s
one  a   1.0
     b   2.0
two  a   3.0
     b   4.0
dtype: float64
>>> s.unstack(level=-1)
     a   b
one  1.0  2.0
two  3.0  4.0

>>> s.unstack(level=0)
   one  two
a  1.0   3.0
b  2.0   4.0

>>> df = s.unstack(level=0)
>>> df.unstack()
one  a  1.0
     b  2.0
two  a  3.0
     b  4.0
dtype: float64