This vignette details the structure and construction of the incidence_fit and incidence_fit_list classes, which are produced by the fit() and fit_optim_split() functions, respectively. By the end of this tutorial, you should be able to construct incidence_fit and incidence_fit_list objects for use with your own models.

Structure of an incidence_fit object

An incidence_fit object contains three elements:

  • $model: The model fit to an incidence object. Currently, this represents a log-linear model, but it can be any model.
  • $info: Information derived from the model
    • r The growth rate
    • r.conf the confidence interval of r
    • pred a data frame containing the predictions of the model using the true dates (dates), their numeric version used in the model (dates.x), the predicted value (fit), and the lower (lwr) and upper (upr) bounds of the associated confidence interval.
    • doubling the predicted doubling time in days (only if r is positive)
    • doubling.conf the confidence interval of the doubling time
    • halving the predicted halving time in days (only if r is negative)
    • halving.conf the confidence interval of the halving time
  • $origin: the date corresponding to day ‘0’

Internally, when fit() is run, these elements are constructed by function incidence:::extract_info(). First we need to setup data. We will use simulated Ebola outbreak data from the outbreaks package over weekly intervals and calculate the fit for the first 20 weeks:

As you can see, the incidence_fit object has a print method and a plot method. If you want to access individual elements in the $info element, you can use the get_info() function:

This will be important later when we combine several incidence_fit objects into a single incidence_fit_list.

Building an incidence_fit object from scratch

The incidence_fit object can be constructed from any model from which you can derive the daily growth rate, doubling/halving times, predictions, and confidence intervals. The following three steps show roughly how it is done from model fitting to construction.

Step 1: create the model

The default model for fit() is a log-linear model on the intervals between dates. To fit this model, we will need to create a data frame with the counts and the midpoints of the intervals:

If we compare that to the $model element produced from fit(), we can see that it is identical:

all.equal(f$model, lm1)
#> [1] TRUE

Step 2: creation of the $info list:

The $info list is created directly from the model itself:

Structure of an incidence_fit_list object

There are several reasons for having multiple fits to a single incidence object. One may want to have a separate fit for different groups represented in the object, or one may want to split the fits at the peak of the epidemic. To aid in plotting and summarizing the different fits, we’ve created the incidence_fit_list class. This class has two defining features:

  • It consists of a named list containing one or more incidence_fit objects or lists containing incidence_fit objects.
  • An attribute called “locations” contains a list whose length is equal to the number of incidence_fit objects in the object. Each list element contains a vector that defines where an incidence_fit object is within the incidence_fit_list.

The reason for this structure is because it is sometimes necessary to nest lists of incidence_fit objects within lists. When this happens, accessing individual elements of the objects cumbersome. To alleviate this, each object has a distinct path within the named list in the “locations” attribute that allows one to access the object directly since R allows you to traverse the elements of a nested list by subsetting with a vector:

Example: A tale of two fits

The function fit_optim_split() attempts to find the optimal split point in an epicurve, producing an incidence_fit_list object in the $fit element of the returned list:

Here you can see that the object looks very similar to the incidence_fit object, but it has extra information. The first thing you may notice is the fact that both “doubling” and “halving” are shown. This is because the two fits have different signs for the daily growth rate. The second thing you may notice is the fact that there is something called attr(x, 'locations'). This attribute gives the location of the incidence_fit objects within the list. We can illustrate how this works if we look at the structure of the object:

Internally, all of the methods for incidence_fit_list use the ‘locations’ attribute to navigate:

For example, it’s often useful to extract the growth rate for all models at once. The get_info() method allows us to do this easily:

Because doubling or halving is determined by whether or not r is negative, we automatically filter out the results that don’t make sense, but you can include them with na.rm = FALSE:

Example: Nested incidence_fit

Above, we showed the example of a basic incidence_fit_list class with two objects representing the fits before and after the peak of an epicurve. However, it is often useful evaluate fits for different groups separately. Here, we will construct an incidence object, but define groups by gender:

gen <- ebola_sim$linelist$gender
ig <- incidence(dat, interval = "week", group = gen)
plot(ig, border = "grey98")

Now if we calculate an optimal fit split, we will end up with four different fits: two for each defined gender.

If we look at the fit object, we can see again that it is an incidence_fit_list but this time with four fits defined.

fg$fit
#> <list of incidence_fit objects>
#> 
#> attr(x, 'locations'): list of vectors with the locations of each incidence_fit object
#> 
#> 'f', 'before'
#> 'm', 'before'
#> 'f', 'after'
#> 'm', 'after'
#> 
#> $model: regression of log-incidence over time
#> 
#> $info: list containing the following items:
#>   $r (daily growth rate):
#>    f_before    m_before     f_after     m_after 
#>  0.02570604  0.02883607 -0.01002297 -0.01038307 
#> 
#>   $r.conf (confidence interval):
#>                2.5 %       97.5 %
#> f_before  0.02289333  0.028518743
#> m_before  0.02502254  0.032649606
#> f_after  -0.01102735 -0.009018595
#> m_after  -0.01138910 -0.009377034
#> 
#>   $doubling (doubling time in days):
#> f_before m_before 
#> 26.96437 24.03750 
#> 
#>   $doubling.conf (confidence interval):
#>             2.5 %   97.5 %
#> f_before 24.30497 30.27725
#> m_before 21.22988 27.70091
#> 
#>   $halving (halving time in days):
#>  f_after  m_after 
#> 69.15586 66.75746 
#> 
#>   $halving.conf (confidence interval):
#>            2.5 %   97.5 %
#> f_after 62.85711 76.85756
#> m_after 60.86059 73.91966
#> 
#>   $pred: data.frame of incidence predictions (111 rows, 7 columns)
str(fg$fit, max.level = 3)
#> List of 2
#>  $ f:List of 2
#>   ..$ before:List of 3
#>   .. ..$ model :List of 12
#>   .. .. ..- attr(*, "class")= chr "lm"
#>   .. ..$ info  :List of 5
#>   .. ..$ origin: Date[1:1], format: "2014-04-07"
#>   .. ..- attr(*, "class")= chr "incidence_fit"
#>   ..$ after :List of 3
#>   .. ..$ model :List of 12
#>   .. .. ..- attr(*, "class")= chr "lm"
#>   .. ..$ info  :List of 5
#>   .. ..$ origin: Date[1:1], format: "2014-09-22"
#>   .. ..- attr(*, "class")= chr "incidence_fit"
#>   ..- attr(*, "locations")=List of 2
#>   .. ..$ : chr "before"
#>   .. ..$ : chr "after"
#>   ..- attr(*, "class")= chr "incidence_fit_list"
#>  $ m:List of 2
#>   ..$ before:List of 3
#>   .. ..$ model :List of 12
#>   .. .. ..- attr(*, "class")= chr "lm"
#>   .. ..$ info  :List of 5
#>   .. ..$ origin: Date[1:1], format: "2014-04-14"
#>   .. ..- attr(*, "class")= chr "incidence_fit"
#>   ..$ after :List of 3
#>   .. ..$ model :List of 12
#>   .. .. ..- attr(*, "class")= chr "lm"
#>   .. ..$ info  :List of 5
#>   .. ..$ origin: Date[1:1], format: "2014-09-15"
#>   .. ..- attr(*, "class")= chr "incidence_fit"
#>   ..- attr(*, "locations")=List of 2
#>   .. ..$ : chr "before"
#>   .. ..$ : chr "after"
#>   ..- attr(*, "class")= chr "incidence_fit_list"
#>  - attr(*, "locations")=List of 4
#>   ..$ : chr [1:2] "f" "before"
#>   ..$ : chr [1:2] "m" "before"
#>   ..$ : chr [1:2] "f" "after"
#>   ..$ : chr [1:2] "m" "after"
#>  - attr(*, "class")= chr "incidence_fit_list"

Notice that the nested lists themselves are of class incidence_fit_list.

Now, even though the fits within nested lists, the ‘locations’ attributes still defines where they are within the object so that the get_info() function still operates normally:

If you need to access all the fits easily, a convenience function to flatten the list is available in get_fit():

Because all that defines an incidence_fit_list is the class definition and the ‘locations’ attribute that defines the positions of the incidence_fit objects within the nesting, then it’s also possible to define the output of fit_optim_split() as an incidence_fit_list class:

Now when we print the object, we can see that it prints only the information related to the incidence_fit_list:

But, we still retain all of the extra information in the list: