libraries = c("nleqslv","dplyr","magrittr","tidyr","rEDM","ggplot2","readxl",
"gridExtra","zoo","scales","humidity")
for(x in libraries) { library(x,character.only=TRUE,warn.conflicts=FALSE,quietly=TRUE) }
### to show the plots as svg-graphics in Jupyter
### although this has some problems when you export the notebook
### to html or other formats
options(jupyter.plot_mimetypes = "image/svg+xml")
packageVersion('rEDM')
set.seed(599213)
nsurr = 500
'%&%' <- function(x,y)paste0(x,y)
[1] ‘0.7.2’
cntr = "Denmark"
df.long = read.csv('Sugihara2012_dataset.txt',header = TRUE)
df.long %<>%
filter(country == cntr) %>%
filter(year >= 1996)
df.long %>% head
| country | variable | year | month | day | value |
|---|---|---|---|---|---|
| Denmark | flu | 1996 | 1 | 1 | NaN |
| Denmark | flu | 1996 | 1 | 8 | NaN |
| Denmark | flu | 1996 | 1 | 15 | NaN |
| Denmark | flu | 1996 | 1 | 22 | NaN |
| Denmark | flu | 1996 | 1 | 29 | NaN |
| Denmark | flu | 1996 | 2 | 5 | NaN |
df.in = df.long %>%
select(-country) %>%
spread(variable,value) %>%
mutate(date = ISOdate(year,month,day)) %>%
select(-year,-month,-day) %>%
select(date,flu,everything())
df.in %>% head
| date | flu | AH | PRCP | RH | T |
|---|---|---|---|---|---|
| 1996-01-01 12:00:00 | NaN | 3.314742 | 6.60 | 0.827500 | 27.1 |
| 1996-01-08 12:00:00 | NaN | 4.991437 | 9.54 | 0.954702 | 34.0 |
| 1996-01-15 12:00:00 | NaN | 4.310679 | 8.50 | 0.928307 | 30.9 |
| 1996-01-22 12:00:00 | NaN | 3.044399 | 7.71 | 0.790498 | 26.1 |
| 1996-01-29 12:00:00 | NaN | 3.531248 | 7.20 | 0.878098 | 27.2 |
| 1996-02-05 12:00:00 | NaN | 2.367563 | 5.40 | 0.729602 | 21.8 |
make_block = function(data,cols,delays,lib=c(1,NROW(data))){
lib = matrix(lib,ncol = 2)
data = as.matrix(data)
ncol = length(cols)
nrow = dim(data)[1]
block = array(NA,dim = c(nrow,ncol))
colnames(block) = 1:ncol
for (i in 1:ncol){
I = 1:nrow
I_delay <- intersect(I,I+delays[i])
block[I_delay-delays[i],i] = data[I_delay,cols[i]]
if (delays[i] < 0){
# remove data points that fall at start of lib segments
block[lib[,1] - (0:(delays[i]+1)),i] = NA
colnames(block)[i] = paste(colnames(data)[cols[i]],'_t-',abs(delays[i]),sep="")
} else if (delays[i] > 0) {
# remove data points that fall at end of lib segments
block[lib[,2] - (0:(delays[i]+1)),i] = NA
colnames(block)[i] <- paste(colnames(data)[cols[i]],'_t+',abs(delays[i]),sep="")
} else {
colnames(block)[i] <- paste(colnames(data)[cols[i]],'_t',sep="")
}
}
return(block)
}
We define a function yearday_anom() that computes the seasonal cycle and day-of-year anomaly for a time-series. As described in the main text, this is done with smoothing splines.
Anomaly is the difference between the observed value and the smoothed spline.
yearday_anom = function(t,x){
# t: date formatted with POSIXt
# x: time-series values to compute seasonal mean and anomaly
doy = as.numeric(strftime(t, format = "%j"))
I_use = which(!is.na(x))
# create time indices to use for smoothing, replicating data to "wrap around"
doy_sm = rep(doy[I_use],3) + rep(c(-366,0,366),each=length(I_use))
x_sm = rep(x[I_use],3)
xsp = smooth.spline(doy_sm, y = x_sm, w = NULL, spar = 0.8, cv = NA,
all.knots = TRUE,keep.data = TRUE, df.offset = 0)
xbar = data.frame(t=t,doy=doy) %>%
left_join(data.frame(doy=xsp$x,xbar=xsp$y),by='doy') %>%
select(xbar)
out = data.frame(t=t,mean=xbar,anomaly=(x - xbar))
names(out) = c('date','mean','anomaly')
return(out)
}
out = yearday_anom(df.in$date,df.in$AH)
AH.bar = out$mean
AH.tilde = out$anomaly
options(repr.plot.width=7,repr.plot.height=4.5)
out %>%
ggplot(aes(x=date)) +
geom_line(aes(y=mean)) +
geom_point(aes(y=mean+anomaly),color="blue",shape=1) +
labs(x="date",y="Absolute humidity",title=cntr) +
theme_bw() -> p1_Denmark
p1_Denmark
Warning message:
“Removed 4 rows containing missing values (geom_point).”
png
AH.surrs = do.call(cbind,
lapply(1:500, function(i) {
I_na = is.na(AH.tilde)
out = AH.bar
out[I_na] = NA
out[!I_na] = out[!I_na] + sample(AH.tilde[!I_na],sum(!I_na),replace = FALSE)
return(out)
})
)
options(repr.plot.width=7,repr.plot.height=5)
plot(df.in$date,df.in$AH,type='l',col='blue',xlab='date',ylab='Absolute Humidity')
lines(df.in$date,AH.surrs[,1],col='red')
legend( x="topright", legend=c("Observed","Seasonal Surrogate"), col=c("blue","red"), lwd=1, lty=c(1,1),bg='white')
png
For set up, we need to determine the set of points to be predicted (pred input) to deal with the fact that many of the influenza time-series have strings of 0 counts. These strings will contain little or no causal information, and more practically can cause problems with how nearest neighbors are calculated for simplex projection when there are many 0 count vectors. We write a function that eliminates all vectors from the prediction that contain a string of at least \(E\) 0’s.
make_pred_nozero = function(time_series,E) {
I_zero_strings = which(time_series==0)
I_zero_strings = Reduce(intersect, lapply((0:E),function(offset) I_zero_strings-offset))
I_zero_strings = c(0,I_zero_strings,NROW(df.in)) # for convenience in next step
N_zero_strings = length(I_zero_strings)
lib_nozeroes = cbind(I_zero_strings[1:(N_zero_strings-1)]+1,I_zero_strings[2:(N_zero_strings)]-1)
lib_out = lib_nozeroes[which(lib_nozeroes[,2] > lib_nozeroes[,1]),]
return(lib_out)
}
df.in %>% mutate(index=1:n()) %>% filter(index>=600&index<615)
| date | flu | AH | PRCP | RH | T | index |
|---|---|---|---|---|---|---|
| 2007-06-25 12:00:00 | NaN | 10.598504 | 55.70 | 0.867344 | 57.60 | 600 |
| 2007-07-02 12:00:00 | NaN | 11.874325 | 69.20 | 0.898884 | 59.90 | 601 |
| 2007-07-09 12:00:00 | NaN | 10.996224 | 59.64 | 0.846580 | 59.40 | 602 |
| 2007-07-16 12:00:00 | NaN | 12.403229 | 19.48 | 0.835184 | 63.40 | 603 |
| 2007-07-23 12:00:00 | NaN | 11.702477 | 40.23 | 0.847995 | 61.20 | 604 |
| 2007-07-30 12:00:00 | NaN | 10.683401 | 42.80 | 0.779359 | 61.00 | 605 |
| 2007-08-06 12:00:00 | NaN | 14.497789 | 12.50 | 0.862870 | 67.15 | 606 |
| 2007-08-13 12:00:00 | NaN | 11.939355 | 52.16 | 0.806622 | 63.30 | 607 |
| 2007-08-20 12:00:00 | NaN | 14.267609 | 11.34 | 0.915726 | 64.85 | 608 |
| 2007-08-27 12:00:00 | NaN | 9.605718 | 26.29 | 0.818989 | 56.40 | 609 |
| 2007-09-03 12:00:00 | NaN | 9.804182 | 21.30 | 0.813357 | 57.20 | 610 |
| 2007-09-10 12:00:00 | NaN | 9.695090 | 17.28 | 0.835153 | 56.10 | 611 |
| 2007-09-17 12:00:00 | NaN | 10.795756 | 7.14 | 0.914172 | 56.60 | 612 |
| 2007-09-24 12:00:00 | NaN | 10.025608 | 18.80 | 0.922036 | 54.20 | 613 |
| 2007-10-01 12:00:00 | NaN | 8.960859 | 40.93 | 0.899003 | 51.70 | 614 |
I_zero_strings = which(df.in$flu==0)
I_zero_strings
I_zero_strings2 = Reduce(intersect, lapply((0:5),function(offset) I_zero_strings-offset))
I_zero_strings2
lapply((0:8),function(offset) I_zero_strings-offset)
Reduce is from the functional programming module of R ## Iterative function application: Funcall = function(f, …) f(…) ## Compute log(exp(acos(cos(0)) Reduce(Funcall, list(log, exp, acos, cos), 0, right = TRUE)
I_zero_strings = I_zero_strings2
I_zero_strings = c(0,I_zero_strings,NROW(df.in)) # for convenience in next step
I_zero_strings
N_zero_strings = length(I_zero_strings)
N_zero_strings
106
lib_nozeroes = cbind(I_zero_strings[1:(N_zero_strings-1)]+1,I_zero_strings[2:(N_zero_strings)]-1)
lib_nozeroes %>% head(16)
| 1 | 138 |
| 140 | 139 |
| 141 | 140 |
| 142 | 141 |
| 143 | 142 |
| 144 | 143 |
| 145 | 144 |
| 146 | 190 |
| 192 | 191 |
| 193 | 192 |
| 194 | 193 |
| 195 | 194 |
| 196 | 195 |
| 197 | 247 |
| 249 | 248 |
| 250 | 249 |
lib_out = lib_nozeroes[which(lib_nozeroes[,2] > lib_nozeroes[,1]),]
lib_out
| 1 | 138 |
| 146 | 190 |
| 197 | 247 |
| 254 | 299 |
| 310 | 325 |
| 327 | 351 |
| 363 | 378 |
| 380 | 403 |
| 407 | 429 |
| 432 | 456 |
| 463 | 509 |
| 519 | 561 |
| 571 | 614 |
| 616 | 750 |
| 752 | 757 |
| 772 | 822 |
| 824 | 831 |
| 835 | 856 |
| 866 | 917 |
| 922 | 969 |
We now determine the proper embedding dimension for cross-mapping following the procedure of Deyle et al. (2016). We determine \(E^*\) that gives optimal prediction of cross-mapping lagged 1 week. Note that we specificy the lib_size to be the largest possible number of points in the library (even though the actual number will be smaller because of NAs and the zero strings). This is for convenience so that the code will automatically use the largest possible lib_size.
block_temp = df.in %>% select(flu,AH)
lib_ccm = c(1,NROW(df.in))
Emax=8
out.temp = do.call(
rbind,
lapply(1:Emax, function(E_i) {
pred_ccm = make_pred_nozero(block_temp$flu,E_i)
ccm(block=block_temp,
E=E_i,
lib=lib_ccm,
pred=pred_ccm,
lib_sizes = NROW(block_temp),
exclusion_radius=0,
random_libs = FALSE,
num_sample=1,
tp = -1,
lib_column = 1,
target_column = 2,
silent = TRUE) }
)
)
out.temp
| E | tau | tp | num_neighbors | lib_column | target_column | lib_size | num_pred | rho | mae | rmse |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 1 | -1 | 2 | 1 | 2 | 614 | 400 | 0.17910471 | 1.843047 | 2.501677 |
| 2 | 1 | -1 | 3 | 1 | 2 | 594 | 400 | 0.05135658 | 1.951606 | 2.597812 |
| 3 | 1 | -1 | 4 | 1 | 2 | 577 | 399 | 0.22749008 | 1.751233 | 2.342287 |
| 4 | 1 | -1 | 5 | 1 | 2 | 559 | 399 | 0.04313842 | 1.883103 | 2.497428 |
| 5 | 1 | -1 | 6 | 1 | 2 | 542 | 406 | 0.06881939 | 2.056252 | 2.737585 |
| 6 | 1 | -1 | 7 | 1 | 2 | 526 | 416 | 0.29636337 | 1.700103 | 2.285878 |
| 7 | 1 | -1 | 8 | 1 | 2 | 510 | 415 | 0.34598041 | 1.645424 | 2.229613 |
| 8 | 1 | -1 | 9 | 1 | 2 | 494 | 415 | 0.32404071 | 1.673133 | 2.290292 |
E_star = out.temp$E[which.max(out.temp$rho)]
E_star
7
We then measure the un-lagged cross-map skill with this value of \(E^*\).
pred_ccm = make_pred_nozero(block_temp$flu,E_star)
df.out.ccm = ccm(block=block_temp,
E=E_star,
lib=lib_ccm,
pred = pred_ccm,
lib_sizes = NROW(block_temp),
exclusion_radius=0,
random_libs = FALSE,
num_sample=1,
tp = 0,
silent = TRUE)
df.out.ccm
| E | tau | tp | num_neighbors | lib_column | target_column | lib_size | num_pred | rho | mae | rmse |
|---|---|---|---|---|---|---|---|---|---|---|
| 7 | 1 | 0 | 8 | 1 | 2 | 510 | 415 | 0.3955595 | 1.560958 | 2.186334 |
df.out.ccm.surr = data.frame()
for (i_surr in 1:dim(AH.surrs)[2]) {
block_temp = data.frame(flu=df.in$flu,AH=AH.surrs[,i_surr])
out.temp = do.call(
rbind,
lapply(1:8, function(E_i){
pred_ccm = make_pred_nozero(block_temp$flu,E_i)
ccm(block=block_temp,
E=E_i,
lib=lib_ccm,
pred=pred_ccm,
lib_sizes = NROW(block_temp),
exclusion_radius=0,
random_libs = FALSE,
num_sample=1,
tp = -1,
lib_column = 1,
target_column = 2,
silent=TRUE)
}
)
)
E_star = out.temp$E[which.max(out.temp$rho)]
pred_ccm = make_pred_nozero(block_temp$flu,E_star)
out.temp = ccm(block = block_temp,
E = E_star,
lib = lib_ccm,
pred = pred_ccm,
lib_sizes = NROW(block_temp),
exclusion_radius = 0,
random_libs = FALSE,
num_sample=1,
tp = 0,
silent=TRUE)
df.out.ccm.surr <- df.out.ccm.surr %>% bind_rows(out.temp)
} # for i_surr
options(repr.plot.width=3.5,repr.plot.height=5)
boxplot(df.out.ccm.surr$rho, ylab='rho (CCM)', xlab=cntr, main='Flu cross-map AH')
points(1,df.out.ccm$rho, pch=8, col='red', cex=2, lwd=2)
png
df.out.ccm
| E | tau | tp | num_neighbors | lib_column | target_column | lib_size | num_pred | rho | mae | rmse |
|---|---|---|---|---|---|---|---|---|---|---|
| 7 | 1 | 0 | 8 | 1 | 2 | 510 | 415 | 0.3955595 | 1.560958 | 2.186334 |