The speed difference is likely due to QGIS using GEOS backend and the current stable sf (v1.0 plus) using s2 library from Google; for more information consider official documentation https://r-spatial.github.io/sf/articles/sf7.html
The s2 library / package introduces a new dimension to intersections - the concept of a model. In the default behaviour (model = "semi-open" for polygons) each polygon contains only half of its boundaries - as consequence bordering polygons don't have any points in common ({sf}
sometimes overrides this default; it is new and not fully settled yet).
This goes against the logic of GEOS (where polygons contain their boundaries in the entirety = bordering polygons share a line).
Good piece of news is that you can use the model
argument to drive the behaviour - model = "closed"
forces the old behavior, model = "open"
should in theory instruct sf::st_join()
to ignore case when only border is shared.
For an example of impact of model consider this example, built on the well known & much loved nc.shp
from {sf}
.
In the first case (with defaults) County Mecklenburg shares no points with the rest of NC; in case of a closed model it shares the internal boundaties / the polygon is open, because to the south from Charlotte is South Carolina, out of scope of the nc.shp
file.
library(sf)
shape <- st_read(system.file("shape/nc.shp", package="sf")) %>%
st_transform(4326)
# cnty mecklenburg - as in Charlotte of Mecklenburg-Strelitz
mecklenburg <- shape[68, ]
rest_of_nc <- shape[-68, ]
# default - nothing
st_intersection(rest_of_nc,
mecklenburg) %>%
st_geometry() %>%
plot()
# closed model - all internal boundaries of cnty Mecklenburg
st_intersection(rest_of_nc,
mecklenburg,
model = "closed") %>%
st_geometry() %>%
plot()

GEOS
is much faster and I recommend usingst_transfrom()
to reproject your layers to a suitable projection and then switch-offs2
(sf_use_s2
) before running anysf
function in the family:st_intersection
,st_difference
, etc.