web-dev-qa-db-fra.com

Existe-t-il un moyen plus efficace de remplacer NULL par NA dans une liste?

Je rencontre assez souvent des données structurées comme ceci:

employees <- list(
    list(id = 1,
             dept = "IT",
             age = 29,
             sportsteam = "softball"),
    list(id = 2,
             dept = "IT",
             age = 30,
             sportsteam = NULL),
    list(id = 3,
             dept = "IT",
             age = 29,
             sportsteam = "hockey"),
    list(id = 4,
             dept = NULL,
             age = 29,
             sportsteam = "softball"))

Dans de nombreux cas, ces listes peuvent contenir des dizaines de millions d'éléments, de sorte que les problèmes de mémoire et d'efficacité sont toujours une préoccupation.

Je voudrais transformer la liste en un dataframe mais si je lance:

library(data.table)
employee.df <- rbindlist(employees)

J'obtiens des erreurs à cause des valeurs NULL. Ma stratégie normale consiste à utiliser une fonction comme:

nullToNA <- function(x) {
    x[sapply(x, is.null)] <- NA
    return(x)
}

puis:

employees <- lapply(employees, nullToNA)
employee.df <- rbindlist(employees)

qui revient

   id dept age sportsteam
1:  1   IT  29   softball
2:  2   IT  30         NA
3:  3   IT  29     hockey
4:  4   NA  29   softball

Cependant, la fonction nullToNA est très lente lorsqu'elle est appliquée à 10 millions de cas, donc ce serait bien s'il y avait une approche plus efficace.

Un point qui semble ralentir le processus: la fonction is.null ne peut être appliquée qu'à un seul élément à la fois (contrairement à is.na qui peut parcourir une liste complète en une seule fois).

Des conseils sur la façon de faire cette opération efficacement sur un grand ensemble de données?

32
Jon M

De nombreux problèmes d'efficacité dans R sont résolus en changeant d'abord les données d'origine sous une forme qui rend les processus qui suivent aussi rapides et faciles que possible. Il s'agit généralement d'une forme matricielle.

Si vous réunissez toutes les données avec rbind, votre fonction nullToNA n'a plus besoin de rechercher dans les listes imbriquées, et donc sapply remplit sa fonction (en regardant dans une matrice) plus efficacement. En théorie, cela devrait accélérer le processus.

Bonne question, au fait.

> dat <- do.call(rbind, lapply(employees, rbind))
> dat
     id dept age sportsteam
[1,] 1  "IT" 29  "softball"
[2,] 2  "IT" 30  NULL      
[3,] 3  "IT" 29  "hockey"  
[4,] 4  NULL 29  "softball"

> nullToNA(dat)
     id dept age sportsteam
[1,] 1  "IT" 29  "softball"
[2,] 2  "IT" 30  NA        
[3,] 3  "IT" 29  "hockey"  
[4,] 4  NA   29  "softball"
16
Rich Scriven

Une approche en deux étapes crée une trame de données après l'avoir peignée avec rbind:

employee.df<-data.frame(do.call("rbind",employees))

Remplacez maintenant le NULL, j'utilise "NULL" car R ne met pas NULL lorsque vous chargez les données et le lit comme caractère lorsque vous le chargez.

employee.df.withNA <- sapply(employee.df, function(x) ifelse(x == "NULL", NA, x))
5
infominer

Une solution bidirectionnelle que je trouve plus facile à lire consiste à écrire une fonction qui fonctionne sur un seul élément et à la mapper sur tous vos NULL.

Je vais utiliser l'approche rbind et lapply de @ rich-scriven pour créer une matrice, puis la transformer en trame de données.

library(magrittr)

dat <- do.call(rbind, lapply(employees, rbind)) %>% 
  as.data.frame()

dat
#>   id dept age sportsteam
#> 1  1   IT  29   softball
#> 2  2   IT  30       NULL
#> 3  3   IT  29     hockey
#> 4  4 NULL  29   softball

Ensuite, nous pouvons utiliser purrr::modify_depth() à une profondeur de 2 pour appliquer replace_x()

replace_x <- function(x, replacement = NA_character_) {
  if (length(x) == 0 || length(x[[1]]) == 0) {
    replacement
  } else {
    x
  }
}

out <- dat %>% 
  purrr::modify_depth(2, replace_x)

out
#>   id dept age sportsteam
#> 1  1   IT  29   softball
#> 2  2   IT  30         NA
#> 3  3   IT  29     hockey
#> 4  4   NA  29   softball
2
amanda

Toutes ces solutions (je pense) cachent le fait que le tableau de données est toujours une perte de listes et non une liste de vecteurs (je n'ai pas remarqué dans mon application non plus jusqu'à ce qu'il commence à lancer des erreurs inattendues pendant := ). Essaye ça:

data.table(t(sapply(employees, function(x) unlist(lapply(x, function(x) ifelse(is.null(x),NA,x))))))

Je crois que cela fonctionne bien, mais je ne sais pas s'il souffrira de lenteur et pourra être optimisé davantage.

1
Barbara Bukhvalova

Je trouve souvent que les fonctions do.call() sont difficiles à lire. Une solution que j'utilise quotidiennement (avec une sortie MySQL contenant "NULL" valeurs de caractères):

NULL2NA <- function(df) {
  df[, 1:length(df)][df[, 1:length(df)] == 'NULL'] <- NA
  return(df)
}

Mais pour toutes les solutions: n'oubliez pas que NA ne peut pas être utilisé pour le calcul sans na.rm = TRUE, mais avec NULL c'est possible. NaN donne le même problème. Par exemple:

> mean(c(1, 2, 3))
2

> mean(c(1, 2, NA, 3))
NA

> mean(c(1, 2, NULL, 3))
2

> mean(c(1, 2, NaN, 3))
NaN
1
MS Berends