Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
---
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
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.