Title | Neighbors on a rectangular grid | |

Author | Nicholas J. Cox, Durham University, UK |

Say you have spatial data for points or areas on a rectangular grid,
meaning a grid with a rectangular mesh, and not necessarily one whose
overall shape is rectangular. Some minor trickery with
**by:** allows you to
identify the neighbors of each point and thus be able to carry out some
simple spatial processing. Even if you are not especially interested in
spatial data, the answer provides good examples of the use of **by:**, so
read on.

Let us imagine a set-up like this:

x coordinates increasing to right * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * y * * * * * * * * * * * * * * * * * * * coordinates * * * * * * * * * * * * * * * * * * * increasing * * * * * * * * * * * * * * * * * * * * to * * * * * * * * * * * * * * * * * * * * * top * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

We are going to assume each row is indexed by the same integer *y*
coordinate and each column is indexed by the same integer *x*
coordinate. As the diagram should suggest, we are not assuming that the
overall shape of the grid is rectangular or even some regular geometric
shape. For concreteness, we are assuming a Cartesian convention about
directions in which coordinates increase. However, a matrix convention in
which row numbers increase downwards is easily accommodated by minor changes
to what follows.

In addition, we are going to skate over the difference between gridded point data (e.g., altitude or rainfall is measured at a notional point) and gridded area data (e.g., number of people is recorded in a rectangle). Although important otherwise, it is secondary to the question of identifying neighbors for which the principles are identical. The terminology of points will include areas.

Using a geographic convention, let us imagine the grid is oriented so that north is at the top, and we can identify neighbors of each point to the north, northeast, east, southeast, south, southwest, west and northwest.

Suppose we type

. by y (x), sort:

After such a **sort**,
points would be ordered first by *y* coordinate and then by *x*
coordinate. Thus for any observation, the previous observation in Stata's
memory is that to the immediate west, and the observation following is that
to the immediate east. Given some variable **z** with information on
what is happening at each **x** and **y** (altitude, number of people
in grid rectangle, whatever),

. by y (x), sort: generate z_w = z[_n-1] . by y: generate z_e = z[_n+1]

However, what we just said is not quite true: it is incorrect for the
beginnings and ends of rows. For example, the observation before that for
the beginning of a row is for the previous row and is not its neighbor to
the west. Nevertheless, this code needs no extra tricks to cope with edge
problems. Because the first point in any row has no neighbor to the west,
**z_w** for that point will be filled in with missing. This is
ensured because everything is done under the aegis of **by:**.
Processing takes place within the groups of observations defined by
**by:**, and there can be no spurious carry-overs between rows.

Reversing the roles of the coordinates, we can get north and south neighbors:

. by x (y), sort: generate z_s = z[_n-1] . by x: generate z_n = z[_n+1]

The other four neighbors require one extra small trick. The NW and SE
neighbors of any point lie on the diagonal through that point for which **x
+ y** is constant, and the NE and SW neighbors of any point lie on the
other diagonal, for which **x − y** is constant. Given variables
constructed for these diagonal sums, the rest is just a variation on the
prevailing theme:

. generate long xpy = x + y . generate long xmy = x - y . by xpy (x), sort: generate z_nw = z[_n-1] . by xpy: generate z_se = z[_n+1] . by xmy (x), sort: generate z_sw = z[_n-1] . by xmy (x): generate z_ne = z[_n+1]

That is it, really. We were a little paranoid in insisting on a **long**
variable type, just in case the sums (in particular) were very large. Quite
a lot of elementary spatial analysis is now in reach just by using the
constructed variables (and without any looping over observations or special
programming). For example, you can get an average of NEWS neighbors and one
of NW, NE, SW, SE neighbors by

. egen z_news = rowmean(z_n z_e z_w z_s) . egen z_nwneswse = rowmean(z_nw z_ne z_sw z_se)

The **egen** function
**rowmean()** and its siblings are especially recommended here for doing
what you would usually regard as the right thing whenever one or more
variables are missing.