dplyr
— R пакет, реализующий грамматику манипуляции данными, состоящую из набора согласованных между собой глаголов, которые помогут вам решить наиболее распространенные проблемы манипулирования данными на языке R.
Это один из наиболее популярных и скачиваемых из CRAN пакетов, сегодня им пользуются миллионы аналитиков и специалистов в области науки о данных.
Хедли Викхем работает над интерфейсом dplyr
с 2014 года, dplyr
это потомок plyr
, но более быстрый и изящный по синтаксису. За 6 лет синтаксис и функционал dplyr
устаканился, в связи с чем 29 мая был официальный релиз версии 1.0.0.
За 6 недель до релиза Викхем начал публиковать серию статей, что бы постепенно ознакомить многочисленных пользователей dplyr
со всеми грядущими изменениями.
В свою очередь я, преследуя туже цель, по большинству статей снимал небольшие видео обзоры, которыми и хотел с вами поделиться.
Эта публикация поможет вам максимально быстро ознакомится со всем, что было изменено или добавлено в dplyr
1.0.0.
Содержание
Если вы интересуетесь анализом данных возможно вам будут интересны мои telegram и youtube каналы. Большая часть контента которых посвящены языку R.
- Ссылки
- Установка dplyr 1.0.0
- Обзор нового функционала dplyr 1.0.0
3.1. Обновлённая функция summarise()
3.2. Функции select(), rename_with(), relocate()
3.3. Обращение к столбцам функцией accros()
3.4. Перебор строк функцией rowwise()
3.5. Добавление, изменение, удаление строк с помощью функций семейства rows_*() - Заключение
Ссылки
- Статьи на основе которых сняты все представленные обзоры
1.1. dplyr 1.0.0: new summarise() features, Hadley Wickham (англ.)
1.2. dplyr 1.0.0: select, rename, relocate, Hadley Wickham (англ.)
1.3. dplyr 1.0.0: working across columns, Hadley Wickham (англ.)
1.4. dplyr 1.0.0: working within rows, Hadley Wickham (англ.)
1.5. dplyr 1.0.0: last minute additions, Hadley Wickham, Kirill Müller (англ.) - Плейлист со всеми обзорами на YouTube
- Подписаться на YouTube канал
- Мой telegram канал
- Официальная страница пакета dplyr
Установка dplyr 1.0.0
Т.к. dplyr 1.0.0
уже опубликован на CRAN для его установки / обновления вам достаточно воспользоваться стандартной командой install.packages('dplyr')
.
Обзор нового функционала dplyr 1.0.0
Ниже представленны 5 видео обзоров снятых по статьям Хедли Викхема.
Длительность каждого видео составляет от 10 до 17 минут.
Обновлённая функция summarise()
summarise()
является одной из основных функций в dplyr
, начиная с версии 1.0.0 её возможности были значительно расширены. Одним из улучшений стало то, что теперь она может возвращать объекты произвольной размерности.
#devtools::install_github("tidyverse/dplyr")
library(dplyr)
# Основные изменения
## теперь sunnarise может вернуть
### вектор любой длинны
### дата фрейм любой размерности
# #######################################################
# тестовые данные
# #######################################################
df <- tibble(
grp = rep(1:2, each = 5),
x = c(rnorm(5, -0.25, 1), rnorm(5, 0, 1.5)),
y = c(rnorm(5, 0.25, 1), rnorm(5, 0, 0.5)),
)
df
# получим минимальные и максимальные значения для каждой группы
# и поместим эти значения в строки
df %>%
group_by(grp) %>%
summarise(rng = range(x))
## функция range возвращает вектор длинны 2
range(df$x)
## но функция summarise разворачивает его,
## приводя каждое из возвращаемых значений в новую строку
# тоже самое, но для столбцов
df %>%
group_by(grp) %>%
summarise(tibble(min = min(x), mean = mean(x)))
# #######################################################
# Расчёт квантилей
# #######################################################
df %>%
group_by(grp) %>%
summarise(x = quantile(x, c(0.25, 0.5, 0.75)), q = c(0.25, 0.5, 0.75))
# можем избежать дублирования кода и написать функцию для вычисления квантиля
quibble <- function(x, q = c(0.25, 0.5, 0.75)) {
tibble(x = quantile(x, q), q = q)
}
# используем собственную функцию в summarise
df %>%
group_by(grp) %>%
summarise(quibble(x, c(0.25, 0.5, 0.75)))
# доработаем функцию таким образом
# что бы названия столбца подтягивались из аргумена
quibble2 <- function(x, q = c(0.25, 0.5, 0.75)) {
tibble("{{ x }}" := quantile(x, q), "{{ x }}_q" := q)
}
df %>%
group_by(grp) %>%
summarise(quibble2(x, c(0.25, 0.5, 0.75)))
# мы не присваивали имена новых столбцов внутри summarise
# потому что если функция возвращает объект сложной стурктуры
# мы получим вложенные дата фреймы
out <- df %>%
group_by(grp) %>%
summarise(quantile = quibble2(y, c(0.25, 0.75)))
str(out)
# обращаемся к вложенному фрейму
out$y
# или к его столбцам
# по смыслу такая конструкция напоминает объяденённые имена стобцов в электронных таблицах
out$quantile$y_q
# summarise + rowwise
# эта комбинация функций теперь может заменить purrr и apply
tibble(path = dir(pattern = "\\.csv$")) %>%
rowwise(path) %>%
summarise(readr::read_csv(path))
# #######################################################
# Предыдущие подходы
# #######################################################
# вычисляем квантили
df %>%
group_by(grp) %>%
summarise(y = list(quibble(y, c(0.25, 0.75)))) %>%
tidyr::unnest(y)
df %>%
group_by(grp) %>%
do(quibble(.$y, c(0.25, 0.75)))
Функции select(), rename_with(), relocate()
В видео рассмотрены функции с помощью которых вы можете обращаться к переменным, или столбцам дата фреймов различными способами, а также менять их расположение: select()
, rename_with()
, relocate()
, any_of()
, all_of()
.
#devtools::install_github("tidyverse/dplyr")
library(dplyr, warn.conflicts = FALSE)
# rename
# Переименовать столбцы для устранение дублирования их имён
df1 <- tibble(a = 1:5, a = 5:1, .name_repair = "minimal")
df1
df1 %>% rename(b = 2)
# select
# обращение к столбцам по типу
df2 <- tibble(x1 = 1, x2 = "a", x3 = 2, y1 = "b", y2 = 3, y3 = "c", y4 = 4)
# числовые столбцы
df2 %>% select(is.numeric)
# НЕ текстовые столбцы
df2 %>% select(!is.character)
# смешанный тип обращения
# числовые стобцы, название которых начинается на X
df2 %>% select(starts_with("x") & is.numeric)
# выбор полей с помощью функций any_of и all_of
vars <- c("x1", "x2", "y1", "z")
df2 %>% select(any_of(vars))
df2 %>% select(all_of(vars))
# функция rename_with
df2 %>% rename_with(toupper)
df2 %>% rename_with(toupper, starts_with("x"))
df2 %>% rename_with(toupper, is.numeric)
# relocate для изменения порядка стобцов
df3 <- tibble(w = 0, x = 1, y = "a", z = "b")
# переместить столбцы y, z в начало
df3 %>% relocate(y, z)
# переместить текстовые столбцы вначало
df3 %>% relocate(is.character)
# поместить столбец w после y
df3 %>% relocate(w, .after = y)
# поместить столбец w перед y
df3 %>% relocate(w, .before = y)
# переместить w в конец
df3 %>% relocate(w, .after = last_col())
Обращение к столбцам функцией accros()
accros()
это новая функция в dplyr
, пришедшая на смену функциям с суффиксами *_at()
, *_if()
, *_all()
. Т.е. accros()
позволяет вам применять какую либо функцию к набору столбцов, не перечисляя все их по именам.
# devtools::install_github("tidyverse/dplyr")
library(dplyr, warn.conflicts = FALSE)
# тестовый дата фрейм
df <- tibble(g1 = as.factor(sample(letters[1:4],size = 10, replace = T )),
g2 = as.factor(sample(LETTERS[1:3],size = 10, replace = T )),
a = runif(10, 1, 10),
b = runif(10, 10, 20),
c = runif(10, 15, 30),
d = runif(10, 1, 50))
# о чём пойдёт речь
## копирование кода, когда требуется
## произвести одну и туже операцию с разными функциями
df %>%
group_by(g1, g2) %>%
summarise(a = mean(a), b = mean(b), c = mean(c), d = mean(c))
# новый способ
## теперь для таких преобразований можно
## использовать тот же синтаксис что и в select()
### посчитать среднее по столбцам от a до d
df %>%
group_by(g1, g2) %>%
summarise(across(a:d, mean))
### или посчитать среднее выбрав все числовые столбцы
df %>%
group_by(g1, g2) %>%
summarise(across(is.numeric, mean))
# ##############################
# Простой пример
# аргументы функции accros
## .cols - выбор столбцов по позиции, имени, функцией, типу данных, или комбинируя любые перечисленные способы
## .fns - функция, или список функций которые необходимо применить к каждому столбцу
## считаем количество униклаьных значений в текстовых полях
starwars %>%
summarise(across(is.character, n_distinct))
## пример с фильтрацией данных
starwars %>%
group_by(species) %>%
filter(n() > 1) %>%
summarise(across(c(sex, gender, homeworld), n_distinct))
## комбинируем accross с другими вычислениями
starwars %>%
group_by(homeworld) %>%
filter(n() > 1) %>%
summarise(across(is.numeric, mean, na.rm = TRUE),
n = n())
# ##############################
# Чем accross лучше предыдущих функций с суфиксами _at, _if, _all
## 1. accross позволяет комбинировать различные вычисления внутри одной summarise
## пример из статьи
df %>%
group_by(g1, g2) %>%
summarise(
across(is.numeric, mean),
across(is.factor, nlevels),
n = n(),
)
## рабочий пример
starwars %>%
group_by(species) %>%
summarise(across(is.character, n_distinct),
across(is.numeric, mean),
across(is.list, length),
n = n()
)
## 2. уменьшает количество необходимых функций в dplyr, что облегчает их запоминание
## 3. объединяет возможности функций с суфиксами if_, at_, и даёт возможность объединять их возможности
## 4. accross не требует от вас использования функции vars для указания нужных столбцлв, как это было раньше
# ##############################
# перевод старого кода на accross
## для перевода функций с суфиксами _at, _if, _all используйте следующие правила
### в accross первым агрументом будет:
### Для *_if() старый второй аргумент.
### Для *_at() старый второй аргумент с удаленным вызовом vars().
### Для *_all(), в качестве первого аргумента передайте функцию everything()
## примеры
df <- tibble(y_a = runif(10, 1, 10),
y_b = runif(10, 10, 20),
x = runif(10, 15, 30),
d = runif(10, 1, 50))
### из _if в accross
df %>% mutate_if(is.numeric, mean, na.rm = TRUE)
# ->
df %>% mutate(across(is.numeric, mean, na.rm = TRUE))
### из _at в accross
df %>% mutate_at(vars(c(x, starts_with("y"))), mean, na.rm = TRUE)
# ->
df %>% mutate(across(c(x, starts_with("y")), mean, na.rm = TRUE))
### из _all в accroos
df %>% mutate_all(mean, na.rm = TRUE)
# ->
df %>% mutate(across(everything(), mean, na.rm = TRUE))
Перебор строк функцией rowwise()
rowwise()
пришла на смену перебору строк в R с помощью различных циклов. Эта новая функция позволяет вам разбить фрейм по строкам, и применять к каждой группе строк агругирующие функции, при этом не меняя количества строк в выходящем фрейме.
#devtools::install_github("tidyverse/dplyr")
library(dplyr)
# test data
df <- tibble(
student_id = 1:4,
test1 = 10:13,
test2 = 20:23,
test3 = 30:33,
test4 = 40:43
)
df
# попытка посчитать среднюю оценку по студету
df %>% mutate(avg = mean(c(test1, test2, test3, test4)))
# используем rowwise для преобразования фрейма
rf <- rowwise(df, student_id)
rf
rf %>% mutate(avg = mean(c(test1, test2, test3, test4)))
# тоже самое с использованием c_across
rf %>% mutate(avg = mean(c_across(starts_with("test"))))
# ###########################
# некоторые арифметические операции векторизированы по умолчанию
df %>% mutate(total = test1 + test2 + test3 + test4)
# этот подход можно использовать для вычисления среднего
df %>% mutate(avg = (test1 + test2 + test3 + test4) / 4)
# так же вы можете использовать некоторые специальные функции
# для вычисления некоторых статистик
df %>% mutate(
min = pmin(test1, test2, test3, test4),
max = pmax(test1, test2, test3, test4),
string = paste(test1, test2, test3, test4, sep = "-")
)
# все векторизированные функции будут работать быстрее чем rowwise
# но rowwise позволяет векторизировать любую функцию
# ##################################
# работа со столбцами списками
df <- tibble(
x = list(1, 2:3, 4:6),
y = list(TRUE, 1, "a"),
z = list(sum, mean, sd)
)
# мы можем перебором обработать каждый список
df %>%
rowwise() %>%
summarise(
x_length = length(x),
y_type = typeof(y),
z_call = z(1:5)
)
# ##################################
# симуляция случайных данных
df <- tribble(
~id, ~ n, ~ min, ~ max,
1, 3, 0, 1,
2, 2, 10, 100,
3, 2, 100, 1000,
)
# используем rowwise для симуляции данных
df %>%
rowwise(id) %>%
mutate(data = list(runif(n, min, max)))
df %>%
rowwise(id) %>%
summarise(x = runif(n, min, max))
# ##################################
# функция nest_by позволяет создавать столбцы списки
by_cyl <- mtcars %>% nest_by(cyl)
by_cyl
# такой подход удобно использовать при построении линейной модели
# используем mutate для подгонки моели под каждую группа данных
by_cyl <- by_cyl %>% mutate(model = list(lm(mpg ~ wt, data = data)))
by_cyl
# теперь с помощью summarise
# можно извлекать сводки или коэфициенты построенной модели
by_cyl %>% summarise(broom::glance(model))
by_cyl %>% summarise(broom::tidy(model))
Добавление, изменение, удаление строк с помощью функций семейства rows_*()
В SQL часто используются так называемые DML (Data Manipulation Language) операции, такие как INSERT
, UPDATE
и DELETE
. Перед самым релизом dplyr 1.0.0
в него было добавлено целое семейство функций rows_*()
которые помогут вам выполнять все DML операции с датафреймами внутри языка R.
Также вы узнаете о новом аргмуенте .groups
, в функции summarise()
. Данный аргумент позволяет управлять группировкой фрейма после агрегации данных.
#devtools::install_github("tidyverse/dplyr")
library(dplyr)
# summarise + .groups
starwars %>%
group_by(homeworld, species) %>%
summarise(n = n())
## аргумент .groups
### .groups = "drop_last" удалит последнюю группу
### .groups = "drop" удалит всю группировку
### .groups = "keep" созранит всю группировку
### .groups = "rowwise" разобъёт фрейм на группы как rowwise()
# rows_*()
## rows_update(x, y) обновляет строки в таблице x найденные в таблице y
## rows_patch(x, y) работает аналогично rows_update() но заменяет только NA
## rows_insert(x, y) добавляет строки в таблицу x из таблицы y
## rows_upsert(x, y) обновляет найденные строки в x и добавляет не найденные из таблицы y
## rows_delete(x, y) удаляет строки из x найденные в таблице y.
# Создаём тестовые данные
df <- tibble(a = 1:3, b = letters[c(1:2, NA)], c = 0.5 + 0:2)
df
new <- tibble(a = c(4, 5), b = c("d", "e"), c = c(3.5, 4.5))
new
# Базовые примеры
## добавляем новые строки
df %>% rows_insert(new)
## row_insert вернёт ошибку если мы попытаемся добавить уже существующую строку
df %>% rows_insert(tibble(a = 3, b = "c"))
## если вы хотите обновить существующее значение необходимо использовать row_update
df %>% rows_update(tibble(a = 3, b = "c"))
## но rows_update вернёт ошибку если вы попытаетесь обновить несуществующее значание
df %>% rows_update(tibble(a = 4, b = "d"))
## rows_patch заполнит только пропущенные значения по ключу
df %>%
rows_patch(tibble(a = 2:3, b = "B"))
## rows_upsert также вы можете добавлять новые и заменять существуюие значения
## функцией rows_upsert
df %>%
rows_upsert(tibble(a = 3, b = "c")) %>%
rows_upsert(tibble(a = 4, b = "d"))
# ################################
# РАЗБЕРЁМ Аргументы немного более подробно
set.seed(555)
# менеджеры
managers <- c("Paul", "Alex", "Tim", "Bill", "John")
# товары
products <- tibble(name = paste0("product_", LETTERS),
price = round(runif(n = length(LETTERS), 100, 400), 0))
# функция генерации купленных товаров
prod_list <- function(prod_list, size_min, size_max) {
prod <- tibble(product = sample(prod_list,
size = round(runif(1, size_min, size_max), 0) ,
replace = F))
return(prod)
}
# генерируем продажи
sales <- tibble(id = 1:200,
manager_id = sample(managers, size = 200, replace = T),
refund = FALSE,
refund_sum = 0)
# генерируем списки купленных товаров
sale_proucts <-
sales %>%
rowwise(id) %>%
summarise(prod_list(products$name, 1, 6)) %>%
left_join(products, by = c("product" = "name"))
# объединяем продажи с товарами
sales <- left_join(sales, sale_proucts, by = "id")
# возвраты
refund <- sample_n(sales, 25) %>%
mutate( refund = TRUE,
refund_sum = price * 0.9) %>%
select(-price, -manager_id)
# отмечаем возвраты в таблице продаж
sales %>%
rows_update(refund)
# аргумент by
result <-
sales %>%
rows_update(refund, by = c("id", "product"))
Заключение
В видео обзорах я затронул только те статьи которые были бы полезны непосредственно прямым пользователям dplyr
, и упустил некоторые статьи общего характера, или направленные разработчикам других пакетов, которые также используют внутри собсвенных пакетов dplyr
.
Ниже список статей по которым небыло обзоров, но они также могут быть вам полезны:
Версия 1.0.0 означает что синтаксис пакета стал стабилен, в дальнейшем будут появляться новые функции и аргументы, но всё, что реализовано в версии 1.0.0 будет иметь в дальнейшем обратную совместимость.
Хедли Викхем и его команда проделали большую работу для того, что бы dplyr 1.0.0
стал наиболее удобным и популярным средством манипуляции данных на языке R, за что хочется выразить всем причастным к его созданию и развитию огромную благодарность.