Як я можу генерувати неправильну сітку, що містить мінімум n балів?


20

З огляду на велику (~ 1 мільйон) вибірку нерівномірно розподілених точок - чи можна сформувати неправильну сітку (за розміром, але може також мати неправильну форму, якщо це можливо?), Яка буде містити вказану мінімальну кількість n балів?

Для мене менш важливо, якщо генеровані "комірки" такої сітки містять рівно n кількість точок або принаймні n балів.

Мені відомі такі рішення, як genvecgrid в ArcGIS або Create Grid Layer в QGIS / mmgis, проте всі вони створюватимуть регулярні сітки, що призведе до виведення порожніх комірок (менша проблема - я можу їх просто відкинути) або комірок з кількістю очок менше n (більша проблема, оскільки мені потрібно рішення для агрегації цих комірок, ймовірно, використовуючи деякі інструменти звідси ?).

Я гуляв безрезультатно і відкритий як до комерційних (ArcGIS & розширень), так і до безкоштовних (Python, PostGIS, R) рішень.


1
Наскільки "регулярною" повинна бути сітка? Цікаво, чи можна зробити ієрархічну кластеризацію, а потім просто вирізати дендрограму, щоб задовольнити ваші потреби (хоча це, ймовірно, розтягує те, що було б визначено як звичайна просторова конфігурація). Документація CrimeStat містить кілька хороших прикладів цього типу кластеризації .
Енді Ш

5
Не могли б ви пояснити, що саме ви маєте на увазі під "неправильною сіткою"? Це звучить як оксиморон :-). Більше того, яка була б мета цієї вправи? Зауважте, що додаткові критерії чи обмеження, ймовірно, потрібні: адже, якщо ви намалювали квадрат навколо всіх 1 мільйона точок, він може вважатися частиною сітки, і він міститиме більше n з них. Ви, мабуть, не потурбувалися б про це банальне рішення: але чому б ні, саме?
whuber

@AndyW Дякую Гарна ідея і варто вивчити. Буде мати вигляд. Розмір і форма 'сітки' є для мене другорядним значенням - пріоритетним (зважаючи на конфіденційність даних) є "приховування" n функцій за одним
radek

@whuber Дякую також. Я погоджуюсь - але не був впевнений, як ще я можу назвати таке розбиття. Як було сказано вище - моя основна мотивація - конфіденційність даних. Маючи п'ять точкових місць (яких я не можу показати на кінцевій карті), я хотів би представити їх за площею, що їх охоплює; і отримати середню / медіану / тощо. значення для цього. Я погоджуюсь, що можна було б намалювати один прямокутник або опуклий корпус, що представляє їх усіх - це був би найкращий захист конфіденційності даних? ;] Однак - корисніше було б зобразити це формами, що обмежують, скажімо, 10 ознак. Потім - я все ще можу зберегти просторовий малюнок.
radek

1
ІМО з огляду на ваш опис, я би використовував певний тип інтерполяції та відображав растрову карту (можливо, адаптивна пропускна здатність розміру вашого мінімального N буде достатньою для згладжування даних). Що стосується CrimeStat, найбільші файли, які я використав, складали приблизно 100 000 випадків (і кластеризація, безумовно, забирає час). Цілком ймовірно, що ви можете зробити деяке попереднє узагальнення своїх даних, щоб представити їх як менші випадки і все-таки отримати бажані результати для всього, що вам потрібно. Це дійсно проста програма, я б запропонував лише взяти кілька хвилин, щоб спробувати її і переконатися в цьому.
Енді Ш

Відповіді:


26

Я бачу MerseyViking рекомендував квадрадерево . Я збирався запропонувати те саме і щоб пояснити це, ось код і приклад. Код записаний, Rале його слід легко перенести, скажімо, на Python.

Ідея надзвичайно проста: розділіть точки приблизно навпіл у напрямку x, потім рекурсивно розділіть дві половинки вздовж напрямку y, чергуючи напрямки на кожному рівні, поки не буде потрібне більше розщеплення.

Оскільки метою є маскування фактичних точкових місць, корисно ввести деяку випадковість у розколи . Один з швидких простих способів зробити це - розділити на кількісний набір невелику випадкову суму від 50%. Таким чином (a) величини розщеплення малоймовірно збігаються з координатами даних, так що точки однозначно потраплять на квадрати, створені розділенням, і (b) координати точок неможливо буде реконструювати точно з квадрату.

Оскільки наміром є підтримка мінімальної кількості kвузлів у кожному аркуші квадрату, ми реалізуємо обмежену форму квадратури. Він підтримуватиме (1) кластеризацію точок у групи, що містять між kта 2 * k-1 елементами, та (2) відображення квадрантів.

Цей Rкод створює дерево вузлів і кінцевих листів, розрізняючи їх за класом. Маркування класу прискорює післяобробку, таку як графічне зображення, показане нижче. Код використовує числові значення для ідентифікаторів. Це працює на дереві до глибини 52 (використовуючи подвійні; якщо використовуються неподписані довгі цілі числа, максимальна глибина - 32). Для глибших дерев (які в будь-якій програмі малоймовірні, оскільки kбуде задіяно принаймні * 2 ^ 52 бали), ідентифікатори повинні бути рядками.

quadtree <- function(xy, k=1) {
  d = dim(xy)[2]
  quad <- function(xy, i, id=1) {
    if (length(xy) < 2*k*d) {
      rv = list(id=id, value=xy)
      class(rv) <- "quadtree.leaf"
    }
    else {
      q0 <- (1 + runif(1,min=-1/2,max=1/2)/dim(xy)[1])/2 # Random quantile near the median
      x0 <- quantile(xy[,i], q0)
      j <- i %% d + 1 # (Works for octrees, too...)
      rv <- list(index=i, threshold=x0, 
                 lower=quad(xy[xy[,i] <= x0, ], j, id*2), 
                 upper=quad(xy[xy[,i] > x0, ], j, id*2+1))
      class(rv) <- "quadtree"
    }
    return(rv)
  }
  quad(xy, 1)
}

Зауважимо, що рекурсивна конструкція цього алгоритму (і, отже, більшості алгоритмів після обробки) означає, що потреба в часі дорівнює O (m), а використання оперативної пам'яті - O (n), де mчисло клітинок і n- кількість балів. mпропорційно nділиться на мінімальний бал на комірку,k. Це корисно для оцінки часу обчислень. Наприклад, якщо потрібно 13 секунд, щоб розділити n = 10 ^ 6 балів у клітинках 50-99 балів (k = 50), m = 10 ^ 6/50 = 20000. Якщо ви хочете замість цього розділити до 5-9 балів на клітинку (k = 5), m в 10 разів більший, тому час розширюється приблизно до 130 секунд. (Оскільки процес поділу набору координат навколо їхніх середніх груп відбувається швидше, коли осередки зменшуються, фактичний термін становив лише 90 секунд.) Щоб пройти весь шлях до k = 1 бал на клітинку, це займе приблизно шість разів довше все-таки, або дев'ять хвилин, і ми можемо очікувати, що код насправді буде трохи швидшим за це.

Перш ніж піти далі, давайте згенеруємо кілька цікавих нерегулярно розміщених даних та створимо їх обмежене квадрати (минуло 0,29 секунди):

Квадрат

Ось код для створення цих сюжетів. Він використовує Rполіморфізм: points.quadtreeвін буде викликатися, коли pointsфункція застосовується, наприклад, до quadtreeоб'єкта. Сила цього проявляється в надзвичайній простоті функції забарвлення точок відповідно до їх ідентифікатора кластера:

points.quadtree <- function(q, ...) {
  points(q$lower, ...); points(q$upper, ...)
}
points.quadtree.leaf <- function(q, ...) {
  points(q$value, col=hsv(q$id), ...)
}

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

lines.quadtree <- function(q, xylim, ...) {
  i <- q$index
  j <- 3 - q$index
  clip <- function(xylim.clip, i, upper) {
    if (upper) xylim.clip[1, i] <- max(q$threshold, xylim.clip[1,i]) else 
      xylim.clip[2,i] <- min(q$threshold, xylim.clip[2,i])
    xylim.clip
  } 
  if(q$threshold > xylim[1,i]) lines(q$lower, clip(xylim, i, FALSE), ...)
  if(q$threshold < xylim[2,i]) lines(q$upper, clip(xylim, i, TRUE), ...)
  xlim <- xylim[, j]
  xy <- cbind(c(q$threshold, q$threshold), xlim)
  lines(xy[, order(i:j)],  ...)
}
lines.quadtree.leaf <- function(q, xylim, ...) {} # Nothing to do at leaves!

Як інший приклад, я створив 1 000 000 балів і розподілив їх на групи по 5-9 у кожній. Хронометраж склав 91,7 секунди.

n <- 25000       # Points per cluster
n.centers <- 40  # Number of cluster centers
sd <- 1/2        # Standard deviation of each cluster
set.seed(17)
centers <- matrix(runif(n.centers*2, min=c(-90, 30), max=c(-75, 40)), ncol=2, byrow=TRUE)
xy <- matrix(apply(centers, 1, function(x) rnorm(n*2, mean=x, sd=sd)), ncol=2, byrow=TRUE)
k <- 5
system.time(qt <- quadtree(xy, k))
#
# Set up to map the full extent of the quadtree.
#
xylim <- cbind(x=c(min(xy[,1]), max(xy[,1])), y=c(min(xy[,2]), max(xy[,2])))
plot(xylim, type="n", xlab="x", ylab="y", main="Quadtree")
#
# This is all the code needed for the plot!
#
lines(qt, xylim, col="Gray")
points(qt, pch=".")

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


Як приклад того, як взаємодіяти з ГІС , давайте випишемо всі квадратичні комірки у вигляді файлу форми багатокутника за допомогою shapefilesбібліотеки. Код імітує відсічні процедури lines.quadtree, але цього разу він повинен генерувати векторні описи комірок. Вони виводяться як кадри даних для використання з shapefilesбібліотекою.

cell <- function(q, xylim, ...) {
  if (class(q)=="quadtree") f <- cell.quadtree else f <- cell.quadtree.leaf
  f(q, xylim, ...)
}
cell.quadtree <- function(q, xylim, ...) {
  i <- q$index
  j <- 3 - q$index
  clip <- function(xylim.clip, i, upper) {
    if (upper) xylim.clip[1, i] <- max(q$threshold, xylim.clip[1,i]) else 
      xylim.clip[2,i] <- min(q$threshold, xylim.clip[2,i])
    xylim.clip
  } 
  d <- data.frame(id=NULL, x=NULL, y=NULL)
  if(q$threshold > xylim[1,i]) d <- cell(q$lower, clip(xylim, i, FALSE), ...)
  if(q$threshold < xylim[2,i]) d <- rbind(d, cell(q$upper, clip(xylim, i, TRUE), ...))
  d
}
cell.quadtree.leaf <- function(q, xylim) {
  data.frame(id = q$id, 
             x = c(xylim[1,1], xylim[2,1], xylim[2,1], xylim[1,1], xylim[1,1]),
             y = c(xylim[1,2], xylim[1,2], xylim[2,2], xylim[2,2], xylim[1,2]))
}

Самі точки можна читати безпосередньо, використовуючи read.shpабо імпортуючи файл даних координат (x, y).

Приклад використання:

qt <- quadtree(xy, k)
xylim <- cbind(x=c(min(xy[,1]), max(xy[,1])), y=c(min(xy[,2]), max(xy[,2])))
polys <- cell(qt, xylim)
polys.attr <- data.frame(id=unique(polys$id))
library(shapefiles)
polys.shapefile <- convert.to.shapefile(polys, polys.attr, "id", 5)
write.shapefile(polys.shapefile, "f:/temp/quadtree", arcgis=TRUE)

(Використовуйте тут будь-яку бажану міру, щоб xylimперейти до субрегіону або розширити відображення у більшому регіоні; цей код за замовчуванням розмір точок.)

Одного лише цього достатньо: просторове приєднання цих багатокутників до початкових точок визначить кластери. Після ідентифікації операції з «підбиття підсумків» бази даних будуть генерувати підсумкову статистику точок всередині кожної комірки.


Оце Так! Фантастичний. Зробимо зйомку з моїми даними ще раз в офісі =)
radek

4
Топ відповідь @whuber! +1
MerseyViking

1
(1) Ви можете читати форму файлів безпосередньо ( зокрема, з shapefilesпакетом) або ж експортувати координати (x, y) у текст ASCII та читати їх read.table. (2) Я рекомендую виписати qtу двох формах: по-перше, як файл форми точки, для xyякого idполя включаються як ідентифікатори кластерів; по-друге, де накреслені відрізки ліній lines.quadtreeвиписуються у формі полілінійного формату (або де аналогічна обробка записує клітини у вигляді формату багатокутника). Це так само просто, як і змінювати lines.quadtree.leafвихід, xylimяк прямокутник. (Див. Правки.)
whuber

1
@whubber Дякую за оновлення. Все працювало гладко. Цілком заслужений +50, хоча зараз я думаю, що він заслуговує +500!
radek

1
Я підозрюю, що обчислені ідентифікатори чомусь не були унікальними. Внесіть ці зміни у визначення quad: (1) ініціалізації id=1; (2) зміна , id/2щоб id*2в lower=лінії; (3) зробити аналогічне зміна , щоб id*2+1в upper=лінії. (Я відредагую свою відповідь, щоб це відобразити.) Це також має враховувати розрахунок площі: залежно від вашого ГІС, всі області будуть позитивними або всі будуть негативними. Якщо всі вони негативні, перейдіть на списки для xі yв cell.quadtree.leaf.
whuber

6

Подивіться, чи дає цей алгоритм достатню анонімність для вашої вибірки даних:

  1. почніть із звичайної сітки
  2. якщо у багатокутника менше порогового значення, зливаються з сусідніми чергуючими (E, S, W, N), що спірально рухаються за годинниковою стрілкою.
  3. якщо у багатокутника менше порогового значення, перейдіть до 2, інакше перейдіть до наступного багатокутника

Наприклад, якщо мінімальний поріг становить 3:

алгоритм


1
Чорт у деталях: здається, що такий підхід (або майже будь-який алгоритм агломеративної кластеризації) загрожує залишити маленькі "осиротілі" точки, розкидані по всьому місцю, які потім не можуть бути оброблені. Я не кажу, що такий підхід неможливий, але я б підтримував здоровий скептицизм за відсутності фактичного алгоритму та прикладу його застосування до реалістичного набору даних.
whuber

Дійсно, такий підхід може бути проблематичним. Одне застосування цього методу, про який я думав, використовує точки як уявлення про житлові будинки. Я думаю, що цей метод добре працює в більш густонаселених районах. Однак все ще будуть випадки, коли буквально одна-дві будівлі "посеред нізвідки", і це займе багато ітерацій, і це призведе до того, що дійсно великі площі, нарешті, досягнуть мінімального порогу.
radek

5

Подібно до цікавого рішення Паулу, як щодо використання алгоритму підрозділу чотирьох дерев?

Встановіть глибину, на яку хочете би піти квадри. Ви також можете мати мінімальну чи максимальну кількість точок на клітинку, щоб деякі вузли були глибшими / меншими за інші.

Розділіть свій світ, відкинувши порожні вузли. Промийте і повторіть, поки не будуть виконані критерії.


Спасибі. Яке програмне забезпечення ви б рекомендували для цього?
radek

1
В принципі, це гарна ідея. Але як виникають порожні вузли, якщо ви ніколи не дозволяєте менше позитивної мінімальної кількості балів на клітинку? (Існує багато видів чотиривіршів, тому можливість порожніх вузлів вказує на те, що ви маєте на увазі той, який не адаптований до даних, що викликає занепокоєння щодо його корисності для наміченого завдання.)
whuber

1
Я уявляю це так: уявіть, що у вузла є більше максимального порогу точок, але вони кластеризовані у верхньому лівому куті вузла. Вузол буде розділений, але нижній правий дочірній вузол буде порожнім, тому його можна обрізати.
MerseyViking

1
Я бачу, що ти робиш (+1). Трюк полягає в підрозділі в точці, визначеній координатами (наприклад, їх медіаною), тим самим гарантуючи відсутність порожніх комірок. В іншому випадку квадратура визначається насамперед простором, займаним точками, а не самими точками; тоді ваш підхід стає ефективним способом втілення загальної ідеї, запропонованої @Paulo.
качан

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.