Propensity Modeling
Turnout and persuasion models built from the voter file, certified precinct results, Census data, and the campaign’s own canvass history, so every score traces back to a source.
Interested in joining Fall 26? Join our email list.
This is the kind of model we build for campaigns and elections clients, not for government relations work (that is covered below). Each square is a precinct in a synthetic county. Pick a goal, drag the threshold, and the dashboard shows which precincts a campaign would flag and what it would take to reach them. This is not an actual voter file.
Hover over or tap a square in the map to see what is behind its score.
Every input comes from a source a campaign can legally obtain, and none of them reveal how any person voted. Tap a source to see what it is.
Not used: purchased consumer or credit data, social media or browsing data, and any record of how an individual voted. Voting is secret, so files show whether someone voted, never for whom.
Five steps, built from ingredients any campaign can obtain. Tap an for the detail behind each one.
Pull the state voter file and certified precinct results, plus Census tables and precinct boundaries.
Standardize names and addresses, drop duplicates and movers, and place every voter in a precinct.
For each precinct: past margins, swing, turnout by election type, drop-off, and neighborhood makeup.
Rank every precinct against the rest of the county for persuasion and for turnout upside.
Cut the list at the client’s budget, best expected votes per door first, then export turfs and track results.
Hover over or tap any square in the precinct map, or focus the map and use the arrow keys. The selected precinct panel fills in with what sits behind its score. Each score is a percentile, so a 78 means the precinct ranks above 78% of the county.
Choose what the program is for, set where to cut, and the numbers on the right answer what it would take. Everything on the dashboard responds to every control.
Synthetic data generated for illustration. Not an actual voter file. See where this fits into a full engagement in a sample client timeline.
Every engagement is covered by a nondisclosure agreement before any file is shared with CSG.
Client data is used only for that client’s project. It is never used to extrapolate, benchmark, or build anything outside the engagement it came from.
When a project ends, CSG deletes the client’s data from its systems.
CSG deploys campaign-grade analytics so every door knocked, dollar spent, and message delivered is backed by a model, not a guess.
Turnout and persuasion models built from the voter file, certified precinct results, Census data, and the campaign’s own canvass history, so every score traces back to a source.
Algorithmically optimized canvassing turfs and quantitative frameworks that guide budget distribution and volunteer scheduling.
Data pipelines feeding interactive dashboards give campaign leadership rapid visibility into early voting returns and field KPIs.
The government relations team's data work is a different job from campaign modeling, and a narrower one. It runs on the public record: what state legislatures and agencies publish, collected on a schedule, cleaned into one format, and checked by an analyst before it reaches a client.
Scope is set per client: the handful of states and agencies that touch one industry, most often startups taking a physical product to market through state regulation. We do not try to cover every legislature.
Bill text and every amended version, sponsors, committee referrals, status history, and roll-call votes, matched to a client’s issues by keyword and statute citation.
Sources: state legislature websites and public legislative data feeds.
Committee hearing calendars, posted agendas, and witness lists, pulled early enough that a client can decide whether to submit testimony or a comment before the date passes.
Sources: committee pages and published hearing notices.
Proposed and final rules, comment deadlines, and effective dates, plus the current text of the statutes and administrative code sections a product has to comply with, compared state by state.
Sources: state registers, agency rulemaking pages, and the Federal Register.
Registered lobbyists, the organizations they represent, and which trade groups and legislative offices are already active on an issue, assembled into a stakeholder map.
Sources: state lobbying registrations and disclosure filings.
Scheduled scrapers and feed pulls for the legislatures and agencies a client cares about.
One schema for every state: bill ID, chamber, sponsors, status, dates, and text version.
Keyword and citation rules, reviewed by an analyst, tie each item to a client issue.
Compare a bill’s introduced and amended text, and flag what changed since last check.
An analyst reads the primary source and writes the plain-English memo. Every record carries its source link and the date it was last checked.
We use official feeds where they exist and collect only publicly posted pages. No voter data is involved in this work.
For StatML, StatDS, and IS majors, CSG is a proving ground. Classroom datasets are clean; real-world voter data is chaotic.
Members on the Data Team architect production ETL pipelines, clean millions of rows of unstructured behavioral data, and build inferential models that directly shape how real campaigns spend budget and deploy staff.
# csg_precinct_model.py (illustrative) import pandas as pd import geopandas as gpd from sklearn.linear_model import LogisticRegression # 1. Ingest: state voter file, certified results, Census ACS, boundaries voters = pd.read_csv('voter_file_export.csv', dtype=str) results = pd.read_csv('precinct_results_2020_2024.csv') acs = pd.read_csv('acs5_block_groups.csv') bounds = gpd.read_file('precinct_boundaries.shp') # 2. Match and clean: standardize, geocode, assign a precinct voters = standardize(voters).pipe(geocode).sjoin(bounds, how='left') review = voters[voters.precinct_id.isna()] # flagged for review, never guessed # 3. Features per precinct: public results + vote history + ACS p = results.groupby('precinct_id').agg(aligned=('aligned_pct', 'mean'), swing=('aligned_pct', 'std')) p['dropoff'] = turnout(voters, 'general_pres') - turnout(voters, 'general_mid') # 4. Scores: percentile ranks inside the county p['persuasion'] = rank_pct(.55*closeness(p) + .25*norm(p.swing) + .20*norm(p.mid_turnout)) p['turnout_upside'] = rank_pct(p.registered * p.dropoff * p.aligned) # 4b. Voter level, when the client’s contact history supports it X = voters[['age', 'voted_2018', 'voted_2020', 'voted_2022']] voters['turnout_score'] = LogisticRegression().fit(X, voters.voted_2024).predict_proba(X)[:, 1] # 5. Allocate: best expected votes per door first, cut at the budget universe = p.sort_values('votes_per_door', ascending=False).pipe(cut_at_budget, doors=9000) universe.to_csv('walk_universe_v1.csv')
Whether you are building a political campaign, launching an advocacy coalition, or scaling a non-profit initiative, our team is equipped to provide the data-driven insights you need to advance your objectives.
Interested in viewing a sample client timeline?