9 Введение в tidyverse

9.1 Вселенная tidyverse

tidyverse - это не один, а целое множество пакетов. Есть ключевые пакеты (ядро тайдиверса), а есть побочные - в основном для работы со специфическими видами данных.

tidyverse — это набор пакетов:

  • ggplot2, для визуализации
  • tibble, для работы с тибблами, продвинутый вариант датафрейма
  • tidyr, для формата tidy data
  • readr, для чтения файлов в R
  • purrr, для функционального программирования (замена семейства функций *apply())
  • dplyr, для преобразованиия данных
  • stringr, для работы со строковыми переменными
  • forcats, для работы с переменными-факторами

Полезно также знать о следующих пакетах, не включенных в ядро, но также считающихся частью тайдиверса:

  • vroom, для быстрой загрузки табоичных данных
  • readxl, для чтения .xls и .xlsx
  • jsonlite, для работы с JSON
  • xml, для работы с XML
  • DBI, для работы с базами данных
  • rvest, для веб-скреппинга
  • lubridate, для работы с временем
  • tidytext, для работы с текстами и корпусами
  • glue, для продвинутого объединения строк
  • magrtittr, с несколькими вариантами pipe оператора
  • tidymodels, для моделирования и машинного обучения17
  • dtplyr, для ускорения dplyr за счет перевод синтаксиса на data.table

И это еще не все пакеты tidyverse! Есть еще много других небольших пакетов, которые тоже считаются частью tidyverse. Кроме официальных пакетов tidyverse есть множество пакетов, которые пытаются соответствовать принципам tidyverse и дополняют его.

Все пакеты tidyverse объединены tidy философией и взаимосовместимым синтаксисом. Это означает, что, во многих случаях даже не нужно думать о том, из какого именно пакета тайдиверса пришла функция. Можно просто установить и загрузить пакет tidyverse.

install.packages("tidyverse")

Пакет tidyverse — это такой пакет с пакетами.

library("tidyverse")
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.3     ✓ purrr   0.3.4
## ✓ tibble  3.0.6     ✓ dplyr   1.0.4
## ✓ tidyr   1.1.2     ✓ stringr 1.4.0
## ✓ readr   1.4.0     ✓ forcats 0.5.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()

Подключение пакета tidyverse автоматически приводит к подключению ядра tidyverse, остальные же пакеты нужно подключать дополнительно при необходимости.

9.2 Загрузка данных с помощью readr

Стандартной функцией для чтения .csv файлов в R является функция read.csv(), но мы будем использовать функцию read_csv() из пакета readr. Синтаксис функции read_csv() очень похож на read.csv(): первым аргументом является путь к файлу (в том числе можно использовать URL), некоторые остальные параметры тоже совпадают.

heroes <- read_csv("https://raw.githubusercontent.com/Pozdniakov/tidy_stats/master/data/heroes_information.csv",
                   na = c("-", "-99"))
## Warning: Missing column names filled in: 'X1' [1]
## 
## ── Column specification ────────────────────────────────────────────────────────
## cols(
##   X1 = col_double(),
##   name = col_character(),
##   Gender = col_character(),
##   `Eye color` = col_character(),
##   Race = col_character(),
##   `Hair color` = col_character(),
##   Height = col_double(),
##   Publisher = col_character(),
##   `Skin color` = col_character(),
##   Alignment = col_character(),
##   Weight = col_double()
## )

Подробнее про импорт данных, в том числе в tidyverse, смотри в @ref(real_data).

##tibble

Когда мы загрузили данные с помощью read_csv(), то мы получили tibble, а не data.frame:

class(heroes)
## [1] "spec_tbl_df" "tbl_df"      "tbl"         "data.frame"

Тиббл (tibble) - это такой “усовершенствованный” data.frame. Почти все, что работает с data.frame, работает и с тибблами. Однако у тибблов есть свои дополнительные фишки. Самая очевидная из них - более аккуратный вывод в консоль:

heroes

Выводятся только первые 10 строк, если какие-то колонки не влезают на экран, то они просто перечислены внизу. Ну а тип данных написан прямо под названием колонки.

Функции различных пакетов tidyverse сами конвертируют в тиббл при необходимости. Если же нужно это сделать самостоятельно, то можно это сделать так:

heroes_df <- as.data.frame(heroes) #создаем простой датафрейм
class(heroes_df)
## [1] "data.frame"
as_tibble(heroes_df) #превращаем обратно в тиббл

В дальнейшем мы будем работать только с tidyverse, а это значит, что только с тибблами, а не обычными датафреймами. Тем не менее, тибблы и датафреймы будут в дальнейшем использоваться как синонимы.

Можно создавать тибблы вручную с помощью функции tibble(), которая работает аналогично функции data.frame():

tibble(
  a = 1:3,
  b = letters[1:3]
)

9.3 magrittr::%>%

Оператор %>% называется “пайпом” (pipe), т.е. “трубой.” Он означает, что следующая функция (справа от пайпа) принимает на вход в качестве первого аргумента результат выполнения предыдущей функции (той, что слева). Фактически, это примерно то же самое, что и вставлять результат выполнения функции в качестве первого аргумента в другую функцию. Просто выглядит это красивее и читабельнее. Как будто данные пропускаются через трубы функций или конвеерную ленту на заводе, если хотите. А то, что первый параметр функции - это почти всегда данные, работает нам здесь на руку. Этот оператор взят из пакета magrittr18. Возможно, даже если вы не захотите пользоваться tidyverse, использование пайпов Вам понравится.

Важно понимать, что пайп не дает какой-то дополнительной функциональности или дополнительной скорости работы19. Он создан исключительно для читабельности и комфорта.

С помощью пайпов вот эту команду…

sum(sqrt(abs(sin(1:22))))
## [1] 16.72656

…можно переписать вот так:

1:22 %>% 
  sin() %>% 
  abs() %>% 
  sqrt() %>% 
  sum()
## [1] 16.72656

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

"Всем привет!" %>%
  c("--", ., "--")
## [1] "--"           "Всем привет!" "--"

Основные функции в tidyverse …

9.4 Главные пакеты tidyverse: dplyr и tidyr

dplyr20 — это самая основа всего tidyverse. Этот пакет предоставляет основные функции для манипуляции с тибблами. Пакет dplyr является наследником и более усовершенствованной версией plyr, так что если увидите использование пакета plyr, то, скорее всего, скрипт был написан очень давно.

Пакет tidyr дополняет dplyr, предоставляя полезные функции для тайдификации тибблов. Тайдификация (“аккуратизация”) данных означает приведение табличных данных к такому формату, в котором:

  • Каждая переменная имеет собственный столбец
  • Каждый наблюдение имеет собственную строку
  • Каждое значение имеет свою собственную ячейку

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

Пакет tidyr — это более усовершенствованная версия пакета reshape2, который в свою очередь является усовершенствованной версией reshape. По аналогии с plyr, если вы видите использование этих пакетов, то это указывает на то, что перед вами морально устаревший код.

Код с использованием dplyr и tidyrсильно непохож на то, что мы видели раньше. Большинство функций dplyr и tidyr работают с целым тибблом сразу, принимая его в качестве первого аргумента и возвращая измененный тиббл. Это позволяет превратить весь код в последовательный набор применяемых функций, соединенный пайпами. На практике это выглядит очень элегантно, и вы в этом скоро убедитесь.

9.5 Работа с колонками тиббла

9.5.1 Выбор колонок: dplyr::select()

Функция dplyr::select() позволяет выбирать колонки по номеру или имени (кавычки не нужны).

heroes %>%
  select(1,5)
heroes %>%
  select(name, Race, Publisher, `Hair color`)

Обратите внимание, если в названии колонки присутствует пробел или, например, колонка начинается с цифры или точки и цифры, то это синтаксически невалидное имя (2.6). Это не значит, что такие названия колонок недопустимы. Но такие названия колонок нужно обособлять ` грависом (правый штрих, на клавиатуре находится там же где и буква ё и ~).

Еще обратите внимание на то, что функции tidyverse не изменяют сами изначальные тибблы/датафреймы. Это означает, что если вы хотите полученный результат сохранить, то нужно добавить присвоение:

heroes_some_cols <- heroes %>%
  select(name, Race, Publisher, `Hair color`)
heroes_some_cols

9.5.2 Мини-язык tidyselect для выбора колонок

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

Можно использовать оператор : для выбора нескольких соседних колонок (по аналогии с созданием числового вектора с шагом 1).

heroes %>%
  select(name:Publisher)
heroes %>%
  select(name:`Eye color`, Publisher:Weight)

Используя ! можно вырезать ненужные колонки.

heroes %>%
  select(!X1)
heroes %>%
  select(!(Gender:Height))

Другие известные нам логические операторы (& и |) тоже работают в tidyselect.

В дополнение к логическим операторам и :, в tidyselect есть набор вспомогательных функций, работающих исключительно в контексте выбора колонок с помощью tidyselect.

Вспомогательная функция last_col() позволит обратиться к последней колонке тиббла:

heroes %>%
  select(name:last_col())

А функция everything() позволяет выбрать все колонки.

heroes %>%
  select(everything())

При этом everything() не будет дублировать выбранные колонки, поэтому можно использовать everything() для перестановки колонок в тиббле:

heroes %>%
  select(name, Publisher, everything())

Впрочем, для перестановки колонок удобнее использовать специальную функцию relocate() (@ref(tidy_relocate)) Можно даже выбирать колонки по паттернам в названиях. Например, с помощью ends_with() можно выбрать все колонки, заканчивающиеся одинаковым суффиксом:

heroes %>%
  select(ends_with("color"))

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

heroes %>%
  select(starts_with("Eye") & ends_with("color"))
heroes %>%
  select(contains("eight"))

Ну и наконец, можно выбирать по содержимому колонок с помощью where(). Это напоминает применение sapply()(@ref(apply_other)) на датафрейме для индексирования колонок: в качестве аргумента для where принимается функция, которая применяется для каждой из колонок, после чего выбираются только те колонки, для которых было получено TRUE.

heroes %>%
  select(where(is.numeric))

Функция where() дает невиданную мощь. Например, можно выбрать все колонки без NA:

heroes %>%
  select(where(function(x) !any(is.na(x))))