Skip to content

Instantly share code, notes, and snippets.

@earthboundkid
Last active October 22, 2025 17:20
Show Gist options
  • Save earthboundkid/ccb3fb4d4440f8c5261833bfd4a6f2fe to your computer and use it in GitHub Desktop.
Save earthboundkid/ccb3fb4d4440f8c5261833bfd4a6f2fe to your computer and use it in GitHub Desktop.
Crunch voter turnout information from registration data
package main
import (
"cmp"
"encoding/csv"
"flag"
"fmt"
"maps"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
csvutil "github.com/earthboundkid/csv/v2"
)
func main() {
app := parse()
if err := app.run(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
type appEnv struct {
in, out string
}
func parse() *appEnv {
var app appEnv
flag.StringVar(&app.in, "input", "voters.csv", "path` for input")
flag.StringVar(&app.out, "out", ".", "`directory` for output")
flag.CommandLine.Init("crunch", flag.ExitOnError)
flag.Parse()
return &app
}
func (app *appEnv) run() error {
return app.counted()
}
type Counts struct {
Total int
Turnout int
Male int
MaleTurnout int
Female int
FemaleTurnout int
Republican int
RepublicanTurnout int
Democratic int
DemocraticTurnout int
Other int
OtherTurnout int
}
type InputRow 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"`
}
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 InputRow
for range csvutil.Scan(csvutil.Options{
Reader: f,
}, &row) {
pennCounts.increment(row)
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)
}
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([]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",
}); err != nil {
return err
}
if err := w.Write(pennCounts.toOutputRow("Pennsylvania")); err != nil {
return err
}
for _, k := range slices.Sorted(maps.Keys(counts)) {
if err := w.Write(counts[k].toOutputRow(k)); err != nil {
return err
}
}
w.Flush()
return w.Error()
}
func (c *Counts) increment(row InputRow) {
c.Total++
turnout := row.LastVoted == "05/20/2025"
if turnout {
c.Turnout++
}
if row.Gender == "M" {
c.Male++
}
if row.Gender == "M" && turnout {
c.MaleTurnout++
}
if row.Gender == "F" {
c.Female++
}
if row.Gender == "F" && turnout {
c.FemaleTurnout++
}
if row.Party == "R" {
c.Republican++
if turnout {
c.RepublicanTurnout++
}
}
if row.Party == "D" {
c.Democratic++
if turnout {
c.DemocraticTurnout++
}
}
if row.Party != "R" && row.Party != "D" {
c.Other++
if turnout {
c.OtherTurnout++
}
}
}
func (c *Counts) toOutputRow(area string) []string {
return []string{
// 0: "Area",
0: strings.TrimPrefix(area, "County "),
// 1: "Total registered",
1: strconv.Itoa(c.Total),
// 2: "Recent voter turnout",
2: strconv.Itoa(c.Turnout),
// 3: "Turnout percentage",
3: pct(c.Turnout, c.Total),
// 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.Male),
5: strconv.Itoa(c.MaleTurnout),
6: pct(c.MaleTurnout, c.Male),
7: pct(c.Male, c.Total),
8: pct(c.MaleTurnout, c.Turnout),
9: performance(c.Male, c.MaleTurnout, c.Total, 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.Female),
11: strconv.Itoa(c.FemaleTurnout),
12: pct(c.FemaleTurnout, c.Female),
13: pct(c.Female, c.Total),
14: pct(c.FemaleTurnout, c.Turnout),
15: performance(c.Female, c.FemaleTurnout, c.Total, 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.Republican),
17: strconv.Itoa(c.RepublicanTurnout),
18: pct(c.RepublicanTurnout, c.Republican),
19: pct(c.Republican, c.Total),
20: pct(c.RepublicanTurnout, c.Turnout),
21: performance(c.Republican, c.RepublicanTurnout, c.Total, 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.Democratic),
23: strconv.Itoa(c.DemocraticTurnout),
24: pct(c.DemocraticTurnout, c.Democratic),
25: pct(c.Democratic, c.Total),
26: pct(c.DemocraticTurnout, c.Turnout),
27: performance(c.Democratic, c.DemocraticTurnout, c.Total, 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.Other),
29: strconv.Itoa(c.OtherTurnout),
30: pct(c.OtherTurnout, c.Other),
31: pct(c.Other, c.Total),
32: pct(c.OtherTurnout, c.Turnout),
33: performance(c.Other, c.OtherTurnout, c.Total, 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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment