2 Solving - Differential - Equations - in - R
2 Solving - Differential - Equations - in - R
2. Section ??
2.1 Section ??
2.2 Section ??
3. Section ??
4. Section 1.5
5. Section ??
5.1 Section ??
5.2 Section ??
6. Section ??
To model processes such as population growth, or the spread of infection in a population for
example, we often consider how the relevant variables change over time. This means we need to
look at differential equations.
dN
= αN
dt
1
This is an Ordinary Differential Equation, or ODE.
We refer to N as a state variable; its value represents the state of the system at a given time. α
(alpha) is a parameter.
Notice here, we’re ignoring some of the realistic constraints on population size: for example,
the size of the flask, and the amount of food available. For now, we’re only looking at the initial
population growth, but later we’ll bring constraints in as well.
You’ll now see how to solve this equation using R, for a given value of α. This is a simple
example that will help you get familiar with the ODE solvers in R. You will then use these same
techniques in the specialisation, when you come to modelling infectious diseases.
In [ ]: ## THIS CELL IS NOT INTENDED TO WORK - it is just to show what the code to run ode() lo
result <- ode( y = state_vars # initial state variables: state variables at first
, times = times # a vector of timepoints
, func = exp_pop_fn # the differential equation itself, in the form of
, parms = parms) # parameters for the equation in func
In [3]: ?ode
(in Jupyter notebooks, this will load the help file in a new browser tab/window)
y must contain the initial state values. In our simple example, we only have N. We start with
1 bacterium, so our initial N = 1
timescontains the sequence of times for which we want to know the output. The first timepoint is
the initial timepoint, corresponding to our initial state values.
func is our differential equation, defined as an R function. The help file shows how to define it
yourself. (You can also use pre-defined equations.)
parms are the parameters for the equation in func.
The ode() function has a number of other arguments but we don’t have to define them all -
method, for example, has a default.
In [2]: # y
state_vars <- c(N = 1) # a vector with a named element
# times
times <- seq(0, 40, by = 0.5) # the shorter the timesteps, the more accurate a so
# func: a function
2
exp_pop_fn <- function(time, state, parameters) {
N <- state['N'] # within this function, the state variables - in this case, just N
# Remember that this function is an argument into another function; it doesn't do a lot
# The inputs come from running the ode() function, the output goes back into the ode()
# parms
parms <- c(alpha = log(2)) # alpha has been chosen so the population doubles each times
parms['alpha'] # you can see "parms" is a vector with one named element, alpha.
# this argument 'parms' gets fed, by ode(), into the function that you s
# so it needs to contain whatever variables that function is expecting.
alpha: 0.693147180559945
• Look at the function exp_pop_fn() defined in the cell above; compare the line dN <-
parameters['alpha']*N to the Section ?? you’ve seen above.
3
# use ggplot to create a graph of times against the population size N
require(ggplot2) # if
time N
<dbl> <dbl>
0.0 1.000000
0.5 1.414216
A data.frame: 6 Œ 2
1.0 2.000003
1.5 2.828433
2.0 4.000009
2.5 5.656869
4
1.4 3. More realistic: logistic growth
Let’s make this closer to reality, and take into account the fact that populations cannot increase
forever in a limited space with limited resources. In ecology we model this using what is known
as a ‘carrying capacity’, called K. As the population size comes close to K, then the rate of growth
slows down. The population equation we want to solve for dN/dt with a carrying capacity K is
the logistic growth equation:
( )
dN N
= αN 1 −
dt K
We’ll need to make this into a suitable function for func:
In [ ]: logistic_fn <- function(t, state, parameters) { # You'll need a K in the parameters arg
5
N <- state['N'] # still our only state variable
return(list(c(dN)))
Assign the K parameter, run ode() with your new logistic function, and plot the output:
1.5 4. Summary
So as long as you can:
you can:
6
you input these into ode(), to solve your differential equation and output the variable(s) of
interest, at each of those timepoints.
For a quick introduction to exponential and logistic equations for popu-
lation growth, visit: https://www.nature.com/scitable/knowledge/library/
how-populations-grow-the-exponential-and-logistic-13240157
For more detailed references on the deSolve package, try searching for deSolve vignettes
https://cran.r-project.org/web/packages/deSolve/vignettes/deSolve.pdf
1.6.1 5.1 Where functions find values associated with names: Scope
A common source of error when writing functions, especially when there are functions-within-
functions, is that you might find that either your function returns an error, or returns a value you
didn’t expect. This could be due to misnaming of variables or arguments. You might find that
following working examples is helpful for avoiding these, but if you want to understand more
about where functions find values, read this section.
Arguments are given their own names within the environment, or the scope of the function. You
give names to the variables that will contain your arguments, and you tell the function what each
argument is, or give them to the function in the order it expects. The function then refers to each
argument to the name it has internally.
For an explanation of scope, try the following references:
A general introduction to the concept: https://en.wikiversity.org/wiki/Introduction_to_
Programming/Scope
How R finds what values are associated with names:
https://adv-r.hadley.nz/functions.html#lexical-scoping
Also R-specific, more technical:
https://www.datamentor.io/r-programming/environment-scope/
An explanation using another language, Python:
https://pythonbasics.org/scope/
This is also taken advantage of if you use with()
• You can make use of as.list() and with() so you can write sections of code referring to
named elements directly. It’s good to note this as an option. Different people will have
different preferences as to whether this is used.
7
The differential equation is more complicated for the logistic function than it was for the expo-
nential function, so one option is to remove the necessity for referring to elements inside vectors
and lists with the full notation:
Using with() and as.list() means we can refer to all the required elements by their names
only:
as.list() tells R to create a list object from what you give it
with() tells R to look “inside” the object you give it to find objects you refer to: looking inside the
Section ?? of the object.
logistic_fn <- function(t, state, parameters) { # You'll need a K in the parameters arg
with(as.list(c(state, parameters)), {
return(list(c(dN)))
})
You may prefer this, or you may prefer to assign your variables within the function - or even
continue to refer to them in full. Whatever you do, clarity and consistency will help both you and
anyone else who needs to work with your code.
dN
= αN there f ore N = N0 eαt
dt
In [ ]: exp(parms['alpha'])
# alpha was chosen so that at each whole timestep, the population doubles
t <- seq(1, 40, by = 1) # vector of times at which to calculate the population size
# these don't have to be the same as the timepoints as the ode() output was generated a
8
N_calc <- state_vars['N'] * 2^t # every day, the population doubles
require(ggplot2)
expplot <- expplot +
geom_point(data = pop_df, # specify different dataframe here
aes(y = N_calc, x = t)
, shape = 4
, size = 2
, colour = "red")
expplot
We directly calculate population size at a series of time points, and can see that it fits the curve
we generated using ode().