Equity and Bias in IIJA Funding Allocation

An Analysis of Population and Political Influence

Ariba Mandavia

Equity and Bias in IIJA Funding Allocation

Introduction

The Infrastructure Investment and Jobs Act (IIJA) represents a historic investment in the infrastructure of the United States, aiming to address transportation, utilities, broadband, and more. With billions of dollars allocated across states and territories, questions of equity and potential political bias in the distribution of funds arise. This analysis seeks to answer two critical questions:

  1. Is the allocation equitable based on the population of each state or territory?
  2. Does the allocation favor the political interests of the Biden administration?

Using population data, IIJA funding allocation figures, and presidential election results, we aim to provide data-driven insights into the fairness and motivations of the funding distribution.

Methodology

Data Sources

  1. IIJA Funding Data: Details the funding allocation (in billions) for each state and territory.
  2. Population Data: Current and estimated population data for 2023.
  3. Presidential Election Results: State-level results from the 2020 presidential election.

Approach

  1. Data cleaning and merging were conducted to create a unified dataset.
  2. Funding per capita was calculated to assess equity in funding allocation relative to population size.
  3. Funding distribution was analyzed against voting patterns to explore potential political bias.
  4. Two visualizations were created:
    • Scatter plot: Funding vs. Population.
    • Bar plot: Funding by political affiliation (based on election results).

Results

1. Equity Analysis: Funding vs. Population

Visualization: The scatter plot below shows the relationship between state populations and their corresponding IIJA funding allocations. States with smaller populations often receive disproportionately higher funding per capita, indicating disparities in the distribution strategy.

Insights: - Larger states like California and Texas receive lower funding per capita despite their significant population sizes. - Smaller states and territories, such as Delaware, benefit from higher funding per capita.

2. Political Bias Analysis

Visualization: The bar plot below examines funding allocation by states’ voting patterns in the 2020 presidential election. States with higher votes for President Biden are compared against those with fewer votes.

Insights: - Funding distribution does not conclusively indicate overt political favoritism. - However, some trends show states with higher votes for the Biden administration receiving relatively favorable allocations.

Conclusion

The analysis reveals disparities in the per-capita allocation of IIJA funding, suggesting inequities in the distribution process. Smaller states and territories often benefit more on a per-capita basis, potentially due to baseline funding guarantees or other strategic priorities. While there is no definitive evidence of political bias, certain trends hint at possible alignments with the Biden administration’s interests.

Recommendations:

  1. Introduce a transparent framework for equitable funding distribution based on population size and infrastructure needs.
  2. Conduct further analysis, incorporating additional factors such as regional economic conditions and infrastructure requirements.

Appendix

Code and Data Processing

The full Python code for data cleaning, merging, and visualization is included in the attached notebook.

Visualizations

  1. Scatter plot: Funding vs. Population.
  2. Bar plot: Funding by Political Bias.

Big-Idea Summary

“The allocation of IIJA funding reveals disparities in per-capita distributions across states, raising questions about equity and potential political bias in the funding strategy.”

!pip install pandas matplotlib seaborn

Infrastructure Funding Analysis

This notebook analyzes the equity and political implications of IIJA funding allocations.

Load IIJA funding data

import pandas as pd
iija_data = pd.read_excel("IIJA FUNDING AS OF MARCH 2023.xlsx")
iija_data.head()
State, Teritory or Tribal Nation Total (Billions)
0 ALABAMA 3.0000
1 ALASKA 3.7000
2 AMERICAN SAMOA 0.0686
3 ARIZONA 3.5000
4 ARKANSAS 2.8000
import pandas as pd
# Check column names and data types
iija_data.info()

# Basic statistics
iija_data.describe()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 57 entries, 0 to 56
Data columns (total 2 columns):
 #   Column                            Non-Null Count  Dtype  
---  ------                            --------------  -----  
 0   State, Teritory or Tribal Nation  57 non-null     object 
 1   Total (Billions)                  57 non-null     float64
dtypes: float64(1), object(1)
memory usage: 1.0+ KB
Total (Billions)
count 57.000000
mean 3.439165
std 3.293913
min 0.068600
25% 1.300000
50% 2.700000
75% 3.900000
max 18.400000

Load governors_county.csv data

governors_data = pd.read_csv("governors_county.csv")
governors_data.head()
state county current_votes total_votes percent
0 Delaware Kent County 85415 87025 100
1 Delaware New Castle County 280039 287633 100
2 Delaware Sussex County 127181 129352 100
3 Indiana Adams County 14154 14209 100
4 Indiana Allen County 168312 169082 100

Load state abbreviations data

state_abbreviations = pd.read_csv("state-abbreviations-2024.csv")
state_abbreviations.head()
state Abbreviation
0 Alabama AL
1 Alaska AK
2 Arizona AZ
3 Arkansas AR
4 California CA

Load population data from PopulationData.csv

population_data = pd.read_csv("PopulationData.csv")
print("Columns in iija_data:", iija_data.columns)
print("Columns in governors_data:", governors_data.columns)
print("Columns in state_abbreviations:", state_abbreviations.columns)
print("Columns in population_data:", population_data.columns)
Columns in iija_data: Index(['State, Teritory or Tribal Nation', 'Total (Billions)'], dtype='object')
Columns in governors_data: Index(['state', 'county', 'current_votes', 'total_votes', 'percent'], dtype='object')
Columns in state_abbreviations: Index(['state', 'State_Abbreviation'], dtype='object')
Columns in population_data: Index(['fips', 'state', 'densityMi', 'pop2024', 'pop2023', 'pop2020',
       'pop2019', 'pop2010', 'growthRate', 'growth', 'growthSince2010', 'area',
       'rank', 'percent'],
      dtype='object')

Rename columns for consistency


iija_data.rename(columns={'State, Teritory or Tribal Nation': 'State', 'Total (Billions)': 'Funding Allocation'}, inplace=True)
population_data.rename(columns={'state': 'State', 'pop2023': 'Population'}, inplace=True)
print("Unique states in iija_data:", iija_data['State'].unique())
print("Unique states in population_data:", population_data['State'].unique())
Unique states in iija_data: ['ALABAMA' 'ALASKA' 'AMERICAN SAMOA' 'ARIZONA' 'ARKANSAS' 'CALIFORNIA'
 'COLORADO' 'CONNECTICUT' 'DELEWARE' 'DISTRICT OF COLUMBIA' 'FLORIDA'
 'GEORGIA' 'GUAM' 'HAWAII' 'IDAHO' 'ILLINOIS' 'INDIANA' 'IOWA' 'KANSAS'
 'KENTUCKY' 'LOUISIANA' 'MAINE' 'MARYLAND' 'MASSACHUSETTS' 'MICHIGAN'
 'MINNESOTA' 'MISSISSIPPI' 'MISSOURI' 'MONTANA' 'NEBRASKA' 'NEVADA'
 'NEW HAMPSHIRE' 'NEW JERSEY' 'NEW MEXICO' 'NEW YORK' 'NORTH CAROLINA'
 'NORTH DAKOTA' 'NORTHERN MARIANA ISLANDS' 'OHIO' 'OKLAHOMA' 'OREGON'
 'PENNSYLVANIA' 'PUERTO RICO' 'RHODE ISLAND' 'SOUTH CAROLINA'
 'SOUTH DAKOTA' 'TENNESSEE' 'TEXAS' 'TRIBAL COMMUNITIES'
 'US VIRGIN ISLANDS' 'UTAH' 'VERMONT' 'VIRGINIA' 'WASHINGTON'
 'WEST VIRGINIA' 'WISCONSIN' 'WYOMING']
Unique states in population_data: ['California' 'Texas' 'Florida' 'New York' 'Pennsylvania' 'Illinois'
 'Ohio' 'Georgia' 'North Carolina' 'Michigan' 'New Jersey' 'Virginia'
 'Washington' 'Arizona' 'Tennessee' 'Massachusetts' 'Indiana' 'Missouri'
 'Maryland' 'Wisconsin' 'Colorado' 'Minnesota' 'South Carolina' 'Alabama'
 'Louisiana' 'Kentucky' 'Oregon' 'Oklahoma' 'Connecticut' 'Utah' 'Iowa'
 'Nevada' 'Arkansas' 'Kansas' 'Mississippi' 'New Mexico' 'Idaho'
 'Nebraska' 'West Virginia' 'Hawaii' 'New Hampshire' 'Maine' 'Montana'
 'Rhode Island' 'Delaware' 'South Dakota' 'North Dakota' 'Alaska'
 'Vermont' 'Wyoming']

iija_data['State'] = iija_data['State'].str.strip().str.title()
population_data['State'] = population_data['State'].str.strip().str.title()
state_abbreviations['state'] = state_abbreviations['state'].str.strip().str.title()
missing_in_population = set(iija_data['State']) - set(population_data['State'])
missing_in_iija = set(population_data['State']) - set(iija_data['State'])

print("States missing in population data:", missing_in_population)
print("States missing in iija data:", missing_in_iija)
States missing in population data: {'District Of Columbia', 'Deleware', 'Guam', 'American Samoa', 'Puerto Rico', 'Northern Mariana Islands', 'Us Virgin Islands', 'Tribal Communities'}
States missing in iija data: {'Delaware'}
iija_data['State'] = iija_data['State'].replace({
    'Deleware': 'Delaware'
})
iija_data = iija_data[~iija_data['State'].isin([
    'Guam', 'American Samoa', 'Puerto Rico', 
    'Northern Mariana Islands', 'Us Virgin Islands', 'Tribal Communities'
])]
missing_in_population = set(iija_data['State']) - set(population_data['State'])
missing_in_iija = set(population_data['State']) - set(iija_data['State'])

print("States missing in population data after cleaning:", missing_in_population)
print("States missing in iija data after cleaning:", missing_in_iija)
States missing in population data after cleaning: {'District Of Columbia'}
States missing in iija data after cleaning: set()

Merge Data

merged_data = pd.merge(iija_data, population_data[['State', 'Population']], on='State', how='inner')
merged_data = pd.merge(merged_data, state_abbreviations, left_on='State', right_on='state', how='left')
merged_data['Funding Per Capita'] = merged_data['Funding Allocation'] * 1e9 / merged_data['Population']

print(merged_data.head())  # Confirm that the merge is successful
        State  Funding Allocation  Population       state State_Abbreviation  \
0     Alabama                 3.0     5108468     Alabama                 AL   
1      Alaska                 3.7      733406      Alaska                 AK   
2     Arizona                 3.5     7431344     Arizona                 AZ   
3    Arkansas                 2.8     3067732    Arkansas                 AR   
4  California                18.4    38965193  California                 CA   

   Funding Per Capita  
0          587.260212  
1         5044.954636  
2          470.978063  
3          912.726405  
4          472.216319  

Visualization: Funding vs Population


import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(10, 6))
sns.scatterplot(data=merged_data, x='Population', y='Funding Allocation', hue='Funding Per Capita', size='Funding Per Capita', sizes=(20, 200))
plt.title('IIJA Funding Allocation vs Population by State')
plt.xlabel('Population')
plt.ylabel('Funding Allocation (Billions)')
plt.legend(title='Funding Per Capita')
plt.show()

png

Standardize state names


merged_data['State'] = merged_data['State'].str.strip().str.title()
governors_data['State'] = governors_data['State'].str.strip().str.title()

# Check for missing states
missing_states = set(merged_data['State']) - set(governors_data['State'])
print("States in merged_data but not in governors_data:", missing_states)

# Exclude missing states if necessary
merged_data = merged_data[~merged_data['State'].isin(missing_states)]

# Re-run the merge
final_data = pd.merge(merged_data, governors_data[['State', 'current_votes']], on='State', how='inner')

# Debugging final_data
if final_data.empty:
    print("final_data is still empty. Check for remaining mismatches or missing data.")
else:
    print("final_data preview:\n", final_data.head())
common_states = set(merged_data['State']) & set(governors_data['State'])
filtered_merged_data = merged_data[merged_data['State'].isin(common_states)]

# Re-run the merge
final_data = pd.merge(filtered_merged_data, governors_data[['State', 'current_votes']], on='State', how='inner')
if final_data.empty:
    print("final_data is still empty. Check if filtering is too restrictive.")
else:
    print("final_data preview:\n", final_data.head())
final_data preview:
       State  Funding Allocation  Population     state State_Abbreviation  \
0  Delaware               0.792     1031890  Delaware                 DE   
1  Delaware               0.792     1031890  Delaware                 DE   
2  Delaware               0.792     1031890  Delaware                 DE   
3   Indiana               3.400     6862199   Indiana                 IN   
4   Indiana               3.400     6862199   Indiana                 IN   

   Funding Per Capita  current_votes  
0          767.523670          85415  
1          767.523670         280039  
2          767.523670         127181  
3          495.467998          14154  
4          495.467998         168312  

Analysis: Funding by total votes (proxy for political affiliation)


party_funding = final_data.groupby('current_votes')['Funding Allocation'].sum().reset_index()
party_funding.rename(columns={'current_votes': 'Votes for Biden'}, inplace=True)

# Visualization: Funding by political bias
if not party_funding.empty:
    plt.figure(figsize=(8, 6))
    sns.barplot(data=party_funding, x='Votes for Biden', y='Funding Allocation', hue='Votes for Biden', palette='coolwarm', dodge=False)
    plt.title('Funding Allocation by Presidential Votes')
    plt.xlabel('Votes for Biden')
    plt.ylabel('Total Funding Allocation (Billions)')
    plt.show()
else:
    print("No data available for visualization.")

png