dplyr
один из наиболее популярных пакетов для языка R, основным преимуществом которого является удобочитаемый и понятный синтаксис. Из недостатков данного пакета можно отметить, что при работе с данными большого объёма он значительно уступает в скорости вычислений например data.table
.
В этом видео уроке мы разберёмся с тем, как можно ускорить вычисления на dplyr
, за счёт бекендов dtplyr
и multidplyr
, а так же узнаем о том, как и зачем можно использовать бекенд dbplyr
, предназначенный для работы с базами данных.
Содержание
Если вы интересуетесь анализом данных возможно вам будут полезны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.
Видео
Тайм коды:
00:00 Вступление
00:59 Какие бекенды мы рассмотрим
01:48 Цель
dtplyr
02:30 Синтаксис
dtplyr
03:33 Пример работы с
dtplyr
05:38 Как осуществляется перевод глаголов
dplyr
в синтаксисdata.table
07:51 Функция
show_query()
11:27 Почему dtplyr медленнее чем
data,table
13:24 Цель
dbplyr
13:40 Синтаксис
dbplyr
14:37 Пример работы с
dbplyr
16:54 Перевод
dplyr
глаголов в SQL запросы17:53 Как реализованы подзапросы в
dbplyr
18:25 Перевод неизвестных R функций и инфиксных операторов в SQL запрос
19:48 Проброс SQL команд в запросы
20:15 Как происходит перевод функций внутри
dplyr
глаголов в SQL запросы, функцияtranslate_sql()
21:26 Введение в
multidplyr
22:17 Варианты применения
multidplyr
22:50 Пример работы с
multidplyr
28:24 Какой пакет использовать
dtplyr
илиmultidplyr
29: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 запросами.
Надеюсь материал предоставленный в этом видео уроке был вам полезен!