-
Notifications
You must be signed in to change notification settings - Fork 7
/
107-beyond_base_r.qmd
129 lines (83 loc) · 13.7 KB
/
107-beyond_base_r.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# За пределами base R: tidyverse и data.table {#sec-beyond_base_r}
Как вы уже, наверное, убедились, базовый R умеет очень много, в том числе и для работы с данными. Однако какие-то операции все равно выполнить довольно непросто.
Возьмем, например, задачу агрегации: вам нужно посчитать средний рост супергероев отдельно для мужчин и для женщин (а еще и для `NA`, за компанию). Три группы еще ничего, а если бы их было 10, 50 или 200? В базовом R для этого есть специальная функция `aggregate()`, но она довольно неудобная.
Поэтому стали появляться пакеты, которые пытаются сделать агрегацию и другие непростые операции максимально безболезненными способами. Основных таких пакетов два: `{data.table}` и `{tidyverse}`. Это огромные пакеты, которые очень сильно изменяют работу в R, в том числе в плане стиля и используемой парадигмы. Тем не менее, в основе своей стоит все то, что мы прошли раньше.
## Подход `{data.table}` {#sec-dt_approach}
`{data.table}` -- это распространенный пакет, который позволяет анализировать датафреймы максимально быстро и с помощью очень лаконичного кода.
``` r
install.packages("data.table")
```
Давайте импортируем наш набор данных про супергероев. Для этого воспользуемся функцией `fread()` из пакета `{data.table}`. Эта функция нам уже знакома как функция для импорта больших наборов данных @sec-fastread.
*"f"* в `fread()` означает *"**f**ast and **f**riendly"*: эта функция очень быстрая и довольно хорошо угадывает формат текстовой таблицы.
```{r}
library(data.table)
heroes_dt <-
fread(
"https://raw.githubusercontent.com/Pozdniakov/tidy_stats/master/data/heroes_information.csv",
na.strings = c("NA", "-", "-99")
)
```
Функция `fread()` создает не просто датафрейм, а **дататейбл *(datatable):***
```{r}
heroes_dt
class(heroes_dt)
```
**Дататейбл** -- это "улучшенный" датафрейм: с ним работают все те функции, которые мы применяли для датафрейма, специальные функции для дататейбла, а что-то работает немного по-другому по сравнению с датафреймом. Например, оператор `[`, т.е. квадратные скобки.
Давайте посмотрим по-внимательнее как это происходит на примере расчета среднего роста супергероев, группируя по полу:
```{r}
heroes_dt[, mean(Height, na.rm = TRUE), by = Gender]
```
Сразу уже усложним задачу: возьмем только хороших (у кого в колонке `Alignment` стоит `"good"`), а потом еще отсортируем по среднему росту.
```{r}
heroes_dt[Alignment == "good",
.(mean_height = mean(Height, na.rm = TRUE)),
by = Gender][
order(-mean_height)
]
```
Уух! Выглядит монструозно, да? Зато как мы все сделали используя минимальное количество знаков. Заметьте, что здесь необычного для нас:
- Не нужно прописывать `heroes_dt$Alignment`, поиск переменной будет начинаться с колонок дататейбла.
- Там, где мы раньше выбирали колонки, мы еще и расчеты можем вести.
- Внутри квадратных скобок появилась вторая запятая, т.е. третье поле, в котором мы прописали группировку.
- Несколько операций прописываются путем соединения квадратных скобочек, код превращается в эдакий паровозик[^107-beyond_base_r-1].
[^107-beyond_base_r-1]: Это работает и в базовом R, но именно в `{data.table}` это очень частая конструкция.
И это не все отличия!
На [сайте пакета `{data.table}`](https://rdatatable.gitlab.io/data.table/) особенно уделяется вниманию скорости `{data.table}`, приводя в качестве доказательства [бэнчмарк](https://h2oai.github.io/db-benchmark/), где сравниваются по скорости различные инструменты для работы с данными. `{data.table}` почти на порядок обгоняет как `{dplyr}`, так и питоновский *pandas* -- самый используемый пакет для анализа данных в *Python*.
Разработчики `{data.table}` делают особый акцент на "консервативности" пакета: у него нет никаких зависимостей (в этом плане пакет `{data.table}` обгоняет большинство российских экспатов в Тбилиси), ему достаточно очень старой версии R, функционирование пакета не будет ломаться из-за выкинутых устаревших функций. В общем, `{data.table}` очень суров и уважаем программистами. Он и не особо пытается понравиться рядовым пользователям. Зато освоив его, вы сможете творить магию: то, что с помощью базового R, *tidyverse* или Python будет выполняться очень долго (если выполнится вообще), `{data.table}` сможет сделать гораздо быстрее, иногда в десятки и сотни раз!
Очень сильно, не правда ли? Чем же может ответить *tidyverse*?
## Подход tidyverse {#sec-tidy_approach}
Давайте посмотрим, как будет выглядеть решение тех же задач (отбор строк по условию, агрегация и сортировка) в *tidyverse*.
``` r
install.packages("tidyverse")
```
```{r}
library(tidyverse)
```
Не пугайтесь сообщений, все в порядке. Во-первых, пакет `{tidyverse}` -- это не просто пакет, а "пакет с пакетами" (да-да, как у вас дома), который подключает сразу несколько других пакетов, которые составляют *ядро tidyverse*. Список и версии этих пакетов `{tidyverse}` выводит при подключении. Разные пакеты *tidyverse* мы очень детально разберем позже (@sec-tidy_intro ), а сейчас просто посмотрите, как это все выглядит.
```{r}
heroes_tbl <- read_csv("https://raw.githubusercontent.com/Pozdniakov/tidy_stats/master/data/heroes_information.csv",
na = c("NA", "-", "-99"))
```
Функция `read_csv()` (не путать с функцией из базового R -- `read.csv()`!) возвращает **тиббл** -- "улучшенный" датафрейм, примерно как это было с дататейблом.
```{r}
heroes_tbl
class(heroes_tbl)
```
Теперь же сделаем то же самое с нашими данными, что мы делали с помощью `{data.table}`:
```{r}
heroes_tbl %>%
filter(Alignment == "good") %>%
group_by(Gender) %>%
summarise(mean_height = mean(Height, na.rm = TRUE)) %>%
arrange(desc(mean_height))
```
Очень сильно отличается от того, как мы работали раньше! Хотя в основе лежит все тот же R. Код, написанный в *tidyverse*, нарочито многословен (особенно по сравнению с `{data.table}`), каждая отдельная операция имеет свою функцию. Писать нужно больше, зато это гораздо легче: меньше нужно думать, какими хитрыми трюками сделать преобразование данных. Нужно просто разделить весь процесс преобразования данных на отдельные операции и последовательно прописать их. Код получается аккуратный и очень читаемый, даже для человека, который не знает *tidyverse* или даже R в целом. Даже этот новый оператор `%>%` выглядит довольно понятно: его можно прочитать как "затем".
Заметьте, что *tidyverse* выводит очень подробные сообщения, которые даже выглядят очень красиво: со всякими иконками, красивым форматированием. Разработчики *tidyverse* работают над тем, чтобы делать свой интерфейс максимально понятным для пользователя: говорящие сами за себя названия функций, куча удобных фишек на все случаи жизни.
*tidyverse* постоянно обновляется, регулярно появляются новые функции, а старые функции заменяются на более удобные новые. И это не всегда плюс: обновив пакеты, установленные год назад, вы можете обнаружить, что старый код перестал работать! Мол, мы тут придумали, как сделать лучше, переписывайте код заново (или используйте старые версии пакетов).
Разработчики *tidyverse*, в целом, не стремится за высокой скоростью. Часто можно заметить, что новые функции работают довольно медленно. Но если у вас строчек меньше миллиона, то разницу в скорости с `{data.table}` вы едва ли заметите.
Команда разработчиков *tidyverse* работает на компанию *Posit* (бывшая *RStudio*). Поэтому в *RStudio* вы найдете несколько "шпаргалок" для *tidyverse*, но не для `{data.table}`. Они также активно активно работают над популяризацией *tidyverse*, стараясь сделать вход в него максимально комфортным, особенно для людей без опыта программирования. *tidyverse* команда открыто заявляет о своей политике diversity, некоторые члены этой команды -- открытые представители гендерных и сексуальных меньшинств.
## {data.table} vs tidyverse {#sec-dt_vs_tidy}
Так что же лучше: `{data.table}` или *tidyverse*? Это один из самых частых споров в R-комьюнити. У обоих подходов есть плюсы, которые можно обсуждать вечно. Сегодня *tidyverse* выигрывает в популярности, особенно за пределами русскоязычного пространства.
В последнее время `{data.table}` и *tidyverse* все меньше противостоят друг другу и все больше взаимодополняют. Например, некоторые используют в качестве основного инструмента tidyverse, но при работе с данными побольше переключаются на `{data.table}`[^107-beyond_base_r-2]. Кроме того, сами разработчики tidyverse пытаются приладить суперскоростной `{data.table}` в *tidyverse*: пакет `{dtplyr}` позволяет "переводить" код, написанный в *tidyverse* в код на `{data.table}`.
[^107-beyond_base_r-2]: Автор книги поступает именно так =)
Таким образом, выбирая из *tidyverse* и `{data.table}`, начинать лучше с более удобного и популярного tidyverse, чем и займемся далее.