{"id":27204500,"url":"https://github.com/simonrolph/simpopmods","last_synced_at":"2025-07-16T12:33:05.801Z","repository":{"id":77839628,"uuid":"129237387","full_name":"simonrolph/simpopmods","owner":"simonrolph","description":"R package for creating simulated integral/matrix population models. Developed as part of my PhD","archived":false,"fork":false,"pushed_at":"2020-01-29T16:18:53.000Z","size":2865,"stargazers_count":3,"open_issues_count":5,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-02T09:32:32.517Z","etag":null,"topics":["demography","ecology","ipm","models","mpm","package","population","r","simulated"],"latest_commit_sha":null,"homepage":"","language":"R","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/simonrolph.png","metadata":{"files":{"readme":"README.Rmd","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2018-04-12T10:58:18.000Z","updated_at":"2024-04-24T12:04:23.000Z","dependencies_parsed_at":"2023-04-19T14:23:40.806Z","dependency_job_id":null,"html_url":"https://github.com/simonrolph/simpopmods","commit_stats":{"total_commits":11,"total_committers":1,"mean_commits":11.0,"dds":0.0,"last_synced_commit":"c7dbbb303d3be1f64d2839e62cc99f6a02c8eac3"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/simonrolph/simpopmods","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonrolph%2Fsimpopmods","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonrolph%2Fsimpopmods/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonrolph%2Fsimpopmods/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonrolph%2Fsimpopmods/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/simonrolph","download_url":"https://codeload.github.com/simonrolph/simpopmods/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/simonrolph%2Fsimpopmods/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265509407,"owners_count":23779351,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["demography","ecology","ipm","models","mpm","package","population","r","simulated"],"created_at":"2025-04-09T22:58:32.925Z","updated_at":"2025-07-16T12:33:05.762Z","avatar_url":"https://github.com/simonrolph.png","language":"R","readme":"---\noutput: github_document\n---\n\n# R package: simpopmods\n\nAn R package for generating **sim**ulated **pop**ulation **mod**els (IPMs and MPMs)\n\n:herb::mushroom::pig2::seedling::cactus::deciduous_tree::water_buffalo:\n\nThis package is under active development - enter at you own risk\n\n## About\n\nThis is an R package I have been developing for my PhD research to simulate population models. It uses integral projection models.\n\n## Installation\n\nHow to install this package from GitHub\n\n```{r}\n#install.packages(\"devtools\")\nlibrary(devtools)\n\n#install_github(\"simonrolph/simpopmods\")\nlibrary(simpopmods)\n\n#install.packages(\"rsvg\")\nlibrary(DiagrammeR)\n\nlibrary(ggplot2)\n```\n\n## Usage\n\n### Define an IPM\n\nBefore 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.\n\nAn `simpopmods` IPM descriptor consists of\n\n * Discrete states\n * Continous states (which all share the same domain)\n * Parameters\n   + Name of parameter\n   + maximum possible value\n   + Minimum possible value\n   + T/F as to whether it is sampled directly, or derived from a combination of other parameters\n * Functions for generating parameters that are not sampled directly\n * Demographic functions (equations of parameters)\n * Kernels (consisting of demographic functions)\n * Functions for the upper limit, lower limit and resolution of the kernel of the continuous state domain.\n \nHere's one I made earlier:\n \n```{r}\n################ IPM_desc\n\nstates \u003c- c(\"mature\")\nstates_z \u003c- c(T)\n\n# $par_info\npar_info \u003c- data.frame(t(data.frame(\n  ## survival\n  surv.int  =  c(par = \"surv.int\",min = -10,max = 4,samp = T),\n  surv.z    =   c(par = \"surv.z\",min = 0,max = 10,samp = T),\n\n  ## flowering\n  flow.int  = c(par = \"flow.int\",min = -10,max = 10,samp = T),\n  flow.z    =   c(par = \"flow.z\",min = 0,max = 10,samp = T),\n\n  ## growth\n  grow.int  =   c(par = \"grow.int\",min = 0,max = 1,samp = T),\n  grow.z    =   c(par = \"grow.z\",min = 0,max = 1,samp = F), # constrained from 0 to 1\n  grow.sd   =   c(par = \"grow.sd\",min = 0.01,max = 0.5,samp = T),\n\n  ## recruit size\n  rcsz.int  =   c(par = \"rcsz.int\",min = 0,max = 0,samp = F), # doesn't change\n  rcsz.sd   =   c(par = \"rcsz.sd\",min = 0.1,max = 0.5,samp = T),\n\n  ## seed size\n  seed.int  =   c(par = \"seed.int\",min = 0,max = 100,samp = T),\n  seed.z    =   c(par = \"seed.z\",min = 0,max = 10,samp = T),\n\n  ## recruitment probability\n  p.r       =   c(par = \"p.r\",min = 0,max = 0.99,samp = T) # constrained from 0 to 1\n)),stringsAsFactors=F)\n\npar_info$min \u003c- as.numeric(par_info$min)\npar_info$max \u003c- as.numeric(par_info$max)\npar_info$samp \u003c- as.logical(par_info$samp)\n\n# $par_fns\npar_fns \u003c- list(\n  rcsz.int = function(params){0},\n  grow.z = function(params){1 - params[\"grow.int\"]}\n)\n\n# $demo_fns\ndemo_fns \u003c- list(\n  ## G_z1z -  Growth function, given you are size z now returns the pdf of size z1 next time\n  grow = function(z1, z, params){\n    mu \u003c- params[\"grow.int\"] + params[\"grow.z\"] * z           # mean size next year\n    sig \u003c- params[\"grow.sd\"]                                 # sd about mean\n    p.den.grow \u003c- dnorm(z1, mean = mu, sd = sig)             # pdf that you are size z1 given you were size z\n    return(p.den.grow)\n  },\n\n  ## s_z Survival function, logistic regression\n  surv = function(z, params){\n\n    linear.p \u003c- params[\"surv.int\"] + params[\"surv.z\"] * z  # linear predictor\n    p \u003c- pmin(1/(1+exp(-linear.p)),rep(0.99,length(z))) # logistic transformation to probability with constant cap\n    p \u003c- 1/(1+exp(-linear.p))\n    return(p)\n  },\n\n  ## p_bz Probability of flowering function, logistic regression\n  flow = function(z, params){\n    linear.p \u003c- params[\"flow.int\"] + params[\"flow.z\"] * z      # linear predictor\n    p \u003c- 1/(1+exp(-linear.p))                                # logistic transformation to probability\n    return(p)\n  },\n\n  ## b_z Seed production function\n  seed = function(z, params){\n    N \u003c- exp(params[\"seed.int\"] + params[\"seed.z\"] * z)   # seed production of a size z plant\n    return(N)\n  },\n\n  ## c_0z1 Recruit size pdf\n  rcsz = function(z1, params){\n    mu \u003c- params[\"rcsz.int\"]\n    sig \u003c- params[\"rcsz.sd\"]\n    p.deRecr \u003c- dnorm(z1, mean = mu, sd = sig)              # pdf of a size z1 recruit\n    return(p.deRecr)\n  }\n)\n\nkernels \u003c- c(\"P\",\"Fec\")\n\nkernel_fns \u003c- init_nested_list(kernels,states)\n# kernel_fns$P$   TO   $   FROM\n\n# SURVIVAL/GROWTH\nkernel_fns$P$mature$mature \u003c- function (z1, z, params) {\n  return(IPM_desc$demo_fns$surv(z, params) * IPM_desc$demo_fns$grow(z1, z, params))\n}\n\n# FECUNDITY\nkernel_fns$Fec$mature$mature \u003c- function (z1, z, params) {\n  #flowering * number of seeds * recruit survival * recruit size * seedbank splitter\n  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))\n}\n\n# lower size limit to prevent eviction\nlimit_lower \u003c- function(params, IPM_desc){\n  max_sd \u003c- params[\"rcsz.sd\"]+params[\"grow.sd\"]\n  # returns the lower size limit of the kernel\n  # a value of 0.05 means that 5% of new recruits are evicted\n\n  qnorm(0.01,mean = 0, sd = max_sd)\n}\n\n#upper size limit to prevent eviction\nlimit_upper \u003c- function(params,IPM_desc){\n\n  upper_lims \u003c- seq(from = 2, to = 10, by = 1)\n\n  #Calculate the proportion of individuals wrongly evicted at a size with a size limit\n  fac1 \u003c- IPM_desc$demo_fns$surv(upper_lims, params) # survival probability ~ z\n  #integrate(function(x) IPM_desc$demo_fns$grow(x, z, params), U, Inf)$value\n\n  inter \u003c- function(x) {\n    integrate(function(x) IPM_desc$demo_fns$grow(x, x, params), x, Inf)$value\n  }\n\n  fac2 \u003c- sapply(upper_lims,inter)\n\n  props \u003c- fac1 * fac2\n  props[props\u003e0.01] \u003c- 0\n  upper_lim \u003c- upper_lims[which.max(props)]\n\n  return(upper_lim)\n}\n\nkernel_res \u003c- function(params,IPM_desc,units_per_sd = 25){\n  min_sd \u003c- min(params[\"rcsz.sd\"],params[\"grow.sd\"])\n  return(units_per_sd/min_sd)\n}\n\n# IPM_desc object\nIPM_desc \u003c- list(\n  states = states,\n  states_z = states_z,\n  kernels = kernels,\n  par_info = par_info,\n  par_fns = par_fns,\n  demo_fns = demo_fns,\n  kernel_fns = kernel_fns,\n  limit_lower = limit_lower,\n  limit_upper = limit_upper,\n  kernel_res = kernel_res\n)\n```\n \nRunning `plot_diagram(IPM_desc = IPM_desc)` produce a diagram produced by `DiagrammR`. Use `DiagrammeR::render_graph(IPM_diagram)` to render the diagram.\n\n```{r}\nIPM_diagram \u003c- plot_diagram(IPM_desc = IPM_desc)\n#render_graph(IPM_diagram)\n\n#export\nlibrary(DiagrammeRsvg)\nlibrary(rsvg)\nIPM_diagram %\u003e%\n  export_graph(\n  file_name = \"IPM_descs/IPM_desc_basic_graph.svg\",\n  file_type = \"SVG\",\n  width = 700,\n  height = 600)\n\n```\n\n![](IPM_descs/IPM_desc_basic_graph.svg)\n \n### Create an IPM\n\nCreate 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.\n\nWhenever 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).\n\n```{r}\n# a starting incomplete parameter set\nparams_ic \u003c- c(\n  surv.int  =  0,\n  surv.z    =   2,\n  flow.int  = 0.3,\n  flow.z    =   0.1,\n  grow.int  =   0.2,\n  grow.sd   =   0.25,\n  rcsz.sd   =   0.3,\n  seed.int  =   2.35,\n  seed.z    =   2.37,\n  p.r       =   0.4\n)\n\n# create a complete parameter set\nparams_c \u003c- make_full_params(params_ic = params_ic, IPM_desc = IPM_desc)\n```\n\nOnce you have a parameter set you can create an IPM with:\n\n```{r}\nIPM \u003c- make_kernels(params_c,IPM_desc)\n```\n\nThis IPM can be discritised to an MPM by specifying a target stabe stage distribution for the MPM like so:\n\n```{r}\nqtiles \u003c- c(0,0.2,0.4,0.7,1)\nMPM \u003c- make_MPM (params_c, IPM_desc, qtiles, submesh_res = 200)\n```\n\nlambda 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:\n\nWe also result with the same stabe stage distribuion as the qtiles that we specified above.\n\n```{r, fig.height=4, fig.width=3}\nplot_MIPM(IPM)\nplot_MIPM(MPM)\n```\n\n```{r}\ncalc_dom_eig(IPM)$lambda\ncalc_dom_eig(MPM)$lambda\n\ndiff(qtiles)\nas.numeric(calc_dom_eig(MPM)$w)\n```\n\nYou can also plot the individual trajectories as IPM diagnosis with `plot_trajectories(MIPM,pop_n = 100,t_steps = 50)`\n\n*This currently only works for IPMs, not discretised MPMs*\n\n```{r}\nplot_trajectories(IPM,pop_n = 100,t_steps = 50)\n```\n\n### Sampling parameter sets\n\nI have been using an Adaptive Metrpolis algorithm implemented with `FME::modMCMC` with success.\n\nFirst, 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.\n\n```{r}\nlikelihood_function \u003c- function(params_ic,IPM_desc){\n  # create the full parameter set\n  params_c \u003c- make_full_params(params_ic = params_ic, IPM_desc = IPM_desc)\n  \n  # make the IPM using simpopmods\n  IPM \u003c- make_kernels(params_c,IPM_desc = IPM_desc)\n  \n  # calculate lambda, at a fairly high tolerence for speed\n  eigens \u003c- calc_dom_eig(IPM,tol = 0.005)\n  growth_rate \u003c- eigens$lambda\n  \n  # calculate log likelihood\n  likelihood \u003c- sum(dnorm(growth_rate,mean = 1, sd = 0.1))\n  log_likelihood \u003c- log(likelihood)\n  \n  #return\n  return(-2*log_likelihood)\n}\n\n#check it correctly gives a likelihood value\nlikelihood_function(params_ic,IPM_desc = IPM_desc)\n\n#Here's an example prior function for the parameters on the survival function:\nIPM_prior_fn \u003c- function(params_ic){\n  params \u003c- as.list(params_ic)\n  log_likelihood \u003c- log(dnorm(params$surv.int,mean = 1, sd = 1)) #surv int\n  log_likelihood \u003c- log_likelihood + log(dnorm(params$surv.z,mean = 2, sd = 2)) # surv.z\n  return(-2*log_likelihood)\n}\n\n```\n\nRun the sampler\n\n```{r, fig.height=8, fig.width=8}\n#load in the FME library\nlibrary(FME)\n\n#extract the upper and lower limits from IPM desc to use as arguments for the modMCMC function\nlower \u003c- IPM_desc$par_info$min[IPM_desc$par_info$samp]\nupper \u003c- IPM_desc$par_info$max[IPM_desc$par_info$samp]\n\n# jump is the starting step size\njump \u003c- (upper-lower)/100\n\nstart_time \u003c- Sys.time()\n# run the sampler, use ?modMCMC for more information, the documentation is quite good.\nsamples \u003c- modMCMC(f = likelihood_function, # -2*log likelihood function\n                       p = params_ic, # starting parameter set\n                       IPM_desc = IPM_desc,\n                       niter = 200, # number of iterations\n                       updatecov = 500, # how many iterations after which to update the covariance matrix\n                       burninlength = 0, # how many iterations to keep updating covariance matrix\n                       lower = lower, # lower bounds\n                       upper = upper, # upper bounds\n                       jump = jump,\n                       ntrydr = 3 # number of tries (delayed rejection)\n                      )\n\n# how long did this take?\nSys.time() - start_time\n\n#extract the parameter sets from the MCMC object and generate a full set of parameters to include those that cwere not sampled directly\nparam_sets_ic \u003c- samples$pars\nparam_sets \u003c- t(apply(param_sets_ic,FUN = make_full_params,1,IPM_desc=IPM_desc))\n\n# use unique() to remove cases where the chain failed to jump to a new parameter set\nparam_sets \u003c- as.data.frame(unique(param_sets))\n\n# pariwise plot\npairs(param_sets,pch = \".\")\n```\n\nCalculate lambda from the parameter sets to see that lambda is ~ 1 (or at least converging towards in this short run)\n\n```{r}\n# apply function to go throug heach parameter set, construct and IPM and calculated lambda.\nparam_sets$lambda \u003c- apply(param_sets,\n        FUN = function(params_c,IPM_desc){\n    IPM \u003c- simpopmods::make_kernels(params_c,IPM_desc)\n    return(calc_dom_eig(IPM)$lambda)\n  },\n  MARGIN = 1,\n  IPM_desc = IPM_desc\n)\n\n# a quick little plot\nplot(param_sets$lambda)\n```\n\nThat's just a taster. You need a lots of burn in and thinning to remove autocorrelation to generate anything vaguely useful.\n\n## Tests\n\nThis R package has tests inplemented with R package `testthat`\n\nThese test to make sure that:\n\n * Lambda does not change during discretisation from IPM to MPM\n * Stable stage distribution of resulting MPM is the same as the target specified quantiles\n \nThe tests uses three different IPM descriptors.\n \n## Contact\n\nSimon Rolph - `srolph1@sheffield.ac.uk`\n\n## Related packages\n\n * [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).\n * [Rage](https://github.com/jonesor/Rage)\n * [Rcompadre](https://github.com/jonesor/Rcompadre)\n\n## IPM book\n\n[Data-driven Modelling of Structured Populations](http://www.springer.com/gb/book/9783319288918) :star::star::star::star::star: \n\n\n## Appendix\n\n\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimonrolph%2Fsimpopmods","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsimonrolph%2Fsimpopmods","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsimonrolph%2Fsimpopmods/lists"}