Exploiter les donnĂ©es du recensement đšđŠ
Bienvenue dans ce nouveau projet ! Nous allons explorer lâimmense quantitĂ© dâinformations provenant du recensement canadien fourni par Statistique Canada ! Pour traiter les donnĂ©es, nous utiliserons la librairie Simple Data Analysis (SDA), que jâai créée (donnez-lui une Ă©toile â).
Pour chaque rĂ©gion mĂ©tropolitaine, nous crĂ©erons une carte montrant les zones oĂč les revenus des mĂ©nages sont infĂ©rieurs ou supĂ©rieurs Ă la mĂ©diane. La carte de MontrĂ©al ci-dessous est un exemple du rĂ©sultat final que nous allons obtenir ensemble.
Dans ce projet, je vais vous montrer des techniques avancĂ©es pour traiter de grands ensembles de donnĂ©es avec TypeScript. Les donnĂ©es du recensement de 2021 pĂšsent environ 30 GB, mais vous aurez besoin dâenviron 70 GB dâespace libre sur votre disque dur pour ce projet. Il est temps de faire un peu de mĂ©nage ! đ§č
Si vous ĂȘtes bloquĂ© Ă un moment donnĂ©, il peut ĂȘtre utile de revoir les leçons prĂ©cĂ©dentes expliquant les bases de SDA :
Nous utiliserons Deno et VS Code. Consultez la leçon Installation si nécessaire.
Câest parti !
Quelle est la question ?
Pour ne pas perdre le fil, définissons la question à laquelle nous essayons de répondre :
- Pour chaque région métropolitaine canadienne, quelles sont les aires de diffusion dont le revenu des ménages est supérieur ou inférieur à la médiane ?
Les régions métropolitaines sont définies ainsi dans le recensement :
Une rĂ©gion mĂ©tropolitaine de recensement (RMR) est formĂ©e dâune ou de plusieurs municipalitĂ©s adjacentes centrĂ©es sur une rĂ©gion urbaine (appelĂ©e le noyau). Une RMR doit avoir une population totale dâau moins 100 000 habitants, dont au moins 50 000 vivent dans le noyau.
Voici la définition des aires de diffusion :
Une aire de diffusion (AD) est une petite unitĂ© gĂ©ographique relativement stable avec une population moyenne de 400 Ă 700 personnes. Câest la plus petite unitĂ© gĂ©ographique pour laquelle toutes les donnĂ©es de recensement sont diffusĂ©es. Les AD couvrent tout le territoire canadien.
Câest parti pour le code !
Configuration
Pour tout configurer, utilisons setup-sda comme dans les leçons précédentes.
Créez un nouveau dossier, ouvrez-le avec VS Code, et exécutez : deno -A jsr:@nshiab/setup-sda
Ensuite, exécutez deno task sda
pour surveiller main.ts
et ses dépendances.
Pour que SDA fonctionne correctement, il est recommandĂ© dâavoir au moins la version 2.1.9 de Deno. Pour vĂ©rifier votre version, exĂ©cutez deno --version
dans votre terminal. Pour la mettre à jour, exécutez simplement deno upgrade
.
Téléchargement des données
Pour télécharger les données du recensement avec le plus de granularité possible, cliquez sur cette page de Statistique Canada.
Cliquez sur le premier volet Fichier de téléchargement complet puis sur le bouton CSV pour Canada, provinces, territoires, divisions de recensement (DR), subdivisions de recensement (SDR) et aires de diffusion (AD).
Si vous ne le trouvez pas, voici le lien direct. Cela téléchargera un fichier zip de 2,25 GB.
Parce que nous voulons travailler sur les rĂ©gions mĂ©tropolitaines, il serait utile dâavoir les noms des rĂ©gions pour chaque aire de diffusion.
Le fichier contenant ces informations se trouve ici. TĂ©lĂ©chargez-le aussi. Câest un autre fichier zip de 9,8 MB.
Et enfin, comme nous voulons créer une carte, nous avons besoin des frontiÚres géospatiales des aires de diffusion. Vous les trouverez ici.
Déplacez tout cela dans le dossier data
de votre projet et décompressez tout sauf les frontiÚres géospatiales dans le fichier lda_000b21a_e.zip
! Câest dĂ©compressĂ© dans mes captures dâĂ©cran, mais câĂ©tait une erreur đ„Č.
Surprise ! Vous avez maintenant plus de 27 GB de donnĂ©es Ă traiter. đ
Les données du recensement
Premier essai
En décompressant les données, nous avons obtenu un dossier contenant plusieurs fichiers. Les données se trouvent dans les fichiers dont le nom contient _data_
.
Essayons dâouvrir le premier pour les provinces de lâAtlantique.
import { SimpleDB } from "@nshiab/simple-data-analysis";
const sdb = new SimpleDB();
const census = sdb.newTable("census");
await census.loadData(
"sda/data/98-401-X2021006_eng_CSV/98-401-X2021006_English_CSV_data_Atlantic.csv",
);
await census.logTable()
await sdb.done();
Hmmm⊠Nous avons un problĂšme. Cette erreur signifie que les donnĂ©es nâutilisent pas lâencodage UTF-8
, qui est pourtant la norme de nos jours et nécessaire pour SDA.
Jâai contactĂ© Statistique Canada Ă ce sujet, et ils mâont dit quâils utilisaient lâencodage Windows-1252
. Cela signifie que notre premiĂšre Ă©tape consiste Ă rĂ©encoder les donnĂ©esâŠ
Et oui, les projets de donnĂ©es dans la vraie vie sont toujours aussi amusants ! đŹ
Réencodage des données
Comme le rĂ©encodage de donnĂ©es est une tĂąche courante, jâai créé la fonction reencode
et je lâai publiĂ©e dans la librairie journalism. Lorsque vous configurez votre projet avec setup-sda
, journalism est automatiquement installé. Cette étape sera donc trÚs facile !
Créons un nouveau fichier toUTF8.ts
dans le dossier helpers
avec le code ci-dessous. Comme nous nâavons besoin de rĂ©encoder les donnĂ©es quâune seule fois, nous nâexportons pas de fonction. Câest simplement un script que nous allons exĂ©cuter une seule fois.
En regardant les noms des fichiers, vous remarquerez quâils ont tous la mĂȘme structure, sauf pour la rĂ©gion. En crĂ©ant une liste avec les rĂ©gions, nous pouvons facilement parcourir tous les fichiers.
La fonction reencode
nécessite quatre arguments :
- le fichier dâentrĂ©e
- le fichier de sortie, qui ici a le mĂȘme nom que le fichier original mais avec
_utf8
Ă la fin - lâencodage dâorigine
- le nouvel encodage
import { reencode } from "@nshiab/journalism";
const regions = [
"Atlantic",
"BritishColumbia",
"Ontario",
"Prairies",
"Quebec",
"Territories",
];
for (const r of regions) {
console.log(`Processing ${r}`);
const newFile =
`sda/data/98-401-X2021006_eng_CSV/98-401-X2021006_English_CSV_data_${r}_utf8.csv`;
const originalFile =
`sda/data/98-401-X2021006_eng_CSV/98-401-X2021006_English_CSV_data_${r}.csv`;
await reencode(originalFile, newFile, "windows-1252", "utf-8");
console.log(`Done with ${r}`);
}
Pour exécuter ce script, nous pouvons créer une nouvelle tùche toUTF8
dans notre deno.json
. Ne vous inquiĂ©tez pas si vous nâavez pas la mĂȘme version que moi dans les imports. Tout va bien aller !
{
"tasks": {
"sda": "deno run --node-modules-dir=auto -A --watch --check sda/main.ts",
"clean": "rm -rf .sda-cache && rm -rf .tmp",
"toUTF8": "deno run -A sda/helpers/toUTF8.ts"
},
"nodeModulesDir": "auto",
"imports": {
"@nshiab/journalism": "jsr:@nshiab/journalism@^1.22.0",
"@nshiab/simple-data-analysis": "jsr:@nshiab/simple-data-analysis@^4.2.0",
"@observablehq/plot": "npm:@observablehq/plot@^0.6.17"
}
}
ArrĂȘtez de surveiller main.ts
(CTRL
+ C
dans votre terminal) et exécutons notre nouveau script avec notre nouvelle tùche : deno task toUTF8
Cela prendra quelques minutes pour réencoder tous les fichiers. Voici ce que vous verrez une fois que ce sera terminé.
De nouveaux fichiers sont apparus avec _utf8
dans leurs noms ! Et maintenant, votre dossier data
pĂšse⊠53 GB. đ€
Si vous manquez dâespace de stockage, supprimez les fichiers de donnĂ©es dâorigine. Nous travaillerons dĂ©sormais avec ceux qui se terminent par _utf8.csv
. Gardez également les autres fichiers, en particulier 98-401-X2021006_English_meta.txt
!
Réessayons
Essayons maintenant de charger et dâafficher le fichier CSV rĂ©encodĂ© pour les provinces de lâAtlantique. Mettez Ă jour main.ts
et exécutez deno task sda
dans votre terminal.
import { SimpleDB } from "@nshiab/simple-data-analysis";
const sdb = new SimpleDB();
const census = sdb.newTable("census");
await census.loadData(
"sda/data/98-401-X2021006_eng_CSV/98-401-X2021006_English_CSV_data_Atlantic_utf8.csv",
);
await census.logTable();
await sdb.done();
Si la disposition du tableau sâaffiche de maniĂšre Ă©trange dans votre terminal, câest parce que la largeur du tableau est supĂ©rieure Ă celle de votre terminal. Faites un clic droit sur le terminal et cherchez Toggle size with content width
. Il y a aussi un raccourci pratique que jâutilise tout le temps pour cela : OPTION
+ Z
sur Mac et ALT
+ Z
sur PC.
Ăa fonctionne ! đ„ł
Essayons un autre fichier : celui des Prairies, qui couvre les provinces de lâAlberta, de la Saskatchewan et du Manitoba.
import { SimpleDB } from "@nshiab/simple-data-analysis";
const sdb = new SimpleDB();
const census = sdb.newTable("census");
await census.loadData(
"sda/data/98-401-X2021006_eng_CSV/98-401-X2021006_English_CSV_data_Prairies_utf8.csv",
);
await census.logTable();
await sdb.done();
Oh non ! Encore une erreur⊠Il semble que ce fichier CSV soit mal formatĂ©âŠ
Nous pouvons ajuster les options pour rendre le chargement du CSV moins stricte et voir si cela fonctionne.
import { SimpleDB } from "@nshiab/simple-data-analysis";
const sdb = new SimpleDB();
const census = sdb.newTable("census");
await census.loadData(
"sda/data/98-401-X2021006_eng_CSV/98-401-X2021006_English_CSV_data_Prairies_utf8.csv",
{ strict: false },
);
await census.logTable();
await sdb.done();
Magnifique ! Tout fonctionne maintenant !
Charger toutes les données
JusquâĂ prĂ©sent, nous avons chargĂ© les donnĂ©es un fichier Ă la fois. Mais vous pouvez Ă©galement charger tous les fichiers CSV dans une seule table facilement.
Créons un nouveau fichier crunchData.ts
pour cela. Cette fonction async
aura un paramĂštre sdb
et retournera une table census
.
Lorsque vous avez des fichiers dont les noms suivent le mĂȘme modĂšle, vous pouvez utiliser des jokers *
. Dans notre cas, nous voulons charger tous les fichiers CSV se terminant par _utf8.csv
, donc nous les chargeons tous en utilisant *_utf8.csv
, comme montré à la ligne 6 ci-dessous.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv",
{
strict: false,
});
return census;
}
Mettons Ă jour main.ts
pour utiliser cette nouvelle fonction. Nous réglons également cacheVerbose
sur true
lors de la création de notre SimpleDB
. Cela enregistrera la durée totale et sera utile par la suite lorsque nous utiliserons le cache.
import { SimpleDB } from "@nshiab/simple-data-analysis";
import crunchData from "./helpers/crunchData.ts";
const sdb = new SimpleDB({ cacheVerbose: true });
const census = await crunchData(sdb);
await census.logTable();
await sdb.done();
En fonction de la mémoire RAM disponible sur votre ordinateur, vous pourriez voir un dossier .tmp
apparaßtre. Si les données sont plus volumineuses que votre RAM, ce dossier sera utilisé pour traiter toutes les données en y stockant des morceaux prétraités.
Ce dossier .tmp
peut devenir assez volumineux. Sur ma machine, aprÚs la premiÚre exécution, il pÚse environ 16 GB.
Si vous souhaitez nettoyer votre cache, exécutez deno task clean
. Cela supprimera .tmp
et .sda-cache
(nous en reparlerons plus tard). Vous pouvez Ă©galement les supprimer manuellement, mais nâoubliez pas de vider votre corbeille.
Nous pouvons enfin jeter un Ćil aux donnĂ©es. Avec 166 millions de lignes et 23 colonnes, nous avons environ 3,8 milliards de points de donnĂ©es. đ
Et charger tout cela a pris moins dâune minute sur mon ordinateur. Pas mal !
Limite et cache
Pour commencer Ă travailler sur les donnĂ©es, nous nâavons pas besoin de tout charger. Nous pouvons utiliser lâoption limit
pour ne charger que le premier million de lignes.
Maintenant, le chargement des données prend environ une seconde.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
limit: 1_000_000,
});
return census;
}
Nous pouvons également utiliser la méthode cache
. Tout ce qui est enveloppé par cette méthode sera exécuté une seule fois et le résultat sera stocké dans le dossier .sda-cache
. Si le code ne change pas dans la méthode cache
, les données seront chargées depuis le cache au lieu de relancer les calculs.
Lors de la premiÚre exécution, cela prend un peu plus de temps car les données sont écrites dans le cache.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.cache(async () => {
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
limit: 1_000_000,
});
});
return census;
}
Mais lors des exĂ©cutions suivantes, les donnĂ©es sont chargĂ©es depuis le cache, ce qui est beaucoup plus rapide. Sur mon MacBook Pro, câest 10 fois plus rapide ! đ±
Filtrage
Lâune des premiĂšres choses Ă faire lorsque lâon travaille avec de grands ensembles de donnĂ©es est de les filtrer pour ne conserver que les informations qui nous intĂ©ressent.
Notre question est :
- Pour chaque région métropolitaine canadienne, quelles sont les aires de diffusion dont le revenu des ménages est supérieur ou inférieur à la médiane ?
Pour trouver le revenu total des ménages, vous pouvez consulter le fichier 98-401-X2021006_English_meta.txt
. Il contient la liste de toutes les variables du recensement.
Le CHARACTERISTIC_ID
pour le Median total income of household in 2020 ($)
est 243
.
De plus, les fichiers de données du recensement que nous avons téléchargés contiennent différents niveaux géographiques, mais nous avons seulement besoin des aires de diffusion.
Enfin, nous nâavons besoin que de trois colonnes :
DGUID
, qui contient lâID gĂ©ospatial unique des aires de diffusion. Nous lâutiliserons pour trouver les bonnes frontiĂšres pour crĂ©er une carte.GEO_NAME
, qui contient lâID de nommage unique des aires de diffusion. Nous lâutiliserons pour rĂ©cupĂ©rer les noms des rĂ©gions mĂ©tropolitaines.C1_COUNT_TOTAL
, qui contient les valeurs de la variable. Dans notre cas, il sâagit du revenu total mĂ©dian dans chaque aire de diffusion. Nous pouvons renommer cette colonne pour avoir quelque chose de plus lisible.
Mettons Ă jour crunchData
pour ne conserver que ce dont nous avons besoin.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.cache(async () => {
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
limit: 1_000_000,
});
await census.keep({
GEO_LEVEL: "Dissemination area",
CHARACTERISTIC_ID: [243], // Median total income of household in 2020 ($)
});
await census.selectColumns([
"DGUID",
"GEO_NAME",
"C1_COUNT_TOTAL",
]);
await census.renameColumns({ C1_COUNT_TOTAL: "medianIncome" });
});
return census;
}
Câest beaucoup mieux ! Nous pouvons maintenant nous concentrer sur lâajout des noms des rĂ©gions mĂ©tropolitaines.
Les régions métropolitaines
Premier essai
Essayons de charger les noms qui se trouvent dans le fichier 2021_92-151_X.csv
. Nous pouvons mettre Ă jour crunchData.ts
. Nous continuons de travailler dans la méthode cache
.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.cache(async () => {
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
limit: 1_000_000,
});
await census.keep({
GEO_LEVEL: "Dissemination area",
CHARACTERISTIC_ID: [243], // Median total income of household in 2020 ($)
});
await census.selectColumns([
"DGUID",
"GEO_NAME",
"C1_COUNT_TOTAL",
]);
await census.renameColumns({ C1_COUNT_TOTAL: "medianIncome" });
const names = sdb.newTable("names");
await names.loadData("sda/data/2021_92-151_X.csv");
await names.logTable();
});
return census;
}
Nous connaissons cette erreur ! Câest encore un problĂšme dâencodage !
Réencodage à nouveau
Mettons Ă jour toUTF8.ts
pour convertir Ă©galement ce fichier CSV. Nous commentons le code prĂ©cĂ©dent car nous nâavons pas besoin de reconvertir les fichiers du recensement.
import { reencode } from "@nshiab/journalism";
// const regions = [
// "Atlantic",
// "BritishColumbia",
// "Ontario",
// "Prairies",
// "Quebec",
// "Territories",
// ];
// for (const r of regions) {
// console.log(`Processing ${r}`);
// const newFile =
// `sda/data/98-401-X2021006_eng_CSV/98-401-X2021006_English_CSV_data_${r}_utf8.csv`;
// const originalFile =
// `sda/data/98-401-X2021006_eng_CSV/98-401-X2021006_English_CSV_data_${r}.csv`;
// await reencode(originalFile, newFile, "windows-1252", "utf-8");
// console.log(`Done with ${r}`);
// }
console.log(`Processing names data`);
await reencode(
"sda/data/2021_92-151_X.csv",
"sda/data/2021_92-151_X_utf8.csv",
"windows-1252",
"utf-8",
);
console.log("Done with names data");
ArrĂȘtez de surveiller main.ts
dans votre terminal (CTRL
+ C
) et exécutez deno task toUTF8
.
Maintenant, chargeons notre nouveau fichier sda/data/2021_92-151_X_utf8.csv
dans crunchData.ts
.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.cache(async () => {
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
limit: 1_000_000,
});
await census.keep({
GEO_LEVEL: "Dissemination area",
CHARACTERISTIC_ID: [243], // Median total income of household in 2020 ($)
});
await census.selectColumns([
"DGUID",
"GEO_NAME",
"C1_COUNT_TOTAL",
]);
await census.renameColumns({ C1_COUNT_TOTAL: "medianIncome" });
const names = sdb.newTable("names");
await names.loadData("sda/data/2021_92-151_X_utf8.csv");
await names.logTable();
});
return census;
}
Encore une erreur⊠Nous devons Ă nouveau dĂ©finir lâoption strict
sur false
.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.cache(async () => {
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
limit: 1_000_000,
});
await census.keep({
GEO_LEVEL: "Dissemination area",
CHARACTERISTIC_ID: [243], // Median total income of household in 2020 ($)
});
await census.selectColumns([
"DGUID",
"GEO_NAME",
"C1_COUNT_TOTAL",
]);
await census.renameColumns({ C1_COUNT_TOTAL: "medianIncome" });
const names = sdb.newTable("names");
await names.loadData("sda/data/2021_92-151_X_utf8.csv", { strict: false });
await names.logTable();
});
return census;
}
Et maintenant, ça fonctionne ! Mais ce fichier contient un impressionnant total de 63 colonnes. đł
Filtrage
Si vous lisez la documentation (et que vous connaissez bien votre recensement đ„ž), vous rĂ©aliserez que vous nâavez besoin que de deux colonnes, aprĂšs avoir filtrĂ© CMATYPE_RMRGENRE
pour le type B
afin de ne conserver que les régions métropolitaines.
Comme le fichier contient des donnĂ©es pour diffĂ©rents niveaux gĂ©ographiques, nous supprimons les doublons créés en sĂ©lectionnant seulement deux colonnes. Et puisque le but est dâajouter les noms des rĂ©gions mĂ©tropolitaines Ă notre table census
, nous renommons la colonne DADGUID_ADIDUGD
en DGUID
pour joindre facilement les deux tables. Nous renommons également CMANAME_RMRNOM
en CMA
par souci de commodité.
Voici une version mise Ă jour de crunchData.ts
.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.cache(async () => {
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
limit: 1_000_000,
});
await census.keep({
GEO_LEVEL: "Dissemination area",
CHARACTERISTIC_ID: [243], // Median total income of household in 2020 ($)
});
await census.selectColumns([
"DGUID",
"GEO_NAME",
"C1_COUNT_TOTAL",
]);
await census.renameColumns({ C1_COUNT_TOTAL: "medianIncome" });
const names = sdb.newTable("names");
await names.loadData("sda/data/2021_92-151_X_utf8.csv", { strict: false });
await names.keep({
CMATYPE_RMRGENRE: "B",
});
await names.selectColumns(["DADGUID_ADIDUGD", "CMANAME_RMRNOM"]);
await names.removeDuplicates();
await names.renameColumns({
DADGUID_ADIDUGD: "DGUID",
CMANAME_RMRNOM: "CMA",
});
await census.join(names);
});
return census;
}
Victoire ! Nous avons maintenant le nom de la rĂ©gion mĂ©tropolitaine pour chaque aire de diffusion ! đ„ł
Les frontiĂšres des aires de diffusion
Simplification
Comme nous voulons créer une carte, nous avons besoin des frontiÚres des aires de diffusion. Nous les avons téléchargées précédemment sous forme de fichier compressé lda_000b21a_e.zip
(et je vous avais dit de ne pas le dĂ©compresser đŹ).
Statistique Canada fournit des donnĂ©es gĂ©ospatiales trĂšs dĂ©taillĂ©es. Mais comme nous voulons seulement dessiner des cartes, nous nâavons pas besoin dâun niveau de dĂ©tail aussi Ă©levĂ©. Une version simplifiĂ©e suffira et rendra notre code plus rapide.
Un de mes outils prĂ©fĂ©rĂ©s pour simplifier les donnĂ©es gĂ©ospatiales est mapshaper.org. Allez leur donner une â sur GitHub si vous avez un compte !
Allez sur le site et faites glisser lda_000b21a_e.zip
sur la page. Notez que vous ne pouvez pas faire glisser le fichier depuis VS Code. Faites-le depuis votre dossier Ă lâaide du Finder ou de lâExplorateur de fichiers de votre ordinateur.
AprĂšs quelques secondes, vous verrez toutes les aires de diffusion.
Vous pouvez maintenant cliquer sur Simplify
en haut à droite et sélectionner les options suivantes :
prevent shape removal
Visvalingam / weighted area
Cliquez sur Apply
!
Pour lâĂ©tape suivante, je zoome gĂ©nĂ©ralement sur une zone Ă haute densitĂ©, comme MontrĂ©al. Ensuite, Ă lâaide du curseur en haut, je vise un seuil de simplification qui ne modifie pas les formes globales.
Ici, 10% fonctionne plutÎt bien. Notez que vous pouvez également saisir directement le pourcentage souhaité.
LâĂ©tape suivante consiste Ă exporter les donnĂ©es simplifiĂ©es !
Cliquez sur le bouton Export
en haut Ă droite, conservez le format de fichier original Shapefile
, puis cliquez sur Export
.
Vous pouvez maintenant renommer ce fichier en lda_000b21a_e_simplified.shp.zip
(remarquez que jâai ajoutĂ© .shp.zip
Ă lâextension pour aider SDA Ă comprendre quâil sâagit dâun shapefile) et le dĂ©placer dans votre dossier data
.
Au lieu de 197 MB, nos données géospatiales ne pÚsent plus que 27 MB, ce qui accélérera nos calculs et le dessin des cartes !
Chargement des géométries
Pour charger les géométries, nous pouvons utiliser la méthode loadGeoData
. Nâoubliez pas de modifier lâextension du fichier Shapefile simplifiĂ© en .shp.zip
. Le shp
est important pour SDA. Sans cela, SDA ne reconnaĂźt pas le fichier en tant que Shapefile et ne peut pas le charger correctement.
Lorsque vous chargez de nouvelles données géospatiales, il est toujours important de vérifier la projection. Ci-dessous, nous utilisons la méthode logProjections
pour cela.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.cache(async () => {
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
limit: 1_000_000,
});
await census.keep({
GEO_LEVEL: "Dissemination area",
CHARACTERISTIC_ID: [243], // Median total income of household in 2020 ($)
});
await census.selectColumns([
"DGUID",
"GEO_NAME",
"C1_COUNT_TOTAL",
]);
await census.renameColumns({ C1_COUNT_TOTAL: "medianIncome" });
const names = sdb.newTable("names");
await names.loadData("sda/data/2021_92-151_X_utf8.csv", { strict: false });
await names.keep({
CMATYPE_RMRGENRE: "B",
});
await names.selectColumns(["DADGUID_ADIDUGD", "CMANAME_RMRNOM"]);
await names.removeDuplicates();
await names.renameColumns({
DADGUID_ADIDUGD: "DGUID",
CMANAME_RMRNOM: "CMA",
});
await census.join(names);
const disseminationAreas = sdb.newTable("disseminationAreas");
await disseminationAreas.loadGeoData(
"sda/data/lda_000b21a_e_simplified.shp.zip",
);
await disseminationAreas.logProjections()
await disseminationAreas.logTable();
});
return census;
}
Nous nâavons aucun problĂšme Ă charger les donnĂ©es gĂ©ospatiales. Nous pouvons voir les 57 932 aires de diffusion sous forme de lignes avec leurs propriĂ©tĂ©s et leurs gĂ©omĂ©tries.
Mais la projection proj=lcc
avec ses units=m
pose problÚme. Statistique Canada utilise la projection conforme conique de Lambert avec des coordonnées en mÚtres. Pour que de nombreuses méthodes de SDA fonctionnent correctement, nous avons besoin des coordonnées avec la projection WGS84 utilisant la latitude et la longitude.
Mais ne vous inquiĂ©tez pas, SDA gĂšre cela pour vous. Il vous suffit de passer lâoption { toWGS84: true }
pour convertir vos données géospatiales au bon format.
Pendant quâon y est, sĂ©lectionnons uniquement les colonnes qui nous intĂ©ressent :
DGUID
, qui est lâID unique pour les aires de diffusion. Nous lâutiliserons pour joindre les gĂ©omĂ©tries aux donnĂ©es du recensement.geom
, qui contient les géométries.
Et joignons la table disseminationAreas
Ă la table census
! Comme nous avons un DGUID
dans chaque table, SDA lâutilisera pour faire correspondre les donnĂ©es du recensement des aires de diffusion avec les bonnes frontiĂšres.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.cache(async () => {
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
limit: 1_000_000,
});
await census.keep({
GEO_LEVEL: "Dissemination area",
CHARACTERISTIC_ID: [243], // Median total income of household in 2020 ($)
});
await census.selectColumns([
"DGUID",
"GEO_NAME",
"C1_COUNT_TOTAL",
]);
await census.renameColumns({ C1_COUNT_TOTAL: "medianIncome" });
const names = sdb.newTable("names");
await names.loadData("sda/data/2021_92-151_X_utf8.csv", { strict: false });
await names.keep({
CMATYPE_RMRGENRE: "B",
});
await names.selectColumns(["DADGUID_ADIDUGD", "CMANAME_RMRNOM"]);
await names.removeDuplicates();
await names.renameColumns({
DADGUID_ADIDUGD: "DGUID",
CMANAME_RMRNOM: "CMA",
});
await census.join(names);
const disseminationAreas = sdb.newTable("disseminationAreas");
await disseminationAreas.loadGeoData(
"sda/data/lda_000b21a_e_simplified.shp.zip",
{ toWGS84: true },
);
await disseminationAreas.selectColumns(["DGUID", "geom"]);
await census.join(disseminationAreas);
});
return census;
}
Nos données sont enfin complÚtes ! Nous avons nos aires de diffusion avec leur revenu total médian des ménages, le nom de leur région métropolitaine et leurs frontiÚres !
Nous pouvons supprimer lâoption limit
à la ligne 9 et sélectionner uniquement les trois colonnes que nous utiliserons par la suite. Nous pouvons également supprimer les lignes avec des valeurs manquantes.
Traitons toutes les données maintenant !
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.cache(async () => {
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
});
await census.keep({
GEO_LEVEL: "Dissemination area",
CHARACTERISTIC_ID: [243], // Median total income of household in 2020 ($)
});
await census.selectColumns([
"DGUID",
"GEO_NAME",
"C1_COUNT_TOTAL",
]);
await census.renameColumns({ C1_COUNT_TOTAL: "medianIncome" });
const names = sdb.newTable("names");
await names.loadData("sda/data/2021_92-151_X_utf8.csv", { strict: false });
await names.keep({
CMATYPE_RMRGENRE: "B",
});
await names.selectColumns(["DADGUID_ADIDUGD", "CMANAME_RMRNOM"]);
await names.removeDuplicates();
await names.renameColumns({
DADGUID_ADIDUGD: "DGUID",
CMANAME_RMRNOM: "CMA",
});
await census.join(names);
const disseminationAreas = sdb.newTable("disseminationAreas");
await disseminationAreas.loadGeoData(
"sda/data/lda_000b21a_e_simplified.shp.zip",
{ toWGS84: true },
);
await disseminationAreas.selectColumns(["DGUID", "geom"]);
await census.join(disseminationAreas);
await census.selectColumns(["medianIncome", "CMA", "geom"]);
await census.removeMissing();
});
return census;
}
Nous avons maintenant nos données pour environ 37 000 aires de diffusion situées dans des régions métropolitaines.
Et comme tout ce code est dans la méthode cache
, il ne sâexĂ©cutera quâune seule fois, ce qui nous permettra de travailler rapidement sur les prochaines Ă©tapes ! Comme indiquĂ© ci-dessus, lors de la premiĂšre exĂ©cution, le code a mis 1 min 31 s Ă sâexĂ©cuter sur mon ordinateur. Mais lors de la seconde, il nâa fallu que 97 ms. đ
La structuration, le formatage, le filtrage et le nettoyage des donnĂ©es sont souvent les Ă©tapes les plus longues dans lâanalyse et la visualisation de donnĂ©es. đ« Mais il est Ă©galement extrĂȘmement important de bien les faire pour Ă©viter les erreurs dans votre analyse et vos visualisations.
Prenez toujours le temps de lire la documentation des donnĂ©es. Cela peut sembler une perte de temps au dĂ©but, mais cela vous fera en rĂ©alitĂ© gagner beaucoup de temps par la suite. Jâai dĂ©jĂ vĂ©cu ça. Faites-moi confiance. đ«Ł
Répondre à la question
Variation par rapport à la médiane
La question à laquelle nous voulons répondre est :
- Pour chaque région métropolitaine canadienne, quelles sont les aires de diffusion dont le revenu des ménages est supérieur ou inférieur à la médiane ?
Nous devons donc trouver le revenu total mĂ©dian des mĂ©nages pour chaque rĂ©gion mĂ©tropolitaine. Câest facile Ă faire avec la mĂ©thode summarize
.
Notez que nous pouvons maintenant travailler en dehors de la méthode cache
. Les données étant nettoyées et filtrées, cela devient beaucoup plus léger à traiter.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.cache(async () => {
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
});
await census.keep({
GEO_LEVEL: "Dissemination area",
CHARACTERISTIC_ID: [243], // Median total income of household in 2020 ($)
});
await census.selectColumns([
"DGUID",
"GEO_NAME",
"C1_COUNT_TOTAL",
]);
await census.renameColumns({ C1_COUNT_TOTAL: "medianIncome" });
const names = sdb.newTable("names");
await names.loadData("sda/data/2021_92-151_X_utf8.csv", { strict: false });
await names.keep({
CMATYPE_RMRGENRE: "B",
});
await names.selectColumns(["DADGUID_ADIDUGD", "CMANAME_RMRNOM"]);
await names.removeDuplicates();
await names.renameColumns({
DADGUID_ADIDUGD: "DGUID",
CMANAME_RMRNOM: "CMA",
});
await census.join(names);
const disseminationAreas = sdb.newTable("disseminationAreas");
await disseminationAreas.loadGeoData(
"sda/data/lda_000b21a_e_simplified.shp.zip",
{ toWGS84: true },
);
await disseminationAreas.selectColumns(["DGUID", "geom"]);
await census.join(disseminationAreas);
await census.selectColumns(["medianIncome", "CMA", "geom"]);
await census.removeMissing();
});
const medians = await census.summarize({
values: "medianIncome",
categories: "CMA",
summaries: "median",
outputTable: "medians",
});
await medians.logTable();
return census;
}
Maintenant que nous avons la médiane pour chaque CMA dans la table medians
, nous pouvons joindre la table medians
Ă la table census
. Comme les deux tables ont la colonne CMA
, SDA pourra facilement faire correspondre les lignes. Par commodité, nous pouvons supprimer la colonne value
de medians
avant de faire la jointure.
Nous pouvons maintenant ajouter une nouvelle colonne varPerc
avec la variation en pourcentage par rapport Ă la mĂ©diane pour chaque aire de diffusion. Nous pouvons Ă©galement arrondir les valeurs. Câest ce que nous utiliserons pour colorer nos cartes.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.cache(async () => {
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
});
await census.keep({
GEO_LEVEL: "Dissemination area",
CHARACTERISTIC_ID: [243], // Median total income of household in 2020 ($)
});
await census.selectColumns([
"DGUID",
"GEO_NAME",
"C1_COUNT_TOTAL",
]);
await census.renameColumns({ C1_COUNT_TOTAL: "medianIncome" });
const names = sdb.newTable("names");
await names.loadData("sda/data/2021_92-151_X_utf8.csv", { strict: false });
await names.keep({
CMATYPE_RMRGENRE: "B",
});
await names.selectColumns(["DADGUID_ADIDUGD", "CMANAME_RMRNOM"]);
await names.removeDuplicates();
await names.renameColumns({
DADGUID_ADIDUGD: "DGUID",
CMANAME_RMRNOM: "CMA",
});
await census.join(names);
const disseminationAreas = sdb.newTable("disseminationAreas");
await disseminationAreas.loadGeoData(
"sda/data/lda_000b21a_e_simplified.shp.zip",
{ toWGS84: true },
);
await disseminationAreas.selectColumns(["DGUID", "geom"]);
await census.join(disseminationAreas);
await census.selectColumns(["medianIncome", "CMA", "geom"]);
await census.removeMissing();
});
const medians = await census.summarize({
values: "medianIncome",
categories: "CMA",
summaries: "median",
outputTable: "medians",
});
await medians.removeColumns("value");
await census.join(medians);
await census.addColumn(
"varPerc",
"number",
`(medianIncome - median) / median * 100`,
);
await census.round("varPerc");
return census;
}
Nous avons maintenant la réponse à notre question : la variation par rapport à la médiane dans chaque aire de diffusion pour toutes les régions métropolitaines.
Il est temps de visualiser tout ça !
Création des cartes
Pour garder notre code organisĂ© et pour rester sain dâesprit, crĂ©ons un nouveau fichier visualizeData.ts
dans le dossier helpers
.
La nouvelle fonction aura besoin de la table census
et, pour lâinstant, elle se contentera de lâafficher dans la console.
import { SimpleTable } from "@nshiab/simple-data-analysis";
export default async function visualizeData(census: SimpleTable) {
await census.logTable();
}
Et mettons Ă jour main.ts
pour appeler cette nouvelle fonction.
import { SimpleDB } from "@nshiab/simple-data-analysis";
import crunchData from "./helpers/crunchData.ts";
import visualizeData from "./helpers/visualizeData.ts";
const sdb = new SimpleDB({ cacheVerbose: true });
const census = await crunchData(sdb);
await visualizeData(census);
await sdb.done();
Nous voulons dessiner une carte pour chaque région métropolitaine, alors commençons par récupérer toutes les régions métropolitaines uniques dans nos données.
Ensuite, nous pouvons les parcourir dans une boucle, cloner la table census
et ne garder que les lignes correspondant Ă la bonne CMA
.
Comme vous pouvez le voir dans la capture dâĂ©cran ci-dessous, lorsque vous ne spĂ©cifiez pas de nom pour les tables, SDA les nomme automatiquement table0
, table1
, etc. Ici, nous voyons que la derniĂšre table est table42
, ce qui signifie que nous parcourons 43 régions métropolitaines.
Pour connaßtre le nombre de régions métropolitaines, vous pouvez également vérifier la propriété .length
de allCMAs
, puisque getUniques
renvoie toutes les valeurs uniques dâune colonne sous forme de liste.
import { SimpleTable } from "@nshiab/simple-data-analysis";
export default async function visualizeData(census: SimpleTable) {
const allCMAs = await census.getUniques("CMA");
for (const CMA of allCMAs) {
const tableCMA = await census.cloneTable();
await tableCMA.keep({ CMA });
await tableCMA.logTable();
}
}
Comme la table census
possĂšde une colonne CMA
et que la boucle utilise également la variable CMA
, nous pouvons écrire { CMA }
comme raccourci pour { CMA: CMA }
.
Nous pouvons maintenant créer une fonction pour dessiner nos cartes. Lorsque nous avons tout configuré avec setup-sda
, nous avons automatiquement installĂ© Plot. Cette puissante librairie de visualisation de donnĂ©es fonctionne trĂšs bien avec SDA et câest ce que nous allons utiliser pour dessiner nos cartes.
Comme câest la mĂȘme fonction pour dessiner toutes les cartes, nous pouvons la crĂ©er en dehors de la boucle. Cette fonction attendra les donnĂ©es au format GeoJSON, avec une liste de features contenant des propriĂ©tĂ©s. Pour commencer, dessinons les polygones des aires de diffusion avec le marqueur geo
et colorons le fill
et le stroke
avec les valeurs calculées dans la colonne varPerc
.
Dans la boucle, nous pouvons passer cette fonction à la méthode writeMap
, qui lui transmettra les donnĂ©es de la table au format GeoJSON. Nous devons Ă©galement spĂ©cifier lâemplacement oĂč nous voulons enregistrer la carte, et nous utilisons le nom de la rĂ©gion mĂ©tropolitaine pour crĂ©er des fichiers uniques dans le dossier output
.
Comme nous sommes encore en phase dâexploration pour la crĂ©ation des cartes, limitons la boucle pour ne travailler que sur la premiĂšre rĂ©gion mĂ©tropolitaine pour lâinstant. Cela nous permettra dâitĂ©rer plus rapidement et dâamĂ©liorer notre visualisation des donnĂ©es.
import { SimpleTable } from "@nshiab/simple-data-analysis";
import { geo, plot } from "@observablehq/plot";
export default async function visualizeData(census: SimpleTable) {
const allCMAs = await census.getUniques("CMA");
function drawMap(
data: { features: { properties: { [key: string]: unknown } }[] },
) {
return plot({
marks: [
geo(data, { fill: "varPerc", stroke: "varPearc" }),
],
});
}
for (const CMA of allCMAs) {
const tableCMA = await census.cloneTable();
await tableCMA.keep({ CMA });
await tableCMA.writeMap(drawMap, `./sda/output/${CMA}.png`);
break;
}
}
Câest moche, mais ça fonctionne !
Nous pouvons maintenant ajouter un titre, un sous-titre et une légende. Nous pouvons également utiliser une échelle divergente pour les couleurs et restreindre le domaine de -100 % à +100 %. Nous pouvons aussi ajuster la projection. Si vous voulez en savoir plus sur ces personnalisations, consultez la leçon Visualiser des données.
Une chose que vous vous demandez peut-ĂȘtre est pourquoi nous utilisons data.features[0].properties.CMA
pour le titre.
Lorsque cette fonction sera exécutée, elle ne connaßtra pas la variable CMA
. Nous devons donc récupérer le nom de la région métropolitaine directement à partir des données. Comme nous utilisons writeMap
, SDA transmet les donnĂ©es sous forme de GeoJSON Ă cette fonction. Dans les features GeoJSON, les donnĂ©es sont stockĂ©es dans lâobjet properties
. Nous récupérons donc le nom de la région métropolitaine en prenant le premier feature et en trouvant le nom CMA
dans ses propriétés.
import { SimpleTable } from "@nshiab/simple-data-analysis";
import { geo, plot } from "@observablehq/plot";
export default async function visualizeData(census: SimpleTable) {
const allCMAs = await census.getUniques("CMA");
function drawMap(
data: { features: { properties: { [key: string]: unknown } }[] },
) {
return plot({
title: data.features[0].properties.CMA,
subtitle:
"Variation from the median household total income at the dissemination area level.",
caption: "2021 Census, Statistics Canada",
inset: 10,
projection: {
type: "mercator",
domain: data,
},
color: {
legend: true,
type: "diverging",
domain: [-100, 100],
label: null,
tickFormat: (d) => {
if (d > 0) {
return `+${d}%`;
} else {
return `${d}%`;
}
},
},
marks: [
geo(data, { fill: "varPerc", stroke: "varPerc" }),
],
});
}
for (const CMA of allCMAs) {
const tableCMA = await census.cloneTable();
await tableCMA.keep({ CMA });
await tableCMA.writeMap(drawMap, `./sda/output/${CMA}.png`);
break;
}
}
Ăa a bien meilleure allure.
Une derniĂšre chose que jâaime faire sur mes cartes est dâajouter un contour.
Dans la boucle, nous pouvons cloner la tableCMA
et fusionner toutes les géométries des aires de diffusion en utilisant la méthode aggregateGeo
. Comme cela peut ĂȘtre assez coĂ»teux en termes de calcul lorsquâil y a beaucoup dâaires de diffusion, nous mettons le rĂ©sultat en cache.
Nous pouvons ensuite insérer cette géométrie nouvellement créée dans la tableCMA
.
Dans la fonction drawMap
, nous pouvons ajouter un nouveau marqueur geo
pour dessiner le contour. Mais nous devons filtrer sur le varPerc
pour nous assurer de ne dessiner que le contour et non toutes les aires de diffusion Ă nouveau.
import { SimpleTable } from "@nshiab/simple-data-analysis";
import { geo, plot } from "@observablehq/plot";
export default async function visualizeData(census: SimpleTable) {
const allCMAs = await census.getUniques("CMA");
function drawMap(
data: { features: { properties: { [key: string]: unknown } }[] },
) {
return plot({
title: data.features[0].properties.CMA,
subtitle:
"Variation from the median household total income at the dissemination area level.",
caption: "2021 Census, Statistics Canada",
inset: 10,
projection: {
type: "mercator",
domain: data,
},
color: {
legend: true,
type: "diverging",
domain: [-100, 100],
label: null,
tickFormat: (d) => {
if (d > 0) {
return `+${d}%`;
} else {
return `${d}%`;
}
},
},
marks: [
geo(
data.features.filter((d) => typeof d.properties.varPerc === "number"),
{ fill: "varPerc", stroke: "varPerc" },
),
geo(data.features.filter((d) => d.properties.varPerc === null), {
stroke: "black",
opacity: 0.5,
}),
],
});
}
for (const CMA of allCMAs) {
const tableCMA = await census.cloneTable();
await tableCMA.keep({ CMA });
const outline = await tableCMA.cloneTable();
await outline.cache(async () => {
await outline.aggregateGeo("union");
});
await tableCMA.insertTables(outline);
await tableCMA.writeMap(drawMap, `./sda/output/${CMA}.png`);
break;
}
}
La différence est subtile, mais il est important de voir les trous dans la géométrie de la région métropolitaine.
Maintenant que nous sommes satisfaits de lâapparence de notre carte, il est temps de supprimer lâinstruction break
à la ligne 57 ! Créons 43 cartes ! Exécutez tout le code !
Si vous examinez les cartes aprÚs avoir exécuté votre code, vous remarquerez quelques problÚmes.
Tout dâabord, vous pouvez voir que certains dossiers ont Ă©tĂ© créés pour certaines rĂ©gions mĂ©tropolitaines dans le dossier output
. Câest parce que certains noms contiennent le caractĂšre /
! Nous devons remplacer ce caractĂšre par autre chose avant de le transmettre Ă writeMap
.
Nous pouvons mettre Ă jour visualizeData.ts
pour corriger cela.
import { SimpleTable } from "@nshiab/simple-data-analysis";
import { geo, plot } from "@observablehq/plot";
export default async function visualizeData(census: SimpleTable) {
const allCMAs = await census.getUniques("CMA");
function drawMap(
data: { features: { properties: { [key: string]: unknown } }[] },
) {
return plot({
title: data.features[0].properties.CMA,
subtitle:
"Variation from the median household total income at the dissemination area level.",
caption: "2021 Census, Statistics Canada",
inset: 10,
projection: {
type: "mercator",
domain: data,
},
color: {
legend: true,
type: "diverging",
domain: [-100, 100],
label: null,
tickFormat: (d) => {
if (d > 0) {
return `+${d}%`;
} else {
return `${d}%`;
}
},
},
marks: [
geo(
data.features.filter((d) => typeof d.properties.varPerc === "number"),
{ fill: "varPerc", stroke: "varPerc" },
),
geo(data.features.filter((d) => d.properties.varPerc === null), {
stroke: "black",
opacity: 0.5,
}),
],
});
}
for (const CMA of allCMAs) {
const tableCMA = await census.cloneTable();
await tableCMA.keep({ CMA });
const outline = await tableCMA.cloneTable();
await outline.cache(async () => {
await outline.aggregateGeo("union");
});
await tableCMA.insertTables(outline);
await tableCMA.writeMap(
drawMap,
`./sda/output/${(CMA as string).replaceAll("/", "-")}.png`,
);
}
}
Nous pouvons Ă©galement constater que le contour de certaines villes nâest pas correct et que la carte de MontrĂ©al nâest quâun Ă©norme polygone. Cela se produit gĂ©nĂ©ralement lorsque certaines gĂ©omĂ©tries sont invalides. La simplification crĂ©e souvent des gĂ©omĂ©tries invalides. Heureusement, nous pouvons utiliser la mĂ©thode fixGeo
pour résoudre ce problÚme !
Nous pouvons mettre Ă jour crunchData.ts
, dans la méthode cache
, pour corriger les géométries aprÚs le chargement des données géospatiales.
import { SimpleDB } from "@nshiab/simple-data-analysis";
export default async function crunchData(sdb: SimpleDB) {
const census = sdb.newTable("census");
await census.cache(async () => {
await census.loadData("sda/data/98-401-X2021006_eng_CSV/*_utf8.csv", {
strict: false,
});
await census.keep({
GEO_LEVEL: "Dissemination area",
CHARACTERISTIC_ID: [243], // Median total income of household in 2020 ($)
});
await census.selectColumns([
"DGUID",
"GEO_NAME",
"C1_COUNT_TOTAL",
]);
await census.renameColumns({ C1_COUNT_TOTAL: "medianIncome" });
const names = sdb.newTable("names");
await names.loadData("sda/data/2021_92-151_X_utf8.csv", { strict: false });
await names.keep({
CMATYPE_RMRGENRE: "B",
});
await names.selectColumns(["DADGUID_ADIDUGD", "CMANAME_RMRNOM"]);
await names.removeDuplicates();
await names.renameColumns({
DADGUID_ADIDUGD: "DGUID",
CMANAME_RMRNOM: "CMA",
});
await census.join(names);
const disseminationAreas = sdb.newTable("disseminationAreas");
await disseminationAreas.loadGeoData(
"sda/data/lda_000b21a_e_simplified.shp.zip",
{ toWGS84: true },
);
await disseminationAreas.fixGeo();
await disseminationAreas.selectColumns(["DGUID", "geom"]);
await census.join(disseminationAreas);
await census.selectColumns(["medianIncome", "CMA", "geom"]);
await census.removeMissing();
});
const medians = await census.summarize({
values: "medianIncome",
categories: "CMA",
summaries: "median",
outputTable: "medians",
});
await medians.removeColumns("value");
await census.join(medians);
await census.addColumn(
"varPerc",
"number",
`(medianIncome - median) / median * 100`,
);
await census.round("varPerc");
return census;
}
ArrĂȘtez de surveiller main.ts
(CTRL
+ C
dans votre terminal) et supprimez .temp
et .sda-cache
manuellement ou en exécutant deno task clean
. Supprimez également tout le contenu du dossier output
.
Et relançons tout depuis le début !
Tout fonctionne parfaitement et tous nos problĂšmes ont disparu. đ
Sur mon ordinateur, notre code a pu traiter toutes les donnĂ©es et dessiner toutes les cartes en 2 minutes et 46 secondes. Mais grĂące Ă notre systĂšme de mise en cache astucieux, si je veux ajuster lâapparence des cartes, cela prend seulement 32 secondes pour réécrire les 43 cartes.
Au fait, ici, nous enregistrons nos cartes sous forme dâimages, mais si vous voulez des vecteurs pour les modifier dans Illustrator ou dâautres outils de conception, il vous suffit de remplacer .png
par .svg
dans writeMap
.
Conclusion
Que de chemin parcouru ! Traiter de grands ensembles de donnĂ©es avec plusieurs tables stockant des donnĂ©es tabulaires et gĂ©ospatiales nâest pas une tĂąche facile.
Mais jâespĂšre que cet exemple concret vous a montrĂ© comment vous pouvez couper au travers de gigaoctets de donnĂ©es comme dans du beurre avec SDA. đ§
Amusez-vous bien avec votre prochain projet de donnĂ©es et nâhĂ©sitez pas Ă me contacter si vous souhaitez partager ce que vous crĂ©ez avec SDA ou si vous avez des questions ! đ