Indicators: census tables and consumer prices
Source:vignettes/census-indicators.Rmd
census-indicators.Rmdknbs_indicators() lists what every verified indicator
source serves, and knbs_get_indicator() returns one
indicator as tidy observations. Three families exist today: tables from
the 2019 Population and Housing Census (CC BY 4.0), the
monthly consumer price series from the KNBS CPI
Explorer, the 323 economic dataflows of the KNBS Data
Portal — GDP, trade, balance of payments, county government
finance and much else, in SDMX form — and the 202 datasets
republished by Kenya Open Data for Africa, which carry the one
thing no first-party portal serves as data: Gross County Product.
knbs_indicators() # all families, source_id says which
cpi <- knbs_get_indicator("CPI_ALL_ITEMS", start = "2020-01")
gdp <- knbs_get_indicator("DF_NAT_GDPCP")
pop <- knbs_get_indicator("POPCEN0001", geo_level = "county")
gcp <- knbs_get_indicator("jhbzkjc") # Gross County Product, 2013-2020The Data Portal (SDMX)
Every dataflow keeps its SDMX id (DF_NAT_GDPCP,
DF_BOP_A, DF_CPI_R…);
knbs_indicators(source = "knbs_opendata") lists all 323
with their statistical domain as topic. Annual flows carry
integer-year periods; monthly and quarterly ones carry first-of-period
Dates, so start and end behave as
everywhere else. Dimensions vary per flow; each becomes a pair of
columns named by its SDMX id in lower case — na_indic
holding the labels (“Accommodation and food service activities”) and
na_indic_code the codes — and a Sex dimension becomes
sex, lower-cased as in the census tables. The ids name the
columns, not the portal’s labels, because the portal labels ten
different dimensions just “Indicator”. Single-category dimensions
(“Prices = Current prices”, “Unit = Kenya Shillings”) are facts about
the whole dataflow and fold into knbs_metadata() notes
instead of constant columns.
Two cautions: the portal states no licence, and its
geography codes are SDMX area codes, not census
territories — geo_level is NA there,
and they must never be joined against
knbs_geographies().
Chunks that contact the services are shown but not run, so building this vignette never depends on them.
Open Data for Africa (the republisher)
knbs_indicators(source = "opendataforafrica") lists the
202 datasets of kenya.opendataforafrica.org, the
Knoema-operated portal on KNBS’s own portals page — 169 sourced from
KNBS, the rest from the Central Bank of Kenya, ministries and agencies.
Dataset ids are the portal’s own opaque slugs, so name search is the
natural way in:
knbs_indicators(source = "opendataforafrica", search = "county product")
gcp <- knbs_get_indicator("jhbzkjc")
gcp[gcp$table_code == "T3" & gcp$period == 2020, ] # current-price GCP, 2020Three behaviours to know:
-
Mixed frequencies. One dataset may hold annual,
quarterly and monthly series side by side. Annual periods are integer
years and sub-annual ones are
Dates, which cannot share a column, so a mixed dataset refuses to pick silently — sayfrequency = "annual"(or"quarterly","monthly") to choose. -
Republished provenance.
knbs_metadata()names the portal, the original producer, the dataset’s own licence label (most KNBS-sourced datasets state “Public Domain”) and the portal’s publication and update dates — the data vintage is visible, and can lag the producer’s newest PDFs. -
Geography codes are the portal’s region codes
(
KE,KE-110-NA, …),geo_levelisNA, and they must never be joined againstknbs_geographies().
Consumer prices
Three indicators, all monthly, all from one underlying series:
| id | measure | unit |
|---|---|---|
CPI_ALL_ITEMS |
the index level | index points |
CPI_INFLATION_YOY |
year-on-year change | percent |
CPI_INFLATION_MOM |
month-on-month change | percent |
period is a Date at the first of the month,
with a period_label like "2026-06".
start and end take a year, a
"YYYY-MM" month, or a date.
knbs_get_indicator("CPI_INFLATION_YOY", start = 2015, end = 2020)CPI geography is not census geography
The default is the national series. The other level is
"region" — the CPI collection strata: the Nairobi income
bands, Nairobi Combined, and Rest of Urban.
knbs_get_indicator("CPI_ALL_ITEMS", geo_level = "region")
knbs_get_indicator("CPI_ALL_ITEMS", geo_code = c("national", "1"))These region codes are the source’s own and mean nothing outside it.
Do not join them against
knbs_geographies() — different coding system,
different concept, and the package keeps them apart deliberately.
Two more honesty notes, both from the source audit:
- The region list also names the former provinces (Central, Coast, Eastern, North Eastern, Nyanza, Rift Valley, Western), but they carry no data in any period. Asking for one returns zero rows, because that is the source’s answer.
- The portal states no licence and does not state the
index base period.
knbs_metadata()on any result says both, so the caveat travels with the data rather than living only in this vignette.
Retail prices
The same portal publishes the monthly national average retail prices of the 22 commodities tracked for the CPI basket, in Kenya Shillings per stated unit:
knbs_retail_prices(commodity = "petrol", start = 2024)
knbs_retail_prices(start = "2026-06", end = "2026-06") # one month, all 22One row per commodity per month: commodity_code
(CPI001–CPI022), commodity,
unit, quantity, period,
price. National only — the source holds no regional retail
prices — and the licence caveat above applies here too.
Census tables
What the census source is, and is not
Three properties worth knowing before you use it:
-
It is census data. Every table describes 2019.
There are no time series here:
periodis 2019 throughout, andfrequencyisNAbecause a census table is not a recurring release. Consumer prices, GDP and other running indicators are not in this source (see the source-status vignette for where that work stands). - It is served over plain HTTP. The API host has no TLS listener, so traffic to it is unencrypted in transit. It carries only public census aggregates and no credentials; the registry records the exception explicitly.
- It is first-party. The service is operated by KNBS (built with Istat, Italy’s national statistical institute) and linked from KNBS’s own portals page.
Finding a table
knbs_indicators()
knbs_indicators(topic = "Population")
knbs_indicators(search = "marital")Thirty-two tables across four populated themes at the time of writing.
Getting observations
pop <- knbs_get_indicator("POPCEN0001")
popYou can pass a name instead of an id; if several tables match you get an error listing the candidates rather than a silently chosen one.
The result has one row per cell:
-
Core columns:
indicator_id,indicator_name,period,value. -
Geography, where the table has it:
geo_code,geo_name,geo_level. Codes keep their leading zeros —"00"is Kenya, two digits are counties, four digits are sub-counties — andgeo_levelsays which ("national","county","subcounty"). - Sex, where the table has it, using the source’s own categories (the 2019 census enumerated intersex as well as female and male).
-
Every other dimension becomes a pair of columns
under a short, readable name:
marital_statusholding the source’s labels (“Never Married”, “Total”) andmarital_status_codeits codes ("1","T"). The census API names its dimensions by questionnaire variable (p18_vs1) and labels them in long prose, so the names are curated, one per dimension the 2019 tables carry:age,religion,schooling_status,internet_use,household_head_sexand so on. A dimension added later that has no curated name keeps its source id. The source’s own dimension labels are inknbs_metadata(pop)$notes. -
Age mixes single years (
"0"to"99") with five-year groups ("0-4"…"95-99","100-120") and a total, so summing every row counts people twice.age_typesays which is which —"total","single_year","age_group"or"other"— and the groups are checked, on every read, to add up to the total and the single years to their group.filter(age_type == "age_group")gives a population pyramid that adds up.
Filtering geography
counties <- knbs_get_indicator("POPCEN0001", geo_level = "county")
nairobi <- knbs_get_indicator("POPCEN0001", geo_code = "47")Filters are applied after retrieval — the API serves whole tables — so the first call and the second cost the same request. Tables are cached for seven days by default.
A worked example
County population aged 12+, both sexes, all marital statuses:
pop <- knbs_get_indicator("POPCEN0001", geo_level = "county")
totals <- pop[pop$sex == "total" & pop$marital_status == "Total",
c("geo_code", "geo_name", "value")]
totals[order(-totals$value), ]Because geo_code is character with leading zeros intact,
these codes join cleanly against any other dataset keyed the same
way.
Provenance and citation
knbs_metadata(pop)
knbs_cite(pop)The metadata carry the licence statement, the release date
(2019-12-31), the source statement (“KNBS Population Census
2019”) and the dimension list; the citation is built from them.
Honesty notes
-
unitisNAin the catalogue: the source does not state units, and counts-versus-proportions is knowable only from each table’s name. The package does not guess. - An unknown table id is reported as not found. (The API
itself answers those requests with HTTP 200 and a body reading
errore; the package translates.) - The service checks the shape of everything it parses. If
the API changes, you get
knbs_error_schema_changed, not a plausible-looking wrong table.