data.table의 dplyr, 실제로 data.table을 사용하고 있습니까?


91

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) ]

7
데이터 테이블 구문을 사용하지 않는 이유는 무엇입니까? 우아하고 효율적이기도합니다. 이 질문은 매우 광범위하기 때문에 실제로 대답 할 수 없습니다. 네, 거기에 dplyr데이터 테이블에 대한 방법이 있지만, 데이터 테이블은 또한 자신의 비교 방법이있다
리치 Scriven에

7
데이터 테이블 구문 또는 코스를 사용할 수 있습니다. 그러나 어떻게 든 dplyr 구문이 더 우아하다고 생각합니다. 구문에 대한 선호도에 관계없이. 내가 정말로 알고 싶은 것은 : 데이터 테이블 파워의 100 % 이점을 얻기 위해 순수한 데이터 테이블 구문을 사용해야합니까?
Polymerase 2014

3
s 및 해당 dplyr에 사용되는 최근 벤치 마크 는 여기 (및 그 안의 참조)를 참조 하십시오 . data.framedata.table
Henrik

2
@Polymerase - 나는 그 질문에 대한 대답은 "예"확실히 생각
리치 Scriven에

1
@Henrik : 나중에 데이터 프레임 구성에 대한 코드 만 표시하고 data.table 구성에 사용한 코드는 표시하지 않았기 때문에 해당 페이지를 잘못 해석했음을 나중에 깨달았습니다. 내가 그것을 깨달았을 때, 나는 내 댓글을 삭제했습니다 (당신이 그것을 보지 못했기를 바라며).
IRTFM

답변:


77

이 두 패키지의 철학이 특정 측면에서 다르기 때문에 간단하고 간단한 대답은 없습니다. 따라서 일부 타협은 피할 수 없습니다. 다음은 해결 / 고려해야 할 몇 가지 우려 사항입니다.

i(== filter()slice()dplyr) 관련 작업

DT10 개의 열을 가정합니다 . 다음 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,di

일반적으로 사용되는 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, dselect()

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) 대부분의 경우 병목 현상은 주 메모리에서 캐시로 데이터를 이동하고 가능한 한 캐시의 데이터를 사용하여 캐시 미스를 줄이는 것이므로 메모리 효율성은 속도에 직접적인 영향을 미칩니다 (특히 데이터가 커질수록). -주 메모리 액세스를 줄이기 위해). 여기서는 자세히 설명하지 않습니다.


4
절대적으로 훌륭합니다. 덕분에 그
데이비드 Arenburg

6
좋은 대답이지만 dplyr가 SQL에 사용하는 것과 동일한 접근 방식을 사용하여 효율적인 플러스 를 구현할 수 있습니다 ( 예 : 식을 구축 한 다음 요청에 따라 한 번만 실행). dplyr은 나에게 충분히 빠르며 쿼리 플래너 / 옵티 마이저를 구현하는 것이 상대적으로 어렵 기 때문에 가까운 장래에 구현 될 가능성은 낮습니다. filter()summarise()
해들리

메모리 효율성은 또 다른 중요한 영역에서 도움이됩니다. 실제로 메모리가 부족하기 전에 작업을 완료합니다. 대규모 데이터 세트로 작업 할 때 dplyr 및 pandas에서 그 문제에 직면했지만 data.table은 작업을 정상적으로 완료합니다.
Zaki

25

그냥 시도 해 봐.

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의 의견에 따라 수정 되었습니다.


2
microbenchmark패키지를 사용하여 dplyr원본 (데이터 프레임) 버전 에서 OP의 코드 를 실행하는 diamonds데 0.012 초의 중앙값이 걸리고 diamonds데이터 테이블 로 변환 한 후 0.024 초의 중앙값이 걸린다는 것을 발견했습니다. G. Grothendieck의 data.table코드를 실행하는 데 0.013 초가 걸렸습니다. 내 시스템에 적어도, 그것은 모양 dplyrdata.table동일한 성능에 대한 있습니다. 그러나 dplyr데이터 프레임이 처음 데이터 테이블로 변환 될 때 왜 더 느려질까요?
eipi10 2014

G. Grothendieck에게, 이것은 훌륭합니다. 이 벤치 마크 유틸리티를 보여 주셔서 감사합니다. BTW 당신은 ​​dplyr의 array (desc (Count))와 동등하게 만들기 위해 datatable 버전에서 [order (-Count)]를 잊었습니다. 이것을 추가 한 후에도 datatable은 약 x1.8 (2.9 대신) 더 빠릅니다.
Polymerase

@ eipi10 여기에 데이터 테이블 버전으로 벤치를 다시 실행할 수 있습니다 (마지막 단계에서 desc Count로 정렬 추가됨) : diamondsDT [cut! = "Fair", list (AvgPrice = mean (price), MedianPrice = as.numeric (median) (price)), Count = .N), by = cut] [order (-Count)]
Polymerase

여전히 0.013 초. 정렬 작업은 4 개의 행만있는 최종 테이블의 순서를 변경하기 때문에 시간이 거의 걸리지 않습니다.
eipi10

1
dplyr 구문에서 데이터 테이블 구문으로 변환하는 데 약간의 고정 된 오버 헤드가 있으므로 다양한 문제 크기를 시도해 볼 가치가 있습니다. 또한 dplyr에서 가장 효율적인 데이터 테이블 코드를 구현하지 않았을 수 있습니다. 패치는 항상 환영합니다
hadley

22

질문에 답하려면 :

  • 예, 사용하고 있습니다. 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)

굉장합니다, 감사합니다. 즉, dplyr :: group_by ()가 내부 데이터 복사 단계로 인해 메모리 요구 사항 (순수 데이터 테이블 구문과 비교하여)을 두 배로 늘릴 것입니까? 내 데이터 테이블 객체 크기가 1GB이고 원래 게시물의 것과 유사한 dplyr 체인 구문을 사용하는 경우 의미합니다. 결과를 얻으려면 최소 2GB의 여유 메모리가 필요합니까?
Polymerase

2
개발자 버전에서 수정 한 것 같나요?
해들리

@hadley, 나는 CRAN 버전에서 일하고 있었다. dev를 보면 부분적으로 문제를 해결 한 것처럼 보이지만 실제 사본은 남아 있습니다 (테스트되지 않았으며 R / grouped-dt.r의 c (20, 30:32) 행만 보았습니다. 지금은 더 빠를 것입니다. 나는 느린 단계는 복사 내기.
BrodieG

3
또한 data.table에서 얕은 복사 기능을 기다리고 있습니다. 그 전까지는 빠른 것보다 안전한 것이 낫다고 생각합니다.
hadley

2

당신은 사용할 수 있습니다 dtplyr 의 일부입니다, 지금 tidyverse . 평상시처럼 dplyr 스타일 문을 사용할 수 있지만 지연 평가를 활용하고 문을 내부에서 data.table 코드로 변환합니다. 번역의 오버 헤드는 최소화되지만, 그렇지 않은 경우 data.table의 대부분의 이점을 모두 얻을 수 있습니다. 자세한 내용은 공식 git repo here 및 tidyverse 페이지를 참조하십시오 .

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