Não há uma resposta direta / simples porque as filosofias de ambos os pacotes diferem em certos aspectos. Portanto, alguns compromissos são inevitáveis. Aqui estão algumas das preocupações que você pode precisar abordar / considerar.
Operações envolvendo i
(== filter()
e slice()
em dplyr)
Suponha DT
com, digamos, 10 colunas. Considere estas expressões data.table:
DT[a > 1, .N] ## --- (1)
DT[a > 1, mean(b), by=.(c, d)] ## --- (2)
(1) fornece o número de linhas em DT
que coluna a > 1
. (2) retorna mean(b)
agrupado por c,d
para a mesma expressão em i
(1).
dplyr
Expressões comumente usadas seriam:
DT %>% filter(a > 1) %>% summarise(n()) ## --- (3)
DT %>% filter(a > 1) %>% group_by(c, d) %>% summarise(mean(b)) ## --- (4)
Claramente, os códigos data.table são mais curtos. Além disso, eles também são mais eficientes em termos de memória 1 . Por quê? Porque em (3) e (4), filter()
retorna linhas para todas as 10 colunas primeiro, quando em (3) precisamos apenas do número de linhas, e em (4) precisamos apenas de colunas b, c, d
para as operações sucessivas. Para superar isso, temos que select()
colunas a priori:
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)
É essencial destacar uma grande diferença filosófica entre os dois pacotes:
Em data.table
, gostamos de manter essas operações relacionadas juntas, e isso permite olhar para j-expression
(da mesma chamada de função) e perceber que não há necessidade de quaisquer colunas em (1). A expressão em i
é calculada e .N
é apenas a soma daquele vetor lógico que fornece o número de linhas; todo o subconjunto nunca é realizado. Em (2), apenas as colunas b,c,d
são materializadas no subconjunto, as demais colunas são ignoradas.
Mas em dplyr
, a filosofia é ter uma função que faz exatamente uma coisa bem . Não há (pelo menos atualmente) nenhuma maneira de dizer se a operação posterior filter()
precisa de todas as colunas que filtramos. Você precisará pensar no futuro se quiser realizar essas tarefas com eficiência. Pessoalmente, acho isso contra-intutivo neste caso.
Observe que em (5) e (6), ainda subconjuntos de colunas a
que não exigimos. Mas não tenho certeza de como evitar isso. Se a filter()
função tivesse um argumento para selecionar as colunas a serem retornadas, poderíamos evitar esse problema, mas a função não fará apenas uma tarefa (que também é uma escolha de design do dplyr).
Subatribuir por referência
dplyr nunca será atualizado por referência. Esta é outra grande diferença (filosófica) entre os dois pacotes.
Por exemplo, em data.table, você pode fazer:
DT[a %in% some_vals, a := NA]
que atualiza a coluna a
por referência apenas nas linhas que satisfazem a condição. No momento, o dplyr deep copia todo o data.table internamente para adicionar uma nova coluna. @BrodieG já mencionou isso em sua resposta.
Mas a cópia profunda pode ser substituída por uma cópia superficial quando FR # 617 for implementado. Também relevante: dplyr: FR # 614 . Observe que, ainda assim, a coluna que você modificar sempre será copiada (portanto, um pouco mais lenta / menos eficiente em termos de memória). Não haverá como atualizar as colunas por referência.
Outras funcionalidades
Em data.table, você pode agregar durante a junção, e isso é mais simples de entender e é eficiente em termos de memória, pois o resultado da junção intermediária nunca é materializado. Verifique esta postagem para um exemplo. Você não pode (no momento?) Fazer isso usando a sintaxe data.table / data.frame do dplyr.
O recurso rolling joins de data.table também não é compatível com a sintaxe do dplyr.
Recentemente, implementamos junções de sobreposição em data.table para juntar em intervalos de intervalo ( aqui está um exemplo ), que é uma função separada foverlaps()
no momento e, portanto, pode ser usada com os operadores de pipe (magrittr / pipeR? - nunca tentei sozinho).
Mas, no final das contas, nosso objetivo é integrá-lo ao [.data.table
para que possamos colher os outros recursos como agrupamento, agregação durante a união etc., que terão as mesmas limitações descritas acima.
Desde 1.9.4, data.table implementa indexação automática usando chaves secundárias para subconjuntos baseados em busca binária rápida na sintaxe R regular. Ex: DT[x == 1]
e DT[x %in% some_vals]
criará automaticamente um índice na primeira execução, que será usado em subconjuntos sucessivos da mesma coluna para subconjunto rápido usando pesquisa binária. Esse recurso continuará a evoluir. Verifique esta essência para uma breve visão geral desse recurso.
Da maneira como filter()
é implementado para data.tables, não tira proveito deste recurso.
Um recurso do dplyr é que ele também fornece interface para bancos de dados usando a mesma sintaxe, o que data.table não faz no momento.
Portanto, você terá que pesar esses (e provavelmente outros pontos) e decidir com base se essas compensações são aceitáveis para você.
HTH
(1) Observe que ter memória eficiente impacta diretamente a velocidade (especialmente à medida que os dados ficam maiores), já que o gargalo na maioria dos casos é mover os dados da memória principal para o cache (e usar os dados no cache tanto quanto possível - reduzir perdas de cache - de modo a reduzir o acesso à memória principal). Não vou entrar em detalhes aqui.
dplyr
métodos para tabelas de dados, mas a tabela de dados também tem seus próprios métodos comparáveis