Comment convertir un facteur en entier \ numérique sans perte d'informations?

600

Lorsque je convertis un facteur en numérique ou en entier, j'obtiens les codes de niveau sous-jacents, pas les valeurs sous forme de nombres.

f <- factor(sample(runif(5), 20, replace = TRUE))
##  [1] 0.0248644019011408 0.0248644019011408 0.179684827337041 
##  [4] 0.0284090070053935 0.363644931698218  0.363644931698218 
##  [7] 0.179684827337041  0.249704354675487  0.249704354675487 
## [10] 0.0248644019011408 0.249704354675487  0.0284090070053935
## [13] 0.179684827337041  0.0248644019011408 0.179684827337041 
## [16] 0.363644931698218  0.249704354675487  0.363644931698218 
## [19] 0.179684827337041  0.0284090070053935
## 5 Levels: 0.0248644019011408 0.0284090070053935 ... 0.363644931698218

as.numeric(f)
##  [1] 1 1 3 2 5 5 3 4 4 1 4 2 3 1 3 5 4 5 3 2

as.integer(f)
##  [1] 1 1 3 2 5 5 3 4 4 1 4 2 3 1 3 5 4 5 3 2

Je dois y recourir pastepour obtenir les vraies valeurs:

as.numeric(paste(f))
##  [1] 0.02486440 0.02486440 0.17968483 0.02840901 0.36364493 0.36364493
##  [7] 0.17968483 0.24970435 0.24970435 0.02486440 0.24970435 0.02840901
## [13] 0.17968483 0.02486440 0.17968483 0.36364493 0.24970435 0.36364493
## [19] 0.17968483 0.02840901

Existe-t-il une meilleure façon de convertir un facteur en numérique?

Adam SO
la source
6
Les niveaux d'un facteur sont de toute façon stockés en tant que type de données de caractère ( attributes(f)), donc je ne pense pas qu'il y ait un problème avec as.numeric(paste(f)). Peut-être serait-il préférable de penser pourquoi (dans le contexte spécifique) vous obtenez un facteur en premier lieu, et essayez d'arrêter cela. Par exemple, l' decargument read.tableest-il correctement défini?
CJB
Si vous utilisez une trame de données, vous pouvez utiliser la conversion de hablar. df %>% convert(num(column)). Ou si vous avez un vecteur de facteur que vous pouvez utiliseras_reliable_num(factor_vector)
davsjob

Réponses:

713

Voir la section Avertissement de ?factor:

En particulier, as.numericappliqué à un facteur n'a pas de sens et peut se produire par contrainte implicite. Pour transformer un facteur fà approximativement ses valeurs numériques d'origine, il as.numeric(levels(f))[f]est recommandé et légèrement plus efficace que as.numeric(as.character(f)).

La FAQ sur R contient des conseils similaires .


Pourquoi est as.numeric(levels(f))[f]plus efficace que as.numeric(as.character(f))?

as.numeric(as.character(f))est efficace as.numeric(levels(f)[f]), vous effectuez donc la conversion en numérique sur des length(x)valeurs plutôt que sur des nlevels(x)valeurs. La différence de vitesse sera plus apparente pour les vecteurs longs avec peu de niveaux. Si les valeurs sont pour la plupart uniques, il n'y aura pas beaucoup de différence de vitesse. Quelle que soit la façon dont vous effectuez la conversion, il est peu probable que cette opération soit le goulot d'étranglement dans votre code, alors ne vous en faites pas trop.


Quelques horaires

library(microbenchmark)
microbenchmark(
  as.numeric(levels(f))[f],
  as.numeric(levels(f)[f]),
  as.numeric(as.character(f)),
  paste0(x),
  paste(x),
  times = 1e5
)
## Unit: microseconds
##                         expr   min    lq      mean median     uq      max neval
##     as.numeric(levels(f))[f] 3.982 5.120  6.088624  5.405  5.974 1981.418 1e+05
##     as.numeric(levels(f)[f]) 5.973 7.111  8.352032  7.396  8.250 4256.380 1e+05
##  as.numeric(as.character(f)) 6.827 8.249  9.628264  8.534  9.671 1983.694 1e+05
##                    paste0(x) 7.964 9.387 11.026351  9.956 10.810 2911.257 1e+05
##                     paste(x) 7.965 9.387 11.127308  9.956 11.093 2419.458 1e+05
Joshua Ulrich
la source
4
Pour les horaires, voir cette réponse: stackoverflow.com/questions/6979625/…
Ari B. Friedman
3
Merci beaucoup pour votre solution. Puis-je demander pourquoi le as.numeric (niveaux (f)) [f] est plus précis et plus rapide? Merci.
Sam
7
@Sam as.character (f) nécessite une "recherche primitive" pour trouver la fonction as.character.factor (), qui est définie comme as.numeric (niveaux (f)) [f].
Jonathan
12
quand appliquer as.numeric (niveaux (f)) [f] OU as.numeric (as.character (f)), j'ai un message d'avertissement: Message d'avertissement: NAs introduits par coercition. Savez-vous où pourrait être le problème? Merci !
Maycca
@maycca avez-vous surmonté ce problème?
user08041991
91

R a un certain nombre de fonctions pratiques (non documentées) pour convertir les facteurs:

  • as.character.factor
  • as.data.frame.factor
  • as.Date.factor
  • as.list.factor
  • as.vector.factor
  • ...

Mais ennuyeux, il n'y a rien pour gérer le facteur -> conversion numérique . Dans le prolongement de la réponse de Joshua Ulrich, je suggère de surmonter cette omission avec la définition de votre propre fonction idiomatique:

as.numeric.factor <- function(x) {as.numeric(levels(x))[x]}

que vous pouvez stocker au début de votre script, ou mieux encore dans votre .Rprofilefichier.

Jealie
la source
14
Il n'y a rien pour gérer la conversion de facteur en entier (ou numérique) car il est prévu qu'il as.integer(factor)renvoie les codes entiers sous-jacents (comme indiqué dans la section des exemples de ?factor). Il est probablement correct de définir cette fonction dans votre environnement global, mais vous pouvez provoquer des problèmes si vous l'enregistrez réellement en tant que méthode S3.
Joshua Ulrich
1
C'est un bon point et je suis d'accord: une redéfinition complète de la conversion facteur-> numérique est susceptible de gâcher beaucoup de choses. Je me suis retrouvé à écrire la factor->numericconversion encombrante beaucoup avant de réaliser qu'il s'agit en fait d'une lacune de R: une fonction de commodité devrait être disponible ... L'appeler a du as.numeric.factorsens pour moi, mais YMMV.
Jealie
4
Si vous vous trouvez le faire beaucoup , alors vous devriez le faire en amont quelque chose pour éviter tout rassemblement.
Joshua Ulrich
2
as.numeric.factor renvoie NA?
jO.
@jO.: dans les cas où vous avez utilisé quelque chose comme v=NA;as.numeric.factor(v)ou v='something';as.numeric.factor(v)alors il le faudrait, sinon vous avez quelque chose de bizarre qui se passe quelque part.
Jealie
33

La façon la plus simple serait d'utiliser la unfactorfonction du package varhandle

unfactor(your_factor_variable)

Cet exemple peut être un démarrage rapide:

x <- rep(c("a", "b", "c"), 20)
y <- rep(c(1, 1, 0), 20)

class(x)  # -> "character"
class(y)  # -> "numeric"

x <- factor(x)
y <- factor(y)

class(x)  # -> "factor"
class(y)  # -> "factor"

library(varhandle)
x <- unfactor(x)
y <- unfactor(y)

class(x)  # -> "character"
class(y)  # -> "numeric"
Mehrad Mahmoudian
la source
La unfactorfonction est d'abord convertie en type de données de caractères, puis reconvertie en numérique. Tapez unfactorsur la console et vous pouvez le voir au milieu de la fonction. Par conséquent, cela ne donne pas vraiment une meilleure solution que ce que le demandeur avait déjà.
CJB
Cela dit, les niveaux d'un facteur sont de toute façon de type caractère, donc rien n'est perdu par cette approche.
CJB
La unfactorfonction s'occupe des choses qui ne peuvent pas être converties en numérique. Consultez les exemples danshelp("unfactor")
Mehrad Mahmoudian
2
@Selrac J'ai mentionné que cette fonction est disponible dans le package varhandle , ce qui signifie que vous devez d'abord charger le package ( library("varhandle")) (comme je l'ai mentionné dans la première ligne de ma réponse !!)
Mehrad Mahmoudian
1
@Gregor ajouter une légère dépendance ne nuit généralement pas et bien sûr, si vous cherchez le moyen le plus efficace, écrire le code vous-même pourrait être plus rapide. mais comme vous pouvez également le voir dans votre commentaire, ce n'est pas anodin car vous mettez également le as.numeric()et as.character()dans un mauvais ordre;) Ce que fait votre bloc de code est de transformer l'index de niveau du facteur en une matrice de caractères, donc ce que vous aurez au et est un vecteur de caractères qui contient des nombres qui ont été attribués à un certain niveau de votre facteur. Les fonctions de ce package sont là pour éviter ces confusions
Mehrad Mahmoudian
23

Remarque: cette réponse particulière n'est pas pour convertir des facteurs à valeur numérique en chiffres, c'est pour convertir des facteurs catégoriels en leurs numéros de niveau correspondants.


Chaque réponse dans ce post n'a pas généré de résultats pour moi, les NA étaient générées.

y2<-factor(c("A","B","C","D","A")); 
as.numeric(levels(y2))[y2] 
[1] NA NA NA NA NA Warning message: NAs introduced by coercion

Ce qui a fonctionné pour moi, c'est ceci -

as.integer(y2)
# [1] 1 2 3 4 1
Indi
la source
Êtes-vous sûr d'avoir eu un facteur? Regardez cet exemple. y<-factor(c("5","15","20","2")); unclass(y) %>% as.numericCela renvoie 4,1,3,2, pas 5,15,20,2. Cela ressemble à des informations incorrectes.
MrFlick
Ok, c'est similaire à ce que j'essayais de faire aujourd'hui: - y2 <-factor (c ("A", "B", "C", "D", "A")); as.numeric (niveaux (y2)) [y2] [1] NA NA NA NA NA Message d'avertissement: les NA introduites par coercition alors que déclassement (y2)%>% as.numeric m'a donné les résultats dont j'avais besoin.
Indi
4
D'accord, ce n'est pas la question qui a été posée ci-dessus. Dans cette question, les niveaux de facteurs sont tous "numériques". Dans votre cas, as.numeric(y)aurait dû très bien fonctionner, pas besoin de unclass(). Mais encore une fois, ce n'est pas de cela qu'il s'agissait. Cette réponse n'est pas appropriée ici.
MrFlick
3
Eh bien, j'espère vraiment que cela aidera quelqu'un qui était pressé comme moi et ne lira que le titre!
Indi
1
Si vous avez des caractères représentant les entiers comme facteurs, c'est celui que je recommanderais. c'est le seul qui a fonctionné pour moi.
aimme
9

C'est possible seulement dans le cas où les étiquettes de facteurs correspondent aux valeurs d'origine. Je vais l'expliquer avec un exemple.

Supposons que les données soient vectorielles x:

x <- c(20, 10, 30, 20, 10, 40, 10, 40)

Je vais maintenant créer un facteur avec quatre étiquettes:

f <- factor(x, levels = c(10, 20, 30, 40), labels = c("A", "B", "C", "D"))

1) xest de type double, fest de type entier. Il s'agit de la première perte inévitable d'informations. Les facteurs sont toujours stockés sous forme d'entiers.

> typeof(x)
[1] "double"
> typeof(f)
[1] "integer"

2) Il n'est pas possible de revenir aux valeurs d'origine (10, 20, 30, 40) ayant uniquement fdisponible. Nous pouvons voir que fne contient que les valeurs entières 1, 2, 3, 4 et deux attributs - la liste des étiquettes ("A", "B", "C", "D") et l'attribut de classe "facteur". Rien de plus.

> str(f)
 Factor w/ 4 levels "A","B","C","D": 2 1 3 2 1 4 1 4
> attributes(f)
$levels
[1] "A" "B" "C" "D"

$class
[1] "factor"

Pour revenir aux valeurs d'origine, nous devons connaître les valeurs des niveaux utilisés pour créer le facteur. Dans ce cas c(10, 20, 30, 40). Si nous connaissons les niveaux d'origine (dans le bon ordre), nous pouvons revenir aux valeurs d'origine.

> orig_levels <- c(10, 20, 30, 40)
> x1 <- orig_levels[f]
> all.equal(x, x1)
[1] TRUE

Et cela ne fonctionnera que si des étiquettes ont été définies pour toutes les valeurs possibles dans les données d'origine.

Donc, si vous avez besoin des valeurs d'origine, vous devez les conserver. Sinon, il y a de fortes chances qu'il ne soit pas possible d'y revenir uniquement à partir d'un facteur.

djhurio
la source
2

Vous pouvez utiliser hablar::convertsi vous avez un bloc de données. La syntaxe est simple:

Exemple df

library(hablar)
library(dplyr)

df <- dplyr::tibble(a = as.factor(c("7", "3")),
                    b = as.factor(c("1.5", "6.3")))

Solution

df %>% 
  convert(num(a, b))

vous donne:

# A tibble: 2 x 2
      a     b
  <dbl> <dbl>
1    7.  1.50
2    3.  6.30

Ou si vous voulez qu'une colonne soit un entier et un numérique:

df %>% 
  convert(int(a),
          num(b))

résulte en:

# A tibble: 2 x 2
      a     b
  <int> <dbl>
1     7  1.50
2     3  6.30
davsjob
la source
0

On dirait que la solution as.numeric (levels (f)) [f] ne fonctionne plus avec R 4.0.

Solution alternative:

factor2number <- function(x){
    data.frame(levels(x), 1:length(levels(x)), row.names = 1)[x, 1]
}

factor2number(yourFactor)
Life_Searching_Steps
la source
-1

D'après les nombreuses réponses que j'ai pu lire, la seule façon donnée était d'augmenter le nombre de variables en fonction du nombre de facteurs. Si vous avez une variable "animal de compagnie" avec les niveaux "chien" et "chat", vous vous retrouvez avec pet_dog et pet_cat.

Dans mon cas, je voulais rester avec le même nombre de variables, en traduisant simplement la variable facteur en une variable numérique, d'une manière qui peut s'appliquer à de nombreuses variables à plusieurs niveaux, de sorte que cat = 1 et dog = 0 par exemple.

Veuillez trouver la solution correspondante ci-dessous:

crime <- data.frame(city = c("SF", "SF", "NYC"),
                    year = c(1990, 2000, 1990),
                    crime = 1:3)

indx <- sapply(crime, is.factor)

crime[indx] <- lapply(crime[indx], function(x){ 
  listOri <- unique(x)
  listMod <- seq_along(listOri)
  res <- factor(x, levels=listOri)
  res <- as.numeric(res)
  return(res)
}
)
Xavier Prudent
la source
-2

tard dans le jeu, par accident, j'ai trouvé trimws()peut se convertir factor(3:5)en c("3","4","5"). Ensuite, vous pouvez appeler as.numeric(). C'est:

as.numeric(trimws(x_factor_var))
Jerry T
la source
3
Y a-t-il une raison pour laquelle vous recommanderiez d'utiliser trimwsOver as.charactercomme décrit dans la réponse acceptée? Il me semble que, sauf si vous aviez réellement un espace à supprimer, vous trimwsallez simplement faire un tas de travaux d'expression régulière inutiles pour retourner le même résultat.
MrFlick
as.numeric (niveaux (f)) [f] est peut-être un peu déroutant et difficile à retenir pour les débutants. trimws ne fait pas de mal.
Jerry T