---
title: "Comparisons"
subtitle: "Exercise 8 --- PMAP 8551/4551, Spring 2026"
author: "Andrew Heiss"
date: "2026-03-09"
date-format: "long"
format:
  html:
    toc: true
    knitr:
      opts_chunk: 
        dev: "ragg_png"
        dpi: 300
---

# Task 1: Session check-in

::: {.callout-tip title="Answer"}
Hopefully you wrote some interesting and muddy things!
:::


# Task 2: US presidential elections

```{r}
#| label: setup
#| warning: false
#| message: false

library(tidyverse)
library(ggrepel)
library(targets)

tar_load(election_data_clean)
tar_load(shelby_counties)
tar_load(shelby_map_png)
```

## Data description

This data comes from the [MIT Election Lab](https://electionlab.mit.edu/data) and contains county-level vote counts for US presidential elections from 2000 to 2024.^[The full data is hosted at the [MIT Election Data + Science Lab Dataverse](https://doi.org/10.7910/DVN/VOQCHQ) and is licensed as [Creative Commons Public Domain](http://creativecommons.org/publicdomain/zero/1.0).] Because of differences in how states report election data, the original MIT data was really tricky to work with. To simplify things, I've provided you with a pre-cleaned dataset with one row per candidate-county-year.[^cleaning-code] I also merged in region and division data from the US Census.

[^cleaning-code]: For the morbidly curious, [see here for the full data cleaning code](https://github.com/andrewheiss/dataviz-projects_08-comparisons/blob/main/build_data.R).

There are 15 columns:

| Variable         | Definition                                                 |
|:-----------------|:-----------------------------------------------------------|
| `year`           | Election year                                              |
| `year_cat`       | Election year (categorical version)                        |
| `state_po`       | State postal abbreviation                                  |
| `state`          | State name                                                 |
| `region`         | US Census region                                           |
| `division`       | US Census division                                         |
| `county_name`    | County name                                                |
| `county_fips`    | County FIPS code                                           |
| `office`         | Election type (President)                                  |
| `candidate`      | Candidate name                                             |
| `party`          | Candidate party                                            |
| `candidatevotes` | Votes received by this candidate for this particular party |
| `totalvotes`     | Total number of votes cast in this county-year             |
| `version`        | Date when dataset was finalized                            |
| `mode`           | Mode of ballots cast                                       |

The code below loads and cleans the data. **You don't need to modify anything in this chunk of code---you only need to run it.**

```{r}
#| label: load-election-data
#| warning: false
#| message: false

county_results <- election_data_clean |>
  # MIT stores county, candidate, and party names in ALL CAPS, so we change
  # them to Title Case instead
  mutate(
    county_name = str_to_title(county_name),
    candidate = str_to_title(candidate),
    party = str_to_title(party)
  ) |> 
  # Make a categorical version of year
  mutate(year_cat = factor(year))
```


## Recreate data summary

Right now there's a row for each candidate in each county in each year, which means there are `{r} nrow(county_results)` rows to deal with. That's too much data to plot. We need to condense that down with `group_by()` and `summarize()`.

Use `filter()`, `group_by()` and `summarize()` and `mutate()` to create a data frame named `state_prop_gop` that looks like this:

```{r}
#| label: recreate-me-data-1
#| echo: false
#| message: false
#| ref.label: recreation-data-1
```

**Some hints!**

- You need to filter the data so that it only includes counts for the Republican party
- You need to group by multiple things---year (the categorical version), state, and region
- You need to create two summarized columns in `summarize()` with `sum()` to add the total number of `candidatevotes` and `totalvotes` in each group
- Make a new column named `prop_gop` that is the total number of votes for the candidate divided by the total number of votes in the state/year

::: {.callout-tip title="Answer"}

```{r}
#| label: recreation-data-1
#| message: false

state_prop_gop <- county_results |>
  filter(party == "Republican") |>
  group_by(state, year_cat, region) |>
  summarize(
    candidatevotes_total = sum(candidatevotes),
    totalvotes_total = sum(totalvotes)
  ) |>
  mutate(prop_gop = candidatevotes_total / totalvotes_total)
state_prop_gop
```

:::


## Recreate small multiples plot

Run the code chunk below to see a plot.

```{r fig.path="images/"}
#| label: recreate-me-1
#| fig-width: 8
#| fig-height: 9
#| echo: false
#| ref.label: recreation-plot-1
```

Use `ggplot()` in the chunk below to re-create the plot above using the `state_prop_gop` dataset.

**Some hints!**

- Because you're using `year_cat`, ggplot won't want to draw lines across the categories. Add `aes(group = state)` to `geom_line()` to let it draw lines across the years
- There's a thin (`linewidth = 0.5`) dotted red horizontal line at 50% (or 0.5). Add this with `geom_hline()` (check the documentation!)
- Use 6 columns with `facet_wrap()`
- Use free y axis scales with `facet_wrap()` (check the documentation!)
- Because there are 51 states here, you'll need to adjust `fig-width` and `fig-height` in the chunk options so that it's visible when you render it. The recreation plot above is 8 inches wide and 9 inches tall
- This plot uses `theme_light(base_size = 9)` with some other theme adjustments, like a rotated x-axis

::: {.callout-tip title="Answer"}

```{r}
#| label: recreation-plot-1
#| fig-width: 8
#| fig-height: 9

ggplot(state_prop_gop, aes(x = year_cat, y = prop_gop)) +
  geom_point() +
  geom_line(aes(group = state)) +
  geom_hline(yintercept = 0.5, linewidth = 0.5, color = "red") +
  scale_y_continuous(labels = scales::label_percent()) +
  facet_wrap(vars(state), ncol = 5, scales = "free_y") +
  theme_light(base_size = 9) +
  theme(
    panel.grid.minor = element_blank(),
    strip.text = element_text(face = "bold"),
    axis.text.x = element_text(angle = 90)
  )
```

:::

## Interpret small multiples plot

What does this plot tell us? In a paragraph, explore a story in this plot. Which states are solidly Republican? Solidly Democratic? Which states have been shifting over time? What's up with Utah in 2016?

Do that here.


# Task 3: *Shelby County v. Holder* and polling places

## Data description

Sections 4 and 5 of the Voting Rights Act of 1965 required that some states and counties obtain federal preclearance before making any changes in any policies related to voting. The preclearance requirement was based on historical patterns in racial discrimination---generally speaking, if a state or county engaged in racial discrimination when setting voting policy, it was subject to preclearance rules. @fig-preclearance shows all the counties that faced these requirements:

![All counties that were subject to preclearance requirements under the Voting Rights Act of 1965](`{r} shelby_map_png`){#fig-preclearance width="100%"}

In 2013 in *Shelby County v. Holder*, the United States Supreme Court [struck down Section 4(b)](https://en.wikipedia.org/wiki/Shelby_County_v._Holder) of the Voting Rights Act, which essentially invalidated the preclearance rule and opened the door for all counties to set voting policies without federal oversight. This was done based on the rationale that the US had made sufficient racial progress and that preclearance processes were no longer necessary.

In a dissent joined by Stephen Breyer, Sonia Sotomayor, and Elena Kagan, Ruth Bader Ginsburg [criticized the ruling](https://supreme.justia.com/cases/federal/us/570/529/#tab-opinion-1970751), saying that

> [t]hrowing out preclearance when it has worked and is continuing to work to stop discriminatory changes is like throwing away your umbrella in a rainstorm because you are not getting wet.

In this exercise you'll compare the number of polling places in counties compare the number of polling places in preclearance counties before and after *Shelby*. The data comes from a 2019 report published by The Leadership Conference on Civil and Human Rights, [*Democracy Diverted: Polling Place Closures and the Right to Vote*](https://civilrights.org/democracy-diverted/). 

The data contains 16 columns:

| Variable               | Definition                                            |
|:-----------------------|:------------------------------------------------------|
| `geoid`                | US Census county ID                                   |
| `county`               | County name                                           |
| `state_po`             | State postal abbreviation                             |
| `year`                 | Year                                                  |
| `polling_places`       | Number of polling places in the county                |
| `period`               | Before vs. after the 2013 *Shelby* ruling             |
| `population`           | Population in 2019                                    |
| `prop_high_poverty`    | Proportion of residents facing high poverty[^poverty] |
| `prop_hispanic_latino` | Proportion Hispanic or Latino                         |
| `prop_2more`           | Proportion two or more races                          |
| `prop_aian`            | Proportion American Indian and Alaska Native          |
| `prop_asian`           | Proportion Asian                                      |
| `prop_black`           | Proportion Black or African American                  |
| `prop_nhpi`            | Proportion Native Hawaiian and Pacific Islander       |
| `prop_other`           | Proportion some other race                            |
| `prop_white`           | Proportion white                                      |

[^poverty]: The Leadership Conference on Civil and Human Rights defines this as the proportion for whom the ratio of income to the federal poverty level is less than 2.

The code below loads and cleans and merges the data. **You don't need to modify anything in this chunk of code---you only need to run it.**

```{r}
#| label: load-shelby-data
#| warning: false
#| message: false

shelby_counties <- shelby_counties |>
  # Make sure that "Before" comes before "After" in plots
  mutate(period = factor(period, levels = c("Before", "After"))) |>
  # Remove states with only a handful of counties
  filter(!(state_po %in% c("CA", "FL", "SD", "NY")))
```

## Recreate data summary

The data currently has two rows for each county---one for before and one for after the *Shelby* decision. We want to compare state-level counts of polling places, so we need to collapse the data a bit.

Use `group_by()` and `summarize()` to create a data frame named `state_totals` that looks like this:

```{r}
#| label: recreate-me-data-2
#| echo: false
#| message: false
#| ref.label: recreation-data-2
```

::: {.callout-tip title="Answer"}

```{r}
#| label: recreation-data-2
#| message: false

state_totals <- shelby_counties |> 
  group_by(state_po, period) |> 
  summarize(total = sum(polling_places)) |> 
  ungroup()
state_totals
```

:::

## Recreate slopegraph

Run the code chunk below to see a plot.

```{r fig.path="images/"}
#| label: recreate-me-2
#| echo: false
#| ref.label: recreation-plot-2
```

Use `ggplot()` in the chunk below to re-create the slopegraph above using the `state_totals` dataset.

**Some hints!** (beyond the example on the class website, which will be very helpful too):

- Because you're using `period` on the x-axis, ggplot won't want to draw lines across the categories. Add `aes(group = state_po)` to `geom_line()` to let it draw lines across the before/after categories
- You'll need two `geom_text_repel()` layers: one for before that uses a negative `nudge_x` value and one for after that uses a positive `nudge_x` value
- Control the thickness and color of the repel guidelines with `segment.size` and `segment.color`
- Use `seed = 1234` in `geom_text_repel()` to get the same random positioning each time
- This plot uses `theme_minimal()`

::: {.callout-tip title="Answer"}

```{r}
#| label: recreation-plot-2

ggplot(state_totals, aes(x = period, y = total, group = state_po)) +
  geom_line(linewidth = 1.5) +
  geom_text_repel(
    data = filter(state_totals, period == "Before"),
    aes(label = state_po),
    direction = "y",
    nudge_x = -0.5,
    segment.size = 0.25,
    seed = 1234
  ) +
  geom_text_repel(
    data = filter(state_totals, period == "After"),
    aes(label = state_po),
    direction = "y",
    nudge_x = 0.5,
    seed = 1234
  ) +
  theme_minimal()
```

:::

## Interpret slopegraph

What does this slopegraph tell us? In a paragraph, explore a story in this plot. Which states saw the biggest changes?

Do that here.

## Recreate data summary

One way to compare changes is to calculate the percent change between two periods. You can calculate percent change with this formula:

$$
\text{percent change} = \frac{\text{new} - \text{old}}{\text{old}}
$$

You can remember this as **NOO**, for "**n**ew minus **o**ld divided by **o**ld." If a state had 500 polling places before and 400 polling places after, that's a decrease of 100 polling places. The percent change would be 400 (new) - 500 (old) divided by 500 (old), or $\frac{400 - 500}{500}$, or -0.2, or a 20% decrease.

Percent changes are helpful because they make it easier to compare units with different amounts. See the slopegraph above where Texas is all alone at the top because it has so many polling places (since it's so big)---Texas closing 700 polling places is not the same as Georgia closing 700 polling places. Comparing the percent changes between states gives a different view of the changes in polling place counts before and after *Shelby*.

The data currently has two rows for each county---one for before and one for after the *Shelby* decision. To calculate the percent change, we need two *columns* for the counts---one for before and one for after. This will let us do something like `mutate(pct_change = (After - Before) / Before)` to make a column for each state's percent change.

Use `pivot_wider()` and `mutate()` on the `state_totals` data frame you made earlier to create a new data frame named  `pct_changes` that looks like this:

```{r}
#| label: recreate-me-data-3
#| echo: false
#| message: false
#| ref.label: recreation-data-3
```

::: {.callout-tip title="Answer"}

```{r}
#| label: recreation-data-3
#| message: false

pct_changes <- state_totals |> 
  pivot_wider(names_from = period, values_from = total) |> 
  mutate(pct_change = (After - Before) / Before)
pct_changes
```

:::

## Recreate percent change plot

Run the code chunk below to see a plot.

```{r fig.path="images/"}
#| label: recreate-me-3
#| echo: false
#| ref.label: recreation-plot-3
```

Use `ggplot()` in the chunk below to re-create the plot above using the `pct_changes` dataset.

**Some hints!**

- Make sure you order the states in the x-axis by percent change. There's a huge hint in the plot, since I purposely left the ugly axis label. You can also look at your work in Exercise 6 where you reordered categories for your lollipop chart.
- Format the y-axis labels as percents. See the Week 6 FAQs at the course website for how.
- This plot uses `theme_minimal()`

::: {.callout-tip title="Answer"}

```{r}
#| label: recreation-plot-3

ggplot(pct_changes, aes(x = fct_reorder(state_po, pct_change), y = pct_change)) + 
  geom_col() + 
  scale_y_continuous(labels = scales::label_percent()) +
  theme_minimal()
```

:::

## Interpret percent change plot

What does this plot tell us? In a paragraph, explore a story in this plot. Which states saw the biggest changes?

Do that here.
