Skip to content

Instantly share code, notes, and snippets.

@earthboundkid
Last active November 18, 2025 21:17
Show Gist options
  • Select an option

  • Save earthboundkid/ccb3fb4d4440f8c5261833bfd4a6f2fe to your computer and use it in GitHub Desktop.

Select an option

Save earthboundkid/ccb3fb4d4440f8c5261833bfd4a6f2fe to your computer and use it in GitHub Desktop.
Crunch voter turnout information from registration data
package main
import (
"bufio"
"cmp"
"encoding/csv"
"flag"
"fmt"
"maps"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
csvutil "github.com/earthboundkid/csv/v2"
)
const ElectionDay = "11/04/2025"
func main() {
app := parse()
if err := app.run(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
type mode int
const (
filterMode mode = iota + 1
crunchMode
idMode
)
type appEnv struct {
mode
in, out string
}
func parse() *appEnv {
flag.CommandLine.Init("voterreg", flag.ExitOnError)
flag.Parse()
var app appEnv
if flag.NArg() < 1 {
return &app
}
switch flag.Arg(0) {
case "filter":
app.mode = filterMode
case "crunch":
app.mode = crunchMode
case "id":
app.mode = idMode
}
fl := flag.NewFlagSet("voterreg", flag.ExitOnError)
fl.StringVar(&app.in, "in", "voters.csv", "`file path` for input")
fl.StringVar(&app.out, "out", ".", "`directory path` for output")
fl.Parse(flag.Args()[1:])
return &app
}
func (app *appEnv) run() error {
switch app.mode {
case crunchMode:
return app.counted()
case filterMode:
return app.filter()
case idMode:
return app.addID()
default:
return fmt.Errorf("invalid mode. Must be crunch, filter, or id")
}
}
type Counts struct {
TotalReg int
Turnout int
MaleReg int
MaleTurnout int
FemaleReg int
FemaleTurnout int
RepublicanReg int
RepublicanTurnout int
DemocraticReg int
DemocraticTurnout int
OtherReg int
OtherTurnout int
}
type CrunchInputRow struct {
Gender string `csv:"Gender"`
DOB string `csv:"DOB"`
FirstRegistered string `csv:"First registered"`
LastVoted string `csv:"Last voted"`
Party string `csv:"Party"`
ZIP string `csv:"ZIP"`
HouseDistrict string `csv:"State House District"`
SenateDistrict string `csv:"State Senate District"`
CongressionalDistrict string `csv:"Congressional District"`
County string `csv:"County"`
PrecinctCode string `csv:"Precinct code"`
Ward string `csv:"Ward"`
}
func (row CrunchInputRow) CountyPrecinct() string {
if row.County == "PHILADELPHIA" {
return "ZPrecinct " + row.County + "-" + strings.TrimPrefix(row.Ward, "WD")
}
return "ZPrecinct " + row.County + "-" + row.PrecinctCode
}
func (app *appEnv) counted() error {
f, err := os.Open(app.in)
if err != nil {
return err
}
defer f.Close()
counts := make(map[string]*Counts)
var pennCounts Counts
var row CrunchInputRow
for range csvutil.Scan(csvutil.Options{
Reader: f,
}, &row) {
pennCounts.increment(row)
// Prefix names of counties so they sort together
county := "County " + row.County
setDefault(counts, county).increment(row)
setDefault(counts, row.CongressionalDistrict).increment(row)
setDefault(counts, row.HouseDistrict).increment(row)
setDefault(counts, row.SenateDistrict).increment(row)
setDefault(counts, row.CountyPrecinct()).increment(row)
}
out, err := os.Create(filepath.Join(app.out, "counts.csv"))
if err != nil {
return err
}
defer func() {
err = cmp.Or(err, out.Close())
}()
w := csv.NewWriter(out)
if err := w.Write(outputHeader); err != nil {
return err
}
if err := w.Write(pennCounts.toOutputRow("Pennsylvania")); err != nil {
return err
}
for _, k := range slices.Sorted(maps.Keys(counts)) {
// Strip sorting prefix, if present
area := strings.TrimPrefix(k, "County ")
area = strings.TrimPrefix(area, "ZPrecinct ")
if err := w.Write(counts[k].toOutputRow(area)); err != nil {
return err
}
}
w.Flush()
return w.Error()
}
func (c *Counts) increment(row CrunchInputRow) {
c.TotalReg++
turnout := row.LastVoted == ElectionDay
if turnout {
c.Turnout++
}
if row.Gender == "M" {
c.MaleReg++
}
if row.Gender == "M" && turnout {
c.MaleTurnout++
}
if row.Gender == "F" {
c.FemaleReg++
}
if row.Gender == "F" && turnout {
c.FemaleTurnout++
}
if row.Party == "R" {
c.RepublicanReg++
if turnout {
c.RepublicanTurnout++
}
}
if row.Party == "D" {
c.DemocraticReg++
if turnout {
c.DemocraticTurnout++
}
}
if row.Party != "R" && row.Party != "D" {
c.OtherReg++
if turnout {
c.OtherTurnout++
}
}
}
var outputHeader = []string{
0: "Area",
1: "Total registered",
2: "Recent voter turnout",
3: "Turnout percentage",
4: "Males registered",
5: "Male turnout",
6: "Male turnout percentage",
7: "Male percentage of registration",
8: "Male percentage of total turnout",
9: "Male turnout performance",
10: "Females registered",
11: "Female turnout",
12: "Female turnout percentage",
13: "Female percentage of registration",
14: "Female percentage of total turnout",
15: "Female turnout performance",
16: "Republicans registered",
17: "Republican turnout",
18: "Republican turnout percentage",
19: "Republican percentage of registration",
20: "Republican percentage of total turnout",
21: "Republican turnout performance",
22: "Democrats registered",
23: "Democratic turnout",
24: "Democratic turnout percentage",
25: "Democratic percentage of registration",
26: "Democratic percentage of total turnout",
27: "Democratic turnout performance",
28: "Other party registered",
29: "Other party turnout",
30: "Other party turnout percentage",
31: "Other party percentage of registration",
32: "Other party percentage of total turnout",
33: "Other party turnout performance",
}
func (c *Counts) toOutputRow(area string) []string {
return []string{
// 0: "Area",
0: area,
// 1: "Total registered",
1: strconv.Itoa(c.TotalReg),
// 2: "Recent voter turnout",
2: strconv.Itoa(c.Turnout),
// 3: "Turnout percentage",
3: pct(c.Turnout, c.TotalReg),
// 4: "Males registered",
// 5: "Male turnout",
// 6: "Male turnout percentage",
// 7: "Male percentage of registration",
// 8: "Male percentage of total turnout",
// 9: "Male turnout performance",
4: strconv.Itoa(c.MaleReg),
5: strconv.Itoa(c.MaleTurnout),
6: pct(c.MaleTurnout, c.MaleReg),
7: pct(c.MaleReg, c.TotalReg),
8: pct(c.MaleTurnout, c.Turnout),
9: performance(c.MaleReg, c.MaleTurnout, c.TotalReg, c.Turnout),
// 10: "Females registered",
// 11: "Female turnout",
// 12: "Female turnout percentage",
// 13: "Female percentage of registration",
// 14: "Female percentage of total turnout",
// 15: "Female turnout performance",
10: strconv.Itoa(c.FemaleReg),
11: strconv.Itoa(c.FemaleTurnout),
12: pct(c.FemaleTurnout, c.FemaleReg),
13: pct(c.FemaleReg, c.TotalReg),
14: pct(c.FemaleTurnout, c.Turnout),
15: performance(c.FemaleReg, c.FemaleTurnout, c.TotalReg, c.Turnout),
// 16: "Republicans registered",
// 17: "Republican turnout",
// 18: "Republican turnout percentage",
// 19: "Republican percentage of registration",
// 20: "Republican percentage of total turnout",
// 21: "Republican turnout performance",
16: strconv.Itoa(c.RepublicanReg),
17: strconv.Itoa(c.RepublicanTurnout),
18: pct(c.RepublicanTurnout, c.RepublicanReg),
19: pct(c.RepublicanReg, c.TotalReg),
20: pct(c.RepublicanTurnout, c.Turnout),
21: performance(c.RepublicanReg, c.RepublicanTurnout, c.TotalReg, c.Turnout),
// 22: "Democrats registered",
// 23: "Democratic turnout",
// 24: "Democratic turnout percentage",
// 25: "Democratic percentage of registration",
// 26: "Democratic percentage of total turnout",
// 27: "Democratic turnout performance",
22: strconv.Itoa(c.DemocraticReg),
23: strconv.Itoa(c.DemocraticTurnout),
24: pct(c.DemocraticTurnout, c.DemocraticReg),
25: pct(c.DemocraticReg, c.TotalReg),
26: pct(c.DemocraticTurnout, c.Turnout),
27: performance(c.DemocraticReg, c.DemocraticTurnout, c.TotalReg, c.Turnout),
// 28: "Other party registered",
// 29: "Other party turnout",
// 30: "Other party turnout percentage",
// 31: "Other party percentage of registration",
// 32: "Other party percentage of total turnout",
// 33: "Other party turnout performance",
28: strconv.Itoa(c.OtherReg),
29: strconv.Itoa(c.OtherTurnout),
30: pct(c.OtherTurnout, c.OtherReg),
31: pct(c.OtherReg, c.TotalReg),
32: pct(c.OtherTurnout, c.Turnout),
33: performance(c.OtherReg, c.OtherTurnout, c.TotalReg, c.Turnout),
}
}
// pct returns a string representing num divided by denom as a percentage
func pct(num, denom int) string {
return fmt.Sprintf("%.1f%%", 100*float64(num)/float64(denom))
}
// performance returns a percentage represening how much more or less a group turned out versus their percentage of registrants
func performance(groupReg, groupTurnout, total, turnout int) string {
g, gt, r, t := float64(groupReg), float64(groupTurnout), float64(total), float64(turnout)
perf := (gt * r / g / t) - 1
return fmt.Sprintf("%.1f%%", 100*perf)
}
// setDefault is like Python's dict.setdefault.
// It adds a map entry for key if one doesn't exist
// and returns the existing entry if it does.
func setDefault[M ~map[K]*V, K comparable, V any](m M, k K) *V {
if v := m[k]; v != nil {
return v
}
v := new(V)
m[k] = v
return v
}
func printOnce(s string, m map[string]int) {
m[s]++
if n := m[s]; n > 1 {
return
}
fmt.Println(s)
}
func printSorted[K cmp.Ordered, V any](m map[K]V) {
for _, k := range slices.Sorted(maps.Keys(m)) {
fmt.Println(k, m[k])
}
}
func (app *appEnv) filter() (err error) {
f, err := os.Open(app.in)
if err != nil {
return err
}
defer f.Close()
out, err := os.Create(filepath.Join(app.out, "filtered.csv"))
if err != nil {
return err
}
defer func() {
err = cmp.Or(err, out.Close())
}()
type InputRow struct {
Title string `csv:"Title"`
First string `csv:"First"`
Middle string `csv:"Middle"`
Last string `csv:"Last"`
Suffix string `csv:"Suffix"`
Gender string `csv:"Gender"`
DOB string `csv:"DOB"`
FirstRegistered string `csv:"First registered"`
LastVoted string `csv:"Last voted"`
StatusLastUpdated string `csv:"Status last updated"`
Status string `csv:"Status"`
Party string `csv:"Party affiliation"`
HouseNo string `csv:"House number"`
HouseSuffix string `csv:"House number suffix"`
Street string `csv:"Street name"`
Apartment string `csv:"Apartment number"`
City string `csv:"City"`
State string `csv:"State"`
ZIP string `csv:"ZIP"`
PrecinctCode string `csv:"Precinct code"`
Ward string `csv:"Ward"`
HouseDistrict string `csv:"State House District"`
SenateDistrict string `csv:"State Senate District"`
CongressionalDistrict string `csv:"Congressional District"`
Mailing1 string `csv:"Mailing address 1"`
Mailing2 string `csv:"Mailing address 2"`
Home string `csv:"Home phone"`
Registration string `csv:"Registration method"`
County string `csv:"County"`
}
w := csv.NewWriter(out)
if err := w.Write([]string{
"Row",
"Gender",
"DOB",
"First registered",
"Last voted",
"Party",
"ZIP",
"State House District",
"State Senate District",
"Congressional District",
"Precinct code",
"Ward",
"County",
}); err != nil {
return err
}
n := 0
var row InputRow
for range csvutil.Scan(csvutil.Options{
Reader: f,
}, &row) {
if row.Status != "A" {
continue
}
n++
if err := w.Write([]string{
strconv.Itoa(n),
row.Gender,
row.DOB,
row.FirstRegistered,
row.LastVoted,
row.Party,
row.ZIP,
row.HouseDistrict,
row.SenateDistrict,
row.CongressionalDistrict,
row.PrecinctCode,
row.Ward,
row.County,
}); err != nil {
return err
}
}
w.Flush()
if err := w.Error(); err != nil {
return err
}
fmt.Println("rows:", n)
return nil
}
func (app *appEnv) addID() error {
// Open the input file
in, err := os.Open(app.in)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(filepath.Join(app.out, "voter-id.csv"))
if err != nil {
return err
}
defer out.Close()
s := bufio.NewScanner(in)
w := bufio.NewWriter(out)
n := 0
for s.Scan() {
line := s.Text()
if n == 0 {
_, err := w.WriteString("rowid,")
if err != nil {
return err
}
} else {
_, err := fmt.Fprintf(w, "%d,", n)
if err != nil {
return err
}
}
_, err := w.WriteString(line + "\n")
if err != nil {
return err
}
n++
}
if err := s.Err(); err != nil {
return err
}
return w.Flush()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment