Menu

Перестройка данных в R: pivot_wider и pivot_longer (широкий и длинный формат)

Широкий и длинный форматы данных и как между ними переходить: pivot_longer() и pivot_wider() из tidyr, старые имена spread/gather и reshape() из базового R.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Широкий против длинного: одни данные, две формы

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

Идентичная информация, разная геометрия. У широкого формата одна строка на город и один столбец на квартал — квартал живёт в именах столбцов. У длинного одна строка на наблюдение (город × квартал), квартал понижен до обычного столбца, а все измерения лежат в единственном столбце sales.

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

Почему для анализа побеждает длинный формат

Посмотрите, что широкий формат сделал с понятием «квартал»: он раскрошил одну переменную по именам столбцов. Ничто не умеет вычислять по имени столбца. В длинном формате quarter снова является данными, и весь инструментарий группового анализа включается:

  • aggregate(sales ~ quarter, ...) или group_by(quarter) — в широком формате невозможно, там каждый квартал отдельный столбец, который пришлось бы обрабатывать вручную (групповые сводки все предполагают длинные данные).
  • ggplot2 отображает столбцы на эстетики: aes(x = quarter, y = sales, color = city) требует существования этих трёх столбцов. Рисовать широкие данные — постоянная борьба; рисовать длинные — одна строка.
  • Добавление Q3 — это добавление строк, без изменения схемы, тогда как широкий формат обзаводится новым столбцом, о котором должен узнать каждый последующий шаг.

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

Из широкого в длинный: pivot_longer()

Пакет tidyr (родственник dplyr в tidyverse) владеет современной перестройкой. Его сниппеты здесь статичны — песочница выполняет только базовый R. Удлинение требует трёх решений, по аргументу на каждое:

library(tidyr)

long <- pivot_longer(wide,
    cols      = c(Q1, Q2),      # which columns to stack
    names_to  = "quarter",      # new column that receives the old NAMES
    values_to = "sales"         # new column that receives the cell VALUES
)

Разберём: cols называет растворяемые столбцы (синтаксис диапазона Q1:Q2 и помощники вроде starts_with("Q") тоже работают — удобно, когда их двадцать). Каждая выбранная ячейка становится строкой; столбец, из которого она пришла, попадает в quarter, а само число — в sales. Столбцы, не перечисленные в cols (здесь city), считаются идентификаторами и повторяются вниз по строкам. Результат — ровно тот фрейм long, что напечатан выше.

Из длинного в широкий: pivot_wider()

Обратный путь требует двух решений — откуда берутся новые имена столбцов и что их заполняет:

wide <- pivot_wider(long,
    names_from  = quarter,   # distinct values here become new columns
    values_from = sales      # these values fill the cells
)

Каждое различное значение quarter (Q1, Q2) становится столбцом; каждая ячейка заполняется значением sales из строки, соответствующей этому городу и кварталу. Оставшиеся столбцы (city) выступают идентификаторами строк — по одной выходной строке на каждую различную комбинацию. Город без какого-то квартала получит NA в этой ячейке (аргумент values_fill подставляет что-то другое, например values_fill = 0 для счётных данных).

В старых учебниках вам встретится и предыдущее поколение: spread() — это pivot_wider(), а gather()pivot_longer(). Они вытеснены с tidyr 1.0, по-прежнему работают, и учить их дальше узнавания не стоит.

В базовом R есть reshape() — честное предупреждение

Базовый R умеет это без пакетов, через reshape() — функцию, настолько знаменитую своей непонятностью, что её собственная документация исторически за неё извинялась. Она спроектирована вокруг словаря лонгитюдных исследований (idvar, timevar, direction), имена аргументов неуклюже ложатся на повседневные данные, и все забывают их между использованиями. Работать она работает:

Обратите внимание на имена в выводе: sales.Q1, sales.Q2 — имя столбца значений приклеено к каждому. Сойдёт на безрыбье, в окружении без зависимостей или когда вы встретили это в старом коде. Но если вы перестраиваете данные чаще раза в год, честной рекомендацией будет install.packages("tidyr") — это единственная задача обработки данных, где инструмент базового R действительно обходится дороже, чем зависимость, от которой он избавляет.

Ловушка дублирующихся ключей при расширении

Расширение предполагает, что каждая комбинация «идентификатор + имя» задаёт одно значение. Если у Lima две строки Q1, какое число попадёт в единственную ячейку Q1? pivot_wider() отказывается гадать: она выдаёт предупреждение (values are not uniquely identified) и кладёт в ячейку столбец-список — датафрейм с вложенными списками, что сломает следующее же действие с ним.

Увидев это предупреждение, не хватайтесь сразу за запасной выход values_fn — спросите, почему существуют дубликаты. Обычно данные оказываются более мелкозернистыми, чем вы думали (строки дневные, а не квартальные, — значит, сначала сводка, потом расширение), либо соединение выше по потоку продублировало строки. values_fn = mean (или sum) — законное решение только тогда, когда вы можете вслух сказать, какой агрегацией должны схлопнуться дубликаты. reshape() здесь хуже: она молча оставляет первый дубликат и отбрасывает остальные, лишь с предупреждением, которое легко проскроллить.

Что вы уносите с собой

  • Широкий формат: переменные прячутся в именах столбцов, сделано для чтения. Длинный: одна строка на наблюдение, сделано для анализа и ggplot2.
  • Храните и анализируйте в длинном формате, представляйте в широком.
  • pivot_longer(cols, names_to, values_to) складывает столбцы в строки; неперечисленные столбцы становятся повторяющимися идентификаторами.
  • pivot_wider(names_from, values_from) разворачивает строки в столбцы; пробелы заполняются NA.
  • spread()/gather() — старые имена; базовая reshape() работает, но знаменита своей неуклюжестью.
  • Дублирующиеся пары «идентификатор + имя» делают расширение неоднозначным — сначала сводка, а не просто заглушение предупреждения.

Дальше: чтение настоящих данных из файлов — read.csv() и её острые края.

Часто задаваемые вопросы

В чём разница между широкими и длинными данными в R?

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

Как преобразовать широкие данные в длинные в R?

pivot_longer(df, cols = Q1:Q4, names_to = "quarter", values_to = "sales") из tidyr складывает выбранные столбцы в два новых: старые имена столбцов уходят в столбец names_to, а значения ячеек — в столбец values_to.

Как преобразовать длинные данные в широкие в R?

pivot_wider(df, names_from = quarter, values_from = sales) из tidyr разворачивает строки в столбцы: каждое различное значение names_from становится столбцом, заполняемым соответствующими записями values_from. Отсутствующие комбинации становятся NA.

Что заменило spread и gather в R?

pivot_wider() заменила spread(), а pivot_longer() заменила gather() в tidyr 1.0 (2019). Старые функции по-прежнему работают — вы встретите их в старых учебниках, — но весь новый код должен использовать пару pivot_* с более понятными аргументами и большими возможностями.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ