Use the left/right arrow keys to cycle between maps, spacebar to pause/resume the slideshow, or click on an individual state on the bottom bar to navigate directly to its map. These maps were created using the Wistia colorblind-friendly heatmap pallete and the base map provided at: https://www.mapchart.net/usa.html
These state-level heatmaps show the sources of individual contributions (from contributors who have donated over $200 to candidate committees during this election cycle and/or over $200 to PACs and campaign committees during the given calendar year) made directly to the primary campaign committees for the congressional candidates (both winners and losers for both senate and congressional representative races) for the specified states during the 2022 campaign season, based on FEC data retrieved on 11-14-2022 from https://www.fec.gov/data/browse-data/?tab=bulk-data.
Note that these contributions represent only a fraction of the total contributions made to these candidates, and the overall distribution of the campaign finance sources may differ drastically from the distribution of this particular subset of donations (especially since both indirect donations through PACs and small contributions are excluded from this data set). As such, these maps are meant more to prompt questions about the influence of some states on the elections of other states' representatives as opposed to providing definitive answers. Also, note that the color key varies by map, with the "maximum" color representing the largest amount donated from any single state to the target state for that particular map.
Just as transparency is critical in campaign financing, it is also critical when providing statistics about campaign financing, so we will detail how exactly the numbers shown in the above maps were calculated and provide the code used to process the data sets.
Two input data files, both downloaded from the FEC site on 11-14-2022, were used. The first, the 'contributions by individuals' file for 2021-2022 (itcont.txt), contains information about individual contributions made to political committees by individuals who either (1) have donated over $200 to candidate committees during this election cycle or (2) have donated over $200 to PACs and campaign committees during the given calendar year (https://www.fec.gov/campaign-finance-data/contributions-individuals-file-description/). The second, the 'candidate master' for 2021-2022 (cn.txt), contains information about candidates themselves, notably their primary campaign committee and the state they represent (https://www.fec.gov/campaign-finance-data/candidate-master-file-description/).
So, to calculate how much of the individual contributions for candidates in each state came from each state, we did the following:
The code used to process the itcont.txt file and produce camp_dons_by_state.txt (step (1) above) is below. Note that it writes to stdout, so if this code was in a file named process_itcont.py and we wanted to output the results to camp_dons_by_state.txt, then we might run the below command from the command line:
python process_itcont.py > camp_dons_by_state.txt
Code for process_itcont.py:
campaign_contrib_cnts = {}
campaign_contri_amts = {}
states = {'AK':0, 'AL':1,'AR':2,'AS':3,'AZ':4,'CA':5,'CO':6,'CT':7,'DC':8,'DE':9,'FL':10,'GA':11,'GU':12,'HI':13, \
'IA':14,'ID':15,'IL':16,'IN':17,'KS':18,'KY':19,'LA':20,'MA':21,'MD':22,'ME':23,'MI':24,'MN':25,'MO':26,'MS':27, \
'MT':28,'NC':29,'ND':30,'NE':31,'NH':32,'NJ':33,'NM':34,'NV':35,'NY':36,'OH':37,'OK':38,'OR':39,'PA':40,'PR':41, \
'RI':42,'SC':43,'SD':44,'TN':45,'TX':46,'UT':47,'VA':48,'VI':49,'VT':50,'WA':51,'WI':52,'WV':53,'WY':54,'MP':55, \
'US':56, 'ZZ':57
}
with open('itcont.txt') as read_file:
for line in read_file:
vals = [value for value in line.split('|')]
if(vals[18] == 'X'):
continue #memo code indicates not to be included in itemization total, so skip
if(vals[3][1:5] != '2022'):
continue #wrong election cycle, so skip
if(vals[9]) == '':
vals[9] = 'ZZ' #vals[9] is state; assign blanks to ZZ
elif(vals[9] not in states):
vals[9] = 'ZZ' #assign invalid states to ZZ
if vals[0] in campaign_contrib_cnts:
campaign_contrib_cnts[vals[0]][states[vals[9]]] += 1 #add to existing committee's counts
campaign_contri_amts[vals[0]][states[vals[9]]] += float(vals[14]) #add contribution dollar amount
else:
campaign_contrib_cnts[vals[0]] = [0 for n in range(0,58)] #create committee record, then add count
campaign_contrib_cnts[vals[0]][states[vals[9]]] += 1
campaign_contri_amts[vals[0]] = [0.0 for n in range(0,58)]
campaign_contri_amts[vals[0]][states[vals[9]]] += float(vals[14])
# print output to file
for camp in campaign_contrib_cnts:
for key in states:
print('{0}|{1}|{2}|{3}'.format(camp,key,campaign_contrib_cnts[camp][states[key]],format(campaign_contri_amts[camp][states[key]])))
The Pandas code for steps (2)-(4) is below. If this code was placed in a file called state_donations.py, then it could be executed from the command line using the below command:
python state_donations.py
Code for state_donations.py:
import pandas as pd
cand_mast = pd.read_csv('cn.txt',sep='|',names=['CAND_ID','CAND_NAME','CAND_PTY_AFFILIATION', \
'CAND_ELECTION_YR','CAND_OFFICE_ST', 'CAND_OFFICE','CAND_OFFICE_DISTRICT','CAND_ICI', \
'CAND_STATUS','CAND_PCC','CAND_ST1','CAND_ST2','CAND_CITY','CAND_ST','CAND_ZIP'], \
error_bad_lines=False)
don_by_state = pd.read_csv('camp_dons_by_state.txt',sep='|',names=['CMTE_ID','STATE','NUM_DONS','AMT'],error_bad_lines=False)
cand_sources = pd.merge(cand_mast, don_by_state,how='left',left_on='CAND_PCC',right_on='CMTE_ID',suffixes=['_cands', '_dons'])
output = cand_sources.groupby(['CAND_OFFICE_ST','STATE'])['AMT'].sum()
output.to_csv('state_donation_sources_breakdown.csv')
Want to download the resultant state_donation_sources_breakdown.csv file used to assign the state colors for the various heat maps above? It is available for download here:
Feel free to reuse this data either with or without attribution, but please be upfront and clear about how it is calculated and what it represents.