데이터 프레임 결합 (병합) 방법 (내부, 외부, 왼쪽, 오른쪽)


1232

두 개의 데이터 프레임이 주어지면 :

df1 = data.frame(CustomerId = c(1:6), Product = c(rep("Toaster", 3), rep("Radio", 3)))
df2 = data.frame(CustomerId = c(2, 4, 6), State = c(rep("Alabama", 2), rep("Ohio", 1)))

df1
#  CustomerId Product
#           1 Toaster
#           2 Toaster
#           3 Toaster
#           4   Radio
#           5   Radio
#           6   Radio

df2
#  CustomerId   State
#           2 Alabama
#           4 Alabama
#           6    Ohio

데이터베이스 스타일, 즉 SQL 스타일을 조인하려면 어떻게해야합니까? 즉, 어떻게 얻을 수 있습니까?

  • 내부 조인df1df2:
    왼쪽 테이블에 오른쪽 테이블에 키를 일치 한있는 돌아 행만.
  • 외부 조인df1df2:
    두 테이블에서 반환 모든 행은 오른쪽 테이블에 일치하는 키가 왼쪽에서 레코드를 가입 할 수 있습니다.
  • A는 외부 조인 (또는 간단히 왼쪽에 가입) 왼쪽df1df2
    우측 테이블에서 키를 일치하는 반환 왼쪽 테이블의 모든 행과 행을.
  • 오른쪽 외부 조인df1df2
    왼쪽 테이블에서 키를 일치하는 오른쪽 테이블의 모든 행과 행을 반환.

추가 크레딧 :

SQL 스타일 select 문을 어떻게 수행 할 수 있습니까?


4
stat545-ubc.github.io/bit001_dplyr-cheatsheet.html ←이 질문에 가장 좋아하는 답변
isomorphismes

RStudio에 의해 만들어지고 유지 dplyr 치트 시트와 함께 데이터 변환은 dplyr의에서 작업을 조인 방법에 대한 좋은 인포 그래픽이 rstudio.com/resources/cheatsheets을
아서 깨갱에게

2
팬더 데이터 프레임 병합에 대해 알고 싶다면 여기 에서 해당 리소스를 찾을 수 있습니다 .
cs95

답변:


1348

merge기능 및 선택적 매개 변수 를 사용하여 다음을 수행하십시오.

내부 조인 : merge(df1, df2) R은 공통 변수 이름으로 프레임을 자동으로 조인하기 때문에 이러한 예제에서 작동하지만merge(df1, df2, by = "CustomerId")원하는 필드에서만 일치하도록 지정하려고합니다. 일치하는 변수의 이름이 다른 데이터 프레임에서 다른 경우by.xby.y매개 변수를사용할 수도 있습니다.

외부 조인 : merge(x = df1, y = df2, by = "CustomerId", all = TRUE)

왼쪽 바깥 쪽 : merge(x = df1, y = df2, by = "CustomerId", all.x = TRUE)

오른쪽 바깥 쪽 : merge(x = df1, y = df2, by = "CustomerId", all.y = TRUE)

크로스 조인 : merge(x = df1, y = df2, by = NULL)

내부 조인과 마찬가지로 일치 변수로 "CustomerId"를 R에 명시 적으로 전달할 수 있습니다. 거의 항상 병합하려는 식별자를 명시 적으로 언급하는 것이 가장 좋습니다. 입력 데이터 프레임이 예기치 않게 변경되고 나중에 읽기가 더 안전합니다.

by벡터 를 제공 하여 여러 열을 병합 할 수 있습니다 ( 예 :) by = c("CustomerId", "OrderId").

병합에 열 이름에 동일하지 않으면, 당신은, 예를 들어, 지정할 수 있습니다 by.x = "CustomerId_in_df1", by.y = "CustomerId_in_df2"경우 CustomerId_in_df1첫 번째 데이터 프레임의 열 이름이고, CustomerId_in_df2두 번째 데이터 프레임의 열 이름입니다. 여러 열에서 병합해야하는 경우 벡터가 될 수도 있습니다.


2
@MattParker 데이터 프레임에 대한 전체 복잡한 쿼리에 대해 sqldf 패키지를 사용하고 있으며 자체 교차 조인 (즉, data.frame 교차 결합 자체)을 수행하는 데 실제로 필요했습니다. 성능 관점에서 비교하는 방법이 궁금합니다 ... . ???
니콜라스 해밀턴

9
@ ADP 나는 sqldf를 실제로 사용한 적이 없으므로 속도에 대해 확신하지 못한다. 성능이 중요한 문제라면 data.table패키지를 살펴 봐야 합니다. 완전히 새로운 조인 구문 세트이지만 여기서 말하는 것보다 훨씬 빠릅니다.
매트 파커

5
더 명확하고 설명과 함께 ..... mkmanu.wordpress.com/2016/04/08/…
Manoj Kumar

42
나에게 도움이되는 약간의 추가 사항merge(x=df1,y=df2, by.x=c("x_col1","x_col2"), by.y=c("y_col1","y_col2"))
Dileep Kumar Patchigolla

8
이것은 data.table지금 동일한 기능에서 더 빨리 작동합니다 .
marbel

222

Gabor Grothendieck의 sqldf package를 확인하여 이러한 작업을 SQL로 표현할 수 있습니다.

library(sqldf)

## inner join
df3 <- sqldf("SELECT CustomerId, Product, State 
              FROM df1
              JOIN df2 USING(CustomerID)")

## left join (substitute 'right' for right join)
df4 <- sqldf("SELECT CustomerId, Product, State 
              FROM df1
              LEFT JOIN df2 USING(CustomerID)")

SQL 구문이 R과 동등한 것보다 간단하고 자연 스럽다는 것을 알았습니다 (그러나 이것은 내 RDBMS 편향을 반영 할 수 있습니다).

조인에 대한 자세한 내용 은 Gabor의 sqldf GitHub 를 참조하십시오.


198

내부 조인 에는 data.table 접근 방식이 있으며 시간과 메모리 효율성이 뛰어나며 더 큰 데이터 프레임에 필요합니다.

library(data.table)

dt1 <- data.table(df1, key = "CustomerId") 
dt2 <- data.table(df2, key = "CustomerId")

joined.dt1.dt.2 <- dt1[dt2]

mergedata.tables에서도 작동합니다 (일반적이고 호출하기 때문에 merge.data.table)

merge(dt1, dt2)

stackoverflow에 문서화 된 data.table :
data.table 병합 작업을 수행하는 방법
외래 키의 SQL 조인을 R data.table 구문으로 변환 중대형 데이터
를 병합하는 효율적인 대안 프레임 R 데이터
와 기본 왼쪽 외부 조인을 수행하는 방법 R에?

또 다른 옵션은 plyr 패키지 join에있는 기능입니다

library(plyr)

join(df1, df2,
     type = "inner")

#   CustomerId Product   State
# 1          2 Toaster Alabama
# 2          4   Radio Alabama
# 3          6   Radio    Ohio

옵션 type: inner, left, right, full.

From ?join:와 달리 merge[ join]는 어떤 조인 유형이 사용 되더라도 x 순서를 유지합니다.


8
언급 +1 plyr::join. 마이크로 벤치마킹은보다 약 3 배 빠른 성능을 나타냅니다 merge.
Beasterfield

20
그러나 data.table둘 다보다 훨씬 빠릅니다. SO에도 많은 지원이 있습니다. 저는 많은 패키지 작성자가 data.table작가 또는 기고자 만큼 자주 질문에 대답하는 것을 보지 못합니다 .
marbel

1
데이터 프레임 목록data.table 을 병합하는 구문 은 무엇입니까 ?
Aleksandr Blekh

5
참고 : dt1 [dt2]는 오른쪽 외부 조인 ( "순수한"내부 조인이 아님) 이므로 dt1에 일치하는 행이없는 경우에도 dt2의 모든 행이 결과의 일부가됩니다. 영향 : dt2의 키 값이 dt1의 키 값과 일치하지 않으면 원하지 않는 행이 발생할 수 있습니다.
R Yoda

8
이 경우 @RYoda를 지정할 수 있습니다 nomatch = 0L.
David Arenburg

181

Hadley Wickham의 멋진 dplyr 패키지를 사용하여 조인을 수행 할 수도 있습니다 .

library(dplyr)

#make sure that CustomerId cols are both type numeric
#they ARE not using the provided code in question and dplyr will complain
df1$CustomerId <- as.numeric(df1$CustomerId)
df2$CustomerId <- as.numeric(df2$CustomerId)

조인 변경 : df2의 일치 항목을 사용하여 df1에 열 추가

#inner
inner_join(df1, df2)

#left outer
left_join(df1, df2)

#right outer
right_join(df1, df2)

#alternate right outer
left_join(df2, df1)

#full join
full_join(df1, df2)

필터링 조인 : df1에서 행을 필터링하고 열을 수정하지 마십시오.

semi_join(df1, df2) #keep only observations in df1 that match in df2.
anti_join(df1, df2) #drops all observations in df1 that match in df2.

16
CustomerId숫자 로 변환해야 합니까? 나는 (모두 문서에있는 대한 언급이 표시되지 않습니다 plyrdplyr제한의 유형에 대한 참조). 병합 열이 character(특히 관심있는 plyr) 유형 인 경우 코드가 제대로 작동하지 않습니까? 뭔가 빠졌습니까?
Aleksandr Blekh

semi_join (df1, df2, df3, df4)을 사용하여 나머지 열과 일치하는 df1의 관측치 만 유지할 수 있습니까?
Ghose Bishwajit

@GhoseBishwajit 열 대신 나머지 데이터 프레임을 의미한다고 가정하면 df2, df3 및 df4에서 동일한 구조를 가진 경우 df2, df3 및 df4에서 rbind를 사용할 수 있습니다. semi_join (df1, rbind (df2, df3, df4))
abhy3

예, 데이터 프레임을 의미했습니다. 그러나 특정 행에서 일부가 누락 된 것과 동일한 구조는 아닙니다. 4 개의 데이터 프레임의 경우 여러 국가에 대해 4 개의 다른 지표 (GDP, GNP GINI, MMR)에 대한 데이터가 있습니다. 4 개 지표 모두에 해당 국가 만 표시되도록 데이터 프레임에 참여하고 싶습니다.
Ghose Bishwajit

86

R Wiki 에서이 작업을 수행하는 좋은 예가 있습니다. 나는 여기서 몇 가지를 훔칠 것이다 :

병합 방법

키 이름이 동일하기 때문에 내부 조인을 수행하는 짧은 방법은 merge ()입니다.

merge(df1,df2)

"all"키워드를 사용하여 전체 내부 조인 (두 테이블의 모든 레코드)을 작성할 수 있습니다.

merge(df1,df2, all=TRUE)

df1 및 df2의 왼쪽 외부 조인 :

merge(df1,df2, all.x=TRUE)

df1과 df2의 오른쪽 외부 조인 :

merge(df1,df2, all.y=TRUE)

당신은 그것을 뒤집고, 때리고 그들을 문지르면 다른 두 개의 외부 조인을 얻을 수 있습니다 :)

첨자 방법

아래 첨자 방법을 사용하여 왼쪽에서 df1을 사용하는 왼쪽 외부 조인은 다음과 같습니다.

df1[,"State"]<-df2[df1[ ,"Product"], "State"]

외부 외부 조인의 다른 조합은 왼쪽 외부 조인 첨자 예제를 병합하여 만들 수 있습니다. (그렇습니다. "독자 운동으로 남겨 두겠습니다 ..."라고 말하는 것과 같습니다.


4
"R Wiki"링크가 끊어졌습니다.
zx8754

79

2014 년 새로운 기능 :

특히 정렬, 필터링, 하위 설정, 요약 등의 일반적인 데이터 조작에 관심 dplyr이있는 경우 데이터 프레임을 사용하여 작업을 용이하게하도록 설계된 다양한 기능이 포함 된을 살펴보십시오. 그리고 다른 특정 데이터베이스 유형. 심지어 매우 정교한 SQL 인터페이스와 (대부분의) SQL 코드를 R로 직접 변환하는 기능도 제공합니다.

dplyr 패키지의 4 가지 조인 관련 함수는 다음과 같습니다.

  • inner_join(x, y, by = NULL, copy = FALSE, ...): y에 일치하는 값이있는 x의 모든 행과 x와 y의 모든 열을 반환합니다.
  • left_join(x, y, by = NULL, copy = FALSE, ...): x의 모든 행과 x와 y의 모든 열을 반환합니다.
  • semi_join(x, y, by = NULL, copy = FALSE, ...): x의 열만 유지하면서 y에 일치하는 값이있는 x의 모든 행을 반환합니다.
  • anti_join(x, y, by = NULL, copy = FALSE, ...): x의 열만 유지하면서 y에 일치하는 값이없는 x의 모든 행을 반환합니다.

여기 에 모두 자세히 설명되어 있습니다.

로 열을 선택할 수 있습니다 select(df,"column"). 그것이 SQL-ish가 충분하지 않으면 SQL sql()코드를 그대로 입력 할 수 있는 기능이 있으며 R을 모두 쓰는 것처럼 지정한 작업을 수행합니다 (자세한 내용은 참조하십시오. 받는 사람 dplyr / 데이터베이스 비네팅 ). 예를 들어, 올바르게 적용되면 sql("SELECT * FROM hflights")"hflights"dplyr 테이블 ( "tbl")에서 모든 열을 선택합니다.


dplyr 패키지가 지난 2 년 동안 획득 한 중요성을 감안할 때 확실히 최상의 솔루션입니다.
Marco Fumagalli

72

데이터 세트 가입을위한 data.table 메서드 업데이트 각 조인 유형에 대한 아래 예를 참조하십시오. 두 [.data.table번째 data.table을 하위 집합에 대한 첫 번째 인수로 전달할 때 부터 두 가지 방법이 있으며 , 다른 방법은 merge빠른 data.table 메서드에 디스패치 하는 함수 를 사용 하는 것입니다.

df1 = data.frame(CustomerId = c(1:6), Product = c(rep("Toaster", 3), rep("Radio", 3)))
df2 = data.frame(CustomerId = c(2L, 4L, 7L), State = c(rep("Alabama", 2), rep("Ohio", 1))) # one value changed to show full outer join

library(data.table)

dt1 = as.data.table(df1)
dt2 = as.data.table(df2)
setkey(dt1, CustomerId)
setkey(dt2, CustomerId)
# right outer join keyed data.tables
dt1[dt2]

setkey(dt1, NULL)
setkey(dt2, NULL)
# right outer join unkeyed data.tables - use `on` argument
dt1[dt2, on = "CustomerId"]

# left outer join - swap dt1 with dt2
dt2[dt1, on = "CustomerId"]

# inner join - use `nomatch` argument
dt1[dt2, nomatch=NULL, on = "CustomerId"]

# anti join - use `!` operator
dt1[!dt2, on = "CustomerId"]

# inner join - using merge method
merge(dt1, dt2, by = "CustomerId")

# full outer join
merge(dt1, dt2, by = "CustomerId", all = TRUE)

# see ?merge.data.table arguments for other cases

아래의 벤치 마크는 기본 R, sqldf, dplyr 및 data.table을 테스트합니다.
벤치 마크는 키가없는 / 인덱싱되지 않은 데이터 세트를 테스트합니다. 벤치 마크는 50M-1 행 데이터 세트에서 수행되며, 조인 열에는 50M-2 공통 값이 있으므로 각 시나리오 (내부, 왼쪽, 오른쪽, 전체)를 테스트 할 수 있으며 조인은 여전히 ​​쉽지 않습니다. 결합 알고리즘을 잘 강조하는 결합 유형입니다. 타이밍이의 같다 sqldf:0.4.11, dplyr:0.7.8, data.table:1.12.0.

# inner
Unit: seconds
   expr       min        lq      mean    median        uq       max neval
   base 111.66266 111.66266 111.66266 111.66266 111.66266 111.66266     1
  sqldf 624.88388 624.88388 624.88388 624.88388 624.88388 624.88388     1
  dplyr  51.91233  51.91233  51.91233  51.91233  51.91233  51.91233     1
     DT  10.40552  10.40552  10.40552  10.40552  10.40552  10.40552     1
# left
Unit: seconds
   expr        min         lq       mean     median         uq        max 
   base 142.782030 142.782030 142.782030 142.782030 142.782030 142.782030     
  sqldf 613.917109 613.917109 613.917109 613.917109 613.917109 613.917109     
  dplyr  49.711912  49.711912  49.711912  49.711912  49.711912  49.711912     
     DT   9.674348   9.674348   9.674348   9.674348   9.674348   9.674348       
# right
Unit: seconds
   expr        min         lq       mean     median         uq        max
   base 122.366301 122.366301 122.366301 122.366301 122.366301 122.366301     
  sqldf 611.119157 611.119157 611.119157 611.119157 611.119157 611.119157     
  dplyr  50.384841  50.384841  50.384841  50.384841  50.384841  50.384841     
     DT   9.899145   9.899145   9.899145   9.899145   9.899145   9.899145     
# full
Unit: seconds
  expr       min        lq      mean    median        uq       max neval
  base 141.79464 141.79464 141.79464 141.79464 141.79464 141.79464     1
 dplyr  94.66436  94.66436  94.66436  94.66436  94.66436  94.66436     1
    DT  21.62573  21.62573  21.62573  21.62573  21.62573  21.62573     1

당신이 사용하여 수행 할 수있는 조인의 다른 종류가 있습니다주의 data.table:
- 가입에 대한 업데이트를 당신이 당신의 기본 테이블에 다른 테이블의 값을 조회하려면 -
- 가입에 집계 당신은 당신이하지 않아도 합류 키를 집계 할 경우 - 모든 결과에 가입 실현하기
- 가입 중복 당신이 범위에 의해 병합 할 경우 -
- 압연 가입 - 당신이 병합 / 선행 전방 또는 후방을 굴려 행을 다음의 값에 일치 할 수 있도록하려면
- 비 동등 가입 - 경우 조인 조건이 동일하지 않습니다

재현 할 코드 :

library(microbenchmark)
library(sqldf)
library(dplyr)
library(data.table)
sapply(c("sqldf","dplyr","data.table"), packageVersion, simplify=FALSE)

n = 5e7
set.seed(108)
df1 = data.frame(x=sample(n,n-1L), y1=rnorm(n-1L))
df2 = data.frame(x=sample(n,n-1L), y2=rnorm(n-1L))
dt1 = as.data.table(df1)
dt2 = as.data.table(df2)

mb = list()
# inner join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x"),
               sqldf = sqldf("SELECT * FROM df1 INNER JOIN df2 ON df1.x = df2.x"),
               dplyr = inner_join(df1, df2, by = "x"),
               DT = dt1[dt2, nomatch=NULL, on = "x"]) -> mb$inner

# left outer join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x", all.x = TRUE),
               sqldf = sqldf("SELECT * FROM df1 LEFT OUTER JOIN df2 ON df1.x = df2.x"),
               dplyr = left_join(df1, df2, by = c("x"="x")),
               DT = dt2[dt1, on = "x"]) -> mb$left

# right outer join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x", all.y = TRUE),
               sqldf = sqldf("SELECT * FROM df2 LEFT OUTER JOIN df1 ON df2.x = df1.x"),
               dplyr = right_join(df1, df2, by = "x"),
               DT = dt1[dt2, on = "x"]) -> mb$right

# full outer join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x", all = TRUE),
               dplyr = full_join(df1, df2, by = "x"),
               DT = merge(dt1, dt2, by = "x", all = TRUE)) -> mb$full

lapply(mb, print) -> nul

다른 열 이름을 사용하는 방법을 보여주는 예를 추가하는 것이 가치가 on = 있습니까?
SymbolixAU

1
이 추가됩니다 우리가 1.9.8 릴리스가 나올 때까지 기다리는 수 @Symbolix 비 - 동등은에 연산자를 조인 on인수
jangorecki

다른 생각; 그것은 가치가 메모를 추가하고 그와 함께 merge.data.table기본이 sort = TRUE결과에 병합와 잎이가 동안 키를 추가 인수는. 특히 키 설정을 피하려는 경우 특히주의해야합니다.
SymbolixAU

1
멍청이가 있으면 대부분이 작동하지 않는다고 언급 한 것에 대해 아무도 놀랐습니다 ...
statquant

@statquant와 데카르트 조인을 data.table할 수 있습니다. 무슨 뜻입니까? 좀 더 구체적으로 말씀해 주시겠습니까?
David Arenburg

32

0.4 이후 dplyr은을 포함하여 모든 조인을 구현 outer_join했지만 0.4 이전의 첫 번째 릴리스에서는 제공하지 않았으므로 결과적으로 꽤 나쁜 해키 해결 방법 사용자 코드가 꽤 오랫동안 떠 다니고 있음에 주목할 가치가 있습니다. 나중에 (SO, Kaggle 답변, 해당 기간의 github에서 여전히 그러한 코드를 찾을 수 있습니다. 따라서이 답변은 여전히 ​​유용한 목적으로 사용됩니다.)outer_join

조인 관련 릴리즈 하이라이트 :

에게 V0.5 (2천16분의 6)

  • POSIXct 유형, 시간대, 복제본, 다른 요인 수준에 대한 처리. 더 나은 오류 및 경고.
  • 접미사 중복 변수 이름에 수신되는 것을 제어하는 ​​새로운 접미사 인수 (# 1296)

v0.4.0 (2015 년 1 월)

  • 오른쪽 조인 및 외부 조인 구현 (# 96)
  • 하나의 테이블에 다른 변수와 일치하는 행에서 새 변수를 추가하는 결합 조인. 필터링 조인-다른 테이블의 관측치와 일치하는지 여부에 따라 한 테이블의 관측치를 필터링합니다.

v0.3 (2014 년 10 월)

  • 이제 각 테이블에서 다른 변수로 left_join을 수행 할 수 있습니다. df1 %> % left_join (df2, c ( "var1"= "var2"))

v0.2 (2014 년 5 월)

  • * _join ()은 더 이상 열 이름을 재정렬하지 않습니다 (# 324)

v0.1.3 (2014 년 4 월)

해당 문제에 대한 hadley의 의견에 대한 해결 방법 :

  • right_join (x, y)은 행의 측면에서 left_join (y, x)와 동일하지만 열의 순서는 다릅니다. select (new_column_order)로 쉽게 해결할 수 있습니다.
  • outer_join 은 기본적으로 union (left_join (x, y), right_join (x, y))입니다. 즉, 두 데이터 프레임의 모든 행을 유지합니다.

1
@Gregor : 아니오 삭제해서는 안됩니다. R 코드 사용자는 수년 동안 결합 ​​기능이 누락되었다는 것을 알아야합니다. 대부분의 코드에는 해결 방법 또는 임시 수동 구현 또는 인덱스 벡터가 포함 된 애드혹이 포함되어 있기 때문에 이러한 패키지 또는 전혀 작동하지 않습니다. 매주 나는 그런 질문을 SO에 봅니다. 우리는 앞으로 몇 년 동안 혼란을 풀 것입니다.
smci

@Gregor 및 기타 질문 : 업데이트, 역사적 변경 사항 요약 및이 질문을받을 때 몇 년 동안 누락 된 사항. 이는 해당 기간의 코드가 주로 해킹되었거나 dplyr 조인을 사용하지 않고 병합시 대체 된 이유를 보여줍니다. SO 및 Kaggle에서 과거 코드베이스를 확인하면 채택 지연과 이로 인해 혼동되는 사용자 코드가 계속 표시 될 수 있습니다.이 답변이 여전히 부족한 경우 알려주십시오.
smci

@Gregor : 2014 년 중반에 채택한 사람들은 최고의 순간을 선택하지 않았습니다. (2013 년 초에 (0.0.x) 이전 릴리스가 있다고 생각했지만 실수는 아닙니다.) 그럼에도 불구하고 2015 년에는 여전히 많은 쓰레기 코드가 있었 으므로이 게시물을 게시하도록 동기를 부여했습니다. 내가 Kaggle, github, SO에서 찾은 crud.
smci

2
예, 이해합니다. 여러분이 그 일을 잘한다고 생각합니다. (저도 얼리 어답터였으며, 여전히 dplyr구문이 마음 lazyevalrlang들지만 백엔드로 변경 하면 많은 코드 가 깨져서 더 많은 것을 배우게 data.table되었고 지금은 주로 사용 data.table합니다.)
Gregor Thomas

@Gregor : 흥미 롭습니다. 저를 다루는 Q & A (귀하 또는 다른 사람)를 알려 주시겠습니까? plyr/ dplyr/ data.table/ tidyverse 의 각 채택은 우리가 시작한 연도 및 현재와는 반대로 당시의 패키지 상태 (배아) 상태에 크게 좌우되는 것 같습니다 ...
smci

25

각각 ~ 백만 개의 행을 가진 두 개의 데이터 프레임 (하나는 2 개의 열이 있고 다른 하나는 ~ 20이있는)에 합쳐서 놀랍게도 merge(..., all.x = TRUE, all.y = TRUE)그보다 빠르다 는 것을 알았 습니다 dplyr::full_join(). 이것은 dplyr v0.4입니다

병합에는 ~ 17 초가 걸리고 full_join에는 ~ 65 초가 걸립니다.

비록 일반적으로 조작 작업을 위해 dplyr을 기본값으로 사용하기 때문에 일부 음식.


24

0..*:0..1카디널리티 가있는 왼쪽 조인 또는 카디널리티가있는 오른쪽 조인의 0..1:0..*경우 결합 자 ( 0..1테이블) 에서 일방적 인 열을 직접 결합 자 ( 테이블) 에 할당 0..*하여 생성을 피할 수 있습니다. 완전히 새로운 데이터 테이블. 이를 위해서는 조인의 키 열을 조인자와 일치시키고 할당에 따라 조인자의 행을 인덱싱 + 정렬해야합니다.

키가 단일 열인 경우 단일 호출을 사용 match()하여 일치 시킬 수 있습니다 . 이 답변에서 다룰 사례입니다.

다음 df2은 조이너에서 일치하지 않는 키의 경우를 테스트하기 위해 id가 7 인 행을 추가 한 것을 제외하고는 OP를 기반으로 한 예 입니다. 이것은 효과적으로 df1왼쪽 조인입니다 df2.

df1 <- data.frame(CustomerId=1:6,Product=c(rep('Toaster',3L),rep('Radio',3L)));
df2 <- data.frame(CustomerId=c(2L,4L,6L,7L),State=c(rep('Alabama',2L),'Ohio','Texas'));
df1[names(df2)[-1L]] <- df2[match(df1[,1L],df2[,1L]),-1L];
df1;
##   CustomerId Product   State
## 1          1 Toaster    <NA>
## 2          2 Toaster Alabama
## 3          3 Toaster    <NA>
## 4          4   Radio Alabama
## 5          5   Radio    <NA>
## 6          6   Radio    Ohio

위에서 키 열이 두 입력 테이블의 첫 번째 열이라는 가정을 하드 코딩했습니다. 키 열이있는 data.frame이있는 경우 data.frame의 첫 번째 열로 설정되지 않은 경우 이상한 일이 있기 때문에 일반적으로 이것은 부당한 가정이 아니라고 주장합니다. 처음. 그리고 열을 항상 재정렬하여 열을 만들 수 있습니다. 이 가정의 유리한 결과는 키 열의 이름을 하드 코딩 할 필요가 없지만 한 가정을 다른 가정으로 대체한다고 가정합니다. 결정은 속도뿐만 아니라 정수 색인의 또 다른 장점입니다. 아래 벤치 마크에서 문자열 이름 인덱싱을 사용하여 경쟁 구현과 일치하도록 구현을 변경합니다.

하나의 큰 테이블에 대해 조인 할 여러 테이블이있는 경우 이것이 특히 적합한 솔루션이라고 생각합니다. 각 병합에 대해 전체 테이블을 반복해서 다시 작성하는 것은 불필요하고 비효율적입니다.

반면에 어떤 이유로 든이 작업을 통해 변경없이 참여자가 필요하면이 솔루션은 참여자를 직접 수정하므로 사용할 수 없습니다. 이 경우 간단하게 사본을 작성하고 사본에서 적절한 위치 지정을 수행 할 수 있습니다.


참고로, 다중 열 키에 대해 가능한 일치하는 솔루션을 간단히 살펴 보았습니다. 불행히도 내가 찾은 일치하는 솔루션은 다음과 같습니다.

  • 비효율적 인 연결. 예를 들어 match(interaction(df1$a,df1$b),interaction(df2$a,df2$b)), 또는 같은 아이디어입니다 paste().
  • 비효율적 인 직교 결합, 예 outer(df1$a,df2$a,`==`) & outer(df1$b,df2$b,`==`).
  • 기본 R merge()및 동등한 패키지 기반 병합 함수. 병합 된 결과를 리턴하기 위해 항상 새 테이블을 할당하므로 전체 지정 기반 솔루션에 적합하지 않습니다.

예를 들어, 참조 다른 데이터 프레임에 여러 열을 일치하고 그 결과로 다른 열을 받고 , 다른 두 열이있는 두 개의 열이 일치 , 여러 열에서 매칭 , 나는 원래의 위치에서 해결책을 온이 질문의 잘 속는 사람, 결합 R의 행들의 상이한 수의 두 데이터 프레임 .


벤치마킹

내부 할당 방식이이 질문에서 제공 한 다른 솔루션과 어떻게 비교되는지 확인하기 위해 자체 벤치마킹을 수행하기로 결정했습니다.

테스트 코드 :

library(microbenchmark);
library(data.table);
library(sqldf);
library(plyr);
library(dplyr);

solSpecs <- list(
    merge=list(testFuncs=list(
        inner=function(df1,df2,key) merge(df1,df2,key),
        left =function(df1,df2,key) merge(df1,df2,key,all.x=T),
        right=function(df1,df2,key) merge(df1,df2,key,all.y=T),
        full =function(df1,df2,key) merge(df1,df2,key,all=T)
    )),
    data.table.unkeyed=list(argSpec='data.table.unkeyed',testFuncs=list(
        inner=function(dt1,dt2,key) dt1[dt2,on=key,nomatch=0L,allow.cartesian=T],
        left =function(dt1,dt2,key) dt2[dt1,on=key,allow.cartesian=T],
        right=function(dt1,dt2,key) dt1[dt2,on=key,allow.cartesian=T],
        full =function(dt1,dt2,key) merge(dt1,dt2,key,all=T,allow.cartesian=T) ## calls merge.data.table()
    )),
    data.table.keyed=list(argSpec='data.table.keyed',testFuncs=list(
        inner=function(dt1,dt2) dt1[dt2,nomatch=0L,allow.cartesian=T],
        left =function(dt1,dt2) dt2[dt1,allow.cartesian=T],
        right=function(dt1,dt2) dt1[dt2,allow.cartesian=T],
        full =function(dt1,dt2) merge(dt1,dt2,all=T,allow.cartesian=T) ## calls merge.data.table()
    )),
    sqldf.unindexed=list(testFuncs=list( ## note: must pass connection=NULL to avoid running against the live DB connection, which would result in collisions with the residual tables from the last query upload
        inner=function(df1,df2,key) sqldf(paste0('select * from df1 inner join df2 using(',paste(collapse=',',key),')'),connection=NULL),
        left =function(df1,df2,key) sqldf(paste0('select * from df1 left join df2 using(',paste(collapse=',',key),')'),connection=NULL),
        right=function(df1,df2,key) sqldf(paste0('select * from df2 left join df1 using(',paste(collapse=',',key),')'),connection=NULL) ## can't do right join proper, not yet supported; inverted left join is equivalent
        ##full =function(df1,df2,key) sqldf(paste0('select * from df1 full join df2 using(',paste(collapse=',',key),')'),connection=NULL) ## can't do full join proper, not yet supported; possible to hack it with a union of left joins, but too unreasonable to include in testing
    )),
    sqldf.indexed=list(testFuncs=list( ## important: requires an active DB connection with preindexed main.df1 and main.df2 ready to go; arguments are actually ignored
        inner=function(df1,df2,key) sqldf(paste0('select * from main.df1 inner join main.df2 using(',paste(collapse=',',key),')')),
        left =function(df1,df2,key) sqldf(paste0('select * from main.df1 left join main.df2 using(',paste(collapse=',',key),')')),
        right=function(df1,df2,key) sqldf(paste0('select * from main.df2 left join main.df1 using(',paste(collapse=',',key),')')) ## can't do right join proper, not yet supported; inverted left join is equivalent
        ##full =function(df1,df2,key) sqldf(paste0('select * from main.df1 full join main.df2 using(',paste(collapse=',',key),')')) ## can't do full join proper, not yet supported; possible to hack it with a union of left joins, but too unreasonable to include in testing
    )),
    plyr=list(testFuncs=list(
        inner=function(df1,df2,key) join(df1,df2,key,'inner'),
        left =function(df1,df2,key) join(df1,df2,key,'left'),
        right=function(df1,df2,key) join(df1,df2,key,'right'),
        full =function(df1,df2,key) join(df1,df2,key,'full')
    )),
    dplyr=list(testFuncs=list(
        inner=function(df1,df2,key) inner_join(df1,df2,key),
        left =function(df1,df2,key) left_join(df1,df2,key),
        right=function(df1,df2,key) right_join(df1,df2,key),
        full =function(df1,df2,key) full_join(df1,df2,key)
    )),
    in.place=list(testFuncs=list(
        left =function(df1,df2,key) { cns <- setdiff(names(df2),key); df1[cns] <- df2[match(df1[,key],df2[,key]),cns]; df1; },
        right=function(df1,df2,key) { cns <- setdiff(names(df1),key); df2[cns] <- df1[match(df2[,key],df1[,key]),cns]; df2; }
    ))
);

getSolTypes <- function() names(solSpecs);
getJoinTypes <- function() unique(unlist(lapply(solSpecs,function(x) names(x$testFuncs))));
getArgSpec <- function(argSpecs,key=NULL) if (is.null(key)) argSpecs$default else argSpecs[[key]];

initSqldf <- function() {
    sqldf(); ## creates sqlite connection on first run, cleans up and closes existing connection otherwise
    if (exists('sqldfInitFlag',envir=globalenv(),inherits=F) && sqldfInitFlag) { ## false only on first run
        sqldf(); ## creates a new connection
    } else {
        assign('sqldfInitFlag',T,envir=globalenv()); ## set to true for the one and only time
    }; ## end if
    invisible();
}; ## end initSqldf()

setUpBenchmarkCall <- function(argSpecs,joinType,solTypes=getSolTypes(),env=parent.frame()) {
    ## builds and returns a list of expressions suitable for passing to the list argument of microbenchmark(), and assigns variables to resolve symbol references in those expressions
    callExpressions <- list();
    nms <- character();
    for (solType in solTypes) {
        testFunc <- solSpecs[[solType]]$testFuncs[[joinType]];
        if (is.null(testFunc)) next; ## this join type is not defined for this solution type
        testFuncName <- paste0('tf.',solType);
        assign(testFuncName,testFunc,envir=env);
        argSpecKey <- solSpecs[[solType]]$argSpec;
        argSpec <- getArgSpec(argSpecs,argSpecKey);
        argList <- setNames(nm=names(argSpec$args),vector('list',length(argSpec$args)));
        for (i in seq_along(argSpec$args)) {
            argName <- paste0('tfa.',argSpecKey,i);
            assign(argName,argSpec$args[[i]],envir=env);
            argList[[i]] <- if (i%in%argSpec$copySpec) call('copy',as.symbol(argName)) else as.symbol(argName);
        }; ## end for
        callExpressions[[length(callExpressions)+1L]] <- do.call(call,c(list(testFuncName),argList),quote=T);
        nms[length(nms)+1L] <- solType;
    }; ## end for
    names(callExpressions) <- nms;
    callExpressions;
}; ## end setUpBenchmarkCall()

harmonize <- function(res) {
    res <- as.data.frame(res); ## coerce to data.frame
    for (ci in which(sapply(res,is.factor))) res[[ci]] <- as.character(res[[ci]]); ## coerce factor columns to character
    for (ci in which(sapply(res,is.logical))) res[[ci]] <- as.integer(res[[ci]]); ## coerce logical columns to integer (works around sqldf quirk of munging logicals to integers)
    ##for (ci in which(sapply(res,inherits,'POSIXct'))) res[[ci]] <- as.double(res[[ci]]); ## coerce POSIXct columns to double (works around sqldf quirk of losing POSIXct class) ----- POSIXct doesn't work at all in sqldf.indexed
    res <- res[order(names(res))]; ## order columns
    res <- res[do.call(order,res),]; ## order rows
    res;
}; ## end harmonize()

checkIdentical <- function(argSpecs,solTypes=getSolTypes()) {
    for (joinType in getJoinTypes()) {
        callExpressions <- setUpBenchmarkCall(argSpecs,joinType,solTypes);
        if (length(callExpressions)<2L) next;
        ex <- harmonize(eval(callExpressions[[1L]]));
        for (i in seq(2L,len=length(callExpressions)-1L)) {
            y <- harmonize(eval(callExpressions[[i]]));
            if (!isTRUE(all.equal(ex,y,check.attributes=F))) {
                ex <<- ex;
                y <<- y;
                solType <- names(callExpressions)[i];
                stop(paste0('non-identical: ',solType,' ',joinType,'.'));
            }; ## end if
        }; ## end for
    }; ## end for
    invisible();
}; ## end checkIdentical()

testJoinType <- function(argSpecs,joinType,solTypes=getSolTypes(),metric=NULL,times=100L) {
    callExpressions <- setUpBenchmarkCall(argSpecs,joinType,solTypes);
    bm <- microbenchmark(list=callExpressions,times=times);
    if (is.null(metric)) return(bm);
    bm <- summary(bm);
    res <- setNames(nm=names(callExpressions),bm[[metric]]);
    attr(res,'unit') <- attr(bm,'unit');
    res;
}; ## end testJoinType()

testAllJoinTypes <- function(argSpecs,solTypes=getSolTypes(),metric=NULL,times=100L) {
    joinTypes <- getJoinTypes();
    resList <- setNames(nm=joinTypes,lapply(joinTypes,function(joinType) testJoinType(argSpecs,joinType,solTypes,metric,times)));
    if (is.null(metric)) return(resList);
    units <- unname(unlist(lapply(resList,attr,'unit')));
    res <- do.call(data.frame,c(list(join=joinTypes),setNames(nm=solTypes,rep(list(rep(NA_real_,length(joinTypes))),length(solTypes))),list(unit=units,stringsAsFactors=F)));
    for (i in seq_along(resList)) res[i,match(names(resList[[i]]),names(res))] <- resList[[i]];
    res;
}; ## end testAllJoinTypes()

testGrid <- function(makeArgSpecsFunc,sizes,overlaps,solTypes=getSolTypes(),joinTypes=getJoinTypes(),metric='median',times=100L) {

    res <- expand.grid(size=sizes,overlap=overlaps,joinType=joinTypes,stringsAsFactors=F);
    res[solTypes] <- NA_real_;
    res$unit <- NA_character_;
    for (ri in seq_len(nrow(res))) {

        size <- res$size[ri];
        overlap <- res$overlap[ri];
        joinType <- res$joinType[ri];

        argSpecs <- makeArgSpecsFunc(size,overlap);

        checkIdentical(argSpecs,solTypes);

        cur <- testJoinType(argSpecs,joinType,solTypes,metric,times);
        res[ri,match(names(cur),names(res))] <- cur;
        res$unit[ri] <- attr(cur,'unit');

    }; ## end for

    res;

}; ## end testGrid()

앞에서 설명한 OP를 기반으로 한 예제의 벤치 마크는 다음과 같습니다.

## OP's example, supplemented with a non-matching row in df2
argSpecs <- list(
    default=list(copySpec=1:2,args=list(
        df1 <- data.frame(CustomerId=1:6,Product=c(rep('Toaster',3L),rep('Radio',3L))),
        df2 <- data.frame(CustomerId=c(2L,4L,6L,7L),State=c(rep('Alabama',2L),'Ohio','Texas')),
        'CustomerId'
    )),
    data.table.unkeyed=list(copySpec=1:2,args=list(
        as.data.table(df1),
        as.data.table(df2),
        'CustomerId'
    )),
    data.table.keyed=list(copySpec=1:2,args=list(
        setkey(as.data.table(df1),CustomerId),
        setkey(as.data.table(df2),CustomerId)
    ))
);
## prepare sqldf
initSqldf();
sqldf('create index df1_key on df1(CustomerId);'); ## upload and create an sqlite index on df1
sqldf('create index df2_key on df2(CustomerId);'); ## upload and create an sqlite index on df2

checkIdentical(argSpecs);

testAllJoinTypes(argSpecs,metric='median');
##    join    merge data.table.unkeyed data.table.keyed sqldf.unindexed sqldf.indexed      plyr    dplyr in.place         unit
## 1 inner  644.259           861.9345          923.516        9157.752      1580.390  959.2250 270.9190       NA microseconds
## 2  left  713.539           888.0205          910.045        8820.334      1529.714  968.4195 270.9185 224.3045 microseconds
## 3 right 1221.804           909.1900          923.944        8930.668      1533.135 1063.7860 269.8495 218.1035 microseconds
## 4  full 1302.203          3107.5380         3184.729              NA            NA 1593.6475 270.7055       NA microseconds

여기에서는 임의의 입력 데이터를 벤치마킹하여 두 입력 테이블간에 서로 다른 스케일과 다른 키 겹침 패턴을 시도합니다. 이 벤치 마크는 여전히 단일 열 정수 키의 경우로 제한됩니다. 또한 내부 솔루션이 동일한 테이블의 왼쪽 및 오른쪽 조인 모두에 대해 작동하도록하기 위해 모든 무작위 테스트 데이터는 0..1:0..1카디널리티를 사용합니다 . 이것은 두 번째 data.frame의 키 열을 생성 할 때 첫 번째 data.frame의 키 열을 대체하지 않고 샘플링함으로써 구현됩니다.

makeArgSpecs.singleIntegerKey.optionalOneToOne <- function(size,overlap) {

    com <- as.integer(size*overlap);

    argSpecs <- list(
        default=list(copySpec=1:2,args=list(
            df1 <- data.frame(id=sample(size),y1=rnorm(size),y2=rnorm(size)),
            df2 <- data.frame(id=sample(c(if (com>0L) sample(df1$id,com) else integer(),seq(size+1L,len=size-com))),y3=rnorm(size),y4=rnorm(size)),
            'id'
        )),
        data.table.unkeyed=list(copySpec=1:2,args=list(
            as.data.table(df1),
            as.data.table(df2),
            'id'
        )),
        data.table.keyed=list(copySpec=1:2,args=list(
            setkey(as.data.table(df1),id),
            setkey(as.data.table(df2),id)
        ))
    );
    ## prepare sqldf
    initSqldf();
    sqldf('create index df1_key on df1(id);'); ## upload and create an sqlite index on df1
    sqldf('create index df2_key on df2(id);'); ## upload and create an sqlite index on df2

    argSpecs;

}; ## end makeArgSpecs.singleIntegerKey.optionalOneToOne()

## cross of various input sizes and key overlaps
sizes <- c(1e1L,1e3L,1e6L);
overlaps <- c(0.99,0.5,0.01);
system.time({ res <- testGrid(makeArgSpecs.singleIntegerKey.optionalOneToOne,sizes,overlaps); });
##     user   system  elapsed
## 22024.65 12308.63 34493.19

위의 결과에 대한 로그 로그 플롯을 작성하는 코드를 작성했습니다. 각 중복 비율에 대해 별도의 플롯을 생성했습니다. 약간 혼란 스럽지만 모든 솔루션 유형과 조인 유형이 동일한 플롯으로 표시되는 것을 좋아합니다.

스플라인 보간법을 사용하여 개별 pch 기호로 그려진 각 솔루션 / 결합 유형 조합에 대해 부드러운 곡선을 표시했습니다. 결합 유형은 왼쪽, 오른쪽의 내부, 왼쪽 및 오른쪽 꺾쇠 괄호와 다이아몬드의 전체를 사용하여 pch 기호로 캡처됩니다. 솔루션 유형은 범례에 표시된대로 색상으로 캡처됩니다.

plotRes <- function(res,titleFunc,useFloor=F) {
    solTypes <- setdiff(names(res),c('size','overlap','joinType','unit')); ## derive from res
    normMult <- c(microseconds=1e-3,milliseconds=1); ## normalize to milliseconds
    joinTypes <- getJoinTypes();
    cols <- c(merge='purple',data.table.unkeyed='blue',data.table.keyed='#00DDDD',sqldf.unindexed='brown',sqldf.indexed='orange',plyr='red',dplyr='#00BB00',in.place='magenta');
    pchs <- list(inner=20L,left='<',right='>',full=23L);
    cexs <- c(inner=0.7,left=1,right=1,full=0.7);
    NP <- 60L;
    ord <- order(decreasing=T,colMeans(res[res$size==max(res$size),solTypes],na.rm=T));
    ymajors <- data.frame(y=c(1,1e3),label=c('1ms','1s'),stringsAsFactors=F);
    for (overlap in unique(res$overlap)) {
        x1 <- res[res$overlap==overlap,];
        x1[solTypes] <- x1[solTypes]*normMult[x1$unit]; x1$unit <- NULL;
        xlim <- c(1e1,max(x1$size));
        xticks <- 10^seq(log10(xlim[1L]),log10(xlim[2L]));
        ylim <- c(1e-1,10^((if (useFloor) floor else ceiling)(log10(max(x1[solTypes],na.rm=T))))); ## use floor() to zoom in a little more, only sqldf.unindexed will break above, but xpd=NA will keep it visible
        yticks <- 10^seq(log10(ylim[1L]),log10(ylim[2L]));
        yticks.minor <- rep(yticks[-length(yticks)],each=9L)*1:9;
        plot(NA,xlim=xlim,ylim=ylim,xaxs='i',yaxs='i',axes=F,xlab='size (rows)',ylab='time (ms)',log='xy');
        abline(v=xticks,col='lightgrey');
        abline(h=yticks.minor,col='lightgrey',lty=3L);
        abline(h=yticks,col='lightgrey');
        axis(1L,xticks,parse(text=sprintf('10^%d',as.integer(log10(xticks)))));
        axis(2L,yticks,parse(text=sprintf('10^%d',as.integer(log10(yticks)))),las=1L);
        axis(4L,ymajors$y,ymajors$label,las=1L,tick=F,cex.axis=0.7,hadj=0.5);
        for (joinType in rev(joinTypes)) { ## reverse to draw full first, since it's larger and would be more obtrusive if drawn last
            x2 <- x1[x1$joinType==joinType,];
            for (solType in solTypes) {
                if (any(!is.na(x2[[solType]]))) {
                    xy <- spline(x2$size,x2[[solType]],xout=10^(seq(log10(x2$size[1L]),log10(x2$size[nrow(x2)]),len=NP)));
                    points(xy$x,xy$y,pch=pchs[[joinType]],col=cols[solType],cex=cexs[joinType],xpd=NA);
                }; ## end if
            }; ## end for
        }; ## end for
        ## custom legend
        ## due to logarithmic skew, must do all distance calcs in inches, and convert to user coords afterward
        ## the bottom-left corner of the legend will be defined in normalized figure coords, although we can convert to inches immediately
        leg.cex <- 0.7;
        leg.x.in <- grconvertX(0.275,'nfc','in');
        leg.y.in <- grconvertY(0.6,'nfc','in');
        leg.x.user <- grconvertX(leg.x.in,'in');
        leg.y.user <- grconvertY(leg.y.in,'in');
        leg.outpad.w.in <- 0.1;
        leg.outpad.h.in <- 0.1;
        leg.midpad.w.in <- 0.1;
        leg.midpad.h.in <- 0.1;
        leg.sol.w.in <- max(strwidth(solTypes,'in',leg.cex));
        leg.sol.h.in <- max(strheight(solTypes,'in',leg.cex))*1.5; ## multiplication factor for greater line height
        leg.join.w.in <- max(strheight(joinTypes,'in',leg.cex))*1.5; ## ditto
        leg.join.h.in <- max(strwidth(joinTypes,'in',leg.cex));
        leg.main.w.in <- leg.join.w.in*length(joinTypes);
        leg.main.h.in <- leg.sol.h.in*length(solTypes);
        leg.x2.user <- grconvertX(leg.x.in+leg.outpad.w.in*2+leg.main.w.in+leg.midpad.w.in+leg.sol.w.in,'in');
        leg.y2.user <- grconvertY(leg.y.in+leg.outpad.h.in*2+leg.main.h.in+leg.midpad.h.in+leg.join.h.in,'in');
        leg.cols.x.user <- grconvertX(leg.x.in+leg.outpad.w.in+leg.join.w.in*(0.5+seq(0L,length(joinTypes)-1L)),'in');
        leg.lines.y.user <- grconvertY(leg.y.in+leg.outpad.h.in+leg.main.h.in-leg.sol.h.in*(0.5+seq(0L,length(solTypes)-1L)),'in');
        leg.sol.x.user <- grconvertX(leg.x.in+leg.outpad.w.in+leg.main.w.in+leg.midpad.w.in,'in');
        leg.join.y.user <- grconvertY(leg.y.in+leg.outpad.h.in+leg.main.h.in+leg.midpad.h.in,'in');
        rect(leg.x.user,leg.y.user,leg.x2.user,leg.y2.user,col='white');
        text(leg.sol.x.user,leg.lines.y.user,solTypes[ord],cex=leg.cex,pos=4L,offset=0);
        text(leg.cols.x.user,leg.join.y.user,joinTypes,cex=leg.cex,pos=4L,offset=0,srt=90); ## srt rotation applies *after* pos/offset positioning
        for (i in seq_along(joinTypes)) {
            joinType <- joinTypes[i];
            points(rep(leg.cols.x.user[i],length(solTypes)),ifelse(colSums(!is.na(x1[x1$joinType==joinType,solTypes[ord]]))==0L,NA,leg.lines.y.user),pch=pchs[[joinType]],col=cols[solTypes[ord]]);
        }; ## end for
        title(titleFunc(overlap));
        readline(sprintf('overlap %.02f',overlap));
    }; ## end for
}; ## end plotRes()

titleFunc <- function(overlap) sprintf('R merge solutions: single-column integer key, 0..1:0..1 cardinality, %d%% overlap',as.integer(overlap*100));
plotRes(res,titleFunc,T);

R- 병합 벤치 마크-단일 열-정수-선택-일대일 -99

R- 병합 벤치 마크-단일 열-정수-선택-일대일 -50

R- 병합 벤치 마크-단일 열-정수-선택-일대일 -1


다음은 키 열의 수와 유형 및 카디널리티와 관련하여 더 강력한 두 번째 대규모 벤치 마크입니다. 이 벤치 마크에서는 카디널리티 (즉, 0..*:0..*) 에 대한 제한없이 세 개의 키 열 (문자, 정수 및 논리 )을 사용합니다. (일반적으로 부동 소수점 비교 복잡성으로 인해 이중 또는 복잡한 값으로 키 열을 정의하는 것은 바람직하지 않으며 기본적으로 아무도 원시 유형을 사용하지 않으며 키 열에 대해서는 훨씬 적으므로 키에 해당 유형을 포함시키지 않았습니다. 또한 정보를 위해 처음에는 POSIXct 키 열을 포함하여 네 개의 키 열을 사용하려고 시도했지만 POSIXct 유형은 sqldf.indexed어떤 이유로 든 부동 소수점 비교 이상으로 인해 솔루션 과 잘 작동하지 않았 으므로 제거했습니다.)

makeArgSpecs.assortedKey.optionalManyToMany <- function(size,overlap,uniquePct=75) {

    ## number of unique keys in df1
    u1Size <- as.integer(size*uniquePct/100);

    ## (roughly) divide u1Size into bases, so we can use expand.grid() to produce the required number of unique key values with repetitions within individual key columns
    ## use ceiling() to ensure we cover u1Size; will truncate afterward
    u1SizePerKeyColumn <- as.integer(ceiling(u1Size^(1/3)));

    ## generate the unique key values for df1
    keys1 <- expand.grid(stringsAsFactors=F,
        idCharacter=replicate(u1SizePerKeyColumn,paste(collapse='',sample(letters,sample(4:12,1L),T))),
        idInteger=sample(u1SizePerKeyColumn),
        idLogical=sample(c(F,T),u1SizePerKeyColumn,T)
        ##idPOSIXct=as.POSIXct('2016-01-01 00:00:00','UTC')+sample(u1SizePerKeyColumn)
    )[seq_len(u1Size),];

    ## rbind some repetitions of the unique keys; this will prepare one side of the many-to-many relationship
    ## also scramble the order afterward
    keys1 <- rbind(keys1,keys1[sample(nrow(keys1),size-u1Size,T),])[sample(size),];

    ## common and unilateral key counts
    com <- as.integer(size*overlap);
    uni <- size-com;

    ## generate some unilateral keys for df2 by synthesizing outside of the idInteger range of df1
    keys2 <- data.frame(stringsAsFactors=F,
        idCharacter=replicate(uni,paste(collapse='',sample(letters,sample(4:12,1L),T))),
        idInteger=u1SizePerKeyColumn+sample(uni),
        idLogical=sample(c(F,T),uni,T)
        ##idPOSIXct=as.POSIXct('2016-01-01 00:00:00','UTC')+u1SizePerKeyColumn+sample(uni)
    );

    ## rbind random keys from df1; this will complete the many-to-many relationship
    ## also scramble the order afterward
    keys2 <- rbind(keys2,keys1[sample(nrow(keys1),com,T),])[sample(size),];

    ##keyNames <- c('idCharacter','idInteger','idLogical','idPOSIXct');
    keyNames <- c('idCharacter','idInteger','idLogical');
    ## note: was going to use raw and complex type for two of the non-key columns, but data.table doesn't seem to fully support them
    argSpecs <- list(
        default=list(copySpec=1:2,args=list(
            df1 <- cbind(stringsAsFactors=F,keys1,y1=sample(c(F,T),size,T),y2=sample(size),y3=rnorm(size),y4=replicate(size,paste(collapse='',sample(letters,sample(4:12,1L),T)))),
            df2 <- cbind(stringsAsFactors=F,keys2,y5=sample(c(F,T),size,T),y6=sample(size),y7=rnorm(size),y8=replicate(size,paste(collapse='',sample(letters,sample(4:12,1L),T)))),
            keyNames
        )),
        data.table.unkeyed=list(copySpec=1:2,args=list(
            as.data.table(df1),
            as.data.table(df2),
            keyNames
        )),
        data.table.keyed=list(copySpec=1:2,args=list(
            setkeyv(as.data.table(df1),keyNames),
            setkeyv(as.data.table(df2),keyNames)
        ))
    );
    ## prepare sqldf
    initSqldf();
    sqldf(paste0('create index df1_key on df1(',paste(collapse=',',keyNames),');')); ## upload and create an sqlite index on df1
    sqldf(paste0('create index df2_key on df2(',paste(collapse=',',keyNames),');')); ## upload and create an sqlite index on df2

    argSpecs;

}; ## end makeArgSpecs.assortedKey.optionalManyToMany()

sizes <- c(1e1L,1e3L,1e5L); ## 1e5L instead of 1e6L to respect more heavy-duty inputs
overlaps <- c(0.99,0.5,0.01);
solTypes <- setdiff(getSolTypes(),'in.place');
system.time({ res <- testGrid(makeArgSpecs.assortedKey.optionalManyToMany,sizes,overlaps,solTypes); });
##     user   system  elapsed
## 38895.50   784.19 39745.53

위에 제공된 동일한 플로팅 코드를 사용한 결과 플롯 :

titleFunc <- function(overlap) sprintf('R merge solutions: character/integer/logical key, 0..*:0..* cardinality, %d%% overlap',as.integer(overlap*100));
plotRes(res,titleFunc,F);

R- 병합 벤치 마크 분류 키-옵션-다-다-다 -99

R- 병합 벤치 마크 분류 키-옵션 다 대다 -50

R- 병합 벤치 마크 분류 키-옵션-다-다-다 --1


아주 좋은 분석이지만 10 ^ 1에서 10 ^ 6까지 스케일을 설정하는 것이 유감입니다. 속도 차이가 거의 관련이없는 매우 작은 세트입니다. 10 ^ 6에서 10 ^ 8까지는 흥미로울 것입니다!
jangorecki

1
또한 벤치마킹에 클래스 강제 시간을 포함시켜 조인 작업에 적합하지 않은 것으로 나타났습니다.
jangorecki 2018

8
  1. merge함수를 사용하여 SQL의 select 문에 익숙한 것과 같은 방식으로 왼쪽 테이블 또는 오른쪽 테이블의 변수를 선택할 수 있습니다 (EX : Select a. * ... 또는 Select b. * from .....)
  2. 우리는 새로 조인 된 테이블에서 부분 집합되는 코드를 추가해야한다.

    • SQL :- select a.* from df1 a inner join df2 b on a.CustomerId=b.CustomerId

    • R :- merge(df1, df2, by.x = "CustomerId", by.y = "CustomerId")[,names(df1)]

같은 길

  • SQL :- select b.* from df1 a inner join df2 b on a.CustomerId=b.CustomerId

  • R :- merge(df1, df2, by.x = "CustomerId", by.y = "CustomerId")[,names(df2)]


7

내부는 모든 컬럼에 참여를 들어, 당신은 또한 사용할 수 있습니다 fintersect으로부터 data.table -package 또는 intersect으로부터 dplyr의 대안으로 -package merge지정하지 않고 by-columns합니다. 이렇게하면 두 데이터 프레임 사이에 동일한 행이 제공됩니다.

merge(df1, df2)
#   V1 V2
# 1  B  2
# 2  C  3
dplyr::intersect(df1, df2)
#   V1 V2
# 1  B  2
# 2  C  3
data.table::fintersect(setDT(df1), setDT(df2))
#    V1 V2
# 1:  B  2
# 2:  C  3

데이터 예 :

df1 <- data.frame(V1 = LETTERS[1:4], V2 = 1:4)
df2 <- data.frame(V1 = LETTERS[2:3], V2 = 2:3)

5

조인 업데이트. 다른 중요한 SQL 스타일 조인은 한 테이블의 열이 다른 테이블을 사용하여 업데이트 (또는 생성) 되는 " 업데이트 조인 "입니다.

OP의 예제 테이블 수정 중 ...

sales = data.frame(
  CustomerId = c(1, 1, 1, 3, 4, 6), 
  Year = 2000:2005,
  Product = c(rep("Toaster", 3), rep("Radio", 3))
)
cust = data.frame(
  CustomerId = c(1, 1, 4, 6), 
  Year = c(2001L, 2002L, 2002L, 2002L),
  State = state.name[1:4]
)

sales
# CustomerId Year Product
#          1 2000 Toaster
#          1 2001 Toaster
#          1 2002 Toaster
#          3 2003   Radio
#          4 2004   Radio
#          6 2005   Radio

cust
# CustomerId Year    State
#          1 2001  Alabama
#          1 2002   Alaska
#          4 2002  Arizona
#          6 2002 Arkansas

고객의 상태를 cust구매 테이블 에 추가하려고한다고 가정 합니다.sales연도 열을 무시하고 . 기본 R을 사용하면 일치하는 행을 식별 한 후 다음 값을 복사 할 수 있습니다.

sales$State <- cust$State[ match(sales$CustomerId, cust$CustomerId) ]

# CustomerId Year Product    State
#          1 2000 Toaster  Alabama
#          1 2001 Toaster  Alabama
#          1 2002 Toaster  Alabama
#          3 2003   Radio     <NA>
#          4 2004   Radio  Arizona
#          6 2005   Radio Arkansas

# cleanup for the next example
sales$State <- NULL

여기에서 볼 수 있듯이 matchcustomer 테이블에서 첫 번째로 일치하는 행을 선택합니다.


여러 열로 조인 업데이트위의 접근법은 단일 열에서만 결합하고 첫 번째 일치에 만족할 때 효과적입니다. 고객 테이블의 측정 연도가 판매 연도와 일치한다고 가정합니다.

@의 bgoldst의 대답은 언급대로, match로는 interaction이 경우에 대한 옵션이 될 수 있습니다. 더 간단하게 data.table을 사용할 수 있습니다.

library(data.table)
setDT(sales); setDT(cust)

sales[, State := cust[sales, on=.(CustomerId, Year), x.State]]

#    CustomerId Year Product   State
# 1:          1 2000 Toaster    <NA>
# 2:          1 2001 Toaster Alabama
# 3:          1 2002 Toaster  Alaska
# 4:          3 2003   Radio    <NA>
# 5:          4 2004   Radio    <NA>
# 6:          6 2005   Radio    <NA>

# cleanup for next example
sales[, State := NULL]

롤링 업데이트 조인. 또는 고객이 마지막으로 찾은 상태를 확인하고 싶을 수도 있습니다.

sales[, State := cust[sales, on=.(CustomerId, Year), roll=TRUE, x.State]]

#    CustomerId Year Product    State
# 1:          1 2000 Toaster     <NA>
# 2:          1 2001 Toaster  Alabama
# 3:          1 2002 Toaster   Alaska
# 4:          3 2003   Radio     <NA>
# 5:          4 2004   Radio  Arizona
# 6:          6 2005   Radio Arkansas

위의 세 가지 예는 모두 새 열 작성 / 추가에 중점을 둡니다. 기존 열을 업데이트 / 수정하는 예 는 관련 R FAQ 를 참조하십시오 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.