У меня есть более 60 000 контуров строительных полигонов в формате шейп-файла (polygonZ), где мне нужно заменить значения z (для каждого кольца) для всех вершин многоугольника, чтобы все значения Z для многоугольника были одинаковыми. Проблема в том, что у нас есть 3D-полигоны, которые не редактировались в 3D, поэтому некоторые кольца имеют правильные значения Z для всех вершин, в то время как другие кольца имеют несколько ошибочных значений Z, которые необходимо исправить.
Я могу сделать это с помощью цикла for, но на это уходит добрых 45 минут, так как нужно перебрать все полигоны.
Мне было интересно, есть ли у кого-нибудь идеи, как это ускорить?
Я попробовал следующее, думая, что смогу заменить все значения сразу с помощью индексации, но это не сработало. Я предполагаю, что это связано с тем, что количество вершин на полигон отличается от «новых значений», которые я хочу использовать в качестве замены.
> myshape$geometry[][[1]][[1]][[1]][,2] <- new_values[]
Error in myshape$geometry[][[1]][[1]][[1]][, 2] <- new_values[] :
number of items to replace is not a multiple of replacement length
Вот рабочий пример с использованием стандартных данных R. В этом примере я заменяю X вместо Z.
library(sf)
library(tictoc)
# load test data
myshape <- st_read(system.file("shape/nc.shp", package = "sf"))
# recast to polygon (my actual data are polygons, not multipolygons)
myshape <- st_cast(myshape, "POLYGON")
# make some random data
new_values <- runif (nrow(myshape))
# replace some values
tic()
for (row in 1:nrow(myshape)) {
# replace
myshape$geometry[row][[1]][[1]][,2] <- new_values[row]
}
toc()
редактировать: уточнено, что каждое кольцо будет иметь одинаковое значение Z, преобразовано в многоугольники (мои фактические данные - это многоугольники), предоставлено описание моей проблемы, а также исправлена ошибка в моем цикле for.
На самом деле это не работает для общей геометрии мультиполигона, поскольку вы меняете только первое кольцо геометрии мультиполигона. Вам действительно нужно перебрать все myshape$geometry[row][[1]][[ring]][[part]][, 2]
для всех значений row
, ring
и part
внутри функций.
Спасибо, что заметили 1:nrows(myshape)! Сейчас я отредактировал исходное сообщение и, надеюсь, пояснил, что мне нужно обновить все значения для каждого кольца.
Происходит что-то такое, что, возможно, кто-то другой сможет объяснить — возможно, весь столбец geometry
перезаписывается при каждой итерации с использованием цикла for
. Подход Map
работает на порядки быстрее с более крупным объектом sf
:
library(sf)
# load test data
myshape <- st_read(system.file("shape/nc.shp", package = "sf"))[sample(100, 1e4, 1),]
# make some random data
new_values <- runif (1e4)
myshape$geometry2 <- myshape$geometry3 <- myshape$geometry
system.time({
for (row in 1:nrow(myshape)) {
# replace
myshape$geometry2[row][[1]][[1]][[1]][,2] <- new_values[row]
}
})
#> user system elapsed
#> 105.19 0.39 105.64
system.time({
f <- function(g, x) {
g[[1]][[1]][,2] <- x
g
}
myshape$geometry3[] <- Map(f, myshape$geometry, new_values)
})
#> user system elapsed
#> 0.07 0.00 0.06
identical(myshape$geometry2, myshape$geometry3)
#> [1] TRUE
Обратите внимание, что мой комментарий выше о мультиполигональной геометрии применим и здесь. например, проверьте myshape$geometry[4]
, у которого Y установлен только для первого звонка.
Просто следуя коду ОП. Я смогу обновить его, как только ОП подтвердит, что целью является не только первый звонок.
Я попробовал простую матрицу, и цикл был «счастливым»: foo <- матрица(nr=100,nc=10000) floop <- function(x,y) { for(jrow in 1:nrow(x)) x[jrow ,] <- y return(invisible(x)) } system.time(floop(foo,1:1e4)) system.time(floop(foo,1:1e4)) user истекло 0,034 0,010 0,042
@jblood94 Огромное СПАСИБО за то, что научили меня функции map()! Мой сценарий, выполнение которого заняло 45 минут, теперь завершен всего за 26 секунд, поскольку у меня было несколько других циклов, которые делали то же самое. Заменил их на map() и теперь он молниеносно молниеносный.
@CarlWitthoft, я подозреваю, что это как-то связано со структурой myshape$geometry
и способом доступа к данным в цикле for
.
@Норрислам, я не думаю, что это Map
само по себе особенно быстро. Цикл for
обычно будет не менее быстрым, чем Map
. Как я уже говорил @CarlWitthoft, я подозреваю, что низкая производительность как-то связана со структурой myshape$geometry
и способом доступа к данным в цикле for
.
@jblood94 спасибо за разъяснения. не уверен, почему доступ к геометрии занимает так много времени. Я проведу несколько тестов, когда у меня будет возможность. Спасибо за помощь!
Это относительно хорошо масштабируется. Повторное выражение основано на данных nc.shp, более крупный пример см. ниже. Рабочий процесс:
st_cast()
myshape для многоугольника, назначайте pid отдельным многоугольникам, st_cast()
для точек, объединяйте значения XYZ и создавайте геометрию, перестраивайте мультиполигоныlibrary(sf)
library(sfheaders)
library(dplyr)
library(ggplot2)
# Load test data
myshape <- st_read(system.file("shape/nc.shp", package = "sf"))
# Make some random z coordinate data with associated feature ID e.g. myshape$NAME
set.seed(1)
new_values <- data.frame(NAME = myshape$NAME,
z = runif (nrow(myshape)))
# Create output with z values assigned
sf_poly <- myshape %>%
# Split multipolygons and assign unique id to each polygon for
st_cast("POLYGON") %>%
mutate(pid = 1:n(), .by = NAME) %>%
# Cast to point, retrieve lon/lat values, left_join() z values
st_cast("POINT") %>%
mutate(x = st_coordinates(.)[,1],
y = st_coordinates(.)[,2]) %>%
left_join(., new_values, by = "NAME") %>%
# Drop original geometry, create new XYZ geometry, assign CRS
st_drop_geometry() %>%
sf_pt(., keep = TRUE) %>%
rename(geometryz = geometry) %>%
st_set_crs(st_crs(myshape)) %>%
# Rebuild multipolygons in two steps, otherwise non-contiguous features will be joined
summarise(geometryz = st_combine(geometryz), .by = c(NAME, pid)) %>%
st_cast("POLYGON") %>%
summarise(geometryz = st_combine(geometryz), .by = NAME) %>%
st_cast("MULTIPOLYGON")
Результат из большего набора данных на ПК с низкими характеристиками:
Используя эти данные с 60 324 полигонами:
# Set extent
sfc <- st_sfc(st_polygon(list(rbind(c(0,0), c(20,0), c(20,20), c(0,0)))))
# Make polygon sf
sf_grid <- st_make_grid(sfc, cellsize = .088, square = FALSE) %>%
st_as_sf() %>%
mutate(NAME = paste0("poly", sprintf("%02d", 1:n()))) %>%
st_set_crs(st_crs(myshape)) %>%
rename(geometry = x)
Это заняло ~4,63 минуты. Не совсем репрезентативно, поскольку sf_grid не имеет функций мультиполигонов, но, тем не менее, обеспечивает приличное сокращение времени.
УХ ТЫ! это какое-то удивительное волшебство R! Мне понадобится немного времени, чтобы понять и реализовать код моего скрипта.
@Norrislam - я обновил код аннотациями и удалил некоторые ненужные шаги.
Спасибо! Я все еще новичок в R и пытаюсь осмыслить конвейер dplyr, так здорово увидеть решение, использующее это.
for (row in nrow(myshape))
будет перебирать толькоnrow(myshape)
, а не от 1 доnrow(myshape)
! Вместо этого попробуйте1:nrow(myshape)
. В противном случае это выглядит нормально (хотя это модифицирует X, а не Z, но вы, наверное, это знаете...)