Build reconstructions
We now build the full reconstruction using the selected models. This code takes about 30 seconds (depending on your computer), and will give you reconstruction results for all 30 available stations. To get results for all stations, skip to the next chunk to read the pre-computed results.
ldsFit <-
foreach(s = availIDs,
.packages = c('data.table', 'ldsr'),
.final = function(x) setNames(x, availIDs)) %dopar% {
Qa <- instQ[s]
p <- ldsChoice[s, p]
kwfMax <- ldsChoice[s, kwf]
u <- t(ensemblePCs[[s]][[kwfMax]][[p]])
transform = trans[s, trans]
LDS_reconstruction(Qa, u, u, 1200, transform = transform,
num.restarts = 100, return.raw = TRUE)
}
ldsFit <- readRDS('results/ldsFit.RDS')
We can also create the full 30-member ensembles using the following code, which takes more than an hour. It’s better to run this on a server.
As always, we provided pre-computed results for all stations, in the file results/ensemble_reconst.RDS.
ldsFitFull <-
foreach(s = stationIDs,
.packages = c('data.table', 'ldsr'),
.final = function(x) setNames(x, stationIDs)) %:%
foreach(kwfMax = kwfNames,
.final = function(x) setNames(x, kwfNames)) %:%
foreach(p = pNames,
.final = function(x) setNames(x, pNames)) %dopar% {
Qa <- instQ[s]
u <- t(ensemblePCs[[s]][[kwfMax]][[p]])
transform = trans[s, trans]
LDS_reconstruction(Qa, u, u, 1200, transform = transform,
num.restarts = 100, return.raw = TRUE)
}
ensembleRec <- lapplyrbind(ldsFitFull, lapplyrbind, lapplyrbind, '[[', 'rec')
colnames(ensembleRec)[1:3] <- c('ID', 'kwf', 'p')
ensembleRec[, c('Xl', 'Xu', 'Ql', 'Qu') := NULL]
setkey(ensembleRec, ID)
# Use RDS format to save space as it is compressed
saveRDS(ensembleRec, 'results/ensemble_reconst_rerun.RDS')
Streamflow history plots
Now let’s plot the reconstructed flow history (Figure 5). The plot function is provided in R/flow_history.R We need to calculate the standardize streamflow index by normalizing reconstructed flow with the observed mean and standard deviation (with log-transformation when necessary). The normalization constants are provided in data/instQsummary.csv.
instQsummary <- fread('data/instQsummary.csv', key = 'ID')
ldsFit <- readRDS('results/ldsFit.RDS')
ldsRec <- lapplyrbind(ldsFit, '[[', 'rec', id = 'ID')
ldsRec2 <- lapplyrbind(ldsFit, '[[', 'rec2', id = 'ID')
setkey(ldsRec, ID)
setkey(ldsRec2, ID)
fh <- flow_history(ldsRec, instQmeta, trans,
plotGap = TRUE,
stdType = 'inst', instSummary = instQsummary)
fhGrob <- patchworkGrob(fh)
fhWrap <- ggdraw(fhGrob)
yLine <- c(0.3539, 0.4860)
yText <- 0.375
fhAnnotated <- fhWrap +
draw_line(c(0.0730, 0.1370), yLine, colour = 'steelblue', size = 0.2) + #1
draw_line(c(0.0970, 0.1380), yLine, colour = 'steelblue', size = 0.2) + #1
draw_line(c(0.1180, 0.2360), yLine, colour = 'steelblue', size = 0.2) + #2
draw_line(c(0.3685, 0.2690), yLine, colour = 'steelblue', size = 0.2) + #2
draw_line(c(0.3890, 0.2975), yLine, colour = 'steelblue', size = 0.2) + #3
draw_line(c(0.5925, 0.3245), yLine, colour = 'steelblue', size = 0.2) + #3
draw_line(c(0.6120, 0.3545), yLine, colour = 'steelblue', size = 0.2) + #4
draw_line(c(0.6361, 0.3560), yLine, colour = 'steelblue', size = 0.2) + #4
draw_line(c(0.6575, 0.5625), yLine, colour = 'steelblue', size = 0.2) + #5
draw_line(c(0.6892, 0.5664), yLine, colour = 'steelblue', size = 0.2) + #5
draw_line(c(0.7100, 0.6950), yLine, colour = 'steelblue', size = 0.2) + #6
draw_line(c(0.8150, 0.7085), yLine, colour = 'steelblue', size = 0.2) + #6
draw_line(c(0.8355, 0.7325), yLine, colour = 'steelblue', size = 0.2) + #7
draw_line(c(0.8925, 0.7392), yLine, colour = 'steelblue', size = 0.2) + #7
draw_line(c(0.9128, 0.7610), yLine, colour = 'steelblue', size = 0.2) + #8
draw_line(c(0.9371, 0.7625), yLine, colour = 'steelblue', size = 0.2) + #8
draw_line(c(0.9575, 0.8290), yLine, colour = 'steelblue', size = 0.2) + #9
draw_line(c(0.9820, 0.8308), yLine, colour = 'steelblue', size = 0.2) + #9
draw_text(paste0('(', 1:9, ')'),
c(0.090, 0.244, 0.490, 0.623, 0.674, 0.767, 0.863, 0.921, 0.965),
yText, size = 9) +
draw_text(c('a)', 'b)'), 0.03, c(0.95, 0.4), size = 11, fontface = 'bold')
fhAnnotated

Zoom in to the instrumental period (Figure S5a).
flow_history(ldsRec, instQmeta, trans,
stdType = 'inst', instSummary = instQsummary,
startYear = 1950,
plotLower = FALSE, plotSegments = FALSE,
breaks = seq(1950, 2010, 10))

Time series in the instrumental period (Figure S6 with Mekong and Yangtze replaced).
DT <- merge(ldsRec2[s2b], instQ[s2b], by = c('ID', 'year'))
ggplot(DT) +
geom_ribbon(aes(year, ymin = Ql, ymax = Qu, fill = '95% Confidence Interval'),
alpha = 0.25) +
geom_line(aes(year, Q, colour = 'Reconstruction')) +
geom_line(aes(year, Qa, colour = 'Observation')) +
facet_wrap(vars(ID), ncol = 2, scales = 'free', labeller = as_labeller(ID_to_name_basin)) +
scale_colour_manual(name = NULL, values = c('darkorange', 'black')) +
scale_fill_manual(name = NULL, values = 'gray') +
scale_x_continuous(breaks = seq(1960, 2010, 10)) +
labs(x = NULL, y = 'Mean annual flow [m\u00b3/s]') +
theme(legend.position = 'top',
legend.key.width = unit(1.5, 'cm'))

Time series for full reconstructions (Figure S7).
lp <- copy(ldsRec2[s4])[, lp := dplR::pass.filt(Q, 20, 'low', 'Butterworth')]
lp[, ID := factor(ID, levels = c('IN_0000061', 'TH_0000178', 'TH_0000156', 'CN_0000192'))]
ggplot() +
geom_rect(aes(xmin = firstYear, xmax = finalYear, ymin = -Inf, ymax = Inf), megadroughts,
fill = 'darkorange', alpha = 0.2) +
geom_hline(aes(yintercept = Qm, colour = 'Long term mean'),
lp[, .(Qm = mean(Q)), by = ID]) +
geom_line(aes(year, Q, colour = 'Reconstruction'), lp, size = 0.2) +
geom_line(aes(year, lp, colour = '20-yr low pass'),
lp[, .SD[5:(.N-5)], by = ID], size = 0.5) +
facet_wrap(vars(ID), ncol = 1, scales = 'free_y',
labeller = as_labeller(ID_to_name_basin)) +
labs(x = NULL, y = 'Q [m\u00b3/s]') +
scale_x_continuous(breaks = seq(1200, 2000, 50), expand = c(0, 10)) +
scale_colour_manual(name = NULL, values = c('black', 'maroon', 'gray70')) +
theme(legend.position = 'top',
legend.key.width = unit(2, 'cm'))

Now we plot the full ensemble for some stations (in gray) compared with the best member that we chose above (in blue).
ensembleRec <- readRDS('results/ensemble_reconst.RDS')
ggplot(ensembleRec[s4]) +
geom_line(aes(year, Q, group = interaction(kwf, p)), colour = 'gray85') +
geom_line(aes(year, Q), ldsRec[s4], colour = 'steelblue') +
scale_x_continuous(expand = c(0, 0)) +
scale_y_continuous(expand = c(0, 0)) +
facet_wrap(vars(ID), ncol = 1, scales = 'free_y',
labeller = as_labeller(ID_to_name),
strip.position = 'right') +
labs(x = NULL, y = 'Q [m\u00b3/s]')

Cross-correlations of all stations
Figure S8, but with unavailable stations masked out.
# Correlations in instrumental data
trimmedIDs <- trim_ID(instQmeta[order(code), ID])
instCorMat <- instQ[, dcast(.SD, year ~ ID, value.var = 'Qa')
][, cor(.SD, use = 'pairwise.complete'), .SDcols = -1]
instCor <- data.table(instCorMat, keep.rownames = 'ID1') %>%
melt(id.var = 'ID1', variable.name = 'ID2', value.name = 'rho', variable.factor = FALSE)
instCor[, c('ID1', 'ID2') := lapply(.SD, function(x) factor(trim_ID(x), levels = trimmedIDs)),
.SDcols = c('ID1', 'ID2')]
instCor <- instCor[as.numeric(ID1) < as.numeric(ID2)]
# Corelations in reconstructions
# Where instrumental data are available, use the same years as instrumental data
# Otherwise, use a nominal period 1950:2012
recCorMat <- rbind(ldsRec[instQ[, .(ID, year)], on = c('ID', 'year')],
ldsRec[!instQ][year %in% 1950:2012]
)[, dcast(.SD, year ~ ID, value.var = 'Q')
][, cor(.SD, use = 'pairwise.complete'), .SDcols = -1]
recCor <- data.table(recCorMat, keep.rownames = 'ID1') %>%
melt(id.var = 'ID1', variable.name = 'ID2', value.name = 'rho', variable.factor = FALSE)
recCor[, c('ID1', 'ID2') := lapply(.SD, function(x) factor(trim_ID(x), levels = trimmedIDs)),
.SDcols = c('ID1', 'ID2')]
recCor <- recCor[as.numeric(ID1) > as.numeric(ID2)]
limits <- absRange(c(instCor$rho, recCor$rho))
DT <- rbind(instCor, recCor)
ggplot(DT) +
geom_tile(aes(ID1, ID2, fill = rho)) +
geom_line(aes(x, y), data.table(x = c(0.5, 62.5), y = c(0.5, 62.5)),
size = 1.5, lineend = 'round') +
geom_linerange(aes(y = stt, xmin = 0.5, xmax = 62.5), regionCount[-1], size = 0.1) +
geom_linerange(aes(x = stt, ymin = 0.5, ymax = 62.5), regionCount[-1], size = 0.1) +
geom_text(aes(mid, 63.5, label = region), regionCount, size = 3.5) +
geom_text(aes(63, mid, label = region), regionCount, size = 3.5, hjust = 0) +
coord_equal() +
scale_x_discrete(limits = c(trimmedIDs, ' ', ' ', ' ')) +
scale_y_discrete(limits = c(trimmedIDs, ' ', ' ')) +
scale_fill_distiller(name = 'Correlation', palette = 'RdBu', direction = 1,
breaks = seq(-0.8, 0.8, 0.2), limits = limits) +
labs(x = NULL, y = NULL) +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1, size = 7),
axis.text.y = element_text(vjust = 0.5, size = 7),
axis.ticks = element_blank(),
axis.line = element_blank(),
legend.key.height = unit(2, 'cm'))

Volcanic eruptions
Figure S10
volcano0 <- ldsRec[, .(year, Y = {
s <- .BY
tf <- trans[s, trans]
Y <- if (tf == 'log') log(Q) else Q
standardize(Y)
}), keyby = ID
][year %in% c(1257, 1452, 1815)
][, event := fifelse(year == 1257, 'Samalas',
fifelse(year == 1452, 'Kuwae', 'Tambora'))
][, event := factor(event, levels = c('Samalas', 'Kuwae', 'Tambora'))
][instQmeta
][, region := factor(region, levels = c('CA', 'EA', 'CN', 'WA', 'SEA', 'SA'))]
volcano1 <- ldsRec[, .(year, Y = {
s <- .BY
tf <- trans[s, trans]
Y <- if (tf == 'log') log(Q) else Q
standardize(Y)
}), keyby = ID
][year %in% c(1258, 1453, 1816)
][, event := fifelse(year == 1258, 'Samalas',
fifelse(year == 1453, 'Kuwae', 'Tambora'))
][, event := factor(event, levels = c('Samalas', 'Kuwae', 'Tambora'))
][instQmeta
][, region := factor(region, levels = c('CA', 'EA', 'CN', 'WA', 'SEA', 'SA'))]
p1 <- ggplot(volcano0, aes(event, Y)) +
geom_hline(yintercept = 0, colour = 'gray', size = 0.2) +
geom_boxplot(aes(fill = region), size = 0.2, varwidth = TRUE) +
facet_wrap(vars(region)) +
scale_fill_manual(name = 'Region', values = regionFillPal) +
labs(x = NULL, y = 'Standardized streamflow [-]', title = 'a) Year t') +
theme(legend.position = 'none',
panel.background = element_rect(NA, 'black', size = 0.1),
panel.spacing.x = unit(0, 'pt'))
p2 <- ggplot(volcano1, aes(event, Y)) +
geom_hline(yintercept = 0, colour = 'gray', size = 0.2) +
geom_boxplot(aes(fill = region), size = 0.2, varwidth = TRUE) +
facet_wrap(vars(region)) +
scale_fill_manual(name = 'Region', values = regionFillPal) +
labs(x = NULL, y = 'Standardized streamflow [-]', title = 'b) Year t + 1') +
theme(legend.position = 'none',
panel.background = element_rect(NA, 'black', size = 0.1),
panel.spacing.x = unit(0, 'pt'))
p1 / p2
