Урок 4 Пакет purrr
4.1 Описание
Рассмотренные в прошлом уроке функции семейства apply
действительно полноценно могут заменить цикл for
, и повысить производительность вашего кода. Но есть и более продвинутые функционалы, которые пердоставляет пакет purrr
.
В этом уроке вы знаете:
- Какие преимущества даёт пакет
purrr
перед функциямиapply
. - Познакомитесь с семействами функций
map
,map2
,pmap
,invoke
. - Узнаете некоторые другие дополнительные функции из пакета
purrr
.
4.3 Тайм коды
00:00 Вступление.
00:57 Какие преимущества даёт пакет purrr
.
02:15 Какие семейства функций есть в purrr
.
03:29 Семейство функций map
.
04:26 Основные аргументы функций пакета purrr
.
05:20 Работа с функциями семейства map()
.
08:23 Пример сравнения map()
с циклом for
.
08:56 Функции map_dfr()
, map_dfc()
.
13:01 Итерирование сразу по нескольким объектам, семейства функций map2
и pmap
.
15:01 Синтаксис формул в purrr.
20:05 Функции семейства walk.
22:31 Функции keep()
и discard()
.
26:27 Итерация по функциям с помощью функций семейства invoke
.
29:12 Функции reduce()
и accumulate()
.
34:23 Заключение.
4.4 Код
# install.packages('purrr')
library(purrr)
library(dplyr)
# функции map_*------------------------------------------------------------
## Генерируем случайные выборки с нормальным распределением
v_sizes <- c(5, 12, 20, 30)
map(v_sizes, rnorm)
# используем доп аргументы
rnd_list <- map(v_sizes, runif, min = 10, max = 25)
# получаем вектора
map_dbl(rnd_list, mean)
# аналог в цикле
for ( i in rnd_list ) cat(mean(i), " ")
# пример с таблицами
products <- tibble(
product_id = 1:10,
name = c('Notebook',
'Smarthphone',
'Smart watch',
'PC',
'Playstation',
'TV',
'XBox',
'Wifi router',
'Air conditioning',
'Tablet'),
price = c(1000, 850, 380, 1500, 1000, 700, 870, 80, 500, 150)
)
managers <- c("Svetlana", "Andrey", "Ivan")
clients <- paste0('client ', 1:30)
create_transaction <- function(
transaction_id,
products_number = 3,
product_dict,
counts = c(1, 3),
dates = c(Sys.Date() - 30, Sys.Date()),
managers,
clients
) {
transaction <- sample_n(product_dict, size = products_number, replace = F) %>%
mutate(date = sample( seq(dates[1], dates[2], by = 'day'), size = 1 ),
manager = sample(managers, 1),
clients = sample(clients, 1),
count = sample(seq(counts[1], counts[2]), products_number, replace = T),
sale_sum = price * count,
transaction_id)
return(transaction)
}
# генерируем 5 транзакций
map_dfr(1:5,
create_transaction,
products_number = sample(1:10, 1),
product_dict = products,
counts = c(1, 3),
dates = c(Sys.Date() - 30, Sys.Date()),
managers = managers,
clients = clients,
.id = 'transaction_id')
# функции pmap_* ----------------------------------------------------------
# для итерации по двум объектам можно использовтаь функции map2_*
x <- list(1, 1, 1)
y <- list(10, 20, 30)
map2(x, y, ~ .x + .y)
# если необходимо итерировать более чем по двум объектам используйте pmap_*
params <- tibble(
transaction_id = 1:3,
products_number = c(4, 2, 6),
product_dict = list(products, products, products),
counts = list(c(1, 3), c(7, 10), c(2, 7)),
dates = list(c(as.Date('2021-11-01'), as.Date('2021-11-04')),
c(as.Date('2021-11-05'), as.Date('2021-11-08')),
c(as.Date('2021-11-09'), as.Date('2021-11-14'))),
managers = list(managers, managers, managers),
clients = list(clients, clients, clients)
)
tranaction_df <- pmap_df(params, create_transaction)
# функции walk ------------------------------------------------------------
# генерируем 7 транзакций
transactions <- map(1:7,
create_transaction,
products_number = sample(1:10, 1),
product_dict = products,
counts = c(1, 3),
dates = c(Sys.Date() - 30, Sys.Date()),
managers = managers,
clients = clients)
file_names <- paste0('transaction_', 1:7, ".csv")
walk2(
.x = transactions,
.y = file_names,
write.csv
)
# функции keep и discard --------------------------------------------------
# смотрим количество товаров в транзакциях
map_dbl(transactions, ~ sum(.x$sale_sum))
# оставить транзакции с суммой более 3000
transactions %>%
keep(~ sum(.x$sale_sum) >= 3000)
# исключить транзакции с суммой более 4000
transactions %>%
discard(~ sum(.x$sale_sum) >= 4000)
# теперь используем в конвейере функции keep и walk
transactions %>%
keep(~ sum(.x$sale_sum) >= 3000) %>%
walk2(
.x = .,
.y = paste0('transaction_3k_', seq_along(.), ".csv"),
write.csv
)
# применяем несколько функций к объекту invoke ----------------------------
fun <- c('mean', 'sum', 'length')
params <- list(
list(x = tranaction_df$sale_sum),
list(... = tranaction_df$sale_sum),
list(x = tranaction_df$sale_sum)
)
invoke_map_dbl(fun, params)
df <- tibble::tibble(
f = c("runif", "rpois", "rnorm"),
params = list(
list(n = 10),
list(n = 5, lambda = 10),
list(n = 10, mean = -3, sd = 10)
)
)
df
invoke_map(df$f, df$params)
# функции reduce и accumulate ---------------------------------------------
# допустим что у нас каждый менеджер имеет индивидуальный процент от продаж
# А каждый клиент персональную скидку по договору
managers_dict <- tibble(
manager = managers,
department = c('Sale', 'Sale', 'Marketing'),
salary_percent = c(0.1, 0.12, 0.2)
)
clients_dict <- tibble(
clients = clients,
discount = runif(length(clients), min = 0, max = 0.4)
)
data_model <- list(tranaction_df, managers_dict, clients_dict)
reduce(transaction_data, left_join) %>%
mutate(manager_bonus = sale_sum * salary_percent,
total_sum = sale_sum - (sale_sum * discount),
cumulate_minuses = accumulate(sale_sum - total_sum + manager_bonus, sum))
# эквивалент на чистом dplyr
tranaction_df %>%
left_join(managers_dict) %>%
left_join(clients_dict) %>%
mutate(manager_bonus = sale_sum * salary_percent,
total_sum = sale_sum - (sale_sum * discount),
cumulate_minuses = cumsum(sale_sum - total_sum + manager_bonus))
4.7 Дополнительные материалы
- Крайне рекомендую ознакомится с 17 главой книги “Язык R в задачах науки о данных”.