#' Compute walking path pump neighborhoods.
#'
#' Group cases into neighborhoods based on walking distance.
#' @param pump.select Numeric. Vector of numeric pump IDs to define pump neighborhoods (i.e., the "population"). Negative selection possible. \code{NULL} selects all pumps. Note that you can't just select the pump on Adam and Eve Court (#2) because it's technically an isolate.
#' @param vestry Logical. \code{TRUE} uses the 14 pumps from the Vestry report. \code{FALSE} uses the 13 in the original map.
#' @param weighted Logical. \code{TRUE} computes shortest path weighted by road length. \code{FALSE} computes shortest path in terms of the number of nodes.
#' @param case.set Character. "observed" or "expected".
#' @param latlong Logical.
#' @param multi.core Logical or Numeric. \code{TRUE} uses \code{parallel::detectCores()}. \code{FALSE} uses one, single core. You can also specify the number logical cores. See \code{vignette("Parallelization")} for details.
#' @export
neighborhoodWalking <- function(pump.select = NULL, vestry = FALSE,
weighted = TRUE, case.set = "observed", latlong = FALSE, multi.core = FALSE) {
if (!is.null(pump.select)) {
if (length(pump.select) == 1) {
if (pump.select == 2) {
msg1 <- "You can't just select the pump on Adam and Eve Court (#2).\n"
msg2 <- " It's an isolate, unreachable for observed and most expected fatalities."
stop(paste(msg1, msg2))
}
}
}
if (!case.set %in% c("observed", "expected")) {
stop('case.set must be "observed" or "expected.', call. = FALSE)
}
if (.Platform$OS.type == "windows") {
cores <- 1L
} else {
cores <- multiCore(multi.core)
}
if (vestry) {
pump.data <- cholera::pumps.vestry
} else {
pump.data <- cholera::pumps
}
# pumps to consider or target
p.sel <- selectPump(pump.data, pump.select = pump.select, vestry = vestry)
dat <- sohoGraph(case.set = case.set, vestry = vestry, latlong = latlong)
g <- dat$g
nodes <- dat$nodes
edges <- dat$edges
p.select <- nodes[nodes$pump != 0 & nodes$pump %in% p.sel, ]
p.select <- p.select[order(p.select$pump), ]
if (case.set == "observed") {
case.data <- nodes[nodes$case != 0, ]
case.data <- case.data[order(case.data$case), ]
case <- case.data$case
ds <- igraph::distances(graph = g, v = case.data$node, to = p.select$node,
weights = edges$d)
id <- vapply(seq_len(nrow(ds)), function(i) which.min(ds[i, ]), numeric(1L))
d <- vapply(seq_along(id), function(i) ds[i, ][id[i]], numeric(1L))
pump <- vapply(seq_along(id), function(i) p.sel[id[i]], integer(1L))
nr.pump <- data.frame(case = case, pump = pump, distance = d)
obs.pump <- sort(unique(pump))
case.pump <- lapply(obs.pump, function(x) case[pump %in% x])
names(case.pump) <- obs.pump
## compute observed path edges ##
neigh.edges <- parallel::mclapply(names(case.pump), function(p.nm) {
pump.sel <- p.select$pump == p.nm
id2 <- lapply(case.pump[[p.nm]], function(cs) {
case.sel <- nodes$case == cs
p <- igraph::shortest_paths(graph = g, from = nodes[case.sel, "node"],
to = p.select[pump.sel, "node"], weights = edges$d)$vpath
p <- names(unlist(p))
edge.select <- vapply(seq_along(p[-1]), function(i) {
ab <- edges$node1 %in% p[i] & edges$node2 %in% p[i + 1]
ba <- edges$node2 %in% p[i] & edges$node1 %in% p[i + 1]
which(ab | ba)
}, numeric(1L))
edges[edge.select, "id2"]
})
unique(unlist(id2))
}, mc.cores = cores)
obs.pump.nm <- paste0("p", obs.pump)
names(neigh.edges) <- obs.pump.nm
names(case.pump) <- obs.pump.nm
out <- list(case.pump = case.pump,
pump.data = pump.data,
edges = edges,
neigh.edges = neigh.edges,
case.set = case.set,
pump.select = pump.select,
p.sel = p.sel,
vestry = vestry,
snow.colors = snowColors(vestry = vestry),
latlong = latlong,
cores = cores)
} else if (case.set == "expected") {
## Isolates: Adam and Eve Court; Falconberg Court and Mews
adam.eve.ct <- "44-1"
falconberg.ct.mews <- c("40-1", "41-1", "41-2", "63-1")
sel <- !cholera::road.segments$id %in% c(falconberg.ct.mews, adam.eve.ct)
## Isolate: Adam and Eve Court pump (#2)
p.select.adam.eve <- p.select[p.select$pump == 2, ]
p.select <- p.select[p.select$pump != 2, ]
rd.segs <- cholera::road.segments
if (latlong) {
adam.eve.seg <- rd.segs[rd.segs$id == adam.eve.ct, ]
adam.eve.seg$n1 <- paste0(adam.eve.seg$lon1, "_&_", adam.eve.seg$lat1)
adam.eve.seg$n2 <- paste0(adam.eve.seg$lon2, "_&_", adam.eve.seg$lat2)
rd.segs <- rd.segs[sel, ]
rd.segs$n1 <- paste0(rd.segs$lon1, "_&_", rd.segs$lat1)
rd.segs$n2 <- paste0(rd.segs$lon2, "_&_", rd.segs$lat2)
} else {
adam.eve.seg <- rd.segs[rd.segs$id == adam.eve.ct, ]
adam.eve.seg$n1 <- paste0(adam.eve.seg$x1, "_&_", adam.eve.seg$y1)
adam.eve.seg$n2 <- paste0(adam.eve.seg$x2, "_&_", adam.eve.seg$y2)
rd.segs <- rd.segs[sel, ]
rd.segs$n1 <- paste0(rd.segs$x1, "_&_", rd.segs$y1)
rd.segs$n2 <- paste0(rd.segs$x2, "_&_", rd.segs$y2)
}
eps <- unique(c(rd.segs$n1, rd.segs$n2))
ds <- igraph::distances(graph = g, v = eps, to = p.select$node,
weights = edges$d)
nr.pmp <- vapply(seq_len(nrow(ds)), function(r) {
p.select$pump[which.min(ds[r, ])]
}, numeric(1L))
d <- vapply(seq_len(nrow(ds)), function(r) min(ds[r, ]), numeric(1L))
endpt.pump <- data.frame(node = eps, pump.id = nr.pmp, d = d)
endpt.data <- merge(rd.segs, endpt.pump, by.x = "n1", by.y = "node")
endpt.data <- merge(endpt.data, endpt.pump, by.x = "n2", by.y = "node")
sel <- names(endpt.data) %in% c("pump.id.x", "d.x", "pump.id.y", "d.y")
names(endpt.data)[sel] <- c("pump1", "d1", "pump2", "d2")
if (latlong) {
vars <- c("street", "id", "name", "lon1", "lat1", "lon2", "lat2", "n1",
"n2", "pump1", "pump2", "d1", "d2")
} else {
vars <- c("street", "id", "name", "x1", "y1", "x2", "y2", "n1", "n2",
"pump1", "pump2", "d1", "d2")
}
endpt.data <- endpt.data[, vars]
if (2L %in% p.sel) {
ds.AE <- igraph::distances(
graph = g,
v = unlist(adam.eve.seg[, c("n1", "n2")]),
to = p.select.adam.eve$node,
weights = edges$d)
ds.AE <- as.data.frame(ds.AE)
names(ds.AE) <- "d"
ds.AE$node <- row.names(ds.AE)
adam.eve.seg <- merge(adam.eve.seg, ds.AE, by.x = "n1", by.y = "node")
adam.eve.seg <- merge(adam.eve.seg, ds.AE, by.x = "n2", by.y = "node")
names(adam.eve.seg)[grep("d.", names(adam.eve.seg))] <- paste0("d", 1:2)
adam.eve.seg[, c("pump1", "pump2")] <- 2L
adam.eve.seg <- adam.eve.seg[, vars]
endpt.data <- rbind(endpt.data, adam.eve.seg)
}
same_pump.endpts <- endpt.data[endpt.data$pump1 == endpt.data$pump2, ]
# road segments with endpoints with shortest path to _same_ pump
same_pump.road_segs <- split(same_pump.endpts$id, same_pump.endpts$pump1)
if (latlong) {
same_pump.cases <- lapply(same_pump.road_segs, function(segs) {
sel <- cholera::latlong.sim.ortho.proj$road.segment %in% segs
cholera::latlong.sim.ortho.proj[sel, "case"]
})
} else {
same_pump.cases <- lapply(same_pump.road_segs, function(segs) {
sel <- cholera::sim.ortho.proj$road.segment %in% segs
cholera::sim.ortho.proj[sel, "case"]
})
}
if (any(endpt.data$pump1 != endpt.data$pump2)) {
# road segments with endpoints with shortest path to _different_ pump
diff_pump.endpts <- endpt.data[endpt.data$pump1 != endpt.data$pump2, ]
# find midpoint/cutpoint along segment that leads to different pumps
if (latlong) {
mid.point <- midpointLatlong(diff_pump.endpts, endpt.data,
same_pump.cases, cores)
} else {
mid.point <- midpointNative(diff_pump.endpts, endpt.data,
same_pump.cases, cores)
}
out <- list(exp.pump.case = mid.point$exp.pump.case,
same_pump.road_segs = same_pump.road_segs,
diff_pump.road_segs = mid.point$diff_pump.road_segs,
pump.data = pump.data,
case.set = case.set,
pump.select = pump.select,
p.sel = p.sel,
vestry = vestry,
snow.colors = snowColors(vestry = vestry),
latlong = latlong,
cores = cores)
} else {
out <- list(exp.pump.case = same_pump.cases,
same_pump.road_segs = same_pump.road_segs,
pump.data = pump.data,
case.set = case.set,
pump.select = pump.select,
p.sel = p.sel,
vestry = vestry,
snow.colors = snowColors(vestry = vestry),
latlong = latlong,
cores = cores)
}
}
class(out) <- "walking"
out
}
#' Plot method for neighborhoodWalking().
#'
#' @param x An object of class "walking" created by \code{neighborhoodWalking()}.
#' @param type Character. Type of expected plot: "roads", "area.points" or "area.polygons". Valid only when \code{case.set = "expected"}.
#' @param add Logical. Add graphic to plot.
#' @param tsp.method Character. Traveling salesperson problem algorithm.
#' @param path.width Numeric. Set width of paths.
#' @param alpha.level Numeric. Alpha level transparency for area plot: a value in [0, 1].
#' @param polygon.type Character. "border" or "solid".
#' @param polygon.col Character.
#' @param polygon.lwd Numeric.
#' @param ... Additional plotting parameters.
#' @return A base R plot.
#' @note When plotting area graphs with simulated data (i.e., \code{case.set = "expected"}), there may be discrepancies between observed cases and expected neighborhoods, particularly between neighborhoods. type = "roads" inspired by Shiode et. al. (2015).
#' @export
plot.walking <- function(x, type = "area.points", add = FALSE,
tsp.method = "repetitive_nn", path.width = 2, alpha.level = 0.75,
polygon.type = "solid", polygon.col = NULL, polygon.lwd = 2, ...) {
if (x$case.set == "expected") {
if (!type %in% c("area.points", "area.polygons", "roads")) {
stop('type must be "area.points", "area.polygons" or "roads".',
call. = FALSE)
}
}
if (!polygon.type %in% c("border", "solid")) {
stop('polygon.type must be "border" or "solid".', call. = FALSE)
}
if (x$latlong) {
vars <- c("lon", "lat")
seg.vars <- paste0(vars, c(rep(1, 2), rep(2, 2)))
} else {
vars <- c("x", "y")
seg.vars <- paste0(vars, c(rep(1, 2), rep(2, 2)))
}
if (x$case.set == "observed") {
edges <- x$edges
neigh.edges <- x$neigh.edges
if (!add) snowMap(add.cases = FALSE, add.pumps = FALSE, latlong = x$latlong)
invisible(lapply(names(neigh.edges), function(nm) {
n.edges <- edges[edges$id2 %in% neigh.edges[[nm]], ]
segments(n.edges[, seg.vars[1]], n.edges[, seg.vars[2]],
n.edges[, seg.vars[3]], n.edges[, seg.vars[4]],
lwd = path.width, col = x$snow.colors[nm])
}))
invisible(lapply(names(x$case.pump), function(nm) {
sel <- cholera::fatalities.address$anchor %in% x$case.pump[[nm]]
points(cholera::fatalities.address[sel, vars], pch = 20, cex = 0.75,
col = x$snow.colors[nm])
}))
if (is.null(x$pump.select)) {
points(x$pump.data[, vars], pch = 24, lwd = 2, col = x$snow.colors)
text(x$pump.data[, vars], pos = 1, cex = 0.9,
labels = paste0("p", x$p.sel))
} else {
obs <- x$pump.data$id %in% x$p.sel
pos.data <- x$pump.data[obs, vars]
neg.data <- x$pump.data[!obs, vars]
pos.labels <- paste0("p", x$pump.data$id[obs])
neg.labels <- paste0("p", x$pump.data$id[!obs])
points(pos.data, pch = 24, lwd = 2, col = x$snow.colors[obs])
points(neg.data, pch = 24, lwd = 1, col = "gray")
text(pos.data, pos = 1, cex = 0.9, labels = pos.labels)
text(neg.data, pos = 1, cex = 0.9, col = "gray", labels = neg.labels)
}
} else if (x$case.set == "expected") {
if (!add) snowMap(add.cases = FALSE, add.pumps = FALSE, add.roads = FALSE,
latlong = x$latlong)
if (x$latlong) {
reg.cases <- cholera::latlong.regular.cases[, c("lon", "lat")]
} else {
reg.cases <- cholera::regular.cases
}
rd.segs <- cholera::road.segments
case.fix <- ifelse(x$latlong, 0, 2000L)
if (type == "roads") {
invisible(lapply(names(x$same_pump.road_segs), function(nm) {
tmp <- rd.segs[rd.segs$id %in% x$same_pump.road_seg[[nm]], ]
pmp <- paste0("p", nm)
if (x$latlong) {
segments(tmp$lon1, tmp$lat1, tmp$lon2, tmp$lat2,
col = x$snow.colors[pmp], lwd = path.width)
} else {
segments(tmp$x1, tmp$y1, tmp$x2, tmp$y2, col = x$snow.colors[pmp],
lwd = path.width)
}
}))
invisible(lapply(names(x$diff_pump.road_segs), function(nm) {
tmp <- x$diff_pump.road_segs[[nm]]
pmp <- paste0("p", nm)
if (x$latlong) {
segments(tmp$lon1, tmp$lat1, tmp$lon2, tmp$lat2,
col = x$snow.colors[pmp], lwd = path.width)
} else {
segments(tmp$x1, tmp$y1, tmp$x2, tmp$y2, col = x$snow.colors[pmp],
lwd = path.width)
}
}))
falconberg.ct.mews <- c("40-1", "41-1", "41-2", "63-1")
FCM <- rd.segs[rd.segs$id %in% falconberg.ct.mews, ]
if (x$latlong) {
segments(FCM$lon1, FCM$lat1, FCM$lon2, FCM$lat2, lty = "dotted")
} else {
segments(FCM$x1, FCM$y1, FCM$x2, FCM$y2, lty = "dotted")
}
} else if (type == "area.points") {
invisible(lapply(names(x$exp.pump.case), function(nm) {
points(reg.cases[x$exp.pump.case[[nm]] - case.fix, ], pch = 15,
cex = 1.25, col = x$snow.colors[paste0("p", nm)])
}))
if (!add) addRoads(col = "black", latlong = x$latlong)
} else if (type == "area.polygons") {
neighborhood.cases <- lapply(x$exp.pump.case, function(x) x - case.fix)
periphery.cases <- parallel::mclapply(neighborhood.cases,
peripheryCases, latlong = x$latlong, mc.cores = x$cores)
# p8 fix with pump.select = 6:9
if (identical(x$p.sel, 6:9)) {
# "Picadilly", "Bruton Street" (x2), "New Burlington Street" (x2)
sel <- periphery.cases$`8` %in% c("206", "4022", "3875", "7131", "7277")
periphery.cases$`8` <- periphery.cases$`8`[!sel]
}
pearl.string <- parallel::mclapply(periphery.cases, travelingSalesman,
latlong = x$latlong, tsp.method = tsp.method, mc.cores = x$cores)
if (!add) addRoads(col = "black", latlong = x$latlong)
if (polygon.type == "border") {
invisible(lapply(names(pearl.string), function(nm) {
polygon(reg.cases[pearl.string[[nm]], ],
border = x$snow.colors[paste0("p", nm)], lwd = polygon.lwd)
}))
} else if (polygon.type == "solid") {
invisible(lapply(names(pearl.string), function(nm) {
polygon(reg.cases[pearl.string[[nm]], ],
col = grDevices::adjustcolor(x$snow.colors[paste0("p", nm)],
alpha.f = alpha.level))
}))
}
}
if (!add) pumpTokens(x, type, alpha.level, polygon.type)
}
if (!add) {
if (is.null(x$pump.select)) {
title(main = "Pump Neighborhoods: Walking")
} else {
if (length(x$pump.select) > 1) {
pmp <- "Pumps "
} else if (length(x$pump.select) == 1) {
pmp <- "Pump "
}
title(main = paste0("Pump Neighborhoods: Walking", "\n", pmp,
paste(sort(x$pump.select), collapse = ", ")))
}
}
}
#' Print method for neighborhoodWalking().
#'
#' Parameter values for neighborhoodWalking().
#' @param x An object of class "walking" created by \code{neighborhoodWalking()}.
#' @param ... Additional parameters.
#' @return A list of argument values.
#' @export
#' @examples
#' \dontrun{
#' neighborhoodWalking()
#' print(neighborhoodWalking())
#' }
print.walking <- function(x, ...) {
print(x[c("p.sel", "case.set", "vestry", "latlong")])
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.