2 votes

Extraction de lignes définies par plusieurs facteurs avec un grand nombre de niveaux

Ce que je voudrais faire

J'ai un cadre de données avec plusieurs facteurs de regroupement et d'autres données. J'aimerais regrouper les lignes en fonction de ces facteurs et marquer ou extraire toutes les lignes qui appartiennent à des groupes comptant plus d'un membre.

Le problème/la question

J'ai pu trouver une solution (voir l'exemple ci-dessous), mais elle n'est pas pratique en raison d'une inefficacité de interaction() . Même si drop = TRUE le temps d'exécution de interaction() augmente considérablement lorsque le nombre de niveaux augmente. En fin de compte, j'aimerais traiter 10 à 20 facteurs avec jusqu'à 50 000 niveaux sur un data.frame de quelques centaines de milliers de lignes.

Questions : 1) Quelle est l'approche la plus efficace pour résoudre ce problème ? ("Efficace" est mesuré dans cet ordre par le temps d'exécution, la mémoire requise et la lisibilité du code)

Question 2) Quel est le problème avec interaction() ?

L'exemple

# number of rows
nobs <- 100000
# number of levels
nlvl <- 5000

#create two factors with a decent number of levels
fc1 <- factor(sample.int(nlvl, size = nobs, replace = TRUE))
fc2 <- factor(sample.int(nlvl, size = nobs, replace = TRUE))
#package in a data.frame together with some arbitrary data
wdf <- data.frame(fc1, fc2, vals = sample.int(2, size = nobs, replace = TRUE))
#just for information: number of unique combinations of factors, i.e. groups
ngroups <- nrow(unique(wdf[,1:2]))
print(ngroups)

#granular grouping, tt has nobs elements and ngroups levels
tt <- interaction(wdf[,1:2], drop = TRUE)

#grpidx contains for each row the corresponding group (i.e. level of tt)
#observe that length(grpidx) == nobs and max(grpidx) == ngroups
grpidx <- match(tt, levels(tt))
#split into list of groups (containing row indices)
grplst <- split(seq_along(grpidx), grpidx)
#flag groups with more than one member
flg_dup <- vapply(grplst, FUN = function(x)length(x)>1, FUN.VALUE = TRUE)
#collect all row indices of groups with more than one member
dupidx <- unlist(grplst[flg_dup])
#select the corresponding rows
nonunqdf <- cbind(grpidx[dupidx], wdf[dupidx,])

Moment de la ligne tt <- interaction(wdf[,1:2], drop = TRUE)

  • nlvl == 500 : 82 millisecondes
  • nlvl == 5000 : 28 secondes
  • nlvl == 10000 : 233 secondes

2voto

Frank Points 51885

Utilisation de data.table (avec exemple de taille) nobs = 1e5; nlvl = 5e3 comme dans l'OP)...

library(data.table)
setDT(wdf) # convert to data.table in place

system.time(
  res <- wdf[, if (.N > 1) c(g = .GRP, .SD), by=.(fc1, fc2)]
)
# 0.04 seconds

DT[i, j, by] signifie "filtre par i , groupe par by , puis faire j ".

Dans ce cas, nous sommes donc

  1. regroupement par fc1, fc2

  2. en comptant les lignes dans chaque groupe, .N

  3. s'il y a suffisamment de lignes, renvoie le compteur de groupe .GRP avec le sous-ensemble de données, .SD

Véase ?data.table pour une couverture générale de la notation et de la ?.N concernant les symboles spéciaux.

Je recommande de visiter le site web et en parcourant les vignettes pour commencer avec le paquet.


Alternatives. Cette méthode permet de conserver l'ordre original des lignes :

system.time(res2 <- wdf[, `:=`(g = .GRP, n = .N), by=.(fc1, fc2)][n > 1L])
# 0.06 seconds 

Et cette base R échoue :

system.time(res3 <- wdf[ave(vals, fc1, fc2, FUN = length) > 1])
# causes R to freeze while eating all my RAM... 
# probably because of too many factor combos

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X