sPop: Age-structured discrete-time population... | F1000Research
ALL Metrics
-
Views
Get PDF
Get XML
Cite
Export
Track
Software Tool Article
Revised

sPop: Age-structured discrete-time population dynamics model in C, Python, and R

[version 2; peer review: 2 approved]
PUBLISHED 13 Dec 2018
Author details Author details
OPEN PEER REVIEW
REVIEWER STATUS

This article is included in the RPackage gateway.

This article is included in the Python collection.

Abstract

This article describes the sPop packages implementing the deterministic and stochastic versions of an age-structured discrete-time population dynamics model. The packages enable mechanistic modelling of a population by monitoring the age and development stage of each individual. Survival and development are included as the main effectors and they progress at a user-defined pace: follow a fixed-rate, delay for a given time, or progress at an age-dependent manner. The model is implemented in C, Python, and R with a uniform design to ease usage and facilitate adoption. Early versions of the model were previously employed for investigating climate-driven population dynamics of the tiger mosquito and the chikungunya disease spread by this vector. The sPop packages presented in this article enable the use of the model in a range of applications extending from vector-borne diseases towards any age-structured population including plant and animal populations, microbial dynamics, host-pathogen interactions, infectious diseases, and other time-dependent epidemiological processes.

Keywords

deterministic, stochastic, vector, population, model, age-specific, survival, development, dynamic, difference equations, C, Python, R

Revised Amendments from Version 1

This version of the manuscript is prepared in response to the comments and suggestions of the reviewers. We have:

  • Revised the Introduction section and provided additional links to the relevant literature and use cases.
  • Revised and elaborated on the definition of the model in the Methods section.
  • Introduced a section titled Temporal resolution and accuracy to discuss the effect of time step size on the accuracy of numerical simulations.
  • Used the analogy of human pregnancy to elaborate on the concepts of age (age of the mother), degree of development (duration of her pregnancy), and development cycle (the number of completed development processes which corresponds to the number of births the mother has given in this analogy).
  • Provided a simple example to demonstrate the intended use of the pertub method (previously named as the perturbate method).
  • Clarified the findings of the model for the Nicholson's blowflies example.
  • Explained how canonical SIR model can be constructed as age-structured population models in the context of the Great Plague in Eyam.
  • Clarified Hastings' modelling strategy in the Age-structured host-parasite interactions section
  • Re-plotted all Python-generated figures and updated case_studies.py to improve the visibility of the data points.
  • Updated all three implementations of the model to prevent the update of devcycle when the perturb method is called. 
  • Updated the Python implementation of the sPop model as follows:
    • Replaced the comparison "death_sd is 0" with "death_sd == 0".
    • Replaced the comparison "dev_sd is 0" with "dev_sd == 0".
  • Performed minor corrections as recommended by the reviewer.

See the author's detailed response to the review by Matthew Silk

Introduction: The age-structured population dynamics model

Heterogeneity is inherent in most naturally occurring populations. Individuals possess or attain in time certain characteristics, which could result in differences in behaviour or response to stimuli. Time-dependent heterogeneity may result in the stratification of a population into chronological units. This could introduce time delays to certain processes and might have a strong non-linear impact on dynamics1.

For instance, distinct physiological stages emerge during insect development where each stage reacts differently to environmental factors such as temperature2. In addition, the minimum incubation period of an infection requires time delays, which is often ignored in canonical modelling approaches3. Time-dependent heterogeneity is ubiquitous in many areas including biomedical, engineering, and social sciences4. The analysis of lifetime data4, the degree day methodology5, and age- or stage-structured population dynamics modelling6 are common approaches for investigating such phenomena.

Incorporating age dependency in mathematical models can be challenging due to the need to keep track of the age of each individual in a population. A common work-around is to introduce predetermined intermittent stages to account for the different characteristics of each stage and the time it takes to pass from one to another. This approach has been extensively used in various contexts, e.g. to model animal development7, insect life cycle8,9, disease transmission10,11, and economic surplus12. Although intermittent stages are capable of representing age-structured populations to a certain extent, a large number of age classes are required for accuracy. Consequently, model development becomes a non-trivial task.

Numerous packages including popbio13, demogR14, and bayesPop15 have been implemented to facilitate modelling and analysis of age- and stage-structured projection matrix models. As a viable alternative, Kettle and Nutter implemented an R package for agestructured population dynamics, StagePop, which offers true time delays in continuous time domain using deterministic delay differential equations16.

Here, I present an alternative age-structured population dynamics model based on the population dynamics and disease-transmission models described in Erguler et al. 201617 and 201718. The approach involves automatically classifying a population into distinct age and development groups and applying a projection matrix to derive the next state. In its current form, the model is based on discrete-time difference equations. Three implementations of the model exists (the sPop packages) for three programming languages, C, Python, and R. The sPop packages provide a flexible number of age and development categories, include both deterministic and stochastic dynamics, and offer high-speed simulations to facilitate parameter inference.

The following section describes the theory behind the model and presents the use of each implementation with a commonly encountered case. The same case is modelled with each sPop package to emphasise the nuances in their usage. The Temporal resolution and accuracy section investigates the accuracy of numerical simulations with regards to different time step sizes. The Use cases section concludes with the sPop implementations of a short list of well-known mathematical models selected from a range of disciplines.

Models and software

The sPop packages help to incorporate age-structured populations in discrete-time deterministic and stochastic difference equations models. The population is assumed to be in the same (heterogeneous) state shared by all individuals. Two processes determine the length of stay in this state: survival (𝒮) and development (𝒟). These processes are assumed to act in a sequential manner, where development proceeds only if survival is guaranteed for the duration of an iteration, . Survival and development may (i) progress at a fixed propensity, (ii) delay for a given number of iterations, (iii) follow a gamma-distributed (or negative binomial-distributed) life-time, or (iv) halt for a given time period. Here, I adopt the term propensity to refer to either the rate of a deterministic process or the probability of a stochastic one.

The earlier version of the age-structured population dynamics model17 follows the degree day approach and represents progress with a physiological development unit accumulating at each iteration. The rate of accumulation can be fixed or variable in response to external or internal factors. When the value of this indicator exceeds a predefined threshold, the corresponding individuals are removed from the population (the process of survival or development is complete for them). The more recent version of the model18 employs propensity, which can be seen as the probability (or rate) of the indicator exceeding the threshold at each iteration. Exchanging the indicator with propensity aids the development of both deterministic and stochastic models with variable state duration, and is the preferred approach for the implementation of the sPop packages.

According to the model, age and development are the two counters of elapsed time, one of which (development) iterates a third counter (development cycle) on completion. Human pregnancy can be given as an analogy; a woman’s age would be tα and the stage of her pregnancy would be tδ. The population would then be comprised of a group of women in different stages of pregnancy. It is important to note that, since age and development progress in synchrony, their time units should be the same, e.g. month or day. In this analogy, birth would be triggered by tδ, which, in turn, would iterate tπ, which could be seen as the number of births each mother has given.

Although age and development advance at the same pace, progress is assessed by each counter approaching the completion of survival or development. Propensity can be assumed constant, or it can be derived from the duration of survival or development. The duration of each process can be defined as a user-supplied function, which may depend on age (tα), number of completed development cycles (tπ), the degree of completion of the present development cycle (tδ), or any other environmental factors.

Each individual is allowed to stay in the population given that neither death (𝒳 where Pr(𝒳) = 1Pr(𝒮)) nor development occurs during an iteration. In a deterministic setup, a given fraction of the population will die (r𝒳) or survive and complete development (r𝒟) every . In a stochastic setup, the number of individuals to die or develop is chosen from a binomial distribution, (n, p), where p is the daily probability of death (p𝒳) or development (p𝒟) and n is the size of the population. In most experimental setups, development is observed on the condition that individuals survive; therefore, this condition is implicit in the definition of development propensity, i.e. p𝒟 = Pr(D|S) per individual per .

At each iteration, tα and tδ are incrementally adjusted to keep track of the age and the degree of development, respectively, for each individual. When a batch of individuals complete development, the indicator tπ is adjusted, tδ is reset to zero, and the batch is removed from the population. When modelling periodic development processes, such as the gonotrophic cycle or human pregnancy, the batch can be reintroduced to the population for the subsequent cycle of development.

When propensity is fixed for either 𝒳 or 𝒟, the time required for the completion of life or development follows a geometric distribution where the size of the population (n) can be described with

nt=nt-1 (1r𝒳)(1r𝒟),

for the deterministic case, or with

Pr(nt=𝓍)=Pr(nt1=𝓍+1)p𝒳(𝓍+1)+Pr(nt1=𝓍+1)(1p𝒳)p𝒟(𝓍+1)Pr(nt1=𝓍)(p𝒳+(1p𝒳)p𝒟)𝓍,

for the stochastic case, where n0 is a user-defined initial condition.

We assumed that the survival and development processes are driven by two counters: (i) age, tα, and (ii) the degree of development, tδ. Age affects survival and the degree of development determines if development has been completed. The duration of each process can be a fixed number of iterations or can be described by a discrete negative binomial distribution or a continuous gamma distribution. In case of a negative binomial or gamma distribution, the daily propensity of death or development can be calculated as the ratio of the probability that the process is completed between iteration d and d+1 to the total probability of surviving or developing for at least d days. For instance, the daily probability of death in a stochastic setup can be written as

p𝒳=Pr(𝘛d<𝓍𝘛d+1)Pr(𝘛d<𝓍)=F(𝘛d+1;μ,σ)F(𝘛d;μ,σ)1F(𝘛d;μ,σ),

where x represents the iteration when death occurs, and F(·) is the cumulative distribution function of either the negative binomial distribution or the gamma distribution. The parameters of each distribution are determined from the desired mean (µ) and standard deviation (σ) of the number of iterations to complete the process. An advantage of the gamma distribution over the negative binomial is its flexibility in accommodating various combinations of mean and standard deviation. However, the negative binomial distribution is restrictive over the minimum allowed standard deviation for a given mean.

Implementation

The R implementation of sPop is available on CRAN as the albopictus package (v.0.5) and on the GitHub repository https://doi.org/10.5281/ zenodo.1685054. The C and Python implementations are available as part of the albopictus package (v.1.11.0) on PyPI and the GitHub repository https://doi.org/10.5281/zenodo.1685289. The packages are implemented for R version 3.5.1 and Python version 3.7.0.

This section is reserved for outlining the use of each implementation to model the same theoretical population where both development and survival are age-dependent and gamma-distributed. In addition, the population exhibits a periodic development process with a mean duration of 50 hours and a standard deviation of 10 hours, and survival is a function of the number of development cycles,

μ=max(240,48048tπ)hoursσ=μ/10.(1)

R

Before we begin modelling, we load the albopictus package in R, and define the survival function as described in Equation 1.

R > library(albopictus)
R >
R > death <- function(pop) {
R +     if (nrow(pop)==0)
R +         return(data.frame(mean=480, sd=48.0))
R +     mn <- 480.0 - (48.0 * pop$devcycle)
R +     mn[mn < 240.0] <- 240.0
R +     return(data.frame(mean=mn, sd=0.1*mn))
R + }

The function returns a data.frame with a desired mean and standard deviation. Next, we initiate a population by calling the initiation routine of the spop class.

R > vec <- spop(stochastic=TRUE, prob="gamma")

With this line, we construct a stochastic population model with the gamma distribution as the basis of survival and development. Setting prob to nbinom selects the negative binomial distribution instead.

In order to introduce the first batch of individuals, we use the add method.

R > add(vec) <- data.frame(number=1000)

By default, age, development cycle, and the duration of development will be set to zero for all individuals. These can be customised by supplying additional fields to the data.frame: age to set age, devcycle to set the number of development cycles, and development to set the number of iterations the current development cycle has taken.

We can directly access the population structure of the spop class to inspect the number of individuals grouped with respect to age, development cycle, and the degree of development. Here, we will use these information to calculate the mean and standard deviation of expected lifetime for each age-development group.

R > tmp <- death(vec@pop)

The following step iterates the population for one time-unit by using the iterate method.

R > iterate(vec) <- data.frame(dev_mean = 50,
R +                            dev_sd = 10,
R +                            death_mean = tmp$mean,
R +                            death_sd = tmp$sd)

By defining dev_mean and dev_sd, we opt to use the gamma distribution to describe the probability of development. Setting dev_sd to zero results in the gamma distribution being discarded and a fixed number of iterations (indicated by dev_mean) being assigned for development. Instead, setting dev instead of dev_mean and dev_sd results in a daily constant development probability. The same principles apply for the survival process, where we provide the mean and the standard deviation of the gamma-distribution for each age- development group as calculated by the death function. After each iteration, the age and degree of development of the population are updated and the total number of individuals completing development is recorded together with the detailed account of the corresponding age-development groups. We access these data using the developed and devtable methods, respectively. In addition, the dead method returns the number of dead individuals following an iteration.

R > d <- developed(vec)
R > add(vec) <- devtable(vec)

In this example, we assume a periodic development process; therefore, we introduce all the individuals completing development back to the population using the add method.

Finally, we read the total size of the population using the size method.

R > s <- size(vec)

In the R implementation, we also provide an accessory method, perturb, to perform the same functions as the iterate method without updating age or development. By using perturb, the structure and size of a population can be modified to model the impact of an intervention or migration. The effect is immediate and in addition to the continuing survival and development processes. For instance, perturb can be used to model population control. If a certain agent kills a fraction of a population upon delivery, perturb can be used by specifying death to remove the affected individuals from the population. Otherwise, if the effect is age-dependent, death.mean and death.sd can be specified to calculate the fraction to be removed. If there is a need to migrate a subset of a population (population A) to a different population (population B), perturb can be used by specifying an appropriate development process (dev for age-independent, dev.mean and dev.sd for age-dependent migration). This results in keeping a detailed record of the age and stage of development of the individuals removed from population A. Essentially, this is the list of individuals selected for migration, and it can be used as desired. A simple example of migration is given below.


R > A <- spop(stochastic=FALSE, prob="gamma")
R > B <- spop(stochastic=FALSE, prob="gamma")
R > add(A) <- data.frame(number=1000)
R > perturb(A) <- data.frame(dev=0.5, death=0)
R > add(B) <- devtable(A)

The above code results in the migration of 500 individuals from population A to B while freezing the age, development cycle, and development counters.

Python

The Python implementation of the population dynamics model can be imported from the albopictus package.

Python >>> from albopictus.population import spop

We begin by declaring the survival function as in Equation 1.

Python >>> def death(pop):
Python ...     if pop.shape[1]==0:
Python ...         return [480.0, 48.0]
Python ...     mn = 480.0 - 48.0 * pop[:,1]
Python ...     mn[mn < 240.0] = 240.0
Python ...     return [mn, 0.1*mn]

Unlike the R implementation, the population structure is stored in a numpy.ndarray with the following order of columns: age, development cycle, degree of development, and number. Although the initiation step is similar to the R implementation, a two-dimensional list or a numpy.ndarray should be supplied to intriduce batches of individuals to a population.

Python >>> vec = spop(stochastic=True,prob="gamma")
Python >>> vec.add( [ [0, 0, 0, 1000] ] )

By using the add method above, we introduce 1000 individuals with zero age, development cycle, and degree of development. The population structure is directly accessible, which enables us to calculate a different mean and standard deviation for the gamma-distributed development of each age-development group.

Python >>> tmp = death(vec.pop)

Next, we iterate the population for one time-unit using the iterate method.

Python >>> vec.iterate(dev_mean = 50,
Python ...             dev_sd = 10,
Python ...             death_mean = tmp[0],
Python ...             death_sd = tmp[1])

In order to read the total number of individuals completing development, the detailed account of the corresponding age-development groups, and the total size of the population, we access the developed, devtable, and size attributes of the spop class. Please note that these attributes are overwritten each time the iterate method is called. Here, we record the total number of developed individuals and the population size, and reintroduce the developed individuals to the population for the next round of development.

Python >>> d = vec.developed
Python >>> vec.add(vec.devtable)
Python >>> s = vec.size

The Python implementation of the iterate method accepts an additional logical indicator pause to prevent updating age and development. If this parameter is supplied and if it is false, the iterate method acts as the perturb method of the R implementation.

C

The C implementation of the sPop package is further optimised for speed. The source code resides in the albopictus package of Python, and it needs to be compiled with the GNU Scientific Library (version 2.1 or later). We begin by locating the package directory and compiling three source files into the object code. Assuming that the file name of our model is test_spop.c, we produce the executable with the following.

$ gcc -c -o ran_gen.o ${pkgdir}/ran_gen.c
$ gcc -c -o gamma.o ${pkgdir}/gamma.c
$ gcc -c -o spop.o ${pkgdir}/spop.c
$ gcc -I${pkgdir} -lgsl -o test_spop ran_gen.o
    gamma.o spop.o test_spop.c

where $pkgdir is a bash variable holding the package directory. In order to use the package, we need to include the following header files in test_spop.c.

C   #include "ran_gen.h"
C   #include "gamma.h"
C   #include "spop.h"

The first header file defines the routines required for random number generation, and the second one defines the routines for the gamma and negative binomial distributions. The last header file defines the spop population structure and the associated functions for initialisation, modification, and garbage collection.

Each age-development group is stored in the individual_st data structure,

C   typedef struct individual_st {
C     unsigned int age;
C     unsigned int devcycle;
C     unsigned int development;
C     sdnum number;
C   } individual_data;

where the age, development cycle, degree of development, and the number of individuals in each age-development group are stored in age, devcycle, development, and number variables in the same order. The sdnum is a union data structure holding an unsigned int for a stochastic population or a double for a deterministic population. The spop data structure holds an array of individuals (individuals), population size (size), the number of dead and developed individuals following an iteration (dead and developed, respectively), a detailed account of developed individuals (devtable), an indicator for the probability distribution of age dependence (gamma_mode), a logical indicator for a stochastic or a deterministic model (stochastic), and two counters to manage the dynamic size of individuals (ncat and cat).

C   typedef struct population_st {
C     individual_data *individuals;
C     sdnum size;
C     sdnum dead;
C     sdnum developed;
C     void *devtable;
C     unsigned char gamma_mode;
C     unsigned char stochastic;
C     unsigned int ncat;
C     unsigned int cat;
C   } *spop;

Following the procedure in previous sections, we begin implementing the model in test_spop.c by declaring the survival function in Equation 1.

C   void death(const individual_data *ind,
C              double *death_prob,
C              double *death_mean,
C              double *death_sd) {
C     (*death_prob) = 0;
C     (*death_mean) = 480.0 - (ind->devcycle > 4 ?
     240.0 : 48.0 * ind->devcycle);
C     (*death_sd) = 0.1 * (*death_mean);
C   }

Please note that the C implementation handles a single age-development group at a time; therefore, the survival function is redesigned accordingly.

Next, we initiate a stochastic model with the gamma distribution as the basis of survival and development using the spop_init function with the first parameter set to a logical true.

C   vec = spop_init(1,MODE_GAMMA_HASH);

The macro MODE_GAMMA_HASH refers to the optimised implementation of the gamma distribution. Alternatively, MODE_NBINOM_RAW and MODE_GAMMA_RAW refer to the unoptimised implementations of the negative binomial and the gamma distributions. Optimisation involves recording previously-used values in a hash table for reuse, however, is memory intensive and should be used with caution. Faster more efficient implementations of the probability distributions are the main concern for future releases.

Having initiated vec, we introduce 1000 individuals of zero age with the spop_add function.

C   spop_add(vec,0,0,0,1000);

spop_add accepts parameters in the following order:

  • 1. spop s: the spop data structure

  • 2. unsigned int age: the age of individuals

  • 3. unsigned int devcycle: the number of development cycles passed

  • 4. unsigned int development: the degree of development of individuals

  • 5. sdnum number: the number of individuals (unsigned int or double)

In order to iterate the population for one time interval, we use the spop_iterate function.

C   spop_iterate(vec,
C                0,
C                50.0, 10.0,
C                0,
C                0,
C                0, 0,
C                death,
C                0);

spop_iterate accepts the following parameters in the given order:

  • 1. spop s: the spop data structure

  • 2. double dev_prob: fixed daily development probability (priority over the other development-related parameters)

  • 3. double dev_mean: mean development time (gamma or negative binomial)

  • 4. double dev_sd: standard deviation of the development time

  • 5. iter_func dev_fun: development function (similar to the death function above)

  • 6. double death_prob: fixed daily death probability (priority over the other survival-related parameters)

  • 7. double death_mean: mean time of death (gamma or negative binomial)

  • 8. double death_sd: standard deviation of the time of death

  • 9. iter_func death_fun: survival function

  • 10. unsigned char pause: logical indicator to prevent updating age and development

Following each iteration, the list of age-development groups that completed their development is stored in the devtable variable of the spop data structure. In order to reintroduce these individuals back to the population, we use the spop_popadd function.

C   spop_popadd(vec,vec->devtable);

It is possible to obtain a summary output of the population structure by using the spop_print function, which takes the spop data structure as the only parameter. spop can be recycled by emptying its contents with the spop_empty function.

C   spop_empty(vec);

Finally, in order to clear the memory used by vec, we supply its address to the spop_destroy function.

C   spop_destroy(&vec);

Model output

All three implementations of the model are given in Supplementary File 1Supplementary File 3 (test_spop.R, test_spop.py, and test_spop.c. The resulting distribution of the number of individuals completing a development cycle during the first 20 days of simulation is given in Figure 1.

d4682d2c-c411-4505-89f4-1ab465b63f5c_figure1.gif

Figure 1. Number of individuals completing a development cycle in 20 days.

Thick solid line indicates the mean trajectory from the deterministic simulation, while the thin solid line and the dotted lines indicate the median and the 95% range of the stochastic simulation output. The timestep for each iteration is one hour.

Five cycles of development are clearly seen from the figure, while the population survives for less than 15 days with the survival function defined in Equation 1. Blending of development cycles is apparent and progressive due to the uncertainty in the duration of development (50 hours on average with a standard deviation of 10 hours).

Temporal resolution and accuracy

In the previous section, we simulated a hypothetical age-structured population using 1-hour time steps. Here, we investigate the effect of time step size to the accuracy of simulations by using a simplified version of this model. We focus on the deterministic version and assume that the default development duration is 10 (±2) days and the lifetime is 50 (±10) days. We define a scaling factor α to tune step size between days and hours. The resulting Python code for the iteration of this population (named vec) is given below.

Python >>> vec.iterate(dev_mean = 10* alpha,
Python ...             dev_sd = 2*alpha,
Python ...             death_mean = 50*alpha,
Python ...             death_sd = 10*alpha)

In addition, we let α scale the number of iterations, which is by default 50. Consequently, when α = 1, the average lifetime is 50 and the simulation runs for 50 iterations each corresponding to 1 day. When α = 24, the average lifetime becomes 1200 and the number of iterations also becomes 1200, which implies that each iteration corresponds to 1 hour. It is straightforward to have intermediary time steps. For instance, α = 2 and α = 4 yield half-day and quarter-day iterations, respectively.

We demonstrate the effect of four time step sizes on the number of individuals completing a development cycle in Figure 2. The model yields oscillations fading in amplitude similar to the original model (Figure 1). Although the peak height decreases, the cumulative number of developed individuals agree well in each case. In the inset of Figure 2, we show that the overall numerical error increases linearly with increasing time step size.

d4682d2c-c411-4505-89f4-1ab465b63f5c_figure2.gif

Figure 2. The effect of time step size to simulation accuracy.

The number of individuals completing a development cycle is shown for different time steps. The widths of the bars correspond to the following time step sizes: 24 hours (α = 1), 12 hours (α = 2), 6 hours (α = 4), and 1 hour (α = 24) from the widest to the thinnest. The inset graph shows the total number of developed individuals with respect to different step sizes (solid dots). The line of best fit is also shown in the graph.

Use cases

This section describes how the Python implementation of sPop can be used to model some of the well-known population dynamics models. These models can also be constructed in R and C by following the guidelines presented in the previous section.

Nicholson’s blowflies

We begin with Nicholson’s Blowflies, a classic example of time-delayed stage-structured population model16,19. The model comprises five distinct life-stages and exhibits stable quasi-cyclic oscillations. Although, originally the model was constructed using continuous time-delay equations, we will demonstrate that the sPop model adheres well to the observed dynamics and the implementation presented in the StagePop package16. Furthermore, we will present a stochastic version of the model, which helps to improve our understanding of the observed variation.

Both the deterministic and stochastic versions of the model are presented in Supplementary File 4 (case_studies.py). The adaptation assumes fixed daily survival and strict development durations, values of which are the same as the original model (Figure 2 in Gurney et al. 198319). A scaling factor is introduced to calculate hourly instead of daily propensities to improve accuracy on a par with the continuous-time simulations.

As a result, the output of the model is almost identical to the output of the original model (compare Figure 3(a) with the Figure 3a of Gurney et al. 198319). The six peaks shown between generations 100 and 300 are matched by the stochastic version of the model (Figure3(b)). Evidently, the highest variability in stochastic simulations is in peak amplitude, whereas the frequency of the oscillations is well conserved. During each peak, two sub-peaks are observed, which are separated by a trough. The heights of each peak and trough largely vary suggesting that the two peaks may not be resolved in the observations of natural populations. Nevertheless, similar fluctuations were observed in the laboratory culture of the Australian sheep blowfly reported in Nicholson 195420 (also presented in Gurney et al. 198319 Figure 1).

d4682d2c-c411-4505-89f4-1ab465b63f5c_figure3.gif

Figure 3. Age- and stage-structured model of Nicholson’s Blowflies in discrete time.

In (a), the deterministic trajectories of eggs (dashed line) and mature adults (solid line) are shown for the corresponding generations. In (b), five stochastic trajectories of mature adults are shown for the same duration.

Age-structured host-parasite interactions

Another classic example of age dependency in population dynamics was proposed by 21 as a variation of the host-parasite interaction model of 22. The Nicholson-Bailey model considers dynamics in discrete generations where parasites traverse a given area in search of a host. As a result, the number of parasites in the subsequent generation corresponds to the number of hosts parasitised. Hastings used this model for prey-predator interactions where he represented preys as hosts and predators as parasites. He introduced age-structure to the prey population and assumed that only juvenile preys are targeted by predators. Following Hastings, we use the terms host and parasite as analogous to prey and predator, respectively, regarding the emerging dynamics.

Both the original Nicholson-Bailey model and its age-structured version are implemented in Supplementary File 4 (case_studies.py). As shown in Figure 4(a), the original model without age dependency (dashed lines) exhibits oscillations with increasing amplitude around an unstable steady state. The model output matches Figure 10 in Nicholson (1935)22. Introducing age-structure with a fixed survival rate for host results in the stabilisation of dynamics as reported by Hastings (1984)21 and seen in Figure 4(a) (solid lines).

d4682d2c-c411-4505-89f4-1ab465b63f5c_figure4.gif

Figure 4. Age-structured host-parasite interactions of Nicholson-Bailey-Hastings.

In (a), the Nicholson-Bailey model (dashed lines) is compared with its age-structured version (solid lines) where parasites choose host in an age-dependent manner. The number of parasites is scaled down to 25% to aid visualisation. In (b), the effect of age-dependent host survival on stability is shown. Mortality rate is given in the inset with respect to age (the number of generations). Dashed line: age-independent mortality; solid orange: life expectancy of precisely 10 generations; solid green, red, and purple: gamma-distributed life expectancy with mean 10 and standard deviation 1.25, 2.5, and 5 generations, respectively.

Hastings (1984)21 also discusses the disruptive effect of age-dependent host survival in stability. This is evident in Figure 4(b), where the survival imbalance between young and old individuals drives the dynamics away from the steady state, eventually rendering it unstable. When the age-dependent mortality curve is close to being horizontal, the dynamics closely resemble the Hastings’ model with no age limit (solid lines in Figure 4(a) and the dashed line in Figure 4(b)). When mortality is considerably higher in older individuals (green line in Figure 4(b)) or a strict age limit is introduced (orange line in Figure 4(b)), the stability is lost and the amplitude of oscillations increase in time.

The Great Plague in Eyam

The final case we study is the severe outbreak of bubonic plague in Eyam (Sheffield, UK) in 1665–166623. The outbreak was initially modelled by Raggett et al. 198223, and later, a simple deterministic susceptible-infectious-removed (SIR) epidemic model was developed by Brauer et al. 200124 to study the outbreak.

Although canonical SIR models are not age-structured, the underlying transmission dynamics is intricately time-dependent. This becomes clear when, for instance, the (intrinsic) incubation period is observed to be strictly more than a couple of days but not less. While a system of ordinary differential (or difference) equations describes an uninterrupted flow between the incubation period and the infectious state, the sPop age-structured model is able to track the length of the incubation period for each individual and trigger state change only for the right individuals at the right times.

An age-structured version of the SIR epidemic model where the infectious stage duration is modelled with a gamma distribution is presented in Supplementary File 4 (case_studies.py). As shown in Figure 5, the number of infectious cases with respect to the number of susceptible individuals, the S-I plane, (Figure 5(a)) and the time trajectory of the outbreak (Figure 5(b)) closely follow the data presented in Brauer et al. 200124 Table 9.1.

d4682d2c-c411-4505-89f4-1ab465b63f5c_figure5.gif

Figure 5. The age-structured SIR model of the Great Plague in Eyam.

In (a), the simulated numbers of infectious versus susceptible individuals are shown together with the outbreak data. In (b), the number of susceptibles and infectious cases are plotted with respect to time for the duration of the outbreak. Model simulations (solid lines) are compared with the outbreak data (dots).

Figure 6 demonstrates the effect of age-structure on outbreak dynamics by comparing model output with different characteristics of the infectious period. The blue lines indicate the trajectory matching the Great Plague in Eyam. Please note that the corresponding infectious period is only slightly time-dependent where there is a minor difference between the mortalities of newly infected and long-time infectious individuals (plotted in the inset). If the rate of exit from the infectious stage is completely independent from the duration spent in the stage (as is the case with canonical SIR models), the outbreak duration increases (the green lines). In the opposite scenario, where the length of the infectious case is precise, the entire outbreak resolves rapidly as seen in the orange trajectory. Since time-dependence has a significant impact on outbreak trajectory, using realistic mathematical formulations to infer outbreak parameters, such as the incubation period and the rate of infection, becomes a critical step in developing predictive epidemic models.

d4682d2c-c411-4505-89f4-1ab465b63f5c_figure6.gif

Figure 6. The effect of age-structure in modelling epidemics.

The outbreak trajectory shown in Figure 5(b) (blue lines) is compared with alternative forms of mortality. Green lines indicate time-independence where the gamma distributed infectious period has σ = µ. Orange lines indicate that the infectious period has a precise length of µ = 11.71 days (σ = 0).

Summary

The sPop packages are designed to incorporate age dependence in discrete-time population dynamics models. The underlying model incorporates survival, development, and migration processes, and it can accommodate both deterministic and stochastic dynamics. In order to promote applications, three versions of the model were implemented: the R and Python implementations are aimed at educational and introductory level use, while the C implementation offers further optimisation and high-speed simulations. This paper demonstrated that the model is capable of representing age-structured population dynamics from a range of disciplines including insect populations, host-parasite/prey-predator interactions, and infectious disease epidemiology. Future research concerns optimising to limit simulation times and implementing the model in continuous time domain for improved accuracy.

Software and data availability

R implementation of sPop:

C and Python implementation of sPop:

All data and source code for running the examples and plotting the figures in this manuscript are provided in the Supplementary Material:

  • test_spop.R: R script file for Section Implementation:R.

  • test_spop.py: Python script file for Section Implementation:Python.

  • test_spop.c: C code file for Section Implementation:C.

  • plot_test_spop.R: R script file for plotting Figure 1.

  • case_studies.py: Python script file for Section Use Cases.

Comments on this article Comments (1)

Version 3
VERSION 3 PUBLISHED 28 Sep 2020
Revised
Version 2
VERSION 2 PUBLISHED 13 Dec 2018
Revised
Discussion is closed on this version, please comment on the latest version above.
  • Reader Comment 23 Jul 2020
    Sean L. Wu, Division of Epidemiology & Biostatistics, University of California, Berkeley, Berkeley, California, USA
    23 Jul 2020
    Reader Comment
    Thank you for this interesting and flexible software. Having an implementation in 3 of the most widely used programming languages will certainly address the occasional barrier to innovative software not ... Continue reading
  • Discussion is closed on this version, please comment on the latest version above.
Author details Author details
Competing interests
Grant information
Copyright
Download
 
Export To
metrics
Views Downloads
F1000Research - -
PubMed Central
Data from PMC are received and updated monthly.
- -
Citations
CITE
how to cite this article
Erguler K. sPop: Age-structured discrete-time population dynamics model in C, Python, and R [version 2; peer review: 2 approved]. F1000Research 2018, 7:1220 (https://doi.org/10.12688/f1000research.15824.2)
NOTE: If applicable, it is important to ensure the information in square brackets after the title is included in all citations of this article.
track
receive updates on this article
Track an article to receive email alerts on any updates to this article.

Open Peer Review

Current Reviewer Status: ?
Key to Reviewer Statuses VIEW
ApprovedThe paper is scientifically sound in its current form and only minor, if any, improvements are suggested
Approved with reservations A number of small changes, sometimes more significant revisions are required to address specific details and improve the papers academic merit.
Not approvedFundamental flaws in the paper seriously undermine the findings and conclusions
Version 1
VERSION 1
PUBLISHED 08 Aug 2018
Views
0
Cite
Reviewer Report 22 Oct 2018
Matthew Silk, Centre for Ecology and Conservation, University of Exeter, Penryn, UK 
Approved
VIEWS 0
In this manuscript Erguler introduces the software tool that can be applied to modelling discrete time, age-structured population dynamics in R, Python and C. The basics of using the software in all three programming languages are well covered and the ... Continue reading
CITE
CITE
HOW TO CITE THIS REPORT
Silk M. Reviewer Report For: sPop: Age-structured discrete-time population dynamics model in C, Python, and R [version 2; peer review: 2 approved]. F1000Research 2018, 7:1220 (https://doi.org/10.5256/f1000research.17272.r39332)
NOTE: it is important to ensure the information in square brackets after the title is included in all citations of this article.
  • Author Response 13 Dec 2018
    Kamil Erguler
    13 Dec 2018
    Author Response
    I would like to thank Dr Silk for reviewing the manuscript and for his valuable comments and suggestions. I have submitted a revised version with an aim to address the ... Continue reading
COMMENTS ON THIS REPORT
  • Author Response 13 Dec 2018
    Kamil Erguler
    13 Dec 2018
    Author Response
    I would like to thank Dr Silk for reviewing the manuscript and for his valuable comments and suggestions. I have submitted a revised version with an aim to address the ... Continue reading
Views
0
Cite
Reviewer Report 28 Aug 2018
Juliane Liepe, Max-Planck-Institute for Biophysical Chemistry, Göttingen, Germany 
Approved
VIEWS 0
The manuscript “sPop: Age-structured discrete-time population dynamics model in C, Python, and R” by Kamil Erguler provides a comprehensive yet well summarised description of a novel software tool to model age-structured discrete-time population dynamics.
 
The software tool ... Continue reading
CITE
CITE
HOW TO CITE THIS REPORT
Liepe J. Reviewer Report For: sPop: Age-structured discrete-time population dynamics model in C, Python, and R [version 2; peer review: 2 approved]. F1000Research 2018, 7:1220 (https://doi.org/10.5256/f1000research.17272.r36953)
NOTE: it is important to ensure the information in square brackets after the title is included in all citations of this article.

Comments on this article Comments (1)

Version 3
VERSION 3 PUBLISHED 28 Sep 2020
Revised
Version 2
VERSION 2 PUBLISHED 13 Dec 2018
Revised
Discussion is closed on this version, please comment on the latest version above.
  • Reader Comment 23 Jul 2020
    Sean L. Wu, Division of Epidemiology & Biostatistics, University of California, Berkeley, Berkeley, California, USA
    23 Jul 2020
    Reader Comment
    Thank you for this interesting and flexible software. Having an implementation in 3 of the most widely used programming languages will certainly address the occasional barrier to innovative software not ... Continue reading
  • Discussion is closed on this version, please comment on the latest version above.
Alongside their report, reviewers assign a status to the article:
Approved - the paper is scientifically sound in its current form and only minor, if any, improvements are suggested
Approved with reservations - A number of small changes, sometimes more significant revisions are required to address specific details and improve the papers academic merit.
Not approved - fundamental flaws in the paper seriously undermine the findings and conclusions
Sign In
If you've forgotten your password, please enter your email address below and we'll send you instructions on how to reset your password.

The email address should be the one you originally registered with F1000.

Email address not valid, please try again

You registered with F1000 via Google, so we cannot reset your password.

To sign in, please click here.

If you still need help with your Google account password, please click here.

You registered with F1000 via Facebook, so we cannot reset your password.

To sign in, please click here.

If you still need help with your Facebook account password, please click here.

Code not correct, please try again
Email us for further assistance.
Server error, please try again.