data.table vs dplyr : 다른 사람이 할 수없는 일이나 잘 할 수없는 일이 있습니까?


758

개요

나는에 익숙 data.table하지 않고 비교적 익숙하다 dplyr. 나는 SO에 나타난 몇 가지 dplyr비네팅 과 예제를 읽었 으며 지금까지 나의 결론은 다음과 같습니다.

  1. data.tabledplyr속도 비교입니다,이 많은 (예> -10 만) 그룹, 그리고 몇 가지 다른 상황에서 (아래 벤치 마크를 참조) 경우를 제외하고
  2. dplyr 더 접근하기 쉬운 구문이 있습니다
  3. dplyr 잠재적 인 DB 상호 작용을 추상화 (또는 의지)
  4. 약간의 기능 차이가 있습니다 (아래의 "예 / 사용"참조).

내 마음에 2. 나는 그것에 상당히 익숙하기 때문에 많은 무게를 견디지 data.table못하지만, 두 사용자 모두에게 새로운 사용자에게는 큰 요인이 될 것임을 이해합니다. 나는 이미 익숙한 누군가의 관점에서 질문 한 특정 질문과 관련이 없기 때문에 더 직관적 인 주장을 피하고 싶습니다 data.table. 또한 "보다 직관적 인"방법으로 빠른 분석으로 이어지는 방법에 대한 논의를 피하고 싶습니다 (확실히 사실이지만 여기서 가장 관심있는 것은 아닙니다).

질문

내가 알고 싶은 것은 :

  1. 패키지에 익숙한 사람들을 위해 하나 또는 다른 패키지로 코딩하기가 훨씬 쉬운 분석 작업이 있습니까?
  2. 한 패키지에서 다른 패키지로 실질적으로 (즉, 2 배 이상)보다 효율적으로 수행되는 분석 작업이 있습니까?

하나 개는 최근 SO 질문은 나 때문에 내가 생각하지 않았다 그 시점이 될 때까지까지, 좀 더 이것에 대해 생각하고 있어요 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방법이 실제로 더 간결하거나 실질적으로 더 우수하다는 것입니다.

용법
  • dplyreddi의 질문 에서 임의의 수의 행을 반환하는 그룹화 된 작업을 허용하지 않습니다 ( 참고 : 이것은 dplyr 0.5 에서 구현되는 것처럼 보이며 @beginneR은 do@eddi의 질문에 대한 답변에 사용 가능한 잠재적 해결 방법을 보여줍니다 ).
  • data.table지원 압연 조인 아니라 (감사 @dholstius)로 중복 조인
  • data.table동일한 기본 R 구문을 사용하는 동안 이진 검색 을 사용하는 자동 색인 작성 을 통해 양식의 표현 DT[col == value]또는 속도에DT[col %in% values] 대해 내부적으로 최적화 합니다. 자세한 내용과 작은 벤치 마크는 여기참조하십시오 .
  • dplyr이벤트 기능 (예를 들어, 표준 평가 버전 regroup, summarize_each_) 그의 프로그램 사용을 단순화 할 수 있습니다 dplyr(주목 프로그램을 사용 data.table, 좀 신중하게 생각을 확실히 가능 필요합니다, 대체 / 인용 등, 내 지식에 적어도)
벤치 마크

데이터

이것은 질문 섹션에서 보여준 첫 번째 예입니다.

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

9
dplyr하나 를 읽는 것과 비슷한 해결책 은 다음과 같습니다.as.data.table(dat)[, .SD[job != "Boss" | year == min(year)][, cumjob := cumsum(job2)], by = list(name, job)]
eddi

7
# 1 dplyrdata.table팀 모두 벤치 마크 작업을하고 있으므로 어느 시점에 답이있을 것입니다. # 2 (구문) imO는 엄격하게 허위이지만, 그것은 분명히 의견 영역으로 모험을하기 때문에, 나는 또한 종결하기로 투표하고 있습니다.
eddi

13
다시 한 번 더 imO, 더 명확하게 표현 된 일련의 문제 (d)plyr는 0을 측정했습니다
eddi

28
@BrodieG 모두 정말 버그 날 그 한 가지 dplyrplyr구문에 관해서 기본적으로 내가 그들의 구문을 싫어하는 주된 이유이며, 내가 너무 많은 방법 이름으로 추가 기능 ((1 개 이상 읽기를) 배울 필요가 있다는 것입니다 그 여전히 이해가 안 돼요), 그들이하는 일, 그들이 취한 주장 등을 기억하십시오. 그것은 항상 plyr-philosophy에서 저에게 큰 도움이되었습니다.
eddi

43
@eddi [tongue-in-cheek] data.table 구문에 대해 정말로 나를 괴롭히는 것 중 하나는 너무 많은 함수 인수가 상호 작용하는 방식과 암호 단축키가 무엇을 의미하는지 알아야한다는 것입니다 (예 :) .SD. [
진실

답변:


532

우리는 이러한 측면은 (중요성의 특별한 순서없이) 종합 대답 / 비교를 제공하기 위해 적어도 커버 필요 Speed, Memory usage, SyntaxFeatures.

내 의도는 data.table 관점에서 가능한 한 명확하게 각 항목을 다루는 것입니다.

참고 : 달리 명시하지 않는 한 dplyr을 참조하여 내부가 Rcpp를 사용하여 C ++ 인 dplyr의 data.frame 인터페이스를 참조합니다.


data.table 구문은 형식이 일관성이 DT[i, j, by]있습니다. 유지하기 위해 i, j그리고 by함께하는 디자인입니다. 관련 작업을 함께 유지하면 구문의 일관성을 유지하면서 속도 및보다 중요한 메모리 사용에 대한 작업 을 쉽게 최적화 할 수 있으며 강력한 기능 도 제공 할 수 있습니다.

1. 속도

(주로하지만 작업을 그룹화에) 꽤 많은 벤치 마크는 이미 도착 data.table을 보여주는 질문에 추가 된 빠른 포함 증가하여 그룹에 그룹 및 / 또는 행의 수, 등 dplyr보다 매트에 의해 벤치 마크 에서 그룹화에 천만에 1 억 -1 천만 개의 그룹 과 다양한 그룹화 열 에서 20 억 개의 행 (RAM에서 100GB) 과 비교합니다 pandas. 포함 및 업데이트 된 벤치 마크 도 참조하십시오 .Sparkpydatatable

벤치 마크에서는 다음과 같은 나머지 측면도 다루는 것이 좋습니다.

  • 하위 집합을 포함하는 그룹화 작업 ( 예 : DT[x > val, sum(y), by = z]유형 작업)

  • 업데이트조인 과 같은 다른 작업을 벤치마킹하십시오 .

  • 또한 런타임 외에 각 작업의 메모리 풋 프린트 를 벤치마킹하십시오 .

2. 메모리 사용량

  1. 관련된 작업 filter()또는 slice()dplyr 인은 (모두 data.frames 및 data.tables)에 비효율적 인 메모리가 될 수 있습니다. 이 게시물을 참조하십시오 .

    참고 해들리의 코멘트 에 대해 이야기 속도 여기에 주요 관심사 반면 (즉 dplyr 그를 위해 풍부한 빠른)이며, 메모리 .

  2. 현재 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이 제공하는 많은 기능이 부족합니다.

  3. 가입하는 동안 집계 :

    다음과 같이 두 개의 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각 행에 들어가기 를 원합니다 . 우리는 다음 중 하나를 수행 할 수 있습니다.DT2x,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. 중간 결과가 구체화되지 않으며 결합 + 집계가 한 번에 수행됩니다.

    실제 사용 시나리오에 대한 this , thisthis 게시물을 살펴보십시오 .

    여기서는 dplyr먼저 결합하고 집계하거나 집계 한 다음 결합해야합니다 . 그 중 어느 것도 메모리 측면에서 효율적이지 않습니다 (결과적으로 속도로 변환 됨).

  4. 업데이트 및 조인 :

    아래에 표시된 data.table 코드를 고려하십시오.

    DT1[DT2, col := i.mul]

    키 열이 일치 하는 행에서 from 을 사용하여 DT1col을 추가 / 업데이트합니다 . 에이 작업과 정확히 동등한 것은 없다고 생각합니다 . 즉, 작업 을 피하지 않고 새로운 열을 추가하기 위해 전체를 복사해야 하므로 불필요합니다.mulDT2DT2DT1dplyr*_joinDT1

    실제 사용 시나리오는 이 게시물 을 확인하십시오 .

요약하면 모든 최적화가 중요하다는 것을 인식하는 것이 중요합니다. 로 그레이스 호퍼 , 말을 당신의 나노초 마음 !

3. 구문

구문을 보자 . 해들리가 여기에 댓글을 달았 습니다 .

데이터 테이블은 매우 빠른하지만 나는 그들의 간결 그것을하게 생각 열심히 배우고 그리고 당신이 그것을 쓴 후 읽기 어렵 코드를 사용하는 ...

나는이 말이 매우 주관적이기 때문에 무의미하다고 생각합니다. 우리가 시도 할 수있는 것은 구문의 일관성 을 대조하는 것 입니다. data.table과 dplyr 구문을 나란히 비교합니다.

아래에 표시된 더미 데이터로 작업합니다.

DT = data.table(x=1:10, y=11:20, z=rep(1:2, each=5))
DF = as.data.frame(DT)
  1. 기본 집계 / 업데이트 작업.

    # 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에서 요약 하면서 사용해야 했습니다 . 그러나 업데이 트하는 동안 우리는 내부로 로직을 옮겨야했습니다 mutate(). 그러나 data.table에서 우리는 동일한 논리로 두 연산을 표현합니다-행에서 작동 x > 2하지만 첫 번째 경우 get sum(y)이지만 두 번째 경우에는 y누적 합계로 해당 행을 업데이트합니다 .

      이것이 우리가 DT[i, j, by]양식 이 일관성이 있다고 말할 때의 의미 입니다.

    • 마찬가지로 (c)의 경우, if-else조건이있을 때 data.table과 dplyr에 논리를 "있는 그대로" 표현할 수 있습니다. 그러나 if조건이 만족 되는 행만 반환 하고 다른 방법으로 건너 뛰려면 summarise()직접 사용할 수 없습니다 (AFAICT). 우리는에있는 filter()제 때문에 다음 요약 summarise()항상 기대하고 단일 값을 .

      동일한 결과를 반환하지만 filter()여기를 사용하면 실제 작업이 덜 분명합니다.

      filter()첫 번째 경우에도 사용하는 것이 가능할 수도 있지만 (나에게 분명하지는 않지만) 내 요점은 우리가 할 필요는 없다는 것입니다.

  2. 여러 열에 대한 집계 / 업데이트

    # 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하여 a list를 반환하는 에 연결할 수 list있습니다.

    참고 : data.table에서 다시 한 번에 목록을 반환하기 만하면됩니다 j. 목록의 각 요소는 결과적으로 열이됩니다. 당신이 사용할 수있는 c(), as.list(), lapply(), list()새로운 기능을 학습 할 필요없이 등 ... 기본 기능은이 작업을 수행 할 수 있습니다.

    당신은 특별한 변수를 배울 필요가있을 것이다 - .N그리고 .SD적어도. 있습니다 dplyr에 동등한 n().

  3. 조인

    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"]
    ??
    • 일부는 각 조인에 대해 별도의 함수를 찾을 수 있지만 (왼쪽, 오른쪽, 내부, 앤티, 세미 등), 다른 사람들은 data.table 's를 좋아 DT[i, j, by]하거나 merge()기본 R과 비슷합니다.

    • 그러나 dplyr 조인은 그렇게합니다. 더 이상 없습니다. 그 이하도 아닙니다.

    • data.tables는 조인하는 동안 열을 선택할 수 있으며 (2) dplyr select()에서는 위의 그림과 같이 조인하기 전에 두 data.frame에서 먼저 열을 지정해야합니다 . 그렇지 않으면 나중에 불필요한 열만 조인하여 구체화하면 비효율적입니다.

    • data.tables는 결합하는 동안 집계 할 수 있으며 (3) 기능을 사용하여 결합하는 동안 업데이트 할 수도 있습니다 (4) by = .EACHI. 몇 개의 열만 추가 / 업데이트하기 위해 전체 결합 결과가 중요한 이유는 무엇입니까?

    • data.table은 롤링 조인 (5)-롤 포워드, LOCF , 롤백, NOCB , 가장 가까운 롤링을 수행 할 수 있습니다.

    • data.table에는 first , last 또는 all match (6) mult =를 선택하는 인수 도 있습니다 .

    • data.table에는 allow.cartesian = TRUE실수로 잘못된 조인을 방지하는 인수가 있습니다.

다시 한번, 구문은 DT[i, j, by]출력을 더 제어 할 수있는 추가 인수와 일치 합니다.

  1. do()...

    dplyr 's summary는 단일 값을 반환하는 함수를 위해 특별히 설계되었습니다. 함수가 여러 값이 아닌 값을 반환하면에 의지해야합니다 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에 해당하는 .

    • data.table에서 거의 모든 것을 던질 수 있습니다 j-기억해야 할 것은 목록의 각 요소가 열로 변환되도록 목록을 반환하는 것입니다.

    • dplyr에서는 그렇게 할 수 없습니다. do()함수가 항상 단일 값을 반환하는지 여부에 대한 확신 에 의존해야합니다. 그리고 그것은 매우 느립니다.

다시 한번, data.table의 구문은와 일치합니다 DT[i, j, by]. 우리는 j이런 것들에 대해 걱정할 필요없이 계속해서 표현을 던질 수 있습니다 .

한 번 봐 가지고 이 SO 질문이 하나 . dplyr의 구문을 사용하여 대답을 간단하게 표현할 수 있는지 궁금합니다 ...

요약하면, 특히 dplyr의 구문이 비효율적이거나 제한적이거나 작업을 간단하게 수행하지 못하는 몇 가지 사례를 강조 했습니다 . 이는 data.table이 "붙여 읽기 / 학습하기 어려운"구문 (위의 붙여 넣기 / 연결된 구문)에 대해 약간의 백래시를 가져 오기 때문입니다. dplyr을 다루는 대부분의 게시물은 가장 간단한 작업에 대해 이야기합니다. 그리고 그것은 위대하다. 그러나 구문과 기능 제한을 인식하는 것이 중요하며 아직 그 게시물을 보지 못했습니다.

data.table에도 단점이 있습니다 (일부에서는 수정하려고한다고 지적했습니다). 우리는 여기서 강조 표시 한대로 data.table의 조인을 개선하려고합니다 .

그러나 data.table과 비교할 때 dplyr에없는 기능의 수를 고려해야합니다.

4. 특징

나는 여기 와이 게시물의 대부분의 기능을 지적했습니다 . 게다가:

  • fread- 빠른 파일 리더가 오랫동안 사용되었습니다.

  • 에 fwrite - parallelised 빠른 파일 라이터를 사용할 수 있습니다. 구현에 대한 자세한 설명과 추가 개발을 추적하기위한 # 1664 에 대해서는 이 게시물 을 참조하십시오 .

  • 자동 인덱싱 -기본 R 구문을 내부적으로 최적화하는 또 다른 편리한 기능입니다.

  • Ad-hoc grouping : dplyr동안 변수를 그룹화하여 결과를 자동으로 정렬 summarise()하므로 항상 바람직하지는 않습니다.

  • 위에서 언급 한 data.table 조인 (속도 / 메모리 효율성 및 구문)의 여러 장점

  • 동등하지 않은 조인 : <=, <, >, >=data.table 조인의 다른 모든 장점과 함께 다른 연산자 를 사용하여 조인 할 수 있습니다.

  • 최근 data.table에서 겹치는 범위 조인 이 구현되었습니다. 이 게시물 에서 벤치마킹 개요를 확인하십시오 .

  • setorder() data.table의 함수는 참조로 data.tables를 실제로 빠르게 재정렬 할 수 있습니다.

  • dplyr은 현재 동일한 data.table과 동일한 구문을 사용하여 데이터베이스에 대한 인터페이스를 제공합니다 .

  • data.table빠른 등가물 제공하는 일련의 작업 - (월 Gorecki에 의해 작성) fsetdiff, fintersect, funion그리고 fsetequal추가로 all인수 (SQL에서와 같이)를.

  • data.table은 마스킹 경고없이 깨끗하게로드되며 R 패키지로 전달 될 때 호환성을 위해 여기 에 설명 된 메커니즘이 있습니다[.data.frame . dplyr베이스의 기능을 변화 filter, lag[문제가 발생할 수있다; 예를 들어 여기여기에 .


드디어:

  • 데이터베이스에서-data.table이 유사한 인터페이스를 제공 할 수없는 이유는 없지만 이것이 우선 순위가 아닙니다. 사용자가이 기능을 매우 좋아하면 문제가 발생할 수 있습니다. 확실하지 않습니다.

  • 병렬 처리-누군가가 나아가서 할 때까지 모든 것이 어렵습니다. 물론 노력이 필요합니다 (스레드 안전).

    • 를 사용하여 점진적인 성능 향상을 위해 알려진 시간 소모적 인 부품을 병렬화하기 위해 현재 (v1.9.7 버전에서) 진행 중입니다 OpenMP.

9
@ bluefeet : 당신이 그 토론을 채팅으로 옮겨서 우리에게 나머지 훌륭한 서비스를했다고 생각하지 않습니다. 나는 Arun이 개발자 중 하나라는 인상을 받았으며 이로 인해 유용한 통찰력이 생겼을 수 있습니다.
IRTFM

2
귀하의 링크를 사용하여 채팅을 갔을 때 "필터를 사용해야합니다"라는 의견 다음에 나오는 모든 자료가 사라진 것 같습니다. SO 채팅 메커니즘에 대해 뭔가 빠졌습니까?
IRTFM

6
내 생각 엔 어디 어디 참조 (에 의해 할당을 사용하고있는 모든 것을 :=), dplyr사용도 수 있어야 동등한 <-같이 DF <- DF %>% mutate...대신의DF %>% mutate...
데이비드 Arenburg

4
구문에 대하여. 나는 구문에 dplyr익숙한 사용자에게는 더 쉬울 수 plyr있지만 , data.table같은 언어 구문 SQL과 그 뒤에 관계형 대수 를 쿼리하는 사용자에게는 더 쉬울 수 있다고 생각합니다. 이는 테이블 형식 데이터 변환에 관한 것입니다. @Arun 당신은 설정 연산자 는 랩핑 data.table기능 으로 매우 쉽게 수행 할 수 있으며 물론 상당한 속도 향상을 가져옵니다.
jangorecki

9
이 게시물을 여러 번 읽었으며 data.table을 이해하고 더 잘 사용할 수 있도록 도와주었습니다. 대부분의 경우 dplyr 또는 pandas 또는 PL / pgSQL보다 data.table을 선호합니다. 그러나 나는 그것을 표현하는 방법에 대한 생각을 멈출 수 없었다. 구문은 쉽지 않거나 명확하지 않거나 장황 하지 않습니다 . 사실, 나는 data.table을 많이 사용한 후에도 종종 내 자신의 코드를 이해하기 위해 어려움을 겪고 있습니다. 일주일 전에 문자 그대로 작성했습니다. 이것은 쓰기 전용 언어의 실제 예입니다. en.wikipedia.org/wiki/Write-only_language 그러니 언젠가 data.table에서 dplyr을 사용할 수 있기를 바랍니다.
Ufos

385

다음은 Arun의 답변에 대한 광범위한 개요에 따라 dplyr 관점에서 포괄적 인 답변을 시도한 것입니다 (그러나 다른 우선 순위에 따라 다소 재정렬 됨).

통사론

구문에는 주관성이 있지만 data.table의 결정은 배우기가 어렵고 읽기가 더 어렵다는 내 진술을지지합니다. 이것은 dplyr이 훨씬 쉬운 문제를 해결하고 있기 때문입니다!

dplyr이 당신에게 가장 중요한 것은 옵션 이 제한적 이라는 것 입니다. 나는 대부분의 단일 테이블 문제는 단지 "그룹 별"부사와 함께 5 개의 핵심 동사 필터, 선택, 변경, 정렬 및 요약으로 해결할 수 있다고 주장한다. 이러한 제약은 데이터 조작을 학습 할 때 큰 도움이됩니다. 문제에 대한 생각을 정리하는 데 도움이되기 때문입니다. dplyr에서 각 동사는 단일 함수에 매핑됩니다. 각 기능은 하나의 작업을 수행하며 개별적으로 이해하기 쉽습니다.

이 간단한 작업을와 함께 파이핑하여 복잡성을 만듭니다 %>%. 다음은 Arun이 링크 한 게시물 중 하나의 예입니다 .

diamonds %>%
  filter(cut != "Fair") %>%
  group_by(cut) %>%
  summarize(
    AvgPrice = mean(price),
    MedianPrice = as.numeric(median(price)),
    Count = n()
  ) %>%
  arrange(desc(Count))

전에 dplyr (또는 R!)을 본 적이 없더라도 함수가 모두 영어 동사이기 때문에 현재 상황을 파악할 수 있습니다. 영어 동사의 단점은보다 많은 타이핑이 필요하다는 것입니다 [.하지만 더 나은 자동 완성 기능으로 크게 완화 할 수 있다고 생각합니다.

동등한 data.table 코드는 다음과 같습니다.

diamondsDT <- data.table(diamonds)
diamondsDT[
  cut != "Fair", 
  .(AvgPrice = mean(price),
    MedianPrice = as.numeric(median(price)),
    Count = .N
  ), 
  by = cut
][ 
  order(-Count) 
]

data.table에 익숙하지 않으면이 코드를 따르는 것이 더 어렵습니다. (나는 또한 [ 내 눈에 좋아 보이는 방식으로 반복을 들여 쓰는 방법을 알 수 없었습니다 ). 개인적으로, 제가 6 개월 전에 작성한 코드를 살펴보면, 낯선 사람이 작성한 코드를 보는 것과 같습니다.

가독성을 약간 떨어 뜨린다 고 생각하는 다른 두 가지 사소한 요소 :

  • 거의 모든 데이터 테이블 작업이 사용 [되므로 발생하는 상황을 파악하기 위해 추가 컨텍스트가 필요합니다. 예를 들어 x[y] 두 데이터 테이블을 조인하거나 데이터 프레임에서 열을 추출합니까? 잘 작성된 코드에서 변수 이름은 무슨 일이 일어나고 있는지 제안해야하기 때문에 이것은 작은 문제 일뿐입니다.

  • 나는 그것이 group_by()dplyr에서 별도의 작업 인 것을 좋아합니다 . 계산을 근본적으로 변경하므로 코드를 감추면 분명해야한다고 생각하며에 group_by()대한 by인수 보다 쉽게 찾을 수 [.data.table있습니다.

또한 파이프 가 단지 하나의 패키지로 제한되지 않는다는 것이 좋습니다. 당신은 당신의 데이터를 정리하여 시작할 수 있습니다 tidyr 과의 플롯으로 마무리 ggvis . 그리고 내가 작성하는 패키지에만 국한되지는 않습니다. 누구나 데이터 조작 파이프의 완벽한 부분을 구성하는 함수를 작성할 수 있습니다. 사실, 나는 이전 data.table 코드를 %>%다음 과 같이 다시 작성하는 것을 선호합니다 .

diamonds %>% 
  data.table() %>% 
  .[cut != "Fair", 
    .(AvgPrice = mean(price),
      MedianPrice = as.numeric(median(price)),
      Count = .N
    ), 
    by = cut
  ] %>% 
  .[order(-Count)]

그리고 파이핑 개념은 %>%데이터 프레임에만 국한되지 않고 대화 형 웹 그래픽 , 웹 스크랩 핑 , 요점 , 런타임 계약 등과 같은 다른 컨텍스트로 쉽게 일반화됩니다.

메모리와 성능

나는 그것들을 함께 모았습니다. 왜냐하면 나에게는 그렇게 중요하지 않기 때문입니다. 대부분의 R 사용자는 백만 행 미만의 데이터를 처리하며 dplyr은 처리 시간을 알지 못하는 데이터 크기에 비해 충분히 빠릅니다. 매체 데이터의 표현력을 위해 dplyr을 최적화합니다. 큰 데이터에서 원시 속도로 data.table을 자유롭게 사용하십시오.

dplyr의 유연성은 동일한 구문을 사용하여 성능 특성을 쉽게 조정할 수 있음을 의미합니다. 데이터 프레임 백엔드에서 dplyr의 성능이 충분하지 않은 경우 약간 제한된 기능 세트가 있지만 data.table 백엔드를 사용할 수 있습니다. 작업중인 데이터가 메모리에 맞지 않으면 데이터베이스 백엔드를 사용할 수 있습니다.

그러나 dplyr 성능은 장기적으로 향상 될 것입니다. 기수 순서와 같은 훌륭한 data.table 아이디어를 구현하고 조인 및 필터에 동일한 인덱스를 사용합니다. 또한 병렬화 작업을 수행하여 여러 코어를 활용할 수 있습니다.

풍모

2015 년에 진행할 몇 가지 사항 :

  • readr패키지는 유사한 메모리 디스크 오프에서 파일을 쉽게 얻을를 만들기 위해 fread().

  • 동일하지 않은 조인에 대한 지원을 포함하여보다 유연한 조인.

  • 부트 스트랩 샘플, 롤업 등과 같은보다 유연한 그룹화

또한 R의 데이터베이스 커넥터 를 개선 하고 웹 API 와 대화 할 수있는 기능을 개선 하고 html 페이지를 쉽게 긁어내는 데 시간을 투자 하고 있습니다 .


27
부수적으로, 나는 많은 논증에 동의하지만 ( data.table구문을 선호 하지만) 스타일이 마음에 들지 않으면 작업 %>%을 파이프하기 위해 쉽게 사용할 수 있습니다 . 는 특정 적이 지 않고 별도의 패키지 (공동 저자이기도 함)에서 제공되므로 대부분의 구문 단락 에서 무엇을 말하려고하는지 이해하지 못합니다 . data.table[%>%dplyr
David Arenburg

11
@DavidArenburg 좋은 지적입니다. 필자의 주요 요점을 더 명확하게 설명 %>%하고 data.table과 함께 사용할 수 있음을 강조하기 위해 구문을 다시 작성 했습니다.
hadley

5
해들리 감사합니다, 이것은 유용한 관점입니다. 들여 쓰기는 일반적으로 실제로 작동합니다 DT[\n\texpression\n][\texpression\n]( gist ). 그는 더 직접적으로 구문의 접근에 대해 너무 많이하지 않습니다 내 특정 질문에 대한 답변으로 내가 대답으로 아룬의 답변을 유지하고있어,하지만 난 사이의 차이점 / 공통점에 대한 일반적인 느낌을 얻을려고 사람들이 좋은 응답 생각 dplyrdata.table.
BrodieG

33
왜 이미 빠른 읽기 작업을하고 fread()있습니까? fread ()를 개선하거나 다른 (개발되지 않은) 것들을 연구하는데 시간이 더 걸리지 않습니까?
EDi

10
의 API data.table[]표기법 의 대규모 남용에 기반합니다 . 그것이 가장 큰 힘과 가장 큰 약점입니다.
Paul

65

질문 제목에 직접 응답하여 ...

dplyr 확실히 그 일을data.table 할 수없는 .

요점 # 3

dplyr 초록 또는 잠재적 DB 상호 작용

귀하의 질문에 대한 직접적인 답변이지만 충분히 높은 수준으로 올라가지 않습니다. 단일 dplyr데이터 확장으로 여러 데이터 저장 메커니즘으로 확장 가능한 프런트 엔드 data.table입니다.

dplyr대상과 핸들러를 마음대로 확장 할 수있는 동일한 그래머를 사용하는 모든 대상이있는 백엔드 불가지론 인터페이스로 살펴보십시오 . 관점 data.table에서 dplyr볼 때 이러한 목표 중 하나입니다.

data.table디스크에서 또는 네트워크로 연결된 데이터 저장소에서 작동하는 SQL 문을 작성하기 위해 쿼리를 변환하려고 시도 하는 하루를 결코 보지 못할 것 입니다.

dplyr할 수 data.table있거나하지 않을 수도 있습니다.

메모리 내 작업 설계를 기반으로 data.table하는 것보다 쿼리 병렬 처리로 확장하기가 훨씬 더 어려울 수 있습니다 dplyr.


신체 내 질문에 대한 답변으로 ...

용법

패키지 에 익숙한 사람들을 위해 하나 또는 다른 패키지 를 사용 하여 코딩하기가 훨씬 쉬운 분석 작업이 있습니까 (예 : 필요한 키 밀림과 난이도 수준 중 일부가 적은 경우).

이것은 펀트처럼 보일 수 있지만 실제 대답은 아닙니다. 도구에 익숙한 사람들 은 가장 친숙한 도구 나 실제로 작업에 적합한 도구를 사용하는 것 같습니다. 말했듯이, 때로는 특정 가독성, 때로는 성능 수준을 제시하고 싶을 때와 둘 다 높은 수준이 필요할 때 이미 추상화를 명확하게하기 위해 필요한 다른 도구를 사용해야 할 수도 있습니다. .

공연

한 패키지에서 다른 패키지로 실질적으로 (즉, 2 배 이상)보다 효율적으로 수행되는 분석 작업이 있습니까?

다시 요 기본 데이터 저장소 및 등록 된 처리기로 제한되는 부담을받는 data.table모든 작업 에서 효율적으로 작동 dplyr합니다.

이 방법 당신은 성능 문제로 실행하면 data.table당신이 확신이 쿼리 기능에하고있는 경우가 될 수 있다 실제로와 병목 data.table다음 자신에게 보고서를 제출의 기쁨을 수상했습니다. 백엔드로 dplyr사용 data.table하는 경우에도 마찬가지 입니다 . 당신 은 약간의 오버 헤드를 볼 있지만 쿼리 일 가능성이 있습니다.dplyr

dplyr백 엔드 성능 문제를 가지고 당신은 실행하기 전에 생성 된 쿼리를 조작 (데이터베이스의 경우) 하이브리드 평가하거나하는 기능을 등록하여 주위를 얻을 수 있습니다.

또한 data.table보다 plyr가 더 나은 경우에 대한 대답을 참조하십시오 .


3
캔트 dplyr는 tbl_dt로 data.table을 래핑합니까? 두 세계의 장점을 모두 누리십시오.
aaa90210

22
"data.table은 확실히 dplyr가 할 수없는 일을한다"라는 역 진술을 언급하는 것을 잊어 버렸습니다 .
jangorecki

25
Arun 답변이 잘 설명되어 있습니다. 성능 측면에서 가장 중요한 것은 확산, 참조로 업데이트, 롤링 조인, 겹치는 조인입니다. 나는 그 기능들과 경쟁 할 수있는 패키지 (dplyr뿐만 아니라)도 없다고 생각합니다. 프레젠테이션의 마지막 슬라이드가 좋은 예 입니다.
jangorecki

15
완전히 data.table은 여전히 ​​R을 사용하는 이유입니다. 그렇지 않으면 팬더를 사용합니다. 팬더보다 훨씬 빠릅니다.
marbel

8
나는 단순하고 SQL 구문 구조와 유사하기 때문에 data.table을 좋아합니다. 저의 업무는 통계 모델링을 위해 매일 매우 강렬한 임시 데이터 분석 및 그래픽을 수행하는 일이며 복잡한 작업을 수행 할 수있을만큼 간단한 도구가 필요합니다. 이제 툴킷을 ​​data.table로만 줄이고 일상 업무에서 그래프로 그릴 수 있습니다. $ DT [group == 1, y_hat : = predict (fit1, data = .SD),] $와 같은 연산도 수행 할 수있는 예를 들어보십시오. 고전적인 R 환경.
xappppp
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.