Geostatistical Analysis of Airbnb Prices
Spatial prediction with variograms and kriging implemented in Python
On this page
This page is a technical log corresponding to my Airbnb geostatistics project. I made this so I could write about the statistics involved a litle more and explain the code behind the whole approach!
Each code block below is self-contained. Click Run in any section without needing to run the ones before it.
Step 1: Simulating Spatially Correlated Data
For the purposes of this simulation, we start with a draw of prices from a multivariate normal distribution where correlation between listings follows an exponential covariance function (Chilès & Delfiner, 2012; Cressie, 1993):
Three parameters control the spatial pattern:
- Range : How far the correlation extends. Past this, prices are essentially unrelated.
- Partial sill : Characterizes how much prices vary due to location.
- Nugget : Introduces random noise between neighbors.
1a. Set up coordinates
We scatter 200 listings across downtown Austin. The coordinate range (-97.8 to -97.6 longitude, 30.2 to 30.4 latitude) covers roughly 11 by 14 miles. In a real dataset these would be actual lat/lon coordinates scraped from Airbnb listings, but for our purposes we just use uniform random points to demonstrate the spatial methods.
1b. Build the covariance matrix
The covariance matrix encodes every pairwise relationship. Entry tells us how much listing ‘s price tells us about listing ‘s price. The diagonal entries ( where ) are the total variance, which is set to . Non-diagonal entries decay exponentially with distance: two listings on the same block (0.01 degrees apart) have covariance around while two listings on opposite sides of the city (0.15 degrees apart) have covariance near zero.
With , correlation drops by 95% at roughly degrees, or about 8 miles. At Austin’s latitude (30°N), one degree of longitude is about 0.87 times the distance of one degree of latitude.
Note: Using raw Euclidean degrees distorts the covariance structure at city scale, but this is ignored for this demonstration. This problem is usually handled by using projected coordinates to keep true distances.
1c. Draw prices
Now we sample from the multivariate normal. Because of the spatial covariance structure, nearby listings will tend to have similar prices. Listings far apart can differ by $50 or more even though they share the same mean. This is the spatial structure we’re trying to detect and exploit.
Step 2: Sample Variogram
The variogram measures dissimilarity over distance (Cressie, 1993; Matheron, 1963). Take all pairs of points roughly apart; if their prices are similar, the variogram is low; if prices vary wildly, it’s high:
The plot should rise from a small value (nearby points are similar) toward a flat plateau (where far apart = uncorrelated).
2a. Bin pairs by distance
The variogram replaces the smooth covariance function with an empirical estimate. Take all listing pairs, group them by how far apart they are, and average the squared price difference within each bin. If spatial structure exists, nearby pairs should have small differences (low semivariance) and distant pairs should have large differences (high semivariance), eventually leveling off at the total variance.
The bin width matters. Too narrow and each bin has few pairs so the data is noisy and prone to variation. Too wide and you can inflate the nugget estimate. Here we use 0.008 degrees (about 0.3 miles), which gives about 15 bins across the 0.15-degree range.
2b. Plot the variogram
Dollar differences between listing pairs organized by distance:
The variogram rises from ~100 (the nugget — noise between neighbors) and approaches ~500 (the sill — total variance). The distance at which it levels off corresponds to the range parameter .
Step 3: Fitting a Variogram Model
The sample variogram is noisy — we need a smooth function for prediction. The exponential model captures the shape with three parameters (Chilès & Delfiner, 2012; Cressie, 1993):
- Nugget — noise between neighbors
- Partial sill — spatial variance
- Range — how fast correlation decays. At , the variogram reaches 95% of the sill.
3a. Fit with scipy
The true parameters we used in Step 1 were nugget = 100, partial sill = 400 (sill = 500), range = 0.04. The fitted values should be close. The nugget is the hardest to properly estimate because it uses only the closest pairs, which are the least common.
3b. Plot sample vs fitted curve
The fitted curve passes through the middle of the noisy sample points. The resulting parameters (nugget ≈ 100, sill ≈ 500, range ≈ 0.04) match the true values we used in Step 1, so our model works!
Step 4: Ordinary Kriging
Now we use our model for further price prediction.
Kriging takes a weighted average of observed prices, with weights from the variogram model (Chilès & Delfiner, 2012; Cressie, 1993). Nearby points get more weight, but clustered points share weight so no single group dominates:
The weights solve a linear system built from the variogram and a Lagrange multiplier to enforce a sum-to-one constraint:
4a. Build the kriging system
We use 140 training points to predict 60 test points. The true variogram parameters are unknown in actual kriging, but we use them here to focus on how kriging works:
The kriging matrix has a block structure.
The top-left block contains the variogram matrix, , where each entry is the variogram value between a pair of training locations. These values encode the spatial relationships within the known data.
The last row and column are ones, (with a zero in the bottom-right corner), enforcing the Lagrange constraint we mentioned earlier that the kriging weights satisfy .
This is a general structure. Regardless of the number of training points, the matrix is always of size and is symmetric, with this same block form,
4b. Predict at each test location
For each test point, the right-hand side vector measures its variogram distance to every training point. Solving the kriging system produces one weight for each training point. Each weight is then multiplied by the corresponding known training price, and the weighted prices are summed to obtain the predicted value.
An RMSE of about 20 means the typical prediction error is 22), so an RMSE of R^21 - 20^2/500 = 0.2$. Thus, we can say using ONLY location captures about a fifth of the variation in Airbnb prices at this scale. Pretty good!
4c. Plot predictions vs actual
Points cluster near the 1:1 line, meaning kriging captures the spatial structure. The scatter around the line reflects the nugget noise. Even if we have perfect spatial knowledge, the individual listings will vary randomly.
This analysis is based on material from Nicolas Christou’s UCLA course Statistics C173/C273: Applied Geostatistics. See the project page for the full R-based analysis on real Airbnb data.