| Title: | Access Federal, State, and Local Election Data |
| Version: | 0.1.0 |
| Description: | Provides an 'R' interface for downloading and standardizing election data to support research workflows. Election results are published by states through heterogeneous and often dynamic web interfaces that are not consistently accessible through existing 'R' packages or APIs. To address this, the package wraps state-specific 'Python' web scrapers through the 'reticulate' package, enabling access to dynamic content while exposing consistent 'R' functions for querying election availability and results across jurisdictions. The package is intended for responsible use and relies on publicly accessible election result pages. |
| License: | Apache License (≥ 2.0) |
| URL: | https://gchickering21.github.io/DownBallotR/, https://github.com/gchickering21/DownBallotR |
| BugReports: | https://github.com/gchickering21/DownBallotR/issues |
| Encoding: | UTF-8 |
| Language: | en-US |
| SystemRequirements: | Python (>= 3.10), pip |
| Depends: | R (≥ 4.1.0) |
| Imports: | purrr, reticulate, rlang |
| Suggests: | knitr, dplyr, pak, remotes, rmarkdown, testthat (≥ 3.0.0), withr |
| Config/testthat/edition: | 3 |
| RoxygenNote: | 7.3.3 |
| NeedsCompilation: | no |
| Packaged: | 2026-04-22 15:39:09 UTC; grahamchickering |
| Author: | Graham Chickering [aut, cre], Chris Warshaw [ctb] |
| Maintainer: | Graham Chickering <grahamchickering@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-04-23 20:30:07 UTC |
Assign each element of a list result into the caller's environment
Description
When level = "all" returns a named list of data frames, this assigns each
frame into the caller's environment with a state-prefixed name
(e.g. ga_state, ga_county).
Usage
.assign_list_result(result, state, caller_env)
Check that a normalized state name is a recognized US state
Description
Raises a user-friendly error with fuzzy-match suggestions when the state
cannot be identified. Pass required = FALSE to only warn (used when
NULL is a valid "all states" sentinel).
Usage
.check_state_recognized(state)
Warn or prompt when a year range exceeds the threshold
Description
In interactive sessions the user is asked to confirm; in non-interactive sessions (Rscript, knitr, batch jobs) an error is raised immediately so accidental large scrapes are blocked.
Usage
.check_year_span(year_from, year_to, source, threshold = 5L)
Normalize a path safely (no warnings on NA)
Description
Normalize a path safely (no warnings on NA)
Usage
.db_norm_path(x)
Import the Python registry module (after ensuring Python is ready)
Description
Import the Python registry module (after ensuring Python is ready)
Usage
.db_registry()
Emit source availability and unconfirmed-year notice
Description
Emit source availability and unconfirmed-year notice
Usage
.emit_availability(source, state, year_to)
Normalise a state to canonical title-case full name
Description
Accepts 2-letter abbreviations (any case) or full names in any
case/spacing/underscore style. Returns NULL invisibly when the input
is NULL.
Usage
.normalize_state(state)
Route office + normalised state to a registry source name
Description
Route office + normalised state to a registry source name
Usage
.route_to_source(state)
Call the Connecticut CTEMS election results scraper
Description
Call the Connecticut CTEMS election results scraper
Usage
.scrape_ct(
year_from = NULL,
year_to = NULL,
level = "all",
max_town_workers = 2L
)
Call the ElectionStats scraper
Description
Call the ElectionStats scraper
Usage
.scrape_election_stats(
state,
year_from = 1789L,
year_to = NULL,
level = "all",
parallel = TRUE
)
Call the Georgia SOS election results scraper
Description
Call the Georgia SOS election results scraper
Usage
.scrape_ga(
year_from = NULL,
year_to = NULL,
level = "all",
max_county_workers = 4L,
include_vote_methods = FALSE
)
Call the Indiana General Election results scraper
Description
Call the Indiana General Election results scraper
Usage
.scrape_in(year_from = NULL, year_to = NULL, level = "all")
Call the Louisiana Secretary of State election results scraper
Description
Call the Louisiana Secretary of State election results scraper
Usage
.scrape_la(
year_from = NULL,
year_to = NULL,
level = "all",
max_parish_workers = 2L
)
Call the NC results scraper
Description
Call the NC results scraper
Usage
.scrape_nc(year_from = NULL, year_to = NULL, level = "all")
Call the Utah election results scraper
Description
Call the Utah election results scraper
Usage
.scrape_ut(
year_from = NULL,
year_to = NULL,
level = "all",
max_county_workers = 4L
)
Human-readable label for a source, used in messages and errors
Description
Human-readable label for a source, used in messages and errors
Usage
.source_label(source, state = NULL)
Convert canonical title-case state name to 2-letter abbreviation (lowercase)
Description
Convert canonical title-case state name to 2-letter abbreviation (lowercase)
Usage
.state_to_abbrev(state)
Convert canonical title-case state to ElectionStats key (lowercase, underscores)
Description
Convert canonical title-case state to ElectionStats key (lowercase, underscores)
Usage
.state_to_es_key(state)
Stop if a scalar argument has length != 1
Description
Stop if a scalar argument has length != 1
Usage
.stop_if_not_scalar(x, arg)
Coerce a year value to integer, accepting numeric, string, or NULL
Description
Coerce a year value to integer, accepting numeric, string, or NULL
Usage
.to_year(x, arg = deparse(substitute(x)))
Validate and coerce max_workers to a positive integer, capped at 4
Description
R users are capped at 4 parallel workers to avoid overwhelming public election data sites. Values above 4 are silently reduced with a message.
Usage
.validate_max_workers(x, arg = "max_workers")
Show data availability for election scrapers
Description
Returns a data frame listing the earliest available year for each state and scraper source tracked by DownBallotR. All sources include data through the current calendar year.
Usage
db_available_years(state = NULL)
Arguments
state |
Optional state name to filter results (e.g. |
Value
A data.frame with columns source, state,
start_year, and end_year.
Examples
# All sources
db_available_years()
# Filter to one state
db_available_years(state = "Virginia")
Ensure reticulate is bound to DownBallotR's Python environment
Description
This function is idempotent: safe to call multiple times in a session. It binds reticulate to the package's configured virtualenv and adds inst/python to sys.path so Python can import our modules.
Usage
db_bind_python()
List all registered Python scraper sources
Description
List all registered Python scraper sources
Usage
db_list_sources()
Value
Character vector of source names.
List states supported by DownBallotR scrapers
Description
List states supported by DownBallotR scrapers
Usage
db_list_states(source = NULL)
Arguments
source |
One of the sources returned by |
Value
Named character vector of canonical state names. When
source = NULL each element is named by its source; when a single
source is given the names are omitted.
Internal: stop if reticulate initialized to a different python
Description
Internal: stop if reticulate initialized to a different python
Usage
db_stop_if_python_initialized_to_other(envname = "downballotR")
Ensure downballot Python environment is ready
Description
Ensure downballot Python environment is ready
Usage
downballot_ensure_python(envname = "downballot")
Install Python dependencies for downballotR
Description
Creates/uses a named virtual environment and installs Python requirements (pandas, requests, lxml, bs4, playwright), then installs Playwright Chromium.
Usage
downballot_install_python(
envname = "downballotR",
python = NULL,
reinstall = FALSE,
install_chromium = TRUE,
quiet = FALSE
)
Arguments
envname |
Name of the virtualenv to create/use. |
python |
Path to a python executable to use when creating the env (optional). |
reinstall |
If TRUE, reinstall packages even if already installed. |
install_chromium |
If TRUE, install Playwright Chromium browser.
In interactive sessions, the user will be prompted for explicit consent
before the download (~100-200 MB) begins. In non-interactive sessions,
the function will error if Chromium is missing; set
|
quiet |
If TRUE, suppress progress messages. |
Details
Python must already be installed on your system before calling this
function. downballot_install_python() creates a virtual environment
using an existing Python interpreter — it does not install Python itself.
If Python is not found, reticulate will error with a message about
being unable to create a virtualenv.
-
Windows: Install Python from https://www.python.org/downloads/. Make sure to check "Add Python to PATH" during installation.
-
macOS: Python 3 is available via Xcode Command Line Tools (
xcode-select --install) or https://www.python.org/downloads/. -
Linux: Install via your package manager, e.g.
sudo apt install python3 python3-venv(Debian/Ubuntu) orsudo dnf install python3(Fedora/RHEL).
If the environment already exists and all required packages are present,
the function prints a message and returns without doing work (unless Chromium
is missing and install_chromium = TRUE). In all cases, it attempts to
initialize reticulate to the selected interpreter for this session.
Value
Called for side effects. Returns invisible(TRUE) on success,
or invisible(FALSE) if the user declines the Chromium download.
Check Python environment status for downballotR
Description
Reports whether the Python virtual environment exists, whether reticulate is initialized (and which Python is active), which required packages are missing, and whether Playwright Chromium is available.
Usage
downballot_python_status(
envname = "downballotR",
required_pkgs = db_required_python_packages(),
quiet = FALSE
)
Arguments
envname |
Name of the virtualenv to check. |
required_pkgs |
Character vector of required Python packages. Defaults
to |
quiet |
If |
Details
This function does not modify the environment.
Value
An object of class downballot_python_status. Invisibly when
quiet = FALSE.
Use the DownBallotR Python virtualenv in this R session
Description
Pins reticulate to the package's virtualenv for the current R session. If reticulate is already initialized to a different interpreter, this errors with a clear message (reticulate cannot switch interpreters mid-session).
Usage
downballot_use_python(envname = "downballotR")
Arguments
envname |
Name of the virtualenv to use. |
Value
Invisibly TRUE on success.
Print a downballot_python_status object
Description
Print a downballot_python_status object
Usage
## S3 method for class 'downballot_python_status'
print(x, ...)
Arguments
x |
A |
... |
Further arguments passed to or from other methods (unused). |
Value
Invisibly returns x, the downballot_python_status
object passed in, following the S3 print method convention.
Scrape election data
Description
A single entry point that automatically routes to the appropriate scraper
based on state. Use db_list_states("election_stats")
to see states supported by the general-election scraper.
Usage
scrape_elections(
state = NULL,
year_from = NULL,
year_to = NULL,
level = c("all", "state", "county", "precinct", "town", "parish"),
parallel = TRUE,
max_workers = 4L,
include_vote_methods = FALSE
)
Arguments
state |
State name or 2-letter abbreviation, accepted in any case or
spacing style (e.g. |
year_from |
Start year, inclusive (default |
year_to |
End year, inclusive (default |
level |
What to return. |
parallel |
( |
max_workers |
(Georgia / Utah / Connecticut / Louisiana) Maximum number
of parallel Chromium browsers (default |
include_vote_methods |
(Georgia only) If |
Details
Routing rules (applied in order):
-
statematches North Carolina (e.g."NC","north_carolina") → NC State Board of Elections scraper (2000–present). -
statematches Connecticut (e.g."CT","connecticut") → Connecticut CTEMS scraper (2016–present). -
statematches Georgia (e.g."GA","georgia") → Georgia Secretary of State scraper (2000–present). -
statematches Utah (e.g."UT","utah") → Utah election results scraper (2023–present). -
statematches Indiana (e.g."IN","indiana") → Indiana General Election results scraper (2019–present). -
statematches Louisiana (e.g."LA","louisiana") → Louisiana Secretary of State scraper (1982–present). All other states → ElectionStats multi-state scraper.
Value
A data.frame, or a named list when level = "all":
$state + $county (+ $precinct when available) for ElectionStats;
$state + $county + $precinct for Georgia / Utah (or just $state / $county / $precinct alone when the corresponding level is specified); $state + $county for Indiana;
$state + $town for Connecticut;
$state + $parish for Louisiana;
$precinct + $county + $state for North Carolina.
Each component is also assigned directly into the calling environment
(e.g. ga_state, ga_county) when level = "all".
Examples
# General election results — Virginia
df <- scrape_elections(state = "virginia", year_from = 2023, year_to = 2023,
level = "state")
# General election results — Virginia, both state and county levels
res <- scrape_elections(state = "virginia", year_from = 2023, year_to = 2023)
res$state # candidate-level data frame
res$county # county vote breakdown data frame
# North Carolina — single year
df <- scrape_elections(state = "NC", year_from = 2024, year_to = 2024)
# Connecticut — statewide + town results for 2024
res <- scrape_elections(state = "CT", year_from = 2024, year_to = 2024)
res$state # statewide totals
res$town # town-level results
# Connecticut — statewide only (faster; no town scraping)
df <- scrape_elections(state = "CT", year_from = 2024, year_to = 2024,
level = "state")
# Connecticut — with more parallel workers
res <- scrape_elections(state = "CT", year_from = 2022, year_to = 2022,
max_workers = 4L)
# Georgia — statewide + county results
res <- scrape_elections(state = "GA", year_from = 2024, year_to = 2024)
# Georgia — statewide only (faster)
df <- scrape_elections(state = "GA", year_from = 2024, year_to = 2024,
level = "state")
# Georgia — with vote-method breakdown
res <- scrape_elections(state = "GA", year_from = 2024, year_to = 2024,
include_vote_methods = TRUE)
# Utah — statewide + county results
res <- scrape_elections(state = "UT", year_from = 2024, year_to = 2024)
# Indiana — General Election results (statewide + county)
res <- scrape_elections(state = "IN", year_from = 2024, year_to = 2024)
res$state # statewide candidate totals
res$county # county-level breakdown
# Indiana — statewide only (faster)
df <- scrape_elections(state = "IN", year_from = 2022, year_to = 2022,
level = "state")
# Louisiana — statewide + parish results
res <- scrape_elections(state = "LA", year_from = 2024, year_to = 2024)
res$state # statewide candidate totals
res$parish # parish-level breakdown
# Louisiana — statewide only (faster; skips parish scraping)
df <- scrape_elections(state = "LA", year_from = 2023, year_to = 2023,
level = "state")
Summarize an election results data frame
Description
Computes aggregate statistics for a data frame of election results. The
state is detected automatically from the state column when present,
or from the variable name (e.g. ga_results -> "Georgia").
Usage
summarize_results(df, state = NULL)
Arguments
df |
A data frame returned by |
state |
Optional two-letter state abbreviation or full state name. Overrides auto-detection when supplied. |
Value
A named list (printed on call) with:
stateDetected or supplied state name.
yearsInteger vector of election years present.
n_yearsNumber of distinct election years.
n_electionsNumber of distinct elections.
n_candidatesNumber of distinct candidate names.
office_level_breakdownNamed integer vector: distinct elections by level (Federal / State / Local).
offices_by_levelNamed list: distinct office names per level.
Examples
ga_results <- scrape_elections("GA", 2020, 2024)
summarize_results(ga_results)