Se eu usar a sintaxe dplyr no topo de uma tabela de dados , obtenho todos os benefícios de velocidade da tabela de dados enquanto ainda uso a sintaxe de dplyr? Em outras palavras, eu uso indevidamente a tabela de dados se a consultar com a sintaxe dplyr? Ou preciso usar sintaxe de tabela de dados pura para aproveitar todo o seu poder.
Agradecemos antecipadamente por qualquer conselho. Exemplo de código:
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))
Resultados:
# 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
Aqui está a equivalência de dados que eu vim. Não tenho certeza se está em conformidade com as boas práticas da DT. Mas eu me pergunto se o código é realmente mais eficiente do que a sintaxe dplyr nos bastidores:
diamondsDT [cut != "Fair"
] [, .(AvgPrice = mean(price),
MedianPrice = as.numeric(median(price)),
Count = .N), by=cut
] [ order(-Count) ]
r
data.table
dplyr
Polimerase
fonte
fonte
dplyr
métodos para tabelas de dados, mas a tabela de dados também tem seus próprios métodos comparáveisdplyr
é usado emdata.frame
s e correspondentesdata.table
s, veja aqui (e referências nele).Respostas:
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()
eslice()
em dplyr)Suponha
DT
com, digamos, 10 colunas. Considere estas expressões data.table:(1) fornece o número de linhas em
DT
que colunaa > 1
. (2) retornamean(b)
agrupado porc,d
para a mesma expressão emi
(1).dplyr
Expressões comumente usadas seriam: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 colunasb, c, d
para as operações sucessivas. Para superar isso, temos queselect()
colunas a priori: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 afilter()
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
Por exemplo, em data.table, você pode fazer:
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]
eDT[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.
fonte
filter()
plus eficientesummarise()
usando a mesma abordagem que dplyr usa para SQL - isto é, construir uma expressão e então executar apenas uma vez sob demanda. É improvável que isso seja implementado em um futuro próximo porque dplyr é rápido o suficiente para mim e implementar um planejador / otimizador de consulta é relativamente difícil.Apenas tente.
Sobre este problema, parece que data.table é 2,4x mais rápido que dplyr usando data.table:
Revisado com base no comentário da Polymerase.
fonte
microbenchmark
pacote, descobri que executar odplyr
código do OP na versão original (quadro de dados)diamonds
levou um tempo médio de 0,012 segundos, enquanto levou um tempo médio de 0,024 segundos após a conversãodiamonds
para uma tabela de dados. A execução dodata.table
código de G. Grothendieck levou 0,013 segundos. Pelo menos no meu sistema, parecedplyr
edata.table
tem quase o mesmo desempenho. Mas por quedplyr
seria mais lento quando o quadro de dados é convertido pela primeira vez em uma tabela de dados?Para responder às suas perguntas:
data.table
data.table
sintaxe puraEm muitos casos, isso será um meio-termo aceitável para aqueles que desejam a
dplyr
sintaxe, embora possivelmente seja mais lento do quedplyr
com quadros de dados simples.Um grande fator parece ser que
dplyr
copiará odata.table
por padrão ao agrupar. Considere (usando o microbenchmark):A filtragem tem velocidade comparável, mas o agrupamento não. Eu acredito que o culpado é esta linha em
dplyr:::grouped_dt
:onde o
copy
padrão éTRUE
(e não pode ser facilmente alterado para FALSE que eu posso ver). Isso provavelmente não é responsável por 100% da diferença, mas a sobrecarga geral sozinha em algo do tamanhodiamonds
mais provável não é a diferença total.A questão é que, para ter uma gramática consistente,
dplyr
faça o agrupamento em duas etapas. Primeiro, ele define as chaves em uma cópia da tabela de dados original que correspondem aos grupos e, somente depois, agrupa.data.table
apenas aloca memória para o maior grupo de resultados, que neste caso é apenas uma linha, de modo que faz uma grande diferença em quanta memória precisa ser alocada.Para sua informação, se alguém se importa, eu encontrei isso usando
treeprof
(install_github("brodieg/treeprof")
), um visualizador de árvore experimental (e ainda muito alfa) para aRprof
saída:Observe que o acima só funciona atualmente em macs AFAIK. Além disso, infelizmente,
Rprof
registra chamadas do tipopackagename::funname
como anônimas, portanto, podem ser quaisquer e todas asdatatable::
chamadas internasgrouped_dt
que são responsáveis, mas, a partir de um teste rápido, parecia quedatatable::copy
é a maior.Dito isso, você pode ver rapidamente como não há tanta sobrecarga em torno da
[.data.table
chamada, mas também há uma ramificação completamente separada para o agrupamento.EDITAR : para confirmar a cópia:
fonte
Você pode usar dtplyr agora, que faz parte do tidyverse . Ele permite que você use instruções no estilo dplyr como de costume, mas utiliza avaliação preguiçosa e converte suas instruções em código data.table nos bastidores. A sobrecarga na tradução é mínima, mas você obtém todos, se não, a maioria dos benefícios de data.table. Mais detalhes no repositório git oficial aqui e na página tidyverse .
fonte