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
Callout

Normalisation using socialmixr

Normalisation can be performed by the function contact_matrix() in socialmixr. To obtain the normalised matrix we must specify that we want to split out the different components of the contact matrix using the argument split = TRUE. Then we can obtain the normalised matrix as follows:

R

contact_data_split <- socialmixr::contact_matrix(
  survey = survey_load,
  countries = "United Kingdom",
  age_limits = c(0, 20, 40),
  symmetric = TRUE,
  survey_pop = uk_pop,
  split = TRUE
)

# extract components of the contact matrix
contacts_d <- contact_data_split$contacts
matrix_a <- contact_data_split$matrix
demography_n <- contact_data_split$demography$proportion

# calculate normalised matrix
normalised_matrix_split <- contacts_d * matrix_a * demography_n

For details of the different components of the contact matrix see the package vignette on splitting contact matrices.

Callout

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.

Key Points
  • Proper normalization is crucial when using contact matrices in transmission models