Skip to content

Commit 8d66def

Browse files
authored
Merge pull request #1 from MathMarEcol/devel_JDE
Devel jde
2 parents 10a815c + 1b90766 commit 8d66def

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+4813
-880
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
.Rhistory
33
.RData
44
.DS_Store
5+
/doc/
6+
/Meta/

DESCRIPTION

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Imports:
2121
geosphere,
2222
ggplot2,
2323
magrittr,
24-
parallel,
24+
parallelly,
2525
RColorBrewer,
2626
rlang,
2727
sp,
@@ -33,16 +33,18 @@ Suggests:
3333
knitr,
3434
mapplots,
3535
ncdf4,
36-
prettydoc,
36+
patchwork,
3737
rasterVis,
3838
repmis,
3939
rmarkdown,
40-
scales
40+
scales,
41+
tidyterra,
42+
VoCCdata
4143
URL: https://github.com/JorGarMol/VoCC
4244
BugReports: https://github.com/JorGarMol/VoCC/issues
4345
License: AGPL (>= 3)
4446
Encoding: UTF-8
4547
LazyData: true
46-
RoxygenNote: 7.3.1
48+
RoxygenNote: 7.3.2
4749
VignetteBuilder: knitr,
4850
rmarkdown

Meta/vignette.rds

-13 Bytes
Binary file not shown.

NAMESPACE

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Generated by roxygen2: do not edit by hand
22

33
export("%>%")
4-
export(VoCC_get_data)
54
export(climPCA)
65
export(climPlot)
76
export(dVoCC)
@@ -11,10 +10,8 @@ export(shiftTime)
1110
export(spatGrad)
1211
export(sumSeries)
1312
export(tempTrend)
14-
export(trajClas)
15-
export(trajLine)
16-
export(voccTraj)
1713
importFrom(data.table,":=")
1814
importFrom(foreach,"%dopar%")
1915
importFrom(magrittr,"%>%")
2016
importFrom(rlang,.data)
17+
importFrom(stats,na.omit)

R/climPCA.R

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
#' @export
2323
#' @author Jorge Garcia Molinos
2424
#' @examples
25-
#'
25+
#' \dontrun{
2626
#' JapTC <- VoCC_get_data("JapTC.tif")
2727
#'
2828
#' comp <- climPCA(JapTC[[c(1, 3, 5)]], JapTC[[c(2, 4, 6)]],
@@ -31,6 +31,8 @@
3131
#' # Create a data frame with the necessary variables in the required order (see climAna? for details)
3232
#' clim <- comp[[2]][, c(2, 4, 3, 5, 1)]
3333
#' clim[, c("x", "y")] <- terra::xyFromCell(JapTC[[1]], clim$cid)
34+
#' }
35+
#'
3436
climPCA <- function(climp, climf, trans = function(x) log(x), cen = TRUE, sc = TRUE, th = 0.8) {
3537
# get a data table with the pooled values (current/future) of the clim variables
3638
clim <- data.table::data.table(rbind(terra::values(climp), terra::values(climf)))

R/climPlot.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#' @export
1818
#' @author Jorge Garcia Molinos and Naoki H. Kumagai
1919
#' @examples
20-
#'
20+
#' \dontrun{
2121
#' JapTC <- VoCC_get_data("JapTC.tif")
2222
#'
2323
#' # Plot climate space for the two first variables(annual precipitation and maximum temperature)
@@ -32,7 +32,6 @@
3232
#' y.name = "Temperature max (°C)"
3333
#' )
3434
#'
35-
#' \dontrun{
3635
#' # output plots can be saved as:
3736
#' ggplot2::ggsave(
3837
#' plot = out, filename = file.path(getwd(), "example_plot.pdf"),
@@ -44,6 +43,7 @@ climPlot <- function(xy, x.binSize, y.binSize, x.name = "V1", y.name = "V2") {
4443
yp <- xy[, 3]
4544
xf <- xy[, 2]
4645
yf <- xy[, 4]
46+
4747
# bins per axis
4848
x.nbins <- floor((abs(range(xp, xf)[2] - range(xp, xf)[1])) / x.binSize)
4949
y.nbins <- floor((abs(range(yp, yf)[2] - range(yp, yf)[1])) / y.binSize)
@@ -102,7 +102,7 @@ climPlot <- function(xy, x.binSize, y.binSize, x.name = "V1", y.name = "V2") {
102102
Freq2D <- data.frame(x = x.bin, y = rep(y.bin, each = length(x.bin)), freq = Freq2D)
103103
Freq2D <- Freq2D[!is.na(Freq2D$freq), ]
104104

105-
panelAB <- ggplot2::ggplot(Freq2Dpf, ggplot2::aes(x = x, y = y, fill = freq)) +
105+
panelAB <- ggplot2::ggplot(Freq2Dpf, ggplot2::aes(x = .data$x, y = .data$y, fill = freq)) +
106106
ggplot2::geom_raster() +
107107
ggplot2::scale_fill_gradientn(
108108
colors = r,
@@ -121,7 +121,7 @@ climPlot <- function(xy, x.binSize, y.binSize, x.name = "V1", y.name = "V2") {
121121
strip.text = ggplot2::element_blank()
122122
)
123123

124-
panelC <- ggplot2::ggplot(Freq2D, ggplot2::aes(x = x, y = y, fill = freq)) +
124+
panelC <- ggplot2::ggplot(Freq2D, ggplot2::aes(x = .data$x, y = .data$y, fill = freq)) +
125125
ggplot2::geom_raster() +
126126
ggplot2::scale_fill_manual(values = c("#56B4E9", "#009E73", "#D55E00"), name = "Climate type") +
127127
ggplot2::labs(x = x.name, y = y.name)

R/dVoCC.R

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#' @export
4242
#' @author Jorge Garcia Molinos
4343
#' @examples
44+
#' \dontrun{
4445
#' JapTC <- VoCC_get_data("JapTC.tif")
4546
#'
4647
#' # Create a data frame with the necessary variables in the required order
@@ -57,8 +58,6 @@
5758
#' r1[avocc1$focal] <- avocc1$vel
5859
#' terra::plot(r1)
5960
#'
60-
#' \dontrun{
61-
#'
6261
#' # Cell-specific, distance-unrestricted climate analogue velocity based on least-cost path distances
6362
#' # First, create the conductance matrix (all land cells considered to have conductance of 1)
6463
#' r <- JapTC
@@ -82,9 +81,9 @@
8281
dVoCC <- function(clim, n, tdiff, method = "Single", climTol, geoTol,
8382
distfun = "GreatCircle", trans = NA, lonlat = TRUE) {
8483

85-
geoDis <- climDis <- ang <- vel <- target <- cid <- NULL # Fix devtools check warnings
84+
geoDis <- climDis <- ang <- vel <- target <- cid <- a <- NULL # Fix devtools check warnings
8685

87-
if (distfun == "Euclidean" & lonlat == TRUE) {
86+
if (distfun == "Euclidean" && lonlat == TRUE) {
8887
print("Error: Euclidean distances specified for unprojected coordinates")
8988
stop()
9089
}
@@ -99,17 +98,24 @@ dVoCC <- function(clim, n, tdiff, method = "Single", climTol, geoTol,
9998
# matrix with the future climatic values for all cells
10099
fut <- dat[, seq(2, (2 * n), by = 2), with = FALSE]
101100

102-
# set things up for parallel processing
103-
cores <- parallel::detectCores()
104-
ncores <- cores[1] - 1
105-
cuts <- cut(1:nrow(dat), ncores, labels = FALSE)
106-
cl <- parallel::makeCluster(ncores)
101+
# Determine optimal number of cores, ensuring we don't exceed data rows
102+
ncores <- parallelly::availableCores(constraints = "connections", omit = 2)
103+
ncores <- min(ncores, nrow(dat)) # Don't use more cores than data rows
104+
ncores <- max(ncores, 1) # Ensure at least 1 core
107105

108-
doParallel::registerDoParallel(cl)
106+
# Only use parallel processing if we have multiple cores and sufficient data
107+
if (ncores > 1 && nrow(dat) > ncores) {
108+
cuts <- cut(seq_len(nrow(dat)), ncores, labels = FALSE)
109+
cl <- parallelly::makeClusterPSOCK(ncores, autoStop = TRUE)
109110

110-
result <- foreach::foreach(x = 1:ncores, .combine = rbind, .packages = c("terra", "gdistance", "geosphere", "data.table"), .multicombine = TRUE) %dopar% {
111-
a <- x
112-
Dat <- dat[cuts == a, ]
111+
doParallel::registerDoParallel(cl)
112+
113+
result <- foreach::foreach(a = seq_len(ncores),
114+
.combine = rbind,
115+
.packages = c("terra", "gdistance", "geosphere", "data.table"),
116+
.multicombine = TRUE) %dopar% {
117+
118+
Dat <- dat[cuts == a, ]
113119

114120
resu <- data.table::data.table(
115121
focal = Dat$cid,
@@ -177,7 +183,62 @@ dVoCC <- function(clim, n, tdiff, method = "Single", climTol, geoTol,
177183
}
178184
return(resu)
179185
}
180-
parallel::stopCluster(cl)
186+
} else {
187+
# Sequential processing for small datasets or limited cores
188+
result <- data.table::data.table(
189+
focal = dat$cid,
190+
target = as.integer(NA),
191+
climDis = as.double(NA),
192+
geoDis = as.double(NA),
193+
ang = as.double(NA),
194+
vel = as.double(NA)
195+
)
196+
197+
for (i in seq_len(nrow(dat))) {
198+
# for each focal cell subset target cell analogues (within ClimTol)
199+
pres <- as.numeric(dat[i, seq(1, (2 * n), by = 2), with = FALSE])
200+
dif <- data.table::data.table(sweep(fut, 2, pres, "-"))
201+
202+
# Identify future analogue cells
203+
if (method == "Single") { # Ohlemuller et al 2006 / Hamann et al 2015
204+
upper <- colnames(dif)
205+
l <- lapply(upper, function(x) call("<", call("abs", as.name(x)), climTol[grep(x, colnames(dif))]))
206+
ii <- Reduce(function(c1, c2) substitute(.c1 & .c2, list(.c1 = c1, .c2 = c2)), l)
207+
anacid <- dat$cid[dif[eval(ii), which = TRUE]] # cids analogue cells
208+
}
209+
210+
if (method == "Variable") { # Garcia Molinos et al. 2017
211+
climTol <- as.numeric(dat[i, ((2 * n) + 1):(3 * n), with = FALSE]) # focal cell tolerance
212+
upper <- colnames(dif)
213+
l <- lapply(upper, function(x) call("<", call("abs", as.name(x)), climTol[grep(x, colnames(dif))]))
214+
ii <- Reduce(function(c1, c2) substitute(.c1 & .c2, list(.c1 = c1, .c2 = c2)), l)
215+
anacid <- dat$cid[dif[eval(ii), which = TRUE]] # cids analogue cells
216+
}
217+
218+
# LOCATE CLOSEST ANALOGUE
219+
if (length(anacid) > 0) {
220+
# check which of those are within distance and get the analogue at minimum distance
221+
if (distfun == "Euclidean") {
222+
d <- stats::dist(cbind(dat$x[i], dat$y[i]), cbind(dat$x[dat$cid %in% anacid], dat$y[dat$cid %in% anacid]))
223+
} # in x/y units
224+
if (distfun == "GreatCircle") {
225+
d <- (geosphere::distHaversine(cbind(dat$x[i], dat$y[i]), cbind(dat$x[dat$cid %in% anacid], dat$y[dat$cid %in% anacid]))) / 1000
226+
} # in km
227+
228+
an <- anacid[d < geoTol] # cids analogue cells within search radius
229+
dis <- d[d < geoTol] # distance to candidate analogues
230+
if (length(an) > 0) {
231+
result[i, target := an[which.min(dis)]] # cid of geographically closest climate analogue
232+
if (method == "Single") {
233+
result[i, climDis := mean(as.numeric(dif[which(anacid == result[i, target]), ]))]
234+
} # mean clim difference for the closest analogue
235+
result[i, geoDis := min(dis)]
236+
result[i, ang := geosphere::bearing(dat[i, c("x", "y")], dat[cid == result[i, target], c("x", "y")])]
237+
result[i, vel := result$geoDis[i] / tdiff]
238+
}
239+
}
240+
}
241+
}
181242

182243
return(result)
183244
}

R/gVoCC.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
#' @author Jorge Garcia Molinos
2020
#'
2121
#' @examples
22-
#'
22+
#' \dontrun{
2323
#' HSST <- VoCC_get_data("HSST.tif")
2424
#' yrSST <- sumSeries(HSST,
2525
#' p = "1960-01/2009-12", yr0 = "1955-01-01", l = terra::nlyr(HSST),
@@ -32,6 +32,7 @@
3232
#'
3333
#' v <- gVoCC(tr, sg)
3434
#' terra::plot(v)
35+
#' }
3536
#'
3637
gVoCC <- function(tempTrend, spatGrad) {
3738
VoCC <- tempTrend[[1]] / spatGrad[[1]]

R/shiftTime.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@
2121
#' @export
2222
#' @author Jorge Garcia Molinos and Michael T. Burrows
2323
#' @examples
24+
#' \dontrun{
2425
#' HSST <- VoCC_get_data("HSST.tif")
2526
#' Apr <- shiftTime(HSST, yr1 = 1960, yr2 = 2009, yr0 = 1955, th = 10, m = 4)
2627
#'
2728
#' terra::plot(Apr)
29+
#' }
2830
shiftTime <- function(r, yr1, yr2, yr0, th, m) {
2931
# 1. Long term trends in monthly values (e.g. deg/year if temperature)
3032
m1 <- ((yr1 - yr0) * 12) + m

R/spatGrad.R

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121
#' @seealso{\code{\link{tempTrend}}, \code{\link{gVoCC}}}
2222
#'
2323
#' @importFrom rlang .data
24+
#' @importFrom stats na.omit
2425
#'
2526
#' @export
2627
#' @author Jorge Garcia Molinos, David S. Schoeman, and Michael T. Burrows
2728
#' @examples
28-
#'
29+
#' \dontrun{
2930
#' HSST <- VoCC_get_data("HSST.tif")
3031
#'
3132
#' yrSST <- sumSeries(HSST,
@@ -38,13 +39,16 @@
3839
#' sg <- spatGrad(yrSST, th = 0.0001, projected = FALSE)
3940
#'
4041
#' terra::plot(sg)
42+
#' }
4143
#'
4244
spatGrad <- function(r, th = -Inf, projected = FALSE) {
45+
4346
# Fix devtools check warnings
47+
"." <- NULL
4448
gradNS1 <- gradNS2 <- gradNS3 <- gradNS4 <- gradNS5 <- gradNS6 <- gradWE1 <- gradWE2 <- gradWE3 <- gradWE4 <- gradWE5 <- gradWE6 <- NULL
4549
sy <- sx <- NSgrad <- WEgrad <- NULL
4650
clim <- climE <- climN <- climNE <- climNW <- climS <- climSE <- climSW <- climW <- climFocal <- NULL
47-
to <- code <- i.to <- LAT <- angle <- Grad <- NULL
51+
to <- code <- i.to <- LAT <- angle <- Grad <- .SD <- NULL
4852

4953
if (terra::nlyr(r) > 1) {
5054
r <- terra::mean(r, na.rm = TRUE)
@@ -55,26 +59,18 @@ spatGrad <- function(r, th = -Inf, projected = FALSE) {
5559

5660
# Create a columns for focal and each of its 8 adjacent cells
5761
y <- data.table::data.table(terra::adjacent(r, 1:terra::ncell(r), directions = 8, pairs = TRUE))
58-
y <- stats::na.omit(y[, climFocal := terra::values(r)[from]][order(from, to)]) # Get value for focal cell, order the table by raster sequence and omit NAs (land cells)
59-
60-
# TODO JDE added in na.rm = TRUE as I was getting NaN. I can't test if this behaviour has changed from raster....
61-
# On second thought I am not sure if NAs are valid here. It gives errors below when calculating weighted means
62-
y[, clim := terra::values(r, na.rm = TRUE)[to]] # Insert values for adjacent cells
62+
y <- na.omit(y[, climFocal := terra::values(r)[from]][order(from, to)]) # Get value for focal cell, order the table by raster sequence and omit NAs (land cells)
63+
y[, clim := terra::values(r)[to]] # Insert values for adjacent cells
6364
y[, sy := terra::rowFromCell(r, from) - terra::rowFromCell(r, to)] # Column to identify rows in the raster (N = 1, mid = 0, S = -1)
6465
y[, sx := terra::colFromCell(r, to) - terra::colFromCell(r, from)] # Same for columns (E = 1, mid = 0, W = -1)
6566
y[sx > 1, sx := -1] # Sort out the W-E wrap at the dateline, part I
6667
y[sx < -1, sx := 1] # Sort out the W-E wrap at the dateline, part II
6768
y[, code := paste0(sx, sy)] # Make a unique code for each of the eight neighbouring cells
6869

6970
# Code cells with positions
70-
y[
71-
list(
72-
code = c("10", "-10", "-11", "-1-1", "11", "1-1", "01", "0-1"),
73-
to = c("climE", "climW", "climNW", "climSW", "climNE", "climSE", "climN", "climS")
74-
),
75-
on = "code",
76-
code := i.to
77-
]
71+
y[.(code = c("10", "-10", "-11", "-1-1", "11", "1-1", "01", "0-1"),
72+
to = c("climE", "climW", "climNW", "climSW", "climNE", "climSE", "climN", "climS")),
73+
on = "code", code := i.to]
7874
y <- data.table::dcast(y[, c("from", "code", "clim")], from ~ code, value.var = "clim")
7975
y[, climFocal := terra::values(r)[from]] # Put climFocal back in
8076
y[, LAT := terra::yFromCell(r, from)] # Add focal cell latitude
@@ -93,7 +89,8 @@ spatGrad <- function(r, th = -Inf, projected = FALSE) {
9389
y[, gradWE5 := (climE - climFocal) / (cos(co * CircStats::rad(LAT)) * (d * re[1]))]
9490
y[, gradWE6 := (climSE - climS) / (cos(co * CircStats::rad(LAT - re[2])) * (d * re[1]))]
9591

96-
# NS gradients difference in temperatures for each northern and southern pairs divided by the distance between them (111.325 km per degC *re[2] degC)
92+
# NS gradients difference in temperatures for each northern and southern pairs divided by
93+
# the distance between them (111.325 km per degC *re[2] degC)
9794
# Positive values indicate an increase in sst from S to N (i.e., in line with the Cartesian y axis)
9895
y[, gradNS1 := (climNW - climW) / (d * re[2])]
9996
y[, gradNS2 := (climN - climFocal) / (d * re[2])]
@@ -102,29 +99,16 @@ spatGrad <- function(r, th = -Inf, projected = FALSE) {
10299
y[, gradNS5 := (climFocal - climS) / (d * re[2])]
103100
y[, gradNS6 := (climE - climSE) / (d * re[2])]
104101

105-
106-
for (nn in 1:365){
107-
108-
print(nn)
109-
110-
print(stats::weighted.mean(y[nn,12:17], w = c(1, 2, 1, 1, 2, 1), na.rm = TRUE))
111-
112-
113-
}
114-
115-
116-
browser()
117-
# Calulate NS and WE gradients. NOTE: for angles to work (at least using simple positive and negative values on Cartesian axes),
118-
# S-N & W-E gradients need to be positive.
119-
# JDE Notes: 1 in apply = operate over rows
120-
# Lots of NAs in clim. Can these be removed? Should they be? Chat to Dave S
121-
y[, WEgrad := apply(data.table::.SD, 1, function(x) stats::weighted.mean(x, w = c(1, 2, 1, 1, 2, 1), na.rm = TRUE)), .SDcols = gradWE1:gradWE6]
122-
y[, NSgrad := apply(data.table::.SD, 1, function(x) stats::weighted.mean(x, c(1, 2, 1, 1, 2, 1), na.rm = T)), .SDcols = 18:23]
102+
# Calulate NS and WE gradients.
103+
# NOTE: for angles to work (at least using simple positive and negative values on Cartesian axes),
104+
# S-N & W-E gradients need to be positive)
105+
y[, WEgrad := apply(.SD, 1, function(x) stats::weighted.mean(x, c(1, 2, 1, 1, 2, 1), na.rm = TRUE)), .SDcols = 12:17]
106+
y[, NSgrad := apply(.SD, 1, function(x) stats::weighted.mean(x, c(1, 2, 1, 1, 2, 1), na.rm = TRUE)), .SDcols = 18:23]
123107
y[is.na(WEgrad) & !is.na(NSgrad), WEgrad := 0L] # Where NSgrad does not exist, but WEgrad does, make NSgrad 0
124108
y[!is.na(WEgrad) & is.na(NSgrad), NSgrad := 0L] # same the other way around
125109

126110
# Calculate angles of gradients (degrees) - adjusted for quadrant (0 deg is North)
127-
y[, angle := angulo(data.table::.SD$WEgrad, data.table::.SD$NSgrad), .SDcols = c("WEgrad", "NSgrad")]
111+
y[, angle := angulo(.SD$WEgrad, .SD$NSgrad), .SDcols = c("WEgrad", "NSgrad")]
128112

129113
# Calculate the vector sum of gradients (C/km)
130114
y[, Grad := sqrt(apply(cbind((y$WEgrad^2), (y$NSgrad^2)), 1, sum, na.rm = TRUE))]
@@ -139,5 +123,6 @@ spatGrad <- function(r, th = -Inf, projected = FALSE) {
139123
rGrad[rGrad[] < th] <- th
140124
output <- c(rGrad, rAng)
141125
names(output) <- c("Grad", "Ang")
126+
142127
return(output)
143128
}

0 commit comments

Comments
 (0)