Skip to content

Commit

Permalink
Merge pull request #9 from reconhub/fix_no_case_day_one
Browse files Browse the repository at this point in the history
Fix no case day one
  • Loading branch information
thibautjombart authored May 29, 2019
2 parents 537b3b2 + 467c65d commit 8f0400f
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 32 deletions.
11 changes: 9 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
language: r
cache: packages

addons:
apt:
sources:
- sourceline: 'ppa:chris-lea/libsodium'
packages:
- libsodium-dev
- libicu-dev

matrix:
include:
- os: linux
Expand All @@ -13,8 +21,7 @@ matrix:
r: oldrel
- os: osx
osx_image: xcode8.3



r_github_packages:
- jimhester/covr

Expand Down
21 changes: 16 additions & 5 deletions DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@ Package: earlyR
Title: Estimation of Transmissibility in the Early Stages of a Disease Outbreak
Version: 0.0.1
Authors@R: c(person("Thibaut", "Jombart", email = "[email protected]", role = c("aut", "cre")),
person("Anne", "Cori", email = "[email protected]", role = c("aut")),
person("Pierre", "Nouvellet", email = "[email protected]", role = c("aut")),
person("Janetta", "Skarp", email = "[email protected]", role = c("aut")))
person("Anne", "Cori", email = "[email protected]", role = c("aut")),
person("Pierre", "Nouvellet", email = "[email protected]", role = c("aut")),
person("Janetta", "Skarp", email = "[email protected]", role = c("aut")))
Description: Implements a simple, likelihood-based estimation of the reproduction number (R0) using a branching process with a Poisson likelihood. This model requires knowledge of the serial interval distribution, and dates of symptom onsets. Infectiousness is determined by weighting R0 by the probability mass function of the serial interval on the corresponding day. It is a simplified version of the model introduced by Cori et al. (2013) <doi:10.1093/aje/kwt133>.
Depends: R (>= 3.3.0)
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Imports: stats, distcrete, EpiEstim, epitrix, ggplot2
Suggests: testthat, vdiffr, roxygen2, incidence, knitr
Imports: stats,
distcrete,
EpiEstim,
epitrix,
ggplot2
Remotes:
annecori/EpiEstim@new-version,
reconhub/epitrix
Suggests: testthat,
vdiffr,
roxygen2,
incidence,
knitr
RoxygenNote: 6.1.1
URL: http://www.repidemicsconsortium.org/earlyR
BugReports: https://github.com/reconhub/earlyR/issues
Expand Down
39 changes: 33 additions & 6 deletions R/get_R.R
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ get_R.default <- function(x, ...) {
get_R.integer <- function(x, disease = NULL, si = NULL,
si_mean = NULL, si_sd = NULL,
max_R = 10, days = 30, ...) {

if (sum(x, na.rm = TRUE) == 0) {
stop("Cannot estimate R with no cases.")
}

dates <- seq_along(x)
last_day <- max(dates) + days

Expand Down Expand Up @@ -167,15 +172,37 @@ get_R.integer <- function(x, disease = NULL, si = NULL,

x_with_tail <- rep(0, last_day)
x_with_tail[dates] <- x
all_lambdas <- EpiEstim::overall_infectivity(x_with_tail, si)[-1]
dates_lambdas <- seq_along(all_lambdas) + 1
x_lambdas <- all_lambdas[dates]

loglike <- function(R) {
if (R <= 0 ) return(-Inf)
sum(stats::dpois(x[-1], lambda = R * x_lambdas, log = TRUE))

## Important note: we cannot compute the likelihood of the first day with
## case(s), as the force of infection on that day is by default 0: the
## likelihood is in fact conditional on the first day with cases. The
## resulting log-likelihood for the first day with cases will be -Inf. We keep
## track of this day and ignore this data point when summing over
## log-likelihood. In the general case, this will be the first element of x
## (x[1]), but we also implement the general case where the incidence curve
## could have started before the first cases. Note that the force of infection
## for day 1 is also by definition not known (NA), so the first day of data
## (x[1]) is always ignored in any case.

ignored_likelihood <- 1 # fist day always ignored
if (! sum(x_with_tail) == 0) {
days_with_cases <- which(x_with_tail > 0)
first_day_with_cases <- min(days_with_cases, na.rm = TRUE)
ignored_likelihood <- union(1, first_day_with_cases)
}

all_lambdas <- EpiEstim::overall_infectivity(x_with_tail, si)
dates_lambdas <- seq_along(all_lambdas)
lambdas_for_x <- all_lambdas[dates]

loglike <- function(R) {
if (R < 0 ) return(-Inf)
all_likelihoods <- stats::dpois(x,
lambda = R * lambdas_for_x,
log = TRUE)
sum(all_likelihoods[-ignored_likelihood])
}

GRID_SIZE <- 1000

Expand Down
8 changes: 6 additions & 2 deletions R/internals.R
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,14 @@


## Sanitize very low log-likelihood values and put them back on their original
## scale, summing to 1.
## scale, summing to 1. We need to consider the max of log-like could be 0.

loglike_to_density <- function(x) {
out <- x / abs(max(x, na.rm = TRUE))
out <- x
x_max <- max(x, na.rm = TRUE)
if (x_max != 0) {
out <- out / abs(x_max)
}
out <- exp(out)
out <- out / sum(out)
out
Expand Down
Binary file modified tests/testthat/rds/R_1.rds
Binary file not shown.
Binary file modified tests/testthat/rds/print_1.rds
Binary file not shown.
67 changes: 63 additions & 4 deletions tests/testthat/test_get_R.R
Original file line number Diff line number Diff line change
@@ -1,18 +1,74 @@
context("Test get_R")

test_that("Test against reference results", {
test_that("Lambdas sum to the right numbers", {
skip_on_cran()

dat <- sample(1:10, 50, replace = TRUE)
i <- incidence(dat)
## example with a function for SI
si <- distcrete("gamma", interval = 1L,
shape = 1.5,
scale = 2, w = 0)

R_est <- get_R(i, si = si, days = 1000)
expect_equal(sum(R_est$lambdas, na.rm = TRUE), i$n)

})




test_that("Estimation robust to heading zeros", {
skip_on_cran()

dat <- as.Date("2019-01-01") + sample(1:10, 50, replace = TRUE)
i <- incidence(dat)
i_early <- incidence(dat,
first_date = as.Date("2018-12-15"))
## example with a function for SI
si <- distcrete("gamma", interval = 1L,
shape = 1.5,
scale = 2, w = 0)

R_est <- get_R(i, si = si)
R_est_early <- get_R(i_early, si = si)
expect_equal(R_est$ml, R_est_early$ml)
expect_equal(R_est$R_like, R_est_early$R_like)

})





test_that("Test against simple example", {
skip_on_cran()
skip("\n!!! TODO (2019-05-28): REWRITE for changes from 3acfd712232bf36b91f3ec3906e545ced2c2cb6a\n")


## simulate basic epicurve
dat <- c(0, 2, 2, 3, 3, 5, 5, 5, 6, 6, 6, 6)
dat <- c(1, 2, 4)
i <- incidence(dat)


## example with a function for SI
si <- distcrete("gamma", interval = 1L,
shape = 1.5,
scale = 2, w = 0)
R_1 <- get_R(i, si = si)

w <- si$d(0:100)
w[1] <- 0
w <- w / sum(w)

## manual computations of expected forces of infection
expected_lambdas <- c(
day_1 = NA,
day_2 = w[1 + 1],
day_3 = sum(w[1:2 + 1]),
day_4 = sum(w[2:3 + 1]),
day_5 = sum(w[c(4,3,1) + 1])
)

R_est <- get_R(i, si = si)
expect_equal_to_reference(R_1, file = "rds/R_1.rds")

expect_identical(i, R_1$incidence)
Expand Down Expand Up @@ -71,5 +127,8 @@ test_that("Errors are thrown when they should", {
expect_error(get_R(i, si = si),
"interval used in si is not 1 day, but 5")

expect_error(get_R(c(0,0,0)),
"Cannot estimate R with no cases")

})

30 changes: 17 additions & 13 deletions tests/testthat/test_sample_R.R
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
context("Test get_R")
context("Test sample_R")

test_that("Test against reference results", {
skip_on_cran()

## simulate basic epicurve
dat <- c(0, 2, 2, 3, 3, 5, 5, 5, 6, 6, 6, 6)
i <- incidence(dat)
## simulate basic epicurve
dat <- c(0, 2, 2, 3, 3, 5, 5, 5, 6, 6, 6, 6)
i <- incidence(dat)


## example with a function for SI
si <- distcrete("gamma", interval = 1L,
shape = 1.5,
scale = 2, w = 0)
R_1 <- get_R(i, si = si)
## example with a function for SI
si <- distcrete("gamma", interval = 1L,
shape = 1.5,
scale = 2, w = 0)
R_1 <- get_R(i, si = si)

samp_1 <- sample_R(R_1, n = 1e4)

samp_1 <- sample_R(R_1, n = 1e4)
expect_length(samp_1, 1e4)

expect_length(samp_1, 1e4)
expect_true(abs(round(mean(samp_1), 1) - 3.3) < 0.1)
expect_true(abs(round(sd(samp_1), 1) - 2.2) < 0.1)
skip("\n!!! TODO (2019-05-28): These need to be validated with expected values\n")

expect_true(abs(round(sd(samp_1), 1) - 2.2) < 0.1)
expect_true(abs(round(mean(samp_1), 1) - 3.3) < 0.1)

})

Expand All @@ -38,6 +41,7 @@ test_that("Test that right sample size is used", {
si <- distcrete("gamma", interval = 1L,
shape = 1.5,
scale = 2, w = 0)

R_1 <- get_R(i, si = si)

expect_length(sample_R(R_1, 3), 3L)
Expand Down

0 comments on commit 8f0400f

Please sign in to comment.