Довольно редко при анализе данных приходится иметь дело только с одно таблицей данных, на практике, обычно, у вас есть несколько таблиц, каждая из которых влияет на анализ, и вам необходимы инструменты, чтобы гибко комбинировать их:

(Дальнейшее обсуждение предполагает что вы располагаете аккуратными данными, то есть в строках располагаются наблюдения, а в колонках – переменные. Если вы не слишком знакомы с этим подходом, я рекомендую вам сперва почитать про него.)

Все действия над парами таблиц работают похожим образом. Они принимают аргументы x и y, представляющие комбинируемые таблицы. Возвращается всегда новая таблица того же типа что и x.

Мутирующие соединения

Модифицирующие соединения позволяют комбинировать переменные из различных таблиц. Например, возьмём данные из nycflights13. В одной таблице содержатся данные о рейсе и аббревиатура номера борта, а в другой – соответствие аббревиатур полным наименованиям. Вы можете использовать соединение чтобы добавить наименования бортов к данным рейса:

library("nycflights13")
# Удаляем незначащие переменные, 
# так будет легче разобраться в результате соединения.
flights2 <- flights %>% select(year:day, hour, origin, dest, tailnum, carrier)

flights2 %>% 
  left_join(airlines)
#> Joining by: "carrier"
year month day hour origin dest tailnum carrier name
2013 1 1 5 EWR IAH N14228 UA United Air Lines Inc.
2013 1 1 5 LGA IAH N24211 UA United Air Lines Inc.
2013 1 1 5 JFK MIA N619AA AA American Airlines Inc.
2013 1 1 5 JFK BQN N804JB B6 JetBlue Airways
2013 1 1 5 LGA ATL N668DN DL Delta Air Lines Inc.

Контроль над процессом объединения

Наравне с x и y любое объединение принимает аргумент by, который управляет тем, какие именно переменные будут использованы для сопоставления наблюдений между двумя таблицами. Существует несколько способов для их указания, как будет проиллюстрировано далее на примере различных таблиц из nycflights13:

  • NULL, используемый по умолчанию. dplyr будет пытаться использовать все совпадающие (по именам) переменные из обоих таблиц, т. н. естественное соединение. Например, таблицы рейсов и сводок погоды совпадают по их общим переменным: year (год), month (месяц), day (день), hour (час) и origin (место отправления).

    flights2 %>% left_join(weather)
    #> Joining by: c("year", "month", "day", "hour", "origin")
    year month day hour origin dest tailnum carrier temp dewp humid wind_dir
    2013 1 1 5 EWR IAH N14228 UA NA NA NA NA
    2013 1 1 5 LGA IAH N24211 UA NA NA NA NA
    2013 1 1 5 JFK MIA N619AA AA NA NA NA NA
    2013 1 1 5 JFK BQN N804JB B6 NA NA NA NA
    2013 1 1 5 LGA ATL N668DN DL NA NA NA NA

    (Variables not shown: wind_speed (dbl), wind_gust (dbl), precip (dbl), pressure (dbl), visib (dbl))

  • Вектор строк, by = "x". Работает подобно естественному соединению, за тем исключением, что использует только заранее перечисленные имена переменных. Например, flights и planes содержат переменную с именем year, но она имеет различный смысл, поэтому мы будем выполнять соединение только по tailnum.

    flights2 %>% left_join(planes, by = "tailnum")
    year.x month day hour origin dest tailnum carrier year.y
    2013 1 1 5 EWR IAH N14228 UA 1999
    2013 1 1 5 LGA IAH N24211 UA 1998
    2013 1 1 5 JFK MIA N619AA AA 1990
    2013 1 1 5 JFK BQN N804JB B6 2012
    2013 1 1 5 LGA ATL N668DN DL 1991

    (Variables not shown: type (chr), manufacturer (chr), model (chr), engines (int), seats (int), speed (int), engine (chr))

    Note that the year columns in the output are disambiguated with a suffix.

  • Именованные вектор строк: by = c("x" = "a"), позволяет искать совпадение по переменной x в таблице x и переменной a таблице y. Обе переменные будут представлены в результирующей таблице.

    У каждого рейса есть аэропорты вылета и прилёта (airport), так что нам понадобится определить по которому из них делать объединение:

    flights2 %>% left_join(airports, c("dest" = "faa"))
    year month day hour origin dest tailnum carrier
    2013 1 1 5 EWR IAH N14228 UA
    2013 1 1 5 LGA IAH N24211 UA
    2013 1 1 5 JFK MIA N619AA AA
    2013 1 1 5 JFK BQN N804JB B6
    2013 1 1 5 LGA ATL N668DN DL

    (Variables not shown: name (chr), lat (dbl), lon (dbl), alt (int), tz (dbl), dst (chr))

    flights2 %>% left_join(airports, c("origin" = "faa"))
    year month day hour origin dest tailnum carrier name
    2013 1 1 5 EWR IAH N14228 UA Newark Liberty Intl
    2013 1 1 5 LGA IAH N24211 UA La Guardia
    2013 1 1 5 JFK MIA N619AA AA John F Kennedy Intl
    2013 1 1 5 JFK BQN N804JB B6 John F Kennedy Intl
    2013 1 1 5 LGA ATL N668DN DL La Guardia

    (Variables not shown: lat (dbl), lon (dbl), alt (int), tz (dbl), dst (chr))

Типы объединений

Существует четыре вида модифицирующих объединений, отличающихся поведением в случае отсутствия совпадающих строк. Мы рассмотрим каждое на нескольких простых примерах:

(df1 <- data_frame(x = c(1, 2), y = 2:1))
x y
1 2
2 1
(df2 <- data_frame(x = c(1, 3), a = 10, b = "a"))
x a b
1 10 a
3 10 a
  • inner_join(x, y) включает только наблюдения имеющие совпадения в обеих таблицах x и y.

    df1 %>% inner_join(df2) %>% knitr::kable()
    #> Joining by: "x"
    x y a b
    1 2 10 a
  • left_join(x, y) (левое соединение) включает все наблюдения из x, вне зависимости от того, есть ли для них совпадения. Это наиболее востребованный вид объединения, потому что он гарантирует, что вы не потеряете наблюдения из первой таблицы.

    df1 %>% left_join(df2)
    #> Joining by: "x"
    x y a b
    1 2 10 a
    2 1 NA NA
  • right_join(x, y) (правое соединение) включает все наблюдения из y. Это как
    left_join(y, x), только наоборот.

    df1 %>% right_join(df2)
    #> Joining by: "x"
    x y a b
    1 2 10 a
    3 NA 10 a
    df2 %>% left_join(df1)
    #> Joining by: "x"
    x a b y
    1 10 a 2
    3 10 a NA
  • full_join() (полное) включает все наблюдения из x и y.

    df1 %>% full_join(df2)
    #> Joining by: "x"
    x y a b
    1 2 10 a
    2 1 NA NA
    3 NA 10 a

левое, правое и полное соединения также известны под общим именем внешние соединения. Если при вычислении внешнего соединения для строки не находится совпадений – новые переменные заполняются пустыми значениями.

Наблюдения

Хотя, в основном, объединения используются для добавления новых переменных, они также могут породить и новые наблюдения. В тех случаях, когда находится более одного совпадающего наблюдения, происходит добавление всех возможных комбинаций (Декартово произведение) совпадающих наблюдений:

df1 <- data_frame(x = c(1, 1, 2), y = 1:3)
df2 <- data_frame(x = c(1, 1, 2), z = c("a", "b", "a"))

df1 %>% left_join(df2)
#> Joining by: "x"
x y z
1 1 a
1 1 b
1 2 a
1 2 b
2 3 a

Фильтрующие объединения

Фильтрующие объединения ищут совпадающие наблюдения также, как и модифицирующие, но воздействуют на наблюдения, а не на переменные:

Они особенно полезны для поиска несовпадений при объединениях. Например, множество рейсов в наборе данных nycflights13 не имеют совпадений по номеру борта в таблице самолётов:

library("nycflights13")
flights %>% 
  anti_join(planes, by = "tailnum") %>% 
  count(tailnum, sort = TRUE)
tailnum n
2512
N725MQ 575
N722MQ 513
N723MQ 507
N713MQ 483

Если вы сомневаетесь по поводу того какие наблюдения войдут в соединение, начните с semi_join() или anti_join(). semi_join() или anti_join() никогда не размножают, а только удаляют записи.

df1 <- data_frame(x = c(1, 1, 3, 4), y = 1:4)
df2 <- data_frame(x = c(1, 1, 2), z = c("a", "b", "a"))

# Вначале четыре строки:
df1 %>% nrow()
#> [1] 4
# И четыре строки после соединения
df1 %>% inner_join(df2, by = "x") %>% nrow()
#> [1] 4
# Но только два совпадения
df1 %>% semi_join(df2, by = "x") %>% nrow()
#> [1] 2

Операции со множествами

Последний тип действий с двумя таблицами – операции со множествами. Они предполагают, что аргументы x и y имеют одинаковый набор переменных, и обращаются с наблюдениями как с элементами множеств:

Для данных:

(df1 <- data_frame(x = 1:2, y = c(1L, 1L)))
x y
1 1
2 1
(df2 <- data_frame(x = 1:2, y = 1:2))
x y
1 1
2 2

Существуют следующие варианты:

intersect(df1, df2)
x y
1 1
# Обратите внимание, что возвращается 3 строки, а не 4
union(df1, df2)
x y
2 2
2 1
1 1
setdiff(df1, df2)
x y
2 1
setdiff(df2, df1)
x y
2 2

Базы данных

Каждое действие с двумя таблицами имеет свой SQL-эквивалент:

R SQL
inner_join() SELECT * FROM x JOIN y ON x.a = y.a
left_join() SELECT * FROM x LEFT JOIN y ON x.a = y.a
right_join() SELECT * FROM x RIGHT JOIN y ON x.a = y.a
full_join() SELECT * FROM x FULL JOIN y ON x.a = y.a
semi_join() SELECT * FROM x WHERE EXISTS (SELECT 1 FROM y WHERE x.a = y.a)
anti_join() SELECT * FROM x WHERE NOT EXISTS (SELECT 1 FROM y WHERE x.a = y.a)
intersect(x, y) SELECT * FROM x INTERSECT SELECT * FROM y
union(x, y) SELECT * FROM x UNION SELECT * FROM y
setdiff(x, y) SELECT * FROM x EXCEPT SELECT * FROM y

x и y не обязательно должны быть таблицами из одной базы данных. Если вы укажете copy = TRUE, dplyr скопирует данные из y в то же место, что и x. Это удобно, если вы загружаете итоговый набор данных и определяете область, для которой вы хотите сохранить данные в полном объёме. Вы можете вызвать semi_join(x, y, copy = TRUE) для загрузки индексов интересующих наблюдений в ту же базу данных, где находится x, и в дальнейшем выполнять эту операцию более эффективно.

Если вы работаете с большим объёмом данных, возможно, полезно будет установить auto_index = TRUE. Это автоматически приведёт к созданию индекса по полям, использующимся для соединения во временной таблице.

Правила приведения типов

При объединении таблиц dplyr ведёт себя более консервативно, чем R в отношении типов сравниваемых переменных. Это может удивить при работе с факторами:

В остальных случаях, логические типы будут преобразованы в целочисленные без выдачи предупреждения, целочисленные – в числа с плавающей точкой, а попытка приведения к строке будет вызывать ошибку:

df1 <- data_frame(x = 1, y = 1L)
df2 <- data_frame(x = 2, y = 1.5)
full_join(df1, df2) %>% str()
#> Joining by: c("x", "y")
#> Classes 'tbl_df', 'tbl' and 'data.frame':    2 obs. of  2 variables:
#>  $ x: num  1 2
#>  $ y: num  1 1.5

df1 <- data_frame(x = 1, y = 1L)
df2 <- data_frame(x = 2, y = "a")
full_join(df1, df2) %>% str()
#> Joining by: c("x", "y")
#> Error in eval(expr, envir, enclos): cannot join on columns 'y' x 'y': Can't join on 'y' x 'y' because of incompatible types (character / integer)

Многотабличные действия

dplyr не предлагает функций для работы с тремя и более таблицами. Вместо этого предполагается использовать Reduce(), как описано в Advanced R, для интерактивного расширения двухтабличных действий на любое количество таблиц.