import contextily as ctximport geopandas as gpdimport matplotlib.pyplot as pltimport pandas as pdfrom pointpats import Knox, KnoxLocal, plot_density%load_ext watermark%watermark -a 'eli knaap & serge rey'-v -d -u -p pointpats,geopandas,libpysal
The watermark extension is already loaded. To reload it, use:
%reload_ext watermark
Author: eli knaap & serge rey
Last updated: 2025-04-21
Python implementation: CPython
Python version : 3.12.9
IPython version : 8.31.0
pointpats: 2.5.1
geopandas: 1.0.1
libpysal : 4.12.1
A classic tradition in spatial analysis examines the clustering of events in time and space. A set of “events” in this case is a dataset with both a geographic location (i.e. a point with X and Y coordinates) and a temporal location (i.e. a timestamp or some record of when something occurred). Events that cluster together in time and space can often be substantively important across a wide variety of social, behavioral, and natural sciences. For example in epidemiology, a cluster of events may help identify a disease outbreak, but understanding clustering in space-time event data is widely applicable in many fields. Further, the data are often already present in the most useful format. Example research questions and geocoded/timestamped data might include:
public health & epidemiology, e.g. to describe outbreaks or help identify inflection points in an epidemic
infections
vaccines
overdoses
transportation, e.g. to identify dangerous intersections or high-demand transit locations
traffic accidents
taxi hails
DUI violations
housing e.g. to identify transitioning land markets or residential displacement
development permits
foreclosures
evictions
marketing e.g. to understand which campaigns do better in which locations
sales
ad clicks
conversions
criminology e.g. to understand inequality in police enforcement or victimization
stop and frisk incidents
burglaries
firearm sales
earth & climate science e.g. to examine impacts of climate change on catastrophic events
volcanic eruptions
earthquakes
hurricane landfalls
One of the most commonly-used approaches is the Knox statistic (Knox & Bartlett, 1964) first formulated in epidemiology to study disease outbreaks. The Knox statistic looks for clustering of events in space and time using two distance thresholds that define a set of “spatial neighbors” who are nearby in the geographical sense, and a set of “temporal neighbors” who are nearby in time. These thresholds are commonly called delta (\(\delta\), for distance) and tau (\(\tau\), for time) respectively (Kulldorff & Hjalmars, 1999; Rogerson, 2021).
To use a Knox statistic, we adopt the null hypothesis that there is no space-time interaction between events, and we look for non-random groups in the data. That is, if we find there are more events than expected within the two thresholds (i.e. more space-time neighbors than expected), then there is evidence to reject the null in favor of space-time clustering.
# currently, you can download this file from# https://www.dropbox.com/scl/fi/ddduihxo5mnhbtkhsp3vb/sd_collisions.parquet?rlkey=cy19kaxu0zalcpewd3u8gzdwr&dl=0sd_collisions = gpd.read_parquet("../data/sd_collision.parquet")sd_collisions.plot()
Because these points are so dense, a common way to search for “hotspots” is to plot a map using a kernel density estimator (KDE), which we can do for all events, or for subsets of events like pedestrian or bicycle collisions
Code
# note you need statsmodels installed to run this lineax = plot_density(sd_collisions, bandwidth=2000)
Plotting a heatmap of these points using a kernel-density estimator can give us a sense for how the events cluster in space, but it ignores the time dimension entirely. We can see that the heatmaps for pedestrian and and bicycle collisions are a little different, but it is not clear why. It could be because there are more cycle commuters in some regions of the county versus others (so more opportunities for a collision to occur) or because collision risk is higher in some places, or both. Comparisons across kernel density maps for these two classes of events is complicated because we don’t know whether they share the same population-at-risk surface underneath or risks surfaces.
9.2 Global Clustering
The classic Knox statistic is a test for global clustering. It examines whether the pattern of spatio-temporal interaction in the events is random or not. The two critical parameters for the Knox statistic are delta and tau which define our neighborhood thresholds in space and time. The delta argument is measured in the units of the geodataframe CRS, and tau can be measured using any time measurement represented by an integer.
Here, we’ll measure time by the number of days elapsed since the start of data collection. Since the collision date is stored as a string, we can convert to a pandas datetime dtype, then take the difference from the minium date, stored in days (so our time is measured in days).
Code
# convert to datetimesd_collisions.COLLISION_DATE = pd.to_datetime( sd_collisions.COLLISION_DATE, format="%Y%m%d")min_date = sd_collisions.COLLISION_DATE.min()sd_collisions["time_in_days"] = sd_collisions.COLLISION_DATE.apply(lambda x: x - min_date).dt.days
9.2.1 Pedestrian Collisions
Here we set delta to 2000 and tau to 30, meaning our space-time neighborhood is looking for clusters of events that occur within one month and two kilometers of one another. To test for space-time clustering, we then instantiate a new Knox class and pass the geodataframe, the column storing values for time, and the delta and tau thresholds. Note the delta parameter is specified in units of the geodataframe coordinate system (which here is in UTM, and thus measured in meters).
Similar to the Moran classes demonstrated in the previous section, the Knox classes permit two types of inferences, and provide p-values based on analytical solution, and a pseudo p-value that leverages computational inference.
Code
ped_knox.p_poisson
5.88418203051333e-15
Code
ped_knox.p_sim
0.01
Both inferential techniques suggest significant space-time clustering at the \(\alpha=0.05\) level using these delta and tau thresholds.
In both the pedestrian and bicycle collision data the p-values (for both analytical and permutation-based inference) are significant, demonstrating evidence of space-time clustering in the collisions. That is, some times and places appear to be more dangerous than others. Using a local Knox statistic, we can start investigating where and when these places might be.
9.3 Local Clustering
As with a statistic like Moran’s \(I\), the Knox statistic can be decomposed into a local version that describes, at an observation level, whether the event at a given location and time is a member of a significant space-time cluster. This can be considered a kind of “hotspot” analysis where the locally significant values identify particular pockets of the study region (and time period) where events are clustered (Brooke Marshall et al., 2007; Piroutek et al., 2014).
As a local statistic, the resulting KnoxLocal class no longer has a single \(p\)-value, but a value for each observation. Since permutations is included as a default argument in the KnoxLocal class, p-values from both the analytical and simulation-based inference are available as attributes on the fitted class
Code
ped_knox_local.p_hypergeom
array([1. , 0.17142085, 1. , ..., 1. , 1. ,
1. ])
Code
ped_knox_local.p_sims
array([1. , 0.22, 1. , ..., 1. , 1. , 1. ])
Together with the space-time neighbor relationships, we can use these p-values to identify local “hotspots”
Code
ped_knox_local.hotspots()
geometry
focal
pvalue
focal_time
neighbor
orientation
cluster
209
POINT (493633.189 3612664.182)
91417078
0.05
3574
91340273
lead
46
208
POINT (493325.185 3613464.75)
91340273
0.09
3585
91417078
lag
46
188
POINT (489891.324 3613600.67)
90390796
0.30
2226
90375631
lag
17
197
POINT (492414.27 3614208.126)
90671972
0.22
2261
8311972
lag
18
121
POINT (483023.687 3616515.517)
9547792
0.05
4431
9546979
lag
29
...
...
...
...
...
...
...
...
52
POINT (449665.94 3696306.562)
8078797
0.17
1998
8078876
lead
15
16
POINT (454048.752 3700841.589)
5950645
0.01
702
5874764
lag
3
15
POINT (454117.71 3701661.716)
5925674
0.01
691
5901518
lag
3
1
POINT (451566.342 3700510.009)
5542674
0.07
392
5598714
coincident
1
3
POINT (451563.592 3700517.784)
5598714
0.04
392
5542674
coincident
1
228 rows × 7 columns
Observations are identified as hotspots if the observed number of space-time neighbors at that location exceeds the expected number. This means that every observation in the dataset is assigned a p-value that determines whether it is a significant locus of space-time interaction. From a graph theoretic perspective, the number of space-time neighbors for a unit is the number of incident edges for that node, with the graph being formed as the intersection of two other graphs: one for temporal neighbors and one for spatial neighbors. p-values for each node are determined as the probability of exceeding the number of incident edges in the space-time graph given the number of incident edges in each of the temporal and spatial graphs for that unit under the assumption of no space-time interaction.
Since we can use either computational/permutation-based inference or an analytical approximation, we can also use the analytical p-values to define hotspots
Code
ped_knox_local.hotspots(inference="analytic")
geometry
focal
pvalue
focal_time
neighbor
orientation
cluster
174
POINT (489891.324 3613600.67)
90390796
0.228722
2226
90375631
lag
11
186
POINT (492414.27 3614208.126)
90671972
0.102649
2261
8311972
lag
12
120
POINT (483023.687 3616515.517)
9547792
0.017825
4431
9546979
lag
26
182
POINT (482110.403 3623168.458)
90533779
0.001876
2399
8424929
lead
15
50
POINT (481340.52 3624266.245)
8424929
0.002090
2405
90533779
lag
15
...
...
...
...
...
...
...
...
35
POINT (449539.275 3695198.499)
8099893
0.041576
2036
8166763
lag
10
34
POINT (448782.08 3696222.501)
8078876
0.020349
2006
8099893
lead
10
33
POINT (449665.94 3696306.562)
8078797
0.159422
1998
8078876
lead
10
11
POINT (454048.752 3700841.589)
5950645
0.000168
702
5874764
lag
1
10
POINT (454117.71 3701661.716)
5925674
0.000417
691
5901518
lag
1
227 rows × 7 columns
And finally we can plot the hotspots to see where they are. By default:
any observation that is itself significant shows up as red
any observation that is not itself significant, but is a space-time neighbor of a significant observation shows up as yellow
any observation that is insignificant (at the defined crit value) shows up as gray
For convenience, there is also an interactive version of the hot spot map that uses the same defaults. In the interactive version, an edge (line) is drawn between members of space-time neighborhoods. This makes it easy to identify the “neighborhood” of significant events
Code
ped_knox_local.explore(plot_edges=True)
Make this Notebook Trusted to load map: File -> Trust Notebook
Make this Notebook Trusted to load map: File -> Trust Notebook
Comparing these maps, it looks like with these delta and tau parameters, the maps show similarities but also differences. Places like downtown, La Presa, or Escondido can be collision hotspots for both cyclists and pedestrians. But for cyclists, Coronado and Carlsbad show up as particularly dangerous locations.
Continuing with the analysis, the difference between these two hotspot locations could be because of differences in the local environment, such as poorer cycling infrastructure in Coronado and Carlsbad. But these hotspots could also just reflect that these places have greater numbers of cyclists, and thus, tend to have more frequent collisions.
Note
These data span a long time horizon, and in this simplified demonstration, we haven’t considered the population shift bias, which occurs if the underlying spatial distribution of activities changes over time (Mack et al., 2012). In the case of vehicle collisions shown here, there is reason to believe that the underlying population at risk may have shifted over time, which could introduce a known bias into our inference. One important assumption of the Knox statistic is that population growth is constant over the geographic subareas over time, but San Diego county is a large, sprawling region and it’s possible there is more traffic activity in some parts of the region in later time periods due to uneven urban development. If so, this increase in activity will lead bias out count of observed space-time neighbors upwards. It is important to consider the performance of the Knox statistic (and its local variant) any time the underlying population surface may vary over time.
Brooke Marshall, J., Spitzner, D. J., & Woodall, W. H. (2007). Use of the local Knox statistic for the prospective monitoring of disease occurrences in space and time: DISEASE MONITORING USING THE LOCAL KNOX STATISTIC. Statistics in Medicine, 26(7), 1579–1593. https://doi.org/10.1002/sim.2603
Knox, E. G., & Bartlett, M. S. (1964). The Detection of Space-Time Interactions. Applied Statistics, 13(1), 25. https://doi.org/10.2307/2985220
Kulldorff, M., & Hjalmars, U. (1999). The Knox Method and Other Tests for Space-Time Interaction. Biometrics, 55(2), 544–552. http://www.jstor.org/stable/2533804
Mack, E. A., Malizia, N., & Rey, S. J. (2012). Population shift bias in tests of space–time interaction. Computers, Environment and Urban Systems, 36(6), 500–512. https://doi.org/10.1016/j.compenvurbsys.2012.05.001
Piroutek, A., Assunção, R., & Paiva, T. (2014). Space-time prospective surveillance based on Knox local statistics. Statistics in Medicine, 33(16), 2758–2773. https://doi.org/10.1002/sim.6118