Études de cas
Huit métiers, une seule connexion
Que pouvez-vous faire avec MapleStats ? Huit questions de huit métiers différents, chacune résolue par des appels aux outils de MapleStats. Voyez par vous-même ci-dessous (petit avertissement : c'est impressionnant).
158
fichiers de microdonnées pour les statisticiens, gardiens des poids et des marges,
Le baccalauréat, province par province
La part des adultes de 25 à 64 ans dont le plus haut diplôme est un baccalauréat, estimée à partir du fichier de microdonnées à grande diffusion du recensement de 2021 avec ses poids d'enquête. Chaque barre est un intervalle de confiance à 95 % tiré des 16 poids de réplication du fichier : le plus étroit pour la Colombie-Britannique et le Québec, le plus large pour le Nord canadien et l'Île-du-Prince-Édouard, où l'échantillon est plus petit.
Voir les données
| Province | Estimation | IC à 95 %, borne inf. | IC à 95 %, borne sup. |
|---|---|---|---|
| Terre-Neuve-et-Labrador | 13,3 % | 12,5 % | 14,1 % |
| Île-du-Prince-Édouard | 18,2 % | 16,2 % | 20,1 % |
| Nouvelle-Écosse | 20,1 % | 19,4 % | 20,7 % |
| Nouveau-Brunswick | 16,7 % | 16,0 % | 17,3 % |
| Québec | 18,1 % | 17,9 % | 18,4 % |
| Ontario | 23,8 % | 23,6 % | 24,1 % |
| Manitoba | 20,3 % | 19,8 % | 20,9 % |
| Saskatchewan | 18,5 % | 17,8 % | 19,2 % |
| Alberta | 21,7 % | 21,3 % | 22,1 % |
| Colombie-Britannique | 22,9 % | 22,7 % | 23,1 % |
| Nord canadien | 15,1 % | 13,7 % | 16,4 % |
Les appels à l'origine de ce graphique
call_tool{"name": "statcan_pumf_tabulate", "arguments": {"url": "https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip", "rows": ["PR", "HDGREE"], "statistic": "share", "filters": {"AGEGRP": ["9", "10", "11", "12", "13", "14", "15", "16"], "HDGREE": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13"]}}}R
# ============================================================
# StatCan public use microdata file
# Purpose: Fetch the data behind MapleStats MCP's statcan_pumf_tabulate
# (exact: the PUMF ZIP)
# Inputs: https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip
# Outputs: data/raw/cen21_ind_98m0001x_part_rec21.zip; the unzipped files
# ============================================================
# 0. Setup ----
dir.create("data/raw", recursive = TRUE, showWarnings = FALSE)
# 1. Read inputs ----
download.file("https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip", "data/raw/cen21_ind_98m0001x_part_rec21.zip", mode = "wb")
zip::unzip("data/raw/cen21_ind_98m0001x_part_rec21.zip", exdir = "data/raw/cen21_ind_98m0001x_part_rec21")
file.remove("data/raw/cen21_ind_98m0001x_part_rec21.zip")
data_files <- list.files("data/raw/cen21_ind_98m0001x_part_rec21", full.names = TRUE, recursive = TRUE)Python
# ============================================================
# StatCan public use microdata file
# Purpose: Fetch the data behind MapleStats MCP's statcan_pumf_tabulate
# (exact: the PUMF ZIP)
# Inputs: https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip
# Outputs: data/raw/cen21_ind_98m0001x_part_rec21.zip; the unzipped files
# ============================================================
# %% 0. Setup
import zipfile
from pathlib import Path
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "cen21_ind_98m0001x_part_rec21.zip"
# %% 1. Read inputs
with httpx.Client(
http2=True,
follow_redirects=True,
timeout=300,
headers={"User-Agent": "Mozilla/5.0 (compatible; research script)"},
) as client:
response = client.get('https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip')
response.raise_for_status()
raw_path.write_bytes(response.content)
with zipfile.ZipFile(raw_path) as archive:
archive.extractall(RAW_DIR / raw_path.stem)Stata
* ============================================================
* StatCan public use microdata file
* Purpose: Fetch the data behind MapleStats MCP's statcan_pumf_tabulate
* (exact: the PUMF ZIP)
* Inputs: https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip
* Outputs: data/raw/cen21_ind_98m0001x_part_rec21.zip; the unzipped files
* ============================================================
version 18
clear all
set more off
* 0. Setup
capture mkdir "logs"
capture log close
log using "logs/statcan_pumf_tabulate.log", replace
capture mkdir "data"
capture mkdir "data/raw"
* 1. Read inputs
* Stata reads no JSON or HTML and truncates long column names, so its
* built-in Python (Stata 16+) fetches, filters and writes a CSV. Point
* Stata at a Python with these packages first: python set exec <path>.
python:
import json
import zipfile
from pathlib import Path
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "cen21_ind_98m0001x_part_rec21.zip"
with httpx.Client(http2=True, follow_redirects=True, timeout=300, headers={"User-Agent": "Mozilla/5.0 (compatible; research script)"}) as client: response = client.get('https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip')
response.raise_for_status()
raw_path.write_bytes(response.content)
with zipfile.ZipFile(raw_path) as archive: archive.extractall(RAW_DIR / raw_path.stem)
end
log closeJulia
# ============================================================
# StatCan public use microdata file
# Purpose: Fetch the data behind MapleStats MCP's statcan_pumf_tabulate
# (exact: the PUMF ZIP)
# Inputs: https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip
# Outputs: data/raw/cen21_ind_98m0001x_part_rec21.zip; the unzipped files
# ============================================================
# 0. Setup
using Downloads
mkpath("data/raw")
# 1. Read inputs
Downloads.download("https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip", "data/raw/cen21_ind_98m0001x_part_rec21.zip")67
tableaux d'immigration pour les démographes, qui comptent ceux qui viennent s'établir,
Ceux qui viennent s'établir : Edmonton et Calgary
Les nouveaux résidents permanents qui ont indiqué chaque ville comme destination, par année, d'après les mises à jour mensuelles d'IRCC. *2026 ne couvre que les mois de janvier à juillet. IRCC arrondit chaque compte à un multiple de 5.
Voir les données
| Année | Edmonton | Calgary |
|---|---|---|
| 2015 | 16 745 | 21 715 |
| 2016 | 17 895 | 21 430 |
| 2017 | 15 960 | 17 875 |
| 2018 | 15 750 | 18 950 |
| 2019 | 16 425 | 19 635 |
| 2020 | 8 380 | 10 665 |
| 2021 | 14 760 | 17 870 |
| 2022 | 17 360 | 24 725 |
| 2023 | 21 665 | 27 430 |
| 2024 | 24 075 | 31 165 |
| 2025 | 18 980 | 23 560 |
| 2026* | 10 685 | 13 400 |
Les appels à l'origine de ce graphique
call_tool{"name": "ircc_monthly_query", "arguments": {"table_id": "ODP-PR-PT_CMA", "filters": {"census_metropolitan_area": "Edmonton"}, "period": "year", "year_from": 2015}}call_tool{"name": "ircc_monthly_query", "arguments": {"table_id": "ODP-PR-PT_CMA", "filters": {"census_metropolitan_area": "Calgary"}, "period": "year", "year_from": 2015}}R
# ============================================================
# IRCC monthly update: Canada - Permanent Residents by Province/Territory and CMA
# Purpose: Fetch the data behind MapleStats MCP's ircc_monthly_query
# (exact: the source file, then the tool's filters)
# Inputs: https://www.ircc.canada.ca/opendata-donneesouvertes/data/ODP-PR-PT_CMA.csv
# Outputs: data/raw/ODP-PR-PT_CMA.csv; the prepared table as `data`
# ============================================================
# 0. Setup ----
library(dplyr)
library(janitor)
library(readr)
library(stringr)
dir.create("data/raw", recursive = TRUE, showWarnings = FALSE)
# 1. Read inputs ----
download.file("https://www.ircc.canada.ca/opendata-donneesouvertes/data/ODP-PR-PT_CMA.csv", "data/raw/ODP-PR-PT_CMA.csv", mode = "wb")
data <- read_delim("data/raw/ODP-PR-PT_CMA.csv", delim = "\t", na = c("", "NA", "--"), show_col_types = FALSE)
# 2. Check inputs ----
stopifnot("https://www.ircc.canada.ca/opendata-donneesouvertes/data/ODP-PR-PT_CMA.csv returned no rows" = nrow(data) > 0)
# 3. Prepare data ----
# Keep the rows the MapleStats tool kept.
data <- data |>
filter(
str_to_lower(str_trim(`EN_CENSUS_METROPOLITAN_AREA`)) == "edmonton",
`EN_YEAR` >= 2015
)
data <- data |>
clean_names()
# Standard cleaning: trimmed text, empty strings as missing, and numbers
# stored as text converted to numbers.
data <- data |>
mutate(across(where(is.character), \(x) na_if(str_trim(x), ""))) |>
type_convert()Python
# ============================================================
# IRCC monthly update: Canada - Permanent Residents by Province/Territory and CMA
# Purpose: Fetch the data behind MapleStats MCP's ircc_monthly_query
# (exact: the source file, then the tool's filters)
# Inputs: https://www.ircc.canada.ca/opendata-donneesouvertes/data/ODP-PR-PT_CMA.csv
# Outputs: data/raw/ODP-PR-PT_CMA.csv; the prepared table as `data`
# ============================================================
# %% 0. Setup
import re
import unicodedata
from pathlib import Path
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "ODP-PR-PT_CMA.csv"
# %% 1. Read inputs
with httpx.Client(
http2=True,
follow_redirects=True,
timeout=300,
headers={"User-Agent": "Mozilla/5.0 (compatible; research script)"},
) as client:
response = client.get('https://www.ircc.canada.ca/opendata-donneesouvertes/data/ODP-PR-PT_CMA.csv')
response.raise_for_status()
raw_path.write_bytes(response.content)
data = pl.read_csv(raw_path, separator='\t', infer_schema_length=100_000, null_values=['--'])
# %% 2. Check inputs
assert data.height > 0, "https://www.ircc.canada.ca/opendata-donneesouvertes/data/ODP-PR-PT_CMA.csv returned no rows"
# %% 3. Prepare data
# Keep the rows the MapleStats tool kept.
data = data.filter(
pl.col('EN_CENSUS_METROPOLITAN_AREA').cast(pl.Utf8).str.strip_chars().str.to_lowercase() == 'edmonton',
pl.col('EN_YEAR').cast(pl.Float64, strict=False) >= 2015,
)
# Standard cleaning: snake_case names without accents (PÉRIODE -> periode,
# referenceNumber -> reference_number, as janitor does in R), trimmed text,
# empty strings as missing.
data = data.rename(
{
column: re.sub(
r"[^0-9a-z]+",
"_",
re.sub(
r"([a-z0-9])([A-Z])",
r"\1_\2",
unicodedata.normalize("NFKD", column).encode("ascii", "ignore").decode(),
).lower(),
).strip("_")
for column in data.columns
}
)
data = data.with_columns(pl.col(pl.Utf8).str.strip_chars().replace("", None))Stata
* ============================================================
* IRCC monthly update: Canada - Permanent Residents by Province/Territory and CMA
* Purpose: Fetch the data behind MapleStats MCP's ircc_monthly_query
* (exact: the source file, then the tool's filters)
* Inputs: https://www.ircc.canada.ca/opendata-donneesouvertes/data/ODP-PR-PT_CMA.csv
* Outputs: data/raw/ODP-PR-PT_CMA.csv; the prepared table as `data`
* ============================================================
version 18
clear all
set more off
* 0. Setup
capture mkdir "logs"
capture log close
log using "logs/ircc_monthly_query.log", replace
capture mkdir "data"
capture mkdir "data/raw"
* 1. Read inputs
* Stata reads no JSON or HTML and truncates long column names, so its
* built-in Python (Stata 16+) fetches, filters and writes a CSV. Point
* Stata at a Python with these packages first: python set exec <path>.
python:
import json
from pathlib import Path
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "ODP-PR-PT_CMA.csv"
with httpx.Client(http2=True, follow_redirects=True, timeout=300, headers={"User-Agent": "Mozilla/5.0 (compatible; research script)"}) as client: response = client.get('https://www.ircc.canada.ca/opendata-donneesouvertes/data/ODP-PR-PT_CMA.csv')
response.raise_for_status()
raw_path.write_bytes(response.content)
data = pl.read_csv(raw_path, separator='\t', infer_schema_length=100_000, null_values=['--'])
data = data.filter(pl.col('EN_CENSUS_METROPOLITAN_AREA').cast(pl.Utf8).str.strip_chars().str.to_lowercase() == 'edmonton', pl.col('EN_YEAR').cast(pl.Float64, strict=False) >= 2015)
nested = [name for name, dtype in data.schema.items() if dtype.is_nested()]
data = data.with_columns(pl.col(name).map_elements(lambda value: json.dumps(value.to_list() if isinstance(value, pl.Series) else value, default=str), return_dtype=pl.Utf8) for name in nested)
data.write_csv(RAW_DIR / "ODP-PR-PT_CMA_prepared.csv")
end
import delimited "data/raw/ODP-PR-PT_CMA_prepared.csv", clear varnames(1) encoding("utf-8")
* 2. Check inputs
assert _N > 0
* 3. Prepare data
* Standard cleaning: lower-case names, trimmed text, and numbers stored as
* text converted (destring leaves genuinely non-numeric text alone).
rename *, lower
quietly ds, has(type string)
local text_vars `r(varlist)'
foreach var of local text_vars {
replace `var' = strtrim(`var')
}
destring, replace
log closeJulia
# ============================================================
# IRCC monthly update: Canada - Permanent Residents by Province/Territory and CMA
# Purpose: Fetch the data behind MapleStats MCP's ircc_monthly_query
# (exact: the source file, then the tool's filters)
# Inputs: https://www.ircc.canada.ca/opendata-donneesouvertes/data/ODP-PR-PT_CMA.csv
# Outputs: data/raw/ODP-PR-PT_CMA.csv; the prepared table as `data`
# ============================================================
# 0. Setup
using DataFrames
using Downloads
using TidierData
using TidierFiles
mkpath("data/raw")
# 1. Read inputs
Downloads.download("https://www.ircc.canada.ca/opendata-donneesouvertes/data/ODP-PR-PT_CMA.csv", "data/raw/ODP-PR-PT_CMA.csv")
data = read_csv("data/raw/ODP-PR-PT_CMA.csv", delim = "\t", missingstring = ["", "--"])
# 2. Check inputs
@assert nrow(data) > 0 "https://www.ircc.canada.ca/opendata-donneesouvertes/data/ODP-PR-PT_CMA.csv returned no rows"
# 3. Prepare data
# Keep the rows the MapleStats tool kept.
data = filter(row -> begin
(lowercase(strip(coalesce(string(row["EN_CENSUS_METROPOLITAN_AREA"]), ""))) == "edmonton") &&
(!ismissing(row["EN_YEAR"]) && row["EN_YEAR"] >= 2015)
end, data)
# Standard cleaning: snake_case names, trimmed text, empty strings as missing.
data = @chain data begin
@clean_names
end
data = mapcols(
col -> eltype(col) <: Union{Missing, AbstractString} ?
[ismissing(x) || isempty(strip(x)) ? missing : strip(x) for x in col] : col,
data,
)54
tableaux sur le logement pour les urbanistes, qui comptent les grues avant les clés,
Le Canada construit en hauteur, pas en largeur
Les mises en chantier par année selon la SCHL, pour les maisons individuelles et les appartements, dans les centres de 10 000 habitants ou plus. En 2005, le Canada a mis en chantier 93 994 maisons individuelles et 66 404 appartements ; en 2025, 41 837 et 164 839. Les appartements sont passés de 34 % de toutes les mises en chantier à 68 %, et dépassent les maisons individuelles chaque année depuis 2011.
Voir les données
| Année | Individuelles | Appartements |
|---|---|---|
| 2005 | 93 994 | 66 404 |
| 2006 | 94 110 | 69 150 |
| 2007 | 90 855 | 69 062 |
| 2008 | 74 435 | 83 565 |
| 2009 | 60 521 | 46 388 |
| 2010 | 74 244 | 61 396 |
| 2011 | 67 089 | 77 119 |
| 2012 | 67 172 | 92 951 |
| 2013 | 63 143 | 76 009 |
| 2014 | 62 380 | 76 599 |
| 2015 | 57 739 | 92 564 |
| 2016 | 60 549 | 88 007 |
| 2017 | 63 495 | 100 365 |
| 2018 | 54 180 | 109 820 |
| 2019 | 46 909 | 115 664 |
| 2020 | 49 704 | 119 428 |
| 2021 | 63 456 | 141 684 |
| 2022 | 57 515 | 144 043 |
| 2023 | 42 924 | 147 617 |
| 2024 | 44 357 | 149 113 |
| 2025 | 41 837 | 164 839 |
Les appels à l'origine de ce graphique
call_tool{"name": "cmhc_get_table_data", "arguments": {"category_level_1": "New Housing Construction", "category_level_2": "Starts (Actual)", "column_field": "1", "row_field": "TIMESERIES"}}call_tool{"name": "cmhc_list_categories", "arguments": {}}R
# ============================================================
# Reproduce cmhc_get_table_data
# Purpose: Fetch the data behind MapleStats MCP's cmhc_get_table_data
# (exact: the request the tool made, recorded while it ran)
# Inputs: https://www03.cmhc-schl.gc.ca/hmip-pimh/en/TableMapChart/ExportTable
# Outputs: data/raw/ExportTable.txt; the downloaded file
# ============================================================
# 0. Setup ----
library(httr2)
dir.create("data/raw", recursive = TRUE, showWarnings = FALSE)
# 1. Read inputs ----
request("https://www03.cmhc-schl.gc.ca/hmip-pimh/en/TableMapChart/ExportTable") |>
req_body_form(!!!list(`TableId` = "5.6.1", `GeographyId` = "1", `GeographyTypeId` = "1", `exportType` = "csv")) |>
req_perform(path = "data/raw/ExportTable.txt")Python
# ============================================================
# Reproduce cmhc_get_table_data
# Purpose: Fetch the data behind MapleStats MCP's cmhc_get_table_data
# (exact: the request the tool made, recorded while it ran)
# Inputs: https://www03.cmhc-schl.gc.ca/hmip-pimh/en/TableMapChart/ExportTable
# Outputs: data/raw/ExportTable.txt; the downloaded file
# ============================================================
# %% 0. Setup
from pathlib import Path
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "ExportTable.txt"
# %% 1. Read inputs
with httpx.Client(
http2=True,
follow_redirects=True,
timeout=300,
headers={"User-Agent": "Mozilla/5.0 (compatible; research script)"},
) as client:
response = client.post('https://www03.cmhc-schl.gc.ca/hmip-pimh/en/TableMapChart/ExportTable', data={'TableId': '5.6.1', 'GeographyId': '1', 'GeographyTypeId': '1', 'exportType': 'csv'})
response.raise_for_status()
raw_path.write_bytes(response.content)Stata
* ============================================================
* Reproduce cmhc_get_table_data
* Purpose: Fetch the data behind MapleStats MCP's cmhc_get_table_data
* (exact: the request the tool made, recorded while it ran)
* Inputs: https://www03.cmhc-schl.gc.ca/hmip-pimh/en/TableMapChart/ExportTable
* Outputs: data/raw/ExportTable.txt; the downloaded file
* ============================================================
version 18
clear all
set more off
* 0. Setup
capture mkdir "logs"
capture log close
log using "logs/cmhc_get_table_data.log", replace
capture mkdir "data"
capture mkdir "data/raw"
* 1. Read inputs
* Stata reads no JSON or HTML and truncates long column names, so its
* built-in Python (Stata 16+) fetches, filters and writes a CSV. Point
* Stata at a Python with these packages first: python set exec <path>.
python:
import json
from pathlib import Path
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "ExportTable.txt"
with httpx.Client(http2=True, follow_redirects=True, timeout=300, headers={"User-Agent": "Mozilla/5.0 (compatible; research script)"}) as client: response = client.post('https://www03.cmhc-schl.gc.ca/hmip-pimh/en/TableMapChart/ExportTable', data={'TableId': '5.6.1', 'GeographyId': '1', 'GeographyTypeId': '1', 'exportType': 'csv'})
response.raise_for_status()
raw_path.write_bytes(response.content)
end
log closeJulia
# ============================================================
# Reproduce cmhc_get_table_data
# Purpose: Fetch the data behind MapleStats MCP's cmhc_get_table_data
# (exact: the request the tool made, recorded while it ran)
# Inputs: https://www03.cmhc-schl.gc.ca/hmip-pimh/en/TableMapChart/ExportTable
# Outputs: data/raw/ExportTable.txt; the downloaded file
# ============================================================
# 0. Setup
using Downloads
mkpath("data/raw")
# 1. Read inputs
Downloads.request(
"https://www03.cmhc-schl.gc.ca/hmip-pimh/en/TableMapChart/ExportTable";
headers = ["Content-Type" => "application/x-www-form-urlencoded"], method = "POST", input = IOBuffer("TableId=5.6.1&GeographyId=1&GeographyTypeId=1&exportType=csv"),
output = "data/raw/ExportTable.txt",
)962 449
fiches du recensement pour les microéconomistes, tisseurs d'incitations, une vie à la fois,
Le faible revenu d'une génération à l'autre
La part des personnes sous la mesure de faible revenu après impôt, selon la génération, tirée des mêmes microdonnées du recensement de 2021, avec des intervalles de confiance à 95 % issus des poids de réplication. Les immigrants de première génération sont les plus susceptibles d'être à faible revenu (14,0 %). Leurs enfants, nés au Canada, le sont le moins (9,1 % et 8,8 %), moins que la troisième génération ou plus (10,3 %).
Voir les données
| Génération | Estimation | IC à 95 %, borne inf. | IC à 95 %, borne sup. |
|---|---|---|---|
| Première génération (née à l'étranger) | 14,0 % | 13,8 % | 14,1 % |
| Deuxième génération, deux parents nés à l'étranger | 9,1 % | 9,0 % | 9,3 % |
| Deuxième génération, un parent né à l'étranger | 8,8 % | 8,6 % | 9,1 % |
| Troisième génération ou plus | 10,3 % | 10,2 % | 10,4 % |
Les appels à l'origine de ce graphique
call_tool{"name": "statcan_pumf_tabulate", "arguments": {"url": "https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip", "rows": ["GENSTAT", "LOLIMA"], "statistic": "share", "filters": {"GENSTAT": ["1", "2", "3", "4"], "LOLIMA": ["1", "2"]}}}R
# ============================================================
# StatCan public use microdata file
# Purpose: Fetch the data behind MapleStats MCP's statcan_pumf_tabulate
# (exact: the PUMF ZIP)
# Inputs: https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip
# Outputs: data/raw/cen21_ind_98m0001x_part_rec21.zip; the unzipped files
# ============================================================
# 0. Setup ----
dir.create("data/raw", recursive = TRUE, showWarnings = FALSE)
# 1. Read inputs ----
download.file("https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip", "data/raw/cen21_ind_98m0001x_part_rec21.zip", mode = "wb")
zip::unzip("data/raw/cen21_ind_98m0001x_part_rec21.zip", exdir = "data/raw/cen21_ind_98m0001x_part_rec21")
file.remove("data/raw/cen21_ind_98m0001x_part_rec21.zip")
data_files <- list.files("data/raw/cen21_ind_98m0001x_part_rec21", full.names = TRUE, recursive = TRUE)Python
# ============================================================
# StatCan public use microdata file
# Purpose: Fetch the data behind MapleStats MCP's statcan_pumf_tabulate
# (exact: the PUMF ZIP)
# Inputs: https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip
# Outputs: data/raw/cen21_ind_98m0001x_part_rec21.zip; the unzipped files
# ============================================================
# %% 0. Setup
import zipfile
from pathlib import Path
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "cen21_ind_98m0001x_part_rec21.zip"
# %% 1. Read inputs
with httpx.Client(
http2=True,
follow_redirects=True,
timeout=300,
headers={"User-Agent": "Mozilla/5.0 (compatible; research script)"},
) as client:
response = client.get('https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip')
response.raise_for_status()
raw_path.write_bytes(response.content)
with zipfile.ZipFile(raw_path) as archive:
archive.extractall(RAW_DIR / raw_path.stem)Stata
* ============================================================
* StatCan public use microdata file
* Purpose: Fetch the data behind MapleStats MCP's statcan_pumf_tabulate
* (exact: the PUMF ZIP)
* Inputs: https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip
* Outputs: data/raw/cen21_ind_98m0001x_part_rec21.zip; the unzipped files
* ============================================================
version 18
clear all
set more off
* 0. Setup
capture mkdir "logs"
capture log close
log using "logs/statcan_pumf_tabulate.log", replace
capture mkdir "data"
capture mkdir "data/raw"
* 1. Read inputs
* Stata reads no JSON or HTML and truncates long column names, so its
* built-in Python (Stata 16+) fetches, filters and writes a CSV. Point
* Stata at a Python with these packages first: python set exec <path>.
python:
import json
import zipfile
from pathlib import Path
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "cen21_ind_98m0001x_part_rec21.zip"
with httpx.Client(http2=True, follow_redirects=True, timeout=300, headers={"User-Agent": "Mozilla/5.0 (compatible; research script)"}) as client: response = client.get('https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip')
response.raise_for_status()
raw_path.write_bytes(response.content)
with zipfile.ZipFile(raw_path) as archive: archive.extractall(RAW_DIR / raw_path.stem)
end
log closeJulia
# ============================================================
# StatCan public use microdata file
# Purpose: Fetch the data behind MapleStats MCP's statcan_pumf_tabulate
# (exact: the PUMF ZIP)
# Inputs: https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip
# Outputs: data/raw/cen21_ind_98m0001x_part_rec21.zip; the unzipped files
# ============================================================
# 0. Setup
using Downloads
mkpath("data/raw")
# 1. Read inputs
Downloads.download("https://www150.statcan.gc.ca/n1/pub/98m0001x/2023001/cen21_ind_98m0001x_part_rec21.zip", "data/raw/cen21_ind_98m0001x_part_rec21.zip")98
cartes de crédit pour les spécialistes du marketing, lecteurs des petits caractères de la concurrence,
Ce que coûte une carte à récompenses
Toutes les cartes répertoriées pour l'Alberta dans l'outil de comparaison des cartes de crédit de l'Agence de la consommation en matière financière du Canada (ACFC), en dollars canadiens et sans les cartes pour étudiants ni les cartes garanties, qui y font l'objet de recherches distinctes : les frais annuels selon le taux d'intérêt sur les achats. Les 82 cartes à récompenses ont des frais médians de 99 $ et un taux médian de 21,99 % ; les 15 cartes sans récompenses, 25 $ et 13,99 %. Les gros points regroupent plusieurs cartes au même prix. Une carte prépayée, sans intérêt, est exclue.
Voir les données
| Type | Frais annuels | Taux sur les achats | Cartes |
|---|---|---|---|
| Avec récompenses | 0 $ | 19,99 % | 1 |
| Avec récompenses | 0 $ | 20,90 % | 3 |
| Avec récompenses | 0 $ | 20,95 % | 2 |
| Avec récompenses | 0 $ | 20,99 % | 7 |
| Avec récompenses | 0 $ | 21,74 % | 1 |
| Avec récompenses | 0 $ | 21,75 % | 1 |
| Avec récompenses | 0 $ | 21,90 % | 3 |
| Avec récompenses | 0 $ | 21,99 % | 15 |
| Avec récompenses | 39 $ | 20,99 % | 1 |
| Avec récompenses | 39 $ | 21,99 % | 1 |
| Avec récompenses | 50 $ | 12,99 % | 1 |
| Avec récompenses | 75 $ | 20,99 % | 1 |
| Avec récompenses | 89 $ | 21,99 % | 3 |
| Avec récompenses | 99 $ | 20,99 % | 1 |
| Avec récompenses | 99 $ | 21,99 % | 1 |
| Avec récompenses | 100 $ | 20,90 % | 1 |
| Avec récompenses | 110 $ | 20,90 % | 1 |
| Avec récompenses | 119,88 $ | 21,99 % | 1 |
| Avec récompenses | 120 $ | 20,95 % | 1 |
| Avec récompenses | 120 $ | 20,99 % | 4 |
| Avec récompenses | 120 $ | 21,99 % | 3 |
| Avec récompenses | 120 $ | 30,00 % | 1 |
| Avec récompenses | 130 $ | 20,90 % | 1 |
| Avec récompenses | 130 $ | 20,99 % | 1 |
| Avec récompenses | 139 $ | 20,99 % | 1 |
| Avec récompenses | 139 $ | 21,99 % | 7 |
| Avec récompenses | 150 $ | 20,99 % | 1 |
| Avec récompenses | 150 $ | 21,99 % | 3 |
| Avec récompenses | 165 $ | 20,50 % | 1 |
| Avec récompenses | 191,88 $ | 21,99 % | 1 |
| Avec récompenses | 199 $ | 21,99 % | 1 |
| Avec récompenses | 250 $ | 21,99 % | 1 |
| Avec récompenses | 250 $ | 30,00 % | 1 |
| Avec récompenses | 395 $ | 11,90 % | 1 |
| Avec récompenses | 399 $ | 20,99 % | 1 |
| Avec récompenses | 499 $ | 21,99 % | 1 |
| Avec récompenses | 599 $ | 21,99 % | 4 |
| Avec récompenses | 599 $ | 30,00 % | 1 |
| Avec récompenses | 799 $ | 30,00 % | 1 |
| Sans récompenses | 0 $ | 10,90 % | 1 |
| Sans récompenses | 0 $ | 12,99 % | 1 |
| Sans récompenses | 0 $ | 21,99 % | 1 |
| Sans récompenses | 0 $ | 29,90 % | 2 |
| Sans récompenses | 20 $ | 12,99 % | 1 |
| Sans récompenses | 20 $ | 13,99 % | 1 |
| Sans récompenses | 25 $ | 12,90 % | 1 |
| Sans récompenses | 25 $ | 12,99 % | 1 |
| Sans récompenses | 25 $ | 13,99 % | 1 |
| Sans récompenses | 29 $ | 13,99 % | 3 |
| Sans récompenses | 30 $ | 12,99 % | 1 |
| Sans récompenses | 39 $ | 10,99 % | 1 |
Les appels à l'origine de ce graphique
call_tool{"name": "fcac_search_credit_cards", "arguments": {"province": "AB", "limit": 100, "lang": "fr"}}reproduce_code : This is a web page the tool parses, not a data table, so a download does not reproduce the result. Cite the URL and retrieval date, or keep the tool's output as the raw input.6 434
jours de rendements obligataires pour les macroéconomistes, qui lisent la courbe avant le virage,
Quand la courbe des taux s'inverse
L'écart entre les rendements des obligations de référence du gouvernement du Canada à 10 ans et à 2 ans, chaque jour ouvrable depuis 2001, d'après l'API Valet de la Banque du Canada, tracé en moyenne mensuelle. Quand le 2 ans rapporte plus que le 10 ans, la courbe est inversée : les marchés s'attendent à une baisse des taux, le plus souvent parce qu'ils prévoient un ralentissement. Elle s'est inversée en 2007, 2019 à 2020 et 2022 à 2024. Le mois le plus creux a été juillet 2023, à -123 pb ; en septembre 2026, il était en moyenne de 62 pb.
Voir les données
| Année | Moyenne | Mois le plus bas | Mois le plus haut |
|---|---|---|---|
| 2001 | 117 pb | 41 pb | 213 pb |
| 2002 | 169 pb | 139 pb | 225 pb |
| 2003 | 155 pb | 124 pb | 188 pb |
| 2004 | 165 pb | 126 pb | 199 pb |
| 2005 | 88 pb | 23 pb | 131 pb |
| 2006 | 18 pb | 6 pb | 32 pb |
| 2007 | 9 pb | -5 pb | 32 pb |
| 2008 | 95 pb | 54 pb | 171 pb |
| 2009 | 200 pb | 167 pb | 218 pb |
| 2010 | 169 pb | 139 pb | 221 pb |
| 2011 | 141 pb | 110 pb | 158 pb |
| 2012 | 76 pb | 65 pb | 100 pb |
| 2013 | 115 pb | 76 pb | 157 pb |
| 2014 | 118 pb | 84 pb | 150 pb |
| 2015 | 97 pb | 77 pb | 117 pb |
| 2016 | 69 pb | 49 pb | 96 pb |
| 2017 | 69 pb | 34 pb | 95 pb |
| 2018 | 29 pb | 8 pb | 51 pb |
| 2019 | -0 pb | -18 pb | 14 pb |
| 2020 | 24 pb | -12 pb | 49 pb |
| 2021 | 88 pb | 47 pb | 124 pb |
| 2022 | -13 pb | -87 pb | 58 pb |
| 2023 | -91 pb | -123 pb | -71 pb |
| 2024 | -33 pb | -69 pb | 17 pb |
| 2025 | 63 pb | 36 pb | 81 pb |
| 2026 | 70 pb | 61 pb | 84 pb |
Les appels à l'origine de ce graphique
call_tool{"name": "boc_get_observations", "arguments": {"series_names": ["BD.CDN.2YR.DQ.YLD", "BD.CDN.10YR.DQ.YLD"], "start_date": "2001-01-01"}}R
# ============================================================
# Bank of Canada Valet: BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD
# Purpose: Fetch the data behind MapleStats MCP's boc_get_observations
# (exact: Valet request rebuilt from the tool's arguments)
# Inputs: https://www.bankofcanada.ca/valet/observations/BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD/json?start_date=2001-01-01
# Outputs: data/raw/valet_observations.json; the prepared table as `data`
# ============================================================
# 0. Setup ----
library(dplyr)
library(httr2)
library(janitor)
library(jsonlite)
library(lubridate)
library(readr)
library(stringr)
library(tibble)
library(tidyr)
dir.create("data/raw", recursive = TRUE, showWarnings = FALSE)
# 1. Read inputs ----
response <- request("https://www.bankofcanada.ca/valet/observations/BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD/json?start_date=2001-01-01") |>
req_perform()
writeLines(resp_body_string(response), "data/raw/valet_observations.json")
payload <- fromJSON("data/raw/valet_observations.json", flatten = TRUE)
data <- as_tibble(payload[["observations"]])
# 2. Check inputs ----
stopifnot("https://www.bankofcanada.ca/valet/observations/BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD/json?start_date=2001-01-01 returned no rows" = nrow(data) > 0)
# 3. Prepare data ----
data <- data |>
clean_names()
# Valet nests each series as <series>.v; make one row per date and series.
data <- data |>
pivot_longer(-d, names_to = "series", values_to = "value") |>
mutate(
series = str_remove(series, "_v$") |> str_to_upper(),
value = as.numeric(value),
date = ymd(d)
) |>
select(date, series, value)
# Standard cleaning: trimmed text, empty strings as missing, and numbers
# stored as text converted to numbers.
data <- data |>
mutate(across(where(is.character), \(x) na_if(str_trim(x), ""))) |>
type_convert()Python
# ============================================================
# Bank of Canada Valet: BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD
# Purpose: Fetch the data behind MapleStats MCP's boc_get_observations
# (exact: Valet request rebuilt from the tool's arguments)
# Inputs: https://www.bankofcanada.ca/valet/observations/BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD/json?start_date=2001-01-01
# Outputs: data/raw/valet_observations.json; the prepared table as `data`
# ============================================================
# %% 0. Setup
import json
import re
import unicodedata
from pathlib import Path
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "valet_observations.json"
# %% 1. Read inputs
with httpx.Client(
http2=True,
follow_redirects=True,
timeout=300,
headers={"User-Agent": "Mozilla/5.0 (compatible; research script)"},
) as client:
response = client.get('https://www.bankofcanada.ca/valet/observations/BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD/json?start_date=2001-01-01')
response.raise_for_status()
raw_path.write_bytes(response.content)
payload = json.loads(raw_path.read_text(encoding="utf-8"))
records = payload['observations']
data = pl.json_normalize(records, strict=False)
# %% 2. Check inputs
assert data.height > 0, "https://www.bankofcanada.ca/valet/observations/BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD/json?start_date=2001-01-01 returned no rows"
# %% 3. Prepare data
# Valet nests each series as <series>.v; make one row per date and series.
data = data.unpivot(index="d", variable_name="series", value_name="value")
data = data.with_columns(
pl.col("d").str.to_date().alias("date"),
pl.col("series").str.replace(r"\.v$", ""),
pl.col("value").cast(pl.Float64, strict=False),
).select("date", "series", "value")
# Standard cleaning: snake_case names without accents (PÉRIODE -> periode,
# referenceNumber -> reference_number, as janitor does in R), trimmed text,
# empty strings as missing. Names that clean alike are numbered as janitor
# numbers them (Indicator, indicator -> indicator, indicator_2).
clean_names = [
re.sub(
r"[^0-9a-z]+",
"_",
re.sub(
r"([a-z0-9])([A-Z])",
r"\1_\2",
unicodedata.normalize("NFKD", column).encode("ascii", "ignore").decode(),
).lower(),
).strip("_")
for column in data.columns
]
while len(set(clean_names)) < len(clean_names):
name_counts = {}
numbered = []
for name in clean_names:
name_counts[name] = name_counts.get(name, 0) + 1
count = name_counts[name]
numbered.append(name if count == 1 else f"{name}_{count}")
clean_names = numbered
data = data.rename(dict(zip(data.columns, clean_names)))
data = data.with_columns(pl.col(pl.Utf8).str.strip_chars().replace("", None))Stata
* ============================================================
* Bank of Canada Valet: BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD
* Purpose: Fetch the data behind MapleStats MCP's boc_get_observations
* (exact: Valet request rebuilt from the tool's arguments)
* Inputs: https://www.bankofcanada.ca/valet/observations/BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD/json?start_date=2001-01-01
* Outputs: data/raw/valet_observations.json; the prepared table as `data`
* ============================================================
version 18
clear all
set more off
* 0. Setup
capture mkdir "logs"
capture log close
log using "logs/boc_get_observations.log", replace
capture mkdir "data"
capture mkdir "data/raw"
* 1. Read inputs
* Stata reads no JSON or HTML and truncates long column names, so its
* built-in Python (Stata 16+) fetches, filters and writes a CSV. Point
* Stata at a Python with these packages first: python set exec <path>.
python:
import json
from pathlib import Path
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "valet_observations.json"
with httpx.Client(http2=True, follow_redirects=True, timeout=300, headers={"User-Agent": "Mozilla/5.0 (compatible; research script)"}) as client: response = client.get('https://www.bankofcanada.ca/valet/observations/BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD/json?start_date=2001-01-01')
response.raise_for_status()
raw_path.write_bytes(response.content)
payload = json.loads(raw_path.read_text(encoding="utf-8"))
records = payload['observations']
data = pl.json_normalize(records, strict=False)
nested = [name for name, dtype in data.schema.items() if dtype.is_nested()]
data = data.with_columns(pl.col(name).map_elements(lambda value: json.dumps(value.to_list() if isinstance(value, pl.Series) else value, default=str), return_dtype=pl.Utf8) for name in nested)
data.write_csv(RAW_DIR / "valet_observations_prepared.csv")
end
import delimited "data/raw/valet_observations_prepared.csv", clear varnames(1) encoding("utf-8")
* 2. Check inputs
assert _N > 0
* 3. Prepare data
* One column per series (<series>_v), one row per date.
generate date = date(d, "YMD")
format date %td
drop d
* Standard cleaning: lower-case names, trimmed text, and numbers stored as
* text converted (destring leaves genuinely non-numeric text alone).
* rename *, lower stops at a clash (Indicator next to indicator), so names
* that lower-case alike are numbered as janitor numbers them (indicator,
* indicator_2), then renamed in one group rename, which allows swaps.
local names
foreach var of varlist _all {
local names `names' `=strlower("`var'")'
}
local dups : list dups names
while "`dups'" != "" {
local numbered
local before
foreach name of local names {
local count 1
foreach earlier of local before {
if "`earlier'" == "`name'" local ++count
}
local before `before' `name'
if `count' > 1 {
local name = substr("`name'", 1, 32 - strlen("_`count'")) + "_`count'"
}
local numbered `numbered' `name'
}
local names `numbered'
local dups : list dups names
}
local old_names
local new_names
local i 0
foreach var of varlist _all {
local ++i
local name : word `i' of `names'
if "`name'" != "`var'" {
local old_names `old_names' `var'
local new_names `new_names' `name'
}
}
if "`old_names'" != "" {
rename (`old_names') (`new_names')
}
quietly ds, has(type string)
local text_vars `r(varlist)'
foreach var of local text_vars {
replace `var' = strtrim(`var')
}
destring, replace
log closeJulia
# ============================================================
# Bank of Canada Valet: BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD
# Purpose: Fetch the data behind MapleStats MCP's boc_get_observations
# (exact: Valet request rebuilt from the tool's arguments)
# Inputs: https://www.bankofcanada.ca/valet/observations/BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD/json?start_date=2001-01-01
# Outputs: data/raw/valet_observations.json; the prepared table as `data`
# ============================================================
# 0. Setup
using DataFrames
using Downloads
using JSON3
using Tables
using TidierData
mkpath("data/raw")
# 1. Read inputs
Downloads.download("https://www.bankofcanada.ca/valet/observations/BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD/json?start_date=2001-01-01", "data/raw/valet_observations.json")
payload = JSON3.read(read("data/raw/valet_observations.json", String))
records = payload["observations"]
data = DataFrame(Tables.dictrowtable(records))
# 2. Check inputs
@assert nrow(data) > 0 "https://www.bankofcanada.ca/valet/observations/BD.CDN.2YR.DQ.YLD,BD.CDN.10YR.DQ.YLD/json?start_date=2001-01-01 returned no rows"
# 3. Prepare data
# Standard cleaning: snake_case names, trimmed text, empty strings as missing.
data = @chain data begin
@clean_names
end
data = mapcols(
col -> eltype(col) <: Union{Missing, AbstractString} ?
[ismissing(x) || isempty(strip(x)) ? missing : strip(x) for x in col] : col,
data,
)1 610 458
brevets pour les scientifiques, qui vérifient qui est arrivé le premier,
L'apprentissage automatique au bureau des brevets
Les demandes de brevet canadiennes dans la classe CIB G06N, où se classe l'apprentissage automatique, par année de dépôt, d'après les données Horizons PI d'ISDE. De 24 en 2005 à un sommet de 667 en 2021. Les comptes viennent de la diffusion en bloc Horizons PI la plus récente au moment de l'enregistrement des appels, datée du 11 octobre 2024. Les demandes sont publiées environ 18 mois après leur dépôt ; cette diffusion ne contient donc qu'une partie de celles déposées de 2023 à 2025 (122 pour l'instant), et ces années sont exclues.
Voir les données
| Année de dépôt | G06N |
|---|---|
| 2005 | 24 |
| 2006 | 15 |
| 2007 | 24 |
| 2008 | 39 |
| 2009 | 18 |
| 2010 | 24 |
| 2011 | 12 |
| 2012 | 26 |
| 2013 | 36 |
| 2014 | 46 |
| 2015 | 56 |
| 2016 | 77 |
| 2017 | 178 |
| 2018 | 256 |
| 2019 | 478 |
| 2020 | 529 |
| 2021 | 667 |
| 2022 | 654 |
Les appels à l'origine de ce graphique
call_tool{"name": "ised_ip_horizons_search_patents", "arguments": {"filed_from": "1800-01-01", "limit": 1}}21 autres appels semblables, avec d'autres arguments.
R
# ============================================================
# Canadian patent search (CIPO IP Horizons)
# Purpose: Fetch the data behind MapleStats MCP's ised_ip_horizons_search_patents
# (exact: the IP Horizons tables the search used, then the tool's filters)
# Inputs: https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_1_to_2000000_2024-10-11.zip
# Outputs: data/raw/PT_main_1_to_2000000_2024-10-11.zip; the prepared table as `data`
# ============================================================
# 0. Setup ----
library(dplyr)
library(janitor)
library(purrr)
library(readr)
library(stringr)
dir.create("data/raw", recursive = TRUE, showWarnings = FALSE)
# 1. Read inputs ----
files <- c(
main_1 = "https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_1_to_2000000_2024-10-11.zip",
main_2 = "https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_2000001_to_4000000_2024-10-11.zip"
)
walk2(
files,
names(files),
\(url, name) download.file(url, str_c("data/raw/", name, ".zip"), mode = "wb")
)
# Every column as text; headers are bilingual ("Patent Number - Numéro du
# brevet"), so keep the English half in snake_case.
tables <- names(files) |>
map(
\(name) unzip(str_c("data/raw/", name, ".zip"), exdir = "data/raw") |>
read_delim(
delim = "|",
quote = "",
na = c("", "NULL"),
col_types = cols(.default = col_character()),
trim_ws = TRUE
) |>
rename_with(\(x) str_split_i(x, " - ", 1) |> make_clean_names())
) |>
set_names(names(files))
patents <- bind_rows(tables[c("main_1", "main_2")]) |>
filter(str_detect(filing_date, "^\\d{4}-\\d{2}-\\d{2}$"), filing_date >= "1800-01-01")
data <- patents |>
arrange(desc(filing_date))
# 2. Check inputs ----
stopifnot("https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_1_to_2000000_2024-10-11.zip returned no rows" = nrow(data) > 0)
# 3. Prepare data ----
data <- data |>
clean_names()
# Standard cleaning: trimmed text, empty strings as missing, and numbers
# stored as text converted to numbers.
data <- data |>
mutate(across(where(is.character), \(x) na_if(str_trim(x), ""))) |>
type_convert()Python
# ============================================================
# Canadian patent search (CIPO IP Horizons)
# Purpose: Fetch the data behind MapleStats MCP's ised_ip_horizons_search_patents
# (exact: the IP Horizons tables the search used, then the tool's filters)
# Inputs: https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_1_to_2000000_2024-10-11.zip
# Outputs: data/raw/PT_main_1_to_2000000_2024-10-11.zip; the prepared table as `data`
# ============================================================
# %% 0. Setup
import re
import ssl
import unicodedata
import zipfile
from pathlib import Path
import certifi
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "PT_main_1_to_2000000_2024-10-11.zip"
# %% 1. Read inputs
# CIPO's server omits its RapidSSL intermediate certificate; add it from the
# issuer on top of certifi's roots, so verification still anchors at a root.
intermediate = httpx.get("http://cacerts.rapidssl.com/RapidSSLTLSRSACAG1.crt").content
tls = ssl.create_default_context(cafile=certifi.where())
tls.load_verify_locations(cadata=ssl.DER_cert_to_PEM_cert(intermediate))
files = {
'main_1': 'https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_1_to_2000000_2024-10-11.zip',
'main_2': 'https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_2000001_to_4000000_2024-10-11.zip',
}
tables = {}
with httpx.Client(verify=tls, timeout=900, follow_redirects=True) as client:
for name, url in files.items():
zip_path = RAW_DIR / f"{name}.zip"
with client.stream("GET", url) as response, zip_path.open("wb") as handle:
response.raise_for_status()
for chunk in response.iter_bytes(1 << 20):
handle.write(chunk)
with zipfile.ZipFile(zip_path) as archive:
member = archive.namelist()[0]
archive.extract(member, RAW_DIR)
# Every column as text; headers are bilingual ("Patent Number - Numéro
# du brevet"), so keep the English half in snake_case.
tables[name] = (
pl.scan_csv(
RAW_DIR / member,
separator="|",
quote_char=None,
null_values=["NULL"],
infer_schema=False,
)
.rename(
lambda column: re.sub(
r"[^a-z0-9]+", "_", column.split(" - ")[0].strip().lower()
).strip("_")
)
.with_columns(pl.all().str.strip_chars())
)
patents = pl.concat([tables[name] for name in ['main_1', 'main_2']]).filter(pl.col("filing_date").str.contains(r"^\d{4}-\d{2}-\d{2}$"), pl.col("filing_date") >= "1800-01-01")
data = patents.sort("filing_date", descending=True, nulls_last=True).collect()
# %% 2. Check inputs
assert data.height > 0, "https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_1_to_2000000_2024-10-11.zip returned no rows"
# %% 3. Prepare data
# Standard cleaning: snake_case names without accents (PÉRIODE -> periode,
# referenceNumber -> reference_number, as janitor does in R), trimmed text,
# empty strings as missing. Names that clean alike are numbered as janitor
# numbers them (Indicator, indicator -> indicator, indicator_2).
clean_names = [
re.sub(
r"[^0-9a-z]+",
"_",
re.sub(
r"([a-z0-9])([A-Z])",
r"\1_\2",
unicodedata.normalize("NFKD", column).encode("ascii", "ignore").decode(),
).lower(),
).strip("_")
for column in data.columns
]
while len(set(clean_names)) < len(clean_names):
name_counts = {}
numbered = []
for name in clean_names:
name_counts[name] = name_counts.get(name, 0) + 1
count = name_counts[name]
numbered.append(name if count == 1 else f"{name}_{count}")
clean_names = numbered
data = data.rename(dict(zip(data.columns, clean_names)))
data = data.with_columns(pl.col(pl.Utf8).str.strip_chars().replace("", None))Stata
* ============================================================
* Canadian patent search (CIPO IP Horizons)
* Purpose: Fetch the data behind MapleStats MCP's ised_ip_horizons_search_patents
* (exact: the IP Horizons tables the search used, then the tool's filters)
* Inputs: https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_1_to_2000000_2024-10-11.zip
* Outputs: data/raw/PT_main_1_to_2000000_2024-10-11.zip; the prepared table as `data`
* ============================================================
version 18
clear all
set more off
* 0. Setup
capture mkdir "logs"
capture log close
log using "logs/ised_ip_horizons_search_patents.log", replace
capture mkdir "data"
capture mkdir "data/raw"
* 1. Read inputs
* Stata reads no JSON or HTML and truncates long column names, so its
* built-in Python (Stata 16+) fetches, filters and writes a CSV. Point
* Stata at a Python with these packages first: python set exec <path>.
python:
import json
import re
import ssl
import zipfile
from pathlib import Path
import certifi
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "PT_main_1_to_2000000_2024-10-11.zip"
intermediate = httpx.get("http://cacerts.rapidssl.com/RapidSSLTLSRSACAG1.crt").content
tls = ssl.create_default_context(cafile=certifi.where())
tls.load_verify_locations(cadata=ssl.DER_cert_to_PEM_cert(intermediate))
files = {'main_1': 'https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_1_to_2000000_2024-10-11.zip', 'main_2': 'https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_2000001_to_4000000_2024-10-11.zip'}
tables = {}
exec('with httpx.Client(verify=tls, timeout=900, follow_redirects=True) as client:\n for name, url in files.items():\n zip_path = RAW_DIR / f"{name}.zip"\n with client.stream("GET", url) as response, zip_path.open("wb") as handle:\n response.raise_for_status()\n for chunk in response.iter_bytes(1 << 20):\n handle.write(chunk)\n with zipfile.ZipFile(zip_path) as archive:\n member = archive.namelist()[0]\n archive.extract(member, RAW_DIR)\n tables[name] = (pl.scan_csv(RAW_DIR / member, separator="|", quote_char=None, null_values=["NULL"], infer_schema=False) .rename(lambda column: re.sub(r"[^a-z0-9]+", "_", column.split(" - ")[0].strip().lower()).strip("_")) .with_columns(pl.all().str.strip_chars()))\n')
patents = pl.concat([tables[name] for name in ['main_1', 'main_2']]).filter(pl.col("filing_date").str.contains('^\\d{4}-\\d{2}-\\d{2}\x24'), pl.col("filing_date") >= "1800-01-01")
data = patents.sort("filing_date", descending=True, nulls_last=True).collect()
nested = [name for name, dtype in data.schema.items() if dtype.is_nested()]
data = data.with_columns(pl.col(name).map_elements(lambda value: json.dumps(value.to_list() if isinstance(value, pl.Series) else value, default=str), return_dtype=pl.Utf8) for name in nested)
data.write_csv(RAW_DIR / "PT_main_1_to_2000000_202_prepared.csv")
end
import delimited "data/raw/PT_main_1_to_2000000_202_prepared.csv", clear varnames(1) encoding("utf-8")
* 2. Check inputs
assert _N > 0
* 3. Prepare data
* Standard cleaning: lower-case names, trimmed text, and numbers stored as
* text converted (destring leaves genuinely non-numeric text alone).
* rename *, lower stops at a clash (Indicator next to indicator), so names
* that lower-case alike are numbered as janitor numbers them (indicator,
* indicator_2), then renamed in one group rename, which allows swaps.
local names
foreach var of varlist _all {
local names `names' `=strlower("`var'")'
}
local dups : list dups names
while "`dups'" != "" {
local numbered
local before
foreach name of local names {
local count 1
foreach earlier of local before {
if "`earlier'" == "`name'" local ++count
}
local before `before' `name'
if `count' > 1 {
local name = substr("`name'", 1, 32 - strlen("_`count'")) + "_`count'"
}
local numbered `numbered' `name'
}
local names `numbered'
local dups : list dups names
}
local old_names
local new_names
local i 0
foreach var of varlist _all {
local ++i
local name : word `i' of `names'
if "`name'" != "`var'" {
local old_names `old_names' `var'
local new_names `new_names' `name'
}
}
if "`old_names'" != "" {
rename (`old_names') (`new_names')
}
quietly ds, has(type string)
local text_vars `r(varlist)'
foreach var of local text_vars {
replace `var' = strtrim(`var')
}
destring, replace
log closeJulia
# ============================================================
# Canadian patent search (CIPO IP Horizons)
# Purpose: Fetch the data behind MapleStats MCP's ised_ip_horizons_search_patents
# (exact: the IP Horizons tables the search used, then the tool's filters)
# Inputs: https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_1_to_2000000_2024-10-11.zip
# Outputs: data/raw/PT_main_1_to_2000000_2024-10-11.zip; the prepared table as `data`
# ============================================================
# 0. Setup
using CSV
using DataFrames
using Downloads
using TidierData
using ZipFile
mkpath("data/raw")
# 1. Read inputs
files = Dict(
"main_1" => "https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_1_to_2000000_2024-10-11.zip",
"main_2" => "https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_2000001_to_4000000_2024-10-11.zip",
)
tables = Dict{String, DataFrame}()
for (name, url) in files
zip_path = "data/raw/$(name).zip"
Downloads.download(url, zip_path)
archive = ZipFile.Reader(zip_path)
member = archive.files[1]
csv_path = "data/raw/$(member.name)"
write(csv_path, read(member))
close(archive)
# Every column as text; keep the English half of each bilingual header.
table = CSV.read(csv_path, DataFrame; delim = '|', quoted = false, missingstring = "NULL", types = String)
rename!(table, [lowercase(replace(strip(split(n, " - ")[1]), r"[^A-Za-z0-9]+" => "_")) for n in names(table)])
tables[name] = mapcols(col -> [ismissing(x) ? missing : strip(x) for x in col], table)
end
patents = vcat([tables[n] for n in ["main_1", "main_2"]]...)
patents = filter(row -> (!ismissing(row.filing_date) && occursin(r"^\d{4}-\d{2}-\d{2}$", row.filing_date) && row.filing_date >= "1800-01-01"), patents)
data = sort(patents, "filing_date"; rev = true)
# 2. Check inputs
@assert nrow(data) > 0 "https://opic-cipo.ca/cipo/client_downloads/Patent_CSV_2024_10_11/PT_main_1_to_2000000_2024-10-11.zip returned no rows"
# 3. Prepare data
# Standard cleaning: snake_case names, trimmed text, empty strings as missing.
data = @chain data begin
@clean_names
end
data = mapcols(
col -> eltype(col) <: Union{Missing, AbstractString} ?
[ismissing(x) || isempty(strip(x)) ? missing : strip(x) for x in col] : col,
data,
)15 937
séries de la Banque du Canada pour les analystes, condamnés à expliquer le taux,
Le taux directeur depuis 2015
Le taux cible du financement à un jour de la Banque du Canada, chaque jour ouvrable depuis janvier 2015 : 29 changements, d'un creux de 0,25 % à un sommet de 5,00 %. Le 24 septembre 2026, il s'établissait à 2,25 %.
Voir les données
| Date | Taux |
|---|---|
| 1er janvier 2015 | 1,00 % |
| 21 janvier 2015 | 0,75 % |
| 15 juillet 2015 | 0,50 % |
| 12 juillet 2017 | 0,75 % |
| 6 septembre 2017 | 1,00 % |
| 17 janvier 2018 | 1,25 % |
| 11 juillet 2018 | 1,50 % |
| 24 octobre 2018 | 1,75 % |
| 4 mars 2020 | 1,25 % |
| 16 mars 2020 | 0,75 % |
| 27 mars 2020 | 0,25 % |
| 3 mars 2022 | 0,50 % |
| 14 avril 2022 | 1,00 % |
| 2 juin 2022 | 1,50 % |
| 14 juillet 2022 | 2,50 % |
| 8 septembre 2022 | 3,25 % |
| 27 octobre 2022 | 3,75 % |
| 8 décembre 2022 | 4,25 % |
| 26 janvier 2023 | 4,50 % |
| 8 juin 2023 | 4,75 % |
| 13 juillet 2023 | 5,00 % |
| 6 juin 2024 | 4,75 % |
| 25 juillet 2024 | 4,50 % |
| 5 septembre 2024 | 4,25 % |
| 24 octobre 2024 | 3,75 % |
| 12 décembre 2024 | 3,25 % |
| 30 janvier 2025 | 3,00 % |
| 13 mars 2025 | 2,75 % |
| 18 septembre 2025 | 2,50 % |
| 30 octobre 2025 | 2,25 % |
| 24 septembre 2026 | 2,25 % |
Les appels à l'origine de ce graphique
call_tool{"name": "boc_get_observations", "arguments": {"series_names": ["V39079"], "start_date": "2015-01-01"}}R
# ============================================================
# Bank of Canada Valet: V39079
# Purpose: Fetch the data behind MapleStats MCP's boc_get_observations
# (exact: Valet request rebuilt from the tool's arguments)
# Inputs: https://www.bankofcanada.ca/valet/observations/V39079/json?start_date=2015-01-01
# Outputs: data/raw/valet_observations.json; the prepared table as `data`
# ============================================================
# 0. Setup ----
library(dplyr)
library(httr2)
library(janitor)
library(jsonlite)
library(lubridate)
library(readr)
library(stringr)
library(tibble)
library(tidyr)
dir.create("data/raw", recursive = TRUE, showWarnings = FALSE)
# 1. Read inputs ----
response <- request("https://www.bankofcanada.ca/valet/observations/V39079/json?start_date=2015-01-01") |>
req_perform()
writeLines(resp_body_string(response), "data/raw/valet_observations.json")
payload <- fromJSON("data/raw/valet_observations.json", flatten = TRUE)
data <- as_tibble(payload[["observations"]])
# 2. Check inputs ----
stopifnot("https://www.bankofcanada.ca/valet/observations/V39079/json?start_date=2015-01-01 returned no rows" = nrow(data) > 0)
# 3. Prepare data ----
data <- data |>
clean_names()
# Valet nests each series as <series>.v; make one row per date and series.
data <- data |>
pivot_longer(-d, names_to = "series", values_to = "value") |>
mutate(
series = str_remove(series, "_v$") |> str_to_upper(),
value = as.numeric(value),
date = ymd(d)
) |>
select(date, series, value)
# Standard cleaning: trimmed text, empty strings as missing, and numbers
# stored as text converted to numbers.
data <- data |>
mutate(across(where(is.character), \(x) na_if(str_trim(x), ""))) |>
type_convert()Python
# ============================================================
# Bank of Canada Valet: V39079
# Purpose: Fetch the data behind MapleStats MCP's boc_get_observations
# (exact: Valet request rebuilt from the tool's arguments)
# Inputs: https://www.bankofcanada.ca/valet/observations/V39079/json?start_date=2015-01-01
# Outputs: data/raw/valet_observations.json; the prepared table as `data`
# ============================================================
# %% 0. Setup
import json
import re
import unicodedata
from pathlib import Path
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "valet_observations.json"
# %% 1. Read inputs
with httpx.Client(
http2=True,
follow_redirects=True,
timeout=300,
headers={"User-Agent": "Mozilla/5.0 (compatible; research script)"},
) as client:
response = client.get('https://www.bankofcanada.ca/valet/observations/V39079/json?start_date=2015-01-01')
response.raise_for_status()
raw_path.write_bytes(response.content)
payload = json.loads(raw_path.read_text(encoding="utf-8"))
records = payload['observations']
data = pl.json_normalize(records, strict=False)
# %% 2. Check inputs
assert data.height > 0, "https://www.bankofcanada.ca/valet/observations/V39079/json?start_date=2015-01-01 returned no rows"
# %% 3. Prepare data
# Valet nests each series as <series>.v; make one row per date and series.
data = data.unpivot(index="d", variable_name="series", value_name="value")
data = data.with_columns(
pl.col("d").str.to_date().alias("date"),
pl.col("series").str.replace(r"\.v$", ""),
pl.col("value").cast(pl.Float64, strict=False),
).select("date", "series", "value")
# Standard cleaning: snake_case names without accents (PÉRIODE -> periode,
# referenceNumber -> reference_number, as janitor does in R), trimmed text,
# empty strings as missing.
data = data.rename(
{
column: re.sub(
r"[^0-9a-z]+",
"_",
re.sub(
r"([a-z0-9])([A-Z])",
r"\1_\2",
unicodedata.normalize("NFKD", column).encode("ascii", "ignore").decode(),
).lower(),
).strip("_")
for column in data.columns
}
)
data = data.with_columns(pl.col(pl.Utf8).str.strip_chars().replace("", None))Stata
* ============================================================
* Bank of Canada Valet: V39079
* Purpose: Fetch the data behind MapleStats MCP's boc_get_observations
* (exact: Valet request rebuilt from the tool's arguments)
* Inputs: https://www.bankofcanada.ca/valet/observations/V39079/json?start_date=2015-01-01
* Outputs: data/raw/valet_observations.json; the prepared table as `data`
* ============================================================
version 18
clear all
set more off
* 0. Setup
capture mkdir "logs"
capture log close
log using "logs/boc_get_observations.log", replace
capture mkdir "data"
capture mkdir "data/raw"
* 1. Read inputs
* Stata reads no JSON or HTML and truncates long column names, so its
* built-in Python (Stata 16+) fetches, filters and writes a CSV. Point
* Stata at a Python with these packages first: python set exec <path>.
python:
import json
from pathlib import Path
import httpx
import polars as pl
RAW_DIR = Path("data/raw")
RAW_DIR.mkdir(parents=True, exist_ok=True)
raw_path = RAW_DIR / "valet_observations.json"
with httpx.Client(http2=True, follow_redirects=True, timeout=300, headers={"User-Agent": "Mozilla/5.0 (compatible; research script)"}) as client: response = client.get('https://www.bankofcanada.ca/valet/observations/V39079/json?start_date=2015-01-01')
response.raise_for_status()
raw_path.write_bytes(response.content)
payload = json.loads(raw_path.read_text(encoding="utf-8"))
records = payload['observations']
data = pl.json_normalize(records, strict=False)
nested = [name for name, dtype in data.schema.items() if dtype.is_nested()]
data = data.with_columns(pl.col(name).map_elements(lambda value: json.dumps(value.to_list() if isinstance(value, pl.Series) else value, default=str), return_dtype=pl.Utf8) for name in nested)
data.write_csv(RAW_DIR / "valet_observations_prepared.csv")
end
import delimited "data/raw/valet_observations_prepared.csv", clear varnames(1) encoding("utf-8")
* 2. Check inputs
assert _N > 0
* 3. Prepare data
* One column per series (<series>_v), one row per date.
generate date = date(d, "YMD")
format date %td
drop d
* Standard cleaning: lower-case names, trimmed text, and numbers stored as
* text converted (destring leaves genuinely non-numeric text alone).
rename *, lower
quietly ds, has(type string)
local text_vars `r(varlist)'
foreach var of local text_vars {
replace `var' = strtrim(`var')
}
destring, replace
log closeJulia
# ============================================================
# Bank of Canada Valet: V39079
# Purpose: Fetch the data behind MapleStats MCP's boc_get_observations
# (exact: Valet request rebuilt from the tool's arguments)
# Inputs: https://www.bankofcanada.ca/valet/observations/V39079/json?start_date=2015-01-01
# Outputs: data/raw/valet_observations.json; the prepared table as `data`
# ============================================================
# 0. Setup
using DataFrames
using Downloads
using JSON3
using Tables
using TidierData
mkpath("data/raw")
# 1. Read inputs
Downloads.download("https://www.bankofcanada.ca/valet/observations/V39079/json?start_date=2015-01-01", "data/raw/valet_observations.json")
payload = JSON3.read(read("data/raw/valet_observations.json", String))
records = payload["observations"]
data = DataFrame(Tables.dictrowtable(records))
# 2. Check inputs
@assert nrow(data) > 0 "https://www.bankofcanada.ca/valet/observations/V39079/json?start_date=2015-01-01 returned no rows"
# 3. Prepare data
# Standard cleaning: snake_case names, trimmed text, empty strings as missing.
data = @chain data begin
@clean_names
end
data = mapcols(
col -> eltype(col) <: Union{Missing, AbstractString} ?
[ismissing(x) || isempty(strip(x)) ? missing : strip(x) for x in col] : col,
data,
)Un MCP pour que l'Agent les trouve, un MCP pour les réunir tous.
Voir les données
| Sujet | Outils |
|---|---|
| Statistique : statistiques et recensement | 62 |
| Provinces et villes : catalogues de données ouvertes, provincial, municipal | 47 |
| Argent et affaires : monnaie, prix et finances publiques, entreprises, PI et concurrence | 31 |
| Terre et énergie : agriculture et alimentation, environnement et risques naturels, énergie, géographie, transport et sécurité | 31 |
| Population : santé, logement, immigration | 22 |
| Parlement : parlement, droit et élections | 20 |
L'accès aux données et leur découverte ne sont plus le goulot d'étranglement.
Ne payez plus pour des chiffres que le Canada publie déjà gratuitement. Posez la question, obtenez le chiffre avec sa source et un script qui le récupère de nouveau, et consacrez votre temps à ce que vous voulez vraiment faire : le modèle, l'argument, la décision.
MCP signifie Model Context Protocol, une norme ouverte qui relie les agents IA aux outils et aux données. Voyez-le comme un port USB-C pour l'IA : tout agent qui le parle peut se brancher sur tout serveur qui le parle aussi. MapleStats est cette prise pour les données canadiennes : 215 outils d'organismes fédéraux, de provinces, de villes et de catalogues de données ouvertes, derrière une seule connexion.
Ce qu'une seule connexion atteint
- 1 610 458brevets
- 962 449fiches du recensement
- 15 937séries de la Banque du Canada
- 8 271tableaux de Statistique Canada
- 158fichiers de microdonnées
- 98cartes de crédit
- 67tableaux d'immigration
- 54tableaux sur le logement