Как изменять структуру таблиц?

In [1]: import pandas as pd
Данные, использованные в этом уроке:
  • В этом руководстве используется набор данных Titanic, сохраненный в формате CSV. Данные состоят из следующих столбцов данных:

    • PassengerId: Идентификатор каждого пассажира.

    • Survived: Имеет значения 0 и 1. 0 для не выживших и 1 для выживших.

    • Pclass: Существует 3 класса: класс 1, класс 2 и класс 3.

    • Name: Имя пассажира.

    • Sex: Пол пассажира.

    • Age: Возраст пассажира.

    • SibSp: Указание на то, что у пассажира есть братья, сестры и супруг.

    • Parch: Пассажир один или с семьей.

    • Ticket: Номер билета пассажира.

    • Fare: Указание тарифа.

    • Cabin: Каюта пассажира.

    • Embarked: Категория причала.

    Исходные данные
    In [2]: titanic = pd.read_csv("data/titanic.csv")
    
    In [3]: titanic.head()
    Out[3]: 
       PassengerId  Survived  Pclass  ...     Fare Cabin  Embarked
    0            1         0       3  ...   7.2500   NaN         S
    1            2         1       1  ...  71.2833   C85         C
    2            3         1       3  ...   7.9250   NaN         S
    3            4         1       1  ...  53.1000  C123         S
    4            5         0       3  ...   8.0500   NaN         S
    
    [5 rows x 12 columns]
    
  • В этом руководстве используются данные о концентрации \(NO_2\) в воздухе и о твердых частицах размером менее 2,5 микрометров, предоставленные openaq и использующие пакет py-openaq. Набор данных air_quality_long.csv содержит значения \(NO_2\) и \(PM_{25}\) от измерительных станций FR04014, BETR801 и London Westminster в Париже, Антверпене и Лондоне соответственно.

    Набор данных о качестве воздуха включает следующие столбцы:

    • city: город, в котором расположен датчик (Париж, Антверпен или Лондон).

    • country: страна, в которой используется датчик (FR, BE или GB).

    • location: идентификатор датчика (FR04014, BETR801 или London Westminster).

    • parameter: параметр, измеряемый датчиком (\(NO_2\) либо твердые частицы).

    • value: измеренное значение.

    • unit: единица измерения параметра, в данном случае «мкг/м³».

    В качестве индекса в этом DataFrame выступает datetime, то есть дата и время измерения.

    Примечание

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

    Исходные данные
    In [4]: air_quality = pd.read_csv(
       ...:     "data/air_quality_long.csv", index_col="date.utc", parse_dates=True
       ...: )
       ...: 
    
    In [5]: air_quality.head()
    Out[5]: 
                                    city country location parameter  value   unit
    date.utc                                                                     
    2019-06-18 06:00:00+00:00  Antwerpen      BE  BETR801      pm25   18.0  µg/m³
    2019-06-17 08:00:00+00:00  Antwerpen      BE  BETR801      pm25    6.5  µg/m³
    2019-06-17 07:00:00+00:00  Antwerpen      BE  BETR801      pm25   18.5  µg/m³
    2019-06-17 06:00:00+00:00  Antwerpen      BE  BETR801      pm25   16.0  µg/m³
    2019-06-17 05:00:00+00:00  Antwerpen      BE  BETR801      pm25    7.5  µg/m³
    

Сортировка строк таблицы

  • Я хочу отсортировать данные Титаника по возрасту пассажиров.

    In [6]: titanic.sort_values(by="Age").head()
    Out[6]: 
         PassengerId  Survived  Pclass                             Name     Sex  ...  Parch  Ticket     Fare Cabin  Embarked
    803          804         1       3  Thomas, Master. Assad Alexander    male  ...      1    2625   8.5167   NaN         C
    755          756         1       2        Hamalainen, Master. Viljo    male  ...      1  250649  14.5000   NaN         S
    644          645         1       3           Baclini, Miss. Eugenie  female  ...      1    2666  19.2583   NaN         C
    469          470         1       3    Baclini, Miss. Helene Barbara  female  ...      1    2666  19.2583   NaN         C
    78            79         1       2    Caldwell, Master. Alden Gates    male  ...      2  248738  29.0000   NaN         S
    
    [5 rows x 12 columns]
    
  • Я хочу отсортировать данные Титаника по классу салона и возрасту в порядке убывания.

    In [7]: titanic.sort_values(by=['Pclass', 'Age'], ascending=False).head()
    Out[7]: 
         PassengerId  Survived  Pclass                       Name     Sex  ...  Parch  Ticket    Fare Cabin  Embarked
    851          852         0       3        Svensson, Mr. Johan    male  ...      0  347060  7.7750   NaN         S
    116          117         0       3       Connors, Mr. Patrick    male  ...      0  370369  7.7500   NaN         Q
    280          281         0       3           Duane, Mr. Frank    male  ...      0  336439  7.7500   NaN         Q
    483          484         1       3     Turkula, Mrs. (Hedwig)  female  ...      0    4134  9.5875   NaN         S
    326          327         0       3  Nysveen, Mr. Johan Hansen    male  ...      0  345364  6.2375   NaN         S
    
    [5 rows x 12 columns]
    

    С помощью DataFrame.sort_values() строки в таблице сортируются в соответствии с определенными столбцами. Индекс будет следовать порядку строк.

В руководстве пользователя

Более подробная информация о сортировке таблиц приведена в разделе руководства пользователя о сортировке данных.

Из длинного в широкий формат

Давайте воспользуемся небольшим подмножеством набора данных о качестве воздуха. Сфокусируемся на данных о \(NO_2\) и используем только первые два измерения с каждой станции (location), то есть заголовочные строки (.head()) каждой группы. Подмножество данных будет называться no2_subset.

# filter for no2 data only
In [8]: no2 = air_quality[air_quality["parameter"] == "no2"]
# use 2 measurements (head) for each location (groupby)
In [9]: no2_subset = no2.sort_index().groupby(["location"]).head(2)

In [10]: no2_subset
Out[10]: 
                                city country            location parameter  value   unit
date.utc                                                                                
2019-04-09 01:00:00+00:00  Antwerpen      BE             BETR801       no2   22.5  µg/m³
2019-04-09 01:00:00+00:00      Paris      FR             FR04014       no2   24.4  µg/m³
2019-04-09 02:00:00+00:00     London      GB  London Westminster       no2   67.0  µg/m³
2019-04-09 02:00:00+00:00  Antwerpen      BE             BETR801       no2   53.5  µg/m³
2019-04-09 02:00:00+00:00      Paris      FR             FR04014       no2   27.4  µg/m³
2019-04-09 03:00:00+00:00     London      GB  London Westminster       no2   67.0  µg/m³
../../_images/07_pivot.svg
  • Мне нужны значения для трех станций в виде отдельных столбцов рядом друг с другом.

    In [11]: no2_subset.pivot(columns="location", values="value")
    Out[11]: 
    location                   BETR801  FR04014  London Westminster
    date.utc                                                       
    2019-04-09 01:00:00+00:00     22.5     24.4                 NaN
    2019-04-09 02:00:00+00:00     53.5     27.4                67.0
    2019-04-09 03:00:00+00:00      NaN      NaN                67.0
    

    Функция pivot() предназначена исключительно для изменения формы данных: требуется одно значение для каждой комбинации индекса и столбца.

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

In [12]: no2.head()
Out[12]: 
                            city country location parameter  value   unit
date.utc                                                                 
2019-06-21 00:00:00+00:00  Paris      FR  FR04014       no2   20.0  µg/m³
2019-06-20 23:00:00+00:00  Paris      FR  FR04014       no2   21.8  µg/m³
2019-06-20 22:00:00+00:00  Paris      FR  FR04014       no2   26.5  µg/m³
2019-06-20 21:00:00+00:00  Paris      FR  FR04014       no2   24.9  µg/m³
2019-06-20 20:00:00+00:00  Paris      FR  FR04014       no2   21.4  µg/m³
In [13]: no2.pivot(columns="location", values="value").plot()
Out[13]: <AxesSubplot:xlabel='date.utc'>
../../_images/7_reshape_columns.png

Примечание

Когда параметр index не определен, используется существующий индекс (метки строк).

В руководстве пользователя

Дополнительные сведения о pivot() см. в разделе руководства пользователя, посвященном повороту объектов DataFrame.

Сводная таблица

../../_images/07_pivot_table.svg
  • Мне нужны средние концентрации \(NO_2\) и \(PM_{2.5}\) на каждой из станций в виде таблицы.

    In [14]: air_quality.pivot_table(
       ....:     values="value", index="location", columns="parameter", aggfunc="mean"
       ....: )
       ....: 
    Out[14]: 
    parameter                 no2       pm25
    location                                
    BETR801             26.950920  23.169492
    FR04014             29.374284        NaN
    London Westminster  29.740050  13.443568
    

    В случае pivot() данные только переупорядочиваются. Когда же необходимо объединить несколько значений (в нашем случае это значения на разных временных интервалах), можно использовать pivot_table(), указывая агрегирующую функцию (например, среднее) для объединения значений.

Сводные таблицы хорошо известны тем, кто работает с электронными таблицами. Если вам нужны сводные столбцы для каждой группы, установите для параметра margins значение True:

In [15]: air_quality.pivot_table(
   ....:     values="value",
   ....:     index="location",
   ....:     columns="parameter",
   ....:     aggfunc="mean",
   ....:     margins=True,
   ....: )
   ....: 
Out[15]: 
parameter                 no2       pm25        All
location                                           
BETR801             26.950920  23.169492  24.982353
FR04014             29.374284        NaN  29.374284
London Westminster  29.740050  13.443568  21.491708
All                 29.430316  14.386849  24.222743
В руководстве пользователя

Дополнительные сведения о pivot_table() см. в разделе руководства пользователя о сводных таблицах.

Примечание

Если вам интересно, pivot_table() действительно напрямую связан с groupby(). Тот же результат можно получить, группируя как по parameter, так и по location:

air_quality.groupby(["parameter", "location"]).mean()
В руководстве пользователя

Подробнее о сочетании groupby() и unstack() читайте в соответствующем разделе руководства пользователя.

Из широкого в длинный формат

Начинем заново с широкоформатной таблицы, созданной в предыдущем разделе:

In [16]: no2_pivoted = no2.pivot(columns="location", values="value").reset_index()

In [17]: no2_pivoted.head()
Out[17]: 
location                  date.utc  BETR801  FR04014  London Westminster
0        2019-04-09 01:00:00+00:00     22.5     24.4                 NaN
1        2019-04-09 02:00:00+00:00     53.5     27.4                67.0
2        2019-04-09 03:00:00+00:00     54.5     34.2                67.0
3        2019-04-09 04:00:00+00:00     34.5     48.5                41.0
4        2019-04-09 05:00:00+00:00     46.5     59.5                41.0
../../_images/07_melt.svg
  • Я хочу собрать все измерения качества воздуха \(NO_2\) в один столбец (длинный формат).

    In [18]: no_2 = no2_pivoted.melt(id_vars="date.utc")
    
    In [19]: no_2.head()
    Out[19]: 
                       date.utc location  value
    0 2019-04-09 01:00:00+00:00  BETR801   22.5
    1 2019-04-09 02:00:00+00:00  BETR801   53.5
    2 2019-04-09 03:00:00+00:00  BETR801   54.5
    3 2019-04-09 04:00:00+00:00  BETR801   34.5
    4 2019-04-09 05:00:00+00:00  BETR801   46.5
    

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

Приведенное решение — это краткая версия применения pandas.melt(). Метод разберет все столбцы, НЕ упомянутые в id_vars, на два столбца: столбец с именами заголовков исходных столбцов и столбец с собственно значениями. Последний столбец по умолчанию получает имя value.

Метод pandas.melt() можно определить более подробно:

In [20]: no_2 = no2_pivoted.melt(
   ....:     id_vars="date.utc",
   ....:     value_vars=["BETR801", "FR04014", "London Westminster"],
   ....:     value_name="NO_2",
   ....:     var_name="id_location",
   ....: )
   ....: 

In [21]: no_2.head()
Out[21]: 
                   date.utc id_location  NO_2
0 2019-04-09 01:00:00+00:00     BETR801  22.5
1 2019-04-09 02:00:00+00:00     BETR801  53.5
2 2019-04-09 03:00:00+00:00     BETR801  54.5
3 2019-04-09 04:00:00+00:00     BETR801  34.5
4 2019-04-09 05:00:00+00:00     BETR801  46.5

Результат тот же, но определен более подробно:

  • value_vars явно определяет, какие столбцы нужно преобразовать в длинную таблицу.

  • value_name позволяет задать имя для столбца значений вместо используемого по умолчанию имени value.

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

Следовательно, аргументы value_name и var_name — это просто определяемые пользователем имена для двух сгенерированных столбцов. В id_vars и value_vars определяются столбцы для преобразования в длинный формат.

В руководстве пользователя

Преобразование из широкого формата в длинный с помощью pandas.melt() объясняется в разделе руководства пользователя об изменении формата с помощью функции melt.

ЗАПОМНИТЕ

  • Сортировка по одному или нескольким столбцам поддерживается параметром sort_values.

  • Функция pivot предназначена исключительно для реструктурирования данных, pivot_table поддерживает агрегирование.

  • Обратной функцией к pivot (из длинного в широкий формат) является melt (из широкого в длинный формат).

В руководстве пользователя

Полный обзор доступен в руководстве пользователя на страницах об изменении формы и поворотах таблиц.