Skip to contents

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:

c=componentcc = component\ c

a=allitemsCPIindexa =\ 'all\ items'\ CPI\ index

Wyc=weightofcomponentcinyearyW^c_{y} = weight\ of\ component\ c\ in\ year\ yItc=indexforcomponentcinmonthtbasedonJanuaryofcurrentyear=100I^c_t = index\ for\ component\ c\ in\ month\ t\ based\ on\ January\ of\ current\ year =100IJana=allitemsindexforJanuarybasedonpreviousmonth(December)=100I^a_{Jan} = all\ items\ index\ for\ January\ based\ on\ previous\ month\ (December) = 100IDeca=allitemsindexforDecemberbasedonpreviousJanuary=100I^a_{Dec} = all\ items\ index\ for\ December\ based\ on\ previous\ January = 100

(Wy1cWy1a)×((IDeccIt12c)It12a)×100+(WycWya)×((IJanc100)It12a)×IDeca+(WycWya)×((Itc100)It12a)×IJana100×IDeca (\frac{W^c_{y-1}} {W^a_{y-1}}) \times (\frac{({I^c_{Dec}} - {I^c_{t-12}})}{I^a_{t-12}}) \times 100 \ \ +\ \ (\frac{W^c_y}{W^a_y}) \times (\frac{({I^c_{Jan}}-100)}{I^a_{t-12}}) \times I^a_{Dec} \ \ +\ \ (\frac{W^c_y}{W^a_y}) \times (\frac{(I^c_t - 100)}{I^a_{t-12}}) \times \frac{I^a_{Jan}}{100} \times I^a_{Dec}

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 IaI^a, WaW^a, IcI^c, and WcW^c 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")