Обзор библиотеки drake в R
Drake
предлагает систематический подход к построению и управлению зависимостями в проектах, автоматизируя процесс обработки данных и анализа. С помощью drake
можно отслеживать изменения в коде и данных, автоматически перезапуская только те части анализа, которые были изменены.
Создатель drake
, Уилл Ландау, искал способ улучшить репродуктивность исследований в R, и так родилась библиотека drake
. С тех пор она претерпела множество изменений и улучшений.
Установка в RStudio:
install.packages("drake")
Создание проекта
Drake
предпочитает четкую структуру проекта:
R/
для скриптов R, содержащих функции, используемые в планеdrake
.data/
для исходных данных, если они не генерируются в рамках проекта.reports/
для R Markdown документов или других отчетов, генерируемых вашим проектом.
План drake
это сердце проекта. Он определяет, какие задачи должны быть выполнены, их зависимости и порядок выполнения. Создадим файл R/drake_plan.R
и определим в нем план. Например:
library(drake)
my_plan <- drake_plan(
raw_data = read_csv(file_in("data/raw_data.csv")),
processed_data = process_data(raw_data),
results = analyze_data(processed_data),
report = rmarkdown::render(
input = "reports/report.Rmd",
output_file = file_out("reports/report.html"),
params = list(results = results)
)
)
Для каждой задачи в плане drake
создают соответствующие функции и они сохраняются в скриптах директории R/
. Например, функции process_data()
и analyze_data()
могут быть определены в файле R/functions.R
.
После того как план определен и написаны необходимые функции, можно запустить проект, используя функцию make()
:
make(my_plan)
Это команда соберет и выполнит план, учитывая все зависимости и необходимые пересчеты.
После выполнения плана результаты каждой задачи сохраняются в специальном кэше. Функция loadd()
позволяет загрузить эти результаты непосредственно в рабочую сессию R.
Если нужно просто чекнуть результаты или использовать их в условиях ограниченной памяти можно использовать readd()
. Функция позволяет прочитать результаты выполнения задач, не загружая их полностью в рабочую среду.
В процессе работы над проектом данные и результаты могут изменяться. Функция drake_history()
выдает детальный журнал этих изменений, позволяя отслеживать эволюцию проекта.
Для удобства понимая структуры проекта можно визуализировать план с помощью vis_drake_graph()
.
Работа с зависимостями
Drake
использует концепцию плана проекта, чтобы определить, какие задачи необходимо выполнить, и установить между ними зависимости. т.е такdrake
может автоматически отслеживать, какие данные и код изменяются, и перезапускать только те задачи, которые зависят от измененных элементов:
library(drake)
plan <- drake_plan(
raw_data = read_csv("data/raw_data.csv"),
processed_data = clean_data(raw_data),
analysis_results = analyze_data(processed_data)
)
make(plan)
drake
автоматически определит, что processed_data
зависит от raw_data
, а analysis_results
— от processed_data
. Если raw_data.csv
изменится, drake
перезапустит задачи для processed_data
и analysis_results
при следующем вызове make()
.
Можно также юзать wildcard для генерации задач:
plan <- drake_plan(
raw_data = target(
read_csv(file_in(paste0("data/", dataset, ".csv"))),
transform = map(dataset = c("dataset1", "dataset2", "dataset3"))
),
processed_data = target(
clean_data(raw_data),
transform = map(raw_data)
),
analysis_results = target(
analyze_data(processed_data),
transform = map(processed_data)
)
)
make(plan)
map()
используется для создания задач для каждого элемента вектора dataset
. Drake
автоматически генерирует и управляет зависимостями для каждой из этих задач, обеспечивая, чтобы для каждого набора данных были выполнены все необходимые шаги от чтения до анализа.
Drake
поддерживает параллельное выполнение задач. Можно указать drake
использовать несколько ядер или неск. серверов с помощью пакета future:
library(drake)
library(future)
plan <- drake_plan(
raw_data = read_csv("data/raw_data.csv"),
processed_data = clean_data(raw_data),
results = analyze_data(processed_data)
)
# параллельный бэкенд
plan <- drake_config(plan, parallelism = "future")
future::plan(multisession, workers = 4) # использование 4 ядер
make(plan)
Статическое ветвление
Статическое ветвление в drake
использует функцию target()
вместе с аргументом transform
для создания множества целей на основе шаблона:
library(drake)
plan <- drake_plan(
raw_data = target(
read_csv(file_name),
transform = map(file_name = c("data1.csv", "data2.csv", "data3.csv"))
)
)
Для каждого файла из списка c("data1.csv", "data2.csv", "data3.csv")
будет сгенрирована отдельная цель raw_data
, которая считывает данные из файла.
К примеру есть несколько наборов данных, и хочется применить к каждому из них один и тот же аналитический процесс:
plan <- drake_plan(
processed_data = target(
preprocess_data(raw_data),
transform = map(raw_data = c("dataset1", "dataset2", "dataset3"))
),
analysis_results = target(
analyze_data(processed_data),
transform = map(processed_data)
)
)
Статическое ветвление также можно использовать для выполнения параметрических исследований, где одна и та же функция анализа применяется с различными параметрами:
plan <- drake_plan(
analysis_results = target(
analyze_data(param),
transform = map(param = seq(1, 10))
)
)
Можно автоматизировать генерацию отчетов для различных сегментов данных или параметров:
plan <- drake_plan(
report = target(
render_report(data_segment),
transform = map(data_segment = c("segment1", "segment2", "segment3"))
)
)
Динамическое ветвление
Динамическое ветвление в drake
использует функцию drake_plan()
с аргументом transform = dynamic_map()
для создания задач, которые зависят от данных, генерируемых во время выполнения проекта:
library(drake)
plan <- drake_plan(
data = target(
read_csv(file_name),
transform = dynamic_map(file_name = list_files("data/"))
),
analysis = target(
analyze(data),
transform = dynamic_map(data)
)
)
list_files("data/")
генерирует список файлов в реальном времени, и для каждого файла создается отдельная задача чтения данных.
Если данные для анализа генерируются или обновляются в процессе выполнения проекта:
plan <- drake_plan(
simulation_results = target(
simulate_experiment(parameters),
transform = dynamic_map(parameters = generate_parameters())
),
summary = target(
summarize_results(simulation_results),
transform = dynamic_map(simulation_results)
)
)
Когда требуется выполнить анализ для различных сегментов данных, которые определяются на основе предварительного анализа:
plan <- drake_plan(
data_segments = target(
segment_data(raw_data),
transform = dynamic_map(raw_data = load_raw_data())
),
analysis_results = target(
analyze_segment(data_segment),
transform = dynamic_map(data_segment = data_segments)
)
)
Анализ оттока клиентов
Анализ оттока клиентов — это процесс определения причин, по которым клиенты покидают сервис или перестают пользоваться продуктом.
Подготовим данные:
library(drake)
library(tidyverse)
library(lubridate)
plan <- drake_plan(
raw_data = read_csv('path/to/customer_data.csv'),
clean_data = raw_data %>%
mutate(churn_date = as.Date(churn_date, format = "%Y-%m-%d")) %>%
filter(!is.na(churn_date)),
processed_data = clean_data %>%
mutate(churn = if_else(churn_date <= as.Date('2023-12-31'), 1, 0))
)
Для анализа оттока клиентов можно юзать keras:
library(keras)
model_plan <- drake_plan(
model = keras_model_sequential() %>%
layer_dense(units = 256, activation = 'relu', input_shape = c(num_features)) %>%
layer_dropout(rate = 0.4) %>%
layer_dense(units = 128, activation = 'relu') %>%
layer_dropout(rate = 0.3) %>%
layer_dense(units = 1, activation = 'sigmoid'),
compile_model = compile(
model,
optimizer = 'adam',
loss = 'binary_crossentropy',
metrics = c('accuracy')
),
fit_model = fit(
model,
x_train,
y_train,
epochs = 30,
batch_size = 128,
validation_split = 0.2
)
)
После обучения модели следует определить факторы, влияющие на отток клиентов, в нашем примере это делается довольно просто:
evaluation_plan <- drake_plan(
evaluation = evaluate_model(model, x_test, y_test),
key_factors = identify_key_factors(model)
)
Иерархическое моделирование с Stan
Иерархическая модель позволяет учитывать данные, сгруппированные на разных уровнях иерархии. Например, данные о студентах могут быть сгруппированы по классам, а классы — по школам.
Допустим, хочется проанализировать данные о баллах тестов студентов, учитывая влияние конкретной школы:
data {
int N; // колво студентов
int K; // колво школ
int school[N]; // индекс школы для каждого студента
vector[N] score; // баллы тестов
}
parameters {
real alpha; // глоб. среднее
vector[K] beta; // эффекты школ
real sigma; // дефолтное отклонение баллов
real sigma_beta; // дефолтное отклонение эффектов школ
}
model {
beta ~ normal(0, sigma_beta); // априорное распределение для эффектов школ
score ~ normal(alpha + beta[school], sigma); // модель
}
С drake можно включить этапы компиляции и валидации модели Stan как рабочего процесса:
library(drake)
library(rstan)
library(bayesplot)
plan <- drake_plan(
compile_model = stan_model('path/to/model.stan'),
fit_model = sampling(compile_model, data = stan_data),
validate_model = posterior_predict(fit_model, newdata = validation_data),
plot_validation = mcmc_areas(validate_model)
)
compile_model
компилирует модель Stan, fit_model
выполняет сэмплирование для оценки параметров модели на основе данных, validate_model
использует постериорное предсказание для валидации модели на новых данных, а plot_validation
визуализирует результаты валидации.
Для тех, кто заинтересован в более глубоком изучении drake
, существует официальная рекомендация.
В завершение хочу пригласить всех желающих на бесплатный вебинар, где обсудим, что нужно знать про найм в ML/DS в начале карьеры.