Hospital Capacity Planning Using Discrete Event Simulation: OBK

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)

Introduction

Materials and Methods

require("vctrs")
require("slider")
require("dplyr")
require("parallel")
require("simmer")
require("simmer.plot")
require("ggplot2")
require("lubridate")
require("tidyverse")
require("plotly")
require("babsim.hospital")
## install.packages("devtools")
## devtools::install_github("r-lib/devtools")
url <- "http://owos.gm.fh-koeln.de:8055/bartz/spot.git"
devtools::install_git(url = url)

library(SPOT)
packageVersion("SPOT")

OBK Data

Data

Set up simData

The function getObkData generates a list that contains two data frames: 1. simData and 2. field data.

data <- getObkData()
simData <- data$simData
fieldData  <- data$fieldData
conf <- babsimToolsConf()

Perform the simulation

conf <- getConfFromData(conf = conf,
                        simData = simData,
                        fieldData = fieldData)
set.seed(conf$seed)
para <- babsimHospitalPara()
arrivalTimes <- getArrivalTimes(simData$Infected)
envs <- babsimHospital(arrivalTimes = arrivalTimes,
                    conf = conf,
                    para = para
                    )

Plot resource usage

resources <- get_mon_resources(envs)
resources$capacity <- resources$capacity/1e5
plot(resources, metric = "usage", conf$ResourceNames, items = "server")

Get real data (field data)

fieldEvents <- getRealBeds(data = fieldData,
                           resource=c('bed', 'intensiveBed', 'intensiveBedVentilation'))
res <- getDailyMaxResults(conf=conf, envs = envs,  fieldEvents = fieldEvents)
## save as default:
(errDefault <- getError(conf=conf, res))
plotDailyMaxResults(res)

Optimization (to be used from the command line in the folder Run.d)

expName <- "obkNoise35"
require("SPOT")
require("babsim.hospital")
require("simmer")
##########################################################
data <- getObkData()
###########################################################
## n = number of function evaluations, Anzahl der 30-dim x Vektoren, die getestet werden
n = 35
## k = Groesse des initialen Designs für SPOT (k < n)
k = 30
## m = Anzahl der Simulationswiederholungen für Simnf
m = 2
## parallele Auswertung?
PARALLEL = FALSE
##
ICU = FALSE
##
PERCORES = 0.5
## Anzahl der Punkte, die von SPOT fuer das Metamodell verwendet werden. Sollte
## fuer Kriging nicht zu gross gewählt werden (<100):
SUBSET = 50
#############################################################
x0 <- getStartParameter()
bounds <- getBounds()
conf = babsimToolsConf()
set.seed(conf$seed)
conf$parallel = PARALLEL
conf$simRepeats = m
conf$ICU = ICU
conf$percCores = PERCORES
conf$logLevel =1
FILENAME <-  paste0("../Results.d/run", expName, ".RData")
assign( expName, 
        spot(
          x = x0,
          fun = funWrapOptimizeSim,
          lower = bounds$lower,
          upper = bounds$upper,
          control = list(
            funEvals = n,
            noise = TRUE,
            optimizer = optimLBFGSB,
            model =  buildKriging,
            plots = FALSE,
            progress = TRUE,
            subsetSelect = selectN,
            subsetControl = list(N = SUBSET),
            designControl = list(size = k)
          ),
          conf = conf,
          data = data
        )
)
# save(list = eval(parse(text = "expName")) , file = FILENAME)

The OBK Data (Real-world Data)

amntDaysSickness <- 20
obk <- dataCovidBeds20200624
obk$Sick2 <-  slide_dbl(obk$Infected, ~sum(.x), .before = (amntDaysSickness -1))
str(obk)
sum(obk$Sick- obk$Sick2)
obk$InfCum <- cumsum(obk$Infected)
{plot(obk$Day, obk$InfCum, type="l", log="y", ylim=c(1,500))
lines(obk$Day, obk$Infected + 1e-6)}
obk$InfectedSlideWeek <-  slide_dbl(obk$Infected, ~mean(.x), .before = (7 -1))
obk$InfectedSlide2Weeks <-  slide_dbl(obk$Infected, ~mean(.x), .before = (14 -1))
{plot(obk$Day, obk$Infected, type="l")
lines(obk$Day, obk$InfectedSlideWeek, col = "green")
lines(obk$Day, obk$InfectedSlide2Weeks, col = "grey")
legend('topright', lty = 1, c("infected", "infectedWeek",  "infected2Weeks"), col = c("black", "green",  "grey"))
}
{plot(obk$Day, obk$Infected, type="l")
lines(obk$Day, obk$InfectedSlideWeek, col = "green")
lines(obk$Day, obk$bed, col = "blue")
lines(obk$Day, obk$intensiveBed, col = "orange")
lines(obk$Day, obk$intensiveBedVentilation, col = "red")
legend('topright', lty = 1, c("infected", "infectedWeek",  "normal", "intens", "vent"), col = c("black", "green",  "blue", "orange", "red"))
}

Performing Simulations Using Real Data

To define the simulation period, the obk data will be used: \begin{itemize} \item StartDate: the day, logging of real data starts, e.g., "2020-03-03" \item EndDate: last day for simulations, e.g. "2020-05-07" \end{itemize} The observedPeriod is defined as the complete period, which will be analyzed in this project, e.g., observedPeriod = 1 + EndData - StartDate.

StartDate <- obk$Day[1]
EndDate <- obk$Day[length(obk$Day)] 
observedPeriod = 1 + as.numeric(as.Date(EndDate)- as.Date(StartDate))

The OBK Simulations/Predictions

infectedPerDay <- obk$Infected
infectedPerDayCum <- cumsum(infectedPerDay)
obk$Sick <-  slide_dbl(obk$Infected, ~sum(.x), .before = (amntDaysSickness -1))
# print(data.frame(day = 1:length(infectedPerDay), infected= infectedPerDay, sick = obk$Sick, infectedCum = infectedPerDayCum))
{plot(obk$Day, obk$InfCum, type="l")
lines(obk$Day, obk$Infected, col = "red")
lines(obk$Day, obk$Sick, col = "blue")
}

How to Perform Simmer Simulations Using Real Data

str(obk)
set.seed(123)
arrivalTimes <- getArrivalTimes(obk$Infected) 
str(arrivalTimes)
conf = babsimToolsConf()

conf$simRepeats = 10
conf$logLevel = 0
conf$seed = 123
y <- babsimHospital(arrivalTimes = arrivalTimes
                   , conf = conf
                   , para = babsimHospitalPara()
 )

How to Evaluate Results

 GAData <- data.frame(bed=obk$bed, 
                      intensiveBed=obk$intensiveBed, 
                      intensiveBedVentilation=obk$intensiveBedVentilation,
                      Day = obk$Day)
 GABeds <- getRealBeds(data = GAData,  resource=c("bed", "intensiveBed", "intensiveBedVentilation"))
res <- getDailyMaxResults(
   conf=conf,
  envs = y,  
  fieldEvents = GABeds)
getError <- function(res){
res1 <- res %>% filter(resource == "bed"  & source == "babsim")
df1 <- unique(data.frame(date = res1$date, x = res1$med))
res2 <- res %>% filter(resource == "bed"  & source == "GA")
df2 <- unique(data.frame(date = res2$date, x= res2$med))
dfBed <- full_join(df1, df2, by=c("date"))
dfBed[is.na(dfBed)] <- 0
rmseBed <- rmse(dfBed[,2], dfBed[,3])

res1 <- res %>% filter(resource == "intensiveBed"  & source == "babsim")
df1 <- unique(data.frame(date = res1$date, x = res1$med))
res2 <- res %>% filter(resource == "intensiveBed"  & source == "GA")
df2 <- unique(data.frame(date = res2$date, x= res2$med))
dfIntensiveBed <- full_join(df1, df2, by=c("date"))
dfIntensiveBed[is.na(dfIntensiveBed)] <- 0
rmseIntensiveBed <- rmse(dfIntensiveBed[,2], dfIntensiveBed[,3])

res1 <- res %>% filter(resource == "intensiveBedVentilation"  & source == "babsim")
df1 <- unique(data.frame(date = res1$date, x = res1$med))
res2 <- res %>% filter(resource == "intensiveBedVentilation"  & source == "GA")
df2 <- unique(data.frame(date = res2$date, x= res2$med))
dfIntensiveBedVentilation <- full_join(df1, df2, by=c("date"))
dfIntensiveBedVentilation[is.na(dfIntensiveBedVentilation)] <- 0
rmseIntensiveBedVentilation <- rmse(dfIntensiveBedVentilation[,2], dfIntensiveBedVentilation[,3])
return(rmseBed + rmseIntensiveBed + rmseIntensiveBedVentilation)
}
getError(conf=conf, res)

How to Plot Results From Real Simulation:

p <- plotDailyMaxResults(res)
print(p)
ggplotly(p)

Appendix

Sick Persons

In contrast to the babsim simmer model, which uses the daily number of infections, the standard models calculate the number of \sick persons using a time window of size \amntDaysSickness days.

The number of infected people that require hospitalisation are calculated as follows:

Not every infected person requires hospitalization. The number of people, that have to go to the hospital is determined as follows: Only \infected people from the last \amntDaysSickness days are considered. These are labeled as \sick. Note the minus one in the calculation, because the current day counts as one of the \amntDaysSickness days.

amntDaysSickness = 20
Infs <- ex1InfectedDf$Infected
sickPerDay <-  slide_dbl(Infs , ~sum(.x), .before = (amntDaysSickness -1))
head(data.frame(day = 1:length(Infs), infected= Infs, sick = sickPerDay, ex1InfsCum = cumsum(Infs)))
knitr::kable(head(data.frame(day = 1:length(Infs), infected= Infs, sick = sickPerDay, ex1InfsCum = cumsum(Infs))))
p <- {plot( cumsum(Infs), type = "l", col="black", xlab = "days", ylab = "infected, sick")
lines( 1:observedPeriod, Infs, col = "blue")
lines( 1:observedPeriod, sickPerDay, col = "red")
legend('topleft', lty = 1, c("infectedCum","infected", "sick"), col = c("black", "blue", "red"))
}

For the standard models (OBK, Köln, and WHO) the following percentages, based on the number of \sick persons, were determined: \factorHospital, \factorIntensive, and \factorVentilation.

The number of patients that need to go to the hospital is based on the infected people from the last \amntDaysSickness days.

## Get numbers as estimated in excel table based on fixed coefficients
getDataFromFixedNumbers <- function(factorHospital, factorIntensive, factorVentilation, sickPerDay, source){
    date <- as.POSIXct(seq(as.Date(StartDate), as.Date(EndDate), by="days"))
    return(
        rbind(
            data.frame("resource" = "bed","time" = 1:length(sickPerDay), 
                       "med" = ( sickPerDay * factorHospital), "source" = source,
                       "date" = date),
            data.frame("resource" = "intensiveBed","time" = 1:length(sickPerDay), 
                       "med" = ( sickPerDay * factorIntensive), "source" = source,
                       "date" = date),
            data.frame("resource" = "intensiveBedVentilation","time" = 1:length(sickPerDay), 
                       "med" = ( sickPerDay * factorVentilation), "source" = source,
                       "date" = date)
        )
    )
}

Optimization of the Gamma Shape Parameter:

err <- c()
for (i in 1:15){
  trajList <- babsimHospitalTrajectories(GammaShapeParameter = i/10
                   , control = babsimHospitalPara())
  y <- babsimHospital(arrivalTimes = arrivalTimes
                   , simRepeats = 100
                   , control = babsimHospitalPara()
                   , trajList = trajList)
  res <- getDailyMaxResults(
  envs = y,  
  fieldEvents = GABeds)
  err <- c(err,getError(res))
}
errOLD <- c(14.480660, 12.815926, 11.432739, 10.106963,  9.055230,  8.486424,  8.093233,  7.855100,  8.619063,  8.821263,
9.639260, 10.266499, 11.265664, 13.171530, 13.869897)
err <- c(14.654427, 13.418457, 11.827037, 10.962369,  9.486851,  8.965150,  8.272998,  7.771752,  8.017022,  8.544688,  9.035368,  9.875065, 10.331149, 11.202670, 12.220776)
plot( (1:15)/10, err, xlab="Gamma Shape Parameter")
para <- babsimHospitalPara()
conf <- babsimToolsConf()
conf$parallel <- FALSE

y2 <- babsimHospital(arrivalTimes = arrivalTimes
                   , conf = conf
                   , para = para
     )
res2 <- getDailyMaxResults(
   conf=conf,
  envs = y2,  
  fieldEvents = GABeds)
getError(conf=conf, res2)
p <- plotDailyMaxResults(res2)
print(p)

Summary

\bibliography{../bab-bibfiles/bartzAll.bib}



Try the babsim.hospital package in your browser

Any scripts or data that you put into this service are public.

babsim.hospital documentation built on May 30, 2022, 9:05 a.m.