Je trouve que ma fille a l’air sonnée. Qu’est-ce qui a pu la secouer ainsi ? Je l’interroge et elle me dit qu’elle a essayé de manipuler des factors sous R. Ce n’est d’ailleurs pas la première fois que ça la met dans cet état…
Le factor sonne toujours deux fois.
Commençons par créer deux petits jeux d’essai pour mieux saisir ce qu’est un factor dans R. Il s’agit des nombres d’élèves dans l’enseignement du 1er degré (maternelle + primaire) en France métropolitaine en 2017, par classe et par académie. Ces données sont reprises d’une publication de la Depp https://www.education.gouv.fr/cid57096/reperes-et-references-statistiques.html (tableaux 3.1 et 3.2).
eleves2017 <- data.frame(
niveau=c("Très petite section", "Petite section", "Moyenne section", "Grande section",
"CP", "CE1", "CE2", "CM1", "CM2"),
nb=c(92.9, 788.1, 809.1, 832.3, 838.2, 847.3, 842.9, 845.8, 836.2)*1000
)
niveau | nb |
---|---|
Très petite section | 92900 |
Petite section | 788100 |
Moyenne section | 809100 |
Grande section | 832300 |
CP | 838200 |
CE1 | 847300 |
CE2 | 842900 |
CM1 | 845800 |
CM2 | 836200 |
acad2017 <- data.frame(
academie=c("Clermont-Ferrand","Grenoble","Lyon","Besançon","Dijon","Rennes","Orléans-Tours",
"Corse","Nancy-Metz","Reims","Strasbourg","Amiens","Lille","Créteil","Paris",
"Versailles","Caen","Rouen","Bordeaux","Limoges","Poitiers","Montpellier",
"Toulouse","Nantes","Aix-Marseille","Nice"),
nb_deg1_public=c(100218,299618,292506,105361,132642,199696,228006,
24726,202186,115952,170107,183737,370032,486470,127280,
588058,112573,173910,272749,55553,141190,236122,
245309,254733,263702,181375)
)
academie | nb_deg1_public |
---|---|
Clermont-Ferrand | 100218 |
Grenoble | 299618 |
Lyon | 292506 |
Besançon | 105361 |
Dijon | 132642 |
Rennes | 199696 |
Orléans-Tours | 228006 |
Corse | 24726 |
Nancy-Metz | 202186 |
Reims | 115952 |
Strasbourg | 170107 |
Amiens | 183737 |
Lille | 370032 |
Créteil | 486470 |
Paris | 127280 |
Versailles | 588058 |
Caen | 112573 |
Rouen | 173910 |
Bordeaux | 272749 |
Limoges | 55553 |
Poitiers | 141190 |
Montpellier | 236122 |
Toulouse | 245309 |
Nantes | 254733 |
Aix-Marseille | 263702 |
Nice | 181375 |
Qu’est-ce qu’un factor ?
A première vue, les colonnes niveau et académie sont de type caractère. Vérifions :
class(eleves2017$niveau)
## [1] "factor"
str(acad2017)
## 'data.frame': 26 obs. of 2 variables:
## $ academie : Factor w/ 26 levels "Aix-Marseille",..: 6 10 13 3 9 22 18 7 15 21 ...
## $ nb_deg1_public: num 100218 299618 292506 105361 132642 ...
En fait ce sont des factors. C’est à dire que R les a stockées comme des entiers arbitraires (1, 2, 3, et ainsi de suite autant qu’il y aura de valeurs distinctes dans une colonne) puis habillées d’un affichage sous forme de textes. Les correspondances entre valeurs stockées et valeurs affichées sont visibles via la fonction levels. Nous n’avons pas demandé à créer ces factors. C’est le comportement par défaut de la fonction data.frame (de même que les fonctions d’import read.table, read.delim, etc.) ; il existe une option stringsAsFactors=FALSE pour créer de simples colonnes de type texte au lieu des factors.
levels(eleves2017$niveau)
## [1] "CE1" "CE2" "CM1"
## [4] "CM2" "CP" "Grande section"
## [7] "Moyenne section" "Petite section" "Très petite section"
levels(acad2017$academie)
## [1] "Aix-Marseille" "Amiens" "Besançon"
## [4] "Bordeaux" "Caen" "Clermont-Ferrand"
## [7] "Corse" "Créteil" "Dijon"
## [10] "Grenoble" "Lille" "Limoges"
## [13] "Lyon" "Montpellier" "Nancy-Metz"
## [16] "Nantes" "Nice" "Orléans-Tours"
## [19] "Paris" "Poitiers" "Reims"
## [22] "Rennes" "Rouen" "Strasbourg"
## [25] "Toulouse" "Versailles"
Par défaut les niveaux sont attribués par ordre alphabétique des valeurs affichées.
Quels avantages propose un factor ?
Par rapport à une variable de type texte, le factor permet :
- de gagner de la place, si les valeurs textes sont longues et peu nombreuses
- de modifier l’ordre d’affichage des valeurs dans des tableaux et des graphiques
- de choisir la modalité de référence dans un modèle statistique
Convertir un factor : méfiance !
Quand on veut convertir un factor en un vecteur d’un autre type, on utilise les fonctions habituelles : as.character, as.integer, as.numeric ou as.Date. Mais attention, car si la conversion en texte ne pose pas de souci, la conversion en numérique va utiliser les valeurs entières stockées par R.
# d'abord en texte --> aucun problème
as.character(eleves2017$niveau)
## [1] "Très petite section" "Petite section" "Moyenne section"
## [4] "Grande section" "CP" "CE1"
## [7] "CE2" "CM1" "CM2"
# puis en numérique --> on récupère les codes sous-jacents
as.numeric(eleves2017$niveau)
## [1] 9 8 7 6 5 1 2 3 4
Jusqu’ici, tout paraît assez prévisible. Les ennuis peuvent survenir si les valeurs affichées du factor ne sont composés que de chiffres (par exemple des numéros de département).
# départements bretons - en texte
bretagne <- c("22","29","35","56")
# convertis en factor
bretagne <- as.factor(bretagne)
# tout va bien
bretagne
## [1] 22 29 35 56
## Levels: 22 29 35 56
# convertis en texte --> OK
as.character(bretagne)
## [1] "22" "29" "35" "56"
# convertis en numérique --> non !!!
as.numeric(bretagne)
## [1] 1 2 3 4
# convertis correctement en numérique
as.numeric(as.character(bretagne))
## [1] 22 29 35 56
Factors et tableaux
Les niveaux des factors étant attribués par ordre alphabétique par défaut, c’est également l’ordre qui prévaudra dans un tableau.
library(tables)
tabular(Heading("Niveau") * niveau ~ Heading("Effectif") * nb * Heading() * sum,
eleves2017)
Niveau | Effectif |
---|---|
CE1 | 847300 |
CE2 | 842900 |
CM1 | 845800 |
CM2 | 836200 |
CP | 838200 |
Grande section | 832300 |
Moyenne section | 809100 |
Petite section | 788100 |
Très petite section | 92900 |
L’ordre utilisé ici n’a pas beaucoup de sens. Il est strictement alphabétique. Un factor ordonné (une variante du factor de base) permet de conserver un ordre dans les modalités.
eleves2017$niveau_2 <- ordered(eleves2017$niveau,
levels=c("Très petite section", "Petite section", "Moyenne section", "Grande section",
"CP", "CE1", "CE2", "CM1", "CM2"))
str(eleves2017)
## 'data.frame': 9 obs. of 3 variables:
## $ niveau : Factor w/ 9 levels "CE1","CE2","CM1",..: 9 8 7 6 5 1 2 3 4
## $ nb : num 92900 788100 809100 832300 838200 ...
## $ niveau_2: Ord.factor w/ 9 levels "Très petite section"<..: 1 2 3 4 5 6 7 8 9
levels(eleves2017$niveau_2)
## [1] "Très petite section" "Petite section" "Moyenne section"
## [4] "Grande section" "CP" "CE1"
## [7] "CE2" "CM1" "CM2"
Le tableau s’en trouve largement amélioré.
tabular(Heading("Niveau")*niveau_2 ~ Heading("Effectif")*nb * Heading()*sum,
eleves2017)
Niveau | Effectif |
---|---|
Très petite section | 92900 |
Petite section | 788100 |
Moyenne section | 809100 |
Grande section | 832300 |
CP | 838200 |
CE1 | 847300 |
CE2 | 842900 |
CM1 | 845800 |
CM2 | 836200 |
Avec un autre package de construction de tableaux, la problématique est la même.
library(flextable)
library(magrittr)
library(reshape2)
dcast(eleves2017,
niveau ~ .,
value.var = "nb",
fun.aggregate=sum) %>%
flextable() %>%
delete_part("header") %>%
add_header(niveau="Niveau","."="Effectifs") %>%
colformat_int(col_keys=".", big.mark=" ")
Niveau |
Effectifs |
CE1 |
847 300 |
CE2 |
842 900 |
CM1 |
845 800 |
CM2 |
836 200 |
CP |
838 200 |
Grande section |
832 300 |
Moyenne section |
809 100 |
Petite section |
788 100 |
Très petite section |
92 900 |
Beaucoup plus intéressant réordonné avec la colonne niveau_2.
dcast(eleves2017,
niveau_2 ~ .,
value.var = "nb",
fun.aggregate=sum) %>%
flextable() %>%
delete_part("header") %>%
add_header(niveau_2="Niveau","."="Effectifs") %>%
colformat_int(col_keys=".", big.mark=" ")
Niveau |
Effectifs |
Très petite section |
92 900 |
Petite section |
788 100 |
Moyenne section |
809 100 |
Grande section |
832 300 |
CP |
838 200 |
CE1 |
847 300 |
CE2 |
842 900 |
CM1 |
845 800 |
CM2 |
836 200 |
Factors et graphiques
Quand on produit un graphique, particulièrement un diagramme en bâtons, l’ordre des barres peut être a) lié au sens métier des données ou b) tel que les barres sont par ordre croissant de longueur. On constate que l’ordre alphabétique est sans intérêt.
library(ggplot2)
library(forcats)
ggplot(acad2017) +
aes(x=academie, y=nb_deg1_public) +
geom_bar(stat = "identity") +
theme_light() +
xlab("Académie") +
ylab("Effectif d'élèves du 1er degré, secteur public") +
coord_flip() +
scale_y_continuous(labels=scales::number)
Le package {forcats} propose de nombreuses fonctions pour manipuler les factors : les réordonner selon une statistique avec fct_reorder…
ggplot(acad2017) +
aes(x=fct_reorder(academie, nb_deg1_public, sum),
y=nb_deg1_public) +
geom_bar(stat = "identity") +
theme_light() +
xlab("Académie") +
ylab("Effectif d'élèves du 1er degré, secteur public") +
coord_flip() +
scale_y_continuous(labels=scales::number)
… ou modifier leurs libellés avec fct_recode. Ici on propose le même libellé pour plusieurs niveaux, ce qui a pour conséquence de les fusionner.
eleves2017$niveau_3 <- fct_recode(eleves2017$niveau,
"maternelle"="Très petite section",
"maternelle"="Petite section",
"maternelle"="Moyenne section",
"maternelle"="Grande section",
"primaire"="CP","primaire"="CE1","primaire"="CE2",
"primaire"="CM1","primaire"="CM2")
ggplot(eleves2017) +
aes(x=niveau_3, y=nb) +
geom_bar(stat = "summary", fun.y="sum") +
theme_light() +
xlab("niveau agrégé") +
ylab("nombre d'élèves en 2017") +
coord_flip() +
scale_y_continuous(labels=scales::number)
Factors et modélisation statistique
Enfin, l’ordre des modalités d’une variable texte ou factor est utilisé dans R lors de la construction de modèles statistiques et particulièrement de régressions. Pour l’illustrer, nous utiliserons les données de prix de location des maisons AirBnB sur Paris (une partie des données qui servent aux exemples du livre Le langage R au quotidien chez Dunod).
maisons <- read.delim(url("https://github.com/olivierDecourt/livreR/blob/master/houses.csv?raw=true"),
sep=",")
Parmi les multiples informations concernant ces logements, il y a la politique d’annulation.
levels(maisons$cancellation_policy)
## [1] "flexible" "moderate" "strict" "super_strict_30"
## [5] "super_strict_60"
Si on réalise une régression linéaire expliquant le prix par le nombre de personnes accueillies et la politique d’annulation, les coefficients se présentent d’abord ainsi.
library(broom) # affichage des coefficients du modèle sous forme de tibble
library(pander) # affichage markdown de tableaux et tibbles
reg <- lm(price ~ accommodates + cancellation_policy, maisons)
pander(tidy(reg), split.tables=Inf)
term | estimate | std.error | statistic | p.value |
---|---|---|---|---|
(Intercept) | -18 | 12.79 | -1.407 | 0.1598 |
accommodates | 41.53 | 2.2 | 18.88 | 2.141e-63 |
cancellation_policymoderate | -33.3 | 15.02 | -2.218 | 0.02693 |
cancellation_policystrict | 3.002 | 13.85 | 0.2167 | 0.8285 |
cancellation_policysuper_strict_30 | 231.1 | 73.27 | 3.155 | 0.001682 |
cancellation_policysuper_strict_60 | 1806 | 61.08 | 29.56 | 2.422e-121 |
Pour le coefficient du nombre de personnes accueillies, pas de souci : chaque place supplémentaire correspond à une hausse du prix de 41,53€ en moyenne. Pour les coefficients correspondant à la politique d’annulation, ils sont construits par rapport à une situation de référence : il s’agit de la 1e modalité du factor. Ici, c’est par rapport à la politique “flexible”. Par exemple, une politique d’annulation modérée entraîne une baisse de 33€ en moyenne par rapport à une politique flexible – à nombre de personnes accueillies identique. Si on souhaite modifier cette référence (et donc se comparer à une autre politique d’annulation), on utilisera la fonction relevel (du package {stats} préinstallé et préactivé).
maisons$cancellation_policy <- relevel(maisons$cancellation_policy,
"strict")
reg2 <- lm(price ~ accommodates + cancellation_policy, maisons)
pander(tidy(reg2), split.tables=Inf)
term | estimate | std.error | statistic | p.value |
---|---|---|---|---|
(Intercept) | -15 | 14.43 | -1.04 | 0.2989 |
accommodates | 41.53 | 2.2 | 18.88 | 2.141e-63 |
cancellation_policyflexible | -3.002 | 13.85 | -0.2167 | 0.8285 |
cancellation_policymoderate | -36.3 | 14.65 | -2.477 | 0.01349 |
cancellation_policysuper_strict_30 | 228.1 | 73.22 | 3.116 | 0.001917 |
cancellation_policysuper_strict_60 | 1803 | 60.52 | 29.79 | 1.535e-122 |
On constate que la constante et les coefficients de la politique d’annulation ont changé, ce qui est logique. En revanche le coefficient du nombre de personnes accueillies n’est pas modifié. Maintenant la situation de référence est la politique d’annulation dite “stricte”.