dplyr один из наиболее популярных пакетов для языка R, основным преимуществом которого является удобочитаемый и понятный синтаксис. Из недостатков данного пакета можно отметить, что при работе с данными большого объёма он значительно уступает в скорости вычислений например data.table.
В этом видео уроке мы разберёмся с тем, как можно ускорить вычисления на dplyr, за счёт бекендов dtplyr и multidplyr, а так же узнаем о том, как и зачем можно использовать бекенд dbplyr, предназначенный для работы с базами данных.

Содержание
Если вы интересуетесь анализом данных возможно вам будут полезны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.
Видео
Тайм коды:
00:00 Вступление
00:59 Какие бекенды мы рассмотрим
01:48 Цель
dtplyr02:30 Синтаксис
dtplyr03:33 Пример работы с
dtplyr05:38 Как осуществляется перевод глаголов
dplyrв синтаксисdata.table07:51 Функция
show_query()11:27 Почему dtplyr медленнее чем
data,table13:24 Цель
dbplyr13:40 Синтаксис
dbplyr14:37 Пример работы с
dbplyr16:54 Перевод
dplyrглаголов в SQL запросы17:53 Как реализованы подзапросы в
dbplyr18:25 Перевод неизвестных R функций и инфиксных операторов в SQL запрос
19:48 Проброс SQL команд в запросы
20:15 Как происходит перевод функций внутри
dplyrглаголов в SQL запросы, функцияtranslate_sql()21:26 Введение в
multidplyr22:17 Варианты применения
multidplyr22:50 Пример работы с
multidplyr28:24 Какой пакет использовать
dtplyrилиmultidplyr29:27 Заключение
Презентация
Краткий конспект
dtplyr
Цель dtplyr - позволить вам писать код dplyr, который под капотом транслируется в эквивалентный, но зачастую более быстрый, код data.table.
Синтаксис dtplyr:
lazy_dt()- создаёт объектdtplyrдля ленивых вычислений (первое действие);dplyr- далее используем созданный с помощьюlazy_dt()объект с глаголамиdplyr, для реализации необходимых вычислений. При этом фактически никакие вычисления не производятся, а лишь оцениваются;show_query()- показывает в какоеdata.tableвыражение будут преобразованы ваши вычисления;as.data.table()/as.data.frame()/as_tibble()- выполняет вычисления, и приводит результат к тому типу, который соответствует выбранной вами функции.
Примеры кода dtplyr
Пример вычисленний с dtplyr
# dtplyr
library(data.table)
library(dtplyr)
library(dplyr, warn.conflicts = FALSE)
# dtplyr использует ленивые вычисления
mtcars2 <- lazy_dt(mtcars)
# проверка результата
mtcars2 %>%
filter(wt < 5) %>%
mutate(l100k = 235.21 / mpg) %>% # liters / 100 km
group_by(cyl) %>%
summarise(l100k = mean(l100k))
# но для выполнения вычислений следует использовать
# as.data.table(), as.data.frame() или as_tibble()
mtcars2 %>%
filter(wt < 5) %>%
mutate(l100k = 235.21 / mpg) %>% # liters / 100 km
group_by(cyl) %>%
summarise(l100k = mean(l100k)) %>%
as_tibble()
# более подробно о том, как осуществляется перевод кода
df <- data.frame(a = 1:5, b = 1:5, c = 1:5, d = 1:5)
dt <- lazy_dt(df)
# посмотрим предварительную оценку перевода
dt
# если мы хотим посмотреть в какое выражение data.table
# будет преобразован код dplyr используем show_query()
dt %>% show_query()
# простые глаголы
## filter() / arrange() - i
dt %>% arrange(a, b, c) %>% show_query()
dt %>% filter(b == c) %>% show_query()
dt %>% filter(b == c, c == d) %>% show_query()
## select(), summarise(),transmute() - j
dt %>% select(a:b) %>% show_query()
dt %>% summarise(a = mean(a)) %>% show_query()
dt %>% transmute(a2 = a * 2) %>% show_query()
## mutate - j + :=
dt %>% mutate(a2 = a * 2, b2 = b * 2) %>% show_query()
# Другие глаголы dplyr
## rename - setnames
dt %>% rename(x = a, y = b) %>% show_query()
## distinct - unique
dt %>% distinct() %>% show_query()
dt %>% distinct(a, b) %>% show_query()
dt %>% distinct(a, b, .keep_all = TRUE) %>% show_query()
# Операции объединения
dt2 <- lazy_dt(data.frame(a = 1))
## [.data.table
dt %>% inner_join(dt2, by = "a") %>% show_query()
dt %>% right_join(dt2, by = "a") %>% show_query()
dt %>% left_join(dt2, by = "a") %>% show_query()
dt %>% anti_join(dt2, by = "a") %>% show_query()
## merge()
dt %>% full_join(dt2, by = "a") %>% show_query()
# Группировка keyby
dt %>% group_by(a) %>% summarise(b = mean(b)) %>% show_query()
# Комбинации вызовов
dt %>%
filter(a == 1) %>%
select(-a) %>%
show_query()
dt %>%
group_by(a) %>%
filter(b < mean(b)) %>%
summarise(c = max(c)) %>%
show_query()
dbplyr
Цель dbplyr - позволить вам манипулировать данными, хранящимися в удалённых базах данных, так же как если бы они были дата фреймами в среде R. Данный бекенд переводит глаголы dplyr в операторы SQL.
Синтаксис dbplyr
DBI- для инициализации подключения к базе данных;tbl()- для подключения к конкретной таблице;dplyr- реализуем все вычисления на основе глаголовdplyr, на данном этапе идёт только оценка вычислений;show_query()- позволяет посмотреть в какой SQL запрос будет конвертирован написанный на предыдущем шаге код;collect()- выполняет вычисления, и возвращает результат.
Пример кода dbplyr
Пример вычислений с dbplyr
library(dplyr)
library(dbplyr)
library(dplyr, warn.conflicts = FALSE)
con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")
copy_to(con, mtcars)
# обращаемся к таблице
mtcars2 <- tbl(con, "mtcars")
# пока что это всего лишь оценка вычисления
mtcars2
# dbplyr в действии
## генерация SQL запроса
summary <- mtcars2 %>%
group_by(cyl) %>%
summarise(mpg = mean(mpg, na.rm = TRUE)) %>%
arrange(desc(mpg))
## просмотр запроса
summary %>% show_query()
## выполнение запроса, и извлечение результата
summary %>% collect()
# автоматическая генерация подзапросов ------------------------------------
mf <- memdb_frame(x = 1, y = 2)
mf %>%
mutate(
a = y * x,
b = a ^ 2,
) %>%
show_query()
# неизвестные dplyr функции -----------------------------------------------
# любая неизветсная dplyr функция в запрос будет прокинута как есть
mf %>%
mutate(z = foofify(x, y)) %>%
show_query()
mf %>%
mutate(z = FOOFIFY(x, y)) %>%
show_query()
# так же dbplyr умеет переводить инфиксные функции
mf %>%
filter(x %LIKE% "%foo%") %>%
show_query()
# использовать самописный SQL ---------------------------------------------
## для вставки SQL кода используйте sql()
mf %>%
transmute(factorial = sql("CAST(x AS FLOAT)")) %>%
show_query()
mf %>%
filter(x == sql("ANY VALUES(1, 2, 3)")) %>%
show_query()
multidplyr
Цель multidplyr - позволить вам выполнять вычисления в многопоточном режиме, разделив таблицу на логические части, каждая часть обрабатывается на своём узле.
Варианты разбиения данных
multidplyr позволяет вам разбить данные двумя способами:
Разбиение уже существующей в памяти таблицы с помощью
partition().Разбиение таблицы на части в момент загрузки.
Синтаксис multidplyr
new_cluster()- создаёт кластер процессов;partition()- разбивает существующий в памяти набор данных на части, что бы каждая отдельная часть обрабатывалась на своём узле кластера;cluster_assign_partition()- разделяет вектор значений на части таким образом, что бы каждому узлу кластера досталась примерно одинаковое количество элементов;cluster_send()- запускает выполнение вычислений на разны узлах кластера;party_df()- создаёт секционированный дата фрейм;collect()- возвращает секционированный дата фрейм к обычному режиму работы.
Примеры кода multidplyr
Пример вычислений с multidplyr
library(multidplyr)
# создаЄм кластер
cluster <- new_cluster(4)
cluster
# разбиение фрейма на кластера --------------------------------------------
library(nycflights13)
flights1 <- flights %>% group_by(dest) %>% partition(cluster)
flights1
# выполн¤ем вычислени¤ в многопоточном режиме
flight_dest %>%
summarise(delay = mean(dep_delay, na.rm = TRUE), n = n()) %>%
collect()
# чтение файлов разными кластерами ----------------------------------------
# создаЄм временную папку
path <- tempfile()
dir.create(path)
# разбиваем файл по мес¤цам,
# сохран¤¤ данные каждого мес¤ца в отдельный файл
flights %>%
group_by(month) %>%
group_walk(~ vroom::vroom_write(.x, sprintf("%s/month-%02i.csv", path, .y$month)))
# находим все файлы в директории,
# и делим их так, чтобы каждому воркеру досталось (примерно) одинаковое количество файлов
files <- dir(path, full.names = TRUE)
cluster_assign_partition(cluster, files = files)
# считываем файлы на каждом воркере
# и используем party_df() дл¤ создани¤ секционированного фрейма данных
cluster_send(cluster, flights2 <- vroom::vroom(files))
flights2 <- party_df(cluster, "flights2")
flights2
# глаголы dplyr -----------------------------------------------------------
flights1 %>%
summarise(dep_delay = mean(dep_delay, na.rm = TRUE)) %>%
collect()
Какой бекенд выбрать
На данных среднего объёма предпочтительнее использовать
dtplyr, накладные расходы на конвертацию кода минимальны, при высокой скорости вычислений.Если ваша таблица насчитывает более десятков миллионов строк, то есть смысл использовать
multidplyr, накладные расходы на создание кластеров и обмен данных между ними выше, но и производительность выше. Но тут надо понимать, что в данных должна быть категориальная переменная, имеющая ограниченный набор уровней, и именно по этой переменной группируются ваши вычисления. Если такой переменной нет, то и особо прироста в скорости вычисленийmultidplyrне даст, и даже возможен обратный эффект, когда накладные расходы превысят расходы на вычисления в однопоточном режиме.К
dbplyrстоит прибегать в случае, когда данные с которыми вы работаете хранятся в базах данных. Данный бекенд направлен не столько на повышение производительности вычислений, сколько на то, что бы ваш R код не перемешивался с SQL запросами.
Заключение
В ходе этого урока, мы разобрались с тем, как ускорить вычисления реализованные с помощью dplyr глаголов на данных большого объёма, а так же как работать с данными хранящимися в базах данных, не засоряя при этом код громоздкими SQL запросами.
Надеюсь материал предоставленный в этом видео уроке был вам полезен!