web-dev-qa-db-fra.com

Quand dois-je utiliser setDT () au lieu de data.table () pour créer un data.table?

J'ai du mal à saisir l'essence de la fonction setDT(). Lorsque je lis du code sur SO, je rencontre fréquemment l'utilisation de setDT() pour créer un data.table. Bien sûr, l'utilisation de data.table() est omniprésente. J'ai l'impression de bien comprendre la nature de data.table() mais la pertinence de setDT() m'échappe. ?setDT Me dit ceci:

setDT convertit les listes (nommées et non nommées) et data.frames en data.tables par référence.

aussi bien que:

Dans le langage data.table, Toutes les fonctions set* Modifient leur entrée par référence. Autrement dit, aucune copie n'est effectuée du tout, à l'exception de la mémoire de travail temporaire, qui est aussi grande qu'une colonne.

Cela me fait donc penser que je ne devrais utiliser que setDT() pour créer un data.table, non? setDT() est-il simplement un convertisseur de liste en data.table?

library(data.table)

a <- letters[c(19,20,1,3,11,15,22,5,18,6,12,15,23)]
b <- seq(1,41,pi)
ab <- data.frame(a,b)
d <- data.table(ab)
e <- setDT(ab)

str(d)
#Classes ‘data.table’ and 'data.frame': 13 obs. of  2 variables:
# $ a: Factor w/ 12 levels "a","c","e","f",..: 9 10 1 2 5 7 11 3 8 4 ...
# $ b: num  1 4.14 7.28 10.42 13.57 ...
# - attr(*, ".internal.selfref")=<externalptr>

str(e)
#Classes ‘data.table’ and 'data.frame': 13 obs. of  2 variables:
# $ a: Factor w/ 12 levels "a","c","e","f",..: 9 10 1 2 5 7 11 3 8 4 ...
# $ b: num  1 4.14 7.28 10.42 13.57 ...
# - attr(*, ".internal.selfref")=<externalptr>

Apparemment aucune différence dans ce cas. Dans un autre cas, la différence est évidente:

ba <- list(a,b)
f <- data.table(ba)
g <- setDT(ba)

str(f)
#Classes ‘data.table’ and 'data.frame': 2 obs. of  1 variable:
# $ ba:List of 2
#  ..$ : chr  "s" "t" "a" "c" ...
#  ..$ : num  1 4.14 7.28 10.42 13.57 ...
# - attr(*, ".internal.selfref")=<externalptr>

str(g)
#Classes ‘data.table’ and 'data.frame': 13 obs. of  2 variables:
# $ V1: chr  "s" "t" "a" "c" ...
# $ V2: num  1 4.14 7.28 10.42 13.57 ...
# - attr(*, ".internal.selfref")=<externalptr>

Quand dois-je utiliser setDT()? Qu'est-ce qui rend setDT() pertinent? Pourquoi ne pas simplement rendre la fonction data.table() originale capable de faire ce que setDT() est capable de faire?

34
Dodge

Mise à jour:

@ Roland fait quelques bons points dans la section des commentaires, et le post est meilleur pour eux. Alors que je me concentrais à l'origine sur les problèmes de dépassement de mémoire, il a souligné que même si cela ne se produisait pas, la gestion de la mémoire de diverses copies prend beaucoup de temps, ce qui est une préoccupation quotidienne plus courante. Des exemples de ces deux problèmes ont également été ajoutés.

J'aime cette question sur stackoverflow car je pense qu'il s'agit vraiment d'éviter le débordement de pile dans R lorsque l'on traite des ensembles de données plus volumineux. ???? Ceux qui ne sont pas familiers avec la famille data.table D'opérations set peuvent bénéficier de cette discussion!

Il faut utiliser setDT() lorsque vous travaillez avec des ensembles de données plus volumineux qui prennent une quantité considérable de RAM car l'opération modifiera chaque objet en place, en conservant la mémoire. Pour les données qui sont un très petit pourcentage de RAM, en utilisant copier-modifier de data.table est très bien.

La création de la fonction setDT a en fait été inspirée par le thread suivant sur le débordement de pile, qui concerne le travail avec un grand ensemble de données (plusieurs Go). Vous verrez le carillon Matt Dowle dans un suggérer le nom "setDT".

Convertir une trame de données en une table de données sans copie

Un peu plus de profondeur:

Avec R, les données sont stockées en mémoire. Cela accélère considérablement les choses car RAM est beaucoup plus rapide d'accès que les périphériques de stockage. Cependant, un problème peut survenir lorsque l'ensemble de données est une grande partie de RAM. Pourquoi? Parce que la base R a tendance de faire des copies de chaque data.frame lorsque certaines opérations leur sont appliquées. Cela s'est amélioré après la version 3.1, mais le traitement dépasse le cadre de cet article. Si l'on tire plusieurs data.frame ou lists en un data.frame ou data.table, votre utilisation de la mémoire augmentera assez rapidement car à un moment donné de l'opération, plusieurs copies de vos données existent dans la RAM. Si l'ensemble de données est assez grand, vous risquez de manquer de mémoire lorsque toutes les copies sont produites et votre pile va déborder. Voir l'exemple ci-dessous. Nous obtenons une erreur et l'adresse mémoire d'origine et la classe d'objet ne changent pas.

> N <- 1e8
> P <- 1e2
> data <- as.data.frame(rep(data.frame(rnorm(N)), P))
> 
> pryr::object_size(data)
800 MB
> 
> tracemem(data)
[1] "<0000000006D2DF18>"
> 
> data <- data.table(data)
Error: cannot allocate vector of size 762.9 Mb
> 
> tracemem(data)
[1] "<0000000006D2DF18>"
> class(data)
[1] "data.frame"
>

La possibilité de simplement modifier l'objet en place sans copier est un gros problème. C'est ce que fait setDT lorsqu'il prend un list ou data.frame Et renvoie un data.table. Le même exemple que ci-dessus utilisant setDT, fonctionne maintenant correctement et sans erreur. La classe et l'adresse mémoire changent et aucune copie n'a lieu.

> tracemem(data)
[1] "<0000000006D2DF18>"
> class(data)
[1] "data.frame"
> 
> setDT(data)
>  
> tracemem(data)
[1] "<0000000006A8C758>"
> class(data)
[1] "data.table" "data.frame"

@Roland souligne que pour la plupart des gens, la plus grande préoccupation est la vitesse, qui souffre comme effet secondaire d'une utilisation aussi intensive de la gestion de la mémoire. Voici un exemple avec des données plus petites qui ne plantent pas l'unité centrale de traitement et illustre à quel point setDT est plus rapide pour ce travail. Remarquez les résultats de 'tracemem' à la suite de data <- data.table(data), faisant des copies de data. Comparez cela avec setDT(data) qui n'imprime pas une seule copie. Nous devons ensuite appeler tracemem(data) pour voir la nouvelle adresse mémoire.

> N <- 1e5
> P <- 1e2
> data <- as.data.frame(rep(data.frame(rnorm(N)), P))
> pryr::object_size(data)
808 kB

> # data.table method
> tracemem(data)
[1] "<0000000019098438>"
> data <- data.table(data)
tracemem[0x0000000019098438 -> 0x0000000007aad7d8]: data.table 
tracemem[0x0000000007aad7d8 -> 0x0000000007c518b8]: copy as.data.table.data.frame as.data.table data.table 
tracemem[0x0000000007aad7d8 -> 0x0000000018e454c8]: as.list.data.frame as.list vapply copy as.data.table.data.frame as.data.table data.table 
> class(data)
[1] "data.table" "data.frame"
> 
> # setDT method
> # back to data.frame
> data <- as.data.frame(data)
> class(data)
[1] "data.frame"
> tracemem(data)
[1] "<00000000125BE1A0>"
> setDT(data)
> tracemem(data)
[1] "<00000000125C2840>"
> class(data)
[1] "data.table" "data.frame"
> 

Comment cela affecte-t-il le timing? Comme nous pouvons le voir, setDT est beaucoup plus rapide pour cela.

> # timing example
> data <- as.data.frame(rep(data.frame(rnorm(N)), P))
> microbenchmark(setDT(data), data <- data.table(data))
Unit: microseconds
                     expr       min         lq        mean    median            max neval        uq
              setDT(data)    49.948    55.7635    69.66017    73.553        100.238   100    79.198
 data <- data.table(data) 54594.289 61238.8830 81545.64432 64179.131     611632.427   100 68647.917

Les fonctions de définition peuvent être utilisées dans de nombreux domaines, pas seulement lors de la conversion d'objets en data.tables. Vous pouvez trouver plus d'informations sur la sémantique de référence et comment les appliquer ailleurs en appelant la vignette sur le sujet.

library(data.table)    
vignette("datatable-reference-semantics")

C'est une excellente question et ceux qui envisagent d'utiliser R avec des ensembles de données plus volumineux ou qui souhaitent simplement accélérer les manipulations de données actives, peuvent bénéficier de la connaissance des améliorations significatives des performances de la sémantique de référence data.table.

26
Justin

setDT() ne remplace pas data.table(). C'est un remplacement plus efficace de as.data.table() qui peut être utilisé avec certains types d'objets.

  • mydata <- as.data.table(mydata) copiera l'objet derrière mydata, convertira la copie en data.table, puis changer le symbole mydata pour pointer vers la copie.
  • setDT(mydata) changera l'objet derrière mydata en data.table. Aucune copie n'est effectuée.

Alors, quelle est une situation réaliste pour utiliser setDT()? Lorsque vous ne pouvez pas contrôler la classe des données d'origine. Par exemple, la plupart des packages pour travailler avec des bases de données donnent une sortie data.frame. Dans ce cas, votre code serait quelque chose comme

mydata <- dbGetQuery(conn, "SELECT * FROM mytable")  # Returns a data.frame
setDT(mydata)                                        # Make it a data.table

Quand faut-il utiliser as.data.table(x)? Chaque fois que x n'est pas un list ou data.frame. L'utilisation la plus courante concerne les matrices.

15
Nathan Werth