The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.

Benchmarking fastfocal

Version 0.1.3 (2025)

Ho Yi Wan

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

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.

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

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.

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)

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

# --- 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.

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")
#> Mean difference: 0
cat("Max difference :", round(max_diff, 6), "\n")
#> Max difference : 0

Visual comparison

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

sessionInfo()
#> R version 4.5.1 (2025-06-13 ucrt)
#> Platform: x86_64-w64-mingw32/x64
#> Running under: Windows 11 x64 (build 26100)
#> 
#> Matrix products: default
#>   LAPACK version 3.12.1
#> 
#> locale:
#> [1] LC_COLLATE=C                          
#> [2] LC_CTYPE=English_United States.utf8   
#> [3] LC_MONETARY=English_United States.utf8
#> [4] LC_NUMERIC=C                          
#> [5] LC_TIME=English_United States.utf8    
#> 
#> time zone: America/New_York
#> tzcode source: internal
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] dplyr_1.1.4     terra_1.8-60    fastfocal_0.1.3
#> 
#> loaded via a namespace (and not attached):
#>  [1] vctrs_0.6.5       cli_3.6.5         knitr_1.50        rlang_1.1.6      
#>  [5] xfun_0.52         generics_0.1.4    jsonlite_2.0.0    glue_1.8.0       
#>  [9] htmltools_0.5.8.1 sass_0.4.10       rmarkdown_2.29    evaluate_1.0.5   
#> [13] jquerylib_0.1.4   tibble_3.3.0      fastmap_1.2.0     yaml_2.3.10      
#> [17] lifecycle_1.0.4   compiler_4.5.1    codetools_0.2-20  pkgconfig_2.0.3  
#> [21] Rcpp_1.1.0        rstudioapi_0.17.1 digest_0.6.37     R6_2.6.1         
#> [25] tidyselect_1.2.1  pillar_1.11.0     magrittr_2.0.3    bslib_0.9.0      
#> [29] tools_4.5.1       cachem_1.1.0

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

citation("fastfocal")
#> To cite fastfocal in publications, please use:
#> 
#>   Wan HY (2025). _fastfocal: Fast Multi-scale Raster Extraction and
#>   Moving Window Analysis with FFT_. doi:10.5281/zenodo.17074691
#>   <https://doi.org/10.5281/zenodo.17074691>, R package version 0.1.3,
#>   <https://hoyiwan.github.io/fastfocal/>.
#> 
#> A BibTeX entry for LaTeX users is
#> 
#>   @Manual{,
#>     title = {fastfocal: Fast Multi-scale Raster Extraction and Moving Window Analysis with FFT},
#>     author = {Ho Yi Wan},
#>     year = {2025},
#>     note = {R package version 0.1.3},
#>     doi = {10.5281/zenodo.17074691},
#>     url = {https://hoyiwan.github.io/fastfocal/},
#>     publisher = {Zenodo},
#>   }

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.