将 apply() 与简单功能 (SF) 函数一起使用

Use apply() with a simple features (SF) function

提问人:John J. 提问时间:1/30/2018 更新时间:12/7/2021 访问量:3251

问:

我编写了一个函数来计算质心与其多边形边缘之间的最大距离,但我无法弄清楚如何在简单要素 (“sf) data.frame 的每个单独面上运行它。

library(sf)

distance.func <- function(polygon){
  max(st_distance(st_cast(polygon, "POINT"), st_centroid(polygon)))
}

如果我在单个多边形上测试该函数,它会起作用。(警告消息与当前问题无关)。

nc <- st_read(system.file("shape/nc.shp", package="sf")) # built in w/package
nc.1row <- nc[c(1),] # Just keep the first polygon

>distance.func(nc.1row)
24309.07 m
Warning messages:
1: In st_cast.sf(polygon, "POINT") :
   repeating attributes for all sub-geometries for which they may not be constant
2: In st_centroid.sfc(st_geometry(x), of_largest_polygon = of_largest_polygon) :
   st_centroid does not give correct centroids for longitude/latitude data

问题在于将此函数应用于整个 data.frame。

nc$distance <- apply(nc, 1, distance.func)
Error in UseMethod("st_cast") :
 no applicable method for 'st_cast' applied to an object of class "list"

我该怎么做才能为类“sf”对象中的每个多边形运行此函数(或类似的函数)?

r 地理信息系统 r-sf

评论

2赞 lbusett 1/30/2018
一个好的旧循环呢?dist = 列表();for (i in seq_along(nc)) dist[[i]] <- distance.func(nc[i,])for
0赞 John J. 1/30/2018
你是对的。这很好用。如果你把它作为答案,我会接受它。谢谢!

答:

14赞 lbusett 1/30/2018 #1

这里的问题是,直接在对象上使用类似 apply 的函数是“有问题的”,因为几何列是一个列表列,它与“apply”构造不能很好地交互。sf

最简单的解决方法是只使用 for 循环:

library(sf)

nc <- st_read(system.file("shape/nc.shp", package="sf")) %>% 
  st_transform(3857)

distance.func <- function(polygon){
  max(st_distance(st_cast(polygon, "POINT"), st_centroid(polygon)))
}
dist <- list()
for (i in seq_along(nc[[1]])) dist[[i]] <- distance.func(nc[i,]) 

head(unlist(dist))
# [1] 30185.34 27001.39 34708.57 52751.61 57273.54 34598.17

,但它很慢。

为了能够使用类似 apply 的函数,只需将对象的几何列传递给函数。像这样的东西会起作用:

library(purrr)

distance.func_lapply <- function(polygon){
  polygon <- st_sfc(polygon)
  max(st_distance(st_cast(polygon, "POINT"), st_centroid(polygon)))
}

dist_lapply <- lapply(st_geometry(nc),  distance.func_lapply)
dist_map    <- purrr::map(st_geometry(nc), distance.func_lapply)

all.equal(dist, dist_lapply)
# [1] TRUE
all.equal(dist, dist_map)
# [1] TRUE

但是请注意,我不得不轻而易举地修改距离函数,添加一个调用,否则你会得到很多“In st_cast.MULTIPOLYGON(polygon, “POINT”) : point from first coordinate only“警告,结果不正确(我没有调查其原因 - 显然st_cast对象上的行为与对象上的行为不同)。st_sfcsfgsfc

在速度方面,和 解决方案都比 for 循环高出近一个数量级:lapplymap

microbenchmark::microbenchmark(
  forloop = {for (i in seq_along(nc[[1]])) dist[[i]] <- distance.func(nc[i,])}, 
  map     = {dist_map <- purrr::map(st_geometry(nc), distance.func_lapply)},  
  lapply  = {dist_lapply <- lapply(st_geometry(nc),  distance.func_lapply)}, times = 10)

Unit: milliseconds
    expr      min       lq     mean   median       uq       max neval
 forloop 904.8827 919.5636 936.2214 920.7451 929.7186 1076.9646    10
     map 122.7597 124.9074 126.1796 126.3326 127.6940  128.7551    10
  lapply 122.9131 125.3699 126.9642 126.8100 129.3791  131.2675    10

评论

0赞 Faustin Gashakamba 3/27/2023
我艰难地发现了这个事实。尽管循环在 R 中很慢,但它们在语言中仍然占有一席之地。例如,我可以添加一个整洁的栏来监控流程的进度。我发现使用这些功能时很难做到。for*apply
1赞 Jean-François Bourdon 10/29/2021 #2

还有另一种方法可以应用于简单的功能,尽管并不比使用 for 循环更好。在应用距离函数之前,您可以先创建一个简单要素列表。lapply

distance.func <- function(polygon){
  max(st_distance(st_cast(polygon, "POINT"), st_centroid(polygon)))
}

distance.func.ls_sf <- function(sf){
  ls_sf <- lapply(1:nrow(sf), function(x, sf) {sf[x,]}, sf)
  dist <- lapply(ls_sf, distance.func)
}

dist_lapply_ls_sf <- distance.func.ls_sf(nc)

all.equal(dist, dist_lapply_ls_sf)
# [1] TRUE

性能几乎和 for 循环一样差......甚至似乎 4 年后(现在是 R 4.1.1 和 sf 1.0-3),它几乎是两个数量级最差(而不是一个)或使用......lapplymapst_geometry(nc)

microbenchmark::microbenchmark(
  forloop = {for (i in seq_along(nc[[1]])) dist[[i]] <- distance.func(nc[i,])}, 
  map     = {dist_map <- purrr::map(st_geometry(nc), distance.func_lapply)},  
  lapply  = {dist_lapply <- lapply(st_geometry(nc),  distance.func_lapply)},
  ls_sf   = {dist_lapply_ls_sf <- distance.func.ls_sf(nc)},
  times = 10)

Unit: milliseconds
    expr       min        lq      mean    median        uq       max neval
 forloop 7726.9337 7744.7534 7837.6937 7781.2301 7850.7447 8221.2092    10
     map  124.1067  126.2212  135.1502  128.4745  130.2372  182.1479    10
  lapply  122.0224  125.6585  130.6488  127.9388  134.1495  147.9301    10
   ls_sf 7722.1066 7733.8204 7785.8104 7775.5011 7814.3849 7911.3466    10

因此,这是一个糟糕的解决方案,除非您应用于简单特征对象的函数花费的时间比 .st_distance()

如果您需要属性怎么办?

如果您的函数需要 sf 对象的几何和属性部分,那么使用是一个好方法。下面是一个使用三种方法计算婴儿猝死密度 (SID/km²) 的示例:mapply

  • for
  • 在使用之前提取每个特征lapply
  • mapply
microbenchmark::microbenchmark(
  forLoop =
    {
      sid.density.for <- vector("list", nrow(nc))
      for (i in seq(nrow(nc))) sid.density.for[[i]] <- nc[i,][["SID74"]] / st_area(nc[i,]) / 1000^2
    },
  list_nc =
    {
      list_nc <- lapply(seq(nrow(nc)), function(x, nc) { nc[x,] }, nc)
      sid.density.lapply <- lapply(list_nc, function(x) { x[["SID74"]] / as.numeric(st_area(x)) / 1000^2 })
    },
  mapply =
    {
      sid.density.func <- function(geometry, attribute) { attribute / st_area(geometry) / 1000^2 }
      sid.density.mapply <- mapply(sid.density.func, st_geometry(nc), nc[["SID74"]], SIMPLIFY = FALSE)
    },
  times = 10)

Unit: milliseconds
    expr       min        lq       mean     median        uq       max neval
 forLoop 4511.7203 4515.5997 4557.73503 4542.75200 4560.5508 4707.2877    10
 list_nc 4356.3801 4400.5640 4455.35743 4440.38775 4475.2213 4717.5218    10
  mapply   17.4783   17.6885   18.20704   17.99295   18.3078   20.1121    10