Section 5 Quelles questions intéressantes pour une “fouille de données” sur cette base ?
Quelles sont les questions intéressantes que l’on peut se poser ? Qu’est-ce qu’on va pouvoir mettre en évidence à partir de ces données ?
D’abord, on pourrait caractériser les logements fanciliens selon le type de logement (appartement, maison, HLM, …), la superficie, le nombre de pièces, le nombre de personnes y habitant, le statut d’occupation (propriétaire, locataire, …). Ensuite, on pourrait décrire plus précisément les pièces du logement en termes de confort (baignoire / douche, salle climatisée, moyen de chauffage), ainsi que les parties communes de l’immeuble (ascenseur, place de stationnement). Enfin, on pourrait étudier les caractéristiques des occupants de ces logements, et en premier lieu ici ce que l’Insee appelle la “personne de référence du ménage”. Par ailleurs, toutes ces analyses peuvent être réalisées en comparant les différentes communes de la région, ou à un niveau géographique plus fin par quartiers, arrondissements ou encore IRIS.
Pour manipuler cette base et répondre à quelques-unes de ces questions, nous allons nous concentrer sur la commune de Paris, et allons chercher à reproduire des statistiques publiées sur le site de l’Insee. Nous produirons principalement, lors de cette séance, des tableaux de statistiques, l’analyse graphique fera en effet l’objet d’une séance entière car elle nécessite la présentation détaillée de la “grammaire” Ggplot
.
Avant cela, si les tables de données (“RP” et “meta”) ne sont plus dans votre environnement local, il faut de nouveau les importer à partir de l’enregistrement précédemment effectué dans le dossier ‘data’ de votre projet. Pour cela, il faut utiliser la fonction readRDS()
, comme ci-dessous :
5.1 Caractéristiques des logements de la commune de Paris
Sur le site de l’Insee, vous pouvez trouver les statistiques générales sur les logements à Paris en 2019 ici.
Comme nous devons utiliser la pondération pour avoir des statistiques représentatives de la population française, une manière de faire est d’utiliser une méthode de comptage par variable catégorielle en indiquant la pondération à utiliser. La fonction count()
avec l’argument wt=
est un des moyens assez efficace d’y arriver. Cela nous donnera le nombre de ménages concernés par la caractéristique étudiée (par défaut, la variable créée s’appelle “n”, on peut la renommer dans une étape ultérieure avec la fonction rename()
). Souvent, c’est aussi les pourcentages que l’on souhaite avoir, il faudra alors créer une variable faisant la proportion de chaque catégorie sur le nombre total de logements, en utilisant la fonction mutate()
et la fonction prop.table()
. Le package janitor
permet enfin d’ajouter une ligne totale (ou une colonne totale selon ce qu’on souhaite faire) avec la fonction adorn_totals()
, argument “row” pour avoir le total en ligne.
A partir de ces indications, afficher le tableau suivant à partir d’un code utilisant le langage tidyverse et en une seule procédure (sans nécessairement créer de table dans votre environnement) :
Type de logement | Effectif | Pourcentage |
---|---|---|
Maison | 11 260 | 0.8 |
Appartement | 1 346 430 | 96.9 |
Autres | 31 685 | 2.3 |
Total | 1 389 375 | 100.0 |
library(tidyverse)
library(janitor)
library(gt)
%>%
RP filter(COMMUNE == "75056") %>%
count(TYPL_moda, wt=IPONDL) %>%
mutate(Pourcentage=round(prop.table(n)*100, 1)) %>%
adorn_totals("row") %>%
rename(Effectif=n, 'Type de logement'=TYPL_moda) %>%
gt() %>%
fmt_number(columns = 2, sep_mark = " ", decimals = 0)
Non sans grande surprise, la commune de Paris est constituée en majorité d’appartements, presque 97%.
Cherchons maintenant la répartition des logements parisiens par type, est-ce plutôt des résidences principales ou secondaires ? combien y a-t-il de logements vacants ?
Catégorie de logement | Effectif | Pourcentage |
---|---|---|
Résidences principales | 1 137 759 | 81.9 |
Résidences secondaires | 85 675 | 6.2 |
Logements vacants | 120 295 | 8.7 |
Logements occasionnels | 45 645 | 3.3 |
Total | 1 389 375 | 100.1 |
%>%
RP filter(COMMUNE == "75056") %>%
mutate(CATL_moda=case_when(CATL == "1" ~ "Résidences principales",
== "2" ~ "Logements occasionnels",
CATL == "3" ~ "Résidences secondaires",
CATL == "4" ~ "Logements vacants",
CATL TRUE ~ "Autres"),
CATL_moda=fct_relevel(CATL_moda, c("Résidences principales", "Résidences secondaires",
"Logements vacants", "Logements occasionnels"))) %>%
count(CATL_moda, wt=IPONDL) %>%
mutate(Pourcentage=round(prop.table(n)*100, 1)) %>%
adorn_totals("row") %>%
rename(Effectif=n, 'Catégorie de logement'=CATL_moda) %>%
gt() %>%
fmt_number(columns = 2, sep_mark = " ", decimals = 0)
La plupart des logements parisiens sont des résidences principales (81,9%), alors que 6,2% sont des résidences secondaires ; à noter que la part des logements vacants n’est pas négligeable, elle s’élève à 8,7%.
Maintenant, affichons la seule colonne 2019 de ce tableau tiré de l’Insee , en mettant la ligne “Ensemble des résidences principales” plutôt en fin de tableau (ces 2 usages sont possibles, question de préférence…) ; attention au champ sur lequel porte ces moyennes…

%>%
RP filter(COMMUNE == "75056" & CATL== "1" & TYPL_moda != "Autres") %>%
group_by(TYPL_moda) %>%
summarise('2019' = weighted.mean(as.numeric(as.character(NBPI)), IPONDL, na.rm=T)) %>%
bind_rows(summarise(TYPL_moda = "Ensemble des résidences principales",
$COMMUNE == "75056" & RP$CATL == "1" & RP$TYPL_moda != "Autres",],
RP[RP'2019' = weighted.mean(as.numeric(as.character(NBPI)), IPONDL, na.rm=T))) %>%
rename(' ' = TYPL_moda) %>%
gt() %>%
fmt_number(columns = 2, dec_mark = ",", decimals = 1)
A Paris, sur les seules résidences principales, les maisons comportent plus de pièces que les appartements, presque 2 pièces de plus en moyenne. Etant donné que la majorité des logements est constituée par des appartements, la moyenne de l’ensemble des résidences principales est la même que celle des appartements.
Etudions maintenant les résidences principales et l’ancienneté d’emménagement selon le statut d’occupation. Attention, ici il faut procéder en plusieurs étapes : d’abord créer un tableau donnant la répartition en nombre et en pourcentage des ménages par statut d’occupation, puis créer un second tableau donnant la moyenne de l’ancienneté d’emménagement en années par statut d’occupation, puis fusionner ces deux tableaux.
Statut d'occupation | Nombre | Pourcentage | Ancienneté moyenne d'emménagement en année(s) |
---|---|---|---|
Propriétaire | 379 745 | 33.4 | 18.2 |
Locataire | 701 943 | 61.7 | 10.3 |
Logé gratuitement | 56 071 | 4.9 | 10.4 |
Total | 1 137 759 | 100.0 | 13.0 |
<- RP %>%
t1 filter(COMMUNE == "75056" & CATL=="1" & STOCD != "0") %>%
mutate(st_occupation=case_when(STOCD=="10" ~ "Propriétaire",
%in% c("21","22","23") ~ "Locataire",
STOCD =="30" ~ "Logé gratuitement"),
STOCDst_occupation=fct_relevel(st_occupation, c("Propriétaire",
"Locataire",
"Logé gratuitement"))) %>%
count(st_occupation, wt=IPONDL) %>%
mutate(Pourcentage=round(prop.table(n)*100, 1)) %>%
adorn_totals("row") %>%
rename(Nombre=n)
<- RP %>%
t2 filter(COMMUNE == "75056" & CATL=="1" & !STOCD %in% c("0", "ZZ")) %>%
mutate(st_occupation=case_when(STOCD=="10" ~ "Propriétaire",
%in% c("21","22","23") ~ "Locataire",
STOCD =="30" ~ "Logé gratuitement"),
STOCDst_occupation=fct_relevel(st_occupation, c("Propriétaire",
"Locataire",
"Logé gratuitement"))) %>%
group_by(st_occupation) %>%
summarise(anc_moy = weighted.mean(ANEM, IPONDL, na.rm=T)) %>%
bind_rows(summarise(st_occupation = "Total", RP[RP$COMMUNE == "75056" & RP$CATL=="1" & !RP$STOCD %in% c("0", "ZZ"),],
anc_moy = weighted.mean(ANEM, IPONDL, na.rm=T))) %>%
mutate(anc_moy=round(anc_moy, 1)) %>%
rename("Ancienneté moyenne d'emménagement en année(s)"=anc_moy)
%>% left_join(t2, by=join_by(st_occupation)) %>%
t1 rename("Statut d'occupation"=st_occupation) %>%
gt() %>%
fmt_number(columns = 2, sep_mark = " ", decimals = 0)
Il y a donc 1/3 de propriétaires à Paris quand on se concentre sur les résidences principales, et les propriétaires y sont présents depuis plus longtemps que les locataires : 18,2 ans en moyenne contre 10,3 ans.
Enfin, on peut vouloir comparer la moyenne des pièces des appartements parisiens par arrondissement par exemple.
Arrondissement | Nombre moyen de pièces |
---|---|
75101 | 2,6 |
75102 | 2,4 |
75103 | 2,4 |
75104 | 2,6 |
75105 | 2,6 |
75106 | 2,8 |
75107 | 3,0 |
75108 | 3,2 |
75109 | 2,7 |
75110 | 2,5 |
75111 | 2,3 |
75112 | 2,5 |
75113 | 2,6 |
75114 | 2,6 |
75115 | 2,6 |
75116 | 3,2 |
75117 | 2,7 |
75118 | 2,3 |
75119 | 2,6 |
75120 | 2,5 |
Ensemble des appartements | 2,2 |
%>%
RP filter(COMMUNE == "75056" & CATL== "1" & TYPL_moda == "Appartement") %>%
group_by(ARM) %>%
summarise(Moy_pieces = weighted.mean(as.numeric(as.character(NBPI)), IPONDL, na.rm=T)) %>%
bind_rows(summarise(ARM = "Ensemble des appartements",
$COMMUNE == "75056" & RP$CATL == "1" & RP$TYPL_moda != "Appartement",],
RP[RPMoy_pieces = weighted.mean(as.numeric(as.character(NBPI)), IPONDL, na.rm=T))) %>%
rename(Arrondissement=ARM, 'Nombre moyen de pièces'=Moy_pieces) %>%
gt() %>%
fmt_number(columns = 2, dec_mark = ",", decimals = 1)
Ainsi, si l’on veut créer des tableaux de répartition à une seule variable, on peut utiliser ces procédures qui se structurent toujours de la même façon. Au lieu de faire un copié-collé du code et de changer le nom des variables, autrement dit pour systématiser nos procédures, une astuce est de créer ses propres fonctions. C’est ce que nous allons étudier maintenant.
5.2 Systématiser nos procédures : construire une fonction
Pour ré-utiliser un code de façon plus automatique, créer des fonctions est très utile (et on peut même les stocker dans un fichier .r
pour les réutiliser plus tard pour une autre étude).
L’idée est qu’à partir d’un bloc d’instructions ou de lignes de codes, on l’intègre dans une fonction qui portera un nom et qui pourra être appliquée sur les paramètres que l’on veut (table/objet différent, variables différentes) et qui nous retournera une valeur en sortie (qu’il faut préciser donc). Par exemple :
# Exemple fonction avec 3 arguments
<- function(data, var1, var2) {
nom_fonction
# Ce que la fonction fait
expression...
return() # Optionnel, mais la plupart du temps utilisé (!),
# pour sortir le résultat de la fonction
}
# L'appel de la fonction devra ainsi préciser la table de données sur laquelle
# l'appliquer et les autres arguments :
nom_fonction(data = nom_de_ma_table , var1 = nom_de_ma_variable1,
var2 = nom_de_ma_variable2)
# De plus, on pourra créer un nouvel objet (ici "tab_var") pour stocker la table
# qui est en valeur de sortie de la fonction :
<- nom_fonction(data = nom_de_ma_table , var1 = nom_de_ma_variable1,
tab_var var2 = nom_de_ma_variable2)
Les arguments doivent donc être précisés en entrée de notre fonction, si on ne les précise pas cela nous retournera une erreur… à moins que l’on ait spécifié des valeurs par défaut (ce qui peut être utile si on utilise souvent les mêmes paramètres, par exemple la même base de données) ; il peut y avoir autant d’arguments que l’on souhaite.
Si l’on utilise le langage tidyverse, il faut connaître quelques petits “trucs” pour écrire une fonction. Le schéma suivant réalisé par Julien Barnier du CNRS nous sera très utile.

Pour une fonction utilisant le langage tidyverse
Source : Julien Barnier, https://twitter.com/lapply/status/1493908215796535296?s=20&t=p4aYIEV4GsGS3TGftPa0Nw.
Vous trouverez également des informations utiles ici ou là.
On peut d’abord créer une fonction reprenant le code précédent pour la construction de tableau. On l’appelle tableau
, et on lui donne comme arguments “data”, “filtre_com”, “var_quali”, “pond” et “nom_var_quali”.
Dans le langage tidyverse, au sein d’une fonction, il faut appeler une variable avec des doubles-accolades {{ }}
.
Si l’on utilise une fonction summarise()
, une autre subtilité à connaître est que cette syntaxe summarise({{ nom_var }} = mean({{ var }}, na.rm=TRUE))
ne sera pas reconnue, car il faut indiquer non pas un “=” mais un “:=” pour que la fonction puisse être lue lorsque le nom donné à la variable est de type caractère ou “string”.
Enfin, il ne faut pas oublier de retourner un objet en sortie avec return()
.
Essayez donc de créer une fonction tableau()
, reprenant le premier code de la section précédente et remise ci-dessous pour information :
%>%
RP filter(COMMUNE == "75056") %>%
count(TYPL_moda, wt=IPONDL) %>%
mutate(Pourcentage=prop.table(n)*100, Pourcentage=round(Pourcentage, 1)) %>%
adorn_totals("row") %>%
rename(Effectif=n, 'Type de logement'=TYPL_moda) %>%
gt() %>%
fmt_number(columns = 2, sep_mark = " ", decimals = 0)
Type de logement | Effectif | Pourcentage |
---|---|---|
Maison | 11 260 | 0.8 |
Appartement | 1 346 430 | 96.9 |
Autres | 31 685 | 2.3 |
Total | 1 389 375 | 100.0 |
# library(tidyverse)
# library(janitor)
# library(gt)
<- function(data, filtre_com, var_quali, pond=IPONDL, nom_var_quali){
tableau
<- data %>%
tab filter(COMMUNE == filtre_com) %>%
count({{ var_quali }}, wt={{ pond }}) %>%
mutate(Pourcentage=prop.table(n)*100, Pourcentage=round(Pourcentage, 1)) %>%
adorn_totals("row") %>%
rename(Effectif=n, {{nom_var_quali}}:={{ var_quali }})
return(tab)
}
On peut vérifier qu’on obtient bien la même chose :
tableau(data=RP, filtre_com="75056", var_quali=TYPL_moda, nom_var_quali="Type de logement") %>%
gt() %>%
fmt_number(columns = 2, sep_mark = " ", decimals = 0)
Type de logement | Effectif | Pourcentage |
---|---|---|
Maison | 11 260 | 0.8 |
Appartement | 1 346 430 | 96.9 |
Autres | 31 685 | 2.3 |
Total | 1 389 375 | 100.0 |
Essayez d’écrire une seconde fonction somme()
permettant de systématiser le code suivant qui donne un tableau de contingence du nombre de propriétaires par IRIS (cela nous servira pour plus tard…), en ajoutant une fonction de filtre qui peut contenir plusieurs variables. Pour cela, on va utiliser l’argument “…” (lire “dot”) : cet argument est très pratique si l’on ne sait pas combien il y aura de variable(s) dans la fonction à laquelle elle s’applique, c’est-à-dire autant 0 variable, 1 variable ou plus d’une variable ; mais elle peut aussi être “dangereuse” si on ne se souvient plus qu’on l’a créée et/ou si on ne fait pas attention à bien remplir les autres arguments avec les noms correspondants.
On va également introduire la fonction group_by()
pour avoir les tableaux de contingence par IRIS.
%>%
RP filter(COMMUNE == "75056" & STOCD == "10") %>%
group_by(IRIS) %>%
summarise(nb_proprietaires = sum(IPONDL)) %>%
mutate(nb_proprietaires=round(nb_proprietaires, 0))
# A tibble: 917 x 2
IRIS nb_proprietaires
<fct> <dbl>
1 751010101 107
2 751010102 52
3 751010103 35
4 751010201 472
5 751010202 302
6 751010203 511
7 751010204 328
8 751010206 95
9 751010301 584
10 751010302 34
# i 907 more rows
<- function(data, ..., var_gpe, nom_var, var1){
somme
<- data %>%
som_1 filter(...) %>%
group_by({{var_gpe}}) %>%
summarise({{nom_var}}:=sum({{var1}}, na.rm=T)) %>%
mutate({{nom_var}}:=round({{nom_var}}, 0))
return(som_1)
}
Vérifions :
somme(data=RP, COMMUNE == "75056" & STOCD == "10",
var_gpe=IRIS, nom_var=nb_proprietaires, var1=IPONDL)
# A tibble: 917 x 2
IRIS nb_proprietaires
<fct> <dbl>
1 751010101 107
2 751010102 52
3 751010103 35
4 751010201 472
5 751010202 302
6 751010203 511
7 751010204 328
8 751010206 95
9 751010301 584
10 751010302 34
# i 907 more rows
La création de fonctions est donc très utile pour avoir un code plus efficace ; il faut toutefois réfléchir à son usage avant de la créer pour savoir à quel point il faut systématiser les procédures utilisées, certains éléments devant être laissés probablement en-dehors de la fonction, comme dans l’exemple précédent le fait d’arrondir les chiffres. Il faut par ailleurs toujours vérfier, sur un ou deux exemples, que la fonction fonctionne bien, c’est-à-dire donne les mêmes résultats que le code initial.
Pour pouvoir les réutiliser ultérieurement, on peut les réécrire dans un nouveau script qu’on enregistre dans un dossier de notre projet qu’on intitule “fonctions” ; il suffira ensuite d’appeler ce programme avec la fonction source()
:
source("fonctions/fonctions.R")