3

I'm analyzing the growth pattern of certain particles, and want to compare the point pattern with that of a perfectly hexagonal lattice with the same intensity (same number of points per unit of area). I've written a function that does this, but it has an inherent error and I'm not sure from where it originates. Essentially, after the function has run its course, it produces a perfectly hexagonal point pattern that does NOT have the correct number of particles - it is usually off by about 1-4%. If you copy and paste the following code into R, you'll see this - for this particular example, the error is 11.25% - the original point pattern has 71 particles and the perfectly hexagonal point pattern that was generated has 80 particles. This seems very odd, since the code, as you will see, is designed to place the particles a specific distance from one another, thereby creating a window of the same size as the original with the same number of particles.

The following is the code for the function I wrote to generate the hexagonal lattice.

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. 

Diagram of a unit cell of the hexagonal lattice

#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.

First set of points

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. 

Second set of points

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)))

Both sets combined

Now, I'm relatively new to R, so it may very well be a syntax error somewhere in the code that has caused this error. Or, it may be that I have some fault in my train of thought with this process. However, I don't think that is likely, since my results have been quite close to what I've been trying for (only 1-4% error most of the time is fairly good).

In summary, what I would like help with is how to take a point pattern and create another point pattern of the same window size with the same number of particles, but a perfectly hexagonal point pattern.

Please don't hesitate to ask me to elucidate anything if you feel that something is unclear.

Thanks!

markalex
  • 8,623
  • 2
  • 7
  • 32
MikeZ
  • 345
  • 1
  • 4
  • 15
  • 1
    +1 for your nicely constructed and illustrated question. It might be worth your while to take a look at `hgridcent()` in the **hexbin** package, if only to compare the code that you and its author use to construct hexagonal grids. – Josh O'Brien Jul 31 '12 at 23:31
  • I haven't explored that toolbox. I'll definitely take a look at it, thank you. – MikeZ Aug 01 '12 at 14:30

2 Answers2

4

Forgive me if I am wrong, but I believe what you are trying to do is impossible (in the general case), given the constraints that you have shown in the example. Put simply, can you think of a way to draw 71 points in a hexagon pattern on a page with the same height and width as your window? I don't think such a pattern exists.

To explain further, consider the last line in your code:

hexplot <- ppp(xcoords, ycoords, window=owin(c(0,MaxXValue),c(0,MaxYValue)))

Now, since your window in the same size as the original data, to get the same intensity, you would need exactly the same number of points (71). In your hexagon arrangement of points, you have x rows, each containing y points. But there are no integers x and y that multiply to 71.

That being said, if you "stretched" the window width a little, then half of your rows would contain one more point. That is a slightly looser constraint, but there is no guarantee that in the general case there would be a solution.

So, to get the exact same point intensity, you would need to be able to change the relative window size. You would need to stretch it out to add some white space and get a lower point intensity. That still might not work in the general case... but it might, I haven't worked it out. It might be easiest to start with just a plain grid, and then expand your code to hexagons.


Looking at your code, I noticed that you are using while loops when you could have been using the seq function. For example, if you wanted to generate all the x points from 0 to MaxXValue increasing by sqrt(3)*delta, simply do:

x<-seq(0,MaxXValue,by=delta)

instead of that big while. There might be some errors here, but I think you could reduce your whole code to:

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)))

Finally, I noticed in your comments that you mention that the area of a hexagon is ( sqrt(3) / 2 ) * delta^2, but isn't it (3*sqrt(3)/2) * delta^2? `


I was interested in Josh O'Brien's comment, and decided to implement a rotation function to get the exact number of points desired. Here is the code:

# 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)

Rotated Hex Plot

nograpes
  • 18,623
  • 1
  • 44
  • 67
  • 2
    +1 for your main point. Seems to me that the only general way to fit `n` points in to an `x` by `y` area would be to rotate the grid slightly so that whole lines of points don't enter the viewing window at the same time. – Josh O'Brien Jul 31 '12 at 23:24
  • I had never even considered rotating the grid! I wonder if there is a proof out there showing that it would work in the general case. – nograpes Aug 01 '12 at 01:57
  • Glad you liked that. Not sure about a formal proof, but it seems like even with a horizontally oriented grid, you could always get the number of points to within one line of points' worth of the target. Assuming that the bottom line is aligned with the bottom of the window, you can then at least: (1) shift the grid infinitesimally up; (2) rotate it ever so slightly; then (3) gradually shift the grid downward, dropping out one point at a time until the desired number remained. There are additional "edge cases", but that'd be one strategy for a proof. – Josh O'Brien Aug 01 '12 at 04:28
  • 1
    That is an absolutely beautiful solution, nograpes! Your initial explanation as to why my previous method was most likely impossible was very enlightening - it was a hunch I had had for a long time, but your interpretation was much more coherent than mine. The rotation function is also *very* clever :) (Thanks to Josh O'Brien for the notion!). Thank you so much for your time and effort. Additionally, I've added an explanation in my comments in reference to the equation I used for the area of a hexagon. – MikeZ Aug 01 '12 at 14:23
0

Just for completeness I will add that spatstat now has a function hexgrid to generate hexagonal grid, and there is rotate.ppp which could be applied afterwards to rotate the pattern.

Ege Rubak
  • 4,347
  • 1
  • 10
  • 18