Skip to content

Commit

Permalink
differences for PR #63
Browse files Browse the repository at this point in the history
  • Loading branch information
actions-user committed Nov 7, 2024
1 parent ede9edb commit cc89750
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 13 deletions.
4 changes: 3 additions & 1 deletion config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,13 @@ contact: '[email protected]'

# Order of episodes in your lesson
episodes:
- contact-matrices.Rmd
- simulating-transmission.Rmd
- model-choices.Rmd
- modelling-interventions.Rmd
- compare-interventions.Rmd


# Information for Learners
learners:

Expand All @@ -78,6 +80,6 @@ profiles:
# This space below is where custom yaml items (e.g. pinning
# sandpaper and varnish versions) should live


varnish: epiverse-trace/varnish@epiversetheme
# this is carpentries/sandpaper#533 in our fork so we can keep it up to date with main
sandpaper: epiverse-trace/sandpaper@patch-renv-github-bug
313 changes: 313 additions & 0 deletions contact-matrices.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
---
title: 'Contact matrices'
teaching: 40
exercises: 10
---

:::::::::::::::::::::::::::::::::::::: questions

- What is a contact matrix?
- How are contact matrices estimated?
- How are contract matrices used?

::::::::::::::::::::::::::::::::::::::::::::::::

::::::::::::::::::::::::::::::::::::: objectives

- Use the R package `socialmixr` to estimate a contact matrix
- Understand the different types of analysis contact matrices can be used for
::::::::::::::::::::::::::::::::::::::::::::::::

<!-- ::::::::::::::::::::::::::::::::::::: prereq -->

<!-- ## Prerequisites -->



<!-- ::::::::::::::::::::::::::::::::: -->


## Introduction

Some groups of individuals have more contacts than others; the average schoolchild has many more daily contact than the average elderly person, for example. This heterogeneity of contact patterns between different groups can affect disease transmission, because certain groups are more likely to transmit to others within that group, as well as to other groups. The rate at which individuals within and between groups make contact with others can be summarised in a contact matrix. In this tutorial we are going to learn how contact matrices can be used in different analyses and how the `{socialmixr}` package can be used to estimate contact matrices.



``` r
library(socialmixr)
```


:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: instructor



::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

## The contact matrix

The basic contact matrix represents the amount of contact or mixing within and between different subgroups of a population. The subgroups are often age categories but can also be geographic areas or high/low risk groups. For example, a hypothetical contact matrix representing the average number of contacts per day between children and adults could be:

$$
\begin{bmatrix}
2 & 2\\
1 & 3
\end{bmatrix}
$$
In this example, we would use this to represent that children meet, on average, 2 other children and 2 adult per day (first row), and adults meet, on average, 1 child and 3 other adults per day (second row). We can use this kind of information to account for the role heterogeneity in contact plays in infectious disease transmission.


## Using `socialmixr`

Contact matrices can be estimated from diary data. For example, the POLYMOD survey measured contact patterns in 8 European countries using data on the location and duration of contacts reported by the study participants [(Mossong et al. 2008)](https://doi.org/10.1371/journal.pmed.0050074).

The R package `{socialmixr}` contains functions which can estimate contact matrices from POLYMOD and other surveys. We can load the POLYMOD survey data :



``` r
polymod <- socialmixr::polymod
```

Then we can obtain the contact matrix for the age categories we want by specifying `age.limits`.


``` r
contact_data <- contact_matrix(
survey = polymod,
countries = "United Kingdom",
age.limits = c(0, 20, 40),
symmetric = TRUE
)
```

``` output
Removing participants that have contacts without age information. To change this behaviour, set the 'missing.contact.age' option
```

``` r
contact_data
```

``` output
$matrix
contact.age.group
[0,20) [20,40) 40+
[1,] 7.883663 3.120220 3.063895
[2,] 2.794154 4.854839 4.599893
[3,] 1.565665 2.624868 5.005571
$demography
age.group population proportion year
<char> <num> <num> <int>
1: [0,20) 14799290 0.2454816 2005
2: [20,40) 16526302 0.2741283 2005
3: 40+ 28961159 0.4803901 2005
$participants
age.group participants proportion
<char> <int> <num>
1: [0,20) 404 0.3996044
2: [20,40) 248 0.2453017
3: 40+ 359 0.3550940
```

**Note: although the contact matrix `contact_data$matrix` is not itself mathematically symmetric, it satisfies the condition that the total number of contacts of one group with another is the same as the reverse. In other words:
`contact_data$matrix[i,j]*contact_data$demography$proportion[i] = contact_data$matrix[j,i]*contact_data$demography$proportion[j]`.
For the mathematical explanation see [the corresponding section in the socialmixr documentation](https://epiforecasts.io/socialmixr/articles/socialmixr.html#symmetric-contact-matrices).**


::::::::::::::::::::::::::::::::::::: callout
### Why would a contact matrix be non-symmetric?

One of the arguments we gave the function `contact_matrix()` is `symmetric=TRUE`. This ensures that the total number of contacts of age group 1 with age group 2 is the same as the total number of contacts of age group 2 and age group 1 (see the `socialmixr` [vignette](https://cran.r-project.org/web/packages/socialmixr/vignettes/socialmixr.html) for more detail). However, when contact matrices are estimated from surveys or other sources, the *reported* number of contacts may differ by age group resulting in a non-symmetric contact matrix because of biases in recall or reporting across different groups and/or uncertainty from using a limited sample of participants [(Prem et al 2021)](https://doi.org/10.1371/journal.pcbi.1009098). If `symmetric` is set to TRUE, the `contact_matrix()` function will internally use an average of reported contacts to ensure resulting total number of contacts are symmetric.

::::::::::::::::::::::::::::::::::::::::::::::::

The example above uses the POLYMOD survey. There are a number of surveys available in `socialmixr`, to list the available surveys use `list_surveys()`. To download a survey, we can use `get_survey()`


``` r
zambia_sa_survey <- get_survey("https://doi.org/10.5281/zenodo.3874675")
```



::::::::::::::::::::::::::::::::::::: challenge

## Zambia contact matrix

After downloading the survey, generate a symmetric contact matrix for Zambia using the following age bins:

+ [0,15)
+ 20+

:::::::::::::::::::::::: solution


``` r
contact_data_zambia <- contact_matrix(
survey = zambia_sa_survey,
age.limits = c(0, 20),
symmetric = TRUE
)
```

``` output
Removing participants without age information. To change this behaviour, set the 'missing.participant.age' option
```

``` output
Removing participants that have contacts without age information. To change this behaviour, set the 'missing.contact.age' option
```

``` r
contact_data_zambia
```

``` output
$matrix
contact.age.group
[0,20) 20+
[1,] 3.643137 2.282138
[2,] 1.795546 2.542346
$demography
age.group population proportion year
<char> <num> <num> <int>
1: [0,20) 28813173 0.4403347 2010
2: 20+ 36621532 0.5596653 2010
$participants
age.group participants proportion
<char> <int> <num>
1: [0,20) 255 0.07535461
2: 20+ 3129 0.92464539
```
:::::::::::::::::::::::::::::::::
::::::::::::::::::::::::::::::::::::::::::::::::


::::::::::::::::::::::::::::::::::::: callout
## Synthetic contact matrices

Contact matrices can be estimated from data obtained from diary (such as POLYMOD), survey or contact data, or synthetic ones can be used. [Prem et al. 2021](https://doi.org/10.1371/journal.pcbi.1009098) used the POLYMOD data within a Bayesian hierarchical model to project contact matrices for 177 other countries.

::::::::::::::::::::::::::::::::::::::::::::::::




## Analyses with contact matrices

Contact matrices can be used in a wide range of epidemiological analyses, they can be used:

+ to calculate the basic reproduction number while accounting for different rates of contacts between age groups [(Funk et al. 2019)](https://doi.org/10.1186/s12916-019-1413-7),
+ to calculate final size of an epidemic, see the R package `{finalsize}`,
+ to assess the impact of interventions finding the relative change between pre and post intervention contact matrices to calculate the relative difference in $R_0$ [(Jarvis et al. 2020)](https://doi.org/10.1186/s12916-020-01597-8),
+ and in mathematical models of tranmission within a population, to account for group specific contact patterns.


However, all of these applications require us to perform some additional calculations using the contact matrix. Specifically, there are two main calculations we often need to do:

1. **Convert contact matrix into expected number of secondary cases**

If contacts vary between groups, then the average number of secondary cases won't be equal simply to the average number of contacts multiplied by the probability of transmission-per-contact. This is because the average amount of transmission in each generation of infection isn't not just a matter of whom a group came into contact with; it's about whom *their contacts* subsequently come into contact with. The function `r_eff` in the package `{finalsize}` can perform this conversion, taking a contact matrix, demography and proportion susceptible and converting it into an estimate of the average number of secondary cases generated by a typical infectious individual (i.e. the effective reproduction number).

2. **Convert contact matrix into contact rates**

Whereas a contact matrix gives the average number of contacts that one groups makes with another, epidemic dynamics in different groups depend on the rate at which one group infects another. We therefore need to scale the rate of interaction between different groups (i.e. the number of contacts per unit time) to get the rate of transmission. However, we need to be careful that we are defining transmission to and from each group correctly in any model. Specifically, the entry $(i,j)$ in the `contact_data` matrix above represents contacts by group $i$ made with group $j$. But if we want to know the rate at which a group $i$ are getting infected, then we want to add up the number of individuals infected in each group $j$ multiplied by the number of contacts made, $C_{ji}$.

### In mathematical models

Consider the SIR model where individuals are categorized as either susceptible $S$, infected but not yet infectious $E$, infectious $I$ or recovered $R$. The schematic below shows the processes which describe the flow of individuals between the disease states $S$, $I$ and $R$ and the key parameters for each process.

<!--html_preserve--><div class="grViz html-widget html-fill-item" id="htmlwidget-18be6435a2c69178e043" style="width:504px;height:504px;"></div>
<script type="application/json" data-for="htmlwidget-18be6435a2c69178e043">{"x":{"diagram":"digraph {\n\n # graph statement\n #################\n graph [layout = dot,\n rankdir = LR,\n overlap = true,\n fontsize = 10]\n\n # nodes\n #######\n node [shape = square,\n fixedsize = true\n width = 1.3]\n\n S\n I\n R\n\n # edges\n #######\n S -> I [label = \" infection \n(transmission rate &beta;)\"]\n I -> R [label = \" recovery \n(recovery rate &gamma;)\"]\n\n}","config":{"engine":"dot","options":null}},"evals":[],"jsHooks":[]}</script><!--/html_preserve-->

The [differential equations](../learners/reference.md#ordinary) below describe how individuals move from one state to another [(Bjørnstad et al. 2020)](https://doi.org/10.1038/s41592-020-0822-z).


$$
\begin{aligned}
\frac{dS}{dt} & = - \beta S I \\
\frac{dI}{dt} &= \beta S I - \gamma I \\
\frac{dR}{dt} &=\gamma I \\
\end{aligned}
$$
To add age structure to our model, we need to add additional equations for the infection states $S$, $I$ and $R$ for each age group $i$. If we want to assume that there is heterogeneity in contacts between age groups then we must adapt the transmission term $\beta SI$ to include the contact matrix $C$ as follows :

$$ \beta S_i \sum_j C_{i,j} I_j. $$
Susceptible individuals in age group $i$ become infected dependent on their rate of contact with individuals in each age group. For each disease state ($S$, $E$, $I$ and $R$) 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_{j,i} I_j \\
\frac{dI_i}{dt} &= \beta S_i\sum_j C_{j,i} I_j - \gamma I_i \\
\frac{dR_i}{dt} &=\gamma I_i \\
\end{aligned}
$$

::::::::::::::::::::::::::::::::::::: callout
## 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$. Normalisation means converting to a value to be between 0 and 1. 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[j,i]` so that the model has a specified valued of $R_0$. We do this by first transposing the matrix (so row/column entries are now `C[j,i])`, then normalise it so the maximum eigenvalue is one. Because the rate of recovery is $\gamma$, individuals will be infectious on average for $1/\gamma$ days. So $\beta$ is calculated from the scaling factor and the value of $\gamma$ (i.e. mathematically we have the dominant eigenvalue of the matrix $R_0 \times C_{normalised}$ is $\beta / \gamma$).


``` r
contact_matrix <- t(contact_data$matrix)
scaling_factor <- 1 / max(eigen(contact_matrix)$values)
normalised_matrix <- contact_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
### 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](../episodes/simulating-transmission.md) 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.


## Summary

In this tutorial, we have learnt the definition of the contact matrix, how they are estimated and how to access social contact data from `socialmixr`. In the next tutorial, we will learn how to use the R package `{epidemics}` to generate disease trajectories from mathematical models with contact matrices from `socialmixr`.

::::::::::::::::::::::::::::::::::::: keypoints

- `socialmixr` can be used to estimate contact matrices from survey data
- Contact matrices can be used in different types of analyses

::::::::::::::::::::::::::::::::::::::::::::::::
5 changes: 3 additions & 2 deletions md5sum.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"file" "checksum" "built" "date"
"CODE_OF_CONDUCT.md" "549f00b0992a7743c2bc16ea6ce3db57" "site/built/CODE_OF_CONDUCT.md" "2024-10-01"
"LICENSE.md" "14377518ee654005a18cf28549eb30e3" "site/built/LICENSE.md" "2024-10-01"
"config.yaml" "01ba2faa4b450855eba2fba32408867c" "site/built/config.yaml" "2024-11-04"
"config.yaml" "3417130788385932c371ed8e8d04fd7f" "site/built/config.yaml" "2024-11-07"
"index.md" "32bc80d6f4816435cc0e01540cb2a513" "site/built/index.md" "2024-10-01"
"links.md" "8184cf4149eafbf03ce8da8ff0778c14" "site/built/links.md" "2024-10-01"
"episodes/simulating-transmission.Rmd" "946faeb2fbc86729c97e1e64bf66add2" "site/built/simulating-transmission.md" "2024-11-06"
"episodes/contact-matrices.Rmd" "0476f98485400ade81d7a494cb67e6a5" "site/built/contact-matrices.md" "2024-11-07"
"episodes/simulating-transmission.Rmd" "7755aede13b48a0ce78eef3e340449ff" "site/built/simulating-transmission.md" "2024-11-07"
"episodes/model-choices.Rmd" "597b512fd487349fe19eeb1ce3b565dc" "site/built/model-choices.md" "2024-11-06"
"episodes/modelling-interventions.Rmd" "92be2434ff4fd8fd79eb981fc45687a3" "site/built/modelling-interventions.md" "2024-11-06"
"episodes/compare-interventions.Rmd" "2d9b19604352386139e3527d4a6e5918" "site/built/compare-interventions.md" "2024-11-06"
Expand Down
Loading

0 comments on commit cc89750

Please sign in to comment.