library(data.table)
<-
heroes_dt fread(
"https://raw.githubusercontent.com/Pozdniakov/tidy_stats/master/data/heroes_information.csv",
na.strings = c("NA", "-", "-99")
)
9 За пределами base R: tidyverse и data.table
Как вы уже, наверное, убедились, базовый R умеет очень много, в том числе и для работы с данными. Однако какие-то операции все равно выполнить довольно непросто.
Возьмем, например, задачу агрегации: вам нужно посчитать средний рост супергероев отдельно для мужчин и для женщин (а еще и для NA
, за компанию). Три группы еще ничего, а если бы их было 10, 50 или 200? В базовом R для этого есть специальная функция aggregate()
, но она довольно неудобная.
Поэтому стали появляться пакеты, которые пытаются сделать агрегацию и другие непростые операции максимально безболезненными способами. Основных таких пакетов два: {data.table}
и {tidyverse}
. Это огромные пакеты, которые очень сильно изменяют работу в R, в том числе в плане стиля и используемой парадигмы. Тем не менее, в основе своей стоит все то, что мы прошли раньше.
9.1 Подход {data.table}
{data.table}
– это распространенный пакет, который позволяет анализировать датафреймы максимально быстро и с помощью очень лаконичного кода.
install.packages("data.table")
Давайте импортируем наш набор данных про супергероев. Для этого воспользуемся функцией fread()
из пакета {data.table}
. Эта функция нам уже знакома как функция для импорта больших наборов данных Глава 6.7.
“f” в fread()
означает “fast and friendly”: эта функция очень быстрая и довольно хорошо угадывает формат текстовой таблицы.
Функция fread()
создает не просто датафрейм, а дататейбл (datatable):
heroes_dt
V1 name Gender Eye color Race Hair color
1: 0 A-Bomb Male yellow Human No Hair
2: 1 Abe Sapien Male blue Icthyo Sapien No Hair
3: 2 Abin Sur Male blue Ungaran No Hair
4: 3 Abomination Male green Human / Radiation No Hair
5: 4 Abraxas Male blue Cosmic Entity Black
---
730: 729 Yellowjacket II Female blue Human Strawberry Blond
731: 730 Ymir Male white Frost Giant No Hair
732: 731 Yoda Male brown Yoda's species White
733: 732 Zatanna Female blue Human Black
734: 733 Zoom Male red <NA> Brown
Height Publisher Skin color Alignment Weight
1: 203.0 Marvel Comics <NA> good 441
2: 191.0 Dark Horse Comics blue good 65
3: 185.0 DC Comics red good 90
4: 203.0 Marvel Comics <NA> bad 441
5: NA Marvel Comics <NA> bad NA
---
730: 165.0 Marvel Comics <NA> good 52
731: 304.8 Marvel Comics white good NA
732: 66.0 George Lucas green good 17
733: 170.0 DC Comics <NA> good 57
734: 185.0 DC Comics <NA> bad 81
class(heroes_dt)
[1] "data.table" "data.frame"
Дататейбл – это “улучшенный” датафрейм: с ним работают все те функции, которые мы применяли для датафрейма, специальные функции для дататейбла, а что-то работает немного по-другому по сравнению с датафреймом. Например, оператор [
, т.е. квадратные скобки.
Давайте посмотрим по-внимательнее как это происходит на примере расчета среднего роста супергероев, группируя по полу:
mean(Height, na.rm = TRUE), by = Gender] heroes_dt[,
Gender V1
1: Male 191.9749
2: Female 174.6840
3: <NA> 177.0667
Сразу уже усложним задачу: возьмем только хороших (у кого в колонке Alignment
стоит "good"
), а потом еще отсортируем по среднему росту.
== "good",
heroes_dt[Alignment mean_height = mean(Height, na.rm = TRUE)),
.(= Gender][
by order(-mean_height)
]
Gender mean_height
1: Male 188.9601
2: <NA> 179.5000
3: Female 174.7607
Уух! Выглядит монструозно, да? Зато как мы все сделали используя минимальное количество знаков. Заметьте, что здесь необычного для нас:
Не нужно прописывать
heroes_dt$Alignment
, поиск переменной будет начинаться с колонок дататейбла.Там, где мы раньше выбирали колонки, мы еще и расчеты можем вести.
Внутри квадратных скобок появилась вторая запятая, т.е. третье поле, в котором мы прописали группировку.
Несколько операций прописываются путем соединения квадратных скобочек, код превращается в эдакий паровозик1.
И это не все отличия!
На сайте пакета {data.table}
особенно уделяется вниманию скорости {data.table}
, приводя в качестве доказательства бэнчмарк, где сравниваются по скорости различные инструменты для работы с данными. {data.table}
почти на порядок обгоняет как {dplyr}
, так и питоновский pandas – самый используемый пакет для анализа данных в Python.
Разработчики {data.table}
делают особый акцент на “консервативности” пакета: у него нет никаких зависимостей (в этом плане пакет {data.table}
обгоняет большинство российских экспатов в Тбилиси), ему достаточно очень старой версии R, функционирование пакета не будет ломаться из-за выкинутых устаревших функций. В общем, {data.table}
очень суров и уважаем программистами. Он и не особо пытается понравиться рядовым пользователям. Зато освоив его, вы сможете творить магию: то, что с помощью базового R, tidyverse или Python будет выполняться очень долго (если выполнится вообще), {data.table}
сможет сделать гораздо быстрее, иногда в десятки и сотни раз!
Очень сильно, не правда ли? Чем же может ответить tidyverse?
9.2 Подход tidyverse
Давайте посмотрим, как будет выглядеть решение тех же задач (отбор строк по условию, агрегация и сортировка) в tidyverse.
install.packages("tidyverse")
library(tidyverse)
Warning: package 'dplyr' was built under R version 4.2.3
Warning: package 'stringr' was built under R version 4.2.3
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.4
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.4.4 ✔ tibble 3.2.1
✔ lubridate 1.9.3 ✔ tidyr 1.3.0
✔ purrr 1.0.2
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::between() masks data.table::between()
✖ dplyr::filter() masks stats::filter()
✖ dplyr::first() masks data.table::first()
✖ lubridate::hour() masks data.table::hour()
✖ lubridate::isoweek() masks data.table::isoweek()
✖ dplyr::lag() masks stats::lag()
✖ dplyr::last() masks data.table::last()
✖ lubridate::mday() masks data.table::mday()
✖ lubridate::minute() masks data.table::minute()
✖ lubridate::month() masks data.table::month()
✖ lubridate::quarter() masks data.table::quarter()
✖ lubridate::second() masks data.table::second()
✖ purrr::transpose() masks data.table::transpose()
✖ lubridate::wday() masks data.table::wday()
✖ lubridate::week() masks data.table::week()
✖ lubridate::yday() masks data.table::yday()
✖ lubridate::year() masks data.table::year()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Не пугайтесь сообщений, все в порядке. Во-первых, пакет {tidyverse}
– это не просто пакет, а “пакет с пакетами” (да-да, как у вас дома), который подключает сразу несколько других пакетов, которые составляют ядро tidyverse. Список и версии этих пакетов {tidyverse}
выводит при подключении. Разные пакеты tidyverse мы очень детально разберем позже (Глава 10 ), а сейчас просто посмотрите, как это все выглядит.
<- read_csv("https://raw.githubusercontent.com/Pozdniakov/tidy_stats/master/data/heroes_information.csv",
heroes_tbl na = c("NA", "-", "-99"))
New names:
• `` -> `...1`
Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
dat <- vroom(...)
problems(dat)
Rows: 734 Columns: 11
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (8): name, Gender, Eye color, Race, Hair color, Publisher, Skin color, A...
dbl (3): ...1, Height, Weight
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
Функция read_csv()
(не путать с функцией из базового R – read.csv()
!) возвращает тиббл – “улучшенный” датафрейм, примерно как это было с дататейблом.
heroes_tbl
# A tibble: 734 × 11
...1 name Gender `Eye color` Race `Hair color` Height Publisher
<dbl> <chr> <chr> <chr> <chr> <chr> <dbl> <chr>
1 0 A-Bomb Male yellow Human No Hair 203 Marvel C…
2 1 Abe Sapien Male blue Icthyo … No Hair 191 Dark Hor…
3 2 Abin Sur Male blue Ungaran No Hair 185 DC Comics
4 3 Abomination Male green Human /… No Hair 203 Marvel C…
5 4 Abraxas Male blue Cosmic … Black NA Marvel C…
6 5 Absorbing Man Male blue Human No Hair 193 Marvel C…
7 6 Adam Monroe Male blue <NA> Blond NA NBC - He…
8 7 Adam Strange Male blue Human Blond 185 DC Comics
9 8 Agent 13 Female blue <NA> Blond 173 Marvel C…
10 9 Agent Bob Male brown Human Brown 178 Marvel C…
# ℹ 724 more rows
# ℹ 3 more variables: `Skin color` <chr>, Alignment <chr>, Weight <dbl>
class(heroes_tbl)
[1] "spec_tbl_df" "tbl_df" "tbl" "data.frame"
Теперь же сделаем то же самое с нашими данными, что мы делали с помощью {data.table}
:
%>%
heroes_tbl filter(Alignment == "good") %>%
group_by(Gender) %>%
summarise(mean_height = mean(Height, na.rm = TRUE)) %>%
arrange(desc(mean_height))
# A tibble: 3 × 2
Gender mean_height
<chr> <dbl>
1 Male 189.
2 <NA> 180.
3 Female 175.
Очень сильно отличается от того, как мы работали раньше! Хотя в основе лежит все тот же R. Код, написанный в tidyverse, нарочито многословен (особенно по сравнению с {data.table}
), каждая отдельная операция имеет свою функцию. Писать нужно больше, зато это гораздо легче: меньше нужно думать, какими хитрыми трюками сделать преобразование данных. Нужно просто разделить весь процесс преобразования данных на отдельные операции и последовательно прописать их. Код получается аккуратный и очень читаемый, даже для человека, который не знает tidyverse или даже R в целом. Даже этот новый оператор %>%
выглядит довольно понятно: его можно прочитать как “затем”.
Заметьте, что tidyverse выводит очень подробные сообщения, которые даже выглядят очень красиво: со всякими иконками, красивым форматированием. Разработчики tidyverse работают над тем, чтобы делать свой интерфейс максимально понятным для пользователя: говорящие сами за себя названия функций, куча удобных фишек на все случаи жизни.
tidyverse постоянно обновляется, регулярно появляются новые функции, а старые функции заменяются на более удобные новые. И это не всегда плюс: обновив пакеты, установленные год назад, вы можете обнаружить, что старый код перестал работать! Мол, мы тут придумали, как сделать лучше, переписывайте код заново (или используйте старые версии пакетов).
Разработчики tidyverse, в целом, не стремится за высокой скоростью. Часто можно заметить, что новые функции работают довольно медленно. Но если у вас строчек меньше миллиона, то разницу в скорости с {data.table}
вы едва ли заметите.
Команда разработчиков tidyverse работает на компанию Posit (бывшая RStudio). Поэтому в RStudio вы найдете несколько “шпаргалок” для tidyverse, но не для {data.table}
. Они также активно активно работают над популяризацией tidyverse, стараясь сделать вход в него максимально комфортным, особенно для людей без опыта программирования. tidyverse команда открыто заявляет о своей политике diversity, некоторые члены этой команды – открытые представители гендерных и сексуальных меньшинств.
9.3 {data.table} vs tidyverse
Так что же лучше: {data.table}
или tidyverse? Это один из самых частых споров в R-комьюнити. У обоих подходов есть плюсы, которые можно обсуждать вечно. Сегодня tidyverse выигрывает в популярности, особенно за пределами русскоязычного пространства.
В последнее время {data.table}
и tidyverse все меньше противостоят друг другу и все больше взаимодополняют. Например, некоторые используют в качестве основного инструмента tidyverse, но при работе с данными побольше переключаются на {data.table}
2. Кроме того, сами разработчики tidyverse пытаются приладить суперскоростной {data.table}
в tidyverse: пакет {dtplyr}
позволяет “переводить” код, написанный в tidyverse в код на {data.table}
.
Таким образом, выбирая из tidyverse и {data.table}
, начинать лучше с более удобного и популярного tidyverse, чем и займемся далее.