Facteurs dans R: plus qu'un ennui?

95

L'un des types de données de base dans R est les facteurs. D'après mon expérience, les facteurs sont essentiellement une douleur et je ne les utilise jamais. Je me convertis toujours en personnages. J'ai l'impression de manquer quelque chose.

Existe-t-il des exemples importants de fonctions qui utilisent des facteurs comme variables de regroupement lorsque le type de données de facteur devient nécessaire? Y a-t-il des circonstances particulières dans lesquelles je devrais utiliser des facteurs?

JD Long
la source
7
J'ajoute ce commentaire pour les utilisateurs débutants de R qui sont susceptibles de trouver cette question. J'ai récemment écrit un article de blog qui compile une grande partie des informations des réponses ci-dessous dans un didacticiel expliquant quand, comment et pourquoi utiliser des facteurs dans R. gormanalysis.com/?p=115
Ben
J'avais toujours supposé que les facteurs étaient stockés plus efficacement que les caractères - comme si chaque entrée était un pointeur vers le niveau. Mais en le testant pour écrire ceci, j'ai découvert que ce n'était pas vrai!
isomorphismes
2
@isomorphismes bien, qui utilisé pour être vrai, dans les premiers jours de R, mais cela a changé. Voir ce billet de blog: simplystatistics.org/2015/07/24/…
MichaelChirico
4
5+ ans plus tard, cette "stringsAsFactors: Une biographie non autorisée" a été écrite: simplystatistics.org/2015/07/24
JD Long

Réponses:

49

Vous devez utiliser des facteurs. Oui, ils peuvent être une douleur, mais ma théorie est que 90% de la raison pour laquelle ils sont une douleur est parce que dans read.tableet read.csv, l'argument stringsAsFactors = TRUEpar défaut (et la plupart des utilisateurs manquent cette subtilité). Je dis qu'ils sont utiles parce que les packages d'ajustement de modèle comme lme4 utilisent des facteurs et des facteurs ordonnés pour ajuster différemment les modèles et déterminer le type de contrastes à utiliser. Et les packages graphiques les utilisent également pour les regrouper. ggplotet la plupart des fonctions d'ajustement de modèle contraignent les vecteurs de caractères à des facteurs, de sorte que le résultat est le même. Cependant, vous vous retrouvez avec des avertissements dans votre code:

lm(Petal.Length ~ -1 + Species, data=iris)

# Call:
# lm(formula = Petal.Length ~ -1 + Species, data = iris)

# Coefficients:
#     Speciessetosa  Speciesversicolor   Speciesvirginica  
#             1.462              4.260              5.552  

iris.alt <- iris
iris.alt$Species <- as.character(iris.alt$Species)
lm(Petal.Length ~ -1 + Species, data=iris.alt)

# Call:
# lm(formula = Petal.Length ~ -1 + Species, data = iris.alt)

# Coefficients:
#     Speciessetosa  Speciesversicolor   Speciesvirginica  
#             1.462              4.260              5.552  

Message d'avertissement: Dans model.matrix.default(mt, mf, contrasts):

variable Speciesconvertie enfactor

Une chose délicate est le tout drop=TRUE. Dans les vecteurs, cela fonctionne bien pour supprimer les niveaux de facteurs qui ne sont pas dans les données. Par exemple:

s <- iris$Species
s[s == 'setosa', drop=TRUE]
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa
s[s == 'setosa', drop=FALSE]
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa versicolor virginica

Cependant , avec data.frames, le comportement de [.data.frame()est différent: voir cet e-mail ou ?"[.data.frame". Utiliser drop=TRUEon data.frames ne fonctionne pas comme vous l'imaginez:

x <- subset(iris, Species == 'setosa', drop=TRUE)  # susbetting with [ behaves the same way
x$Species
#  [1] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [11] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [21] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [31] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# [41] setosa setosa setosa setosa setosa setosa setosa setosa setosa setosa
# Levels: setosa versicolor virginica

Heureusement, vous pouvez facilement supprimer des facteurs avec droplevels()pour supprimer les niveaux de facteurs inutilisés pour un facteur individuel ou pour chaque facteur dans a data.frame(depuis R 2.12):

x <- subset(iris, Species == 'setosa')
levels(x$Species)
# [1] "setosa"     "versicolor" "virginica" 
x <- droplevels(x)
levels(x$Species)
# [1] "setosa"

Voici comment éviter que les niveaux que vous avez sélectionnés ne figurent dans les ggplotlégendes.

En interne, les factors sont des entiers avec un vecteur de caractère au niveau de l'attribut (voir attributes(iris$Species)et class(attributes(iris$Species)$levels)), qui est propre. Si vous deviez changer un nom de niveau (et que vous utilisiez des chaînes de caractères), ce serait une opération beaucoup moins efficace. Et je change beaucoup de noms de niveau, surtout pour les ggplotlégendes. Si vous simulez des facteurs avec des vecteurs de caractères, vous risquez de ne modifier qu'un seul élément et de créer accidentellement un nouveau niveau distinct.

Vince
la source
1
stringsAsFactorsn'est pas une fonction.
IRTFM
30

les facteurs ordonnés sont géniaux, si j'aime les oranges et que je déteste les pommes mais que les raisins ne me dérangent pas, je n'ai pas besoin de gérer un index étrange pour le dire:

d <- data.frame(x = rnorm(20), f = sample(c("apples", "oranges", "grapes"), 20, replace = TRUE, prob = c(0.5, 0.25, 0.25)))
d$f <- ordered(d$f, c("apples", "grapes", "oranges"))
d[d$f >= "grapes", ]
mdsumner
la source
c'est une application soignée. Jamais pensé à ça.
JD Long
Qu'est-ce que le d$f <- ordered(d$f, c("apples", "grapes", "oranges"))faire? J'aurais deviné qu'il les a commandés dans la trame de données, mais après avoir exécuté cette ligne et imprimé la trame de données, rien ne change. Impose-t-il simplement un ordre interne même si l'ordre imprimé ne change pas?
Addem
... Ouais, je pense que ce que j'ai écrit était quelque chose comme une phrase correcte. Si je comprends votre point de vue, vous nous montrez que vous pouvez attribuer un ordre sur des facteurs, ce que vous ne pouvez pas faire pour les chaînes.
Addem
4
order () crée un ordre arbitraire à partir de toutes les valeurs - dans l'ordre où vous dites qu'elles sont ordonnées. C'est dommage que j'utilise des valeurs triées lexicographiquement, c'est une coïncidence. Par exemple, j'utilise ceci pour les données où "Z" est mauvais, "3" est bon, mais les étiquettes ne sont ni numériques ni alphabétiques - donc je suis ordonné (données, c ("Z", "B", "A", " 0 "," 1 "," 2 "," 3 ")) et alors je peux juste faire des données>" A "et c'est des jours heureux.
mdsumner
19

A factorest le plus analogue à un type énuméré dans d'autres langues. Son utilisation appropriée est pour une variable qui ne peut prendre que l'un des ensembles de valeurs prescrits. Dans ces cas, toutes les valeurs autorisées possibles peuvent ne pas être présentes dans un ensemble particulier de données et les niveaux «vides» reflètent exactement cela.

Prenons quelques exemples. Pour certaines données collectées partout aux États-Unis, l'état doit être enregistré comme un facteur. Dans ce cas, le fait qu'aucune affaire n'a été collectée auprès d'un État particulier est pertinent. Il aurait pu y avoir des données de cet état, mais il est arrivé (pour une raison quelconque, qui peut être une raison d'intérêt) de ne pas l'être. Si la ville natale était recueillie, ce ne serait pas un facteur. Il n'y a pas un ensemble prédéfini de villes natales possibles. Si les données étaient collectées auprès de trois villes plutôt qu'au niveau national, la ville serait un facteur: il y a trois choix qui ont été donnés au départ et si aucun cas / donnée pertinent n'a été trouvé dans l'une de ces trois villes, c'est pertinent.

D'autres aspects de factors, comme fournir un moyen de donner un ordre de tri arbitraire à un ensemble de chaînes, sont des caractéristiques secondaires utiles de factors, mais ne sont pas la raison de leur existence.

Brian Diggs
la source
3
+1. Brian, je pense que vous avez frappé dans le mille avec des niveaux de capture absents des données.
Ricardo Saporta
13

Les facteurs sont fantastiques quand on fait une analyse statistique et qu'on explore réellement les données. Cependant, avant cela, lorsque l'on lit, nettoie, dépanne, fusionne et manipule généralement les données, les facteurs sont une douleur totale. Plus récemment, comme au cours des dernières années, de nombreuses fonctions se sont améliorées pour mieux gérer les facteurs. Par exemple, rbind joue bien avec eux. Je trouve toujours une nuisance totale d'avoir laissé des niveaux vides après une fonction de sous-ensemble.

#drop a whole bunch of unused levels from a whole bunch of columns that are factors using gdata
require(gdata)
drop.levels(dataframe)

Je sais qu'il est simple de recoder les niveaux d'un facteur et de réorganiser les étiquettes et il existe également de merveilleuses façons de réorganiser les niveaux. Mon cerveau ne peut tout simplement pas s'en souvenir et je dois le réapprendre à chaque fois que je l'utilise. Le recodage devrait être beaucoup plus facile qu'il ne l'est.

Les fonctions de chaîne de R sont assez faciles et logiques à utiliser. Donc, lors de la manipulation, je préfère généralement les personnages aux facteurs.

Farrel
la source
1
Avez-vous des exemples d'analyses de statistiques utilisant des facteurs?
JD Long
3
il existe maintenant une fonction base-R droplevels(). Et il ne réorganise pas les facteurs par défaut.
Ben Bolker
6

Quel titre sournois!

Je pense que de nombreuses fonctions d'estimation vous permettent d'utiliser des facteurs pour définir facilement des variables fictives ... mais je ne les utilise pas pour cela.

Je les utilise quand j'ai de très grands vecteurs de caractères avec peu d'observations uniques. Cela peut réduire la consommation de mémoire, en particulier si les chaînes du vecteur de caractères sont plus longues.

PS - Je plaisante sur le titre. J'ai vu votre tweet. ;-)

Joshua Ulrich
la source
1
Vous ne les utilisez donc que pour économiser de l'espace de stockage. Ça a du sens.
JD Long
13
Au moins c'était le cas ;-). Mais il y a quelques versions R, le stockage des caractères a été réécrit pour être haché en interne, donc une partie de cet argument historique est désormais nulle. Les facteurs fixes sont très utiles pour le regroupement et la modélisation.
Dirk Eddelbuettel
1
Selon ?factorelle était R-2.6.0 et il dit, "les valeurs entières sont stockées dans 4 octets alors que chaque référence à une chaîne de caractères a besoin d'un pointeur de 4 ou 8 octets." Économiseriez-vous de l'espace en convertissant en facteur si la chaîne de caractères avait besoin de 8 octets?
Joshua Ulrich
2
N <- 1000; a <- échantillon (c ("a", "b", "c"), N, replace = TRUE); print (object.size (a), units = "Kb"); print (object.size (factor (a)), units = "Kb"); 8 Kb 4,5 Kb donc il semble encore économiser de l'espace.
Eduardo Leoni
2
@Eduardo j'ai 4 Ko contre 4,2 Ko. Car N=100000j'ai 391,5 Kb contre 391,8 Kb. Le facteur prend donc un peu plus de mémoire.
Marek
1

Les facteurs sont un excellent moteur de badges "cas uniques". Je l'ai mal recréé plusieurs fois, et malgré quelques rides occasionnelles, elles sont extrêmement puissantes.

library(dplyr)
d <- tibble(x = sample(letters[1:10], 20, replace = TRUE))

## normalize this table into an indexed value across two tables
id <- tibble(x_u = sort(unique(d$x))) %>% mutate(x_i = row_number())
di <- tibble(x_i = as.integer(factor(d$x)))


## reconstruct d$x when needed
d2 <- inner_join(di, id) %>% transmute(x = x_u)
identical(d, d2)
## [1] TRUE

S'il y a une meilleure façon de faire cette tâche, j'adorerais la voir, je ne vois pas cette capacité de factordiscussion.

mdsumner
la source
-2

tapply (et agrégé ) dépendent de facteurs. Le rapport information / effort de ces fonctions est très élevé.

Par exemple, dans une seule ligne de code (l'appel à tapply ci-dessous), vous pouvez obtenir le prix moyen des diamants par taille et couleur:

> data(diamonds, package="ggplot2")

> head(dm)

   Carat     Cut    Clarity Price Color
1  0.23     Ideal     SI2   326     E
2  0.21   Premium     SI1   326     E
3  0.23      Good     VS1   327     E


> tx = with(diamonds, tapply(X=Price, INDEX=list(Cut=Cut, Color=Color), FUN=mean))

> a = sort(1:diamonds(tx)[2], decreasing=T)  # reverse columns for readability

> tx[,a]

         Color
Cut         J    I    H    G    F    E    D
Fair      4976 4685 5136 4239 3827 3682 4291
Good      4574 5079 4276 4123 3496 3424 3405
Very Good 5104 5256 4535 3873 3779 3215 3470
Premium   6295 5946 5217 4501 4325 3539 3631
Ideal     4918 4452 3889 3721 3375 2598 2629
doug
la source
7
Ce n'est pas un bon exemple, car tous ces exemples fonctionneraient également avec des chaînes.
hadley