Усадка выпуклой оболочки

#r #computational-geometry #convex-hull

#r #вычислительная геометрия #выпуклая оболочка

Вопрос:

У меня есть куча точек в 2D-пространстве, и я вычислил для них выпуклую оболочку. Теперь я хотел бы «подтянуть» оболочку, чтобы она больше не обязательно охватывала все точки. В типичной аналогии с гвоздями, вставленными в доску с резиновой лентой, я хотел бы добиться того, чтобы иметь возможность настраивать эластичность резиновой ленты и позволять гвоздям сгибаться при давлении выше некоторого предела. Это просто аналогия, здесь нет реальной физики. Это было бы отчасти связано с уменьшением площади корпуса, если бы данная точка была удалена, но не совсем, потому что могут быть две точки, которые очень близки друг к другу. Это не обязательно связано с обнаружением выбросов, потому что вы можете представить себе шаблон, в котором большая часть гвоздей будет гнуться, если они находятся на узкой линии (представьте, например, форму молотка). Все это должно выполняться достаточно быстро для тысяч точек. Любые подсказки, где я должен искать с точки зрения алгоритмов? Реализация в R была бы идеальной, но не нужна.

введите описание изображения здесь

РЕДАКТИРОВАТЬ ПОСЛЕ КОММЕНТАРИЯ: три точки, которые я обозначил, — это те, которые имеют наибольший потенциал для уменьшения площади корпуса, если их исключить. На участке нет другого набора из трех точек, который привел бы к большему уменьшению площади. Наивная реализация того, что я ищу, возможно, заключалась бы в случайной выборке некоторой доли точек, вычислении площади корпуса, итеративном удалении каждой точки на корпусе, пересчете площади, повторении много раз и удалении точек, которые, как правило, приводят к значительному уменьшению площади. Может быть, это может быть реализовано в каком-то случайном варианте леса? Это не совсем правильно, потому что я бы хотел, чтобы точки удалялись одна за другой, чтобы вы получили следующий результат. Если бы вы посмотрели на все точки сразу, возможно, было бы лучше обрезать от краев «головки молотка».

введите описание изображения здесь

Комментарии:

1. Итак, вы хотите удалить некоторые точки, которые в настоящее время находятся на выпуклой оболочке, а затем пересчитать выпуклую оболочку? Как вы решаете, какие из них? В вашем образце изображения у вас есть 9 точек на выпуклой оболочке, и вы, кажется, случайным образом отметили две из них со словами «это должно сгибаться», но неясно, почему были выбраны эти точки. Являются ли они случайными? Ваше описание слишком расплывчато, чтобы учитывать алгоритм в его текущем виде.

2. Да, это то, что я хотел бы сделать. Я внес правку, чтобы, надеюсь, сделать ее более понятной.

3. Я все еще скучаю по тому, что ты хочешь сделать. Вы хотите удалить точки из оболочки или переместить некоторые к центру, чтобы получить меньший многоугольник? Вы пытаетесь минимизировать или максимизировать изменения площади? Наконец, вы задаете вопрос XY?

Ответ №1:

Предположим, у меня есть набор точек, подобных этому:

 set.seed(69)

x <- runif(20)
y <- runif(20)

plot(x, y)
  

введите описание изображения здесь

Тогда легко найти точки подмножества, которые находятся на выпуклой оболочке, выполнив:

 ss <- chull(x, y)
  

Это означает, что мы можем построить выпуклую оболочку, выполнив:

 lines(x[c(ss, ss[1])], y[c(ss, ss[1])], col = "red")
  

введите описание изображения здесь

Теперь мы можем случайным образом удалить одну из точек, расположенных на выпуклой оболочке (т.Е. «Согнуть гвоздь»), выполнив:

 bend <- ss[sample(ss, 1)]

x <- x[-bend]
y <- y[-bend]
  

И затем мы можем повторить процесс нахождения выпуклой оболочки этого нового набора точек:

 ss <- chull(x, y)
lines(x[c(ss, ss[1])], y[c(ss, ss[1])], col = "blue", lty = 2)
  

введите описание изображения здесь

Чтобы получить точку, которая при удалении приведет к наибольшему уменьшению площади, одним из вариантов будет следующая функция:

 library(sp)

shrink <- function(coords)
{
  ss <- chull(coords[, 1], coords[, 2])
  outlier <- ss[which.min(sapply(seq_along(ss), 
                function(i) Polygon(coords[ss[-i], ], hole = FALSE)@area))]
  coords[-outlier, ]
}
  

Итак, вы могли бы сделать что-то вроде:

 coords <- cbind(x, y)

new_coords <- shrink(coords)

new_chull <- new_coords[chull(new_coords[, 1], new_coords[, 2]),]
new_chull <- rbind(new_chull, new_chull[1,])

plot(x, y)

lines(new_chull[,1], new_chull[, 2], col = "red")
  

введите описание изображения здесь

Конечно, вы могли бы сделать это в цикле, чтобы оно new_coords возвращалось shrink несколько раз.

Комментарии:

1. «Давление» будет относиться к длинам отрезков, связанных с каждой точкой. Проблема с вашим подходом заключается в том, что он не приводит к значительному уменьшению площади, если есть несколько точек, которые «должны изгибаться», но которые расположены близко друг к другу (как на рисунке). Теперь, когда я думаю об этом, возможно, было бы неплохо итеративно удалять точки на корпусе с отрезком линии выше некоторой длины. Удалите самый длинный, пересчитайте и повторяйте, пока не останется длинных сегментов. Я думаю, это сработало бы. Спасибо, что заставил меня немного подумать 🙂

2. @Rasmus смотрите мои обновления. shrink Функция должна делать то, что вам нужно.

Ответ №2:

Вычислите надежный центр и дисперсию, используя mcd.cov in MASS и расстояние махаланобиса каждой точки от него (используя mahalanobis in psych). Затем мы показываем график квантилей расстояний Махаланобиса с использованием PlotMD from modi, а также показываем связанные выбросы красным цветом на втором графике. (В modi есть и другие функции, которые также могут представлять интерес.)

 library(MASS)
library(modi)
library(psych)

set.seed(69)   
x <- runif(20)
y <- runif(20)
m <- cbind(x, y)

mcd <- cov.mcd(m)
md <- mahalanobis(m, mcd$center, mcd$cov)
stats <- PlotMD(md, 2, alpha = 0.90)
  

предоставление:

(продолжение после скриншота) скриншот

и мы показываем выпуклую оболочку с помощью линий, а выбросы выделены красным:

 plot(m)    
ix <- chull(m)
lines(m[c(ix, ix[1]), ])

wx <- which(md > stats$halpha)
points(m[wx, ], col = "red", pch = 20)
  

скриншот

Ответ №3:

Спасибо вам обоим! Я пробовал различные методы обнаружения выбросов, но это не совсем то, что я искал. Они плохо работали из-за странных форм моих кластеров. Я знаю, что говорил о площади выпуклой оболочки, но я думаю, что фильтрация по длинам сегментов дает лучшие результаты и ближе к тому, что я действительно хотел. Тогда это выглядело бы примерно так:

 shrink <- function(xy, max_length = 30){
      to_keep <- 1:(dim(xy)[1])
      centroid <- c(mean(xy[,1]), mean(xy[,2]))

      while (TRUE){
        ss <- chull(xy[,1], xy[,2])
        ss <- c(ss, ss[1])
        lengths <- sapply(1:(length(ss)-1), function(i) sum((xy[ss[i 1],] - xy[ss[i],])^2))

        # This gets the point with the longest convex hull segment. chull returns points
        # in clockwise order, so the point to remove is either this one or the one
        # after it. Remove the one furthest from the centroid.
        max_point <- which.max(lengths)
        if (lengths[max_point] < max_length) return(to_keep)

        if (sum((xy[ss[max_point],] - centroid)^2) > sum((xy[ss[max_point   1],] - centroid)^2)){
          xy <- xy[-ss[max_point],]
          to_keep <- to_keep[-ss[max_point]]
        }else{
          xy <- xy[-ss[max_point   1],]
          to_keep <- to_keep[-ss[max_point   1]]
        }
      }
    }
  

Это не оптимально, потому что оно учитывает расстояние до центра тяжести, чего я бы хотел избежать, и есть max_length параметр, который должен вычисляться из данных, а не быть жестко запрограммированным.

Нет фильтра: введите описание изображения здесь

Это выглядит так, потому что здесь 500 000 ячеек, и многие из них оказываются «неправильными» при проецировании с ~ 20 000 измерений на 2.

Фильтр: введите описание изображения здесь

Обратите внимание, что он отфильтровывает точки на концах некоторых кластеров. Это не оптимально, но нормально. Перекрытие между некоторыми кластерами является истинным и должно быть.