Р: Как заменить значения в геометрии многоугольника без цикла for?

У меня есть более 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.

for (row in nrow(myshape)) будет перебирать только nrow(myshape), а не от 1 до nrow(myshape) ! Вместо этого попробуйте 1:nrow(myshape). В противном случае это выглядит нормально (хотя это модифицирует X, а не Z, но вы, наверное, это знаете...)
Spacedman 15.05.2024 16:41

На самом деле это не работает для общей геометрии мультиполигона, поскольку вы меняете только первое кольцо геометрии мультиполигона. Вам действительно нужно перебрать все myshape$geometry[row][[1]][[ring]][[part]][, 2] для всех значений row, ring и part внутри функций.

Spacedman 15.05.2024 18:46

Спасибо, что заметили 1:nrows(myshape)! Сейчас я отредактировал исходное сообщение и, надеюсь, пояснил, что мне нужно обновить все значения для каждого кольца.

nola 16.05.2024 09:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
3
129
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Происходит что-то такое, что, возможно, кто-то другой сможет объяснить — возможно, весь столбец 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 установлен только для первого звонка.

Spacedman 15.05.2024 18:47

Просто следуя коду ОП. Я смогу обновить его, как только ОП подтвердит, что целью является не только первый звонок.

jblood94 15.05.2024 20:34

Я попробовал простую матрицу, и цикл был «счастливым»: 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

Carl Witthoft 16.05.2024 03:41

@jblood94 Огромное СПАСИБО за то, что научили меня функции map()! Мой сценарий, выполнение которого заняло 45 минут, теперь завершен всего за 26 секунд, поскольку у меня было несколько других циклов, которые делали то же самое. Заменил их на map() и теперь он молниеносно молниеносный.

nola 16.05.2024 10:27

@CarlWitthoft, я подозреваю, что это как-то связано со структурой myshape$geometry и способом доступа к данным в цикле for.

jblood94 16.05.2024 12:16

@Норрислам, я не думаю, что это Map само по себе особенно быстро. Цикл for обычно будет не менее быстрым, чем Map. Как я уже говорил @CarlWitthoft, я подозреваю, что низкая производительность как-то связана со структурой myshape$geometry и способом доступа к данным в цикле for.

jblood94 16.05.2024 12:19

@jblood94 спасибо за разъяснения. не уверен, почему доступ к геометрии занимает так много времени. Я проведу несколько тестов, когда у меня будет возможность. Спасибо за помощь!

nola 17.05.2024 12:35

Это относительно хорошо масштабируется. Повторное выражение основано на данных nc.shp, более крупный пример см. ниже. Рабочий процесс:

  1. Создайте new_values ​​df со значениями z и соответствующим идентификатором объекта, например. nc$NAME
  2. 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! Мне понадобится немного времени, чтобы понять и реализовать код моего скрипта.

nola 16.05.2024 10:15

@Norrislam - я обновил код аннотациями и удалил некоторые ненужные шаги.

L Tyrone 17.05.2024 05:50

Спасибо! Я все еще новичок в R и пытаюсь осмыслить конвейер dplyr, так здорово увидеть решение, использующее это.

nola 17.05.2024 12:37

Другие вопросы по теме