最小nポイントを含む不規則なグリッドを生成するにはどうすればよいですか?


20

不均一に分布したポイントの大きなサンプル(最大100万個)を指定すると、指定された最小数のnポイントを含む不規則なグリッド(サイズは可能ですが、形状が不規則になる可能性もあります)を生成できますか?

そのようなグリッドのジェネレートされた「セル」に正確にn個のポイントまたは少なくともn個のポイントが含まれている場合、私にとってはそれほど重要ではありません。

ArcGISのgenvecgridやQGIS / mmgisのグリッドレイヤーの作成などのソリューションを知っていますが、それらはすべて通常のグリッドを作成し、空のセル(小さな問題-単純に破棄できます)またはポイントカウントのセルを出力します。n未満(おそらくここからいくつかのツールを使用して、これらのセルを集約するソリューションが必要になるため、より大きな問題ですか?)。

私は何の役にも立ちませんでしたが、商用(ArcGISと拡張機能)または無料(Python、PostGIS、R)の両方のソリューションにオープンです。


1
グリッドはどの程度「通常」である必要がありますか?階層的なクラスタリングを実行し、ニーズに合わせて樹形図を切り取ることができるのではないかと思います(ただし、これは通常の空間構成として定義されるものを拡張する可能性があります)。CrimeStatのドキュメントには、このタイプのクラスタリングの良い例がいくつかあります
アンディW

5
「不規則なグリッド」とはどういう意味ですか?それは矛盾のように聞こえます:-)。さらに、この演習の目的は何ですか?また、追加の基準または制約が必要になる可能性が高いことに注意してください。結局、100万点すべての周りに正方形を描いた場合、それはグリッドの一部と見なされ、n個以上含まれます。ただし、おそらくこの些細な解決策は気にしないでしょう。しかし、なぜそうではないのでしょうか。
whuber

@AndyWありがとう。良いアイデアと探索する価値があります。ご覧になります。「グリッド」のサイズと形状は私にとって二次的に重要です-優先順位(データプライバシーのため)は1つの機能の後ろにn個の機能を「隠す」ことです
radek

@whuberもありがとう。私は同意します-しかし、他にどのようにそのようなパーティションに名前を付けることができるのか分かりませんでした。前述のように、私の主な動機はデータのプライバシーです。5つのポイント位置(最終的なマップに表示することはできません)があるので、それらをカバーするエリアで表現したいと思います。平均値/中央値などを取得します。そのための値。私は、それらすべてを表す1つの長方形または凸包を描くことが可能であることに同意します-それは私が推測する究極のデータプライバシー保護でしょうか?;]ただし、図形の境界で表現する方が便利です。10個の特徴があるとします。その後-私はまだ空間パターンを保持することができます。
-radek

1
IMOはあなたの説明を与えられ、私は何らかのタイプの補間を利用してラスターマップを表示します(おそらく、最小Nのサイズの適応帯域幅でデータを平滑化できます)。CrimeStatに関しては、私が使用した最大のファイルは約100,000件でした(そして、クラスタリングには確かに時間がかかります)。データを事前に一般化して、より少ないケースとして表現し、必要な結果を得ることができる可能性があります。これは本当に簡単なプログラムです。数分かけて試してみて、自分で確認することをお勧めします。
アンディW

回答:


26

私はMerseyVikingが推奨している参照の四分木を。私は同じことを提案しようとしていました、そしてそれを説明するために、ここにコードと例があります。コードは記述されてRいますが、たとえばPythonに簡単に移植できるはずです。

アイデアは非常に単純です。ポイントをx方向に約半分に分割し、さらに分割が必要なくなるまで、各レベルで方向を交互に、y方向に沿って2つの半分を再帰的に分割します。

実際のポイント位置を偽装することを目的としているため、分割にランダム性を導入すると便利です。これを行うための1つの高速で簡単な方法は、50%から離れた小さなランダム量を変位値セットで分割することです。この方法では、(a)分割値がデータ座標と一致する可能性は非常に低いため、ポイントは分割によって作成された象限に一意に分類され、(b)ポイント座標は四分木から正確に再構築することはできません。

k各クアッドツリーリーフ内のノードの最小量を維持することを目的としているため、制限された形式のクアッドツリーを実装します。(1)それぞれk2〜k-1の要素を持つグループへのクラスター化ポイント、および(2)象限のマッピングをサポートします。

このRコードは、ノードとターミナルの葉のツリーを作成し、クラスごとに区別します。クラスのラベル付けは、以下に示すように、プロットなどの後処理を促進します。コードはIDに数値を使用します。これは、ツリー内で最大52の深さまで機能します(倍精度を使用します。符号なし長整数が使用される場合、最大の深さは32です)。より深いツリーの場合(少なくともk* 2 ^ 52ポイントが関係するため、どのアプリケーションでも非常にまれです)、idは文字列でなければなりません。

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)であり、RAM使用量がO(n)でmあることに注意してください。セルおよびnポイントの数です。 セルごとの最小ポイントで割った値にm比例しnます。k。これは、計算時間の見積もりに役立ちます。たとえば、n = 10 ^ 6ポイントを50-99ポイント(k = 50)のセルに分割するのに13秒かかる場合、m = 10 ^ 6/50 =20000。代わりに5-9に分割する場合セルあたりのポイント(k = 5)、mは10倍大きいため、タイミングは約130秒になります。(セルのサイズが小さくなるにつれて、中心の周りの座標セットを分割するプロセスが速くなるため、実際のタイミングはわずか90秒でした。)セルごとにk = 1ポイントに達するには、約6倍時間がかかります。まだ、または9分で、コードは実際にはそれよりも少し速くなると予想できます。

先に進む前に、興味深い不規則な間隔のデータを生成し、制限されたクワッドツリー(経過時間0.29秒)を作成しましょう。

クワッドツリー

これらのプロットを生成するコードは次のとおりです。Rのポリモーフィズムを利用します。たとえばpoints.quadtreepoints関数がquadtreeオブジェクトに適用されるたびに呼び出されます。この機能は、クラスター識別子に応じてポイントに色を付ける機能が非常にシンプルであることから明らかです。

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

グリッド自体のプロットは、4分木分割に使用されるしきい値を繰り返しクリッピングする必要があるため、少し注意が必要ですが、同じ再帰的アプローチはシンプルでエレガントです。必要に応じて、バリアントを使用して象限の多角形表現を作成します。

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=".")

ここに画像の説明を入力してください


GIS対話する方法の例として、shapefilesライブラリを使用して、すべてのquadtreeセルをポリゴンシェープファイルとして書き出しましょう。コードはのクリッピングルーチンをエミュレートしますが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ここで必要な範囲を使用して、サブ領域にウィンドウ表示するか、マッピングをより大きな領域に拡張します。このコードはデフォルトでポイントの範囲になります。)

これだけで十分です。これらのポリゴンを元のポイントに空間的に結合すると、クラスターが識別されます。識別されると、データベースの「要約」操作により、各セル内のポイントの要約統計が生成されます。


うわー!素晴らしい。オフィスに戻ったら、データを
試してみてください

4
トップアンサー@whuber!+1
MerseyViking

1
(1)パッケージを使用して(特に)シェープファイルを直接読み取ることができshapefilesます。または、(x、y)座標をASCIIテキストでエクスポートし、で読み取ることができread.tableます。(2)私は書き込みお勧めqt2つの形態:最初のためのポイントシェープファイルとしてxyここidフィールドはクラスタ識別子として含まれます。2つ目は、プロットさlines.quadtreeれる線分がポリラインシェープファイルとして書き出される場所です(または、類似の処理によりセルがポリゴンシェープファイルとして書き込まれます)。これは、lines.quadtree.leaf出力xylimを長方形として変更するのと同じくらい簡単です。(編集を参照してください。)
whuber

1
@whubber更新してくれてありがとう。すべてがスムーズに機能しました。+50に値するが、今では+500に値すると思う!
ラデック

1
何らかの理由で、計算されたIDは一意ではなかったと思われます。以下の定義でこれらの変更を行いますquad。(1)initialize id=1; (2)変更id/2id*2lower=線と (3)と同様の変更を行うid*2+1upper=ライン。(それを反映するように返信を編集します。)それは面積計算にも注意を払う必要があります:GISに応じて、すべての面積が正または負になります。彼らはすべて否定している場合は、のためにリストを逆にxしてycell.quadtree.leaf
whuber

6

このアルゴリズムがデータサンプルに十分な匿名性を与えるかどうかを確認します。

  1. 通常のグリッドから始めます
  2. ポリゴンのしきい値がしきい値よりも小さい場合、時計回りにらせん状に隣り合う交互の(E、S、W、N)とマージします。
  3. ポリゴンがしきい値よりも小さい場合は2に進み、そうでない場合は次のポリゴンに進みます

たとえば、最小しきい値が3の場合:

アルゴリズム


1
悪魔は詳細にあります。このアプローチ(またはほとんどすべての凝集クラスタリングアルゴリズム)は、場所全体に散らばる小さな「孤立した」ポイントを残し、その後処理できなくなる恐れがあるようです。私はこのアプローチが不可能だと言っているわけではありませんが、実際のアルゴリズムと現実的なポイントデータセットへの適用例がない場合、健全な懐疑論を維持します。
whuber

実際、このアプローチには問題があるかもしれません。私が考えていたこの方法の1つのアプリケーションは、住宅の建物の表現としてポイントを使用します。この方法は、人口密度の高い地域でうまく機能すると思います。ただし、文字通り1つまたは2つの建物が「どこでもない」場所にあり、多くの反復が必要であり、最終的に最小しきい値に達するには非常に大きなエリアが生じる場合があります。
-radek

5

Pauloの興味深い解決策と同様に、四分木細分割アルゴリズムを使用してはどうでしょうか。

クワッドツリーに移動する深さを設定します。一部のノードが他のノードよりも深く/小さくなるように、セルごとに最小または最大のポイント数を設定することもできます。

空のノードを破棄して、ワールドを細分化します。基準が満たされるまですすぎ、繰り返します。


ありがとう。どのソフトウェアをお勧めしますか?
-radek

1
原則として、これは良い考えです。しかし、セルごとに正の最小数未満のポイントを許可しない場合、空のノードはどのように発生しますか?(クアッドツリーには多くの種類があるため、空のノードの可能性は、データに適合していないノードを念頭に置いていることを示しており、目的のタスクに対する有用性に関する懸念が生じます。)
whuber

1
このように想像してみてください。ノードには最大しきい値を超えるポイントがありますが、ノードの左上に向かってクラスター化されています。ノードは細分化されますが、右下の子ノードは空になるため、枝刈りできます。
MerseyViking

1
何をしているのかわかります(+1)。トリックは、座標(中央値など)によって決定されるポイントで細分化することで、空のセルがないことを保証します。それ以外の場合、4分木は、ポイント自体ではなく、ポイントが占めるスペースによって主に決定されます。あなたのアプローチは、@ Pauloによって提案された一般的なアイデアを実行する効果的な方法になります。
whuber

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.