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

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

Содержание

Если вы интересуетесь анализом данных возможно вам будут полезны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.

  1. Видео

    1. Тайм коды

  2. Презентация

  3. Краткий конспект

    1. dtplyr

    2. dbplyr

    3. multidplyr

    4. Какой бекенд выбрать

  4. Заключение

Видео

Тайм коды:

  • 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 запросами.

Надеюсь материал предоставленный в этом видео уроке был вам полезен!