При обработке данных вы должны:
Пакет 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())