Growth predictions in biogrowth

library(biogrowth)
library(tidyverse)
#> ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
#> ✔ dplyr     1.1.1     ✔ readr     2.1.4
#> ✔ forcats   1.0.0     ✔ stringr   1.5.0
#> ✔ ggplot2   3.4.2     ✔ tibble    3.2.1
#> ✔ lubridate 1.9.2     ✔ tidyr     1.3.0
#> ✔ purrr     1.0.1     
#> ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
#> ✖ dplyr::filter() masks stats::filter()
#> ✖ dplyr::lag()    masks stats::lag()
#> ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

The function predict_growth()

Growth predictions in biogrowth are done using the predict_growth() function. It provides a unified interface for making predictions with a variety of modeling approaches. This vignette provides a detailed description of the arguments of predict_growth() and how they can be used to make different kinds of model predictions. For a detailed description of the mathematical models available in the package and the computational methods used, please check the relevant vignette.

The predict_growth() function has 6 arguments, as well as the ... argument to pass additional arguments to the functions used for making the predictions. The prediction is defined by the following 6 arguments:

In the following sections, we will describe how these arguments can define different modeling approaches.

Apart from these arguments, the function includes check. When check=TRUE (default), the function performs several checks of the models (names of the parameters, completeness of the model…) before doing any calculation, returning a warning or error if any inconsistency is detected.

Because the growth of population often spans several orders of magnitude, the models implemented in biogrowth are often described in terms of logarithms. The predict_growth() function allows the definition of different bases for the logarithm both for the population size and the specific growth rate (\(\mu\)). This is a common point of confusion when describing population growth, so we encourage the reader to check the specific vignette on the subject.

Growth predictions under constant environmental conditions

Basic growth predictions

The argument environment defines the type of model that predict_growth() will use to calculate the model predictions. When environment="constant", calculations are calculated based only on primary models. The model equation and the values of the model parameters are defined through the argument primary_model. It must be a named list with one entry called "model" that defines the model equation. The keys identifying the types of model can be retrieved by calling primary_model_data().

primary_model_data()
#> [1] "modGompertz" "Baranyi"     "Trilinear"   "Logistic"    "Richards"

In this example, we will use the modified Baranyi model.

my_model <- "Baranyi"

Then, the values of the model parameters are defined using additional entries in the list. These must be named according to keys defined internally within the package. These (as well as other meta-information on the model) can be retrieved passing the model key to primary_model_data()

primary_model_data(my_model)$pars
#> [1] "logN0"   "logNmax" "mu"      "lambda"

Here we can see that the Baranyi model requires the definition of the logarithm of the initial population size (logN0), the maximum population size in the stationary phase (logNmax), the growth rate during the exponential growth phase mu and the duration of the lag phase lambda. In this example, we will assign logN0=1, logNmax=7, mu=0.2 and lambda=20.

primary_model <- list(model = my_model, logN0 = 1, logNmax = 7, mu = .2, lambda = 20)

For additional details on the model equations and the interpretation of the different model parameters, the reader is referred to the specific vignette about mathematical methods.

For model predictions under constant environmental conditions, the simulations are calculated by solving the algebraic form of the model equation. The time points were it is calculated must be defined using the times argument, which is a numeric vector of any length. In this case, we will use a regular vector from 0 to 100 with 1,000 uniformly spaced points.

my_times <- seq(0, 100, length = 1000)

Once we have defined these arguments, we can call the predict_growth() function

my_prediction <- predict_growth(environment = "constant", my_times, primary_model)

The function returns an instance of GrowthPrediction with several S3 methods to ease the interpretation of the growth prediction. The print() method shows the model selected and the values of the model parameters used for the calculations. It also shows the base of the logarithms used for the definition of the model parameters and the population size. This point will be discussed below.

my_prediction
#> Growth prediction based on primary models
#> 
#> Growth model: Baranyi 
#> 
#> Parameters of the primary model:
#>   logN0 logNmax      mu  lambda 
#>     1.0     7.0     0.2    20.0 
#> 
#> Parameter mu defined in log-10 scale
#> Population size defined in log-10 scale

The values of the model parameters can also be retrieved using the coef() method

coef(my_prediction)
#>   logN0 logNmax      mu  lambda 
#>     1.0     7.0     0.2    20.0

It also implements a plot() method to visualize the predicted growth curve

plot(my_prediction)

Note that the y-axis represents the logarithm of the population size. By default, the label of the y-axis shows within parenthesis the base of the logarithm. This, as well as many other aesthetics of the plot, can be edited by passing additional arguments to plot

plot(my_prediction,
     label_y1 = "log10 of the population size",
     label_x = "Time (years)",
     line_size = 2,
     line_col = "red",
     line_type = "dotted")

For a detailed description of the arguments admitted by the function, please check its help page (e.g. with ?plot.GrowthPrediction). Note that plot() returns an instance of ggplot, so it can be edited using additional layers

plot(my_prediction) + theme_gray() + xlab("Time (years)")

One aspect that is often of interest when analyzing the growth of populations is the time required to reach a value of the population size. In biogrowth, this can be calculated using time_to_size(). This function can take as arguments an instance of GrowthPrediction and a population size (in log units) and returns the time required to reach that population size.

time_to_size(my_prediction, 3)
#> [1] 29.97839

If the population size is not reached during the simulation, the function returns NA.

time_to_size(my_prediction, 9)
#> [1] NA

Alternative definitions of model parameters

In order to facilitate the definition of different types of model, predict_growth() accepts alternative definitions of the model parameters. Namely, * the initial population size can also be described either in log-scale (logN0) or without a log transformation (N0), * the maximum population size in the stationary phase can also be described either in log-scale (logNmax) or without a log transformation (Nmax), * the growth rate during the exponential phase can be named mu_opt instead of mu * the duration of the lag phase can be defined in terms of Q0 instead of lambda. In this case, the duration of the lag phase is calculated as \(\lambda=1/\left( \exp(\mu \cdot \lambda) - 1 \right)\), as per the Baranyi model (see vignette about the mathematical models for the derivation). Note that this calculation is included in biogrowth in the function lambda_to_Q0().

Therefore, this model definition is equivalent to the one defined above:

primary_model <- list(model = my_model, logN0 = 1, logNmax = 7, mu = .2, lambda = 20)

Q0 <- lambda_to_Q0(lambda = 20, mu = .2)

equivalent_pars <- list(model = my_model, N0 = 10^1, Nmax = 10^7, mu_opt = .2, 
                        Q0 = Q0)

equivalent_prediction <- predict_growth(environment = "constant", my_times, 
                                        equivalent_pars)

plot(equivalent_prediction)

Definition of different logarithmic bases

As already mentioned above, growth simulations often cover several orders of magnitude, making it more convenient to express the results in logarithmic scale. By default, all the calculations in biogrowth are done in log10 scale. Nevertheless, this can be modified using the arguments logbase_mu and logbase_logN. We believe the relevance of these bases is a common source of confusion when modeling population growth (although the population size has no units, its logarithmic base affects the calculations), so the reader is strongly encouraged to check the vignette dedicated to the unit system.

The base of the logarithm of the population size is defined by logbase_logN. By default, the calculations are done in log10 scale, but any numeric value can be passed to this argument. One point worth highlighting here is that the model parameters logN0 and logNmax are defined in the same scale as the population size. Therefore, if the primary model is defined based on this parameters, the growth curve is exactly the same, regardless of the base of \(\log N\). The only difference is the label of the y-axis.

primary_model <- list(model = my_model, logN0 = 1, logNmax = 7, mu = .2, lambda = 20)
prediction_base_e <- predict_growth(environment = "constant", my_times, 
                                        primary_model,
                                        logbase_logN = exp(1))

prediction_base_e
#> Growth prediction based on primary models
#> 
#> Growth model: Baranyi 
#> 
#> Parameters of the primary model:
#>   logN0 logNmax      mu  lambda 
#>     1.0     7.0     0.2    20.0 
#> 
#> Parameter mu defined in log-e scale
#> Population size defined in log-e scale
plot(prediction_base_e)

However, if the primary model is defined in terms of \(N_0\) and/or \(N_{max}\), the growth curve will be affected.

primary_model <- list(model = my_model, N0 = 1, Nmax = 1e7, mu = .2, lambda = 20)
prediction_base_e <- predict_growth(environment = "constant", my_times, 
                                        primary_model,
                                        logbase_logN = exp(1))

plot(prediction_base_e)

The base of the logarithm for the parameter \(\mu\) is defined by the argument logbase_mu. By default, this parameter uses the same base as the population size. Nonetheless, it can accept any numeric value. Changes in this base will be reflected both in the print method and the growth curve:

primary_model <- list(model = my_model, logN0 = 1, logNmax = 7, mu = .2, lambda = 20)
prediction_mu_e <- predict_growth(environment = "constant", my_times, 
                                        primary_model,
                                        logbase_mu = exp(1))

prediction_mu_e
#> Growth prediction based on primary models
#> 
#> Growth model: Baranyi 
#> 
#> Parameters of the primary model:
#>   logN0 logNmax      mu  lambda 
#>     1.0     7.0     0.2    20.0 
#> 
#> Parameter mu defined in log-e scale
#> Population size defined in log-10 scale
plot(prediction_mu_e)

As detailed in the vignette dedicated to the unit system, the growth rate for different log-bases of \(\mu\) can be calculated as \(\mu_b = \mu_a \cdot \log_b a\)

primary_model <- list(model = my_model, logN0 = 1, logNmax = 7, 
                      mu = .2 * log(10), 
                      lambda = 20)
prediction_mu_e <- predict_growth(environment = "constant", my_times, 
                                        primary_model,
                                        logbase_mu = exp(1)
                                  )

plot(prediction_mu_e)

Note that time_to_size() also accepts a logbase_logN argument. By default, this argument takes NULL, so the function uses the same logbase as the growth prediction. For instance, if we use prediction_base_e that used natural base for the calculation, a size=5 would mean a size of exp(5) units.

print(prediction_base_e)
#> Growth prediction based on primary models
#> 
#> Growth model: Baranyi 
#> 
#> Parameters of the primary model:
#>     N0   Nmax     mu lambda 
#>  1e+00  1e+07  2e-01  2e+01 
#> 
#> Parameter mu defined in log-e scale
#> Population size defined in log-e scale
time_to_size(prediction_base_e, size = 5)
#> [1] 44.96689

Then, if we want to define the population in log-base10, we would need to set logbase_logN=10.

time_to_size(prediction_base_e, log10(exp(5)), logbase_logN = 10)
#> [1] 44.96689

Growth predictions under dynamic environmental conditions

The function predict_growth() can account for the effect of changes in the environmental conditions on parameter \(\mu\). This behaviour is triggered setting environment="dynamic", and requires the definition of additional arguments:

The dynamic environmental conditions are defined using a tibble (or data.frame). It must have a column defining the elapsed time and as many additional columns as needed for each environmental factor. By default, the column defining the time must be called time, although this can be changed using the formula argument. For the simulations, the value of the environmental conditions at time points not included in env_conditions is calculated by linear interpolation.

In our simulation we will consider two environmental factors: temperature and pH. We can define their variation using this tibble. To illustrate the use of the formula argument, we will use Time for the column describing the elapsed time.

my_conditions <- tibble(Time = c(0, 5, 40),
                         temperature = c(20, 30, 35),
                         pH = c(7, 6.5, 5)
                         )

Then, the simulations would consider this temperature profile

ggplot(my_conditions) + 
  geom_line(aes(x = Time, y = temperature))

And this pH profile

ggplot(my_conditions) + 
  geom_line(aes(x = Time, y = pH))

We could define smoother profiles using additional rows. For time points outside of the range defined in env_conditions, the value at the closes extreme is used (rule=2 from approx function).

For dynamic conditions, biogrowth uses the Baranyi growth model as primary model. This model requires the definition of two model parameters: the specific growth rate at optimum conditions (mu_opt) and the maximum population size (Nmax). Moreover, the initial values of the population size (N0) and the theoretical substance \(Q\) (Q0) must be defined. Note that \(Q_0\) is related to the duration of the lag phase under isothermal conditions by the identity \(\lambda = \ln \left( 1 + 1/q_0 \right)/\mu_{max}\). For the predict_dynamic_growth() function, all variables must be defined in a single list:

my_primary <- list(mu_opt = .9,
             Nmax = 1e8,
             N0 = 1e0,
             Q0 = 1e-3)

The next step is the definition of the secondary models. In biogrowth, the effect of changes on the environmental conditions in \(\mu\) are described based on the gamma concept. In this approach, the effect of each environmental condition (\(X_i\)) is considered as a correction factor with respect to the one observed under optimal conditions \(\mu_{opt}\). Hence, this effect is described by a “gamma” function (\(\gamma_i \left(X_i \right)\)) that takes values between 0 and 1. For additional details on the mathematical methods, the reader is referred to the specific vignette about mathematical methods.

\[ \mu = \mu_{opt} \cdot \gamma_1(X_1) \cdot \gamma_2(X_2) \cdot ... \cdot \gamma_n(X_n) \]

Therefore, in this example, we need to define one secondary model per environmental condition. This must be done using a list. This list should contain the type of gamma model as well as the model parameters for each environmental condition. The function secondary_model_data() can aid in the definition of the secondary models. Calling it without any arguments returns the available model keys.

secondary_model_data()
#> [1] "CPM"           "Zwietering"    "fullRatkowsky"

For instance, we will define a gamma-type model for temperature as defined by Zwietering et al. (1992). This is done by including an item called model in the list and assigning it the value "Zwietering". Then, we define the values of the model parameters. In a similar way as for primary models, passing a model to secondary_model_data() returns meta-information of the model, including the parameter keys

secondary_model_data("Zwietering")$pars
#> [1] "xmin" "xopt" "n"

In this case, we need to define parameters xmin, xopt and n (for a detailed description of the modeling approach, please check the relevant vignette). We define them using individual entries in the list:

sec_temperature <- list(model = "Zwietering",
                        xmin = 25,
                        xopt = 35,
                        n = 1)

Next, we will define a CPM model for the effect of pH. Note that the model selection is for illustration purposes, not based on any scientific knowledge. First of all, we need to set the item model to "CPM". Then, we need to define the model parameters (note that this model also needs xmax).

sec_pH <- list(model = "CPM",
               xmin = 5.5,
               xopt = 6.5,
               xmax = 7.5,
               n = 2)

The final step for the definition of the gamma-type secondary model is gathering all the individual models together in a single list and assigning them to environmental factors. Each element on the list must be named using the same column names as in env_conditions. Before, we had used the column names temperature and pH. Thus

my_secondary <- list(
    temperature = sec_temperature,
    pH = sec_pH
    )

The final argument is the time points where to make the calculations. We can use a numeric vector with 1000 points between 0 and 50 for this:

my_times <- seq(0, 50, length = 1000)

Once we have defined every argument, we can call the predict_dynamic_growth() function. Because we are using Time to define the elapsed time in env_conditions, we must also define the .~Time in the formula argument.

dynamic_prediction <- predict_growth(environment = "dynamic", 
                                     my_times, 
                                     my_primary, 
                                     my_secondary,
                                     my_conditions,
                                     formula = . ~ Time
                                     )

The function returns an instance of GrowthPrediction with the results of the simulation. It includes several S3 methods to ease the interpretation of the results. The print method shows the models used, as well as the environmental factors considered in the simulation. The print method also shows the log-bases used for the calculations. By default, the functions uses log10 base, but this can be modified using logbase_mu and logbase_logN as in simulations for constant environmental conditions.

dynamic_prediction
#> Growth prediction under dynamic environmental conditions
#> 
#> Environmental factors included: temperature, pH 
#> 
#> Parameters of the Baranyi primary model:
#> mu_opt   Nmax     N0     Q0 
#>  9e-01  1e+08  1e+00  1e-03 
#> 
#> Parameter mu defined in log-10 scale
#> 
#> Population size defined in log-10 scale
#> 
#> Secondary model for temperature:
#>        model         xmin         xopt            n 
#> "Zwietering"         "25"         "35"          "1" 
#> 
#> Secondary model for pH:
#> model  xmin  xopt  xmax     n 
#> "CPM" "5.5" "6.5" "7.5"   "2"

Again, the class includes plot methods to visualize the growth curve.

plot(dynamic_prediction)

This plot can include the variation of a single environmental factor alongside the growth curve. As well as above, several aesthetics of the plot can be modified passing additional arguments to the plotting function. Please check the help page of plot.GrowthPrediction for a complete list of options.

plot(dynamic_prediction,
     add_factor = "temperature",
     line_col2 = "steelblue",
     line_col = "magenta",
     label_y2 = "Temperature (ºC)")

Similar to predictions under constant environmental conditions, time_to_size can estimate the time required to reach a given population size:

time_to_size(dynamic_prediction, 3)
#> [1] 16.36868