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.


How These Maps Were Generated


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:


  1. For each campaign committee present in itcont.txt, find the sum of contributions for each state that (a) are marked as being for the 2022 election cycle and (b) do not have a memo code of 'X', which indicates that this contribution should not be included in the itemization total. Write these sums to a text file, camp_dons_by_state.txt, which contains one line for each committee-state combination. Note that the 'STATE' column in itcont.txt is used to determine the source state for the contribution, the 'TRANSACTION_AMT' column is used to get the amount of these contributions, and the 'CMTE_ID' column is used to identify the committee receiving the contributions.

  2. Using the Pandas library, read both the newly created camp_dons_by_state.txt file and the cn.txt file into dataframes (don_by_state and cand_mast, respectively).

  3. Join the don_by_state and cand_mast dataframes using the 'CAND_PCC' (or candidate primary campaign committee) column from cand_mast and the 'CMTE_ID' (or committee ID) column from don_by_state. This will give us a dataframe containing the total value of contributions sourced from each state made directly to each candidate's primary campaign committee.

  4. Finally, group by the 'CAND_OFFICE_ST' and 'STATE' columns and take the sum of the 'AMT' column, which will give the sum of contributions from each source state ('STATE') for candidates in each state ('CAND_OFFICE_ST'), and write the results to a CSV, state_donation_sources_breakdown.csv, which can be used to assign each state's color in the maps.

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')


Download the Data


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.