| Statistique | Estimation (CAD) | Erreur-type (CAD) | Borne basse (CAD) | Borne haute (CAD) | Niveau | B | n |
|---|---|---|---|---|---|---|---|
| mean | 410 580 | 6 450,83 | 398 506,6 | 423 508,6 | 0,95 | 5 000 | 600 |
| median | 366 000 | 3 139,13 | 361 000,0 | 373 000,0 | 0,95 | 5 000 | 600 |
Estimer une moyenne et une médiane par bootstrap
Comment quantifier l’incertitude d’une statistique à partir des 600 maisons observées ?
À produire : Deux intervalles percentiles, une comparaison de la stabilité numérique et six réponses justifiant la population cible et l’interprétation.
Prérequis : Comprendre moyenne, médiane, écart-type et quantile. Exécuter un script dans RStudio, ou utiliser l’outil interactif.
Préparation enseignante
Parcours proposé : 60 à 90 minutes, durée indicative. Extraire la trousse entière, ouvrir Donnees-bleues.Rproj, puis exécuter installer-packages.R avant la séance avec Internet. Le CSV est inclus et le script fonctionne ensuite hors ligne. Les calculs ont été exécutés; l’utilisation en classe n’est pas documentée.
Sans programmation, utiliser l’outil interactif. La lecture courte prépare la discussion sur le niveau de confiance.
Télécharger la trousse avec les données
34 Ko. Préparation : 01/10/2026 à 14:20 (heure du Québec).
Les fichiers de données sont inclus dans la trousse.
Conditions de la source · Provenance et empreinte du ZIP
Fichiers inclus
-
maisons_quebec.csv: 600 lignes, 19 colonnes.
Contexte : une question et une population
On cherche à estimer la moyenne ou la médiane des valeurs foncières des 99 072 unités admissibles de cet instantané de Québec. Le fichier contient 600 unités tirées sans remise. Les critères figurent dans la méthode. Aucune maison n’est retirée selon sa valeur; les 19 colonnes du tirage sont complètes.
Les valeurs sont au rôle 2025, avec une référence au marché le 1er juillet 2023 et une extraction en 2026. Elles ne sont pas des prix de vente. Un intervalle pour la moyenne et un intervalle pour la médiane portent sur deux paramètres différents.
Consignes
- Exécuter
datasets/maisons-quebec/activite-bootstrap.Rdepuis le projet de la trousse. Il conserve les 600 maisons et réalise 5 000 tirages avec remise, graine 20261003. - Examiner le premier tirage et ses 12 premières positions. Comparer le nombre de lignes au nombre d’identifiants distincts dans le tirage complet.
- Comparer l’histogramme des valeurs individuelles aux histogrammes des moyennes et des médianes. Nommer ce que compte chaque axe vertical.
- Lire l’erreur-type bootstrap et les intervalles percentiles à 95 %. Vérifier les quantiles 2,5 % et 97,5 % dans le code.
- Comparer les 500, 2 000 et 5 000 premiers tirages d’une même suite. Puis comparer les niveaux 90 %, 95 % et 99 % sur les 5 000 tirages, sans changer la graine.
- Répondre aux six exercices avant de consulter le corrigé. Les sorties sont enregistrées dans
outputs/.
Résultats calculés
mean désigne la moyenne; median désigne la médiane. Les deux statistiques utilisent les mêmes tirages. Le nombre de maisons observées reste 600.
Les graphiques adaptent leurs axes à chaque distribution. Comparer leurs unités et leurs étendues numériques. Les médianes présentent des ex æquo parce que les valeurs administratives sont arrondies.
| Statistique | B | Erreur-type (CAD) | Borne basse (CAD) | Borne haute (CAD) |
|---|---|---|---|---|
| mean | 500 | 6 445,09 | 398 563,1 | 423 243,9 |
| median | 500 | 3 211,71 | 361 000,0 | 375 262,5 |
| mean | 2 000 | 6 363,87 | 398 294,8 | 423 071,4 |
| median | 2 000 | 3 096,38 | 361 000,0 | 373 000,0 |
| mean | 5 000 | 6 450,83 | 398 506,6 | 423 508,6 |
| median | 5 000 | 3 139,13 | 361 000,0 | 373 000,0 |
Une fluctuation des bornes avec B relève ici du calcul numérique. Leur largeur ne doit pas tendre vers zéro lorsque B augmente.
Six exercices
- Le premier tirage compte 600 lignes. Combien contient-il de maisons distinctes ? Pourquoi peut-il en contenir moins de 600 ?
- L’écart-type des valeurs et l’erreur-type de la moyenne sont-ils interchangeables ? Comparer leurs valeurs dans
repere_moyenne. - Donner les deux intervalles à 95 % et les paramètres qu’ils cherchent à estimer. L’intervalle de la médiane est-il une estimation de la moyenne ?
- Comparer les bornes avec B = 500, 2 000 et 5 000. Qu’est-ce qui reste inchangé ? Augmenter B revient-il à observer plus de maisons ?
- À tirages fixes, comment la largeur change-t-elle entre 90 %, 95 % et 99 % ? Peut-on conclure que le paramètre a une probabilité de 95 % d’être dans les bornes déjà calculées ?
- Expliquer pourquoi « 95 % des maisons du Québec se vendent dans cet intervalle » est incorrect. Nommer la population cible et une limite de l’approximation bootstrap.
- Le premier tirage contient 385 maisons distinctes. Le tirage avec remise permet des répétitions et omet certaines lignes originales. Il contient toujours 600 positions.
- L’écart-type des maisons vaut environ 161 465 CAD. L’erreur-type bootstrap de la moyenne vaut environ 6 451 CAD. Le premier décrit la dispersion individuelle; le second, la variation des moyennes rééchantillonnées.
- Moyenne : [398 507; 423 509] CAD. Médiane : [361 000; 373 000] CAD. Les cibles sont respectivement la moyenne et la médiane des valeurs évaluées dans la population admissible. Elles ne sont pas interchangeables.
- Les 600 observations, leur moyenne et leur médiane restent inchangées. B ajoute des rééchantillonnages de ces lignes. Les bornes fluctuent numériquement; augmenter B ne garantit pas une réduction monotone de leur largeur.
- Les intervalles percentiles sont emboîtés sur les mêmes tirages : un niveau plus élevé donne une largeur supérieure ou égale. Le niveau nominal décrit une procédure répétée; il n’attribue pas une probabilité fréquentiste au paramètre fixe dans cet intervalle réalisé.
- Les valeurs sont évaluées, sans transactions. L’intervalle porte sur une statistique, pas sur les valeurs individuelles. Le périmètre est celui des 99 072 unités admissibles de cet instantané de la ville de Québec, avec les exclusions documentées. Le bootstrap ordinaire néglige la faible fraction de sondage du plan initial sans remise; sa couverture exacte, surtout pour une médiane avec ex æquo, n’est pas établie.
Un repère pour approfondir
Pour la moyenne seulement, comparer l’erreur-type bootstrap au repère s/√n, puis à (s/√n)√(1 − n/N) pour le plan initial sans remise. Ici n = 600 et N = 99 072. La correction multiplie le repère par environ 0,997; elle ne modifie pas les intervalles bootstrap affichés.
| Écart-type individuel (CAD) | Repère avec remise (CAD) | Facteur de correction | Repère sans remise (CAD) |
|---|---|---|---|
| 161 464,9 | 6 591,778 | 0,997 | 6 571,787 |
Limites à faire nommer
Le bootstrap ne transforme pas un mauvais périmètre ou des valeurs erronées en données représentatives. Ces calculs ne constituent pas une étude de couverture. Pour refaire le parcours dans l’application, sélectionner 5 000 tirages, 95 %, la graine 20261003, puis calculer.
Code complet et sources
Afficher le script R
# Bootstrap des maisons à Québec. Aurélien Nicosia (2026).
# Code MIT; données MAMH et contenus CC BY 4.0.
# Valeurs au rôle 2025, référence au marché 2023-07-01, extraction 2026.
suppressPackageStartupMessages({
library(readr)
library(dplyr)
library(ggplot2)
library(scales)
})
bootstrap_labels <- c(valeur_fonciere_cad = "Valeur foncière évaluée (CAD)",
aire_etages_m2 = "Aire d’étages brute (m²)")
bootstrap_statistics <- c(mean = "Moyenne", median = "Médiane")
read_bootstrap_houses <- function(path) {
houses <- read_csv(path, show_col_types = FALSE, col_types = cols(
maison_id = col_character(), arrondissement_code = col_character(),
voisinage_code = col_character(), lien_physique_code = col_character(),
genre_construction_code = col_character()))
stopifnot(nrow(houses) == 600L, ncol(houses) == 19L,
!anyDuplicated(houses$maison_id),
all(vapply(houses[names(bootstrap_labels)], function(x)
is.numeric(x) && all(is.finite(x)), logical(1))))
houses
}
bootstrap_settings <- function(variable = "valeur_fonciere_cad", B = 2000L,
confidence = 0.95, seed = 20261003L) {
stopifnot(length(variable) == 1L, variable %in% names(bootstrap_labels),
length(B) == 1L, is.finite(B), B == as.integer(B), B >= 100L, B <= 10000L,
length(confidence) == 1L, confidence %in% c(0.90, 0.95, 0.99),
length(seed) == 1L, is.finite(seed), seed >= 0, seed <= 2147483646,
seed == as.integer(seed))
list(variable = variable, B = as.integer(B), confidence = confidence,
seed = as.integer(seed))
}
bootstrap_houses <- function(houses, settings) {
x <- houses[[settings$variable]]
stopifnot(length(x) >= 2L, all(is.finite(x)))
# Conserver l’état aléatoire de l’appelant. Fixer les trois algorithmes
# garantit les mêmes tirages dans R natif et dans WebR.
old_kind <- RNGkind()
had_seed <- exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE)
if (had_seed) old_seed <- get(".Random.seed", envir = .GlobalEnv)
on.exit({
do.call(RNGkind, as.list(old_kind))
if (had_seed) assign(".Random.seed", old_seed, envir = .GlobalEnv)
else if (exists(".Random.seed", envir = .GlobalEnv, inherits = FALSE))
rm(".Random.seed", envir = .GlobalEnv)
})
RNGkind("Mersenne-Twister", "Inversion", "Rejection")
set.seed(settings$seed)
n <- length(x)
draws <- matrix(NA_real_, nrow = settings$B, ncol = 2L,
dimnames = list(NULL, c("mean", "median")))
first_indices <- NULL
for (b in seq_len(settings$B)) {
indices <- sample.int(n, size = n, replace = TRUE)
if (b == 1L) first_indices <- indices
draws[b, ] <- c(mean(x[indices]), median(x[indices]))
}
list(settings = settings, n = n, x = x,
observed = c(mean = mean(x), median = median(x)),
replicates = tibble(repetition = seq_len(settings$B),
mean = draws[, 1L], median = draws[, 2L]),
first_indices = first_indices)
}
bootstrap_summary <- function(result, confidence = result$settings$confidence) {
stopifnot(confidence %in% c(0.90, 0.95, 0.99))
alpha <- 1 - confidence
bind_rows(lapply(names(bootstrap_statistics), function(statistic) {
values <- result$replicates[[statistic]]
bounds <- quantile(values, c(alpha / 2, 1 - alpha / 2), type = 7,
names = FALSE)
tibble(statistic = statistic, estimate = unname(result$observed[statistic]),
standard_error = sd(values), lower = bounds[1L], upper = bounds[2L],
confidence = confidence, B = nrow(result$replicates), n = result$n)
}))
}
bootstrap_plot <- function(result, statistic = "mean", original = FALSE) {
stopifnot(statistic %in% names(bootstrap_statistics))
label <- unname(bootstrap_labels[result$settings$variable])
summary <- bootstrap_summary(result) |> filter(.data$statistic == .env$statistic)
values <- if (original) result$x else result$replicates[[statistic]]
plot <- ggplot(tibble(value = values), aes(value)) +
geom_histogram(bins = 35, fill = "#185b83", colour = "white", linewidth = 0.3) +
geom_vline(xintercept = result$observed[statistic], colour = "#c36b25",
linewidth = 1) +
scale_x_continuous(labels = label_number(big.mark = " ", decimal.mark = ",")) +
labs(x = label, y = if (original) "Nombre de maisons" else "Nombre de rééchantillonnages",
title = if (original) "Les 600 valeurs observées" else
paste0("Les ", result$settings$B, " ", tolower(bootstrap_statistics[statistic]), "s rééchantillonnées"),
subtitle = if (original) "Une observation par maison\nTrait orange : statistique observée" else
"Une statistique par tirage de 600 lignes avec remise\nTrait orange : statistique observée") +
theme_minimal(base_size = 13) +
theme(panel.grid.minor = element_blank(), plot.title = element_text(face = "bold"))
if (!original) plot <- plot +
geom_vline(xintercept = c(summary$lower, summary$upper),
colour = "#226e59", linetype = "dashed", linewidth = 1) +
labs(caption = paste0("Traits verts : intervalle percentile à ",
100 * result$settings$confidence, " % (approximation)."))
plot
}
bootstrap_format <- function(x, digits = 0L) {
formatC(x, format = "f", digits = digits, big.mark = " ", decimal.mark = ",")
}
# ACTIVITÉ : exécution autonome depuis le projet de la trousse.
fichier <- "data/processed/maisons-quebec/maisons_quebec.csv"
if (!file.exists(fichier)) {
stop("Extraire la trousse entière et ouvrir Donnees-bleues.Rproj avant d’exécuter ce script.")
}
maisons <- read_bootstrap_houses(fichier)
reglages <- bootstrap_settings(B = 5000L, confidence = 0.95, seed = 20261003L)
resultats <- bootstrap_houses(maisons, reglages)
resume <- bootstrap_summary(resultats)
print(resume)
# Un tirage complet contient 600 positions; certains identifiants reviennent.
premier_tirage <- tibble(position = seq_len(resultats$n),
maison_id = maisons$maison_id[resultats$first_indices],
valeur_fonciere_cad = resultats$x[resultats$first_indices])
print(head(premier_tirage, 12))
print(tibble(lignes = nrow(premier_tirage),
maisons_distinctes = n_distinct(premier_tirage$maison_id)))
# Comparer des préfixes des mêmes tirages, plutôt que changer plusieurs choix.
stabilite <- bind_rows(lapply(c(500L, 2000L, 5000L), function(B) {
prefixe <- resultats
prefixe$settings$B <- B
prefixe$replicates <- head(resultats$replicates, B)
bootstrap_summary(prefixe)
}))
niveaux <- bind_rows(lapply(c(0.90, 0.95, 0.99), function(niveau)
bootstrap_summary(resultats, niveau)))
print(stabilite)
print(niveaux)
# Repère analytique pour la moyenne seulement, sous le plan de tirage décrit.
fraction_tirage <- nrow(maisons) / 99072
repere_moyenne <- tibble(
ecart_type_maisons = sd(resultats$x),
erreur_type_avec_remise = sd(resultats$x) / sqrt(resultats$n),
correction_population_finie = sqrt(1 - fraction_tirage),
erreur_type_sans_remise = sd(resultats$x) / sqrt(resultats$n) *
sqrt(1 - fraction_tirage))
print(repere_moyenne)
graphique_maisons <- bootstrap_plot(resultats, original = TRUE)
graphique_moyennes <- bootstrap_plot(resultats, "mean")
graphique_medianes <- bootstrap_plot(resultats, "median")
print(graphique_maisons)
print(graphique_moyennes)
print(graphique_medianes)
dir.create("outputs", showWarnings = FALSE)
write_csv(resume, "outputs/bootstrap-resume.csv")
write_csv(stabilite, "outputs/bootstrap-stabilite.csv")
write_csv(niveaux, "outputs/bootstrap-niveaux.csv")
write_csv(premier_tirage, "outputs/bootstrap-premier-tirage.csv")
write_csv(resultats$replicates, "outputs/bootstrap-repetitions.csv")
for (nom in c("maisons", "moyennes", "medianes")) {
ggsave(paste0("outputs/bootstrap-", nom, ".png"),
get(paste0("graphique_", nom)), width = 9, height = 5, dpi = 160, bg = "white")
}MAMH (2026), Rôles d’évaluation foncière du Québec, données CC BY 4.0; sélection par Aurélien Nicosia. Efron, B. (1979), Bootstrap Methods: Another Look at the Jackknife. R Core Team, sample et quantile, documentation consultée le 3 octobre 2026. Le repère avec correction de population finie découle de la variance sous sondage aléatoire simple, Statistique Canada (2019).
Vérifier le travail
- Chaque tirage contient 600 positions, avec des identifiants répétés possibles.
- Les bornes à 95 % sont les quantiles 2,5 % et 97,5 % des statistiques rééchantillonnées.
- B est distingué du nombre de maisons observées.
- La conclusion nomme les unités admissibles de Québec et le caractère approximatif de l’intervalle.
Objectifs et adaptations
Objectifs
- Distinguer dispersion des observations et erreur-type d’une statistique.
- Expliquer un tirage avec remise de même taille que l’échantillon initial.
- Construire un intervalle percentile pour une moyenne et une médiane.
- Distinguer incertitude d’échantillonnage et variabilité numérique liée à B.
Adaptations
- Utiliser uniquement l’application pour une discussion sans programmation.
- Refaire le parcours sur l’aire d’étages brute, en m², sans changer la population.
- Comparer l’erreur-type bootstrap de la moyenne à son repère analytique, avec correction de population finie.
Statut documenté : Calculs et script exécutés; utilisation en classe non documentée.
Contribution pédagogique : Aurélien Nicosia. Usage en cours non documenté.
- Ajout
- Mise à jour