data.table 대 dplyr : 한 사람이 다른 사람이 할 수없는 일을 잘 할 수 있습니까?
개요
나는 상대적으로 익숙 data.table
하지만 dplyr
. 나는 몇 가지 dplyr
비 네트 와 튀어 나온 예제를 읽었 으며 지금까지 내 결론은 다음과 같습니다.
data.table
와dplyr
속도 비교입니다,이 많은 (예> -10 만) 그룹, 그리고 몇 가지 다른 상황에서 (아래 벤치 마크를 참조) 경우를 제외하고dplyr
더 접근하기 쉬운 구문이 있습니다.dplyr
잠재적 인 DB 상호 작용을 추상화 (또는 의지)합니다.- 몇 가지 사소한 기능 차이가 있습니다 (아래의 "예제 / 사용법"참조).
내 마음에 2. 꽤 익숙하기 때문에 무게를 많이 견디지 data.table
못하지만 두 가지를 처음 접하는 사용자에게는 큰 요인이 될 것임을 이해합니다. 이미 익숙한 사람의 관점에서 질문 한 특정 질문과 관련이없는보다 직관적 인 논쟁은 피하고 싶습니다 data.table
. 또한 "보다 직관적"이 더 빠른 분석으로 이어지는 방법에 대한 논의를 피하고 싶습니다 (확실히 사실이지만 여기서 가장 관심이있는 것은 아닙니다).
질문
내가 알고 싶은 것은 :
- 패키지에 익숙한 사람들을 위해 하나 또는 다른 패키지로 코딩하기가 훨씬 더 쉬운 분석 작업이 있습니까 (즉, 필요한 키 입력 조합 대 필요한 수준의 밀교주의, 각각이 더 적은 것이 좋은 것입니다).
- 하나의 패키지와 다른 패키지에서 실질적으로 (즉, 2 배 이상) 더 효율적으로 수행되는 분석 작업이 있습니까?
최근의 한 질문 에 대해 좀 더 생각하게되었습니다. 그 시점까지는 dplyr
제가에서 할 수있는 것 이상의 많은 것을 제공 할 것이라고 생각하지 않았기 때문 입니다 data.table
. dplyr
해결책 은 다음과 같습니다 (Q 끝의 데이터).
dat %.%
group_by(name, job) %.%
filter(job != "Boss" | year == min(year)) %.%
mutate(cumu_job2 = cumsum(job2))
data.table
솔루션 에 대한 내 해킹 시도보다 훨씬 낫습니다 . 즉, 좋은 data.table
솔루션도 꽤 좋습니다 (Jean-Robert, Arun에게 감사하며 여기에서 가장 최적의 솔루션보다 단일 문장을 선호했습니다).
setDT(dat)[,
.SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)],
by=list(id, job)
]
후자의 구문은 매우 난해 해 보일 수 있지만, 익숙하다면 실제로는 매우 간단합니다 data.table
(즉, 좀 더 난해한 트릭을 사용하지 않음).
이상적으로 내가보고 싶은 것은 dplyr
또는 data.table
방법이 실질적으로 더 간결하거나 실질적으로 더 나은 성능 을 제공하는 좋은 예 입니다.
예
용법dplyr
임의의 수의 행을 반환하는 그룹화 된 작업을 허용하지 않습니다 ( eddi의 질문 에서 참고 : dplyr 0.5 에서 구현되는 것처럼 보이며 @beginneR은do
@eddi의 질문에 대한 답변에 사용되는 잠재적 해결 방법을 보여줍니다 ).data.table
지원 압연 조인 아니라 (감사 @dholstius)로 중복 조인data.table
내부적 형태의 식을 최적화DT[col == value]
또는DT[col %in% values]
대 속도 통해 자동 인덱싱 사용 이진 검색을 동일한 기지국 R 구문을 이용하면서. 자세한 내용과 작은 벤치 마크는 여기 를 참조하세요 .dplyr
이벤트 기능 (예를 들어, 표준 평가 버전regroup
,summarize_each_
) 그의 프로그램 사용을 단순화 할 수 있습니다dplyr
(주목 프로그램을 사용data.table
, 좀 신중하게 생각을 확실히 가능 필요합니다, 대체 / 인용 등, 내 지식에 적어도)
- 내 벤치 마크를 실행 한 결과 두 패키지 모두 "분할 적용 결합"스타일 분석에서 비교할 수있는 것으로 나타났습니다. 그룹 수가 매우 많을 때 (> 100K)
data.table
훨씬 빨라지 는 경우를 제외하고는 말입니다 . - @Arun은 조인 에 대한 몇 가지 벤치 마크를 실행 하여 그룹 수가 증가 할
data.table
때보 다 더 잘 확장 됨을 보여주었습니다dplyr
(패키지 및 R의 최신 버전에서 최근 개선 사항으로 업데이트 됨). 또한 고유 값 을 얻으려고 할 때의 벤치 마크 는data.table
~ 6 배 더 빠릅니다. - (Unverified)는
data.table
그룹 / 적용 / 정렬의 더 큰 버전에서 75 % 더 빠르며 작은 버전에서는dplyr
40 % 더 빠릅니다 ( 댓글의 또 다른 SO 질문 , danas에게 감사드립니다). - 의 주 저자 인 Matt
data.table
는 최대 20 억 행 (RAM에서 최대 100GB)에 대한data.table
,dplyr
및 python의 그룹화 작업을 벤치마킹했습니다pandas
. - 80K 그룹에 대한 이전 벤치 마크 는
data.table
~ 8 배 더 빠릅니다.
데이터
이것은 질문 섹션에서 보여준 첫 번째 예입니다.
dat <- structure(list(id = c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L,
2L, 2L, 2L, 2L, 2L, 2L), name = c("Jane", "Jane", "Jane", "Jane",
"Jane", "Jane", "Jane", "Jane", "Bob", "Bob", "Bob", "Bob", "Bob",
"Bob", "Bob", "Bob"), year = c(1980L, 1981L, 1982L, 1983L, 1984L,
1985L, 1986L, 1987L, 1985L, 1986L, 1987L, 1988L, 1989L, 1990L,
1991L, 1992L), job = c("Manager", "Manager", "Manager", "Manager",
"Manager", "Manager", "Boss", "Boss", "Manager", "Manager", "Manager",
"Boss", "Boss", "Boss", "Boss", "Boss"), job2 = c(1L, 1L, 1L,
1L, 1L, 1L, 0L, 0L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L)), .Names = c("id",
"name", "year", "job", "job2"), class = "data.frame", row.names = c(NA,
-16L))
우리는 이러한 측면은 (중요성의 특별한 순서없이) 종합 대답 / 비교를 제공하기 위해 적어도 커버 필요 Speed
, Memory usage
, Syntax
와 Features
.
제 의도는 data.table 관점에서 가능한 한 명확하게 이들 각각을 다루는 것입니다.
참고 : 달리 명시 적으로 언급하지 않는 한, dplyr을 참조하여 내부가 Rcpp를 사용하는 C ++ 인 dplyr의 data.frame 인터페이스를 참조합니다.
data.table 구문은- DT[i, j, by]
. 유지하기 위해 i
, j
그리고 by
함께하는 디자인입니다. 관련 작업을 함께 유지함으로써 속도 와 더 중요한 메모리 사용 을 위해 작업 을 쉽게 최적화 하고 구문의 일관성을 유지하면서 몇 가지 강력한 기능을 제공 할 수 있습니다.
1. 속도
(주로하지만 작업을 그룹화에) 꽤 많은 벤치 마크는 이미 도착 data.table을 보여주는 질문에 추가 된 빠른 포함 증가하여 그룹에 그룹 및 / 또는 행의 수, 등 dplyr보다 매트에 의해 벤치 마크 에서 그룹화에 천만에 1 억 ~ 1,000 만 개 그룹 및 다양한 그룹화 열에 20 억 행 (RAM에서 100GB) 이 있습니다 pandas
. 참고 업데이트 된 벤치 마크를 포함 Spark
하고 pydatatable
도 있습니다.
벤치 마크에서 다음과 같은 나머지 측면도 포함하는 것이 좋습니다.
행 의 하위 집합을 포함하는 그룹화 작업 -즉,
DT[x > val, sum(y), by = z]
유형 작업.업데이트 및 조인 과 같은 기타 작업을 벤치마킹 합니다 .
또한 런타임 외에도 각 작업에 대한 메모리 사용량 을 벤치 마크 합니다.
2. 메모리 사용량
관련된 작업
filter()
또는slice()
dplyr 인은 (모두 data.frames 및 data.tables)에 비효율적 인 메모리가 될 수 있습니다. 이 게시물을 참조하십시오 .참고 해들리의 코멘트 에 대해 이야기 속도 여기에 주요 관심사 반면 (즉 dplyr 그를 위해 풍부한 빠른)이며, 메모리 .
현재 data.table 인터페이스를 사용하면 참조로 열을 수정 / 업데이트 할 수 있습니다 (결과를 변수에 다시 할당 할 필요가 없음).
# sub-assign by reference, updates 'y' in-place DT[x >= 1L, y := NA]
그러나 dplyr 은 참조로 업데이트 하지 않습니다 . dplyr에 해당하는 값은 다음과 같습니다 (결과를 다시 할당해야 함).
# copies the entire 'y' column ans <- DF %>% mutate(y = replace(y, which(x >= 1L), NA))
이에 대한 우려는 참조 투명성 입니다. 특히 함수 내에서 참조로 data.table 객체를 업데이트하는 것이 항상 바람직한 것은 아닙니다. 그러나 이것은 매우 유용한 기능입니다 : 볼 이 와 이 재미있는 경우에 대한 글. 그리고 우리는 그것을 유지하고 싶습니다.
따라서 우리는
shallow()
사용자에게 두 가지 가능성 을 모두 제공하는 data.table의 내보내기 기능을 위해 노력하고 있습니다. 예를 들어 함수 내에서 입력 data.table을 수정하지 않는 것이 바람직한 경우 다음을 수행 할 수 있습니다.foo <- function(DT) { DT = shallow(DT) ## shallow copy DT DT[, newcol := 1L] ## does not affect the original DT DT[x > 2L, newcol := 2L] ## no need to copy (internally), as this column exists only in shallow copied DT DT[x > 2L, x := 3L] ## have to copy (like base R / dplyr does always); otherwise original DT will ## also get modified. }
를 사용하지 않으면
shallow()
이전 기능이 유지됩니다.bar <- function(DT) { DT[, newcol := 1L] ## old behaviour, original DT gets updated by reference DT[x > 2L, x := 3L] ## old behaviour, update column x in original DT. }
를 사용하여 얕은 복사본 을 만들면
shallow()
원본 개체를 수정하지 않으려는 것을 이해합니다. 우리는 내부적으로 모든 것을 처리하여 절대적으로 필요한 경우에만 수정하는 열을 복사하도록 합니다 . 구현되면 사용자에게 두 가지 가능성을 모두 제공하면서 참조 투명성 문제를 모두 해결해야합니다 .또한
shallow()
dplyr의 data.table 인터페이스를 일단 내 보내면 거의 모든 사본을 피할 수 있습니다. 따라서 dplyr의 구문을 선호하는 사람들은 data.tables와 함께 사용할 수 있습니다.그러나 참조에 의한 (하위) 할당을 포함하여 data.table이 제공하는 많은 기능이 여전히 부족합니다.
가입하는 동안 집계 :
다음과 같이 두 개의 data.tables가 있다고 가정합니다.
DT1 = data.table(x=c(1,1,1,1,2,2,2,2), y=c("a", "a", "b", "b"), z=1:8, key=c("x", "y")) # x y z # 1: 1 a 1 # 2: 1 a 2 # 3: 1 b 3 # 4: 1 b 4 # 5: 2 a 5 # 6: 2 a 6 # 7: 2 b 7 # 8: 2 b 8 DT2 = data.table(x=1:2, y=c("a", "b"), mul=4:3, key=c("x", "y")) # x y mul # 1: 1 a 4 # 2: 2 b 3
그리고 열로 결합하는 동안
sum(z) * mul
각 행에 대해 얻고 싶습니다 . 다음 중 하나를 수행 할 수 있습니다.DT2
x,y
1) 집계
DT1
하여sum(z)
, 2) 조인 수행 및 3) 곱하기 (또는)# data.table way DT1[, .(z = sum(z)), keyby = .(x,y)][DT2][, z := z*mul][] # dplyr equivalent DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% right_join(DF2) %>% mutate(z = z * mul)
2) 한 번에 모두 수행 (
by = .EACHI
기능 사용 ) :DT1[DT2, list(z=sum(z) * mul), by = .EACHI]
장점은 무엇입니까?
중간 결과를 위해 메모리를 할당 할 필요가 없습니다.
두 번 그룹화 / 해시 할 필요가 없습니다 (하나는 집계 용이고 다른 하나는 결합 용).
그리고 더 중요한 것은
j
(2)를 보면 우리가 수행하고자하는 작업이 명확하다는 것 입니다.
에 대한 자세한 설명은 이 게시물 을 확인하십시오
by = .EACHI
. 중간 결과가 구체화되지 않으며 결합 + 집계가 한 번에 모두 수행됩니다.한 번 봐 가지고 이 , 이 및 이 실제 사용 시나리오에 대한 글.
에서
dplyr
먼저 조인하고 집계하거나 집계 한 다음 조인해야합니다 . 그 어느 것도 메모리 측면에서 효율적이지 않습니다 (즉, 속도로 변환 됨).업데이트 및 조인 :
아래 표시된 data.table 코드를 고려하십시오.
DT1[DT2, col := i.mul]
의 키 열이 일치 하는 행에서 from을 사용 하여
DT1
의 열col
을 추가 / 업데이트합니다 . 에서이 작업과 똑같은 작업이 있다고 생각하지 않습니다 . 즉, 새 열을 추가하기 위해 전체를 복사해야하는 작업 을 피하지 않고 는 필요하지 않습니다.mul
DT2
DT2
DT1
dplyr
*_join
DT1
실제 사용 시나리오는 이 게시물 을 확인하십시오 .
요약하자면 최적화의 모든 부분이 중요하다는 것을 인식하는 것이 중요합니다. 로 그레이스 호퍼 , 말을 당신의 나노초 마음 !
3. 구문
이제 구문을 살펴 보겠습니다 . Hadley는 여기에 언급 했습니다 .
데이터 테이블은 매우 빠른하지만 나는 그들의 간결 그것을하게 생각 열심히 배우고 그리고 당신이 그것을 쓴 후 읽기 어렵 코드를 사용하는 ...
나는이 말이 매우 주관적이기 때문에 무의미하다고 생각합니다. 우리가 시도 할 수있는 것은 구문의 일관성 을 대조하는 것 입니다. data.table과 dplyr 구문을 나란히 비교할 것입니다.
아래에 표시된 더미 데이터로 작업합니다.
DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)
기본 집계 / 업데이트 작업.
# case (a) DT[, sum(y), by = z] ## data.table syntax DF %>% group_by(z) %>% summarise(sum(y)) ## dplyr syntax DT[, y := cumsum(y), by = z] ans <- DF %>% group_by(z) %>% mutate(y = cumsum(y)) # case (b) DT[x > 2, sum(y), by = z] DF %>% filter(x>2) %>% group_by(z) %>% summarise(sum(y)) DT[x > 2, y := cumsum(y), by = z] ans <- DF %>% group_by(z) %>% mutate(y = replace(y, which(x > 2), cumsum(y))) # case (c) DT[, if(any(x > 5L)) y[1L]-y[2L] else y[2L], by = z] DF %>% group_by(z) %>% summarise(if (any(x > 5L)) y[1L] - y[2L] else y[2L]) DT[, if(any(x > 5L)) y[1L] - y[2L], by = z] DF %>% group_by(z) %>% filter(any(x > 5L)) %>% summarise(y[1L] - y[2L])
data.table 구문은 간결하고 dplyr은 매우 장황합니다. (a)의 경우 상황은 다소 동등합니다.
(b)의 경우, 요약
filter()
하면서 dplyr 에서 사용해야 했습니다 . 하지만 업데이트하는 동안 로직을 내부로 옮겨야했습니다 . 그러나 data.table에서 우리는 동일한 논리로 두 연산을 모두 표현합니다 . 첫 번째 경우에는 get 이고 두 번째 경우에는 누적 합계로 해당 행을 업데이트합니다 .mutate()
x > 2
sum(y)
y
이것이 우리가
DT[i, j, by]
형식 이 일관 적이 라고 말할 때 의미하는 바입니다 .마찬가지로 (c)의 경우에도
if-else
조건이있을 때 data.table과 dplyr 모두에서 논리를 "있는 그대로" 표현할 수 있습니다. 그러나if
조건이 충족 되는 행만 반환 하고 그렇지 않으면 건너 뛰려면summarise()
직접 (AFAICT) 사용할 수 없습니다 . 우리는에있는filter()
제 때문에 다음 요약summarise()
항상 기대하고 단일 값을 .동일한 결과를 반환하지만
filter()
여기를 사용하면 실제 작업이 덜 명확 해집니다.filter()
첫 번째 경우에도 사용하는 것이 가능할 수 있지만 (나에게는 분명하지 않은 것 같습니다), 내 요점은 우리가 그럴 필요가 없다는 것입니다.
여러 열에 대한 집계 / 업데이트
# case (a) DT[, lapply(.SD, sum), by = z] ## data.table syntax DF %>% group_by(z) %>% summarise_each(funs(sum)) ## dplyr syntax DT[, (cols) := lapply(.SD, sum), by = z] ans <- DF %>% group_by(z) %>% mutate_each(funs(sum)) # case (b) DT[, c(lapply(.SD, sum), lapply(.SD, mean)), by = z] DF %>% group_by(z) %>% summarise_each(funs(sum, mean)) # case (c) DT[, c(.N, lapply(.SD, sum)), by = z] DF %>% group_by(z) %>% summarise_each(funs(n(), mean))
(a)의 경우 코드는 다소 동등합니다. data.table 용도 익숙한 기본 기능
lapply()
하는 반면,dplyr
소개*_each()
하는 기능과 함께 무리funs()
.data.table
:=
은 열 이름을 제공해야하지만 dplyr은 자동으로 생성합니다.(b)의 경우 dplyr의 구문은 비교적 간단합니다. 여러 함수에 대한 집계 / 업데이트 개선은 data.table의 목록에 있습니다.
하지만 (c)의 경우 dplyr은
n()
한 번이 아닌 여러 열을 반환 합니다. data.table에서해야 할 일은 목록을j
. 목록의 각 요소는 결과에서 열이됩니다. 따라서 다시 한 번 익숙한 기본 함수c()
를 사용.N
하여 alist
를 반환하는 a 에 연결할 수list
있습니다.
참고 : 다시 한번 data.table에서해야 할 일은
j
. 목록의 각 요소는 결과의 열이됩니다. 당신이 사용할 수있는c()
,as.list()
,lapply()
,list()
새로운 기능을 학습 할 필요없이 등 ... 기본 기능은이 작업을 수행 할 수 있습니다.당신은 특별한 변수를 배울 필요가있을 것이다 -
.N
그리고.SD
적어도. dplyr에 해당하는 것은 다음n()
과 같습니다..
조인
dplyr은 data.table이 동일한 구문
DT[i, j, by]
(그리고 이유 포함)을 사용하여 조인을 허용하는 경우 각 조인 유형에 대해 별도의 함수를 제공합니다 . 또한merge.data.table()
대안으로 동등한 기능을 제공합니다 .setkey(DT1, x, y) # 1. normal join DT1[DT2] ## data.table syntax left_join(DT2, DT1) ## dplyr syntax # 2. select columns while join DT1[DT2, .(z, i.mul)] left_join(select(DT2, x, y, mul), select(DT1, x, y, z)) # 3. aggregate while join DT1[DT2, .(sum(z) * i.mul), by = .EACHI] DF1 %>% group_by(x, y) %>% summarise(z = sum(z)) %>% inner_join(DF2) %>% mutate(z = z*mul) %>% select(-mul) # 4. update while join DT1[DT2, z := cumsum(z) * i.mul, by = .EACHI] ?? # 5. rolling join DT1[DT2, roll = -Inf] ?? # 6. other arguments to control output DT1[DT2, mult = "first"] ??
일부는 각 조인에 대해 훨씬 더 좋은 (left, right, inner, anti, semi 등) 별도의 함수를 찾을 수있는 반면, 다른 사람들은 data.table 's를 좋아
DT[i, j, by]
하거나merge()
base R과 유사한 것을 찾을 수 있습니다.그러나 dplyr 조인은 그렇게합니다. 더 이상은 없습니다. 그 이하도 아닙니다.
data.tables는 조인하는 동안 열을 선택할 수 있으며 (2) dplyr에서는 위와
select()
같이 조인하기 전에 먼저 두 data.frames에서 먼저 수행 해야합니다 . 그렇지 않으면 나중에 제거하기 위해서만 불필요한 열로 조인을 구체화 할 수 있으며 이는 비효율적입니다.data.tables는 기능을 사용하여 결합하는 동안 (3) 집계 할 수 있으며 결합 하는 동안 (4) 업데이트 할 수도
by = .EACHI
있습니다. 몇 개의 열만 추가 / 업데이트하기 위해 전체 결합 결과를 재료로 사용하는 이유는 무엇입니까?data.table은 롤 포워드, LOCF , 롤백, NOCB , 가장 가까운 조인 (5) 을 롤링 할 수 있습니다.
data.table에는 첫 번째 , 마지막 또는 모든 일치 항목 (6)
mult =
을 선택하는 인수 도 있습니다 .data.table에는
allow.cartesian = TRUE
실수로 잘못된 조인을 방지하는 인수가 있습니다.
다시 한 번, 구문은
DT[i, j, by]
출력을 더 제어 할 수 있도록 추가 인수와 일치 합니다.
do()
...dplyr의 요약은 단일 값을 반환하는 함수를 위해 특별히 설계되었습니다. 함수가 여러 / 같지 않은 값을 반환하는 경우
do()
. 모든 함수 반환 값에 대해 미리 알아야합니다.DT[, list(x[1], y[1]), by = z] ## data.table syntax DF %>% group_by(z) %>% summarise(x[1], y[1]) ## dplyr syntax DT[, list(x[1:2], y[1]), by = z] DF %>% group_by(z) %>% do(data.frame(.$x[1:2], .$y[1])) DT[, quantile(x, 0.25), by = z] DF %>% group_by(z) %>% summarise(quantile(x, 0.25)) DT[, quantile(x, c(0.25, 0.75)), by = z] DF %>% group_by(z) %>% do(data.frame(quantile(.$x, c(0.25, 0.75)))) DT[, as.list(summary(x)), by = z] DF %>% group_by(z) %>% do(data.frame(as.list(summary(.$x))))
.SD
의 동등한 것은.
In data.table, you can throw pretty much anything in
j
- the only thing to remember is for it to return a list so that each element of the list gets converted to a column.In dplyr, cannot do that. Have to resort to
do()
depending on how sure you are as to whether your function would always return a single value. And it is quite slow.
Once again, data.table's syntax is consistent with
DT[i, j, by]
. We can just keep throwing expressions inj
without having to worry about these things.
Have a look at this SO question and this one. I wonder if it would be possible to express the answer as straightforward using dplyr's syntax...
To summarise, I have particularly highlighted several instances where dplyr's syntax is either inefficient, limited or fails to make operations straightforward. This is particularly because data.table gets quite a bit of backlash about "harder to read/learn" syntax (like the one pasted/linked above). Most posts that cover dplyr talk about most straightforward operations. And that is great. But it is important to realise its syntax and feature limitations as well, and I am yet to see a post on it.
data.table has its quirks as well (some of which I have pointed out that we are attempting to fix). We are also attempting to improve data.table's joins as I have highlighted here.
But one should also consider the number of features that dplyr lacks in comparison to data.table.
4. Features
I have pointed out most of the features here and also in this post. In addition:
fread - fast file reader has been available for a long time now.
fwrite - a parallelised fast file writer is now available. See this post for a detailed explanation on the implementation and #1664 for keeping track of further developments.
Automatic indexing - another handy feature to optimise base R syntax as is, internally.
Ad-hoc grouping:
dplyr
automatically sorts the results by grouping variables duringsummarise()
, which may not be always desirable.Numerous advantages in data.table joins (for speed / memory efficiency and syntax) mentioned above.
Non-equi joins: Allows joins using other operators
<=, <, >, >=
along with all other advantages of data.table joins.Overlapping range joins was implemented in data.table recently. Check this post for an overview with benchmarks.
setorder()
function in data.table that allows really fast reordering of data.tables by reference.dplyr provides interface to databases using the same syntax, which data.table does not at the moment.
data.table
provides faster equivalents of set operations (written by Jan Gorecki) -fsetdiff
,fintersect
,funion
andfsetequal
with additionalall
argument (as in SQL).data.table loads cleanly with no masking warnings and has a mechanism described here for
[.data.frame
compatibility when passed to any R package. dplyr changes base functionsfilter
,lag
and[
which can cause problems; e.g. here and here.
Finally:
On databases - there is no reason why data.table cannot provide similar interface, but this is not a priority now. It might get bumped up if users would very much like that feature.. not sure.
On parallelism - Everything is difficult, until someone goes ahead and does it. Of course it will take effort (being thread safe).
- Progress is being made currently (in v1.9.7 devel) towards parallelising known time consuming parts for incremental performance gains using
OpenMP
.
- Progress is being made currently (in v1.9.7 devel) towards parallelising known time consuming parts for incremental performance gains using
Here's my attempt at a comprehensive answer from the dplyr perspective, following the broad outline of Arun's answer (but somewhat rearranged based on differing priorities).
Syntax
There is some subjectivity to syntax, but I stand by my statement that the concision of data.table makes it harder to learn and harder to read. This is partly because dplyr is solving a much easier problem!
One really important thing that dplyr does for you is that it constrains your options. I claim that most single table problems can be solved with just five key verbs filter, select, mutate, arrange and summarise, along with a "by group" adverb. That constraint is a big help when you're learning data manipulation, because it helps order your thinking about the problem. In dplyr, each of these verbs is mapped to a single function. Each function does one job, and is easy to understand in isolation.
You create complexity by piping these simple operations together with %>%
. Here's an example from one of the posts Arun linked to:
diamonds %>%
filter(cut != "Fair") %>%
group_by(cut) %>%
summarize(
AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = n()
) %>%
arrange(desc(Count))
Even if you've never seen dplyr before (or even R!), you can still get the gist of what's happening because the functions are all English verbs. The disadvantage of English verbs is that they require more typing than [
, but I think that can be largely mitigated by better autocomplete.
Here's the equivalent data.table code:
diamondsDT <- data.table(diamonds)
diamondsDT[
cut != "Fair",
.(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N
),
by = cut
][
order(-Count)
]
It's harder to follow this code unless you're already familiar with data.table. (I also couldn't figure out how to indent the repeated [
in a way that looks good to my eye). Personally, when I look at code I wrote 6 months ago, it's like looking at a code written by a stranger, so I've come to prefer straightforward, if verbose, code.
Two other minor factors that I think slightly decrease readability:
Since almost every data table operation uses
[
you need additional context to figure out what's happening. For example, isx[y]
joining two data tables or extracting columns from a data frame? This is only a small issue, because in well-written code the variable names should suggest what's happening.I like that
group_by()
is a separate operation in dplyr. It fundamentally changes the computation so I think should be obvious when skimming the code, and it's easier to spotgroup_by()
than theby
argument to[.data.table
.
I also like that the the pipe isn't just limited to just one package. You can start by tidying your data with tidyr, and finish up with a plot in ggvis. And you're not limited to the packages that I write - anyone can write a function that forms a seamless part of a data manipulation pipe. In fact, I rather prefer the previous data.table code rewritten with %>%
:
diamonds %>%
data.table() %>%
.[cut != "Fair",
.(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N
),
by = cut
] %>%
.[order(-Count)]
And the idea of piping with %>%
is not limited to just data frames and is easily generalised to other contexts: interactive web graphics, web scraping, gists, run-time contracts, ...)
Memory and performance
I've lumped these together, because, to me, they're not that important. Most R users work with well under 1 million rows of data, and dplyr is sufficiently fast enough for that size of data that you're not aware of processing time. We optimise dplyr for expressiveness on medium data; feel free to use data.table for raw speed on bigger data.
The flexibility of dplyr also means that you can easily tweak performance characteristics using the same syntax. If the performance of dplyr with the data frame backend is not good enough for you, you can use the data.table backend (albeit with a somewhat restricted set of functionality). If the data you're working with doesn't fit in memory, then you can use a database backend.
All that said, dplyr performance will get better in the long-term. We'll definitely implement some of the great ideas of data.table like radix ordering and using the same index for joins & filters. We're also working on parallelisation so we can take advantage of multiple cores.
Features
A few things that we're planning to work on in 2015:
the
readr
package, to make it easy to get files off disk and in to memory, analogous tofread()
.More flexible joins, including support for non-equi-joins.
More flexible grouping like bootstrap samples, rollups and more
I'm also investing time into improving R's database connectors, the ability to talk to web apis, and making it easier to scrape html pages.
In direct response to the Question Title...
dplyr
definitely does things that data.table
can not.
Your point #3
dplyr abstracts (or will) potential DB interactions
is a direct answer to your own question but isn't elevated to a high enough level. dplyr
is truly an extendable front-end to multiple data storage mechanisms where as data.table
is an extension to a single one.
Look at dplyr
as a back-end agnostic interface, with all of the targets using the same grammer, where you can extend the targets and handlers at will. data.table
is, from the dplyr
perspective, one of those targets.
You will never (I hope) see a day that data.table
attempts to translate your queries to create SQL statements that operate with on-disk or networked data stores.
dplyr
can possibly do things data.table
will not or might not do as well.
Based on the design of working in-memory, data.table
could have a much more difficult time extending itself into parallel processing of queries than dplyr
.
In response to the in-body questions...
Usage
Are there analytical tasks that are a lot easier to code with one or the other package for people familiar with the packages (i.e. some combination of keystrokes required vs. required level of esotericism, where less of each is a good thing).
This may seem like a punt but the real answer is no. People familiar with tools seem to use the either the one most familiar to them or the one that is actually the right one for the job at hand. With that being said, sometimes you want to present a particular readability, sometimes a level of performance, and when you have need for a high enough level of both you may just need another tool to go along with what you already have to make clearer abstractions.
Performance
Are there analytical tasks that are performed substantially (i.e. more than 2x) more efficiently in one package vs. another.
Again, no. data.table
excels at being efficient in everything it does where dplyr
gets the burden of being limited in some respects to the underlying data store and registered handlers.
This means when you run into a performance issue with data.table
you can be pretty sure it is in your query function and if it is actually a bottleneck with data.table
then you've won yourself the joy of filing a report. This is also true when dplyr
is using data.table
as the back-end; you may see some overhead from dplyr
but odds are it is your query.
When dplyr
has performance issues with back-ends you can get around them by registering a function for hybrid evaluation or (in the case of databases) manipulating the generated query prior to execution.
Also see the accepted answer to when is plyr better than data.table?
'IT박스' 카테고리의 다른 글
모나드는 endofunctor 범주의 모노 이드 일뿐입니다. 무엇이 문제입니까? (0) | 2020.09.30 |
---|---|
Chrome의 CSS 사용자 정의 스타일 버튼에서 파란색 테두리 제거 (0) | 2020.09.30 |
SQL Server : 첫 번째 행에 조인하는 방법 (0) | 2020.09.30 |
JavaScript Date 객체에 30 분을 추가하는 방법은 무엇입니까? (0) | 2020.09.30 |
__init__.py를 사용해도“패키지가 아닌 상대 가져 오기 시도”를 수정하는 방법 (0) | 2020.09.30 |