lehdr is an R package that provides programmatic access to the Longitudinal Employer-Household Dynamics (LEHD) Origin-Destination Employment Statistics (LODES) dataset. LODES forms the backbone of the U.S. Census Bureau’s OnTheMap web application, which maps commuting patterns at fine geographic scales. While OnTheMap is a useful exploratory tool, it does not support systematic comparisons over time or across geographies. lehdr fills that gap by exposing LODES downloads as tidy data frames and providing built-in functions for common analyses.
LODES contains three file types:
lodes_type = "od"):
Origin-destination flows, giving the number of workers by home-workplace
Census block pair.lodes_type = "rac"): Residential
area characteristics, giving job totals at workers’ home blocks.lodes_type = "wac"): Workplace
area characteristics, giving job totals at workers’ job blocks.The following example retrieves 2020 Oregon OD data aggregated to the
Census tract level. Only primary jobs (job_type = "JT01")
across all worker segments (segment = "S000") are
included.
Pass character vectors to state and/or numeric vectors
to year. The function returns a single bound tibble across
all combinations.
or_ri_od <- grab_lodes(
state = c("or", "ri"),
year = c(2019, 2020),
lodes_type = "od",
job_type = "JT01",
segment = "S000",
state_part = "main",
agg_geo = "tract"
)
head(or_ri_od)Not all state-year combinations are available for every LODES version. Consult the LEHD Technical Document for availability.
agg_geo controls the output geography. Valid values are
"block" (default), "bg" (block group),
"tract", "county", and "state".
Aggregation truncates GEOID strings and sums all numeric columns.
Set geometry = TRUE to retrieve Census geometries via
the tigris package and attach them to the result. For
lodes_type = "rac" or "wac", the return value
is an sf object. For lodes_type = "od", two
geometry columns (h_geometry, w_geometry) are
added but the object remains a tibble — use sf::st_as_sf()
to promote it.
grab_crosswalk() downloads the LODES geographic
crosswalk, which maps Census blocks to all higher-level geographies.
Useful when you need custom aggregations not covered by
agg_geo.
vt_xwalk <- grab_crosswalk("vt")
# LODES7 vintage crosswalk (2010 Census blocks)
vt_xwalk_7 <- grab_crosswalk("vt", version = "LODES7")lehdr includes three functions for common LODES analyses. All examples below use Maryland, which has a rich and varied labor market geography: a large central city (Baltimore), dense inner suburbs, agricultural Eastern Shore counties, and strong federal-sector employment in the Washington suburbs.
compute_commute_stats() summarizes commute flows from an
OD dataset into per-geography metrics: workers arriving (inflow),
workers departing (outflow), internal flows, net flow, and the
self-containment ratio. Self-containment is the share of employed
residents who also work within the same geographic unit — a value close
to 1 indicates a largely self-sufficient labor market, while values near
0 indicate heavy out-commuting.
od_md <- grab_lodes(
state = "md",
year = 2019,
lodes_type = "od",
job_type = "JT00",
segment = "S000",
state_part = "main",
agg_geo = "county"
)
commute_md <- compute_commute_stats(od_md, agg_geo = "county")
# Most self-contained counties
commute_md %>%
arrange(desc(self_containment)) %>%
select(county, workers_in, workers_out, net_flow, self_containment) %>%
head(10)Net flow identifies counties that are net job importers (positive) versus net exporters (negative), a key indicator of job/housing imbalance. The map below shows self-containment at the Census tract level for Baltimore City, where the pattern reflects the city’s mix of dense employment centers and residential neighborhoods with limited local job access.
# See data-raw/render_vignette_figures.R for the full figure code.
# Requires: ggplot2, sf, tigris, scalescompute_lodes_change() computes absolute and percentage
change in any LODES variable between two years. The output can be wide
(one row per geography, with columns for each variable’s change) or long
(one row per geography-variable combination, suitable for
ggplot2).
wac_md_panel <- grab_lodes(
state = "md",
year = c(2010, 2019),
lodes_type = "wac",
job_type = "JT00",
segment = "S000",
agg_geo = "county"
)
change_md <- compute_lodes_change(
wac_md_panel,
geo_col = "w_county",
base_year = 2010,
compare_year = 2019,
variables = c("C000", "CE01", "CE02", "CE03")
)
# Counties with the largest absolute job growth
change_md %>%
arrange(desc(C000_change)) %>%
select(w_county, C000_base, C000_compare, C000_change, C000_pct_change) %>%
head(10)The map below shows the percent change in total jobs
(C000) across Maryland counties from 2010 to 2019. The
diverging palette highlights counties that gained jobs (blue) versus
those that lost jobs (red) over the period.
# See data-raw/render_vignette_figures.R for the full figure code.
# Requires: ggplot2, sf, tigris, scalesLong format is useful for visualizing change across multiple variables at once:
The figures in this vignette were produced with ggplot2
and Census geometries from the tigris package. The general
pattern is:
grab_lodes().tigris::tracts(),
tigris::counties(), etc.geom_sf().The following example shows a simple flow-weighted job accessibility index for Baltimore City tracts. For each home tract, the metric sums the total number of workers flowing to all reachable work tracts — a proxy for labor market connectivity that can be computed entirely from LODES OD data.
od_md <- grab_lodes(
state = "md",
year = 2019,
lodes_type = "od",
job_type = "JT00",
segment = "S000",
state_part = "main",
agg_geo = "tract"
)
accessibility <- od_md %>%
dplyr::filter(startsWith(h_tract, "24510")) %>%
dplyr::group_by(h_tract) %>%
dplyr::summarise(
n_dest = dplyr::n_distinct(w_tract),
total_reachable = sum(S000, na.rm = TRUE),
.groups = "drop"
)
head(accessibility)n_dest captures the breadth of access (number of
distinct job destinations a tract sends workers to), while
total_reachable is a flow-weighted measure of aggregate
connectivity. A full accessibility analysis typically incorporates
travel times or distances (see packages such as r5r or
accessibility); the LODES-only measure here is a useful
starting point when network data are unavailable.
# See data-raw/render_vignette_figures.R for the full figure code.
# Requires: ggplot2, sf, tigris, scalesBy default, downloaded files are deleted after reading. Setting
use_cache = TRUE retains files in the user-level cache
directory (tools::R_user_dir("lehdr", "cache")), which
avoids re-downloading when you call grab_lodes() repeatedly
with the same parameters. You can also set this option globally for a
session:
The cache filename includes the LODES version (e.g.,
lodes8_md_wac_S000_JT00_2019.csv.gz), so switching between
LODES versions will not serve stale data from a different vintage.
Upgrading from lehdr < 1.2.0? Earlier versions
cached files without a version prefix (e.g.,
md_wac_S000_JT00_2019.csv.gz). Those files will never be
matched by the new naming scheme and can be safely deleted. To clear the
entire cache:
cache_dir <- tools::R_user_dir("lehdr", "cache")
list.files(cache_dir) # inspect before deleting
unlink(cache_dir, recursive = TRUE)If you use lehdr in published work, please cite it.
Running citation("lehdr") will return a BibTeX entry. In
Chicago author-date style:
Green, Jamaal, Liming Wang, and Dillon Mahmoudi. 2025. “lehdr: Grab Longitudinal Employer-Household Dynamics (LEHD) Flat Files.” R package version 1.2.0. https://github.com/jamgreen/lehdr/