Skip to content

Instantly share code, notes, and snippets.

@aa989190f363e46d
Last active December 7, 2020 17:11
Show Gist options
  • Save aa989190f363e46d/e65b46dcba8874a202fe to your computer and use it in GitHub Desktop.
Save aa989190f363e46d/e65b46dcba8874a202fe to your computer and use it in GitHub Desktop.
---
title: "Introduction to dplyr"
author: "mz"
date: "09/21/2014"
output:
html_document:
keep_md: yes
---
```{r imports,echo=FALSE, warning=FALSE, error=FALSE, message=FALSE}
library(hflights)
library(dplyr)
library(ggplot2)
```
# Введение в dplyr[*](http://cran.rstudio.com/web/packages/dplyr/vignettes/introduction.html)
При обработке данных вы должны:
1. Представить что вы хотите получить.
2. Детально описать желаемое в виде компьютерной программы.
3. Выполнить код.
Пакет `dplyr` делает каждый из этих шагов на столько простым и быстрым на сколько это возможно следующим образом:
* Выделяя наиболее общие операции манипулирования данными, которые позволяют вам сосредоточиться на решении задачи, а не на манипулировании.
* Предлагая набор простых функций, соответствующих наиболее общим атомарным операциям манипулирования данными, так чтобы замысел максимально легко был выражен в коде.
* Используя эффективные хранилища данных чтобы максимально сократить время ожидания результата.
Цель этого документа -- дать представление о базовых инструментах предоставляемых `dplyr`, и продемонстрировать их применение к структурам данных типа `data.frame`.
Также существуют отдельные руководства по следующим темам:
* `databases`[*](http://cran.rstudio.com/web/packages/dplyr/vignettes/databases.html): наравне с объектами в памяти `dplyr` также может обращаться к данным баз данных. Это позволяет вам работать с удаленными (в смысле нахождения на расстоянии) данными, используя в точности те же инструменты, потому что `dplyr` будет транслировать ваш код на R в соответствующие SQL-запросы.
* `benchmark-baseball`: рассматривает как `dplyr` выглядит в сравнении с другими инструментами для манипулирования данными на реалистичной задаче.
* `window-functions`[*](http://cran.rstudio.com/web/packages/dplyr/vignettes/window-functions.html): функции агрегирования по окну -- это разновидность функций агрегирования, где функция агрегирования вычисляет по n аргументам 1 значение, а функции агрегирования по окну получает n аргументов и возвращает n значений.
# Набор данных 'hflights'
Для исследования основных операций манипулирования данными в `dplyr` мы будем использовать встроенный набор данных `hflights`. Он содержит данные всех `r dim(hflights)[1]` рейсов из Хьюстона за 2011 год, и предоставлен, как написано в `?hflights`, [бюро по перемещениям США](http://www.transtats.bts.gov/DatabaseInfo.asp?DB_ID=120&Link=0).
```{r pic1}
library(hflights)
dim(hflights)
head(hflights)
```
`dplyr` может работать с `data.frame` непосредственно, но при работе с большими объемами данных стоит конвертировать их в `tbl_df`, обертку вокруг `data.frame` которая не станет по случайности выводить кучу данных на экран (это может очень медленным процессом).
```{r pic2}
hflights_df <- tbl_df(hflights)
hflights_df
```
# Основные операции
`dplyr` предлагает пять основных операций для манипуляции данными применимые к отдельной таблице: `filter()`, `arrange()`, `select()`, `mutate()` and `summarise()`. Для тех кто пользовался до этого `plyr` многие из них будут знакомы.
## Фильтрация строк при помощи filter()
`filter()` позволяет вам выбрать подмножество строк из `data.frame`. Первый аргумент -- имя набора данных, второй и последующие -- условия фильтра в контексте этого набора данных.
*Например* мы можем выбрать все вылет 1-го января следующим образом:
```{r pic3}
filter(hflights_df, Month == 1, DayofMonth == 1)
```
Что будет эквивалентно более многословному (длиннее на целых 10 символов!) варианту:
```{r pic4, eval=FALSE}
hflights[hflights$Month == 1 & hflights$DayofMonth == 1, ]
```
`filter()` работает аналогично `subset()` за тем исключением, что вы можете ему передать любое количество условий для фильтра, которые будут объединены вместе через `&` (логическое И, но не `&&` с которым можно случайно перепутать). Вы можете также использовать любы другие логически связки:
```{r pic5, eval=FALSE}
filter(hflights_df, Month == 1 | Month == 2)
```
## Упорядочивание строк при помощи arrange()
`arrange()`, в свою очередь, работает аналогично `subset()` за исключением того, что вместо выбора строк она переупорядочивает их. Функция получает на вход имя набора данных и список имен колонок (или более сложное выражение) для упорядочивания по ним. Если будет указано больше одной колонки, то каждая следующая колонка будет упорядочиваться в пределах каждого отдельного набора значений предыдущих:
---
На этом месте я просто удивлён на сколько косно это звучит на русском по сравнению с английским.
---
```{r pic6}
arrange(hflights_df, DayofMonth, Month, Year)
```
Необходимо использовать `desc()` чтобы задать обратный порядок:
```{r pic7}
arrange(hflights_df, desc(ArrDelay))
```
`dplyr::arrange()` работает так же как и `plyr::arrange()`. Это простая обертка над `order()`, только позволяющая меньше набирать на клавиатуре. Предыдущий пример эквивалентен такому выражению:
```{r pic8, eval=FALSE}
hflights[order(hflights$DayofMonth, hflights$Month, hflights$Year), ]
hflights[order(desc(hflights$ArrDelay)), ]
```
## Выбор колонок при помощи select()
Часто случается что при работе с большим набором данных вам на самом деле интересны только некоторые колонки. `select()` позволяет вам быстро сфокусироваться на интересующей части колонок, используя при этом символические имена на манер номеров колонок:
```{r pic9}
# Выбор колонки по имени
select(hflights_df, Year, Month, DayOfWeek)
# Выбор всех колонок между Year и DayOfWeek (включительно)
select(hflights_df, Year:DayOfWeek)
# Выбор всех колонок за исключением тех что между Year и DayOfWeek (включительно)
select(hflights_df, -(Year:DayOfWeek))
```
Эта функция работает подобно `base::subset()`. Отдельная функция добавлена для поддержания стройности идеологии `dplyr`, заключающейся в наличии функций каждая из которых делает только одну конкретную операцию и наиболее подходящим образом.
## Создание новых колонок при помощи mutate()
Бывает полезно не только выбрать интересующие колонки, но и вычислить на их основании значения некоторой другой колонки. Для этих целей предназначена `mutate()`:
```{r pic10}
mutate(hflights_df,
gain = ArrDelay - DepDelay,
speed = Distance / AirTime * 60)
```
`dplyr::mutate()` работает аналогично `plyr::mutate()` и аналогично `base::transform()`. Главное отличие между `mutate()` and `transform()` в том что первая может ссылаться в вычислениях на колонку которая создается в рамках того же вызова функции:
```{r pic11}
mutate(hflights_df,
gain = ArrDelay - DepDelay,
gain_per_hour = gain / (AirTime / 60)
)
```
```{r pic12, eval=FALSE}
transform(hflights,
gain = ArrDelay - DepDelay,
gain_per_hour = gain / (AirTime / 60)
)
#> Error: object 'gain' not found
```
## Вычисление итогов при помощи summarise()
Последняя рассматриваемая операция -- `summarise()`, она просто сворачивает данные набора в одну колонку. Пока это не слишком полезно:
```{r pic13}
summarise(hflights_df,
delay = mean(DepDelay, na.rm = TRUE))
```
Такое поведение в точности эквивалентно поведению `plyr::summarise()`.
## Итог
У вас может возникнуть впечатление что все перечисленные функции имеют что-то общее:
* Первый аргумент -- `data.frame`.
* Последующие аргументы описываю то что вы хотите проделать с первым, и вы можете обращаться к атрибутам первого аргумента по именам, не используя для доступа `$`.
* Возвращаемый результат -- новый `data.frame`.
Все эти свойства вместе позволяют легко комбинировать множество вызовов в одну цепочку для вычисления сложного результата.
Эти пять функций создают основу для языка манипулирования данными. На самом простом уровне вы можете только изменить что-либо в наборе данных пятью способами: изменить порядок строк (`arrange()`), выбрать наблюдения или атрибуты (`filter()` и `select()`), добавить вычисляемые атрибуты (`mutate()`) или свернуть набор в итог (`summarise()`). Оставшаяся часть этого языка образуется из применения перечисленных функций к различным типам данных, например группированным данным, как описано ниже.
# Операции группировки
Вышеописанные функции полезны сами по себе, но особую мощь они приобретают в комбинации с концепцией (нет, не коллективной безопасности (: ) группировки, повторения вычислений для каждой группы наблюдений в отдельности. В `dplyr` используется функция `group_by()` для разбиения набора данных на части по строкам. Вы можете использовать вывод функции `group_by()` непосредственно как первый аргумент перечисленных выше пяти основных функций, они сами обработают правильно разбитый на группы набор данных.
Из функций в предыдущем разделе `select()` не реагирует на то что его первый аргумент сгруппирован, `arrange()` просто ведет себя так, как будто его вторым аргументом указанны атрибуты по которым сгруппирован первый аргумент. Погруппные `mutate()` и `filter()` наиболее полезны в совокупности с функциями агрегирования по окну[*](http://cran.rstudio.com/web/packages/dplyr/vignettes/window-functions.html), но это описано в отдельном руководстве (`vignette()`). `summarise()` в данном случае весьма полезна и проста в применении как будет более детально показано далее.
В следующем примере мы разделим набор данных по самолетам и рассчитаем количество вылетов (`count = n()`) и средние дальность полета (`dist = mean(Distance, na.rm = TRUE)`) и задержку вылета (`delay = mean(ArrDelay, na.rm = TRUE)`). Затем построим график при помощи `ggplot2`.
```{r pic14, warning=FALSE, message=FALSE}
planes <- group_by(hflights_df, TailNum)
delay <- summarise(planes,
count = n(),
dist = mean(Distance, na.rm = TRUE),
delay = mean(ArrDelay, na.rm = TRUE))
delay <- filter(delay, count > 20, dist < 2000)
# Интересно, на сколько зависит
# задержка от дальности полёта
ggplot(delay, aes(dist, delay)) +
geom_point(aes(size = count), alpha = 1/2) +
geom_smooth() +
scale_size_area()
```
Вы использовали `summarise()` с функциями агрегирования, получающими вектор значений и возвращающими скалярное значение. Множество таких функций есть в базовой среде R, например `min()`, `max()`, `mean()`, `sum()`, `sd()`, `median()` и `IQR()`. `dplyr` предлагает еще несколько полезных:
* `n()`: количество наблюдений в группе,
* `n_distinct(x)`: количество наблюдений с уникальным значением переменной x.
* `first(x)`, `last(x)` и `nth(x, n)` -- работают подобно `x[1]`, `x[length(x)]`, и `x[n]`, но дают больше контроля над результатом если значение не может быть получено.
Например, мы можем использовать их чтобы найти номера самолетов и количество вылетов во все возможные пункты назначения:
```{r pic15}
destinations <- group_by(hflights_df, Dest)
summarise(destinations,
planes = n_distinct(TailNum),
flights = n()
)
```
Также вы можете применить собственную функцию. Для повышения производительности многие функции `dplyr` написаны на `C++`. Если вы хотите использовать собственную функцию на `C++` обратите внимание на соответствующее руководство[*](http://cran.rstudio.com/web/packages/dplyr/vignettes/hybrid-evaluation.html) для уточнения деталей.
При группировке по нескольким переменным вы можете также получить "послойные" итоги для каждого уровня в отдельности и по очереди. Этот прием позволяет кумулятивно получать свертку набора данных:
---
Мутно? Взгляни на оригинала и попробуй прояснить сам:
When you group by multiple variables, each summary peels off one level of the grouping. That makes it easy to progressively roll-up a dataset:
---
```{r}
daily <- group_by(hflights_df, Year, Month, DayofMonth)
(per_day <- summarise(daily, flights = n()))
(per_month <- summarise(per_day, flights = sum(flights)))
(per_year <- summarise(per_month, flights = sum(flights)))
```
Однако необходимо проявлять осторожность при таком методе получения итогов: он подойдет для сумм и количеств, но нужно задуматься при использовании средних и дисперсий, и уж точно не применять с медианой.
# Последовательные манипуляции
API (тот самый мощный ЭПиАй) `dplyr` функционален в том смысле что вызовы функций не имеют побочных эффектов, и вы должны сами сохранять результаты. Это обстоятельство не способствует написанию элегантного кода если вам нужного выполнить множество операций подряд. Вы, конечно, можете выполнять их шаг за шагом:
```{r,eval=FALSE}
a1 <- group_by(hflights, Year, Month, DayofMonth)
a2 <- select(a1, Year:DayofMonth, ArrDelay, DepDelay)
a3 <- summarise(a2,
arr = mean(ArrDelay, na.rm = TRUE),
dep = mean(DepDelay, na.rm = TRUE))
a4 <- filter(a3, arr > 30 | dep > 30)
```
Или, если вы не желаете сохранять промежуточные результаты, можно передавать вызовы функций как аргументы, вкладывая один в другой:
```{r}
filter(
summarise(
select(
group_by(hflights, Year, Month, DayofMonth),
Year:DayofMonth, ArrDelay, DepDelay
),
arr = mean(ArrDelay, na.rm = TRUE),
dep = mean(DepDelay, na.rm = TRUE)
),
arr > 30 | dep > 30
)
```
Что создает сложности с чтением, так как порядок выполнения начинается из середины выражения и аргументы отдаляются от текста вызова функции. Чтобы обойти это проблему `dplyr` использует оператор `%>%`. `x %>% f(y)` преобразуется в `f(x, y)`, что можно использовать для преобразования кода вызовов в удобный для чтения слева направо, сверху вниз:
```{r}
hflights %>%
group_by(Year, Month, DayofMonth) %>%
select(Year:DayofMonth, ArrDelay, DepDelay) %>%
summarise(
arr = mean(ArrDelay, na.rm = TRUE),
dep = mean(DepDelay, na.rm = TRUE)
) %>%
filter(arr > 30 | dep > 30)
```
# Другие источники данных
Также как с `data.frame`, `dplyr` может работать с данными представленными другими способами, такими как `data tables`, базы данных и многомерные массивы.
## Data tables
`dplyr` также предлагает все перечисленные методы манипулирования данным и для объектов типа `data.tables`, вы просто должны заменить набор данных на `data.table`.
`data.table` может оказаться быстрее во многих случаях, потому что возможно выполнение нескольких операций одновременно. Например, вы можете выполнить `mutate` и `select` за один вызов, и `data.table` сообразит что нет нужды рассчитывать новую переменную в строках которые должны быть отфильтрованы.
---
For multiple operations, data.table can be faster because you usually use it with multiple verbs at the same time. For example, with data table you can do a mutate and a select in a single step, and its smart enough to know that theres no point in computing the new variable for the rows youre about to throw away.
---
Преимущества при использовании `data.tables` следующие:
* В большинстве случаев `data.tables` изолирует вас от данных непосредственно, и защищает их таким образом от непреднамеренного изменения.
* Вместо изощренного использования встроенного оператора `[`, предлагается множество относительно простых методов.
## Базы данных
`dplyr` позволяет использовать удаленные базы данных так же как `data.frame`. Избавляет таким образом от необходимости постоянно переключать мышление между языками. Для уточнения деталей необходимо обратиться к соответствующему руководству[*](http://cran.rstudio.com/web/packages/dplyr/vignettes/databases.html).
В сравнении с другим вариантами использования баз данных:
* скрывается, на сколько это возможно, факт использования удаленной базы данных
* исчезает необходимость изучать какой-либо диалект sql (хотя это может быть полезно!)
* предоставляется прослойка между множеством различных реализаций интерфейсов баз данных.
## Многомерные массивы/кубы
`tbl_cube()` предлагает экспериментальный интерфейс к многомерным массивам или кубам (как OLAP-кубы) . Если вы используете подобное представление в R, пожалуйста, свяжитесь с автором чтобы он лучше понял ваши потребности.
# Сравнение с другими подходами
В сравнение со всеми существующими альтернативами, `dplyr`:
* абстрагирует от способа хранения данных, так что возможно использование одного и того же набора функций для манипулирования `data.frame`, `data.tables` и удаленными базами данных. Это позволяет думать только о том что вы хотите сделать с данными, а не об устройстве хранилища данных
* предлагает продуманный метод `print()`, которые не распечатает случайно несколько страниц данных на экран (вдохновение было почерпнуто в `data.tables`).
В сравнении с функциями из основной поставки:
* `dplyr` намного более строен; функции имеют идентичный интерфейс, так что освоив одну вы разбираетесь и в других
* функции из базовой поставки стремятся обрабатывать векторы; `dplyr` концентрируется на наборах данных
В сравнении с `plyr`:
* `dplyr` намного намного быстрее
---
как это правильно перевести?
it provides a better thought out set of joins
---
* предлагает более продуманный набор объединений?
* предлагает инструменты только для работы с `data.frame` (т.о. большая часть `dplyr` эквивалентна `ddply()` с добавлением некоторых функций, `do()` эквивалентна `dlply()`)
В сравнении с использованием виртуальных `data.frame`:
---
в этом месте я не уверен что правильно понял английский текст
* it doesn't pretend that you have a data frame: if you want to run lm etc, you'll still need to manually pull down the data
* it doesn't provide methods for R summary functions (e.g. mean(), or sum())
---
* виртуальный `data.frame` не предполагает что у вас есть данные, так что если необходимо выполнить `lm()` понадобится получить данные вручную
* виртуальный `data.frame` не предоставляет методов для агрегатных функций (т.е. `mean()` или `sum()`)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment