datatable 위에 dplyr 구문을 사용하면 dplyr 구문을 사용 하면서 datatable의 모든 속도 이점을 얻을 수 있습니까? 즉, dplyr 구문으로 쿼리하면 데이터 테이블을 잘못 사용합니까? 아니면 순수한 데이터 테이블 구문을 사용하여 모든 기능을 활용해야합니까?
조언에 미리 감사드립니다. 코드 예 :
library(data.table)
library(dplyr)
diamondsDT <- data.table(ggplot2::diamonds)
setkey(diamondsDT, cut)
diamondsDT %>%
filter(cut != "Fair") %>%
group_by(cut) %>%
summarize(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = n()) %>%
arrange(desc(Count))
결과 :
# cut AvgPrice MedianPrice Count
# 1 Ideal 3457.542 1810.0 21551
# 2 Premium 4584.258 3185.0 13791
# 3 Very Good 3981.760 2648.0 12082
# 4 Good 3928.864 3050.5 4906
여기에 내가 생각 해낸 데이터 테이블 동등성이 있습니다. DT 우수 사례를 준수하는지 확실하지 않습니다. 그러나 코드가 실제로 dplyr 구문보다 더 효율적인지 궁금합니다.
diamondsDT [cut != "Fair"
] [, .(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N), by=cut
] [ order(-Count) ]
답변
이 두 패키지의 철학이 특정 측면에서 다르기 때문에 간단하고 간단한 대답은 없습니다. 따라서 일부 타협은 피할 수 없습니다. 다음은 해결 / 고려해야 할 몇 가지 우려 사항입니다.
i
(== filter()
및 slice()
dplyr) 관련 작업
DT
10 개의 열을 가정합니다 . 다음 data.table 표현식을 고려하십시오.
DT[a > 1, .N] ## --- (1)
DT[a > 1, mean(b), by=.(c, d)] ## --- (2)
(1)의 행수 제공 DT
여기서 열 a > 1
. (2)는 (1)과 동일한 표현식에 대해 mean(b)
그룹화를 반환합니다 .c,d
i
일반적으로 사용되는 dplyr
표현은 다음과 같습니다.
DT %>% filter(a > 1) %>% summarise(n()) ## --- (3)
DT %>% filter(a > 1) %>% group_by(c, d) %>% summarise(mean(b)) ## --- (4)
분명히 data.table 코드는 더 짧습니다. 또한 메모리 효율성이 더 높습니다 1 . 왜? (3)과 (4) 모두 10 개 열 모두에 대한 행을 먼저 filter()
반환 하기 때문에 (3) 에서는 행 수만 필요하고 (4) 에서는 연속 작업을 위해 열만 필요 합니다. 이것을 극복하기 위해, 우리는 apriori 열 이 필요합니다 :b, c, d
select()
DT %>% select(a) %>% filter(a > 1) %>% summarise(n()) ## --- (5)
DT %>% select(a,b,c,d) %>% filter(a > 1) %>% group_by(c,d) %>% summarise(mean(b)) ## --- (6)
두 패키지 간의 주요 철학적 차이점을 강조하는 것이 중요합니다.
에서
data.table
, 우리는 함께이 관련 작업을 계속, 그리고 그것은 볼 수 있습니다j-expression
(같은 함수 호출에서) 및 (1)의 모든 열에 대한 필요가 없습니다 실현. 의 표현식i
이 계산되고.N
행 수를 제공하는 논리 벡터의 합계입니다. 전체 하위 집합은 결코 실현되지 않습니다. (2)에서는b,c,d
열만 서브 세트에서 구체화되고 다른 열은 무시됩니다.그러나에서
dplyr
철학은 기능이 정확히 한 가지 일을 잘하도록하는 것 입니다. (적어도 현재) 작업에filter()
필터링 한 모든 열이 필요한지 여부를 알 수있는 방법이 없습니다 . 이러한 작업을 효율적으로 수행하려면 미리 생각해야합니다. 저는 개인적으로이 경우 반 감각적이라고 생각합니다.
(5)와 (6)에서는 여전히 a
필요하지 않은 열의 하위 집합을 제공 합니다. 그러나 나는 그것을 피하는 방법을 잘 모르겠습니다. 경우 filter()
함수가 반환하는 열을 선택하는 인수했다, 우리는이 문제를 방지 할 수 있지만 그 기능은 (또한 dplyr 디자인 선택 인) 한 작업을하지 않습니다.
참조로 하위 할당
dplyr은 참조로 업데이트 하지 않습니다 . 이것은 두 패키지 간의 또 다른 큰 (철학적) 차이점입니다.
예를 들어 data.table에서 다음을 수행 할 수 있습니다.
DT[a %in% some_vals, a := NA]
조건을 충족하는 행만 a
참조로 열을 업데이트 합니다. 현재 dplyr은 전체 data.table을 내부적으로 복사하여 새 열을 추가합니다. @BrodieG는 이미 그의 답변에서 이것을 언급했습니다.
그러나 FR # 617 이 구현 되면 깊은 복사를 얕은 복사로 대체 할 수 있습니다 . 관련 항목 : dplyr : FR # 614 . 그래도 수정 한 열은 항상 복사됩니다 (따라서 약간 느리거나 메모리 효율성이 떨어짐). 참조로 열을 업데이트하는 방법은 없습니다.
기타 기능
-
data.table에서 조인하는 동안 집계 할 수 있으며 중간 조인 결과가 결코 구체화되지 않으므로 이해하기가 더 쉽고 메모리 효율적입니다. 이 게시물 에서 예를 확인하십시오 . dplyr의 data.table / data.frame 구문을 사용하여 (현재는?) 그렇게 할 수 없습니다.
-
data.table의 롤링 조인 기능은 dplyr의 구문에서도 지원되지 않습니다.
-
우리는 최근에 data.table에 중첩 조인을 구현하여 간격 범위 ( 예제 ) 에 대해 조인했습니다.이 조인 은 현재 별도의 함수
foverlaps()
이므로 파이프 연산자 (magrittr / pipeR?-직접 시도한 적이 없음)와 함께 사용할 수 있습니다.그러나 궁극적으로 우리의 목표는
[.data.table
위에서 설명한 것과 동일한 제한 사항이 적용되는 그룹화, 결합하는 동안 집계 등과 같은 다른 기능을 수집 할 수 있도록 통합 하는 것입니다. -
1.9.4부터 data.table은 일반 R 구문에서 빠른 이진 검색 기반 하위 집합을 위해 보조 키를 사용하여 자동 인덱싱을 구현합니다. 예 :
DT[x == 1]
그리고DT[x %in% some_vals]
첫 번째 실행에서 자동으로 인덱스를 생성 한 다음 이진 검색을 사용하여 동일한 열의 연속 하위 집합에서 빠른 하위 집합에 사용됩니다. 이 기능은 계속 발전 할 것입니다. 이 기능에 대한 간략한 개요를 보려면 이 요점 을 확인하십시오 .filter()
data.tables에 대해 구현 되는 방식 에서이 기능을 활용하지 않습니다. -
dplyr 기능은 현재 data.table이 제공 하지 않는 동일한 구문을 사용하여 데이터베이스에 대한 인터페이스를 제공한다는 것 입니다.
따라서 이러한 (그리고 아마도 다른 요점)을 고려하여 이러한 트레이드 오프가 허용되는지 여부를 결정해야합니다.
HTH
(1) 대부분의 경우 병목 현상은 주 메모리에서 캐시로 데이터를 이동하고 가능한 한 캐시의 데이터를 사용하여 캐시 미스를 줄이는 것이므로 메모리 효율성은 속도에 직접적인 영향을 미칩니다 (특히 데이터가 커질수록). -주 메모리 액세스를 줄이기 위해). 여기서는 자세히 설명하지 않습니다.
답변
그냥 시도 해 봐.
library(rbenchmark)
library(dplyr)
library(data.table)
benchmark(
dplyr = diamondsDT %>%
filter(cut != "Fair") %>%
group_by(cut) %>%
summarize(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = n()) %>%
arrange(desc(Count)),
data.table = diamondsDT[cut != "Fair",
list(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N), by = cut][order(-Count)])[1:4]
이 문제에서 data.table은 data.table을 사용하는 dplyr보다 2.4 배 빠릅니다.
test replications elapsed relative
2 data.table 100 2.39 1.000
1 dplyr 100 5.77 2.414
Polymerase의 의견에 따라 수정 되었습니다.
답변
질문에 답하려면 :
- 예, 사용하고 있습니다.
data.table
- 그러나 순수
data.table
구문을 사용하는 것만 큼 효율적이지 않습니다.
대부분의 경우 이것은 dplyr
구문을 원하는 사람들에게 허용 가능한 타협이 될 수 있지만 dplyr
일반 데이터 프레임 보다 느릴 수 있습니다 .
한 가지 큰 요인은 그룹화 할 때 기본적으로 dplyr
복사 된다는 것 data.table
입니다. 고려 사항 (microbenchmark 사용) :
Unit: microseconds
expr min lq median
diamondsDT[, mean(price), by = cut] 3395.753 4039.5700 4543.594
diamondsDT[cut != "Fair"] 12315.943 15460.1055 16383.738
diamondsDT %>% group_by(cut) %>% summarize(AvgPrice = mean(price)) 9210.670 11486.7530 12994.073
diamondsDT %>% filter(cut != "Fair") 13003.878 15897.5310 17032.609
필터링 속도는 비슷하지만 그룹화는 그렇지 않습니다. 나는 범인이 다음 라인이라고 믿는다 dplyr:::grouped_dt
.
if (copy) {
data <- data.table::copy(data)
}
여기서 copy
기본값은 TRUE
(그리고 내가 볼 수있는 FALSE로 쉽게 변경할 수 없습니다). 이것은 차이의 100 %를 설명하지 않을 가능성이 높지만, 크기가 diamonds
가장 큰 것에 대한 일반적인 오버 헤드만으로는 완전한 차이가 아닙니다.
문제는 일관된 문법을 갖기 위해 dplyr
두 단계로 그룹화를 수행한다는 것입니다. 먼저 그룹과 일치하는 원본 데이터 테이블의 복사본에 키를 설정하고 나중에 그룹화합니다. data.table
가장 큰 결과 그룹에 메모리를 할당합니다.이 경우에는 한 행에 불과하므로 할당해야하는 메모리 양에 큰 차이가 있습니다.
참고로, 누군가 관심이 있다면 출력을 위해 실험적인 (그리고 여전히 매우 많은 알파) 트리 뷰어 인 treeprof
( install_github("brodieg/treeprof")
) 를 사용하여 이것을 발견했습니다 Rprof
.
위의 내용은 현재 Mac AFAIK에서만 작동합니다. 또한 불행히도 Rprof
유형의 호출을 packagename::funname
익명으로 기록 하므로 실제로 책임이있는 모든 datatable::
호출이 될 수 grouped_dt
있지만 빠른 테스트 datatable::copy
에서는 큰 것으로 보입니다 .
즉, [.data.table
호출 주위에 그다지 많은 오버 헤드가 없는지 빠르게 확인할 수 있지만 그룹화를위한 완전히 별도의 분기도 있습니다.
편집 : 복사를 확인하려면 :
> tracemem(diamondsDT)
[1] "<0x000000002747e348>"
> diamondsDT %>% group_by(cut) %>% summarize(AvgPrice = mean(price))
tracemem[0x000000002747e348 -> 0x000000002a624bc0]: <Anonymous> grouped_dt group_by_.data.table group_by_ group_by <Anonymous> freduce _fseq eval eval withVisible %>%
Source: local data table [5 x 2]
cut AvgPrice
1 Fair 4358.758
2 Good 3928.864
3 Very Good 3981.760
4 Premium 4584.258
5 Ideal 3457.542
> diamondsDT[, mean(price), by = cut]
cut V1
1: Ideal 3457.542
2: Premium 4584.258
3: Good 3928.864
4: Very Good 3981.760
5: Fair 4358.758
> untracemem(diamondsDT)
답변
당신은 사용할 수 있습니다 dtplyr 의 일부입니다, 지금 tidyverse . 평상시처럼 dplyr 스타일 문을 사용할 수 있지만 지연 평가를 활용하고 문을 내부에서 data.table 코드로 변환합니다. 번역의 오버 헤드는 최소화되지만, 그렇지 않은 경우 data.table의 대부분의 이점을 모두 얻을 수 있습니다. 자세한 내용은 공식 git repo here 및 tidyverse 페이지를 참조하십시오 .