Статьи
February 27, 2023

От широких датафреймов в Pandas к длинным и обратно

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

Давайте рассмотрим простой пример. В таблице ниже показаны средние цены на пять категорий продуктов питания по всем городам США в период с января 2020 года по апрель 2022 года.

Датафрейм слева имеет длинный формат. Столбцы Series ID и Item представляют категорию продуктов питания. Year Month — это столбец, содержащий все месяцы с января 2020 года по апрель 2022 года. Avg. Price ($) содержит значения средней цены, соответствующие каждому месяцу в столбце Year Month.

Обратите внимание на то, что подобный формат данных подразумевает повторение каждой категории продуктов питания (Item) для каждого соответствующего периода. Хотя у нас есть всего лишь пять категорий продуктов питания, в общей сложности в таблице 139 строк, что делает форму текущего датафрейма «длинной».

Датафрейм в правой же части, напротив, имеет широкий формат. В нём каждая строка представляет собой уникальную категорию продуктов питания. Мы преобразуем столбец Year Month в левом датафрейме таким образом, чтобы каждый месяц находился в отдельном столбце, придавая правому датафрейму «широкую» форму. Так значения столбца Year Month в левой таблице теперь становятся названиями столбцов в правой таблице, а в них указана Avg. Price ($) для каждой категории и периода соответственно.


Теперь, когда мы понимаем разницу между широким и длинным форматом данных, давайте посмотрим, как мы можем легко делать такие преобразования в Pandas. Будем использовать всё тот же набор данных. Данные можно скачать здесь. Давайте сначала прочитаем данные из CSV-файла:

import pandas as pd
import numpy as np 

df = pd.read_csv('file.csv')

def f(row):
    if row['Series ID'] == 'APU0000709112':
        val = 'Milk, Whole per gal' 
    elif row['Series ID'] == 'APU0000708111': 
        val = 'Eggs, large per doz' 
    elif row['Series ID'] == 'APU0000702111': 
        val = 'Bread, whilte per lb'  
    elif row['Series ID'] == 'APU0000703112': 
        val = 'Ground beef,100% per lb'  
    elif row['Series ID'] == 'APU0000706111': 
        val = 'Chicken, whole per lb'  
    else: 
        val = 'NA' 
    return val 
    

df['Item']= df.apply(f, axis=1)
df.style.set_properties(subset=['Item'], **{'width': '300px'})

df = df[['Series ID','Item','Label','Value']]
df = df.rename(columns={'Label': 'Year Month', 'Value': 'Avg. Price ($)'})

Изменение формы с длинной на широкую

Как мы уже выяснили ранее, по факту этот датафрейм имеет длинную форму. Чтобы изменить его размерность на широкую, мы можем использовать метод Pandas pd.pivot().

pd.pivot(df, index=..., columns=..., values=...)
  • columns: столбец, используемый для создания столбцов нового фрейма (например, Year Month).
  • values: столбцы, используемые для заполнения значений (ячеек) нового фрейма (например, Avg. Price ($)).
  • index: столбец, используемый для создания индексов (строк) нового фрейма (например, Series ID и Item). Если None, используется существующий индекс.
# Изменение длинной формы на широкую
df_wide = pd.pivot(df,
                   index=['Series ID', 'Item'],
                   columns = 'Year Month',
                   values = 'Avg. Price ($)')

# Перестановка новых столбцов в правильном порядке
cols = df['Year Month'].unique()
df_wide = df_wide[cols]

Изменение формы с широкой на длинную

Теперь, как нам вернуть широкоформатные данные обратно в длинный формат? Для этого мы можем использовать метод Pandas pd.melt().

pd.melt(df, id_vars=, value_vars=, var_name=, value_name=, ignore_index=)
  • id_vars: столбцы, используемые в качестве идентификаторов.
  • value_vars: столбцы, которые нужно вернуть в прежний формат (преобразовать в значения датафрейма). В нашем примере это был бы список столбцов года/месяца ("2020 Jan", "2020 Feb", "2020 Mar" и т.д.)
  • var_name: название, используемое для столбца "variable".
  • value_name: название, используемое для столбца "value".
  • ignore_index: если "True", исходный индекс игнорируется. Если "False", исходный индекс сохраняется.
year_list = list(df_wide.columns)
df_long = pd.melt(df_wide,
                  value_vars = year_list,
                  value_name = 'Avg. Price ($)',
                  ignore_index = False)

Итого: если вам нужно изменить размер фрейма данных Pandas с длинного на широкий, используйте pd.pivot(). Если вам нужно изменить размер фрейма данных Pandas с широкого на длинный, используйте pd.melt().

Спасибо за чтение! Мы надеемся, что вы найдёте это краткое руководство полезным.

PythonTalk в Telegram

Чат PythonTalk в Telegram

🍩 Поддержать канал 🫶

Источник: Towards Data Science