2017-08-20 1 views
2

Voici un exemple simplifié:factoriser par programme les colonnes sélectionnées dans le cadre de données, la manière ordonnée?

library(tidyverse) 

frame <- tribble(
    ~a, ~b, ~c, 
    1, 1, 2, 
    5, 4, 7, 
    2, 3, 4, 
    3, 1, 6 
) 

key <- tribble(
    ~col, ~name, ~type, ~labels, 
    1, "a", "f",  c("one", "two", "three", "four", "five"), 
    2, "b", "f",  c("uno", "dos", "tres", "cuatro"), 
    3, "c", "f",  1:7 
) 

Y at-il une façon élégante de balayer à travers les colonnes programme en frame et en appliquant la classe spécifique de facteur, en fonction des paramètres de key? Le résultat attendu serait:

# A tibble: 4 x 3 
     a  b  c 
    <fctr> <fctr> <fctr> 
1 one uno  2 
2 five cuatro  7 
3 two tres  4 
4 three uno  6 

La meilleure solution que je l'ai jusqu'à présent est utilise purrr « s map2() mais avec mission qui est l'OMI pas le plus élégant:

frame[key$col] <- map2(key$col, key$labels, 
     function(x, y) factor(frame[[x]], levels = 1:length(y), labels = y)) 

Quelqu'un at-il un plus bien rangé Solution? Notez que mon bloc de données original contient des centaines de colonnes et que j'ai besoin de re-factoriser avec différents niveaux/labels la majorité d'entre eux, donc le processus doit être automatisé.

+0

Hadley a un paquet 'forcats', si cela semble intéressant. – lmo

+0

Merci, je l'ai jeté un coup d'oeil et c'est assez sympa - mais il ne fournit pas directement de fonctions pour manipuler les trames de données comme je le voudrais ... – Krizbi

Répondre

0

Je suis curieux de voir ce que les autres solutions sont proposées pour cela. Ma seule suggestion est de changer légèrement la solution proposée afin qu'il soit plus clair que frame va être modifié d'une certaine façon plutôt que de le laisser dans le corps de la fonction utilisée par map2.

Par exemple, passer frame comme un argument supplémentaire dans l'appel à map2:

frame[key$col] <- map2(key$col, key$labels, 
         function(x, y, z) factor(z[[x]], levels = 1:length(y), labels = y), 
         frame) 

Ou faire la même chose en utilisant l'opérateur de tuyau %>%:

frame[key$col] <- frame %>% 
    { map2(key$col, key$labels, 
     function(x, y, z) factor(z[[x]], levels = 1:length(y), labels = y), .) } 
+0

Merci pour la suggestion, je vais marquer votre message comme une réponse, il semble que ce soit l'approche optimale jusqu'à présent. – Krizbi

0

Je ne sais pas si cette réponse répond à vos exigences d'être bien rangé car elle utilise une vieille boucle for-simple. Mais il fait le travail et à mon avis est facile à lire/comprendre ainsi que raisonnablement rapide.

library(tidyverse) 
frame <- tribble(
~a, ~b, ~c, 
1, 1, 2, 
5, 4, 7, 
2, 3, 4, 
3, 1, 6 
) 

key <- tribble(
~col, ~name, ~type, ~labels, 
1, "a", "f",  c("one", "two", "three", "four", "five"), 
2, "b", "f",  c("uno", "dos", "tres", "cuatro"), 
3, "c", "f",  1:7 
) 

for (i in 1:nrow(key)) { 
var <- key$name[[i]] 
x <- frame[[var]] 
labs <- key$labels[[i]] 
lvls <- 1:max(length(x), length(labs)) # make sure to have the right lengths 

frame <- frame %>% mutate(!! var := factor(x, levels = lvls, labels = labs)) 
} 

frame 
#> # A tibble: 4 x 3 
#>  a  b  c 
#> <fctr> <fctr> <fctr> 
#> 1 one uno  2 
#> 2 five cuatro  7 
#> 3 two tres  4 
#> 4 three uno  6 

L'approche-propre typique serait de remodeler les données pour avoir toutes les variables dans une colonne, puis appliquer une fonction à cette colonne, et enfin au remodelant format original. Cependant, les facteurs n'aiment pas vraiment cela, donc nous devons utiliser d'autres moyens. Les facteurs sont-ils même considérés comme propres?

Modifier

En ce qui concerne mon hypothèse que la boucle for serait similaire à la map2 -fonction, je me suis trompé.

Voici quelques points de repère:

library(microbenchmark) 

frame1 <- frame 
frame2 <- frame 

microbenchmark(
map2 = { 
    frame1[key$col] <- map2(key$col, key$labels, 
          function(x, y) factor(frame[[x]], 
               levels = 1:max(frame[[x]], 
                   length(y)), 
               labels = y)) 
}, 
forloop = { 
    for (i in 1:nrow(key)) { 
    var <- key$name[[i]] 
    x <- frame2[[var]] 
    labs <- key$labels[[i]] 
    lvls <- 1:max(length(x), length(labs)) 
    frame2 <- frame2 %>% mutate(!! var := factor(x, levels = lvls, labels = labs)) 
    } 
} 
) 

# Unit: microseconds 
# expr   min   lq  mean median   uq  max neval cld 
# map2  375.53 416.5805 514.3126 450.825 484.2175 3601.636 100 a 
# forloop 11407.80 12110.0090 12816.6606 12564.176 13425.6840 16632.682 100 b 
+0

Merci @David pour la réponse très élaborée!Il semble cependant que la solution originale que j'ai proposée est un peu plus rapide ... – Krizbi

0

Pour cette question, vous pouvez utiliser une base R code:

(A=`names<-`(data.frame(mapply(function(x,y)x[y],key$labels,frame)),key$name)) 
     a  b c 
1 one uno 2 
2 five cuatro 7 
3 two tres 4 
4 three uno 6 

sapply(A,class) 
    a  b  c 
"factor" "factor" "factor" 
1

Voici une autre solution. Je ne suis pas sûr à quel point c'est "élégant". J'espère que quelqu'un pourra améliorer cela.

suppressPackageStartupMessages(library(tidyverse)) 

frame <- tribble(
    ~a, ~b, ~c, 
    1, 1, 2, 
    5, 4, 7, 
    2, 3, 4, 
    3, 1, 6 
) 

key <- tribble(
    ~col, ~name, ~type, ~labels, 
    1, "a", "f",  c("one", "two", "three", "four", "five"), 
    2, "b", "f",  c("uno", "dos", "tres", "cuatro"), 
    3, "c", "f",  1:7 
) 

colnames(frame) %>% 
    map(~ { 
    factor(pull(frame, .x), 
      levels = 1:length(pluck(key[key$name == .x, "labels"], 1, 1)), 
      labels = pluck(key[key$name == .x, "labels"], 1, 1)) 
    }) %>% 
    set_names(colnames(frame)) %>% 
    as_tibble() 
#> # A tibble: 4 x 3 
#>  a  b  c 
#> <fctr> <fctr> <fctr> 
#> 1 one uno  2 
#> 2 five cuatro  7 
#> 3 two tres  4 
#> 4 three uno  6