Hi there!
Just a short note on the distance calculations. From my experience, it's one of the major bottlenecks in the package. For example, I can't even apply the spatialsample package for my datasets with 40k points in it.
|
distmat <- sf::st_distance(data) |
What if we replace sf::st_distance with a slightly more robust function? For example, there is some evidence that Rfast::Dist works two to three times faster than sf::st_distance(). Perhaps it would be worth adding one more package to the dependency list in the name of a speed boost?
Unfortunately, both algorithms seem to have O(n²) time complexity, which is not good, and Rfast is not a silver bullet. Additionally, in the case of longlat coordinates, sf::st_distance() may still be preferable as it computes Great Circle distance.
I can prepare a PR if you like the approach.
See below some benchmarking
library(sf)
#> Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.3.1; sf_use_s2() is TRUE
suppressPackageStartupMessages(library(Rfast))
library(ggplot2)
library(dplyr)
library(tidyr)
library(bench)
# Function to create points
create_points <- function(n) {
bbox <- sf::st_bbox(c(
xmin = 1400000, xmax = 2100000,
ymin = 5400000, ymax = 6200000
))
bbox <- sf::st_as_sfc(bbox)
sf::st_crs(bbox) <- 2193
sf::st_sample(bbox, n)
}
# Run benchmarks for different n
ns <- seq(1000, 10000, by = 1000)
results <- list()
for (n in ns) {
set.seed(n)
pts <- create_points(n)
bm <- bench::mark(
sf = sf::st_distance(pts, which = "Euclidean"),
Rfast = pts |>
sf::st_coordinates() |>
Rfast::Dist(method = "euclidean"),
time_unit = "ms",
iterations = 5,
check = FALSE
)
# Add n to the results
bm$n <- n
results[[as.character(n)]] <- bm
}
# Combine and prepare results
benchmark_df <- do.call(rbind, results)
# Reshape for faceted plotting
plot_df <-
benchmark_df |>
dplyr::transmute(
n,
method = as.character(expression),
time = as.numeric(median),
mem_alloc = as.numeric(mem_alloc)
) |>
tidyr::pivot_longer(
cols = c(time, mem_alloc),
names_to = "metric",
values_to = "value"
) |>
dplyr::mutate(
metric = factor(metric,
levels = c("time", "mem_alloc"),
labels = c("Time (milliseconds)", "Memory (bytes)")
)
)
# Plot the results
plot_df |>
ggplot2::ggplot(
ggplot2::aes(x = n, y = value, color = method)
) +
ggplot2::geom_smooth(se = FALSE) +
ggplot2::geom_point() +
ggplot2::scale_x_continuous(breaks = ns) +
ggplot2::scale_y_continuous(
breaks = scales::pretty_breaks(n = 5),
labels = scales::label_number(scale_cut = scales::cut_short_scale())
) +
ggplot2::facet_wrap(~metric, scales = "free_y", nrow = 2) +
ggplot2::labs(
title = "sf vs Rfast Distance Calculations",
y = "",
x = "Number of Points",
color = "Method"
) +
ggplot2::theme_minimal() +
ggplot2::theme(
legend.position = "bottom",
panel.grid.minor = ggplot2::element_blank(),
strip.text = ggplot2::element_text(face = "bold")
)
#> `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

# Compare results
set.seed(123)
pts <- create_points(1000)
# Euclidan distance
sf_example <-
sf::st_distance(pts, which = "Euclidean")
Rfast_example <- pts |>
sf::st_coordinates() |>
Rfast::Dist(method = "euclidean")
waldo::compare(as.double(sf_example), as.double(Rfast_example))
#> ✔ No differences
# Session Info
devtools::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#> setting value
#> version R version 4.4.1 Patched (2024-08-05 r86984 ucrt)
#> os Windows 10 x64 (build 19045)
#> system x86_64, mingw32
#> ui RTerm
#> language (EN)
#> collate English_United States.utf8
#> ctype English_United States.utf8
#> tz Pacific/Auckland
#> date 2024-11-13
#> pandoc 3.2 @ c:\\scoop\\apps\\positron\\2024.11.0-140\\resources\\app\\quarto\\bin\\tools/ (via rmarkdown)
#>
#> ─ Packages ───────────────────────────────────────────────────────────────────
#> package * version date (UTC) lib source
#> bench * 1.1.3 2023-05-04 [1] RSPM
#> cachem 1.0.8 2023-05-01 [1] CRAN (R 4.4.1)
#> class 7.3-22 2023-05-03 [2] CRAN (R 4.4.1)
#> classInt 0.4-10 2023-09-05 [1] CRAN (R 4.4.1)
#> cli 3.6.2 2023-12-11 [1] CRAN (R 4.4.1)
#> colorspace 2.1-0 2023-01-23 [1] CRAN (R 4.4.1)
#> DBI 1.2.2 2024-02-16 [1] CRAN (R 4.4.1)
#> devtools 2.4.5 2022-10-11 [1] RSPM (R 4.4.0)
#> digest 0.6.35 2024-03-11 [1] CRAN (R 4.4.1)
#> dplyr * 1.1.4 2023-11-17 [1] CRAN (R 4.4.1)
#> e1071 1.7-14 2023-12-06 [1] RSPM
#> ellipsis 0.3.2 2021-04-29 [1] CRAN (R 4.4.1)
#> evaluate 0.23 2023-11-01 [1] CRAN (R 4.4.1)
#> fansi 1.0.6 2023-12-08 [1] CRAN (R 4.4.1)
#> farver 2.1.1 2022-07-06 [1] CRAN (R 4.4.1)
#> fastmap 1.1.1 2023-02-24 [1] CRAN (R 4.4.1)
#> fs 1.6.4 2024-04-25 [1] CRAN (R 4.4.1)
#> generics 0.1.3 2022-07-05 [1] CRAN (R 4.4.1)
#> ggplot2 * 3.5.1 2024-04-23 [1] CRAN (R 4.4.1)
#> glue 1.7.0 2024-01-09 [1] CRAN (R 4.4.1)
#> gtable 0.3.5 2024-04-22 [1] CRAN (R 4.4.1)
#> htmltools 0.5.8.1 2024-04-04 [1] CRAN (R 4.4.1)
#> htmlwidgets 1.6.4 2023-12-06 [1] CRAN (R 4.4.1)
#> httpuv 1.6.15 2024-03-26 [1] CRAN (R 4.4.1)
#> KernSmooth 2.23-22 2023-07-10 [1] CRAN (R 4.4.1)
#> knitr 1.46 2024-04-06 [1] CRAN (R 4.4.1)
#> later 1.3.2 2023-12-06 [1] CRAN (R 4.4.1)
#> lattice 0.22-6 2024-03-20 [2] CRAN (R 4.4.1)
#> lifecycle 1.0.4 2023-11-07 [1] CRAN (R 4.4.1)
#> magrittr 2.0.3 2022-03-30 [1] CRAN (R 4.4.1)
#> Matrix 1.7-0 2024-04-26 [2] CRAN (R 4.4.1)
#> memoise 2.0.1 2021-11-26 [1] CRAN (R 4.4.1)
#> mgcv 1.9-1 2023-12-21 [2] CRAN (R 4.4.1)
#> mime 0.12 2021-09-28 [1] CRAN (R 4.4.0)
#> miniUI 0.1.1.1 2018-05-18 [1] CRAN (R 4.4.1)
#> munsell 0.5.1 2024-04-01 [1] CRAN (R 4.4.1)
#> nlme 3.1-164 2023-11-27 [1] CRAN (R 4.4.1)
#> pillar 1.9.0 2023-03-22 [1] CRAN (R 4.4.1)
#> pkgbuild 1.4.4 2024-03-17 [1] CRAN (R 4.4.1)
#> pkgconfig 2.0.3 2019-09-22 [1] CRAN (R 4.4.1)
#> pkgload 1.3.4 2024-01-16 [1] RSPM (R 4.4.0)
#> profmem 0.6.0 2020-12-13 [1] RSPM
#> profvis 0.3.8 2023-05-02 [1] CRAN (R 4.4.1)
#> promises 1.3.0 2024-04-05 [1] CRAN (R 4.4.1)
#> proxy 0.4-27 2022-06-09 [1] RSPM
#> purrr 1.0.2 2023-08-10 [1] CRAN (R 4.4.1)
#> R.cache 0.16.0 2022-07-21 [1] CRAN (R 4.4.1)
#> R.methodsS3 1.8.2 2022-06-13 [1] CRAN (R 4.4.0)
#> R.oo 1.26.0 2024-01-24 [1] CRAN (R 4.4.0)
#> R.utils 2.12.3 2023-11-18 [1] CRAN (R 4.4.1)
#> R6 2.5.1 2021-08-19 [1] CRAN (R 4.4.1)
#> Rcpp * 1.0.12 2024-01-09 [1] CRAN (R 4.4.1)
#> RcppParallel * 5.1.7 2023-02-27 [1] CRAN (R 4.4.1)
#> RcppZiggurat * 0.1.6 2020-10-20 [1] RSPM
#> remotes 2.5.0.9000 2024-10-01 [1] Github (r-lib/remotes@5b7eb08)
#> reprex 2.1.0 2024-01-11 [1] CRAN (R 4.4.1)
#> Rfast * 2.1.0 2023-11-09 [1] RSPM
#> rlang 1.1.4 2024-06-04 [1] CRAN (R 4.4.1)
#> rmarkdown 2.28 2024-08-17 [1] RSPM
#> scales 1.3.0 2023-11-28 [1] CRAN (R 4.4.1)
#> sessioninfo 1.2.2 2021-12-06 [1] CRAN (R 4.4.1)
#> sf * 1.0-19 2024-11-05 [1] RSPM
#> shiny 1.8.1.1 2024-04-02 [1] RSPM (R 4.4.0)
#> stringi 1.8.3 2023-12-11 [1] CRAN (R 4.4.1)
#> stringr 1.5.1 2023-11-14 [1] CRAN (R 4.4.1)
#> styler 1.10.3 2024-04-07 [1] CRAN (R 4.4.1)
#> tibble 3.2.1 2023-03-20 [1] CRAN (R 4.4.1)
#> tidyr * 1.3.1 2024-01-24 [1] CRAN (R 4.4.1)
#> tidyselect 1.2.1 2024-03-11 [1] CRAN (R 4.4.1)
#> units 0.8-5 2023-11-28 [1] CRAN (R 4.4.1)
#> urlchecker 1.0.1.9000 2024-09-04 [1] https://r-lib.r-universe.dev (R 4.4.1)
#> usethis 3.0.0 2024-07-29 [1] CRAN (R 4.4.1)
#> utf8 1.2.4 2023-10-22 [1] CRAN (R 4.4.1)
#> vctrs 0.6.5 2023-12-01 [1] CRAN (R 4.4.1)
#> waldo 0.5.2 2023-11-02 [1] CRAN (R 4.4.1)
#> withr 3.0.0 2024-01-16 [1] CRAN (R 4.4.1)
#> xfun 0.43 2024-03-25 [1] CRAN (R 4.4.1)
#> xtable 1.8-4 2019-04-21 [1] CRAN (R 4.4.1)
#> yaml 2.3.8 2023-12-11 [1] CRAN (R 4.4.1)
#>
#> [1] C:/Users/TsyplenkovA/AppData/Local/R/win-library/4.4
#> [2] C:/Program Files/R/R-4.4.1patched/library
#>
#> ──────────────────────────────────────────────────────────────────────────────
Created on 2024-11-13 with reprex v2.1.0
Hi there!
Just a short note on the distance calculations. From my experience, it's one of the major bottlenecks in the package. For example, I can't even apply the
spatialsamplepackage for my datasets with 40k points in it.spatialsample/R/buffer.R
Line 27 in ded1691
What if we replace
sf::st_distancewith a slightly more robust function? For example, there is some evidence thatRfast::Distworks two to three times faster thansf::st_distance(). Perhaps it would be worth adding one more package to the dependency list in the name of a speed boost?Unfortunately, both algorithms seem to have O(n²) time complexity, which is not good, and
Rfastis not a silver bullet. Additionally, in the case of longlat coordinates,sf::st_distance()may still be preferable as it computes Great Circle distance.I can prepare a PR if you like the approach.
See below some benchmarking
Created on 2024-11-13 with reprex v2.1.0