Political Action Committees (PACs) and their opaque cousins Super-PACS are the tip of the special interest sphere. They funnel money directly to candidates, and that money greases the skids of electoral politics. Who are they, and what do they want? This post examines PACs that contribute to House of Representatives Races, looking specifically at Jan to June, 2016. We find that:

library(ggplot2)
library(scales)
library(readr)
library(dplyr)
library(tidyr)
library(tibble)

A bit of quick data loading

In the interest of transparency and reproducibility, here is the script to process donations from PACs to candidates. Throughout the post, click “code” to see my raw code. See the Appendix (Getting the data) for details on my pre-processing in Spark.

dfraw <- as_tibble(jsonlite::fromJSON("C:/tmp/pac_data.json"))
dfraw <- dfraw %>% select(DONOR_COMMITTEE_FEC_ID, DONOR_CANDIDATE_FEC_ID, FILER_COMMITTEE_ID_NUMBER, NumContributions=`count(CONTRIBUTION_AMOUNT_{F3L_Bundled})`, TotalContributions=`sum(CONTRIBUTION_AMOUNT_{F3L_Bundled})`)

# Load candidates from json.
candidates <- as_tibble(jsonlite::fromJSON("C:/tmp/candidate.json"))
candidates <- candidates %>% filter(CAND_PCC != "")  # the rest have to be small!

committees <- as_tibble(jsonlite::fromJSON("C:/tmp/committee.json"))
committees <- committees %>% select(CMTE_NM, CMTE_ID)
df_with_candidate <- dfraw %>% left_join(candidates, by=c("FILER_COMMITTEE_ID_NUMBER" = "CAND_PCC"))
df_with_candidate <- df_with_candidate %>% select(-CAND_ELECTION_YR, -CAND_ST1, -CAND_ST2, -CAND_ZIP)

df_with_committee <- df_with_candidate %>% left_join(committees, by=c("DONOR_COMMITTEE_FEC_ID" = "CMTE_ID"))

I’m going to focus on the two major parties, as they represent the overwhelming majority of money donated:

df_total_by_party <- df_with_committee %>% 
  filter(CAND_OFFICE == "H") %>%
  group_by(CAND_PTY_AFFILIATION, CAND_OFFICE) %>% 
  summarise(TotalContributions=sum(TotalContributions), NumContributions=sum(NumContributions)) %>% 
  arrange(desc(NumContributions))  %>%
  select(-CAND_OFFICE)

ggplot(df_total_by_party, aes(x=reorder(CAND_PTY_AFFILIATION, -NumContributions), y=NumContributions)) + 
  geom_bar(stat="identity") +
  ggtitle("Number of Contributions from PACs to each Party's House Races") + 
  xlab("Party")

Open question for later: are PACS more likely to donate to the two major parties even than individuals?

For the data nerds, this is how I filtered to the two major parties and re-organized my data to show the contributions to each party.

df <- df_with_committee %>% 
  filter((CAND_PTY_AFFILIATION == "DEM") | (CAND_PTY_AFFILIATION == "REP"))

# Sum up the contributions
byPartyAndStatus <- df %>% 
  group_by(CAND_PTY_AFFILIATION, CAND_OFFICE, DONOR_COMMITTEE_FEC_ID, CMTE_NM) %>% 
  summarise(TotalContributions=sum(TotalContributions), NumContributions=sum(NumContributions))

# Produce a pivot with two set rows: donor and recid
byPartyAndStatusNarrow <- byPartyAndStatus %>% 
  tidyr::unite(Donor, DONOR_COMMITTEE_FEC_ID, CMTE_NM, sep = "__") %>%
  select(-NumContributions)

byPartyAndStatusWide <- byPartyAndStatusNarrow %>% 
  tidyr::spread(key = CAND_PTY_AFFILIATION, value=TotalContributions, fill = 0) %>% 
  separate(Donor, into=c("FEC_ID", "CMTE_NM"), sep = "__") %>% 
  mutate(TotalContributions=DEM + REP) %>%
  mutate(DemShare=DEM / TotalContributions) %>%
  arrange(desc(TotalContributions)) %>%
  filter(FEC_ID != "")

# Filter to House races.
houseRacesOnly <- byPartyAndStatusWide %>% filter(CAND_OFFICE == "H") %>% filter(FEC_ID != 'NA') %>%
  mutate(CumulativeContributions=cumsum(TotalContributions)) %>%
  mutate(Index=row_number(desc(TotalContributions)))
  
houseRacesOnly <- houseRacesOnly %>%
  mutate(CumulativeContributions=CumulativeContributions / max(houseRacesOnly$CumulativeContributions))

What Motivates a Donation?

As this plot shows, there are two classes of political donation: the fiercely partisan and the rest. Partisan donors give to candidates from only one political party: they represent the tall bars on the extremes of the plot. These donors clearly have an agenda and they give accordingly (though most of the money is actually flowing from party leadership). What about the rest? As we’ll see, most of the money is donated by organizations whose donation patterns largely follow the party makeup of the House. They are giving to everyone, and they are giving in vast quantities.

houseRacesOnly$Committee <- c(
  head(houseRacesOnly$CMTE_NM, 6), 
  replicate(nrow(houseRacesOnly) - 6, "Other")
)

cbPalette <- c("blue", "red", "#E69F00", "#56B4E9", "#aaaaaa", "#0072B2", "red")
plt <- ggplot(houseRacesOnly, aes(x=DemShare, weights=TotalContributions, fill=Committee)) + 
  geom_histogram(aes(y=..count..), bins = 30, alpha=.66) + 
  scale_fill_manual(values=cbPalette, guide=guide_legend(ncol = 2)) +
  geom_vline(aes(xintercept=0.43)) +
  theme(legend.position="bottom") + 
  theme(legend.text = element_text(size=6)) +
  annotate("text", x = .55, y = 6000000, label = "Current Makeup of House") +
  ylab("Amount of Money") +
  scale_y_continuous(labels = comma) + theme(axis.text.y=element_text(angle=45)) +
  xlab("Percent of PAC's money going to Democrats") + 
  ggtitle("PAC money in House Races by Partisanship of the PAC")

show(plt)

Maintaining Party Loyalty: the Partisans (40% of the total)

The two peaks at the extremes of the x-axis represent money that comes from PACs that donate exclusively to Republicans or exclusively to Democrats. I’ll define partisan PACs as any PAC that gives more than \(95\%\) of its contributions one only one party. They clearly form a big part of the contribution landscape.

houseRacesOnly <- houseRacesOnly %>% mutate(IsPartisan=ifelse(DemShare > 0.95, "Dem", ifelse(DemShare < 0.05, "Rep", "None")))

houseRacesOnlyForPlot <- houseRacesOnly %>% group_by(IsPartisan) %>% summarise(TotalContributions=sum(TotalContributions))

ggplot(houseRacesOnlyForPlot, aes(x=IsPartisan, weight=TotalContributions, fill=IsPartisan)) + geom_bar()

The two parties are reasonably well-matched, and in total represent 40% of the total money from PACs. What’s strange is that the Democrat’s funding is far more consolidated. Nearly half of all the partisan Democratic PAC money comes from ACTBLUE.

houseRacesDems <- houseRacesOnly %>% filter(IsPartisan == "Dem") %>% ungroup %>% select(CMTE_NM, TotalContributions)

houseRacesDems <- houseRacesDems %>% 
  mutate(PercentOfDistributions=100 * TotalContributions / sum(TotalContributions))

knitr::kable(head(houseRacesDems, 10))
CMTE_NM TotalContributions PercentOfDistributions
ACTBLUE 16602531.5 47.043070
INTERNATIONAL BROTHERHOOD OF ELECTRICAL WORKERS POLITICAL ACTION COMMITTEE 821750.0 2.328419
CROWLEY LEADERSHIP FUND 806779.5 2.286000
JSTREETPAC 647980.2 1.836044
MACHINISTS NON PARTISAN POLITICAL LEAGUE OF THE INTERNATIONAL ASSOCIATION OF MACHINISTS & AEROSPACE WORKERS 643500.0 1.823349
AMERICAN ASSOCIATION FOR JUSTICE POLITICAL ACTION COMMITTEE (AAJ PAC) 603000.0 1.708593
EMILY’S LIST 561753.3 1.591721
MODICA FOR SENATE 540361.3 1.531107
SHEET METAL WORKERS’ INTERNATIONAL ASSOCIATION POLITICAL ACTION LEAGUE 533000.0 1.510249
UNITE HERE TIP CAMPAIGN COMMITTEE 503700.0 1.427228

By contract, the Republicans still have a dominant PAC (Team Ryan, controlled by Speaker Paul Ryan) but this PAC only contributes \(20%\) of the total.

houseRacesReps <- houseRacesOnly %>% filter(IsPartisan == "Rep") %>% ungroup %>% select(CMTE_NM, TotalContributions)

houseRacesReps <- houseRacesReps %>% 
  mutate(PercentOfDistributions=100 * TotalContributions / sum(TotalContributions))

knitr::kable(head(houseRacesReps, 10))
CMTE_NM TotalContributions PercentOfDistributions
TEAM RYAN 7410744.9 20.115638
SCALISE LEADERSHIP FUND 2617593.6 7.105165
MCCARTHY VICTORY FUND 1607115.7 4.362336
MAJORITY COMMITTEE PAC–MC PAC 945000.0 2.565097
RYAN-MCCARTHY VICTORY 864673.9 2.347060
NA 575000.0 1.560773
MCMORRIS RODGERS AMERICAN DREAM PROJECT; THE 530308.0 1.439462
KOCH INDUSTRIES INC POLITICAL ACTION COMMITTEE (KOCHPAC) 487500.0 1.323264
PROSPERITY ACTION INC. 425690.9 1.155490
THE EYE OF THE TIGER POLITICAL ACTION COMMITTEE 411000.0 1.115614

There are a few other differences of note:

Generally, of the \(40%\) of money that is flowing from partisan or idealogical organizations to House candidates, the parties collect roughly equal amounts and the money is heavily dominated by Party interests.

Buying Influence: The Interests (60% of the total)

At first glance, here are the top ten contributors that donate to candidates in both political parties.

houseRacesOtherForTable <- houseRacesOnly %>% ungroup %>% 
  filter(IsPartisan == "None") %>% 
  select(CMTE_NM, TotalContributions, DemShare)
knitr::kable(head(houseRacesOtherForTable, 10))
CMTE_NM TotalContributions DemShare
HONEYWELL INTERNATIONAL POLITICAL ACTION COMMITTEE 1083418 0.3765860
NATIONAL ASSOCIATION OF REALTORS POLITICAL ACTION COMMITTEE 1037100 0.4315881
NATIONAL BEER WHOLESALERS ASSOCIATION POLITICAL ACTION COMMITTEE 1004000 0.3984064
LOCKHEED MARTIN CORPORATION EMPLOYEES’ POLITICAL ACTION COMMITTEE 981500 0.3352012
AT&T INC. FEDERAL POLITICAL ACTION COMMITTEE (AT&T FEDERAL PAC) 946250 0.3846764
THE BOEING COMPANY POLITICAL ACTION COMMITTEE 875000 0.3971429
EMPLOYEES OF NORTHROP GRUMMAN CORPORATION PAC 868900 0.3595350
NATIONAL ASSOCIATION OF INSURANCE AND FINANCIAL ADVISORS POLITICAL ACTION COMM 847600 0.2825625
CULAC THE PAC OF CREDIT UNION NATIONAL ASSOCIATION 844500 0.5056246
AMERICAN CRYSTAL SUGAR COMPANY POLITICAL ACTION COMMITTEE 786000 0.4243003

Honeywell is a major conglomerate that produces, among other things, the Drones, Missiles, and Rockets for the US Military. GovermentSpending.us lists $2.8 billion dollars in government contracts. That’s probably worth a million in contributions to the branch of the government that is responsible for appropriations! Similarly Boeing, Lockheed Martin and Northrup Grumman are major contractors for the Federal government (particularly the military) and all contribute heavily to both parties.

Some of the other top entries are interesting: realtors, beer, and telcos represent highly regulated industries. The sugar lobby is interesting: sugar is a highly protected industry in the US with high tariffs for imports.

The unfortunate pattern here is that everyone donating to both parties seems to have a clearly articulatable relationship with the federal government. They either directly benefit from federal spending or they have a business interest in regulatory programs.

Just how much money are Influence Buyers Spending?

So far, we’ve identified that \(60\%\) or just over $100 million dollars of PAC money contributed to House candidates is contributed by organizations that spread their money across both parties. At least for the top donors, there is a good case to make that they benefit financially from the decisions of the people they are funding. However, this is only telling part of the story: many of these PACs contribute to political PACs as well!

For the curious, here is the code to identify contributions from influence buyers to partisan PACs. I treat each partisan PAC as a “mixer” that doesn’t channel specific donations to specific recipients. This may not be true but the internal routing is rather opaque. I aggregate house and total contributions for each political loyalty PAC and use that to compute the “house”House Ratio“. For instance, the Walden for Congress donated $2000 to House Candidates (namely, Greg Walden (OR) himself), but it also donated $2000 to a Senate candidate. Therefore, we count only \(50\%\) of other PAC contributions to Walden for Congress as contributed to House Races. The National Association of Broadcasters PAC donated $56,000 to Walden for Congress (out of $459950 total donated) and we count $28,000 of that as a contribution to House candidates via a partisan PAC.

#
# Figure out how much money is given to other pacs.
#

houseRacesOther <- houseRacesOnly %>% ungroup %>% 
  filter(IsPartisan == "None") %>% 
  select(FEC_ID, CMTE_NM, Committee, TotalContributions, DemShare) %>%
  mutate(PercentOfContributions=100 * TotalContributions / sum(TotalContributions))

houseRacesPartisan <- houseRacesOnly %>% filter(IsPartisan != "Other") %>% select(FEC_ID, IsPartisan)
## Adding missing grouping variables: `CAND_OFFICE`
fromOtherPACToPartisan <- df_with_committee %>% 
  inner_join(houseRacesPartisan, by=c("FILER_COMMITTEE_ID_NUMBER" = "FEC_ID")) %>%
  select(DONOR_COMMITTEE_FEC_ID, FILER_COMMITTEE_ID_NUMBER, NumContributions, TotalContributions, IsPartisan) %>%
  semi_join(houseRacesOther, by=c("DONOR_COMMITTEE_FEC_ID" = "FEC_ID"))


fromOtherPACToPartisan %>% summarize(sum(TotalContributions))
fromOtherPACToPartisan %>% group_by(IsPartisan) %>% summarize(sum(TotalContributions))

fromOtherPACToPartisanAgg <- fromOtherPACToPartisan %>% 
  group_by(DONOR_COMMITTEE_FEC_ID, FILER_COMMITTEE_ID_NUMBER, IsPartisan) %>%
  summarize(TotalContributions=sum(TotalContributions))

fromOtherPACToPartisanAgg %>% ungroup %>% summarize(sum(TotalContributions))
fromOtherPACToPartisanAgg %>% group_by(IsPartisan) %>% summarize(sum(TotalContributions))


# try to get the percent spent on House races.
contributionsByIdOffice <- byPartyAndStatusWide %>% 
  semi_join(houseRacesPartisan, by="FEC_ID") %>%
  group_by(FEC_ID) %>%
  summarize(TotalContributions=sum(TotalContributions))

contributionsByIdOffice %>% ungroup %>% summarize(sum(TotalContributions))

contributionsByIdHouse <- byPartyAndStatusWide %>% 
  filter(CAND_OFFICE == "H") %>%
  semi_join(houseRacesPartisan, by="FEC_ID") %>%
  group_by(FEC_ID) %>%
  summarize(HouseContributions=sum(TotalContributions))

contributionsByIdHouse %>% ungroup %>% summarize(sum(HouseContributions))

  
contributionHouseRatio <- contributionsByIdOffice %>% 
  left_join(contributionsByIdHouse) %>%
  mutate(HouseRatio=HouseContributions / TotalContributions)
## Joining, by = "FEC_ID"
contributionHouseRatio %>% ungroup %>% summarize(sum(HouseContributions))

contributionHouseRatio %>% filter(HouseRatio < 1)

contributionHouseRatio <- select(contributionHouseRatio, FEC_ID, HouseRatio)

fromOtherPACToPartisanHouseOnly <- fromOtherPACToPartisanAgg %>% 
  inner_join(contributionHouseRatio, by=c("FILER_COMMITTEE_ID_NUMBER" = "FEC_ID")) %>%
  mutate(HouseContributions=TotalContributions * HouseRatio)

#fromOtherPACToPartisanHouseOnly %>% ungroup %>% summarize(sum(HouseContributions))

# Get biggest PAC contributors 
PACToPartisanBySize <- fromOtherPACToPartisanHouseOnly %>% 
  group_by(DONOR_COMMITTEE_FEC_ID, IsPartisan) %>% summarize(TotalContributions=sum(HouseContributions)) %>%
  arrange(desc(TotalContributions)) %>% 
  spread(IsPartisan, value=TotalContributions) %>% select(-None) %>% 
  mutate(TotalContributions=Rep+Dem)
  
PACKey <- houseRacesOther %>% select(FEC_ID, CMTE_NM)

PACToPartisanBySizeAgg <- PACToPartisanBySize %>% ungroup %>%
  left_join(PACKey, by=c("DONOR_COMMITTEE_FEC_ID" = "FEC_ID")) %>%
  select(CommitteeName=CMTE_NM, Dem, Rep, TotalContributions) %>% 
  mutate(DemShare=Dem/TotalContributions) %>%
  arrange(desc(TotalContributions))


#knitr::kable(PACToPartisanBySizeAgg)


plt <- ggplot(PACToPartisanBySizeAgg, aes(x=DemShare, weights=TotalContributions)) + 
  geom_histogram(aes(y=..count..), bins = 30, alpha=.66) + 
#  scale_fill_manual(values=cbPalette, guide=guide_legend(ncol = 2)) +
  geom_vline(aes(xintercept=0.43)) +
  theme(legend.position="bottom") + 
  theme(legend.text = element_text(size=6)) +
  annotate("text", x = .55, y = 6000000, label = "Current Makeup of House") +
  ylab("Amount of Money") +
  scale_y_continuous(labels = comma) + theme(axis.text.y=element_text(angle=45)) +
  xlab("Percent of Influencer PAC's money going to Democratic PACs") + 
  ggtitle("Influence Buying PAC to Political PAC Transfers")

show(plt)
## Warning: Removed 231 rows containing non-finite values (stat_bin).

So what do we learn? PACs labeled as “other” PACs in our data set contributed $123 million to PACs listed as Partisan PACs in our data set in addition to the $107 million contributed to candidates directly! And who are the largest donors?

# Get biggest PAC contributors 
PACToPartisanBySize <- fromOtherPACToPartisanHouseOnly %>% 
  group_by(DONOR_COMMITTEE_FEC_ID) %>% summarize(TotalContributions=sum(HouseContributions)) %>%
  arrange(desc(TotalContributions))
  

# Get biggest PAC contributors 
PACToPartisanBySize <- fromOtherPACToPartisanHouseOnly %>% 
  group_by(DONOR_COMMITTEE_FEC_ID) %>% summarize(TotalContributions=sum(HouseContributions)) %>%
  arrange(desc(TotalContributions))
  
PACKey <- houseRacesOther %>% select(FEC_ID, CMTE_NM)

knitr::kable(head(PACToPartisanBySize %>% 
  left_join(PACKey, by=c("DONOR_COMMITTEE_FEC_ID" = "FEC_ID")) %>%
  select(`Committee Name`=CMTE_NM, TotalContributions), 10))
Committee Name TotalContributions
HONEYWELL INTERNATIONAL POLITICAL ACTION COMMITTEE 1806130.5
AT&T INC. FEDERAL POLITICAL ACTION COMMITTEE (AT&T FEDERAL PAC) 1311702.7
NATIONAL ASSOCIATION OF REALTORS POLITICAL ACTION COMMITTEE 1141234.3
LOCKHEED MARTIN CORPORATION EMPLOYEES’ POLITICAL ACTION COMMITTEE 1110515.1
COMCAST CORPORATION & NBCUNIVERSAL POLITICAL ACTION COMMITTEE - FEDERAL 1028960.3
THE HOME DEPOT INC. POLITICAL ACTION COMMITTEE 1000877.6
EMPLOYEES OF NORTHROP GRUMMAN CORPORATION PAC 982785.8
PRICEWATERHOUSECOOPERS POLITICAL ACTION COMMITTEE I 972640.3
NATIONAL BEER WHOLESALERS ASSOCIATION POLITICAL ACTION COMMITTEE 902358.0
DELOITTE POLITICAL ACTION COMMITTEE 887852.1

Conclusion: Does it Matter?

Lol, I just run the numbers. That being said, I’m a little concerned. Honeywell’s donations to PACs and candidates for the House races alone are equivalent to the maximum donation for over 88,000 individual contributors. Moreover, their ability to concentrate funds, to give to every single person on a committee or to the give extensively to the party leadership who then dispense funds within their parties implies a considerable ability to influence policy. When a company receives millions or even billions in direct contracts from the government and then spends millions to influence the very people setting the budget, I think that’s concerning.

Getting the data

The first step in my data collection uses OpenFEC to collect all transactions to registered Committees and PACs in the federal elections.

I filtered to transactions from registered PACs and Party Organizations by filtering to SA11B and SA11C transactions in Spark using the script found here.

Appendix

This is the python program I used to convert my 1-dict-per-line committee and candidate lists into actual JSON blobs suitable for loading into R.

import sys

f = open(sys.argv[1], 'r').read()
f = f.split("\n")
f = [ff for ff in f if ff]
ff = ','.join(f)
print('[' + ff + ']')