як накласти багатокутник на SpatialPointsDataFrame та зберегти SPDF дані?


17

У мене є SpatialPointsDataFrameдеякі додаткові дані. Я хотів би витягти ці точки всередині багатокутника і в той же час зберегти SPDFоб'єкт та відповідні йому дані.

Поки що у мене мало шансів і вдався до узгодження та об'єднання через загальний ідентифікатор, але це працює лише тому, що у мене є сітчасті дані з індивідуальними ідентифікаторами.

Ось короткий приклад, я шукаю точки всередині Червоної площі.

library(sp)
set.seed(357)
pts <- data.frame(x = rnorm(100), y = rnorm(100), var1 = runif(100), var2 = sample(letters, 100, replace = TRUE))
coordinates(pts) <- ~ x + y
class(pts)
plot(pts)
axis(1); axis(2)

ply <- matrix(c(-1,-1, 1,-1, 1,1, -1,1, -1,-1), ncol = 2, byrow = TRUE)
ply <- SpatialPolygons(list(Polygons(list(Polygon(ply)), ID = 1)))
ply <- SpatialPolygonsDataFrame(Sr = ply, data = data.frame(polyvar = 357))
plot(ply, add = TRUE, border = "red")

Найбільш очевидним підходом було б використання over, але це повертає дані з багатокутника.

> over(pts, ply)
    polyvar
1        NA
2       357
3       357
4        NA
5       357
6       357

1
Дякуємо, що надали відтворюваний приклад. Завжди допомагає, коли намагається зрозуміти проблему!
fdetsch

Відповіді:


21

З sp::overдовідки:

 x = "SpatialPoints", y = "SpatialPolygons" returns a numeric
      vector of length equal to the number of points; the number is
      the index (number) of the polygon of ‘y’ in which a point
      falls; NA denotes the point does not fall in a polygon; if a
      point falls in multiple polygons, the last polygon is
      recorded.

Так що, якщо вам конвертувати SpatialPolygonsDataFrameв SpatialPolygonsвас отримати назад вектор індексів і ви можете підмножини окуляри на NA:

> over(pts,as(ply,"SpatialPolygons"))
  [1] NA  1  1 NA  1  1 NA NA  1  1  1 NA NA  1  1  1  1  1 NA NA NA  1 NA  1 NA
 [26]  1  1  1 NA NA NA NA NA  1  1 NA NA NA  1  1  1 NA  1  1  1 NA NA NA  1  1
 [51]  1 NA NA NA  1 NA  1 NA  1 NA NA  1 NA  1  1 NA  1  1 NA  1 NA  1  1  1  1
 [76]  1  1  1  1  1 NA NA NA  1 NA  1 NA NA NA NA  1  1 NA  1 NA NA  1  1  1 NA

> nrow(pts)
[1] 100
> pts = pts[!is.na(over(pts,as(ply,"SpatialPolygons"))),]
> nrow(pts)
[1] 54
> head(pts@data)
         var1 var2
2  0.04001092    v
3  0.58108350    v
5  0.85682609    q
6  0.13683264    y
9  0.13968804    m
10 0.97144627    o
> 

Для сумнівів, ось докази того, що накладні перетворення не є проблемою:

Дві функції - спочатку метод Джефрі Еванса, потім мій оригінал, потім мій злом, а потім версія, заснована на gIntersectsвідповіді Джоша О'Браєна:

evans <- function(pts,ply){
  prid <- over(pts,ply)
  ptid <- na.omit(prid) 
  pt.poly <- pts[as.numeric(as.character(row.names(ptid))),]
  return(pt.poly)
}

rowlings <- function(pts,ply){
  return(pts[!is.na(over(pts,as(ply,"SpatialPolygons"))),])
}

rowlings2 <- function(pts,ply){
  class(ply) <- "SpatialPolygons"
  return(pts[!is.na(over(pts,ply)),])
}

obrien <- function(pts,ply){
pts[apply(gIntersects(columbus,pts,byid=TRUE),1,sum)==1,]
}

Тепер для прикладу реального світу я розкидав кілька випадкових точок по columbusнабору даних:

require(spdep)
example(columbus)
pts=data.frame(
    x=runif(100,5,12),
    y=runif(100,10,15),
    z=sample(letters,100,TRUE))
coordinates(pts)=~x+y

Виглядає чудово

plot(columbus)
points(pts)

Перевірте, чи функції виконують те саме:

> identical(evans(pts,columbus),rowlings(pts,columbus))
[1] TRUE

І запустіть 500 разів для тестування:

> system.time({for(i in 1:500){evans(pts,columbus)}})
   user  system elapsed 
  7.661   0.600   8.474 
> system.time({for(i in 1:500){rowlings(pts,columbus)}})
   user  system elapsed 
  6.528   0.284   6.933 
> system.time({for(i in 1:500){rowlings2(pts,columbus)}})
   user  system elapsed 
  5.952   0.600   7.222 
> system.time({for(i in 1:500){obrien(pts,columbus)}})
  user  system elapsed 
  4.752   0.004   4.781 

Згідно з моєю інтуїцією, це не велика накладні витрати, насправді це може бути менше накладних витрат, ніж перетворення всіх індексів рядків в символ і назад, або запуску na.omit, щоб отримати пропущені значення. Що до речі призводить до іншого режиму відмови evansфункції ...

Якщо рядок кадру даних полігонів є всім NA(що цілком справедливо), то накладка з SpatialPolygonsDataFrameдля точок у цьому полігоні створить вихідний кадр даних з усіма NAs, який evans()потім випаде:

> columbus@data[1,]=rep(NA,20)
> columbus@data[5,]=rep(NA,20)
> columbus@data[17,]=rep(NA,20)
> columbus@data[15,]=rep(NA,20)
> set.seed(123)
> pts=data.frame(x=runif(100,5,12),y=runif(100,10,15),z=sample(letters,100,TRUE))
> coordinates(pts)=~x+y
> identical(evans(pts,columbus),rowlings(pts,columbus))
[1] FALSE
> dim(evans(pts,columbus))
[1] 27  1
> dim(rowlings(pts,columbus))
[1] 28  1
> 

АЛЕ gIntersectsшвидше, навіть не маючи підмітати матрицю, щоб перевірити перехрестя в R, а не в коді C. Я підозрюю його prepared geometryвміння GEOS, створюючи просторові індекси - так, зprepared=FALSE це займає трохи довше, приблизно 5,5 секунди.

Я здивований, що немає функції ні прямо повертати індекси, ні точки. Коли я писав splancs20 років тому, точкові функції багатокутника мали і обидві ...


Чудово, це також працює для декількох полігонів (я додав приклад, з яким можна грати у відповідь Джошуа).
Роман Луштрик

При великих наборах полігонів даних примус до об'єкта SpatialPolygons є великим накладним та не потрібним. Застосування "понад" до SpatialPolygonsDataFrame повертає індекс рядка, який може бути використаний для підмноження точок. Дивіться мій приклад нижче.
Джефрі Еванс

Багато накладних витрат? По суті, це просто взяття слота @polygons з об’єкта SpatialPolygonsDataFrame. Ви навіть можете «підробити» це, призначивши клас SpatialPolygonsDataFrame на «SpatialPolygons» (хоча це хакі і не рекомендується). Що-небудь, що буде використовувати геометрію, все одно доведеться отримати цей проріз на певному етапі, тому відносно кажучи, його взагалі немає. Це все-таки незначно в будь-якому застосуванні в реальному світі, де ви збираєтеся робити випробування точкових багатокутників.
Спайсмен

Існує більше, ніж міркувань щодо швидкості в обліку накладних витрат. Для створення нового об'єкта в просторі імен R ви використовуєте необхідну оперативну пам’ять. Якщо це не є проблемою в невеликих наборах даних, це вплине на продуктивність з великими даними. R виявляє лінійну продуктивність відмирання. Оскільки дані отримують більшу продуктивність, це спричиняє діг. Якщо вам не потрібно створювати додатковий об’єкт, чому б вам це зробити?
Джефрі Еванс

1
Ми цього не знали, поки я не перевірив це лише зараз.
Спайдермен

13

sp надає більш коротку форму для вибору функцій, заснованих на просторовому перехресті, за прикладом OP:

pts[ply,]

як на:

points(pts[ply,], col = 'red')

За лаштунками це коротко

pts[!is.na(over(pts, geometry(ply))),]

Варто зазначити, що існує geometryметод, який скасовує атрибути: overзмінює поведінку, якщо її другий аргумент має атрибути чи ні (це була плутанина ОП). Це працює у всіх просторових класах * sp, хоча деякі overметоди вимагають rgeos, див. Цю віньєтку для детальної інформації, наприклад, у випадку декількох збігів для перекриття полігонів.


Добре знати! Мені не було відомо про метод геометрії.
Джеффрі Еванс

2
Ласкаво просимо на наш сайт, Edzer - приємно бачити вас тут!
whuber

1
Дякую Біллю - на stat.ethz.ch/pipermail/r-sig-geo стає все тихіше , а може, ми повинні розробити програмне забезпечення, яке спричинятиме більше проблем! ;-)
Едзер Пебесма

6

Ви були на правильному шляху із закінченням. Імена рядків повернутого об'єкта відповідають індексу рядків точок. Ви можете реалізувати свій точний підхід за допомогою лише кількох рядків коду додавання.

library(sp)
set.seed(357)

pts <- data.frame(x=rnorm(100), y=rnorm(100), var1=runif(100), 
                  var2=sample(letters, 100, replace=TRUE))
  coordinates(pts) <- ~ x + y

ply <- matrix(c(-1,-1, 1,-1, 1,1, -1,1, -1,-1), ncol=2, byrow=TRUE)
  ply <- SpatialPolygons(list(Polygons(list(Polygon(ply)), ID=1)))
    ply <- SpatialPolygonsDataFrame(Sr=ply, data=data.frame(polyvar=357))

# Subset points intersecting polygon
prid <- over(pts,ply)
  ptid <- na.omit(prid) 
    pt.poly <- pts[as.numeric(as.character(row.names(ptid))),]  

plot(pts)
  axis(1); axis(2)
    plot(ply, add=TRUE, border="red")
      plot(pt.poly,pch=19,add=TRUE) 

Неправильно - назви рядків, що повертаються, відповідають індексу рядків in_this_case - загалом назви рядків здаються назвою рядків точок - які можуть бути навіть не числовими. Ви можете змінити своє рішення, щоб виконати відповідність символів, що може зробити його трохи більш надійним.
Спайсмен

@Sapcedman, не будь таким догматичним. Рішення невірно. Якщо ви хочете підмножити точки до набору багатокутників або призначити значення багатокутників точкам, функція перевиконання працює без примусу. Існує кілька разів, щоб виконати кросворд, як тільки ви отримаєте результат над об'єктом. Ви рішення примусового до об’єкту SpatialPolygon створює значні необхідні накладні витрати, оскільки цю операцію можна виконати безпосередньо на об’єкті SpatialPolygonDataFrame. До речі, перш ніж редагувати публікацію, переконайтеся, що маєте рацію. Термін "бібліотека та пакет" вживаються взаємозамінно в Р.
Джефрі Еванс

Я додав декілька орієнтирів до своєї посади та помітив ще одну проблему з вашою функцією. Також "Пакети - це колекції функцій R, даних та скомпільований код у чітко визначеному форматі. Каталог, де зберігаються пакети, називається бібліотекою"
Spacedman

Хоча ви технічно правильні щодо "пакет" проти "бібліотека", ви сперечаєтесь з семантикою. Щойно у мене був запит редактора «Екологічного моделювання», щоб ми змінили використання «пакету» (що насправді є моїм уподобанням) на «бібліотеку». Моя думка, що вони стають взаємозамінними термінами і питанням уподобань.
Джефрі Еванс

1
"технічно правильно", як одного разу зауважив доктор Шелдон Купер, "є найкращим видом правильного". Цей редактор технічно помиляється, що є найгіршим видом неправильного.
Спайдермен

4

Це те, що ти шукаєш?

Одна примітка, під час редагування: завершувальний виклик apply()потрібен, щоб зробити цю роботу довільноюSpatialPolygons об'єктами, можливо, що містять більше однієї функції багатокутника. Завдяки @Spacedman за те, що він запропонував мені продемонструвати, як застосувати це до більш загального випадку.

library(rgeos)
pp <- pts[apply(gIntersects(pts, ply, byid=TRUE), 2, any),]


## Confirm that it works
pp[1:5,]
#              coordinates       var1 var2
# 2 (-0.583205, -0.877737) 0.04001092    v
# 3   (0.394747, 0.702048) 0.58108350    v
# 5    (0.7668, -0.946504) 0.85682609    q
# 6    (0.31746, 0.641628) 0.13683264    y
# 9   (-0.469015, 0.44135) 0.13968804    m

plot(pts)
plot(ply, border="red", add=TRUE)
plot(pp, col="red", add=TRUE)

Жахливо виходить з ладу, якщо plyмає більше однієї функції, оскільки gIntersectsповертає матрицю з одним рядком для кожної функції. Ви, ймовірно, можете змітати рядки для значення ІСТИНА.
Спайсмен

@Spacedman - Бінго. Треба робити apply(gIntersects(pts, ply, byid=TRUE), 2, any). Насправді я піду вперед і переключу відповідь на це, оскільки він охоплює і випадок одного багатокутника.
Josh O'Brien

Ах, any. Це може бути незначно швидше, ніж версія, яку я щойно оцінив.
Спайдермен

@Spacedman - З моїх швидких тестів, це виглядає , як obrienі rowlings2запустити шиї і декольте, з , obrien можливо , 2% швидше.
Josh O'Brien

@ JoshO'Brien, як можна використовувати цю відповідь на багатьох полігонах? Тобто ppмає бути знак, IDякий вказує, в якому полігоні розташовані точки.
code123

4

Ось можливий підхід за допомогою rgeosпакету. В основному, він використовує gIntersectionфункцію, яка дозволяє перетинати два spоб'єкти. Витягуючи ідентифікатори тих точок, які лежать в межах багатокутника, ви зможете згодом підмножити свої оригінали SpatialPointsDataFrame, зберігаючи всі відповідні дані. Код майже роз'яснюється, але якщо є якісь питання, будь ласка, не соромтеся запитати!

# Required package
library(rgeos)

# Intersect polygons and points, keeping point IDs
pts.intersect <- gIntersection(ply, pts, byid = TRUE)

# Extract point IDs from intersected data
pts.intersect.strsplit <- strsplit(dimnames(pts.intersect@coords)[[1]], " ")
pts.intersect.id <- as.numeric(sapply(pts.intersect.strsplit, "[[", 2))

# Subset original SpatialPointsDataFrame by extracted point IDs
pts.extract <- pts[pts.intersect.id, ]

head(coordinates(pts.extract))
              x          y
[1,] -0.5832050 -0.8777367
[2,]  0.3947471  0.7020481
[3,]  0.7667997 -0.9465043
[4,]  0.3174604  0.6416281
[5,] -0.4690151  0.4413502
[6,]  0.4765213  0.6068021

head(pts.extract)
         var1 var2
2  0.04001092    v
3  0.58108350    v
5  0.85682609    q
6  0.13683264    y
9  0.13968804    m
10 0.97144627    o

1
Повинен tmpбути pts.intersect? Також розбір подібних повернених дименів залежить від незадокументованої поведінки.
Спайдермен

@Spacedman, який ви маєте рацію tmp, забув видалити його, закінчуючи код. Крім того, ви маєте рацію щодо розбору dimnames. Це було своєрідним швидким рішенням, щоб надати запитувачу швидку відповідь, і напевно є кращі (і більш універсальні) підходи, наприклад ваш :-)
fdetsch

1

Існує надзвичайно просте рішення за допомогою spatialEcoбібліотеки.

library(spatialEco)

# intersect points in polygon
  pts <- point.in.poly(pts, ply)

# check plot
  plot(ply)
  plot(a, add=T)

# convert to data frame, keeping your data
  pts<- as.data.frame(pts)

Перевірте результат:

pts

>             x          y       var1 var2 polyvar
> 2  -0.5832050 -0.8777367 0.04001092    v     357
> 3   0.3947471  0.7020481 0.58108350    v     357
> 5   0.7667997 -0.9465043 0.85682609    q     357
> 6   0.3174604  0.6416281 0.13683264    y     357
> 9  -0.4690151  0.4413502 0.13968804    m     357
> 10  0.4765213  0.6068021 0.97144627    o     357
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.