--- title: "Benchmarking fastfocal" subtitle: "Version 0.1.3 (2025)" author: "Ho Yi Wan" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Benchmarking fastfocal} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include=FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = TRUE, fig.width = 6, fig.height = 4, out.width = "100%" ) library(fastfocal) library(terra) library(dplyr) ``` ## Is it faster? We compare `fastfocal()` against `terra::focal()` across a range of raster sizes and kernel radii. To keep this vignette fast for CRAN, we load precomputed results if available and provide optional code (disabled) to reproduce full benchmarks locally. --- ## Load libraries and parameters ```{r} library(fastfocal) library(terra) library(dplyr) raster_sizes <- c(100, 250, 500, 1000, 2500, 5000) kernel_sizes <- seq(100, 1000, 100) replicates <- 1 res_m <- 30 crs_m <- "EPSG:3857" set.seed(888) ``` --- ## Create test rasters Each raster is square with 30 m resolution. ```{r} rasters <- lapply(raster_sizes, function(size) { ext_x <- size * res_m ext_y <- size * res_m r <- rast(nrows = size, ncols = size, extent = ext(0, ext_x, 0, ext_y), crs = crs_m) values(r) <- runif(ncell(r)) r }) names(rasters) <- as.character(raster_sizes) ``` --- ## Quick peek ```{r} oldpar <- par(no.readonly = TRUE) par(mfrow = c(2, 3), mar = c(2, 2, 3, 1)) raster_labels <- paste0(raster_sizes, "x", raster_sizes) for (i in seq_along(rasters)) { plot(rasters[[i]], main = paste("Raster:", raster_labels[i])) } par(oldpar) ``` --- ## Optional full benchmark (disabled for speed) Note: running the full grid below can take a while. It is disabled to keep CRAN checks fast. Uncomment to run locally. ```{r eval=FALSE} grid <- expand.grid( raster_size = raster_sizes, d = kernel_sizes, method = c("fastfocal", "terra"), stringsAsFactors = FALSE ) dir.create("benchmark_chunks", showWarnings = FALSE) benchmark_row <- function(idx) { size <- grid$raster_size[idx] d <- grid$d[idx] method <- grid$method[idx] fname <- sprintf("benchmark_chunks/%s_%d_%dm.csv", method, size, d) if (file.exists(fname)) return(NULL) r <- rasters[[as.character(size)]] times <- sapply(seq_len(replicates), function(i) { t0 <- Sys.time() if (method == "fastfocal") { fastfocal(x = r, d = d, w = "circle", fun = "mean", engine = "auto", pad = "auto") } else { w <- focalMat(r, d, type = "circle") if (all(w == 0)) return(NA_real_) focal(r, w = w, fun = mean, na.rm = TRUE, na.policy = "omit") } as.numeric(difftime(Sys.time(), t0, units = "secs")) }) chunk_df <- data.frame(method = method, raster_size = size, d = d, time = times) write.csv(chunk_df, file = fname, row.names = FALSE) } invisible(sapply(seq_len(nrow(grid)), benchmark_row)) # After running, you can combine chunks into a single CSV under inst/extdata/benchmark.csv ``` --- ## Load precomputed results (with fallback) ```{r} bench_path <- system.file("extdata", "benchmark.csv", package = "fastfocal") if (nzchar(bench_path) && file.exists(bench_path)) { df <- read.csv(bench_path) } else { # Fallback tiny demo dataset for CRAN if extdata is not installed df <- expand.grid( method = c("fastfocal", "terra"), raster_size = c(250, 500, 1000), d = c(100, 300, 500), KEEP.OUT.ATTRS = FALSE, stringsAsFactors = FALSE ) set.seed(1) df$time <- ifelse(df$method == "fastfocal", runif(nrow(df), 0.05, 0.20), runif(nrow(df), 0.08, 0.35)) } stopifnot(all(c("method","raster_size","d","time") %in% names(df))) ``` --- ## Summarize and visualize ```{r} # --- summary --- summary_df <- df %>% group_by(method, raster_size, d) %>% summarize( mean_time = mean(time, na.rm = TRUE), se_time = sd(time, na.rm = TRUE) / sqrt(sum(is.finite(time))), .groups = "drop" ) %>% mutate(raster_label = factor( paste0(raster_size, "x", raster_size), levels = paste0(sort(unique(df$raster_size)), "x", sort(unique(df$raster_size))) )) oldpar <- par(no.readonly = TRUE) layout(matrix(1:6, nrow = 2, byrow = TRUE)) par(mar = c(4, 4, 3, 1)) cols <- c("fastfocal" = "#0072B2", "terra" = "#D55E00") raster_labels <- levels(summary_df$raster_label) for (label in raster_labels) { subdf <- subset(summary_df, raster_label == label) if (nrow(subdf) == 0) next plot(NA, xlim = range(subdf$d), ylim = range(subdf$mean_time + subdf$se_time, na.rm = TRUE), xlab = "Kernel size (m)", ylab = "Mean time (s)", main = paste("Raster:", label)) methods <- unique(subdf$method) for (m in methods) { data <- subdf[subdf$method == m, ] lines(data$d, data$mean_time, col = cols[m], type = "b", pch = 16) max_time <- max(subdf$mean_time, na.rm = TRUE) min_se <- 0.001 * max_time se <- ifelse(is.na(data$se_time), 0, data$se_time) se_final <- pmax(se, min_se) suppressWarnings(arrows( x0 = data$d, y0 = data$mean_time - se_final, x1 = data$d, y1 = data$mean_time + se_final, angle = 90, code = 3, length = 0.05, col = cols[m] )) } legend("topleft", legend = methods, col = cols[methods], pch = 16, lty = 1, bty = "n") } layout(1) par(oldpar) ``` --- ## Bonus: accuracy check Compare a single case at moderate size. ```{r} test_r <- rasters[["1000"]] kernel_d <- 500 # fastfocal r_fast <- fastfocal(test_r, d = kernel_d, w = "circle", fun = "mean", engine = "auto", pad = "auto") # terra::focal w <- focalMat(test_r, kernel_d, type = "circle") r_terra <- focal(test_r, w = w, fun = mean, na.rm = TRUE, na.policy = "omit") # Differences r_diff <- abs(r_fast - r_terra) v_diff <- values(r_diff) mean_diff <- mean(v_diff, na.rm = TRUE) max_diff <- max(v_diff, na.rm = TRUE) cat("Mean difference:", round(mean_diff, 6), "\n") cat("Max difference :", round(max_diff, 6), "\n") ``` ### Visual comparison ```{r} oldpar <- par(no.readonly = TRUE) par(mfrow = c(2, 2), mar = c(2, 2, 3, 2)) plot(test_r, main = "Original", col = terrain.colors(20)) plot(r_terra, main = "terra::focal (500 m)", col = terrain.colors(20)) plot(r_fast, main = "fastfocal (500 m)", col = terrain.colors(20)) plot(r_diff, main = "Absolute difference", col = hcl.colors(20, "YlOrRd", rev = TRUE)) par(oldpar) ``` --- ## Session info ```{r} sessionInfo() ``` ## Citation To cite the package: Wan, H. Y. (2025). *fastfocal: Fast Multi-scale Raster Extraction and Moving Window Analysis with FFT*. R package version 0.1.3. Zenodo. https://doi.org/10.5281/zenodo.17074691 ```{r} citation("fastfocal") ```