Les variables catégoriques peuvent prendre des valeurs numériques ou être des chaines de caractères. Pour les utiliser de manière correcte en modélisation statistique et tester leur impact sur d’autres variables, il est nécessaire de les transformer en facteurs qui doivent être des entiers, ce qui peut nécessiter une conversion. Certaines fonctions sous R le font de manière cachée : c’est le cas de boxplot!
Exemple:
Ci-dessous, on affiche la distribution de la consommation en carburant (variable mpg
= miles per gallon) en fonction du nombre de cylindres (variable cyl
). En statistiques, le nombre de cylindres est un facteur avec 3 niveaux 4,6 et 8). Mais quand on regarde la structure du dataframe mtcasr
le type est numérique et non un facteur.
str(mtcars) boxplot(data=mtcars, mpg~cyl)
Si transformez la variable "cyl" en variable avec une chaîne de caractères en ajoutant par exemple "cyl_" devant la valeur numérique, la fonction boxplot sera toujours capable de traiter cette variable comme un facteur.
class(paste("cyl_", mtcars$cyl))
boxplot(mtcars$mpg ~ paste("cyl_", mtcars$cyl))
factor()
:¶La fonction factor()
permet de convertir vous-même un vecteur (une variable) en un facteur qui encode les différentes valeurs possibles de la variable.
Sous R, les valeurs entières de 1 à n correspondront aux index du facteur. Ces valeurs d’index ou « codes » sont attribuées par défaut selon l’ordre alphabétique ou numérique (cf. help(factor)) des différentes valeurs possibles, appelés niveaux ou levels de la variable.
Elles sont elles-mêmes renumérotées ou ré-indexées de 1 à n ! Par exemple, si vous avez les entiers de 4 à 10 dans un vecteur v1 :
v1<-4:10
print(factor(v1))
[1] 4 5 6 7 8 9 10 Levels: 4 5 6 7 8 9 10
str(factor(v1))
Factor w/ 7 levels "4","5","6","7",..: 1 2 3 4 5 6 7
La factorisation de v1 a donc créé un 1 facteur à 7 niveaux correspondant aux valeurs catégoriques “4” à “10” mais indexées 1 à 7 !
Elles sont indexées de 1 à n : Par exemple, si je factorise le vecteur suivant contenant 4 valeurs :
v2 <- c("Homme","Femme","Homme","Homme")
print(factor(v2))
[1] Homme Femme Homme Homme Levels: Femme Homme
Les valeurs “Homme” et “Femme” auront respectivement comme index les valeurs 2 et 1 car “Femme” sort avant “Homme” selon l’ordre alphabétique. Les valeurs 1, 3 et 4 du vecteur v2 seront donc codées 2, tandis que la 2ème sera codée 1, ce que vous voyez en regardant la structure du facteur créé :
str(factor(v2))
Factor w/ 2 levels "Femme","Homme": 2 1 2 2
Mais ce n’est cependant pas nécessairement le code que vous voulez garder : souvent on met « 2 » pour les femmes et « 1 » pour les « hommes ». De même, “cas” et “controles” seront convertis en 1 et 2. Or il est souvent préférable de donner la valeur 1 à la référence à laquelle on compare et donc on peut souhaiter imposer que 1 corresponde aux “controles” et 2 aux “cas”
Il peut donc être nécessaire d’imposer par vous-mêmes l’ordre de votre choix qui ne serait pas alphabétique ou numérique. L’argument levels
permet d’imposer cet ordre.
En utilisant la fonction factor()
, vous pouvez utiliser en particulier deux arguments pour choisir vous- même l’ordre des niveaux d’une part et leurs noms d’autre part:
1. L’argument levels
permet de préciser l’ordre de vos niveaux dans un ordre qui pourrait être différent de l’ordre alphabétique ou numérique.
2. L’argument labels
permet de renommer chaque niveau du facteur, sans modifier l’ordre par défaut ou spécifié par levels. Par exemple, vous voulez renommer “Homme” par “Man” et “Femme” par “Woman”.
Pour ces deux arguments, vous donnez un vecteur avec les noms des niveaux.
factor()
avec les arguments levels
et labels
¶Prenez par exemple le vecteur d’un score de satisfaction à une enquête:
satisfaction<-c("mauvais","bon","acceptable","bon","bon","mauvais")
print(satisfaction)
[1] "mauvais" "bon" "acceptable" "bon" "bon" [6] "mauvais"
Vous pouvez le convertir en un facteur avec la fonction factor()
satisfaction_factor <- factor(satisfaction)
print(satisfaction_factor)
[1] mauvais bon acceptable bon bon mauvais Levels: acceptable bon mauvais
L’objet obtenu est un facteur à 3 niveaux recodés par les index 1, 2 et 3.
Vous ne voyez pas les index directement, mais ils sont visibles si vous utiliser la fonction str()
. Voir un peu plus bas.
Par défaut, les codes/index 1 à n sont donnés aux niveaux selon l’ordre alphabétique des valeurs uniques. Dans l’exemple, “acceptable” est donc recodé 1, “bon” 2 et “mauvais” 3. Avant de factoriser, vous pouvez identifier l’ordre avec sort()
appliqué aux valeurs uniques de votre vecteur de départ.
sort(unique(satisfaction))
Vous retrouvez cet ordre avec la fonction levels()appliquée à votre facteur.
levels(satisfaction_factor)
Ainsi, “acceptable” a l’index 1, “bon” l’index 2 et “mauvais” l’index 3 du vecteur levels Si vous regardez la structure de votre facteur avec la fonction str()
, vous obtenez bien un facteur à 3 niveaux, dont le nom trié alpha-numériquement correspondant au résultats de levels()
est donné, suivi de l’affichage des premières valeurs codées. Ici, vous obtenez 3 puis 2 puis 1 puis 2 puis 2 puis 3, correspondant aux index/codes des niveaux
str(satisfaction_factor)
Factor w/ 3 levels "acceptable","bon",..: 3 2 1 2 2 3
Si vous voulez imposer que “mauvais” soit codé par 1, “acceptable” par 2 et “bon” par 3, vous ajoutez ces 3 niveaux dans cet ordre au moment de créer votre facteur avec l’argument level
auquel vous donnez le vecteur_des_niveaux_du_facteur_dans_ordre_souhaité, par exemple de 1 à 3 du moins bon au meilleur. Dans l’exemple, j’ajoute “2” au nom de ce nouvel objet R:
satisfaction_factor2 <- factor(satisfaction, levels = c("mauvais","acceptable", "bon"))
Vous constatez alors avec la fonction str() que les données sont désormais 1, puis 3, puis 2, puis 3 etc…selon les niveaux choisis :
str(satisfaction_factor2)
Factor w/ 3 levels "mauvais","acceptable",..: 1 3 2 3 3 1
Si maintenant vous souhaitez en plus renommer les niveaux par “bad”, “satisfactory”, “good”, vous ajoutez l’argument “labels” dans une version 3:
satisfaction_factor3<-factor(satisfaction,levels=c("mauvais", "acceptable", "bon"), labels=c("bad","satisfactory", "good"))
Si vous regardez la structure et à quels index correspondent les données, c’est dans l’ordre souhaité comme dans le facteur version 2.
str(satisfaction_factor3)
Factor w/ 3 levels "bad","satisfactory",..: 1 3 2 3 3 1
Et vous pouvez voir la reconversion des noms des niveaux:
print(satisfaction_factor3)
[1] bad good satisfactory good good [6] bad Levels: bad satisfactory good
satisfaction_factor4<-factor(satisfaction,labels=c("bad", "satisfactory", "good"))
print(satisfaction_factor4)
[1] good satisfactory bad satisfactory satisfactory [6] good Levels: bad satisfactory good
str(satisfaction_factor4)
Factor w/ 3 levels "bad","satisfactory",..: 3 2 1 2 2 3
SI vous n'êtes pas convaincus, affichons les comptes obtenus avec la fonction table()
et comparons aux comptes initiaux:
table(satisfaction_factor4)
satisfaction_factor4 bad satisfactory good 1 3 2
table(satisfaction)
satisfaction acceptable bon mauvais 1 3 2
=> IL EST DONC IMPORTANT DE CONTROLER VOUS-MEMES LA FACON DONT LES VARIABLES SONT FACTORISEES.
Finalement, attention aux factorisations indésirables, notamment lors de la création d’un dataframe à partir d’une matrice, en particululier dans les version de R < 4.
Soit un vecteur numérique de 20 éléments issus d'une loi exponentionelle de paramètre 7 créé avec la fonction rexp :
x <- rexp(20,7)
Avec le vecteur des 20 premières lettres de l’alphabet, on crée un data frame. On utilise pour commencer la fonction data.frame():
y <- data.frame(V1=LETTERS[1:20], V2=x)
head(y)
V1 | V2 | |
---|---|---|
<chr> | <dbl> | |
1 | A | 0.15257001 |
2 | B | 0.06412483 |
3 | C | 0.22912544 |
4 | D | 0.19553022 |
5 | E | 0.19924652 |
6 | F | 0.08110180 |
str(y)
'data.frame': 20 obs. of 2 variables: $ V1: chr "A" "B" "C" "D" ... $ V2: num 0.1526 0.0641 0.2291 0.1955 0.1992 ...
Rien n'a été converti en facteur dans les version R>=4 car l'argument stringsAsFactors=FALSE
est l'option par défaut comme vous pouvez le lire dans l'aide de la fonction.
?data.frame
data.frame {base} | R Documentation |
The function data.frame()
creates data frames, tightly coupled
collections of variables which share many of the properties of
matrices and of lists, used as the fundamental data structure by most
of R's modeling software.
data.frame(..., row.names = NULL, check.rows = FALSE, check.names = TRUE, fix.empty.names = TRUE, stringsAsFactors = default.stringsAsFactors()) default.stringsAsFactors() # << this is deprecated !
... |
these arguments are of either the form |
row.names |
|
check.rows |
if |
check.names |
logical. If |
fix.empty.names |
logical indicating if arguments which are
“unnamed” (in the sense of not being formally called as
|
stringsAsFactors |
logical: should character vectors be converted
to factors? The ‘factory-fresh’ default has been |
A data frame is a list of variables of the same number of rows with
unique row names, given class "data.frame"
. If no variables
are included, the row names determine the number of rows.
The column names should be non-empty, and attempts to use empty names
will have unsupported results. Duplicate column names are allowed,
but you need to use check.names = FALSE
for data.frame
to generate such a data frame. However, not all operations on data
frames will preserve duplicated column names: for example matrix-like
subsetting will force column names in the result to be unique.
data.frame
converts each of its arguments to a data frame by
calling as.data.frame(optional = TRUE)
. As that is a
generic function, methods can be written to change the behaviour of
arguments according to their classes: R comes with many such methods.
Character variables passed to data.frame
are converted to
factor columns unless protected by I
or argument
stringsAsFactors
is false. If a list or data
frame or matrix is passed to data.frame
it is as if each
component or column had been passed as a separate argument (except for
matrices protected by I
).
Objects passed to data.frame
should have the same number of
rows, but atomic vectors (see is.vector
), factors and
character vectors protected by I
will be recycled a
whole number of times if necessary (including as elements of list
arguments).
If row names are not supplied in the call to data.frame
, the
row names are taken from the first component that has suitable names,
for example a named vector or a matrix with rownames or a data frame.
(If that component is subsequently recycled, the names are discarded
with a warning.) If row.names
was supplied as NULL
or no
suitable component was found the row names are the integer sequence
starting at one (and such row names are considered to be
‘automatic’, and not preserved by as.matrix
).
If row names are supplied of length one and the data frame has a
single row, the row.names
is taken to specify the row names and
not a column (by name or number).
Names are removed from vector inputs not protected by I
.
default.stringsAsFactors
is a utility that takes
getOption("stringsAsFactors")
and ensures the result is
TRUE
or FALSE
(or throws an error if the value is not
NULL
). This function is deprecated now and will no longer
be available in the future.
A data frame, a matrix-like structure whose columns may be of differing types (numeric, logical, factor and character and so on).
How the names of the data frame are created is complex, and the rest
of this paragraph is only the basic story. If the arguments are all
named and simple objects (not lists, matrices of data frames) then the
argument names give the column names. For an unnamed simple argument,
a deparsed version of the argument is used as the name (with an
enclosing I(...)
removed). For a named matrix/list/data frame
argument with more than one named column, the names of the columns are
the name of the argument followed by a dot and the column name inside
the argument: if the argument is unnamed, the argument's column names
are used. For a named or unnamed matrix/list/data frame argument that
contains a single column, the column name in the result is the column
name in the argument. Finally, the names are adjusted to be unique
and syntactically valid unless check.names = FALSE
.
In versions of R prior to 2.4.0 row.names
had to be
character: to ensure compatibility with such versions of R, supply
a character vector as the row.names
argument.
Chambers, J. M. (1992) Data for models. Chapter 3 of Statistical Models in S eds J. M. Chambers and T. J. Hastie, Wadsworth & Brooks/Cole.
I
,
plot.data.frame
,
print.data.frame
,
row.names
, names
(for the column names),
[.data.frame
for subsetting methods
and I(matrix(..))
examples;
Math.data.frame
etc, about
Group methods for data.frame
s;
read.table
,
make.names
,
list2DF
for creating data frames from lists of variables.
L3 <- LETTERS[1:3] fac <- sample(L3, 10, replace = TRUE) (d <- data.frame(x = 1, y = 1:10, fac = fac)) ## The "same" with automatic column names: data.frame(1, 1:10, sample(L3, 10, replace = TRUE)) is.data.frame(d) ## do not convert to factor, using I() : (dd <- cbind(d, char = I(letters[1:10]))) rbind(class = sapply(dd, class), mode = sapply(dd, mode)) stopifnot(1:10 == row.names(d)) # {coercion} (d0 <- d[, FALSE]) # data frame with 0 columns and 10 rows (d.0 <- d[FALSE, ]) # <0 rows> data frame (3 named cols) (d00 <- d0[FALSE, ]) # data frame with 0 columns and 0 rows
Dans les versions de R antérieures à la version 4, le même paramètre est stringsAsFactors=TRUE
ce qui conduit à une factorisation automatique des variables catégroriques même si ce n'est pas souhaité. Nous aurions donc:
y <- data.frame(V1=LETTERS[1:20], V2=x, stringsAsFactors = TRUE)
head(y)
V1 | V2 | |
---|---|---|
<fct> | <dbl> | |
1 | A | 0.15257001 |
2 | B | 0.06412483 |
3 | C | 0.22912544 |
4 | D | 0.19553022 |
5 | E | 0.19924652 |
6 | F | 0.08110180 |
str(y)
'data.frame': 20 obs. of 2 variables: $ V1: Factor w/ 20 levels "A","B","C","D",..: 1 2 3 4 5 6 7 8 9 10 ... $ V2: num 0.1526 0.0641 0.2291 0.1955 0.1992 ...
Ci-dessus, la variable V1 a été factorisée.
z <- cbind(V1=LETTERS[1:20], V2=x)
head(z)
V1 | V2 |
---|---|
A | 0.152570014281317 |
B | 0.0641248345907245 |
C | 0.229125442496548 |
D | 0.195530224718538 |
E | 0.199246518719093 |
F | 0.0811017959245614 |
str(z)
chr [1:20, 1:2] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" ... - attr(*, "dimnames")=List of 2 ..$ : NULL ..$ : chr [1:2] "V1" "V2"
On voit que x a été contraint (‘coerced’) d’être transformé en vecteur chaine de caractères. Le plus souvent, cela passe inaperçu à cette étape et le problème est révélé lors de la transformation en dataframe:
u <- as.data.frame(z, stringsAsFactors=TRUE)
head(u)
V1 | V2 | |
---|---|---|
<fct> | <fct> | |
1 | A | 0.152570014281317 |
2 | B | 0.0641248345907245 |
3 | C | 0.229125442496548 |
4 | D | 0.195530224718538 |
5 | E | 0.199246518719093 |
6 | F | 0.0811017959245614 |
str(u)
'data.frame': 20 obs. of 2 variables: $ V1: Factor w/ 20 levels "A","B","C","D",..: 1 2 3 4 5 6 7 8 9 10 ... $ V2: Factor w/ 20 levels "0.0148050158568569",..: 13 7 17 14 15 9 12 19 4 5 ...
On constate que les deux variables sont maintenant des facteurs. Pour la variable x qui était numérique au départ, cela est généralement involontaire.
Pour rattraper cette “erreur”, on utilise alors spontanément la fonction as.numeric():
u$V2 <- as.numeric(u$V2)
str(u)
'data.frame': 20 obs. of 2 variables: $ V1: Factor w/ 20 levels "A","B","C","D",..: 1 2 3 4 5 6 7 8 9 10 ... $ V2: num 13 7 17 14 15 9 12 19 4 5 ...
On constate que la transformation as.numeric()
a en fait recyclé les niveaux correspondant aux valeurs, et pas du tout les valeurs elles-mêmes. Dans cet exemple, l’erreur est rapidement détectée car l’on partait de valeurs décimales. Lorsqu’il s’agit d’entiers au départ, le problème peut très facilement rester inaperçu.
Dans cette situation, il faut en fait procéder en deux étapes (après avoir recréé le dataframe à partir de la matrice) en conversitassant d'abord en chaine de caractères puis en valeur numérique:
t <- as.data.frame(z)
t$V2 <- as.character(t$V2)
str(t)
'data.frame': 20 obs. of 2 variables: $ V1: chr "A" "B" "C" "D" ... $ V2: chr "0.152570014281317" "0.0641248345907245" "0.229125442496548" "0.195530224718538" ...
t$V2 <- as.numeric(t$V2)
str(t)
'data.frame': 20 obs. of 2 variables: $ V1: chr "A" "B" "C" "D" ... $ V2: num 0.1526 0.0641 0.2291 0.1955 0.1992 ...
head(t)
V1 | V2 | |
---|---|---|
<chr> | <dbl> | |
1 | A | 0.15257001 |
2 | B | 0.06412483 |
3 | C | 0.22912544 |
4 | D | 0.19553022 |
5 | E | 0.19924652 |
6 | F | 0.08110180 |