I have a data set of developments of various types in a single US City. My aim is to run a nearest neighbor analysis for each of the various development types to explore how clustered, random or ordered each is across the city. I know most development types to be clustered to an extent, my aim is to quantify differences between the types given that development is allowed for all types roughly equally around the city. There are no zoning issues, etc. precluding certain areas which would be the explanatory factor for clustering.

My intuition is that the area used in the nearest neighbor calculation should be held constant as randomly dispersed developments could/should occur anywhere in the city with equal likelihood for all development types I'm looking at.

However, reading the code of the nearest neighbor processing tool used by QGIS, the area calculation uses the layer extent:

A = layer.extent()
A = float(A.width() * A.height())

After testing the lines of code above on several filtered and unfiltered versions of the layer, the layer extent does change substantially and thus there area does as well. For a highly clustered development type in particular, the area is ~1/3rd of the total city, leading the reported expected distance and standard error to be incorrect or at least inconsistent with my intuition on the problem.

My questions:

1) Is my intuition for keeping a constant extent correct or is this better suited for another point pattern analysis technique?

2) To hold the area constant, is there an available tool or should I look at retrofitting the existing nearest neighbor tool to accept a second input to calculate area from?

EDIT: Solution to Question 2 Posted Below

I've implemented a pyqgis solution that can be run in the QGIS python console in case it's of any interest to someone else. Variables do need to be changed by hand unfortunately.

#Run a nearest neighbor analysis in QGIS on filtered data with a fixed area equal to the unfiltered point layer
#Select the base, unfiltered point layer for the analysis 
base_layer = QgsMapLayerRegistry.instance().mapLayersByName(layer_name)[0]
#set filters
filter1 = ''
filter2 = ''
filter3 = ''
#duplicate base layer for filtering
point_layer = QgsVectorLayer(base_layer.source(), base_layer.name(), base_layer.providerType())
#filter layer
point_layer.setSubsetString(‘“column1”=\‘%s\’ and “column2” like \‘%s\’ and “column3”=\‘%s\‘’ % (filter1, filter2, filter3))
#Testing code: Adds the Filtered point layer to map

#Start NNA Code taken from QGIS NNA processing tool
spatialIndex = QgsSpatialIndex(point_layer.getFeatures())

neighbour = QgsFeature()
distance = QgsDistanceArea()

sumDist = 0.00
A = base_layer.extent()
A = float(A.width() * A.height())

features = point_layer.getFeatures()
count = point_layer.featureCount()

for current, feat in enumerate(features):
   neighbourID = spatialIndex.nearestNeighbor(
       feat.geometry().asPoint(), 2)[1]
   request = QgsFeatureRequest().setFilterFid(neighbourID)
   neighbour = point_layer.getFeatures(request).next()
   sumDist += distance.measureLine(neighbour.geometry().asPoint(), feat.geometry().asPoint())

do = float(sumDist) / count
de = float(0.5 / math.sqrt(count / A))
d = float(do / de)
SE = float(0.26136 / math.sqrt(count ** 2 / A))
zscore = float((do - de) / SE)

print ‘Area Used: ’ + str(A)
print ‘Number of Points: ’ + str(count)
print ‘Observed Distance: ’ + str(do)
print ‘Expected Distance: ’ + str(de)
print ‘Nearest Neighbor Index: ’ + str(d)
print ‘Standard Error: ’ + str(SE)
print ‘Z Score: ’ + str(zscore)

Word of warning: if filters are set on the base layer in the QGIS gui, it will throw errors with the math module's sqrt function to produce the estimated distance, even though the observed distance is accurately produced and reported. I am unsure of reason for this, but removing existing filters on the base layer and applying all in the setSubsetString made the script functional.

The error was observed on both a Windows 10 QGIS 2.18.10 and a OSX QGIS 2.18.7 install, and corrected in the same manner.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.