Calculating contribution to annual rate
Source:vignettes/articles/contribution.Rmd
contribution.Rmd
Introduction
mm23 contains functions to reproduce the calculation of an items contribution to its parents annual inflation rate. These are quite niche, and rely on the data being in a specific structure. Theres no real error checking in here (yet) so take care!
This article will explain the steps needed to reproduce the calculation. It is informed by section 11.7 of the ONS CPI Technical Manual.
Weights
CPIH weights are a problem. They are adjusted twice a year (since
2017), in December and January. But the mm23 dataset only has a single
weight for each year. The only place the full weights are exposed are in
the ‘detailed reference tables’ spreadsheet. The functions
get_cpi*_weights()
which generates a weights dataset.
It may not be very robust, it needs some testing, but if everything
is in the right place it works OK.
It returns a dataset with a weight for every month.
Unchaining and calculating contribution
The unchain()
function can be used within a
mutate()
call to produce an unchained index. Its based on
the assumption that CPI weights change twice a year, and so an unchained
index is based on previous January = 100, or for the January index,
based on previous December = 100. Example code below.
Calculating contribution to ‘all items’ 12 month rate
The calculation is as follows, where:
Note that ‘all items’ doesn’t have to be all items CPIH, it can be a category total (such as food). It is important that the calculations are performed using unchained indices (i.e. based on previous January = 100, or for the January index, based on previous December =100). For the month of interest, the contribution of each component of the CPI to the 12-month rate is calculated. The same is done for the preceding month. The differences between the two are the contributions to the change in the CPI 12-month rate.
The contribution()
function applies this calculation to
a wide dataset (1 column with month number or date, and the indices and
weights in subsequent columns). It can then be used in a
mutate()
call. It takes
,
,
,
and
as parameters, and leverages dplyr::lag()
to do the rest.
Example below.
Contribution - data setup
Lets get the data we need for the calculations and reduce the need for lots of downloads.
library(mm23)
library(dplyr)
library(tidyr)
library(lubridate)
library(ggplot2)
# Get our data together
mm23 <- acquire_mm23()
data <- get_mm23_month(mm23)
cpih_wts <- get_weights(mm23, measure = "cpih")
cpi_wts <- get_weights(mm23, measure = "cpi")
CPIH food weight contribution
Code to calculate the contribution to changes in food and non-alcoholic drinks inflation.
# Select and unchain food indices
food_cdids <- c("L523", "L52I", "L52J", "L52K",
"L52L", "L52M", "L52N", "L52O",
"L52P", "L52Q", "L52S", "L52T")
food_unchained <- data |>
filter(cdid %in% food_cdids & date >= "2017-01-01") |>
group_by(cdid) |>
mutate(unchained_value = unchain(month(date), value)) |>
select(date, cdid, value = unchained_value)
# Extract the relevant food weights
food_weights <- c("L5CZ", "L5DH", "L5DI", "L5DJ",
"L5DK", "L5DL", "L5DM", "L5DN",
"L5DO", "L5DP", "L5DR", "L5DS")
foodwts <- cpih_wts |>
filter(cdid %in% food_weights & date >= "2017-01-01")
# Combine the indices and weights and calculate the contribution to annual rate
unchained <- food_unchained |>
bind_rows(foodwts) |>
pivot_wider(names_from = cdid)
contribution <- unchained |>
mutate(bread = contribution(month = month(date),
all_items_index = L523,
all_items_weight = L5CZ,
component_index = L52I,
component_weight = L5DH),
meat = contribution(month = month(date),
all_items_index = L523,
all_items_weight = L5CZ,
component_index = L52J,
component_weight = L5DI),
fish = contribution(month = month(date),
all_items_index = L523,
all_items_weight = L5CZ,
component_index = L52K,
component_weight = L5DJ),
dairy = contribution(month = month(date),
all_items_index = L523,
all_items_weight = L5CZ,
component_index = L52L,
component_weight = L5DK),
oils = contribution(month = month(date),
all_items_index = L523,
all_items_weight = L5CZ,
component_index = L52M,
component_weight = L5DL),
fruit = contribution(month = month(date),
all_items_index = L523,
all_items_weight = L5CZ,
component_index = L52N,
component_weight = L5DM),
vegetables = contribution(month = month(date),
all_items_index = L523,
all_items_weight = L5CZ,
component_index = L52O,
component_weight = L5DN),
sugar = contribution(month = month(date),
all_items_index = L523,
all_items_weight = L5CZ,
component_index = L52P,
component_weight = L5DO),
food_nec = contribution(month = month(date),
all_items_index = L523,
all_items_weight = L5CZ,
component_index = L52Q,
component_weight = L5DP),
coffee = contribution(month = month(date),
all_items_index = L523,
all_items_weight = L5CZ,
component_index = L52S,
component_weight = L5DR),
water = contribution(month = month(date),
all_items_index = L523,
all_items_weight = L5CZ,
component_index = L52T,
component_weight = L5DS)
)
tail(contribution) |> knitr::kable()
date | L523 | L52I | L52J | L52K | L52L | L52M | L52N | L52O | L52P | L52Q | L52S | L52T | L5CZ | L5DH | L5DI | L5DJ | L5DK | L5DL | L5DM | L5DN | L5DO | L5DP | L5DR | L5DS | bread | meat | fish | dairy | oils | fruit | vegetables | sugar | food_nec | coffee | water |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2024-11-01 | 101.9274 | 101.1046 | 100.55380 | 99.47800 | 101.70092 | 109.80958 | 102.1887 | 102.2642 | 103.80228 | 99.46773 | 104.31711 | 102.5253 | 91.3270 | 16.2085 | 16.1931 | 3.2381 | 10.4821 | 2.6154 | 7.7334 | 11.9345 | 9.4822 | 3.9755 | 2.0904 | 7.3737 | 0.2397799 | 0.0141069 | -0.1297452 | 0.2092386 | 0.2790751 | 0.2049426 | 0.3595699 | 0.5731582 | -0.0579390 | 0.0580878 | 0.2600242 |
2024-12-01 | 102.4463 | 101.8409 | 100.63291 | 98.73229 | 101.84266 | 111.36757 | 103.3962 | 102.7925 | 105.09506 | 99.46773 | 104.86656 | 102.5253 | 91.3270 | 16.2085 | 16.1931 | 3.2381 | 10.4821 | 2.6154 | 7.7334 | 11.9345 | 9.4822 | 3.9755 | 2.0904 | 7.3737 | 0.0931139 | 0.0002089 | -0.1172670 | 0.1700282 | 0.2785660 | 0.3119533 | 0.3436031 | 0.7360874 | -0.0660815 | 0.0703085 | 0.1855466 |
2025-01-01 | 100.8683 | 100.4338 | 101.65094 | 100.30211 | 100.34795 | 98.96373 | 100.1460 | 100.0000 | 102.82200 | 99.39799 | 104.64072 | 101.4075 | 90.1944 | 15.7250 | 15.2398 | 2.9878 | 10.6920 | 2.7215 | 7.8108 | 12.2034 | 9.5957 | 3.6447 | 2.1407 | 7.4331 | 0.4041790 | 0.4011568 | -0.0317736 | 0.2523243 | 0.2913247 | 0.3030455 | 0.3653365 | 0.8493949 | -0.0460782 | 0.2221861 | 0.3228234 |
2025-02-01 | 100.2152 | 100.7919 | 99.92266 | 100.00000 | 99.37587 | 100.15707 | 100.3644 | 100.0734 | 100.91485 | 100.74024 | 98.99857 | 100.3470 | 90.1944 | 15.7250 | 15.2398 | 2.9878 | 10.6920 | 2.7215 | 7.8108 | 12.2034 | 9.5957 | 3.6447 | 2.1407 | 7.4331 | 0.4935172 | 0.3276556 | -0.0267039 | 0.1687838 | 0.2482791 | 0.3579173 | 0.2562592 | 0.9823436 | -0.0142532 | 0.1472391 | 0.3689217 |
2025-03-01 | 100.1435 | 101.1519 | 99.61330 | 97.66566 | 100.13870 | 99.52880 | 100.2187 | 100.6608 | 99.78888 | 101.00942 | 97.99714 | 100.3470 | 90.1944 | 15.7250 | 15.2398 | 2.9878 | 10.6920 | 2.7215 | 7.8108 | 12.2034 | 9.5957 | 3.6447 | 2.1407 | 7.4331 | 0.5179568 | 0.3569476 | -0.0746107 | 0.3343265 | 0.2167102 | 0.3123252 | 0.2589002 | 0.6761359 | 0.0315697 | 0.1224608 | 0.2869109 |
2025-04-01 | 100.8608 | 101.7999 | 101.54679 | 98.41867 | 99.51456 | 101.15183 | 99.4898 | 100.0000 | 101.68895 | 101.34589 | 100.14306 | 102.0819 | 90.1944 | 15.7250 | 15.2398 | 2.9878 | 10.6920 | 2.7215 | 7.8108 | 12.2034 | 9.5957 | 3.6447 | 2.1407 | 7.4331 | 0.5804171 | 0.6074584 | -0.0750497 | 0.2655243 | 0.2056142 | 0.2720361 | 0.1370597 | 0.7952864 | 0.0425487 | 0.1404590 | 0.4616383 |
# put the data back into tidy form and plot some results
cont <- contribution |>
select(date, bread:water) |>
pivot_longer(cols = bread:water) |>
filter(!is.na(value))
cont |>
filter(date >= "2021-07-01") |>
ggplot2::ggplot() +
ggplot2::geom_col(aes(x = date, y = value, fill = name)) +
ggplot2::labs(title = "Contribution to annual food and non-alc CPIH inflation rate")
CPI food weight contribution
# Select and unchain food indices
food_cdids <- c("D7BU", "D7D5", "D7D6", "D7D7",
"D7D8", "D7D9", "D7DA", "D7DB",
"D7DC", "D7DD","D7DE", "D7DF")
food_unchained <- data |>
filter(cdid %in% food_cdids & date >= "2017-01-01") |>
group_by(cdid) |>
mutate(
unchained_value = unchain(month(date), value)
) |>
select(date, cdid, value = unchained_value)
# Extract the relevant food weights
food_weights <- c("CHZR", "CJWB", "CJWC", "CJWD",
"CJWE", "CJWF", "CJWG", "CJWH",
"CJWI", "CJWJ", "CJWK", "CJWL")
foodwts <- cpi_wts |>
filter(cdid %in% food_weights & date >= "2017-01-01")
# Combine the indices and weights and calculate the contribution to annual rate
unchained <- food_unchained |>
bind_rows(foodwts) |>
pivot_wider(names_from = cdid)
contribution <- unchained |>
mutate(`Bread and cereals` = contribution(month = month(date),
all_items_index = D7BU,
all_items_weight = CHZR,
component_index = D7D5,
component_weight = CJWB),
`Meat` = contribution(month = month(date),
all_items_index = D7BU,
all_items_weight = CHZR,
component_index = D7D6,
component_weight = CJWC),
`Fish` = contribution(month = month(date),
all_items_index = D7BU,
all_items_weight = CHZR,
component_index = D7D7,
component_weight = CJWD),
`Milk, cheese and eggs` = contribution(month = month(date),
all_items_index = D7BU,
all_items_weight = CHZR,
component_index = D7D8,
component_weight = CJWE),
`Oils and fats` = contribution(month = month(date),
all_items_index = D7BU,
all_items_weight = CHZR,
component_index = D7D9,
component_weight = CJWF),
`Fruit` = contribution(month = month(date),
all_items_index = D7BU,
all_items_weight = CHZR,
component_index = D7DA,
component_weight = CJWG),
`Vegetables` = contribution(month = month(date),
all_items_index = D7BU,
all_items_weight = CHZR,
component_index = D7DB,
component_weight = CJWH),
`Sugar, jam and confectionery` = contribution(month = month(date),
all_items_index = D7BU,
all_items_weight = CHZR,
component_index = D7DC,
component_weight = CJWI),
`Food products (nec)` = contribution(month = month(date),
all_items_index = D7BU,
all_items_weight = CHZR,
component_index = D7DD,
component_weight = CJWJ),
`Coffee and tea` = contribution(month = month(date),
all_items_index = D7BU,
all_items_weight = CHZR,
component_index = D7DE,
component_weight = CJWK),
`Mineral waters, soft drinks` = contribution(month = month(date),
all_items_index = D7BU,
all_items_weight = CHZR,
component_index = D7DF,
component_weight = CJWL)
)
tail(contribution) |> knitr::kable()
date | D7BU | D7D5 | D7D6 | D7D7 | D7D8 | D7D9 | D7DA | D7DB | D7DC | D7DD | D7DE | D7DF | CHZR | CJWB | CJWC | CJWD | CJWE | CJWF | CJWG | CJWH | CJWI | CJWJ | CJWK | CJWL | Bread and cereals | Meat | Fish | Milk, cheese and eggs | Oils and fats | Fruit | Vegetables | Sugar, jam and confectionery | Food products (nec) | Coffee and tea | Mineral waters, soft drinks |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
2024-11-01 | 101.8574 | 101.1046 | 100.55380 | 99.47800 | 101.70092 | 109.80958 | 102.1887 | 102.2642 | 103.80228 | 99.46773 | 104.31711 | 102.5253 | 113.1464 | 20.081 | 20.0618 | 4.0117 | 12.9864 | 3.2403 | 9.5811 | 14.7858 | 11.7476 | 4.9253 | 2.5899 | 9.1354 | 0.2391270 | 0.0127719 | -0.1301043 | 0.2108764 | 0.2826209 | 0.2050262 | 0.3540759 | 0.5718534 | -0.0578805 | 0.0581625 | 0.2629572 |
2024-12-01 | 102.3774 | 101.8409 | 100.63291 | 98.73229 | 101.84266 | 111.36757 | 103.3962 | 102.7925 | 105.09506 | 99.46773 | 104.86656 | 102.5253 | 113.1464 | 20.081 | 20.0618 | 4.0117 | 12.9864 | 3.2403 | 9.5811 | 14.7858 | 11.7476 | 4.9253 | 2.5899 | 9.1354 | 0.0933516 | 0.0002905 | -0.1172988 | 0.1701815 | 0.2788062 | 0.3121648 | 0.3438676 | 0.7364695 | -0.0660982 | 0.0703914 | 0.1856950 |
2025-01-01 | 100.8708 | 100.4338 | 101.65094 | 100.30211 | 100.34795 | 98.96373 | 100.1460 | 100.0000 | 102.82200 | 99.39799 | 104.64072 | 101.4075 | 112.5699 | 19.626 | 19.0204 | 3.7290 | 13.3445 | 3.3966 | 9.7484 | 15.2309 | 11.9762 | 4.5488 | 2.6717 | 9.2772 | 0.4040479 | 0.4009521 | -0.0317029 | 0.2521709 | 0.2911494 | 0.3028086 | 0.3650831 | 0.8486430 | -0.0460172 | 0.2220621 | 0.3226082 |
2025-02-01 | 100.2158 | 100.7919 | 99.92266 | 100.00000 | 99.37587 | 100.15707 | 100.3644 | 100.0734 | 100.91485 | 100.74024 | 98.99857 | 100.3470 | 112.5699 | 19.626 | 19.0204 | 3.7290 | 13.3445 | 3.3966 | 9.7484 | 15.2309 | 11.9762 | 4.5488 | 2.6717 | 9.2772 | 0.4933705 | 0.3274694 | -0.0267104 | 0.1688034 | 0.2483001 | 0.3578877 | 0.2562508 | 0.9820653 | -0.0142564 | 0.1471780 | 0.3688228 |
2025-03-01 | 100.1439 | 101.1519 | 99.61330 | 97.66566 | 100.13870 | 99.52880 | 100.2187 | 100.6608 | 99.78888 | 101.00942 | 97.99714 | 100.3470 | 112.5699 | 19.626 | 19.0204 | 3.7290 | 13.3445 | 3.3966 | 9.7484 | 15.2309 | 11.9762 | 4.5488 | 2.6717 | 9.2772 | 0.5181489 | 0.3570588 | -0.0746202 | 0.3345308 | 0.2169035 | 0.3125338 | 0.2590298 | 0.6764372 | 0.0315820 | 0.1225064 | 0.2870235 |
2025-04-01 | 100.8633 | 101.7999 | 101.54679 | 98.41867 | 99.51456 | 101.15183 | 99.4898 | 100.0000 | 101.68895 | 101.34589 | 100.14306 | 102.0819 | 112.5699 | 19.626 | 19.0204 | 3.7290 | 13.3445 | 3.3966 | 9.7484 | 15.2309 | 11.9762 | 4.5488 | 2.6717 | 9.2772 | 0.5801466 | 0.6070812 | -0.0750201 | 0.2655292 | 0.2056117 | 0.2720551 | 0.1370571 | 0.7949475 | 0.0425280 | 0.1403770 | 0.4614407 |
# put the data back into tidy form and plot some results
cont <- contribution |>
select(date, `Bread and cereals`:`Mineral waters, soft drinks`) |>
pivot_longer(cols = `Bread and cereals`:`Mineral waters, soft drinks`) |>
filter(!is.na(value))
cont |>
filter(date >= "2020-01-01") |>
ggplot2::ggplot() +
ggplot2::geom_col(aes(x = date, y = value, fill = name)) +
ggplot2::labs(title = "Contribution to annual food and non-alc inflation rate")