Contact matriz normalization
Last updated on 2026-07-21 | Edit this page
Estimated time: 50 minutes
Overview
Questions
- How to normalize contact matrices used in epidemiological analysis?
Objectives
- Understand the normalization of contact matrices
Introduction
Let’s download and load the POLYMOD survey data directly from Zenodo
using contactsurveys and socialmixr. Then,
obtain the contact matrix for the United Kingdom — passing
countries = "United Kingdom" to select data from the
intended country, age_limits to define age categories, and
survey_pop to supply the population structure from
{wpp2024} required by socialmixr.
R
library(contactsurveys)
library(socialmixr)
library(wpp2024)
library(tidyverse)
R
survey_files <- contactsurveys::download_survey(
survey = "https://doi.org/10.5281/zenodo.3874557",
verbose = FALSE
)
survey_load <- socialmixr::load_survey(files = survey_files)
R
data(popAge1dt, package = "wpp2024")
uk_pop <- popAge1dt %>%
dplyr::filter(name == "United Kingdom", year == 2020) %>%
dplyr::select(lower.age.limit = age, population = pop) %>%
dplyr::mutate(population = population * 1000)
contacts_byage <- socialmixr::contact_matrix(
survey = survey_load,
countries = "United Kingdom",
age_limits = c(0, 20, 40),
symmetric = TRUE,
survey_pop = uk_pop
)
contacts_byage
OUTPUT
$matrix
contact.age.group
age.group [0,20) [20,40) [40,Inf)
[0,20) 7.883663 3.114224 3.230298
[20,40) 2.799168 4.854839 4.873347
[40,Inf) 1.507146 2.529653 5.005571
$demography
age.group population proportion year
<char> <num> <num> <int>
1: [0,20) 15842062 0.2349693 NA
2: [20,40) 17625140 0.2614159 NA
3: [40,Inf) 33954633 0.5036148 NA
$participants
age.group participants proportion
<char> <int> <num>
1: [0,20) 404 0.3996044
2: [20,40) 248 0.2453017
3: [40,Inf) 359 0.3550940
Also, let’s recover the differential equations that describe the flow of individuals between the disease states \(S\), \(I\), and \(R\) and the key parameters for each process. For each disease state and age group (\(i\)), we have a differential equation describing the rate of change with respect to time.
\[ \begin{aligned} \frac{dS_i}{dt} & = - \beta S_i \sum_j C_{i,j} I_j/N_j \\ \frac{dI_i}{dt} &= \beta S_i\sum_j C_{i,j} I_j/N_j - \gamma I_i \\ \frac{dR_i}{dt} &=\gamma I_i \\ \end{aligned} \]
Normalising the contact matrix to ensure the correct value of \(R_0\)
When simulating an epidemic, we often want to ensure that the average number of secondary cases generated by a typical infectious individual (i.e. \(R_0\)) is consistent with known values for the pathogen we’re analysing. In the above model, we scale the contact matrix by the \(\beta\) to convert the raw interaction data into a transmission rate. But how do we define the value of \(\beta\) to ensure a certain value of \(R_0\)?
Rather than just using the raw number of contacts, we can instead normalise the contact matrix to make it easier to work in terms of \(R_0\). In particular, we normalise the matrix by scaling it so that if we were to calculate the average number of secondary cases based on this normalised matrix, the result would be 1 (in mathematical terms, we are scaling the matrix so the largest eigenvalue is 1). This transformation scales the entries but preserves their relative values.
In the case of the above model, we want to define \(\beta C_{i,j}\) so that the model has a
specified valued of \(R_0\). If the
entry of the contact matrix \(C[i,j]\)
represents the contacts of population \(i\) with \(j\), it is equivalent to
contacts_byage$matrix[i,j], and the maximum eigenvalue of
this matrix represents the typical magnitude of contacts, not the
typical magnitude of transmission. We must therefore normalise the
matrix \(C\) so the maximum eigenvalue
is one; we call this matrix \(C_{normalised}\). Because the rate of
recovery is \(\gamma\), individuals
will be infectious on average for \(1/\gamma\) days. So \(\beta\) as a model input is calculated from
\(R_0\), the scaling factor and the
value of \(\gamma\)
(i.e. mathematically we use the fact that the dominant eigenvalue of the
matrix \(R_0 \times C_{normalised}\) is
equal to \(\beta / \gamma\)).
R
contacts_byage_matrix <- contacts_byage$matrix
scaling_factor <- 1 / max(eigen(contacts_byage_matrix)$values)
normalised_matrix <- contacts_byage_matrix * scaling_factor
As a result, if we multiply the scaled matrix by \(R_0\), then converting to the number of expected secondary cases would give us \(R_0\), as required.
R
infectious_period <- 7.0
basic_reproduction <- 1.46
transmission_rate <- basic_reproduction * scaling_factor / infectious_period
# check the dominant eigenvalue of R0 x C_normalised is R0
max(eigen(basic_reproduction * normalised_matrix)$values)
OUTPUT
[1] 1.46
Check the dimension of \(\beta\)
In the SIR model without age structure the rate of contact is part of the transmission rate \(\beta\), where as in the age-structured model we have separated out the rate of contact, hence the transmission rate \(\beta\) in the age structured model will have a different value.
We can use contact matrices from socialmixr with
mathematical models in the R package {epidemics}. See the
tutorial Simulating
transmission for examples and an introduction to
epidemics.
Contact groups
In the example above the dimension of the contact matrix will be the same as the number of age groups, i.e. if there are 3 age groups then the contact matrix will have 3 rows and 3 columns. Contact matrices can be used for other groups as long as the dimension of the matrix matches the number of groups.
For example, we might have a meta population model with two geographic areas. Then our contact matrix would be a 2 x 2 matrix with entries representing the contact between and within the geographic areas.
- Proper normalization is crucial when using contact matrices in transmission models