Boundary Discontinuity Designs on Networks

Estimation and inference for boundary discontinuity designs on street networks. Extends the location-based framework of 'rd2d' (Cattaneo, Titiunik and Yu, ) from Euclidean coordinates to network-native coordinates (signed network distance to the boundary and offset along it) and user-defined network metrics (edge costs, travel time, road-class transition penalties, precomputed matrices). Provides side-specific local polynomial estimates of boundary average treatment effect curves with robust bias-corrected inference (Calonico, Cattaneo and Titiunik, 2014, ), uniform confidence bands, weighted and largest aggregate effects, fuzzy designs, covariates, derivative targets, heteroskedasticity-robust, cluster and network Conley (1999, ) standard errors, a port of the 'rd2d' direct plug-in bandwidth selector, and a network-normalised version of the local polynomial density test of Cattaneo, Jansson and Ma (2020, ).


rd2dnet (R)

Boundary discontinuity designs on street networks. rd2dnet adapts the location-based boundary discontinuity framework of rd2d (Cattaneo, Titiunik & Yu) from Euclidean coordinates to network-native coordinates and user-defined network metrics. This is the R package; the Python package lives at the root of the same repository and gives the same numbers on the bundled data.

Built on rd2d. rd2dnet is a direct extension of the boundary discontinuity methodology and software of Matias D. Cattaneo, Rocío Titiunik and Ruiqi Rae Yu (rd2d, part of the rdpackages project). The estimands, side-specific local polynomial estimation, robust bias-corrected inference, uniform bands and the bandwidth selector (a port of rdbw2d's direct plug-in) follow their work. The density test follows Cattaneo, Jansson and Ma's rddensity. If you use rd2dnet, please cite the rd2d papers (citation("rd2dnet")), as well as this package. rd2dnet is an independent project and is not affiliated with or endorsed by the rd2d authors.

Installation

# development version, from GitHub
# install.packages("remotes")
remotes::install_github("zwinship/rd2dnet", subdir = "R-package")

The package needs a C++ compiler when installed from source (Rtools on Windows, Xcode command line tools on macOS). Optional: sf (sf geometries, projecting lon/lat), igraph (networks from igraph graphs), dodgr (OpenStreetMap download).

Quick start

library(rd2dnet)
city <- load_mock_midtown()                    # bundled, works offline
net <- city$network("length")                  # = network_graph(city$edges, city$nodes, "length")
coords <- network_coordinates(net, city$boundary, city$obs, n_eval = 21)

res <- rdnet(city$obs$outcome, coords,
             weights = city$obs$far,           # intensity / floor-area scaling
             cluster = city$obs$block)         # or conley_cutoff = 150
res                                            # pointwise BATEC table
plot(res)
wbate(res, as.numeric(res$table$status == "ok"))   # weighted average effect
lbate(res)                                         # largest effect

rdbwnet(city$obs$outcome, coords)              # bandwidths
density(coords)                                # sorting test per metre of street
rdnet(city$obs$outcome, coords, fuzzy = take_up)   # fuzzy design (take_up = your 0/1 or share)

vignette("rd2dnet") walks through every feature on the mock city (metrics, coordinates, bandwidths, aggregate effects, covariates, fuzzy designs, derivatives, cluster and Conley errors, the density test) and the recipes for donut designs, covariate balance, placebo boundaries and difference-in-discontinuities.

Your own data

A network is a data frame of edges (from, to, attributes such as length, optional geometry) and a data frame of nodes (name, projected x, y):

net <- network_graph(edges, nodes, metric = "length")
net <- network_graph_from_igraph(g)                    # igraph with x/y vertex attributes
net <- network_graph_from_adjacency(A, coords)
net <- network_graph_from_place("Manhattan, New York") # OpenStreetMap via dodgr + sf

bnd <- boundary(line, treated_region = zone,           # matrices or sf geometries
                mask = list(park_polygon, c(a, b)))    # masked stretches have no mass

Distance is a metric you choose

network_graph(edges, nodes, "length")                       # metres along streets
network_graph(edges, nodes, travel_time())                  # seconds, from speed_kph
network_graph(edges, nodes, node_contrast("far", 50))       # length + 50 * |land-use change|
network_graph(edges, nodes, function(edges) 2 * edges$length)   # any vectorised edge cost
network_graph(edges, nodes, precomputed(D))                 # your own node-by-node matrix

# Switching road class costs extra. Ramps are transparent connectors, so
# highway -> ramp -> highway is close while highway -> ramp -> local street is far.
network_graph(edges, nodes, transition_cost(
  "length", class_attr = "highway",
  penalty = data.frame(from = "motorway", to = "residential", penalty = 2000),
  connectors = c("motorway_link", "trunk_link")))

The coordinates s and o, the kernel distances and the bandwidths are all in the metric's units. Treatment side is always geographic.

Method

rd2d rd2dnet
coordinates (X1 - b1, X2 - b2) s_i: signed network distance to the boundary (negative on the control side)
o_i(b): signed offset along the boundary from b to where i reaches it
kernel distance ‖X_i - b‖ d_i(b): shortest-path distance from b to i
estimate side-specific intercept difference same, with polynomial order p in (s, o)
inference robust bias correction with q = p + 1 same, plus uniform bands

See the README at the repository root for the full feature list, the Monte Carlo validation, known limitations and references; all of it applies to the R package.

Python and R

Function names follow the Python package in snake case: NetworkGraph is network_graph(), Boundary is boundary(), EdgeCost/TransitionCost/Precomputed are edge_cost()/transition_cost()/precomputed(), RDNetResult.wbate() is wbate(), and rd2dnet.density() is the density() method for network coordinates. Indices (evaluation points, nodes) are 1-based. Cost functions are vectorised over the edge table. On the bundled mock city the two packages agree to about 1e-12; they can differ only in which boundary node an observation is assigned to when two are exactly equally far.

Development

Rscript -e 'devtools::test()'                          # offline test suite
RD2DNET_ONLINE=true Rscript -e 'devtools::test(filter = "online")'   # live OpenStreetMap download

The online test needs internet access and dodgr, osmdata and sf, like the Python package's pytest -m online. .github/workflows/online.yml runs both on demand.

License

GPL-3, the same license as rd2d and rddensity, from which parts of the implementation are ported.

Reference manual

It appears you don't have a PDF plugin for this browser. You can click here to download the reference manual.

install.packages("rd2dnet")

0.1.0 by Zach Winship, 9 hours ago


https://github.com/zwinship/rd2dnet


Report a bug at https://github.com/zwinship/rd2dnet/issues


Browse source code at https://github.com/cran/rd2dnet


Authors: Zach Winship [aut, cre, cph] , Matias D. Cattaneo [ctb, cph] (author of rd2d and rddensity , from which the bandwidth selector and density test are ported) , Rocío Titiunik [ctb, cph] (author of rd2d , from which the bandwidth selector is ported) , Ruiqi Rae Yu [ctb, cph] (author of rd2d , from which the bandwidth selector is ported) , Michael Jansson [ctb, cph] (author of rddensity , from which the density test is ported) , Xinwei Ma [ctb, cph] (author of rddensity , from which the density test is ported)


Documentation:   PDF Manual  


GPL-3 license


Imports graphics, grDevices, methods, Rcpp, stats, utils

Suggests dodgr, igraph, knitr, Matrix, osmdata, rmarkdown, sf, testthat

Linking to Rcpp


See at CRAN