-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.Rmd
418 lines (307 loc) · 12.8 KB
/
README.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
---
output: github_document
---
# R package: simpopmods
An R package for generating **sim**ulated **pop**ulation **mod**els (IPMs and MPMs)
:herb::mushroom::pig2::seedling::cactus::deciduous_tree::water_buffalo:
This package is under active development - enter at you own risk
## About
This is an R package I have been developing for my PhD research to simulate population models. It uses integral projection models.
## Installation
How to install this package from GitHub
```{r}
#install.packages("devtools")
library(devtools)
#install_github("simonrolph/simpopmods")
library(simpopmods)
#install.packages("rsvg")
library(DiagrammeR)
library(ggplot2)
```
## Usage
### Define an IPM
Before you generate any simulated populations, you first need to define a template for your population model. This is done through a IPM descriptor object. You can generate a template for the IPM descriptor by using the `init_IPM_desc()` function.
An `simpopmods` IPM descriptor consists of
* Discrete states
* Continous states (which all share the same domain)
* Parameters
+ Name of parameter
+ maximum possible value
+ Minimum possible value
+ T/F as to whether it is sampled directly, or derived from a combination of other parameters
* Functions for generating parameters that are not sampled directly
* Demographic functions (equations of parameters)
* Kernels (consisting of demographic functions)
* Functions for the upper limit, lower limit and resolution of the kernel of the continuous state domain.
Here's one I made earlier:
```{r}
################ IPM_desc
states <- c("mature")
states_z <- c(T)
# $par_info
par_info <- data.frame(t(data.frame(
## survival
surv.int = c(par = "surv.int",min = -10,max = 4,samp = T),
surv.z = c(par = "surv.z",min = 0,max = 10,samp = T),
## flowering
flow.int = c(par = "flow.int",min = -10,max = 10,samp = T),
flow.z = c(par = "flow.z",min = 0,max = 10,samp = T),
## growth
grow.int = c(par = "grow.int",min = 0,max = 1,samp = T),
grow.z = c(par = "grow.z",min = 0,max = 1,samp = F), # constrained from 0 to 1
grow.sd = c(par = "grow.sd",min = 0.01,max = 0.5,samp = T),
## recruit size
rcsz.int = c(par = "rcsz.int",min = 0,max = 0,samp = F), # doesn't change
rcsz.sd = c(par = "rcsz.sd",min = 0.1,max = 0.5,samp = T),
## seed size
seed.int = c(par = "seed.int",min = 0,max = 100,samp = T),
seed.z = c(par = "seed.z",min = 0,max = 10,samp = T),
## recruitment probability
p.r = c(par = "p.r",min = 0,max = 0.99,samp = T) # constrained from 0 to 1
)),stringsAsFactors=F)
par_info$min <- as.numeric(par_info$min)
par_info$max <- as.numeric(par_info$max)
par_info$samp <- as.logical(par_info$samp)
# $par_fns
par_fns <- list(
rcsz.int = function(params){0},
grow.z = function(params){1 - params["grow.int"]}
)
# $demo_fns
demo_fns <- list(
## G_z1z - Growth function, given you are size z now returns the pdf of size z1 next time
grow = function(z1, z, params){
mu <- params["grow.int"] + params["grow.z"] * z # mean size next year
sig <- params["grow.sd"] # sd about mean
p.den.grow <- dnorm(z1, mean = mu, sd = sig) # pdf that you are size z1 given you were size z
return(p.den.grow)
},
## s_z Survival function, logistic regression
surv = function(z, params){
linear.p <- params["surv.int"] + params["surv.z"] * z # linear predictor
p <- pmin(1/(1+exp(-linear.p)),rep(0.99,length(z))) # logistic transformation to probability with constant cap
p <- 1/(1+exp(-linear.p))
return(p)
},
## p_bz Probability of flowering function, logistic regression
flow = function(z, params){
linear.p <- params["flow.int"] + params["flow.z"] * z # linear predictor
p <- 1/(1+exp(-linear.p)) # logistic transformation to probability
return(p)
},
## b_z Seed production function
seed = function(z, params){
N <- exp(params["seed.int"] + params["seed.z"] * z) # seed production of a size z plant
return(N)
},
## c_0z1 Recruit size pdf
rcsz = function(z1, params){
mu <- params["rcsz.int"]
sig <- params["rcsz.sd"]
p.deRecr <- dnorm(z1, mean = mu, sd = sig) # pdf of a size z1 recruit
return(p.deRecr)
}
)
kernels <- c("P","Fec")
kernel_fns <- init_nested_list(kernels,states)
# kernel_fns$P$ TO $ FROM
# SURVIVAL/GROWTH
kernel_fns$P$mature$mature <- function (z1, z, params) {
return(IPM_desc$demo_fns$surv(z, params) * IPM_desc$demo_fns$grow(z1, z, params))
}
# FECUNDITY
kernel_fns$Fec$mature$mature <- function (z1, z, params) {
#flowering * number of seeds * recruit survival * recruit size * seedbank splitter
return( IPM_desc$demo_fns$flow(z, params) * IPM_desc$demo_fns$seed(z, params) * params["p.r"] * IPM_desc$demo_fns$rcsz(z1, params))
}
# lower size limit to prevent eviction
limit_lower <- function(params, IPM_desc){
max_sd <- params["rcsz.sd"]+params["grow.sd"]
# returns the lower size limit of the kernel
# a value of 0.05 means that 5% of new recruits are evicted
qnorm(0.01,mean = 0, sd = max_sd)
}
#upper size limit to prevent eviction
limit_upper <- function(params,IPM_desc){
upper_lims <- seq(from = 2, to = 10, by = 1)
#Calculate the proportion of individuals wrongly evicted at a size with a size limit
fac1 <- IPM_desc$demo_fns$surv(upper_lims, params) # survival probability ~ z
#integrate(function(x) IPM_desc$demo_fns$grow(x, z, params), U, Inf)$value
inter <- function(x) {
integrate(function(x) IPM_desc$demo_fns$grow(x, x, params), x, Inf)$value
}
fac2 <- sapply(upper_lims,inter)
props <- fac1 * fac2
props[props>0.01] <- 0
upper_lim <- upper_lims[which.max(props)]
return(upper_lim)
}
kernel_res <- function(params,IPM_desc,units_per_sd = 25){
min_sd <- min(params["rcsz.sd"],params["grow.sd"])
return(units_per_sd/min_sd)
}
# IPM_desc object
IPM_desc <- list(
states = states,
states_z = states_z,
kernels = kernels,
par_info = par_info,
par_fns = par_fns,
demo_fns = demo_fns,
kernel_fns = kernel_fns,
limit_lower = limit_lower,
limit_upper = limit_upper,
kernel_res = kernel_res
)
```
Running `plot_diagram(IPM_desc = IPM_desc)` produce a diagram produced by `DiagrammR`. Use `DiagrammeR::render_graph(IPM_diagram)` to render the diagram.
```{r}
IPM_diagram <- plot_diagram(IPM_desc = IPM_desc)
#render_graph(IPM_diagram)
#export
library(DiagrammeRsvg)
library(rsvg)
IPM_diagram %>%
export_graph(
file_name = "IPM_descs/IPM_desc_basic_graph.svg",
file_type = "SVG",
width = 700,
height = 600)
```
![](IPM_descs/IPM_desc_basic_graph.svg)
### Create an IPM
Create a parameter set or use a sampler to sample parameter sets. I have been using an Adaptive Metropolis Algorithym with delayed rejection from the R package `FME`. This requires some informative priors.
Whenever I'm using parameter sets my convention is to refer to an incomplete parameter set (aka only the parameters that are sampled directly) as `params_ic` (as in parameters incomplete) and full parameter sets as `params_c` (as in, parameters complete).
```{r}
# a starting incomplete parameter set
params_ic <- c(
surv.int = 0,
surv.z = 2,
flow.int = 0.3,
flow.z = 0.1,
grow.int = 0.2,
grow.sd = 0.25,
rcsz.sd = 0.3,
seed.int = 2.35,
seed.z = 2.37,
p.r = 0.4
)
# create a complete parameter set
params_c <- make_full_params(params_ic = params_ic, IPM_desc = IPM_desc)
```
Once you have a parameter set you can create an IPM with:
```{r}
IPM <- make_kernels(params_c,IPM_desc)
```
This IPM can be discritised to an MPM by specifying a target stabe stage distribution for the MPM like so:
```{r}
qtiles <- c(0,0.2,0.4,0.7,1)
MPM <- make_MPM (params_c, IPM_desc, qtiles, submesh_res = 200)
```
lambda doesn't change in the discretisation process and you can use the `calc_dom_eig()` function to calculate the dominant eigenvalue from the mega kernel:
We also result with the same stabe stage distribuion as the qtiles that we specified above.
```{r, fig.height=4, fig.width=3}
plot_MIPM(IPM)
plot_MIPM(MPM)
```
```{r}
calc_dom_eig(IPM)$lambda
calc_dom_eig(MPM)$lambda
diff(qtiles)
as.numeric(calc_dom_eig(MPM)$w)
```
You can also plot the individual trajectories as IPM diagnosis with `plot_trajectories(MIPM,pop_n = 100,t_steps = 50)`
*This currently only works for IPMs, not discretised MPMs*
```{r}
plot_trajectories(IPM,pop_n = 100,t_steps = 50)
```
### Sampling parameter sets
I have been using an Adaptive Metrpolis algorithm implemented with `FME::modMCMC` with success.
First, define a function that returns -2 x log(likelihood) from an incomplete parameter set. It it also useful to define a similar prior function that also returns -2 x log(likelihood) from an incomplete parameter set.
```{r}
likelihood_function <- function(params_ic,IPM_desc){
# create the full parameter set
params_c <- make_full_params(params_ic = params_ic, IPM_desc = IPM_desc)
# make the IPM using simpopmods
IPM <- make_kernels(params_c,IPM_desc = IPM_desc)
# calculate lambda, at a fairly high tolerence for speed
eigens <- calc_dom_eig(IPM,tol = 0.005)
growth_rate <- eigens$lambda
# calculate log likelihood
likelihood <- sum(dnorm(growth_rate,mean = 1, sd = 0.1))
log_likelihood <- log(likelihood)
#return
return(-2*log_likelihood)
}
#check it correctly gives a likelihood value
likelihood_function(params_ic,IPM_desc = IPM_desc)
#Here's an example prior function for the parameters on the survival function:
IPM_prior_fn <- function(params_ic){
params <- as.list(params_ic)
log_likelihood <- log(dnorm(params$surv.int,mean = 1, sd = 1)) #surv int
log_likelihood <- log_likelihood + log(dnorm(params$surv.z,mean = 2, sd = 2)) # surv.z
return(-2*log_likelihood)
}
```
Run the sampler
```{r, fig.height=8, fig.width=8}
#load in the FME library
library(FME)
#extract the upper and lower limits from IPM desc to use as arguments for the modMCMC function
lower <- IPM_desc$par_info$min[IPM_desc$par_info$samp]
upper <- IPM_desc$par_info$max[IPM_desc$par_info$samp]
# jump is the starting step size
jump <- (upper-lower)/100
start_time <- Sys.time()
# run the sampler, use ?modMCMC for more information, the documentation is quite good.
samples <- modMCMC(f = likelihood_function, # -2*log likelihood function
p = params_ic, # starting parameter set
IPM_desc = IPM_desc,
niter = 200, # number of iterations
updatecov = 500, # how many iterations after which to update the covariance matrix
burninlength = 0, # how many iterations to keep updating covariance matrix
lower = lower, # lower bounds
upper = upper, # upper bounds
jump = jump,
ntrydr = 3 # number of tries (delayed rejection)
)
# how long did this take?
Sys.time() - start_time
#extract the parameter sets from the MCMC object and generate a full set of parameters to include those that cwere not sampled directly
param_sets_ic <- samples$pars
param_sets <- t(apply(param_sets_ic,FUN = make_full_params,1,IPM_desc=IPM_desc))
# use unique() to remove cases where the chain failed to jump to a new parameter set
param_sets <- as.data.frame(unique(param_sets))
# pariwise plot
pairs(param_sets,pch = ".")
```
Calculate lambda from the parameter sets to see that lambda is ~ 1 (or at least converging towards in this short run)
```{r}
# apply function to go throug heach parameter set, construct and IPM and calculated lambda.
param_sets$lambda <- apply(param_sets,
FUN = function(params_c,IPM_desc){
IPM <- simpopmods::make_kernels(params_c,IPM_desc)
return(calc_dom_eig(IPM)$lambda)
},
MARGIN = 1,
IPM_desc = IPM_desc
)
# a quick little plot
plot(param_sets$lambda)
```
That's just a taster. You need a lots of burn in and thinning to remove autocorrelation to generate anything vaguely useful.
## Tests
This R package has tests inplemented with R package `testthat`
These test to make sure that:
* Lambda does not change during discretisation from IPM to MPM
* Stable stage distribution of resulting MPM is the same as the target specified quantiles
The tests uses three different IPM descriptors.
## Contact
Simon Rolph - `[email protected]`
## Related packages
* [popbio](https://github.com/cstubben/popbio) - popbio is an R package for modeling population growth rates using age- or stage-classified matrix models. The package consists mainly of methods described in Hal Caswell's Matrix Population Models (2001) and Morris and Doak's Quantitative Conservation Biology (2002).
* [Rage](https://github.com/jonesor/Rage)
* [Rcompadre](https://github.com/jonesor/Rcompadre)
## IPM book
[Data-driven Modelling of Structured Populations](http://www.springer.com/gb/book/9783319288918) :star::star::star::star::star:
## Appendix