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

Функция melt() и wide_to_long() модуля pandas в Python

Содержание:


Обзор функций pandas.melt() и pandas.wide_to_long()

Функция pandas.melt() и соответствующий метод DataFrame.melt() полезны для преобразования DataFrame в формат, где один или несколько столбцов являются переменными-идентификаторами, при этом все остальные столбцы, считающиеся измеряемыми переменными, "не привязаны" к оси строк, оставляя только два столбца без идентификаторов, "переменная" и "значение". Имена этих столбцов можно настроить, указав аргументы var_name и value_name.

>>> import pandas as pd

cheese = pd.DataFrame(
    {
        "first": ["John", "Mary"],
        "last": ["Doe", "Bo"],
        "height": [5.5, 6.0],
        "weight": [130, 150],
    }
)

>>> cheese
#   first last  height  weight
# 0  John  Doe     5.5     130
# 1  Mary   Bo     6.0     150

>>> cheese.melt(id_vars=["first", "last"])
#   first last variable  value
# 0  John  Doe   height    5.5
# 1  Mary   Bo   height    6.0
# 2  John  Doe   weight  130.0
# 3  Mary   Bo   weight  150.0

>>> cheese.melt(id_vars=["first", "last"], var_name="quantity")
#   first last quantity  value
# 0  John  Doe   height    5.5
# 1  Mary   Bo   height    6.0
# 2  John  Doe   weight  130.0
# 3  Mary   Bo   weight  150.0

При преобразовании DataFrame с помощью pandas.melt() индекс будет проигнорирован. Исходные значения индекса можно сохранить, установив для аргумента ignore_index=False (по умолчанию True). Аргумент ignore_index=False будет дублировать значения индекса.

# создадим `MultiIndex` для предыдущего `DataFrame`
>>> index = pd.MultiIndex.from_tuples([("person", "A"), ("person", "B")])

cheese = pd.DataFrame(
    {
        "first": ["John", "Mary"],
        "last": ["Doe", "Bo"],
        "height": [5.5, 6.0],
        "weight": [130, 150],
    },
    index=index,
)

>>> cheese
#          first last  height  weight
# person A  John  Doe     5.5     130
#        B  Mary   Bo     6.0     150

>>> cheese.melt(id_vars=["first", "last"])
#   first last variable  value
# 0  John  Doe   height    5.5
# 1  Mary   Bo   height    6.0
# 2  John  Doe   weight  130.0
# 3  Mary   Bo   weight  150.0

>>> cheese.melt(id_vars=["first", "last"], ignore_index=False)
#          first last variable  value
# person A  John  Doe   height    5.5
#        B  Mary   Bo   height    6.0
#        A  John  Doe   weight  130.0
#        B  Mary   Bo   weight  150.0

Функция pandas.wide_to_long() похожа на функцию pandas.melt() менее гибкая, но более удобная для пользователя с большей настраиваемостью сопоставления столбцов.

>>> import numpy as np

dft = pd.DataFrame(
    {
        "A1970": {0: "a", 1: "b", 2: "c"},
        "A1980": {0: "d", 1: "e", 2: "f"},
        "B1970": {0: 2.5, 1: 1.2, 2: 0.7},
        "B1980": {0: 3.2, 1: 1.3, 2: 0.1},
        "X": dict(zip(range(3), np.random.randn(3))),
    }
)

>>> dft["id"] = dft.index
>>> dft
#   A1970 A1980  B1970  B1980         X  id
# 0     a     d    2.5    3.2  0.534195   0
# 1     b     e    1.2    1.3 -1.392514   1
# 2     c     f    0.7    0.1 -1.372925   2

>>> pd.wide_to_long(dft, ["A", "B"], i="id", j="year")
#                 X  A    B
# id year                  
# 0  1970  0.534195  a  2.5
# 1  1970 -1.392514  b  1.2
# 2  1970 -1.372925  c  0.7
# 0  1980  0.534195  d  3.2
# 1  1980 -1.392514  e  1.3
# 2  1980 -1.372925  f  0.1

pandas.melt(frame, id_vars=None, value_vars=None, var_name=None, value_name='value', col_level=None, ignore_index=True):

Pandas предоставляет эквивалентный метод DataFrame.melt(), который принимает аналогичные аргументы за исключением frame, т.к. сам является вызывающим (исходным) DataFrame.

Функция pandas.melt() отменяет поворот DataFrame из широкого формата в длинный, при необходимости оставив установленные идентификаторы.

Эта функция полезна для преобразования фрейма данных в формат, в котором один или несколько столбцов являются переменными-идентификаторами (id_vars), в то время как все остальные столбцы, считаются измеряемыми переменными (value_vars) - "не привязаны" к оси строк, оставляя только два столбца без идентификаторов, ‘variable’ и ‘value’.

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

  • frame - исходный DataFrame.
  • id_vars=None - столбцы, которые будут использоваться в качестве переменных-идентификаторов.
  • value_vars=None - столбцы, которые нужно отключить. Если не указано, то будут использоваться все столбцы, которые не заданы как id_vars.
  • var_name=None - имя, используемое для столбца ‘variable’. Если его нет, то используется frame.columns.name или ‘variable’.
  • value_name='value' - имя, которое будет использоваться для столбца ‘value’.
  • col_level=None - используйте этот уровень, если столбцы являются MultiIndex.
  • ignore_index=True - если True, то исходный индекс игнорируется. Если False, исходный индекс сохраняется. Метки индексов будут повторяться по мере необходимости.

Примеры использования pandas.melt():

Визуализация работы функции `DataFrame.melt()` в pandas

>>> import pandas as pd

df = pd.DataFrame({'A': {0: 'a', 1: 'b', 2: 'c'},
                   'B': {0: 1, 1: 3, 2: 5},
                   'C': {0: 2, 1: 4, 2: 6}})

>>> df
#    A  B  C
# 0  a  1  2
# 1  b  3  4
# 2  c  5  6

>>> pd.melt(df, id_vars=['A'], value_vars=['B'])
#    A variable  value
# 0  a        B      1
# 1  b        B      3
# 2  c        B      5

>>> pd.melt(df, id_vars=['A'], value_vars=['B', 'C'])
#    A variable  value
# 0  a        B      1
# 1  b        B      3
# 2  c        B      5
# 3  a        C      2
# 4  b        C      4
# 5  c        C      6

Имена столбцов ‘variable’ и ‘value’ можно настроить:

>>> pd.melt(df, id_vars=['A'], value_vars=['B'],
...         var_name='myVarname', value_name='myValname')
#    A myVarname  myValname
# 0  a         B          1
# 1  b         B          3
# 2  c         B          5

Исходные значения индекса могут быть сохранены:

>>> pd.melt(df, id_vars=['A'], value_vars=['B', 'C'], ignore_index=False)
#    A variable  value
# 0  a        B      1
# 1  b        B      3
# 2  c        B      5
# 0  a        C      2
# 1  b        C      4
# 2  c        C      6

Если есть столбцы имеют MultiIndex:

>>> df.columns = [list('ABC'), list('DEF')]
>>> df
#    A  B  C
#    D  E  F
# 0  a  1  2
# 1  b  3  4
# 2  c  5  6

>>> pd.melt(df, col_level=0, id_vars=['A'], value_vars=['B'])
#    A variable  value
# 0  a        B      1
# 1  b        B      3
# 2  c        B      5

>>> pd.melt(df, id_vars=[('A', 'D')], value_vars=[('B', 'E')])
#   (A, D) variable_0 variable_1  value
# 0      a          B          E      1
# 1      b          B          E      3
# 2      c          B          E      5

pandas.wide_to_long(df, stubnames, i, j, sep='', suffix='\\d+'):

Функция pandas.wide_to_long() похожа на функцию pandas.melt(). Более удобная для пользователя (менее гибкая, но с большей настраиваемостью сопоставления столбцов).

С помощью stubnames=[‘A’, ‘B’] эта функция ожидает найти одну или несколько групп столбцов формата A-suffix1, A-suffix2,..., B-suffix1, B-suffix2,… В результирующем формате с помощью аргумента j (например, j=’year’) указываем, как нужно назвать этот суффикс.

Предполагается, что каждая строка этих переменных однозначно идентифицируется символом, переданным аргументу i (это может быть имя одного столбца или список имен столбцов)

Все остальные переменные остаются нетронутыми. Здесь под капотом просто используется pandas.melt(), но жестко запрограммирован для "правильного действия" в типичном случае.

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

  • df - исходный DataFrame.
  • stubnames - имя/имена префиксов. Предполагается, что столбцов с суффиксами начинаются с этих префиксов.
  • i - столбец/столбцы, которые будут использоваться в качестве идентификаторов переменных.
  • j - имя переменной поднаблюдения. Как нужно назвать объединенный столбец (аргумент i) в новом формате данных.
  • sep='' - символ, обозначающий разделение имен переменных (с суффиксами), который должен быть удален из имен. Например, если имена столбцов A-suffix1, A-suffix2, то можно убрать дефис, указав sep=’-’.
  • suffix='\d+' - регулярное выражение, фиксирующее нужные суффиксы. Регулярное выражение '\d+' фиксирует числовые суффиксы. Суффиксы без чисел могут быть указаны с помощью класса отрицаемых символов '\D+'. Дополнительно можно устранить неоднозначность суффиксов, например, если переменные имеют вид A-one, B-two, .. , и есть несвязанный столбец A-rating, то можно проигнорировать последний, указав suffix='(!?one|two)'. Когда все суффиксы являются числовыми, они преобразуются в int64/float64.

Примеры использования pandas.wide_to_long():

>>> import pandas as pd
>>> import numpy as np
>>> np.random.seed(123)

df = pd.DataFrame({"A1970" : {0 : "a", 1 : "b", 2 : "c"},
                   "A1980" : {0 : "d", 1 : "e", 2 : "f"},
                   "B1970" : {0 : 2.5, 1 : 1.2, 2 : .7},
                   "B1980" : {0 : 3.2, 1 : 1.3, 2 : .1},
                   "X"     : dict(zip(range(3), np.random.randn(3)))
                  })

>>> df["id"] = df.index
>>> df
#   A1970 A1980  B1970  B1980         X  id
# 0     a     d    2.5    3.2 -1.085631   0
# 1     b     e    1.2    1.3  0.997345   1
# 2     c     f    0.7    0.1  0.282978   2

>>> pd.wide_to_long(df, ["A", "B"], i="id", j="year")
#                 X  A    B
# id year
# 0  1970 -1.085631  a  2.5
# 1  1970  0.997345  b  1.2
# 2  1970  0.282978  c  0.7
# 0  1980 -1.085631  d  3.2
# 1  1980  0.997345  e  1.3
# 2  1980  0.282978  f  0.1

С несколькими столбцами идентификаторов

df = pd.DataFrame({
    'famid': [1, 1, 1, 2, 2, 2, 3, 3, 3],
    'birth': [1, 2, 3, 1, 2, 3, 1, 2, 3],
    'ht1': [2.8, 2.9, 2.2, 2, 1.8, 1.9, 2.2, 2.3, 2.1],
    'ht2': [3.4, 3.8, 2.9, 3.2, 2.8, 2.4, 3.3, 3.4, 2.9]
})

>>> df
#    famid  birth  ht1  ht2
# 0      1      1  2.8  3.4
# 1      1      2  2.9  3.8
# 2      1      3  2.2  2.9
# 3      2      1  2.0  3.2
# 4      2      2  1.8  2.8
# 5      2      3  1.9  2.4
# 6      3      1  2.2  3.3
# 7      3      2  2.3  3.4
# 8      3      3  2.1  2.9
>>> l = pd.wide_to_long(df, stubnames='ht', i=['famid', 'birth'], j='age')
>>> l
#                   ht
# famid birth age
# 1     1     1    2.8
#             2    3.4
#       2     1    2.9
#             2    3.8
#       3     1    2.2
#             2    2.9
# 2     1     1    2.0
#             2    3.2
#       2     1    1.8
#             2    2.8
#       3     1    1.9
#             2    2.4
# 3     1     1    2.2
#             2    3.3
#       2     1    2.3
#             2    3.4
#       3     1    2.1
#             2    2.9

Переход от длинного DataFrame к широкому требует некоторого творческого использования DataFrame.unstack()

>>> w = l.unstack()
>>> w.columns = w.columns.map('{0[0]}{0[1]}'.format)
>>> w.reset_index()
#    famid  birth  ht1  ht2
# 0      1      1  2.8  3.4
# 1      1      2  2.9  3.8
# 2      1      3  2.2  2.9
# 3      2      1  2.0  3.2
# 4      2      2  1.8  2.8
# 5      2      3  1.9  2.4
# 6      3      1  2.2  3.3
# 7      3      2  2.3  3.4
# 8      3      3  2.1  2.9

Также обрабатываются менее сложные имена столбцов

>>> np.random.seed(0)

df = pd.DataFrame({'A(weekly)-2010': np.random.rand(3),
                   'A(weekly)-2011': np.random.rand(3),
                   'B(weekly)-2010': np.random.rand(3),
                   'B(weekly)-2011': np.random.rand(3),
                   'X' : np.random.randint(3, size=3)})

>>> df['id'] = df.index
>>> df 
#    A(weekly)-2010  A(weekly)-2011  B(weekly)-2010  B(weekly)-2011  X  id
# 0        0.548814        0.544883        0.437587        0.383442  0   0
# 1        0.715189        0.423655        0.891773        0.791725  1   1
# 2        0.602763        0.645894        0.963663        0.528895  1   2

>>> pd.wide_to_long(df, ['A(weekly)', 'B(weekly)'], i='id', j='year', sep='-')
#          X  A(weekly)  B(weekly)
# id year
# 0  2010  0   0.548814   0.437587
# 1  2010  1   0.715189   0.891773
# 2  2010  1   0.602763   0.963663
# 0  2011  0   0.544883   0.383442
# 1  2011  1   0.423655   0.791725
# 2  2011  1   0.645894   0.528895

Если фрейм содержит много столбцов, то можно использовать регулярное выражение для поиска имен-префиксов и передачи этого списка в pandas.wide_to_long()

stubnames = sorted(
    set([match[0] for match in df.columns.str.findall(
        r'[A-B]\(.*\)').values if match != []])
)

>>> list(stubnames)
# ['A(weekly)', 'B(weekly)']

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

df = pd.DataFrame({
    'famid': [1, 1, 1, 2, 2, 2, 3, 3, 3],
    'birth': [1, 2, 3, 1, 2, 3, 1, 2, 3],
    'ht_one': [2.8, 2.9, 2.2, 2, 1.8, 1.9, 2.2, 2.3, 2.1],
    'ht_two': [3.4, 3.8, 2.9, 3.2, 2.8, 2.4, 3.3, 3.4, 2.9]
})

>>> df
#    famid  birth  ht_one  ht_two
# 0      1      1     2.8     3.4
# 1      1      2     2.9     3.8
# 2      1      3     2.2     2.9
# 3      2      1     2.0     3.2
# 4      2      2     1.8     2.8
# 5      2      3     1.9     2.4
# 6      3      1     2.2     3.3
# 7      3      2     2.3     3.4
# 8      3      3     2.1     2.9

>>> l = pd.wide_to_long(df, stubnames='ht', i=['famid', 'birth'], j='age', sep='_', suffix=r'\w+')
>>> l
#                   ht
# famid birth age
# 1     1     one  2.8
#             two  3.4
#       2     one  2.9
#             two  3.8
#       3     one  2.2
#             two  2.9
# 2     1     one  2.0
#             two  3.2
#       2     one  1.8
#             two  2.8
#       3     one  1.9
#             two  2.4
# 3     1     one  2.2
#             two  3.3
#       2     one  2.3
#             two  3.4
#       3     one  2.1
#             two  2.9