163 votes

Moyen le plus rapide de remplacer les NA dans un grand data.table

J'ai un grand de données.table, avec beaucoup de valeurs manquantes dispersés tout au long de son ~200k lignes et 200 colonnes. Je tiens à re code ceux NA valeurs zéros de manière aussi efficace que possible.

Je vois deux options:
1: Convertir les données.cadre, et d'utiliser quelque chose comme ceci
2: une sorte de fraîcheur des données.tableau de réglage de la commande sous

Je vais être heureux avec une assez efficace solution de type 1. Conversion des données.cadre et ensuite de retour à un ensemble de données.table de ne pas prendre trop de temps.

195voto

Matt Dowle Points 20936

Voici une solution en utilisant des données.table' := de l'opérateur, en s'appuyant sur Andrie et Ramnath réponses.

require(data.table)  # v1.6.6
require(gdata)       # v2.8.2

set.seed(1)
dt1 = create_dt(2e5, 200, 0.1)
dim(dt1)
[1] 200000    200    # more columns than Ramnath's answer which had 5 not 200

f_andrie = function(dt) remove_na(dt)

f_gdata = function(dt, un = 0) gdata::NAToUnknown(dt, un)

f_dowle = function(dt) {     # see EDIT later for more elegant solution
    na.replace = function(v,value=0) { v[is.na(v)] = value; v }
    for (i in names(dt))
        eval(parse(text=paste("dt[,",i,":=na.replace(",i,")]")))
}

system.time(a_gdata = f_gdata(dt1)) 
   user  system elapsed 
 18.805  12.301 134.985 

system.time(a_andrie = f_andrie(dt1))
Error: cannot allocate vector of size 305.2 Mb
Timing stopped at: 14.541 7.764 68.285 

system.time(f_dowle(dt1))
  user  system elapsed 
 7.452   4.144  19.590     # EDIT has faster than this

identical(a_gdata, dt1)   
[1] TRUE

Notez que f_dowle mis à jour dt1 par référence. Si une copie locale est nécessaire, alors qu'un appel explicite à l' copy fonction est nécessaire pour faire une copie locale de l'ensemble du jeu de données. les données.le tableau est setkey, key<- et := n'avez pas de copie sur écriture.

Ensuite, nous allons voir où f_dowle est de passer son temps.

Rprof()
f_dowle(dt1)
Rprof(NULL)
summaryRprof()
$by.self
                  self.time self.pct total.time total.pct
"na.replace"           5.10    49.71       6.62     64.52
"[.data.table"         2.48    24.17       9.86     96.10
"is.na"                1.52    14.81       1.52     14.81
"gc"                   0.22     2.14       0.22      2.14
"unique"               0.14     1.36       0.16      1.56
... snip ...

Là, je me concentrerais sur na.replace et is.na, où il y a un peu de vecteur de copies et de vecteur d'analyses. Ceux-ci peuvent assez facilement être éliminé par l'écriture d'un petit na.remplacer C de la fonction qui met à jour NA par référence dans le vecteur. Qui permettrait de réduire au moins de moitié les 20 secondes je pense. Une telle fonction existe dans aucun package R?

La raison en f_andrie d'échec peut être parce qu'il copie l'intégralité de l' dt1, ou crée une logique de la matrice aussi grand que l'ensemble de l' dt1, quelques fois. Les 2 autres méthodes de travail sur une seule colonne à la fois (même si j'ai brièvement regardé NAToUnknown).

EDIT (solution plus élégante, comme demandé par Ramnath dans les commentaires) :

f_dowle2 = function(DT) {
    for (i in names(DT))
        DT[is.na(get(i)),i:=0,with=FALSE]
}

system.time(f_dowle2(dt1))
  user  system elapsed 
 6.468   0.760   7.250   # faster, too

identical(a_gdata, dt1)   
[1] TRUE

Je veux je l'ai fait de cette façon pour commencer!

EDIT2 (plus de 1 an plus tard, maintenant)

Il est également set(). Cela peut être plus rapide si il y a beaucoup de colonne étant bouclé, car elle évite les (petits) les frais généraux de l'appel d' [,:=,] dans une boucle. set est un loopable :=. Voir ?set.

f_dowle3 = function(DT) {
    # either of the following for loops

    # by name :
    for (j in names(DT))
        set(DT,which(is.na(DT[[j]])),j,0)

    # or by number (slightly faster than by name) :
    for (j in seq_len(ncol(DT)))
        set(DT,which(is.na(DT[[j]])),j,0)
}

11voto

Ramnath Points 24798

Voici une solution utilisant NAToUnknown dans le package gdata . J'ai utilisé la solution d'Andrie pour créer un énorme tableau de données et également inclus des comparaisons de temps avec la solution d'Andrie.

 # CREATE DATA TABLE
dt1 = create_dt(2e5, 200, 0.1)

# FUNCTIONS TO SET NA TO ZERO   
f_gdata  = function(dt, un = 0) gdata::NAToUnknown(dt, un)
f_Andrie = function(dt) remove_na(dt)

# COMPARE SOLUTIONS AND TIMES
system.time(a_gdata  <- f_gdata(dt1))

user  system elapsed 
4.224   2.962   7.388 

system.time(a_andrie <- f_Andrie(dt1))

 user  system elapsed 
4.635   4.730  20.060 

identical(a_gdata, g_andrie)  

TRUE
 

6voto

Andrie Points 66979

Ma compréhension est que le secret de la rapide des opérations dans R est d'utiliser des vecteurs (ou des tableaux, qui sont des vecteurs sous le capot.)

Dans cette solution, j'utilise un data.matrix qui est un array mais se comportent un peu comme un data.frame. Parce que c'est un tableau, vous pouvez utiliser un simple vecteur de substitution pour remplacer l' NAs:

Une petite aide de la fonction pour supprimer l' NAs. L'essence est une seule ligne de code. - Je faire cela seulement à mesurer le temps d'exécution.

remove_na <- function(x){
  dm <- data.matrix(x)
  dm[is.na(dm)] <- 0
  data.table(dm)
}

Une petite aide de la fonction pour créer un data.table d'une taille donnée.

create_dt <- function(nrow=5, ncol=5, propNA = 0.5){
  v <- runif(nrow * ncol)
  v[sample(seq_len(nrow*ncol), propNA * nrow*ncol)] <- NA
  data.table(matrix(v, ncol=ncol))
}

Démonstration sur un petit échantillon:

library(data.table)
set.seed(1)
dt <- create_dt(5, 5, 0.5)

dt
            V1        V2        V3        V4        V5
[1,]        NA 0.8983897        NA 0.4976992 0.9347052
[2,] 0.3721239 0.9446753        NA 0.7176185 0.2121425
[3,] 0.5728534        NA 0.6870228 0.9919061        NA
[4,]        NA        NA        NA        NA 0.1255551
[5,] 0.2016819        NA 0.7698414        NA        NA

remove_na(dt)
            V1        V2        V3        V4        V5
[1,] 0.0000000 0.8983897 0.0000000 0.4976992 0.9347052
[2,] 0.3721239 0.9446753 0.0000000 0.7176185 0.2121425
[3,] 0.5728534 0.0000000 0.6870228 0.9919061 0.0000000
[4,] 0.0000000 0.0000000 0.0000000 0.0000000 0.1255551
[5,] 0.2016819 0.0000000 0.7698414 0.0000000 0.0000000

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