
# The analysis is performed using language in R and in Python for LSTM, so we will need to change from language to another for analysis that require LSTM. The others only require R
# This document provide the exact code necessary to run a similar analysis as the implemented in the paper
# The user needs to paste all the functions as indicated and run them as in the examples.

# The following lines are intended to be run in R
## 1. LOADING THE NECESSARY PACKAGES
## required packages for different methods or data management
library(surveillance)
library(dplyr)
library(glmmTMB)
library(forecast)
library(tscount)
library(fitdistrplus)
library(otsad)
library(qcc)
library(tidyr)
# if any of them is not installed, use first the function install.packages("package") to install it.

## 2. LOADING THE DATA 
# Load the data for the analysis
# We provide a sample of data consisted of 209 weekly counts. As in the paper we will use the first 0-157 for training and the 158-209 for testing (i.e., the window of analysis). These limits can be changed to adapt to other situations, for example, for using more weeks of  training. It is only needed to change the limits when it corresponds. 

data<-read.csv("{path}/exampledata.csv")

# the sample data is stored in the object "data", which we will use in the analysis 
# the names of the columns cannot be changed in order the functions work


# 3. GENERATION OF OUTBREAKS 

# We use the function "otbk.gen4" to generate a data frame of time series with synthetic outbreaks. 
# The arguments of the function are: 
	# "k": the desired k parameter. We are using k = {4,8,12,16,20}; if you need to use other k, just type the specific value for this parameter in this position
	# "data": the data previously loaded with columns "week" and "count" 
	# "niter": to define the number of time series with synthetic outbreaks that we want to create. We set this value in 1,000
	# "limit": the last position of the time series that will be used as reference. We set this value in 156. This means the synthetic outbreak will be inserted after the 156th position


otbk.gen4<-function (k,data,limit, niter)   {
  limit<-limit+1
  k<-k*sd(data$count[1:limit])
  dataotbk<-data.frame(date=data$week)
  iter<-1
  
  repeat{
    size<-rpois(1,k)
    outbk<-rlnorm(size,meanlog=0,sdlog=0.5)
    outbk2<-if(length(outbk)==0) (1) else(outbk)# this is to correct one bug when rlnorm is empty
    h1<-hist(outbk2,breaks = seq(0,ceiling(max(outbk2)),1),plot=F)
    case<-h1$count
    init<-sample(limit:(limit+nstep),1)
    outbk3<-data$count
    outbk3[init:(init+length(case)-1)]<-case+outbk3[init:(init+length(case)-1)]
    dataotbk[iter+1]<-outbk3
    
    iter<-iter+1
    if (iter==niter+1) break
  }
  dataotbk
}

3.1 EXAMPLE
# The function provides a data frame with one column for date and, in our case, 1,000 columns, each one a time series with different synthetic outbreaks distributed after the 157th position
# We can save different sets of synthetic outbreaks as in the following example:

dataotbk_k4<-otbk.gen4(4,data,157,1000) 
dataotbk_k8<-otbk.gen4(8,data,157,1000)
dataotbk_k12<-otbk.gen4(12,data,157,1000)
dataotbk_k16<-otbk.gen4(16,data,157,1000)
dataotbk_k20<-otbk.gen4(20,data,157,1000)

# 4.CREATING FUNCTIONS FOR EACH METHOD 

# We have three different situations here: i) methods only run in R; ii) methods run in Python; and iii) methods run in Python and R. In all the case, the performance measures are calculated in R.

# 4.1 METHODS RUN IN R
# We describe the functions that we used for each method. These functions will be called at the end to run the analysis
# These functions automatize the process of analysis in various ways:
	# They repeat automatically the analysis for all the time series with synthetic outbreaks
	# In each time series, they move the 4-week rolling window one step and collect the data

# 4.1.1 FUNCTIONS FOR EACH METHOD RUN IN R

# In this section, we present the functions for each of the methods. 
# These functions provide results for one iteration. To automatize several iterations and get the performance measures, it is necessary to use the function "finaltest", which will call these functions internally. For this reason the specific function of each method needs to be run first in order "finaltest" can find it and call them

# 4.1.1.1 REGRESSION TECHNIQUES

# 4.2.1.1.1 ORIGINAL'S FARRINGTON ALGORITHM (far)
# package: surveillance
# We run the following function:

iter.method.far<-function (df,nstep,outbk, Limita,Limitb,Limitc,Limitd) {
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  
  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)

 
    datstep0<-as.matrix(c(train$otbk,test$otbk))
    datstept<-sts(datstep0,freq=52)# it may be necessary to change it if non annual periods are considered
    # parameters
    forcntrl<-list(range=Limitc:Limitd,m=1,w=3,b=2,alpha=0.025,thresholdMethod="nbPlugin")
    #analysis
    fartest<-farrington(datstept,forcntrl)
    
    #2. return alarms
    graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        fitted=df2$count[Limita:Limitd]*0,
                        outbk=df2$otbk[Limita:Limitd],
                        thr=NA)#create column to complete later
    graph.p$thr[Limitc:Limitd]<-fartest@upperbound
    graph.p$alert<-ifelse(graph.p$outbk>graph.p$thr,20,NA)
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}

# 4.2.1.1.2 IMPROVED FARRINGTON'S ALGORITHM (farflex)
# package: surveillance
# We run the following function:

iter.method.farflex<-function (df,nstep,outbk,Limita,Limitb,Limitc,Limitd) {
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  
  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)
    

    datstep0<-as.matrix(c(train$otbk,test$otbk))
    datstept<-sts(datstep0,freq=52)
    # parameters
    forcntrl<-list(range=Limitc:Limitd,m=1,w=3,b=2,alpha=0.025,thresholdMethod="nbPlugin")
    #analysis
    fartest<-farringtonFlexible(datstept,forcntrl)
    
  
    graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        fitted=df2$count[Limita:Limitd]*0,
                        outbk=df2$otbk[Limita:Limitd],
                        thr=NA)#create column to complete later
    graph.p$thr[Limitc:Limitd]<-fartest@upperbound
    graph.p$alert<-ifelse(graph.p$outbk>graph.p$thr,20,NA)
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}

# 4.2.1.1.3 NEGATIVE BINOMIAL REGRESSION (breg)
# We run a negative binomial regression modeled with the package glmmTMB
# We run the following function:

iter.method.breg<-function (df,nstep,outbk,Limita,Limitb,Limitc,Limitd) {
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  
  #set limits
  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)
 
    df2$trend<-seq(Limita,Limitd,1)
    df2$sin.term<-sin(2*pi*df2$trend/52)
    df2$cos.term<-cos(2*pi*df2$trend/52)
    
    # analysis
    md.bn<-glmmTMB(count~trend+sin.term+cos.term,data=df2[Limita:Limitb,],family=nbinom2)
    resbn<-data.frame(predict(md.bn,newdata=df2[Limitc:Limitd,],type="response",se.fit=T))
    
    # return alarms
    graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        fitted=c(fitted(md.bn),resbn$fit),
                        outbk=df2$otbk[Limita:Limitd],
                        thr=NA) # create column to complete later
    
    graph.p$resid<-graph.p$outbk-graph.p$fitted
    graph.p$thr[Limitc:Limitd]<-graph.p$fitted[Limitc:Limitd]+2*sd(graph.p$resid[Limita:Limitb])
    graph.p$alert<-ifelse(graph.p$outbk>graph.p$thr,20,NA)
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}

# 4.2.1.2 BAYESIAN TECHNIQUES (bay1-3)
# We used three possible methods: bay1, bay2 or bay3. 
# package: surveillance
# We run the following function:

iter.method.bay<-function (df,nstep,outbk, Limita,Limitb,Limitc,Limitd, method) {
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  
  #set limits
  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)
    
    datstep0<-as.matrix(c(train$otbk,test$otbk))
    datstept<-sts(datstep0,freq=52)
    datstept2<-create.disProg(week=1:nrow(datstep0),
                              observed=datstep0,
                              freq=52,
                              start=2016,1)
    # parameters
    bayescntrl<-list(range=Limitc:Limitd)
    
    baytest<-if (method=="bay1") {algo.bayes1(datstept2,bayescntrl)
    } else if (method=="bay2") {algo.bayes2(datstept2,bayescntrl)
    } else if (method=="bay3") {algo.bayes3(datstept2,bayescntrl)} 
    
    
    #2. return alarms
    graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        fitted=df2$count[Limita:Limitd]*0,
                        outbk=df2$otbk[Limita:Limitd],
                        thr=NA)#create column to complete later
    graph.p$thr[Limitc:Limitd]<-baytest$upperbound
    graph.p$alert<-ifelse(graph.p$outbk>graph.p$thr,20,NA)
    
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}

# 4.2.1.3 TIME SERIES TECHNIQUES

# 4.2.1.3.1 HOLT-WINTERS (hw_g/ng)
# package forecast is required for predictions
# We run the following function:

iter.method.hw<-function (df,nstep,outbk,Limita,Limitb,Limitc,Limitd) {
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  
  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)
       
    dat.ts<-ts(train$otbk,start =c(2016,1),freq=52)
    
    # analysis
    hw.t<-HoltWinters(dat.ts,seasonal="additive",start.periods = 2)
    p.fort<-forecast(hw.t,h=4)
    
    #2. return alarms
    graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        fitted=c(rep(NA,Limitb),p.fort$mean),
                        outbk=df2$otbk[Limita:Limitd],
                        thr=NA)#create column to complete later
    
    resid.tw<-residuals(hw.t)
    graph.p$thr[Limitc:Limitd]<-graph.p$fitted[158:161]+2*sd(resid.tw)
    graph.p$alert<-ifelse(graph.p$outbk>graph.p$thr,20,NA)
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}

# 4.2.1.3.2  ARIMA (ari_g/ng) 
# The package tscount is used to create the model
# We run the following function:

iter.method.ari<-function (df,nstep,outbk,Limita,Limitb,Limitc,Limitd) {
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  
  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)
    
    df2$trend<-seq(1,161,1)
    df2$sin.term<-sin(2*pi*df2$trend/52)
    df2$cos.term<-cos(2*pi*df2$trend/52)
      
    dat.ts<-ts(train$otbk,start =c(2016,1),freq=52)
    
    regressors<-cbind(df2$trend[Limita:Limitb],
                      df2$sin.term[Limita:Limitb],
                      df2$cos.term[Limita:Limitb])
    
    # analysis
    
    md.arima<-tsglm(dat.ts,model=list(past_obs=c(4,1)),xreg=regressors,distr="nbinom",link="log")
    
    nreg<-cbind(df2$trend[Limitc:Limitd],
                df2$sin.term[Limitc:Limitd],
                df2$cos.term[Limitc:Limitd])
    
    pred.arima<-data.frame(pred=predict(md.arima,n.ahead=4,newxreg=nreg,level=0.9,global=T,B=2000)$pred)
    
    
    graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        fitted=c(rep(NA,Limitb),pred.arima$pred),
                        outbk=df2$otbk[Limita:Limitd],
                        thr=NA)#create column to complete later
    
    graph.p$thr[Limitc:Limitd]<-graph.p$fitted[Limitc:Limitd]+2*sd(md.arima$residuals)
    graph.p$alert<-ifelse(graph.p$outbk>graph.p$thr,20,NA)
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}

# 4.2.1.4 STATISTIC CONTROL CHARTS ALGORITHMS 

# 4.2.1.4.1 RKI (rki1-3)
# We used three possible methods: rki1, rki2 or rki3. 
# package: surveillance
# We run the following function:

iter.method.rki<-function (df,nstep,outbk,Limita,Limitb,Limitc,Limitd,method) {
  
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  
 
  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)
   
    datstep0<-as.matrix(c(train$otbk,test$otbk))
    datstept<-sts(datstep0,freq=52)
    datstept2<-create.disProg(week=1:nrow(datstep0),
                              observed=datstep0,
                              freq=52,
                              start=2016,1)
    # parameters
    rkicntrl<-list(range=Limitc:Limitd)
    #analysis
     rkitest<-if (method=="rki1") {algo.rki1(datstept2,rkicntrl)
                  }else if (method=="rki2"){algo.rki2(datstept2,rkicntrl)
                  } else if (method=="rki3"){algo.rki3(datstept2,rkicntrl)}
    
    graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        fitted=df2$count[Limita:Limitd]*0,
                        outbk=df2$otbk[Limita:Limitd],
                        thr=NA)#create column to complete later
    graph.p$thr[Limitc:Limitd]<-rkitest$upperbound
    graph.p$alert<-ifelse(graph.p$outbk>graph.p$thr,20,NA)
    
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}

# 4.2.1.4.2  EARS (ears1-3)
# We used three possible methods: ears1, ears2 or ears3. 
# package: surveillance
# We run the following function:

iter.method.ears<-function (df,nstep,outbk,Limita,Limitb,Limitc,Limitd,method) {
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  

  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)
    
    datstep0<-as.matrix(c(train$otbk,test$otbk))
    datstept<-sts(datstep0,freq=52)
    
    # parameters
    earcntrl<- if (method=="ears1"){list(range=Limitc:Limitd,
                                         method="C1")
    } else if (method=="ears2"){list(range=Limitc:Limitd,
                                     method="C2")
    } else if (method=="ears3"){list(range=Limitc:Limitd,
                                     method="C3")
    }
    
    #analysis 
    eartest<-earsC(datstept,earcntrl)
    
    graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        fitted=df2$count[Limita:Limitd]*0,
                        outbk=df2$otbk[Limita:Limitd],
                        thr=NA)#create column to complete later
    graph.p$thr[Limitc:Limitd]<-eartest@upperbound
    graph.p$alert<-ifelse(graph.p$outbk>graph.p$thr,20,NA)
    
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}

# 4.2.1.4.3 CUSUM (far_c/breg_c) 
# We used three possible methods of pre-processing: i) lstm; ii) Farrington's algorithm; and iii) regression. The first method requires using Python and is described in another section. The other two are implemented in R with the following functions: 

# 4.2.1.4.3.1 CUSUM with FARRINGTON'S ALGORITHM preprocessing (far_c)
# package: qcc and glmmTMB
# the function already includes the pre-processing
# We run the following function:

iter.method.cusum2<-function (df,nstep,outbk,Limita,Limitb,Limitc,Limitd) {
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  
  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)
    
    predat<-df2
    predat$trend<-seq(Limita,Limitd,1)
    predat$sin.term<-sin(2*pi*predat$trend/52)
    predat$cos.term<-cos(2*pi*predat$trend/52)
    
    md1u<-glmmTMB(otbk~trend+sin.term+cos.term,data=predat,family=nbinom2)
    
    # remove outliers
    s<-2.5
    sd_bn1<-sqrt(fitted(md1u)+fitted(md1u)^2/summary(md1u)$sigma)
    r_bn1<-(predat$count[Limita:Limitb]-fitted(md1u))/sd_bn1
    weights_bn1<-ifelse(r_bn1<s,1,1/r_bn1^2)
    wgs1<-length(fitted(md1u))/sum(weights_bn1)/weights_bn1
    
    md2u<-glmmTMB(otbk~trend+sin.term+cos.term,data=predat,family=nbinom2,weights=wgs1)
    
    predat$fitted<-predict(md2u,newdata=predat,type="response",se.fit=F)
    predat$resid<-predat$otbk-predat$fitted
    
    #analysis 
    md.cusum<-cusum(predat$resid[Limita:Limitb],
                    center=mean(predat$resid[Limita:Limitb],na.rm=T),
                    std.dev=sd(predat$resid[Limita:Limitb],na.rm=T),
                    newdata=predat$resid[Limitc:Limitd],
                    se.shift=1,
                    decision.interval=2,
                    plot=F)
    
   graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        outbk=df2$otbk[Limita:Limitd],
                        alert=NA,
                        thr=NA)#create column to complete later
    
    graph.p$alert<-replace(graph.p$alert,list=md.cusum$violations$upper,values=20)
    graph.p$alert[Limita:Limitb]<-NA
    
    
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}

# 4.2.1.4.3.1 CUSUM with NEGATIVE BINOMIAL REGRESSION preprocessing (breg_c)
# package: qcc, glmmTMB and fitdistrplus
# the function already includes the pre-processing
# We run the following function:

iter.method.cusum3<-function (df,nstep,outbk,Limita,Limitb,Limitc,Limitd) {
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  
  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)
    
    predat<-df2
    predat$trend<-seq(Limita,Limitd,1)
    predat$sin.term<-sin(2*pi*predat$trend/52)
    predat$cos.term<-cos(2*pi*predat$trend/52)
    md1u<-glmmTMB(count~trend+sin.term+cos.term,data=predat,family=nbinom2)
    
    # remove outliers
    nbin.d<-fitdist(predat$count,"nbinom")
    limit.mb.1<-qnbinom(p=0.95,
                        mu=fitted(md1u),
                        size=nbin.d$estimate[[1]],
                        lower.tail=T,
                        log.p=F)
    predat$limit<-limit.mb.1
    
    predat$processed<-ifelse(predat$count>predat$limit,predat$limit,predat$count)
    peaks.bn1<-which(predat$count>round(predat$limit))
    x.smooth.bn1<-predat$count
    x.smooth.bn1[peaks.bn1]<-round(predat$limit[peaks.bn1])
    predat$processed2<-c(x.smooth.bn1[Limita:Limitb],predat$otbk[Limitc:Limitd]) 
    
    # parameters
    md2u<-glmmTMB(processed2~trend+sin.term+cos.term,data=predat,family=nbinom2)
    
    predat$fitted<-fitted(md2u)
    predat$resid<-residuals(md2u)
    
    #analysis 
    md.cusum<-cusum(predat$resid[Limita:Limitb],
                    center=mean(predat$resid[Limita:Limitb],na.rm=T),
                    std.dev=sd(predat$resid[Limita:Limitb],na.rm=T),
                    newdata=predat$resid[Limitc:Limitd],
                    se.shift=1,
                    decision.interval=2,
                    plot=F)
    
    graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        outbk=df2$otbk[Limita:Limitd],
                        alert=NA,
                        thr=NA)#create column to complete later
    
    graph.p$alert<-replace(graph.p$alert,list=md.cusum$violations$upper,values=20)
    graph.p$alert[Limita:Limitb]<-NA
    
    
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}


# 4.2.1.4.4 SHEWHART ALGORITHM (far_s/breg_s)
# We used three possible methods of pre-processing: i) lstm; ii) Farrington's algorithm; and iii) regression. The first method requires using Python and is described in another section. The other two are implemented in R with the following functions: 

# 4.2.1.4.4.1 SHEWHART with FARRINGTON'S ALGORITHM preprocessing (far_s)
# package: qcc and glmmTMB
# the function already includes the pre-processing
# We run the following function:

iter.method.shewhart2<-function (df,nstep,outbk,Limita,Limitb,Limitc,Limitd) {
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  
  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)
    
    #1. train
    predat<-df2
    predat$trend<-seq(Limita,Limitd,1)
    predat$sin.term<-sin(2*pi*predat$trend/52)
    predat$cos.term<-cos(2*pi*predat$trend/52)
    
    md1u<-glmmTMB(otbk~trend+sin.term+cos.term,data=predat,family=nbinom2)
    
    # remove outliers
    s<-2.5
    sd_bn1<-sqrt(fitted(md1u)+fitted(md1u)^2/summary(md1u)$sigma)
    r_bn1<-(predat$count[1:157]-fitted(md1u))/sd_bn1
    weights_bn1<-ifelse(r_bn1<s,1,1/r_bn1^2)
    wgs1<-length(fitted(md1u))/sum(weights_bn1)/weights_bn1
    
    md2u<-glmmTMB(otbk~trend+sin.term+cos.term,data=predat,family=nbinom2,weights=wgs1)
    
    predat$fitted<-predict(md2u,newdata=predat,type="response",se.fit=F)
    predat$resid<-predat$otbk-predat$fitted
    
    #analysis 
    
    stats.shew<-stats.xbar.one(predat$resid)
    sd.xbar.shew<-sd.xbar.one(predat$resid,
                              std.dev = "SD",
                              k=2)
    shew.shew<-limits.xbar.one(center = stats.shew$center,
                               std.dev = as.double(sd.xbar.shew),
                               conf = 2)
    UCL.value.test<-ceiling(shew.shew[2])
    
    graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        outbk=df2$otbk[Limita:Limitd],
                        alert=NA,
                        thr=NA)#create column to complete later
    
    graph.p$alert[Limitc:Limitd]<-ifelse(predat$resid[Limitc:Limitd]>UCL.value.test,20,NA)
    graph.p$alert[Limita:Limitb]<-NA
    
    
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}

# 4.2.1.4.4.2 SHEWHART with NEGATIVE BINOMIAL REGRESSION preprocessing (breg_c)
# package: qcc, glmmTMB and fitdistrplus
# the function already includes the pre-processing
# We run the following function:

iter.method.shewhart3<-function (df,nstep,outbk,Limita,Limitb,Limitc,Limitd) {
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  
  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)
    
    predat<-df2
    predat$trend<-seq(Limita,Limitd,1)
    predat$sin.term<-sin(2*pi*predat$trend/52)
    predat$cos.term<-cos(2*pi*predat$trend/52)
    md1u<-glmmTMB(count~trend+sin.term+cos.term,data=predat,family=nbinom2)
    
    # remove outliers
    nbin.d<-fitdist(predat$count,"nbinom")
    limit.mb.1<-qnbinom(p=0.95,
                        mu=fitted(md1u),
                        size=nbin.d$estimate[[1]],
                        lower.tail=T,
                        log.p=F)
    predat$limit<-limit.mb.1
    
    predat$processed<-ifelse(predat$count>predat$limit,predat$limit,predat$count)
    peaks.bn1<-which(predat$count>round(predat$limit))
    x.smooth.bn1<-predat$count
    x.smooth.bn1[peaks.bn1]<-round(predat$limit[peaks.bn1])
    predat$processed2<-c(x.smooth.bn1[Limita:Limitb],predat$otbk[Limitc:Limitd]) 
    
    # parameters
    md2u<-glmmTMB(processed2~trend+sin.term+cos.term,data=predat,family=nbinom2)
    
    predat$fitted<-fitted(md2u)
    predat$resid<-residuals(md2u)
    
    #analysis 
    stats.shew<-stats.xbar.one(predat$resid)
    sd.xbar.shew<-sd.xbar.one(predat$resid,
                              std.dev = "SD",
                              k=2)
    shew.shew<-limits.xbar.one(center = stats.shew$center,
                               std.dev = as.double(sd.xbar.shew),
                               conf = 2)
    UCL.value.test<-ceiling(shew.shew[2])
    
    graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        outbk=df2$otbk[Limita:Limitd],
                        alert=NA,
                        thr=NA)#create column to complete later
    
    graph.p$alert[Limitc:Limitd]<-ifelse(predat$resid[Limitc:Limitd]>UCL.value.test,20,NA)
    graph.p$alert[Limita:Limitb]<-NA
    
    
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}

# 4.2.1.4.5 EWMA (ewma)
# package: otsad
# We run the following function:

iter.method.ewma<-function (df,nstep,outbk,Limita,Limitb,Limitc,Limitd) {
  t<-1
  df$obs<-df$count
  df$otbk<-outbk
  
  step<-0 # counts the steps
  # create data frames to save results
  result<-data.frame(index=c(1,2,3,4))#alerts
  result3<-data.frame(index=c(1,2,3,4))#thresholds
  result4<-data.frame(index=c(1,2,3,4))#prueba
  
  ## loop
  repeat {
    
    #for the moving window
    limita<-Limita+step;
    limitb<-Limitb+step;
    limitc<-Limitc+step;
    limitd<-Limitd+step
    
    train<-df[limita:limitb,]
    test<-df[limitc:limitd,]
    df2<-rbind(train,test)
    
    #analysis 
    ewma.otsad<-CpSdEwma(df2$otbk[Limita:Limitd],Limitb,0.01,2)
    
    graph.p<-data.frame(obs=df2$count[Limita:Limitd],
                        date=df2$week[Limita:Limitd],
                        fitted=df2$count[Limita:Limitd]*0,
                        outbk=df2$otbk[Limita:Limitd],
                        thr=NA)#create column to complete later
    graph.p$thr[Limitc:Limitd]<-ewma.otsad$ucl[Limitc:Limitd]
    graph.p$alert<-ifelse(graph.p$outbk>graph.p$thr,20,NA)
    
    
    #store the results of each step of the analysis in a data frame
    select<-1+step+1
    result[,select]<-graph.p$alert[Limitc:Limitd]
    result3[,select]<-graph.p$thr[Limitc:Limitd]
    result4[,select]<-graph.p$date[Limitc:Limitd]
    
    step<-step+1
    if (step==nstep) break # to finish the loop
  }
  
  # get the results
  final<-list(result,result3,result4)
  final
  
}


# 4.1.2 ITERATE N ANALYSIS AND GET THE STATISTICS
# The following functions automatize the process:
	# "extract_roll" is a function called by "finaltest" to extract results. This funcion is used internally by "finaltest". It only needs to be run once before "finaltest". 

extract_roll<-function(iterout,dates,ini){
  x<-iterout[[1]]
  y<-iterout[[2]]
  
  df<-data.frame(matrix(,nrow=length(x)+3,ncol=length(x)))
  df2<-df
  extract<-x 
  extract2<-y
  pos1<-1
  pos1.2<-1
  dates<-dates[ini:(ini+length(x)+1)]
  listosave<-list()
  
  repeat {
    
    pos2<-pos1+3    
    colnames(df)[pos1]<-paste("step",pos1-1)
    roll<-data.frame(evo=matrix(,nrow=length(x)+3,ncol=1))
    roll$evo[pos1:pos2]<-extract[[pos1]]
    df[pos1]<-roll$evo
    pos1<-pos1+1
    
    if (pos2==length(x)+3) break
  }
  
  repeat {
    
    pos2.2<-pos1.2+3
    
    colnames(df2)[pos1.2]<-paste("step",pos1.2-1)
    
    roll2<-data.frame(evo=matrix(,nrow=length(y)+3,ncol=1))
    roll2$evo[pos1.2:pos2.2]<-extract2[[pos1.2]]
    df2[pos1.2]<-roll2$evo
    pos1.2<-pos1.2+1
    
    if (pos2.2==length(y)+3) break
  }
  
  
  df<-df[,-1]
  df<-df[-1,]
  df$score<-rowSums(df,na.rm=T)/20
  df$state<-rowMeans(df,na.rm=T)
  rownames(df)<-dates
  
  df2<-df2[,-1]
  df2<-df2[-1,]
  df2$avg<-rowMeans(df2,na.rm=T)
  rownames(df2)<-dates
  
  
  
  listosave[[1]]<-df
  listosave[[2]]<-df2
  
  return(listosave)
}


	# "finaltest" automatizes the whole analysis  
	# The arguments for this function are:
		# df: data frame with the original data to be tested. We used the file that we called "data" in the section 2
		# otbk: data frame with the data with the synthetic outbreaks. We used the data frame obtained in the section 3
		# n: number of iterations. The number of iterations should be equal to the number of outbreak simulations. We used 1,000 
		# funct: indicate the function to be used {far, farflex, breg, hw, ari, bay, rki, ears, far_c. breg_c, breg_s, ewma}. If we have copied the code for these methods, the function will identify the method and use the proper function
		# "Limita","Limitb","Limitc","Limitd": These are the limits of the training and testing windows. We used: 1,157,158,161. This can be modified accordingly to use different window sizes or add guard bands.
		# method: only when a function has different methods:
		  # if bay {bay1, bay2, bay3}
		  # if rki {rki1, rki2, rki3}
		  # if ears {ears1, ears2, ears3}

finaltest<- function (df,otbk,n,funct,nsteps,Limita,Limitb,Limitc,Limitd,method){
  colm<-df
  ldata<-nrow(colm)
  colm$obs0<-colm$count
  #lists to store results
  listres<-list()
  listsummaries<-list()
  #init for iterations
  iterb<-1
  iterc<-2
  
  #loop to repeat the analysis n times
  repeat {
    
    colm$otbk<-otbk[,iterc]
    
    resulto<-if (funct=="far") {iter.method.far(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd) 
    } else if (funct=="farflex") {iter.method.farflex(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd)
    } else if (funct=="breg") {iter.method.breg(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd)
    } else if (funct=="hw") {iter.method.hw(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd)
    } else if (funct=="ari") {iter.method.ari(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd)
    } else if (funct=="bay") {iter.method.bay(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd,method)
    } else if (funct=="rki") {iter.method.rki(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd,method)
    } else if (funct=="ears") {iter.method.ears(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd,method)
    } else if (funct=="far_c") {iter.method.cusum2(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd)
    } else if (funct=="breg_c") {iter.method.cusum3(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd)
    } else if (funct=="far_s") {iter.method.shewhart2(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd)
    } else if (funct=="breg_s") {iter.method.shewhart3(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd)
    } else if (funct=="ewma") {iter.method.ewma(colm,nsteps,colm$otbk,Limita,Limitb,Limitc,Limitd)
    }
    
    resulte<-extract_roll(resulto,colm$week,Limitc) ## function to extract results
    
    # gathering results of each iteration
    resile<-data.frame(date=colm$week[Limitc:ldata],
                       iter=rep(iterb,ldata-Limitb),
                       obs0=colm$obs0[Limitc:ldata],
                       obso=colm$otbk[Limitc:ldata],
                       thr=resulte[[2]]$avg,
                       alarm=resulte[[1]]$score)
        
    # commands to do statistics
    
    resile$outbky<-ifelse(resile$obs0==resile$obso,NA,1) 
    resilesummary<-dplyr::filter(resile,resile$outbky==1)
    resilesummary$is.detected<-ifelse(resilesummary$alarm>0,1,NA) # detection of any signal during the outbreak
    length.rs<-nrow(resilesummary)
    resilesummary$position<-seq(1,length.rs,1)
    resilesummary$alarm.position<-resilesummary$is.detected*resilesummary$position
    
    grabsums<-data.frame(signals=sum(resilesummary$outbky),# number of signals injected in an outbreak
                         is.detected=ifelse(sum(resilesummary$alarm)>0,1,0), # detection of any signal during the outbreak
                         detected.signals=sum(resilesummary$is.detected,na.rm=T), #number of signals detected in an outbreak
                         early.detection=min(resilesummary$alarm.position,na.rm=T), # first week in which the signal is detected
                         early.detection.per=min(resilesummary$alarm.position,na.rm = T)/max(resilesummary$position,na.rm=T))
    
    # save statistics in lists
    listres[[iterb]]<-resile
    listsummaries[[iterb]]<-grabsums
    
    iterb<-iterb+1
    iterc<-iterc+1
    
    if (iterb-1==n) break #stop loop
  }
  #recover results
  finsum<-do.call(rbind,listsummaries)  
  finres<-do.call(rbind,listres)
  fin<-list(finres,finsum,listres)###
  fin
}

# 4.1.3 EXAMPLE
# Once all the previous functions have been run, use "finaltest" to run the analysis and obtain the results:
# Example 1: it uses the example data, a data frame with 5 time series with synthetic outbreaks (k=4 in this case), it runs 5 iterations, 49 steps in the testing rolling window, it selects the Farrington's algorithm and defines the limits as 1:157 for the training window and 158:161 for the testing window

example1<-finaltest(data,dataotbk_k4,5,"far",49, 1, 157, 158,161 )

# The list of methods that can be chosen are: far, farflex, breg, hw, ari, bay, rki, ears, far_c, breg_c, far_s, breg_s, ewma 

# Example 2: if the algorithm selected has different ways to be used, you have to specify the method. This is for "bay", "rki" and "ears"

example2<-finaltest(data,dataotbk_k4,5,"bay",49, 1, 157, 158,161,"bay1")


# 4.2 METHODS RUN IN PYTHON
# The LSTM was performed in python, but the results were evaluated in R
# The outbreaks were generated in R using otbk.gen4. In our case, we generated 1,000 time series with different synthetic outbreaks.
# This data frame is saved in a csv format to use it in Python. This can be done in R using the function "write.csv"

write.csv(dataotbk,"datalstm.csv")

# 4.2.1. ANALYSIS: LSTM (lstm)
# The following part of the code runs in Python

# We first load the libraries that we will need in the analysis
# We will indicate in the code the points in which values can be changed, for example, if we want to use a different testing window, training window, etc.

import numpy as np
from sklearn.svm import SVR 
import matplotlib.pyplot as plt 
import pandas as pd 
import tensorflow as tf

# Create the Stacked LSTM model
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import LSTM

# Load the data that we previously save as csv
df = pd.read_csv('{path}/datalstm.csv',skiprows=1,header=None)

# Generate data frames to store the results
allsteps = pd.DataFrame()
allsteps.insert(0,"position",range(1,5)) # This range is set like this for testing windows of 4 positions. Change if appropriate, e.g. (1,6) for five
allsteps_training = pd.DataFrame()
allsteps_training.insert(0,"position",range(1,147)) # This range is set like this for training windows of 156 positions. This LSTM will use a time step of 10, so 10 is substracted to 156 to set the range
allsteps_testing = pd.DataFrame()
allsteps_testing.insert(0,"position",range(1,5)) # This range is set like this for testing windows of 4 positions.
alliters_thresholds = pd.DataFrame()
alliters_predictions = pd.DataFrame()
alliters_wind_training = pd.DataFrame()
alliters_wind_testing = pd.DataFrame()

# Indications to calculate thresholds 
thr2 = pd.DataFrame()
thr2.insert(0,"position",[1]) # inserts should be appropriate

# Now we create a loop. This loop conducts the analysis for each of the columns in the data frame of synthetic outbreaks that we created earlier. As we created a data frame with 1,000 time series with outbreaks, we will have to repeat the analysis 1,000 times.
#loop for 1000 iterations
for x in range(2,1002): # Adapt to n+2 iterations. The first column is not used as it contains the dates.     
    df1 = df[x]
    for i in range(0,49):  # We moved one position the beginnig of the 4-week window of analysis 49 times. This value might be wanted to be different in other settings
        df2 = df1.iloc[i:i+161] # This selects the first 161 positions in each step (157 training + 4 testing). It must be changed if the user wanted to use other ranges above
        df2.index = pd.RangeIndex(start = 0,stop = 161) # change the start and stop points if necessary
        # scale
        from sklearn.preprocessing import MinMaxScaler
        scaler=MinMaxScaler(feature_range=(0,1))
        df3=scaler.fit_transform(np.array(df2).reshape(-1,1))
        # split in train and test
        training_size=int(157) # use the first 157 positions for training. Change the value if the setting is different
        test_size=len(df3)-training_size
        train_data = df3[0:training_size,:]
        test_data = df3[training_size-(10+1):len(df3),:1]
        # convert an array of values into a dataset matrix
        def create_dataset(dataset, time_step=1):
            dataX, dataY = [], []
            for i in range(len(dataset)-time_step-1):
                a = dataset[i:(i+time_step), 0]   
                dataX.append(a)
                dataY.append(dataset[i + time_step, 0])
            return np.array(dataX), np.array(dataY)
        # reshape into X=t,t+1,t+2,t+3 and Y=t+4
        time_step = 10
        X_train, y_train = create_dataset(train_data, time_step)
        X_test, ytest = create_dataset(test_data, time_step)
        # reshape input to be [samples, time steps, features] which is required for LSTM
        X_train2 =X_train.reshape(X_train.shape[0],X_train.shape[1] , 1)
        X_test2 = X_test.reshape(X_test.shape[0],X_test.shape[1] , 1)
        #LSTM code
        model=Sequential()
        model.add(LSTM(50,return_sequences=True,input_shape=(10,1)))
        model.add(LSTM(50,return_sequences=True))
        model.add(LSTM(50))
        model.add(Dense(1))
        model.compile(loss='mean_squared_error',optimizer='adam')
        model.fit(X_train2,y_train,validation_data=(X_test2,ytest),epochs=100,batch_size=64,verbose=1)
        # get results
        ### Lets Do the prediction and check performance metrics
        train_predict=model.predict(X_train2)
        test_predict=model.predict(X_test2)
        thr = np.std(train_data)*2 #to get thresholds
        ##Transformback to original form
        train_predict2=scaler.inverse_transform(train_predict)
        test_predict2=scaler.inverse_transform(test_predict)
        # retrieve results
        transf_predict = pd.DataFrame(data=test_predict2)
        allsteps[i+1] = transf_predict   
        allsteps_training[i+1] = train_predict
        allsteps_testing[i+1] = test_predict
        thr2[i] = thr
    # manage results
    alliters_wind_training = pd.concat([alliters_wind_training,allsteps_training])
    alliters_wind_testing = pd.concat([alliters_wind_testing,allsteps_testing])
    thr3 = pd.DataFrame()
    for i in range(1,50):
        thr3[i] = thr2.iloc[0][i-1]+allsteps_testing[i] #thresholds
    thresholds = pd.DataFrame(scaler.inverse_transform(thr3))
    alliters_thresholds = pd.concat([alliters_thresholds,thresholds])
    alliters_predictions = pd.concat([alliters_predictions,allsteps])

# The LSTM is finished. Just some final modifications of the dataset to use it later in R

alliters_thresholds.insert(49,"iteration", np.repeat(np.arange(1,3),len(alliters_thresholds)/1000))# the user can change steps (49) and iterations (1000) if the settings are different    
alliters_predictions.insert(49,"iteration", np.repeat(np.arange(1,3),len(alliters_predictions)/1000))# the user can change steps (49) and iterations (1000) if the settings are different  
del alliters_wind_testing[0]
del alliters_wind_training[0] 
alliters_wind_training = scaler.inverse_transform(alliters_wind_training) 
alliters_wind_training = scaler.inverse_transform(alliters_wind_testing)                         
alliters_wind_training.insert(49,"iteration", np.repeat(np.arange(1,3),len(alliters_wind_training)/1000))# the user can change steps (49) and iterations (1000) if the settings are different  
alliters_wind_testing.insert(49,"iteration", np.repeat(np.arange(1,3),len(alliters_wind_testing)/1000))# the user can change steps (49) and iterations (1000) if the settings are different  

# 4.2.2 EXPORTING RESULTS FROM PYTHON
# The next code saves the results in csv so R can import them and use them       

alliters_thresholds.to_csv("THRESHOLDS_k4.csv")
alliters_wind_training.to_csv("TRAINING_k4.csv")
alliters_wind_testing.to_csv("TESTING_k4.csv")


# repeat for each value of k
# This produces four datasets per k value: thresholds, training,testing.
# Thresholds is used for the lstm method
# Training and Testing are used for methods that use lstm pre-processing and then apply a different method (lstm_c/lstm_s). We will describe them in the next section

# 4.2.3 EVALUATING THE PRESENCE OF OUTBREAKS AND GETTING MEASURES
# The next part of the analysis is run in R. 
# Import the results of the LSTM to objects in R. Each k value used is in a different file

lstm.k4.th<-read.csv("THRESHOLDS_k4.csv")
lstm.k8.th<-read.csv("THRESHOLDS_k8.csv")
lstm.k12.th<-read.csv("THRESHOLDS_k12.csv")
lstm.k16.th<-read.csv("THRESHOLDS_k16.csv")
lstm.k20.th<-read.csv("THRESHOLDS_k20.csv")

# 4.2.3.1 IDENTIFY OUTBREAKS AND GET RESULTS
# Now we need to creat a function to identify the outbreaks and extract performance results
# In this case we have run 1,000 analysis, so the function automatizes extracting the results of these 1,000 analysis

# We will need to use two functions
# lstm.alarm.detection → evaluates whether an outbreaks was detected or not
# lstm.summary → get the results

# The arguments of "lstm.alarm.detection" are:
	# "datalstm.otbk": This is the data frame we created in section 3.
	# "lstm.th": The thresholds that we obtain
	# "niter: We need to indicate how many analysis we ran. In our case 1,000
	# "test.start" and "test.end": to indicate the positions of the window of analysis. We only evaluate thresholds here.
	# "nsteps": the number of steps in the testing rolling window. In our case 49
# We need to paste the following function: 

lstm.alarm.detection <-function (datalstm.otbk,lstm.th,niter,test.start,test.end,nsteps){
  #stop.point<-stop.point+2
  obsvrd0<-datalstm.otbk[(test.start:test.end),]
  obsvrd1<-data.frame(point=c(1,2,3,4))
  selection<-2
  obsvrd2<-data.frame()
  
  repeat {
    obsvrd3<-obsvrd0[selection]
    stept<-1
    
    repeat {
      obsvrd4<-data.frame(obsvrd3[stept:(stept+3),])
      colnames(obsvrd4)<-paste("step",stept)
      obsvrd1[(stept+1)]<-obsvrd4
      stept<-stept+1
      
      if (stept==nsteps+1) break 
      
    }
    obsvrd2<-rbind(obsvrd2,obsvrd1)
    selection<-selection+1
    
    if (selection==niter+2) break # 
    
  }
  
  obsvrd2$iteration<-rep(1:niter,each=4)
  cbind(ifelse(obsvrd2[2:50]>lstm.th[2:50],1,0),obsvrd2[51])
}

# We use the next function "lstm.summary" to get summaries
# The arguments are:
	# "alarms": an object with the result of "lstm.alarm.detection" 
	# "data": the example data we are working with
	# "datalstm.otbk": the data frame with the outbreaks that we generated in the previous section
	# "test.start" and "test.end": to indicate the positions of the window of analysis.
	# "nsteps": number of steps of the analysis. In our case 49
	# "niter": number of iterations. In our case 1000
# We run the following function:

lstm.summary<-function(alarms,data,datalstm.otbk,test.start,test.end,nsteps,niter){
  ldata<-nrow(data)
  intervall<-ldata-test.start+1
  dat1<-data.frame(date=data$week)
  dat2<-data.frame()
  dat3<-datalstm.otbk[(test.start:test.end),]
  dat6<-data.frame()
  
  iterat<-1
  
  repeat{
    dat4<-filter(alarms,iteration==iterat)
    stept<-1
    
    repeat{
      dat1[(stept+1)]<-c(rep(NA,test.start+stept-2),as.vector(t(dat4[stept])),(rep(NA,intervall.t-stept-3)))
      colnames(dat1)[stept+1]<-paste("step",stept)
      stept<-stept+1
      if (stept==nsteps+1) break
    }
    dat5<-dat1[test.start:test.end,]
    dat6<-rbind(dat6,dat5)
    iterat<-iterat+1
    
    if (iterat==niter+1) break 
  }
  
  dat6$score<-rowSums(dat6[2:nsteps],na.rm=T)
  listotb<-gather(datalstm.otbk[test.start:test.end,2:(niter+1)])
  dat7<-data.frame(date=rep(data$week[test.start:ldata],(iterat-1)),
                   iteration=rep(1:(iterat-1),each=intervall),
                   obs0=rep(data$count[(test.start:ldata)],(iterat-1)),
                   obso=listotb$value,
                   alarm=ifelse(dat6$score>0,20,0))
  dat7$otbky=ifelse(dat7$obso>dat7$obs0,1,0)
  
  iterat<-1
  resultsf4<-data.frame(signals=NA,is.detected=NA,detected.signals=NA,early.detection=NA)
  
  repeat {
    resultsf3<-filter(dat7,iteration==iterat)
    resultsf4[iterat,]$signals<-sum(resultsf3$otbky)
    resotbk<-filter(resultsf3,otbky==1)
    resultsf4$is.detected[iterat]<-ifelse(sum(resotbk$alarm)>19,1,0)
    resultsf4$detected.signals[iterat]<-sum(resotbk$alarm)/20
    resultsf4$early.detection[iterat]<-ifelse(resotbk$alarm[1]!=0,1,0)
    
    iterat<-iterat+1
    
    if (iterat==niter+1) break
    
  }
  
  resultsf4
  
  
}    

# 4.2.3.2 EXAMPLE 

	# 1. GENERATE OUTBREAKS FOR K = 4 AND 1000 ITERATIONS WITH 49 ROLLING WINDOWS OF 4 WEEKS
	# after pasting the function "otbk.gen4"

dataotbk_k4<-otbk.gen4(4,data,157,1000)

	# 2. RUN THE LSTM IN PYTHON AS DESCRIBED IN SECTION 4.1.1
	# 3. SAVE LSTM RESULTS

lstm.k4.th<-read.csv("THRESHOLDS_k4.csv")

	# 4. EVALUATE THE ANALYSIS AND GET RESULTS

lstm.k4.test<-lstm.alarm.detection(dataotbk_k4,lstm.k4.th,1000,157,161) 
lstm.k4.summary<-lstm.summary(lstm.k4.test,data,dataotbk_k4,157,161,49,1000) 

# 4.3 METHODS RUN IN PYTHON AND R
# This section covers methods that are implemented in R but require a preprocessing using LSTM (lstm_c and lstm_s)
# The LSTM analysis is the same we described in the previous section but we will work with the csv that we created there for Training and Testing, instead of Thresholds.

# 4.3.1 IMPORT LSTM RESULTS
# Therefore, we first import the csv's into R.

lstm.k4.training<-read.csv("TRAINING2_k4.csv")
lstm.k4.testing<-read.csv("TESTING2_k4.csv")

# 4.3.2 ANALYSIS IN R
# packages: qcc, tidyr and dplyr
# This function present the following arguments:
	# "pred.training": indicate the prediction for the training part obtained in the LSTM
	# "pred.testing": indicate the prediction for the training part obtained in the LSTM
	# "data.counts": sample data
	# "datalstm.otbk": the data frame with outbreaks that we generated in section 3
	# "iterations": the number of iterations
	# "Limita","Limitb","Limitc","Limitd": These are the limits of the training and testing windows. We used: 1,157,158,161
	# "nsteps": the number of steps in the testing rolling window
# the number of outbreaks generated, iterations and LSTM analysis should be the same

# 4.3.2.1 CUSUM with LSTM preprocessing (lstm_c)
# We run the following function:


cusum1.test<-function (pred.training,pred.testing,data.counts,datalstm.otbk,iterations,Limita,Limitb,Limitc,Limitd,nsteps) {
  ldata<-nrow(datalstm.otbk)
  iterat<-1
  
  matrix_observations <-function (data){
    
    
    obsvrd1<-data.frame(point=seq(Limita,Limitd,1))
    stept<-1
    
    repeat {
      obsvrd4<-data.frame(data$count[stept:(stept+160)])
      obsvrd1[(stept+1)]<-obsvrd4
      colnames(obsvrd1)[stept+1]<-paste("step",stept)
      stept<-stept+1
      
      if (stept==nsteps+1) break 
      
    }
    obsvrd1
    
    
    
  }
  
  refobs<-matrix_observations(data.counts)
  
  resultsf<-list()
  
  repeat {
    dat1<-filter(pred.training,iteration==iterat)
    dat2<-filter(pred.testing,iteration==iterat)
    dat3<-rbind(dat1,dat2)
    dat4<-dat3[(Limita+1):(nsteps+1)]-(data.frame(rbind(refobs[Limita:(Limitb-11),2:(nsteps+1)],refobs[Limitc:Limitd,2:(nsteps+1)])))
    
    result.alert<-data.frame(index=c(1,2,3,4))#alerts
    result.threshold<-data.frame(index=c(1,2,3,4))#thresholds
    result.date<-data.frame(index=c(1,2,3,4))#prueba
    
    stept<-1
    Limita2<-0; Limitb2<-156; Limitc2<-157; Limitd2<-160 #defines the boundaries of the window of analysis
    
    repeat {
      df1<-data.counts
      df2<-dat4[stept]
      
      df3<-datalstm.otbk[iterat+1]
      
      #for the moving window
      limita<-Limita2+stept;
      limitb<-Limitb2+stept;
      limitc<-Limitc2+stept;
      limitd<-Limitd2+stept
      
      train<-df1[limita:limitb,]
      test<-df1[limitc:limitd,]
      df4<-rbind(train,test)
      
      trainotb<-data.frame(otb=df3[limita:limitb,])
      testotb<-data.frame(otb=df3[limitc:limitd,])
      df5<-rbind(trainotb,testotb)
      
      resid<-data.frame(resid=df2[1:146,])
      testt<-data.frame(nd=df2[147:150,])
      
      md.cusum.t<-cusum(resid$resid,
                        center=mean(resid$resid,na.rm=T),
                        std.dev=sd(resid$resid,na.rm=T),
                        newdata=testt$nd,
                        se.shift=1,
                        decision.interval=2,
                        plot=F)
      
      graph.p<-data.frame(obs=df4$count,
                          date=df4$week,
                          outbk=df5$otb,
                          alert=NA,
                          thr=NA)#create column to complete later
      
      graph.p2<-data.frame(position=seq(Limita,(Limitb-7),1),link=NA) # 7 because of the 10 time steps in LSTM and 4-week period
      #graph.p2$link<-replace(graph.p$link,list=md.cusum.t$violations$upper,values=20)
      
      graph.p$alert[Limitc:Limitd]<-graph.p2$link[(Limitb-10):(Limitb-7)]
      
      select<-stept+1
      result.alert[,select]<-graph.p$alert[Limitc:Limitd]
      result.threshold[,select]<-graph.p$outbk[Limitc:Limitd]
      result.date[,select]<-graph.p$date[Limitc:Limitd]
      
      stept<-stept+1
      
      if (stept==(nsteps+1)) break 
    }
    
    
    result.iter<-list(result.alert,result.threshold,result.date)
    resultsf[[iterat]]<-result.iter
    
    iterat<-iterat+1
    if (iterat==(iterations+1)) break
    
  }
  
  
  df6<-gather(datalstm.otbk[Limitc:ldata,1:iterations+1])
  
  resultsf2<-data.frame(date=rep(data.counts$week[Limitc:ldata],iterations),
                        iteration=rep(1:iterations,each=52),
                        obs0=rep(data.counts$count[Limitc:ldata],iterations),
                        obso=df6$value)
  
  df9<-data.frame()
  selectlist<-1
  repeat{
    stept2<-1
    df8<-data.frame(index=rep(NA,52))
    
    repeat {
      df7<-resultsf[[selectlist]][[1]]
      df7<-df7[,-(1)]
      
      df8[stept2+1]<-c(rep(NA,stept2-1),as.vector(t(df7[stept2])),rep(NA,(nsteps+1)-stept2-1))
      stept2<-stept2+1
      
      if (stept2==nsteps+1) break
      
    }
    
    selectlist<-selectlist+1
    df9<-rbind(df9,df8)
    
    if (selectlist==iterations) break
  }
  
  
  df9<-df9[,-(1)]
  df9$score<-rowSums(df9,na.rm=T)
  df9$alarm<-ifelse(df9$score>1,20,0)
  
  resultsf2$alarm<-df9$alarm
  resultsf2$otbky<-ifelse(resultsf2$obso>resultsf2$obs0,1,0)
  
  iterat<-1
  resultsf4<-data.frame(signals=NA,is.detected=NA,detected.signals=NA,early.detection=NA)
  
  repeat {
    resultsf3<-filter(resultsf2,iteration==iterat)
    resultsf4[iterat,]$signals<-sum(resultsf3$otbky)
    resotbk<-filter(resultsf3,otbky==1)
    resultsf4$is.detected[iterat]<-ifelse(sum(resotbk$alarm)>19,1,0)
    resultsf4$detected.signals[iterat]<-sum(resotbk$alarm)/20
    resultsf4$early.detection[iterat]<-ifelse(resotbk$alarm[1]!=0,1,0)
    
    iterat<-iterat+1
    
    if (iterat==iterations+1) break
    
  }
  
  resultsf4
  
}

# 4.3.2.2 SHEWHART with LSTM preprocessing (lstm_s)

shewhart.test<-function (pred.training,pred.testing,data.counts,datalstm.otbk,iterations,Limita,Limitb,Limitc,Limitd, nsteps) {
  ldata<-nrow(datalstm.otbk)
  iterat<-1
  
  matrix_observations <-function (data){
    
    obsvrd1<-data.frame(point=seq(Limita,Limitd,1))
    
    stept<-1
    
    repeat {
      obsvrd4<-data.frame(data$count[stept:(stept+Limitb+3)])
      obsvrd1[(stept+1)]<-obsvrd4
      colnames(obsvrd1)[stept+1]<-paste("step",stept)
      stept<-stept+1
      
      if (stept==nsteps+1) break 
      
    }
    obsvrd1
       
      }
  
  refobs<-matrix_observations(data.counts)
  
  resultsf<-list()
  
  repeat {
    dat1<-filter(pred.training,iteration==iterat)
    dat2<-filter(pred.testing,iteration==iterat)
    dat3<-rbind(dat1,dat2)
    #dat4<-dat3[2:(nsteps+1)]-(data.frame(rbind(refobs[1:(Limitb-11),2:(nsteps+1)],refobs[Limitc:Limitd,2:(nsteps+1)])))
    dat4<-dat3[2:50]-(data.frame(rbind(refobs[1:146,2:50],refobs[158:161,2:50])))
    
    result.alert<-data.frame(index=c(1,2,3,4))#alerts
    result.threshold<-data.frame(index=c(1,2,3,4))#thresholds
    result.date<-data.frame(index=c(1,2,3,4))#prueba
    
    stept<-1
    Limita2<-0; Limitb2<-156; Limitc2<-157; Limitd2<-160 #defines the boundaries of the window of analysis
    
    repeat {
      df1<-data.counts
      df2<-dat4[stept]
      
      df3<-datalstm.otbk[iterat+1]
      
      #for the moving window
      limita<-Limita2+stept;
      limitb<-Limitb2+stept;
      limitc<-Limitc2+stept;
      limitd<-Limitd2+stept
      
      train<-df1[limita:limitb,]
      test<-df1[limitc:limitd,]
      df4<-rbind(train,test)
      
      trainotb<-data.frame(otb=df3[limita:limitb,])
      testotb<-data.frame(otb=df3[limitc:limitd,])
      df5<-rbind(trainotb,testotb)
      
      resid<-data.frame(resid=df2[1:(Limitb-11),])
      testt<-data.frame(nd=df2[(Limitb-10):(Limitb-7),])
      
      stats.shew<-stats.xbar.one(resid$resid)
      sd.xbar.shew<-sd.xbar.one(resid$resid, 
                                std.dev = "SD",
                                k=2)
      shew.shew<-limits.xbar.one(center = stats.shew$center,
                                 std.dev = as.double(sd.xbar.shew),
                                 conf = 2)
      UCL.value.test<-ceiling(shew.shew[2])
      
      
      graph.p<-data.frame(obs=df4$count,
                          date=df4$week,
                          outbk=df5$otb,
                          alert=NA,
                          thr=NA)#create column to complete later
      
      graph.p$alert[Limitc:Limitd]<-ifelse(graph.p$outbk[Limitc:Limitd]>UCL.value.test,20,NA)
      
      select<-stept+1
      result.alert[,select]<-graph.p$alert[Limitc:Limitd]
      result.threshold[,select]<-graph.p$outbk[Limitc:Limitd]
      result.date[,select]<-graph.p$date[Limitc:Limitd]
      
      stept<-stept+1
      
      if (stept==nsteps+1) break 
    }
    
    
    result.iter<-list(result.alert,result.threshold,result.date)
    resultsf[[iterat]]<-result.iter
    
    iterat<-iterat+1
    if (iterat==iterations+1) break
    
  }
  
  df6<-gather(datalstm.otbk[Limitc:ldata,1:iterations+1])
  
  resultsf2<-data.frame(date=rep(data.counts$week[Limitc:ldata],iterations),
                        iteration=rep(1:iterations,each=52),
                        obs0=rep(data.counts$count[Limitc:ldata],iterations),
                        obso=df6$value)
  
  df9<-data.frame()
  selectlist<-1
  repeat{
    stept2<-1
    df8<-data.frame(index=rep(NA,52))
    
    repeat {
      df7<-resultsf[[selectlist]][[1]]
      df7<-df7[,-(1)]
      
      df8[stept2+1]<-c(rep(NA,stept2-1),as.vector(t(df7[stept2])),rep(NA,nsteps+1-stept2-1))
      stept2<-stept2+1
      
      if (stept2==nsteps+1) break
      
    }
    
    selectlist<-selectlist+1
    df9<-rbind(df9,df8)
    
    if (selectlist==iterations) break
  }
  
  
  df9<-df9[,-(1)]
  df9$score<-rowSums(df9,na.rm=T)
  df9$alarm<-ifelse(df9$score>1,20,0)
  
  resultsf2$alarm<-df9$alarm
  resultsf2$otbky<-ifelse(resultsf2$obso>resultsf2$obs0,1,0)
  
  iterat<-1
  resultsf4<-data.frame(signals=NA,is.detected=NA,detected.signals=NA,early.detection=NA)
  
  repeat {
    resultsf3<-filter(resultsf2,iteration==iterat)
    resultsf4[iterat,]$signals<-sum(resultsf3$otbky)
    resotbk<-filter(resultsf3,otbky==1)
    resultsf4$is.detected[iterat]<-ifelse(sum(resotbk$alarm)>19,1,0)
    resultsf4$detected.signals[iterat]<-sum(resotbk$alarm)/20
    resultsf4$early.detection[iterat]<-ifelse(resotbk$alarm[1]!=0,1,0)
    
    iterat<-iterat+1
    
    if (iterat==iterations+1) break
    
  }
  
  resultsf4
  
}


# 4.3.2.3 EXAMPLES

example3<-cusum1.test(TRAINING_k4,TESTING_k4,data,dataotbk_k4,1000,1,157,158,161,49) # with 1000 iterations 
example4<-shewhart.test<(TRAINING_k4,TESTING_k4,data,dataotbk_k4,1000,1,157,158,161,49)