При обработке данных вы должны:
Пакет dplyr
делает каждый из этих шагов на столько простым и быстрым на сколько это возможно следующим образом:
Цель этого документа – дать представление о базовых инструментах предоставляемых dplyr
, и продемонстрировать их применение к структурам данных типа data.frame
. Также существуют отдельные руководства по следующим темам:
databases
*: наравне с объектами в памяти dplyr
также может обращаться к данным баз данных. Это позволяет вам работать с удалёнными (в смысле нахождения на расстоянии) данными, используя в точности те же инструменты, потому что dplyr
будет транслировать ваш код на R в соответствующие SQL-запросы. benchmark-baseball
: рассматривает как dplyr
выглядит в сравнении с другими инструментами для манипулирования данными на реалистичной задаче. window-functions
*: функции агрегирования по окну – это разновидность функций агрегирования, где функция агрегирования вычисляет по n аргументам 1 значение, а функции агрегирования по окну получает n аргументов и возвращает n значений.Для исследования основных операций манипулирования данными в dplyr
мы будем использовать встроенный набор данных nycflights13
. Он содержит данные всех 336776 рейсов из Нью Йорка за 2013 год, и предоставлен, как написано в ?nycflights13
, бюро по перемещениям США.
library(nycflights13)
dim(flights)
#> [1] 336776 16
head(flights)
#> Source: local data frame [6 x 16]
#>
#> year month day dep_time dep_delay arr_time arr_delay carrier tailnum
#> 1 2013 1 1 517 2 830 11 UA N14228
#> 2 2013 1 1 533 4 850 20 UA N24211
#> 3 2013 1 1 542 2 923 33 AA N619AA
#> 4 2013 1 1 544 -1 1004 -18 B6 N804JB
#> .. ... ... ... ... ... ... ... ... ...
#> Variables not shown: flight (int), origin (chr), dest (chr), air_time
#> (dbl), distance (dbl), hour (dbl), minute (dbl)
dplyr
может работать с data.frame
непосредственно, но при работе с большими объёмами данных стоит конвертировать их в tbl_df
, обёртку вокруг data.frame
которая не станет по случайности выводить кучу данных на экран (это может очень медленным процессом).
Dplyr ставит своей целью дать функции для всех основных пераций манипулирования данным:
filter()
(и slice()
)arrange()
select()
(и rename()
)distinct()
mutate()
(и transmute()
)summarise()
sample_n()
и sample_frac()
Для тех кто пользовался до этого plyr
многие из них будут знакомы.
filter()
позволяет вам выбрать подмножество строк из data.frame
. Первый аргумент – имя набора данных, второй и последующие – условия фильтра в контексте этого набора данных.
Например, мы можем выбрать все вылет 1-го января следующим образом:
filter(flights, month == 1, day == 1)
#> Source: local data frame [842 x 16]
#>
#> year month day dep_time dep_delay arr_time arr_delay carrier tailnum
#> 1 2013 1 1 517 2 830 11 UA N14228
#> 2 2013 1 1 533 4 850 20 UA N24211
#> 3 2013 1 1 542 2 923 33 AA N619AA
#> 4 2013 1 1 544 -1 1004 -18 B6 N804JB
#> .. ... ... ... ... ... ... ... ... ...
#> Variables not shown: flight (int), origin (chr), dest (chr), air_time
#> (dbl), distance (dbl), hour (dbl), minute (dbl)
Что будет эквивалентно более многословному (длиннее на целых 10 символов!) варианту:
flights[flights$month == 1 & flights$day == 1, ]
filter()
работает аналогично subset()
за тем исключением, что вы можете ему передать любое количество условий для фильтра, которые будут объединены вместе через &
(логическое И, но не &&
с которым можно случайно перепутать). Вы можете также использовать любые другие логически связки:
filter(flights, month == 1 | month == 2)
Для выборки строк по позициям используется slice()
:
slice(flights, 1:10)
#> Source: local data frame [10 x 16]
#>
#> year month day dep_time dep_delay arr_time arr_delay carrier tailnum
#> 1 2013 1 1 517 2 830 11 UA N14228
#> 2 2013 1 1 533 4 850 20 UA N24211
#> 3 2013 1 1 542 2 923 33 AA N619AA
#> 4 2013 1 1 544 -1 1004 -18 B6 N804JB
#> .. ... ... ... ... ... ... ... ... ...
#> Variables not shown: flight (int), origin (chr), dest (chr), air_time
#> (dbl), distance (dbl), hour (dbl), minute (dbl)
arrange()
, в свою очередь, работает аналогично filter()
за исключением того, что вместо выбора строк она переупорядочивает их. Функция получает на вход имя таблицы данных и список имён колонок (или более сложное выражение) для упорядочения по ним. Если будет указано больше одной колонки, то каждая следующая колонка будет упорядочиваться в пределах каждого отдельного набора значений предыдущих:
arrange(flights, year, month, day)
#> Source: local data frame [336,776 x 16]
#>
#> year month day dep_time dep_delay arr_time arr_delay carrier tailnum
#> 1 2013 1 1 517 2 830 11 UA N14228
#> 2 2013 1 1 533 4 850 20 UA N24211
#> 3 2013 1 1 542 2 923 33 AA N619AA
#> 4 2013 1 1 544 -1 1004 -18 B6 N804JB
#> .. ... ... ... ... ... ... ... ... ...
#> Variables not shown: flight (int), origin (chr), dest (chr), air_time
#> (dbl), distance (dbl), hour (dbl), minute (dbl)
Необходимо использовать desc()
чтобы задать порядок по убыванию:
arrange(flights, desc(arr_delay))
#> Source: local data frame [336,776 x 16]
#>
#> year month day dep_time dep_delay arr_time arr_delay carrier tailnum
#> 1 2013 1 9 641 1301 1242 1272 HA N384HA
#> 2 2013 6 15 1432 1137 1607 1127 MQ N504MQ
#> 3 2013 1 10 1121 1126 1239 1109 MQ N517MQ
#> 4 2013 9 20 1139 1014 1457 1007 AA N338AA
#> .. ... ... ... ... ... ... ... ... ...
#> Variables not shown: flight (int), origin (chr), dest (chr), air_time
#> (dbl), distance (dbl), hour (dbl), minute (dbl)
dplyr::arrange()
работает так же как и plyr::arrange()
. Это простая обёртка над order()
, только позволяющая меньше набирать на клавиатуре. Предыдущий пример эквивалентен такому выражению:
flights[order(flights$year, flights$month, flights$day), ]
flights[order(desc(flights$arr_delay)), ]
Часто случается что при работе с большим набором данных вам на самом деле интересны только некоторые колонки. select()
позволяет вам быстро сфокусироваться на интересующей части колонок, используя при этом символические имена на манер номеров колонок:
# Select columns by name
select(flights, year, month, day)
#> Source: local data frame [336,776 x 3]
#>
#> year month day
#> 1 2013 1 1
#> 2 2013 1 1
#> 3 2013 1 1
#> 4 2013 1 1
#> .. ... ... ...
# Select all columns between year and day (inclusive)
select(flights, year:day)
#> Source: local data frame [336,776 x 3]
#>
#> year month day
#> 1 2013 1 1
#> 2 2013 1 1
#> 3 2013 1 1
#> 4 2013 1 1
#> .. ... ... ...
# Select all columns except those from year to day (inclusive)
select(flights, -(year:day))
#> Source: local data frame [336,776 x 13]
#>
#> dep_time dep_delay arr_time arr_delay carrier tailnum flight origin
#> 1 517 2 830 11 UA N14228 1545 EWR
#> 2 533 4 850 20 UA N24211 1714 LGA
#> 3 542 2 923 33 AA N619AA 1141 JFK
#> 4 544 -1 1004 -18 B6 N804JB 725 JFK
#> .. ... ... ... ... ... ... ... ...
#> dest
#> 1 IAH
#> 2 IAH
#> 3 MIA
#> 4 BQN
#> .. ...
#> Variables not shown: air_time (dbl), distance (dbl), hour (dbl), minute
#> (dbl)
Эта функция работает подобно аргументу select
в base::subset()
. Отдельная функция добавлена для поддержания стройности идеологии dplyr
, заключающейся в наличии функций каждая из которых делает только одну конкретную операцию и наиболее подходящим образом.
С select()
используется ряд вспомогательных, такие как starts_with()
, ends_with()
, matches()
и contains()
. Они упрощают получение больших блоков колонок которые удовлетворяют некоторому критерию. Для получения подробностей смотрите ?select
.
При помощи select()
вы можете переименовывать колонки используя именованные аргументы:
select(flights, tail_num = tailnum)
#> Source: local data frame [336,776 x 1]
#>
#> tail_num
#> 1 N14228
#> 2 N24211
#> 3 N619AA
#> 4 N804JB
#> .. ...
Но поскольку она отбрасывает все явно не указанные аргументы, используйте вместо неё для этого rename()
:
rename(flights, tail_num = tailnum)
#> Source: local data frame [336,776 x 16]
#>
#> year month day dep_time dep_delay arr_time arr_delay carrier tail_num
#> 1 2013 1 1 517 2 830 11 UA N14228
#> 2 2013 1 1 533 4 850 20 UA N24211
#> 3 2013 1 1 542 2 923 33 AA N619AA
#> 4 2013 1 1 544 -1 1004 -18 B6 N804JB
#> .. ... ... ... ... ... ... ... ... ...
#> Variables not shown: flight (int), origin (chr), dest (chr), air_time
#> (dbl), distance (dbl), hour (dbl), minute (dbl)
Чаще всего select()
используют для выяснения какие значения принимают значения колонки. В частности это удобно использовать совместно с distinct()
, операцией, которая возвращает только строки с уникальными значениями.
distinct(select(flights, tailnum))
#> Source: local data frame [4,044 x 1]
#>
#> tailnum
#> 1 N14228
#> 2 N24211
#> 3 N619AA
#> 4 N804JB
#> .. ...
distinct(select(flights, origin, dest))
#> Source: local data frame [224 x 2]
#>
#> origin dest
#> 1 EWR IAH
#> 2 LGA IAH
#> 3 JFK MIA
#> 4 JFK BQN
#> .. ... ...
(Это очень похоже на base::unique()
, но должно работать быстрее)
Бывает полезно не только выбрать интересующие колонки, но и вычислить на их основании значения некоторой другой колонки. Для этих целей предназначена mutate()
:
mutate(flights,
gain = arr_delay - dep_delay,
speed = distance / air_time * 60)
#> Source: local data frame [336,776 x 18]
#>
#> year month day dep_time dep_delay arr_time arr_delay carrier tailnum
#> 1 2013 1 1 517 2 830 11 UA N14228
#> 2 2013 1 1 533 4 850 20 UA N24211
#> 3 2013 1 1 542 2 923 33 AA N619AA
#> 4 2013 1 1 544 -1 1004 -18 B6 N804JB
#> .. ... ... ... ... ... ... ... ... ...
#> Variables not shown: flight (int), origin (chr), dest (chr), air_time
#> (dbl), distance (dbl), hour (dbl), minute (dbl), gain (dbl), speed (dbl)
dplyr::mutate()
работает аналогично plyr::mutate()
и аналогично base::transform()
. Главное отличие между mutate()
and transform()
в том что первая может ссылаться в вычислениях на колонку которая создаётся в рамках того же вызова функции:
mutate(flights,
gain = arr_delay - dep_delay,
gain_per_hour = gain / (air_time / 60)
)
#> Source: local data frame [336,776 x 18]
#>
#> year month day dep_time dep_delay arr_time arr_delay carrier tailnum
#> 1 2013 1 1 517 2 830 11 UA N14228
#> 2 2013 1 1 533 4 850 20 UA N24211
#> 3 2013 1 1 542 2 923 33 AA N619AA
#> 4 2013 1 1 544 -1 1004 -18 B6 N804JB
#> .. ... ... ... ... ... ... ... ... ...
#> Variables not shown: flight (int), origin (chr), dest (chr), air_time
#> (dbl), distance (dbl), hour (dbl), minute (dbl), gain (dbl),
#> gain_per_hour (dbl)
transform(flights,
gain = arr_delay - delay,
gain_per_hour = gain / (air_time / 60)
)
#> Error: object 'gain' not found
Если вам нужно сохранить только новые колонки используйте transmute()
:
transmute(flights,
gain = arr_delay - dep_delay,
gain_per_hour = gain / (air_time / 60)
)
#> Source: local data frame [336,776 x 2]
#>
#> gain gain_per_hour
#> 1 9 2.378855
#> 2 16 4.229075
#> 3 31 11.625000
#> 4 -17 -5.573770
#> .. ... ...
Ещё одна операция – summarise()
, она просто сворачивает таблицу данных в одну строку. Пока это не слишком полезно:
summarise(flights,
delay = mean(dep_delay, na.rm = TRUE))
#> Source: local data frame [1 x 1]
#>
#> delay
#> 1 12.63907
Эта функция в точности соответствует plyr::summarise()
.
sample_n()
и sample_frac()
Вы можете воспользоваться sample_n()
или sample_frac()
для получения случайного набора строк, фиксированного числа с sample_n()
или в фиксированной пропорции от всего набора с sample_frac()
.
sample_n(flights, 10)
#> Source: local data frame [10 x 16]
#>
#> year month day dep_time dep_delay arr_time arr_delay carrier tailnum
#> 1 2013 1 21 652 -8 946 -11 B6 N636JB
#> 2 2013 5 17 2144 63 47 31 UA N841UA
#> 3 2013 12 6 1521 -8 1721 -19 EV N11165
#> 4 2013 2 1 703 -2 1035 14 B6 N591JB
#> .. ... ... ... ... ... ... ... ... ...
#> Variables not shown: flight (int), origin (chr), dest (chr), air_time
#> (dbl), distance (dbl), hour (dbl), minute (dbl)
sample_frac(flights, 0.01)
#> Source: local data frame [3,368 x 16]
#>
#> year month day dep_time dep_delay arr_time arr_delay carrier tailnum
#> 1 2013 4 21 715 0 903 -13 DL N309US
#> 2 2013 12 18 2041 71 2313 38 AA N482AA
#> 3 2013 3 30 1833 13 2113 -22 AS N549AS
#> 4 2013 2 17 1859 75 2051 103 UA N78509
#> .. ... ... ... ... ... ... ... ... ...
#> Variables not shown: flight (int), origin (chr), dest (chr), air_time
#> (dbl), distance (dbl), hour (dbl), minute (dbl)
Аргумент replace = TRUE
позволяет повторно выбирать одну и ту же строку (как при бутстрепинге), опционально можно указать вес в итоговой выборке каждой исходной строки при помощи аргумента weight
.
У вас может возникнуть впечатление что все перечисленные функции имеют что-то общее:
data.frame
. $
. data.frame
.Все эти свойства вместе позволяют легко комбинировать множество вызовов в одну цепочку для вычисления сложного результата.
Эти пять функций создают основу для языка манипулирования данными. На самом простом уровне вы можете только изменить что-либо в наборе данных пятью способами: изменить порядок строк (arrange()
), выбрать наблюдения или атрибуты (filter()
и select()
), добавить вычисляемые атрибуты (mutate()
) или свернуть набор в итог (summarise()
). Оставшаяся часть этого языка образуется из применения перечисленных функций к различным типам данных, например группированным данным, как описано ниже.
Вышеописанные функции полезны сами по себе, но особую мощь они приобретают в комбинации с концепцией (нет, не коллективной безопасности (: ) группировки, повторения вычислений для каждой группы наблюдений в отдельности. В dplyr
используется функция group_by()
для разбиения набора данных на части по строкам. Вы можете использовать вывод функции group_by()
непосредственно как первый аргумент перечисленных выше пяти основных функций, они сами обработают правильно набор данных разбитый на группы.
Команды реагируют на группировку следующим образом:
select()
не изменяет своего поведения за тем исключением что столбцы по которым данные сгруппированы буду выбираться всегда.arrange()
сначала выстраивает в порядке группирующих столбцов, а затем по условию из своего аргумента.mutate()
и filter()
наиболее полезны в совокупности с оконными функциями*((подобными rank()
, или min(x) == x
)), но детально это описано в отдельном руководстве vignette("window-function")
.sample_n()
и sample_frac()
выбирают из каждой группы отдельно указанное количество строк.slice()
также выбирает из каждой группы необходимое количество строк.summarise()
в данном случае весьма полезна и проста в применении как будет показано далее.В следующем примере мы разделим набор данных по самолётам и рассчитаем количество вылетов (count = n()
) и средние дальность полёта (dist = mean(Distance, na.rm = TRUE)
) и задержку вылета (delay = mean(ArrDelay, na.rm = TRUE)
). Затем построим график при помощи ggplot2
.
planes <- group_by(flights, tailnum)
delay <- summarise(planes,
count = n(),
dist = mean(distance, na.rm = TRUE),
delay = mean(arr_delay, 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]
, но дают больше контроля над результатом если значение не может быть получено.Например, мы можем использовать их чтобы найти количество самолётов и количество вылетов во все возможные пункты назначения:
destinations <- group_by(flights, dest)
summarise(destinations,
planes = n_distinct(tailnum),
flights = n()
)
#> Source: local data frame [105 x 3]
#>
#> dest planes flights
#> 1 ABQ 108 254
#> 2 ACK 58 265
#> 3 ALB 172 439
#> 4 ANC 6 8
#> .. ... ... ...
Также вы можете применить собственную функцию. Для повышения производительности многие функции dplyr
написаны на C++
. Если вы хотите использовать собственную функцию на C++
обратите внимание на соответствующее руководство* для уточнения деталей.
При группировке по нескольким переменным вы можете также получить “послойные” итоги для каждого уровня в отдельности и по очереди. Этот приём позволяет кумулятивно получать свёртку набора данных:
daily <- group_by(flights, year, month, day)
(per_day <- summarise(daily, flights = n()))
#> Source: local data frame [365 x 4]
#> Groups: year, month
#>
#> year month day flights
#> 1 2013 1 1 842
#> 2 2013 1 2 943
#> 3 2013 1 3 914
#> 4 2013 1 4 915
#> .. ... ... ... ...
(per_month <- summarise(per_day, flights = sum(flights)))
#> Source: local data frame [12 x 3]
#> Groups: year
#>
#> year month flights
#> 1 2013 1 27004
#> 2 2013 2 24951
#> 3 2013 3 28834
#> 4 2013 4 28330
#> .. ... ... ...
(per_year <- summarise(per_month, flights = sum(flights)))
#> Source: local data frame [1 x 2]
#>
#> year flights
#> 1 2013 336776
Однако необходимо проявлять осторожность при таком методе получения итогов: он подойдёт для сумм и количеств, но нужно задуматься при использовании средних и дисперсий, и уж точно не применять с медианой.
API (легендарный мощный ЭПиАй) dplyr
функционален, в том смысле, что вызовы функций не имеют побочных эффектов, и вы должны сами сохранять результаты. Это обстоятельство не способствует написанию элегантного кода если вам нужно выполнить множество операций подряд. Вы, конечно, можете выполнять их шаг за шагом:
a1 <- group_by(flights, year, month, day)
a2 <- select(a1, arr_delay, dep_delay)
a3 <- summarise(a2,
arr = mean(arr_delay, na.rm = TRUE),
dep = mean(dep_delay, na.rm = TRUE))
a4 <- filter(a3, arr > 30 | dep > 30)
Или, если вы не желаете сохранять промежуточные результаты, можно передавать вызовы функций как аргументы, вкладывая один в другой:
filter(
summarise(
select(
group_by(flights, year, month, day),
arr_delay, dep_delay
),
arr = mean(arr_delay, na.rm = TRUE),
dep = mean(dep_delay, na.rm = TRUE)
),
arr > 30 | dep > 30
)
#> Source: local data frame [49 x 5]
#> Groups: year, month
#>
#> year month day arr dep
#> 1 2013 1 16 34.24736 24.61287
#> 2 2013 1 31 32.60285 28.65836
#> 3 2013 2 11 36.29009 39.07360
#> 4 2013 2 27 31.25249 37.76327
#> .. ... ... ... ... ...
Что создаёт сложности с чтением, так как порядок выполнения начинается из середины выражения и аргументы отдаляются от текста вызова функции. Чтобы обойти это проблему dplyr
использует оператор %>%
. x %>% f(y)
преобразуется в f(x, y)
, что можно использовать для преобразования кода вызовов в удобный для чтения слева направо, сверху вниз:
flights %>%
group_by(year, month, day) %>%
select(arr_delay, dep_delay) %>%
summarise(
arr = mean(arr_delay, na.rm = TRUE),
dep = mean(dep_delay, na.rm = TRUE)
) %>%
filter(arr > 30 | dep > 30)
Также как с data.frame
, dplyr
может работать с данными представленными другими способами, такими как data tables
, базы данных и многомерные массивы.
dplyr
также предлагает все перечисленные методы манипулирования данным и для объектов типа data.tables
*, вы просто должны заменить набор данных на data.table
.
data.table
может оказаться быстрее во многих случаях, потому что возможно выполнение нескольких операций одновременно. Например, вы можете выполнить mutate
и select
за один вызов, и data.table
сообразит что нет нужды рассчитывать новую переменную в строках которые должны быть отфильтрованы.
Преимущества при использовании data.tables
следующие:
data.tables
изолирует вас от данных непосредственно, и защищает их таким образом от непреднамеренного изменения. [
, предлагается множество относительно простых методов.dplyr
позволяет использовать удалённые базы данных так же как data.frame
. Избавляя таким образом от необходимости постоянно переключать мышление между языками. Для уточнения деталей необходимо обратиться к соответствующему руководству*.
По сравнению с другим вариантами использования баз данных: * скрывается, на сколько это возможно, факт использования удалённой базы данных * исчезает необходимость изучать какой-либо диалект sql (хотя это может быть полезно!) * предоставляется прослойка между множеством различных реализаций интерфейсов баз данных.
tbl_cube()
предлагает экспериментальный интерфейс к многомерным массивам или кубам (как OLAP-кубы) . Если вы используете подобное представление в R, пожалуйста, свяжитесь с автором чтобы он лучше понял ваши потребности.
В сравнение со всеми существующими альтернативами, dplyr
:
data.frame
, data.tables
и удалёнными базами данных. Это позволяет думать только о том что вы хотите сделать с данными, а не об устройстве хранилища данных print()
, которые не распечатает случайно несколько страниц данных на экран (вдохновение было почерпнуто в data.tables
).В сравнении с функциями из основной поставки:
dplyr
намного более строен; функции имеют идентичный интерфейс, так что освоив одну вы разбираетесь и в других dplyr
концентрируется на наборах данныхВ сравнении с plyr
:
dplyr
намного намного быстрее data.frame
(т.о. большая часть dplyr
эквивалентна ddply()
с добавлением некоторых функций, do()
эквивалентна dlply()
)В сравнении с использованием виртуальных data.frame
:
data.frame
не предполагает что у вас есть данные, так что если необходимо выполнить lm()
понадобится получить данные вручную data.frame
не предоставляет методов для агрегатных функций (т.е. mean()
или sum()
)