使用 Spatstat 生成六边形晶格
Posted
技术标签:
【中文标题】使用 Spatstat 生成六边形晶格【英文标题】:Generating a Hexagonal Lattice Using Spatstat 【发布时间】:2012-07-29 09:11:31 【问题描述】:我正在分析某些粒子的生长模式,并想将点模式与具有相同强度(每单位面积的点数相同)的完美六边形晶格的点模式进行比较。我编写了一个函数来执行此操作,但它有一个固有的错误,我不确定它的来源。本质上,在函数运行后,它会产生一个完美的六边形点图案,它没有正确数量的粒子——它通常偏离大约 1-4%。如果您将以下代码复制并粘贴到 R 中,您会看到这一点 - 对于这个特定示例,错误为 11.25% - 原始点图案有 71 个粒子,而生成的完美六边形点图案有 80 个粒子。这看起来很奇怪,因为正如您将看到的那样,代码旨在将粒子彼此之间放置特定距离,从而创建一个与原始窗口大小相同且粒子数量相同的窗口。
下面是我写的生成六边形格子的函数代码。
library(spatstat)
data(swedishpines)
swedishpines.df <- as.data.frame(swedishpines)
MaxXValue <- max(swedishpines.df[1])
MaxYValue <- max(swedishpines.df[2])
#The above two lines dictate the window size
NumberOfParticles <- nrow(swedishpines.df[1])
#Number of entries = number of particles
#calculate delta
intensity <- NumberOfParticles / (MaxXValue*MaxYValue)
#Intensity ---> particles / unit^2
#Area = ( sqrt(3) / 2 ) * delta^2
#Now - in substituting intensity in for area, it is key to recognize
#that there are 3 particles associated with each hexagonal tile.
#This is because each particle on the border of the tile is really 1/3 of a
#a particle due to it being associated with 3 different hexagonal tiles.
#Since intensity = 3 Particles / Area,
delta <- sqrt(2/(intensity*(sqrt(3))))
#This is derived from the equation for the area of a regular hexagon.
#xcoords and ycoords represent the x and y coordintes of all of the generated points. The 'x' and 'y' are temporary holders for the x and y coordinates of a single horizontal line of points (they are appended to xcoords and ycoords at the end of each while loop).
xcoords <- c()
ycoords <- c()
#The following large while loop calculates the coordinates of the first set of points that are vertically aligned with one another. (alternating horizontal lines of particles) This is shown in the image below.
y_shift <- 0
while (y_shift < MaxYValue)
x <- c(0)
x_shift <- 0 + delta
count <- 0
while (x_shift < MaxXValue)
x <- append(x, x_shift)
x_shift <- x_shift + delta
count <- count + 1
y <- c(y_shift)
for (i in seq(0,(count-1)))
y <- append(y, y_shift)
y_shift <- y_shift + sqrt(3)*delta
xcoords <- append(xcoords,x)
ycoords <- append(ycoords,y)
#The following large while loop calculates the coordinates of the second set of points that are vertically aligned with one another. This is shown in the image below.
y_shift <- 0 + (delta*(sqrt(3)))/2
while (y_shift < MaxYValue)
x <- c(0 + (1/2)*delta)
x_shift <- 0 + (1/2)*delta + delta
count <- 0
while (x_shift < MaxXValue)
x <- append(x, x_shift)
x_shift <- x_shift + delta
count <- count + 1
y <- c(y_shift)
for (i in seq(0,(count-1)))
y <- append(y, y_shift)
y_shift <- y_shift + sqrt(3)*delta
xcoords <- append(xcoords,x)
ycoords <- append(ycoords,y)
hexplot <- ppp(xcoords, ycoords, window=owin(c(0,MaxXValue),c(0,MaxYValue)))
现在,我对 R 比较陌生,因此很可能是代码中某处的语法错误导致了此错误。或者,可能是我在这个过程中的思路有问题。但是,我认为这不太可能,因为我的结果与我一直在尝试的结果非常接近(大多数时候只有 1-4% 的错误是相当好的)。
总之,我想要帮助的是如何获取一个点图案并创建另一个具有相同窗口大小和相同数量粒子但完美六边形点图案的点图案。
如果您觉得有什么不清楚的地方,请不要犹豫,请我解释一下。
谢谢!
【问题讨论】:
+1 为您精心构建和图解的问题。看看 hexbin 包中的hgridcent()
可能是值得的,如果只是为了比较您和它的作者用来构建六边形网格的代码。
我还没有探索过那个工具箱。我一定会看看的,谢谢。
【参考方案1】:
如果我错了,请原谅我,但鉴于您在示例中显示的限制,我相信您尝试做的事情是不可能的(在一般情况下)。简而言之,你能想出一种方法,在与你的窗口高度和宽度相同的页面上以六边形图案绘制 71 个点吗?我认为不存在这样的模式。
为了进一步解释,请考虑代码中的最后一行:
hexplot <- ppp(xcoords, ycoords, window=owin(c(0,MaxXValue),c(0,MaxYValue)))
现在,由于您的窗口与原始数据大小相同,因此要获得相同的强度,您需要完全相同数量的点 (71)。在您的六边形点排列中,您有 x 行,每行包含 y 个点。但是没有整数 x 和 y 可以乘以 71。
话虽如此,如果您稍微“拉伸”窗口宽度,那么一半的行将包含一个点。这是一个稍微宽松的约束,但不能保证在一般情况下会有解决方案。
因此,要获得完全相同的点强度,您需要能够更改相对窗口大小。您需要将其拉伸以添加一些空白并获得较低的点强度。在一般情况下,这可能仍然行不通......但它可能,我还没有解决。最简单的方法可能是从一个简单的网格开始,然后将代码扩展为六边形。
查看您的代码,我注意到您正在使用 while
循环,而您本来可以使用 seq
函数。例如,如果您想生成从 0 到 MaxXValue
增加 sqrt(3)*delta
的所有 x
点,只需执行以下操作:
x<-seq(0,MaxXValue,by=delta)
而不是那么大的while
。这里可能存在一些错误,但我认为您可以将整个代码缩减为:
library(spatstat)
data(swedishpines)
swedishpines.df <- as.data.frame(swedishpines)
MaxXValue <- max(swedishpines.df[1])
MaxYValue <- max(swedishpines.df[2])
NumberOfParticles <- nrow(swedishpines.df)
intensity <- NumberOfParticles / (MaxXValue*MaxYValue)
delta <- sqrt(2/(intensity*(sqrt(3))))
x<-seq(0,MaxXValue,by=delta)
y<-seq(0,MaxYValue,by=sqrt(3)*delta)
first.coords=merge(x,y) # Find every combo of x and y
x=seq(delta/2,MaxXValue,by=delta)
y=delta*sqrt(3)/2 + (delta*sqrt(3)*seq(0,length(x)/2))
second.coords=merge(x,y)
coords=rbind(first.coords,second.coords)
ppp(coords$x, coords$y, window=owin(c(0,MaxXValue),c(0,MaxYValue)))
最后,我注意到在您的 cmets 中,您提到六边形的面积是 ( sqrt(3) / 2 ) * delta^2
,但不是 (3*sqrt(3)/2) * delta^2
吗?
`
我对 Josh O'Brien 的评论很感兴趣,并决定实现一个旋转函数来获得所需的确切点数。代码如下:
# Include all above code
rotate=function(deg)
r=matrix(c(cos(deg),-sin(deg),sin(deg),cos(deg)),byrow=T,nrow=2)
rotated.coords=data.frame(t(r %*% t(as.matrix(coords))))
names(rotated.coords)=c('x','y')
rotated.coords
rotate.optim=function(deg)
rotated.coords=rotate(deg)
abs(NumberOfParticles-length(suppressWarnings(ppp(rotated.coords$x, rotated.coords$y, window=owin(c(0,MaxXValue),c(0,MaxYValue)))$x)))
o=optim(0,rotate.optim,lower=0,upper=2*pi,method='Brent')
rotated.coords=rotate(o$par)
rotated.coords.window=rotated.coords[rotated.coords$x >= 0 & rotated.coords$y >= 0 & rotated.coords$x <= MaxXValue & rotated.coords$y <= MaxYValue,]
final=ppp(rotated.coords.window$x,rotated.coords.window$y,window=owin(c(0,MaxXValue),c(0,MaxYValue)))
plot(final)
【讨论】:
+1 表示您的主要观点。在我看来,通过y
区域将n
指向x
的唯一通用方法是稍微旋转网格,这样整行点就不会同时进入查看窗口。
我从来没有考虑过旋转网格!我想知道是否有证据表明它适用于一般情况。
很高兴你喜欢。不确定是否有正式的证明,但似乎即使使用水平方向的网格,您总是可以将点数保持在目标的一行点值以内。假设底线与窗口的底部对齐,那么您至少可以:(1)将网格无限向上移动; (2) 稍微旋转一下;然后 (3) 逐渐向下移动网格,一次删除一个点,直到保留所需的数量。还有其他“边缘情况”,但这是证明的一种策略。
这是一个绝对漂亮的解决方案,nograpes!你对为什么我以前的方法很可能不可能的最初解释非常有启发性——这是我长期以来的一种预感,但你的解释比我的更连贯。旋转功能也非常聪明:)(感谢 Josh O'Brien 的想法!)。非常感谢您的时间和精力。此外,我在我的 cmets 中添加了一个解释,参考了我用于六边形面积的方程。【参考方案2】:
为了完整起见,我要补充一点,spatstat 现在有一个函数 hexgrid
来生成六边形网格,还有一个 rotate.ppp
可以在之后应用来旋转图案。
【讨论】:
以上是关于使用 Spatstat 生成六边形晶格的主要内容,如果未能解决你的问题,请参考以下文章
R语言:基于Spatstat包的单变量和双变量平面Ripley‘s K函数分析