# Campaign Finance Visualization Script
# This script creates downloadable high-quality plots for campaign finance data
# Load necessary packages
if (!require("ggplot2")) install.packages("ggplot2")
## Loading required package: ggplot2
if (!require("dplyr")) install.packages("dplyr")
## Loading required package: dplyr
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
if (!require("viridis")) install.packages("viridis")
## Loading required package: viridis
## Loading required package: viridisLite
if (!require("scales")) install.packages("scales")
## Loading required package: scales
##
## Attaching package: 'scales'
## The following object is masked from 'package:viridis':
##
## viridis_pal
if (!require("gridExtra")) install.packages("gridExtra")
## Loading required package: gridExtra
##
## Attaching package: 'gridExtra'
## The following object is masked from 'package:dplyr':
##
## combine
library(ggplot2) # For creating plots
library(dplyr) # For data manipulation
library(viridis) # For better color palettes
library(scales) # For scale formatting
library(gridExtra) # For arranging multiple plots
# Function to create and check directory with error handling
create_dir_safely <- function(dir_name) {
tryCatch({
# Check if directory exists, if not try to create it
if (!dir.exists(dir_name)) {
dir.create(dir_name, showWarnings = TRUE, recursive = TRUE)
cat(paste("Created directory:", dir_name, "\n"))
} else {
cat(paste("Directory already exists:", dir_name, "\n"))
}
# Test write permissions
test_file <- file.path(dir_name, "test_write.txt")
writeLines("test", test_file)
unlink(test_file)
cat("Directory is writable.\n")
return(TRUE)
}, error = function(e) {
cat(paste("ERROR: Cannot write to directory", dir_name, "\n"))
cat(paste("Error message:", e$message, "\n"))
cat("Using current working directory instead.\n")
return(FALSE)
})
}
# Try to create plots directory, fall back to current directory if needed
plot_dir <- "campaign_finance_plots"
if (!create_dir_safely(plot_dir)) {
plot_dir <- getwd()
cat(paste("Using current working directory for outputs:", plot_dir, "\n"))
}
## Directory already exists: campaign_finance_plots
## Directory is writable.
# Function for safely saving plots with error handling
safe_save <- function(filename, plot, width, height, dpi = 300) {
full_path <- file.path(plot_dir, filename)
tryCatch({
ggsave(full_path, plot, width = width, height = height, dpi = dpi)
cat(paste("Successfully saved:", full_path, "\n"))
}, error = function(e) {
cat(paste("WARNING: Failed to save", full_path, "\n"))
cat(paste("Error message:", e$message, "\n"))
})
}
# Set global theme for all plots
custom_theme <- theme_minimal(base_size = 14) +
theme(
plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
plot.subtitle = element_text(size = 13, color = "gray30", hjust = 0.5),
axis.title = element_text(face = "bold", size = 14),
axis.text = element_text(size = 12),
legend.title = element_text(face = "bold", size = 13),
legend.text = element_text(size = 12),
panel.grid.major = element_line(color = "gray90"),
panel.grid.minor = element_line(color = "gray95"),
plot.background = element_rect(fill = "white", color = NA),
legend.background = element_rect(fill = "white", color = NA),
plot.margin = margin(20, 20, 20, 20)
)
theme_set(custom_theme)
# Create or load sample data
# For demonstration, we'll create simulated data
set.seed(123)
n <- 500
campaign_data <- data.frame(
receipts = rlnorm(n, meanlog = 10, sdlog = 1),
individual_contributions = rlnorm(n, meanlog = 9.5, sdlog = 1.2),
disbursements = rlnorm(n, meanlog = 9.8, sdlog = 0.9),
party = factor(sample(c("Democrat", "Republican"), n, replace = TRUE)),
incumbency = factor(sample(c("Incumbent", "Challenger", "Open Seat"), n, replace = TRUE))
)
# Log transform variables for better visualization
campaign_data$log_receipts <- log(campaign_data$receipts + 1) # Add 1 to handle zeros
campaign_data$log_individual_contributions <- log(campaign_data$individual_contributions + 1)
campaign_data$log_disbursements <- log(campaign_data$disbursements + 1)
# Calculate summary statistics
finance_summary <- campaign_data %>%
group_by(party, incumbency) %>%
summarize(
mean_receipts = mean(receipts, na.rm = TRUE),
median_receipts = median(receipts, na.rm = TRUE),
mean_individual_contributions = mean(individual_contributions, na.rm = TRUE),
median_individual_contributions = median(individual_contributions, na.rm = TRUE),
mean_disbursements = mean(disbursements, na.rm = TRUE),
median_disbursements = median(disbursements, na.rm = TRUE),
count = n()
)
## `summarise()` has grouped output by 'party'. You can override using the
## `.groups` argument.
# Define consistent colors for parties
party_colors <- c("Democrat" = "#3366FF", "Republican" = "#FF3333")
# 1. Boxplot of receipts by party and incumbency
plot_receipts_boxplot <- function() {
p <- ggplot(campaign_data, aes(x = incumbency, y = receipts, fill = party)) +
geom_boxplot(alpha = 0.8, outlier.shape = 21, outlier.size = 3) +
scale_fill_manual(values = party_colors) +
scale_y_continuous(labels = dollar_format(scale = 1e-3, suffix = "K")) +
labs(title = "Campaign Receipts by Party and Incumbency Status",
subtitle = "Dollar amounts in thousands",
x = "Incumbency Status",
y = "Total Receipts ($)",
fill = "Party") +
theme(axis.text.x = element_text(angle = 0, hjust = 0.5))
# Save the plot
safe_save("receipts_boxplot.png", p, width = 10, height = 7, dpi = 300)
return(p)
}
# 2. Boxplot of log receipts
plot_log_receipts_boxplot <- function() {
p <- ggplot(campaign_data, aes(x = incumbency, y = log_receipts, fill = party)) +
geom_boxplot(alpha = 0.8, outlier.shape = 21, outlier.size = 3) +
scale_fill_manual(values = party_colors) +
labs(title = "Log-Transformed Campaign Receipts",
subtitle = "Natural logarithm of receipts",
x = "Incumbency Status",
y = "Log(Total Receipts)",
fill = "Party") +
theme(axis.text.x = element_text(angle = 0, hjust = 0.5))
# Save the plot
safe_save("log_receipts_boxplot.png", p, width = 10, height = 7, dpi = 300)
return(p)
}
# 3. Boxplot of individual contributions
plot_contributions_boxplot <- function() {
p <- ggplot(campaign_data, aes(x = incumbency, y = individual_contributions, fill = party)) +
geom_boxplot(alpha = 0.8, outlier.shape = 21, outlier.size = 3) +
scale_fill_manual(values = party_colors) +
scale_y_continuous(labels = dollar_format(scale = 1e-3, suffix = "K")) +
labs(title = "Individual Contributions by Party and Incumbency Status",
subtitle = "Dollar amounts in thousands",
x = "Incumbency Status",
y = "Individual Contributions ($)",
fill = "Party") +
theme(axis.text.x = element_text(angle = 0, hjust = 0.5))
# Save the plot
safe_save("contributions_boxplot.png", p, width = 10, height = 7, dpi = 300)
return(p)
}
# 4. Boxplot of disbursements
plot_disbursements_boxplot <- function() {
p <- ggplot(campaign_data, aes(x = incumbency, y = disbursements, fill = party)) +
geom_boxplot(alpha = 0.8, outlier.shape = 21, outlier.size = 3) +
scale_fill_manual(values = party_colors) +
scale_y_continuous(labels = dollar_format(scale = 1e-3, suffix = "K")) +
labs(title = "Campaign Disbursements by Party and Incumbency Status",
subtitle = "Dollar amounts in thousands",
x = "Incumbency Status",
y = "Total Disbursements ($)",
fill = "Party") +
theme(axis.text.x = element_text(angle = 0, hjust = 0.5))
# Save the plot
safe_save("disbursements_boxplot.png", p, width = 10, height = 7, dpi = 300)
return(p)
}
# 1. Bar chart of mean receipts
plot_mean_receipts_bar <- function() {
p <- ggplot(finance_summary, aes(x = incumbency, y = mean_receipts, fill = party)) +
geom_col(position = "dodge", alpha = 0.9) +
geom_text(aes(label = paste0("$", round(mean_receipts/1000, 1), "K")),
position = position_dodge(width = 0.9),
vjust = -0.5, size = 4) +
scale_fill_manual(values = party_colors) +
scale_y_continuous(labels = dollar_format(scale = 1e-3, suffix = "K")) +
labs(title = "Mean Campaign Receipts by Party and Incumbency Status",
subtitle = "Dollar amounts in thousands",
x = "Incumbency Status",
y = "Mean Total Receipts ($)",
fill = "Party") +
theme(axis.text.x = element_text(angle = 0, hjust = 0.5))
# Save the plot
safe_save("mean_receipts_bar.png", p, width = 10, height = 7, dpi = 300)
return(p)
}
# 2. Bar chart of mean individual contributions
plot_mean_contributions_bar <- function() {
p <- ggplot(finance_summary, aes(x = incumbency, y = mean_individual_contributions, fill = party)) +
geom_col(position = "dodge", alpha = 0.9) +
geom_text(aes(label = paste0("$", round(mean_individual_contributions/1000, 1), "K")),
position = position_dodge(width = 0.9),
vjust = -0.5, size = 4) +
scale_fill_manual(values = party_colors) +
scale_y_continuous(labels = dollar_format(scale = 1e-3, suffix = "K")) +
labs(title = "Mean Individual Contributions by Party and Incumbency Status",
subtitle = "Dollar amounts in thousands",
x = "Incumbency Status",
y = "Mean Individual Contributions ($)",
fill = "Party") +
theme(axis.text.x = element_text(angle = 0, hjust = 0.5))
# Save the plot
safe_save("mean_contributions_bar.png", p, width = 10, height = 7, dpi = 300)
return(p)
}
# 3. Bar chart of mean disbursements
plot_mean_disbursements_bar <- function() {
p <- ggplot(finance_summary, aes(x = incumbency, y = mean_disbursements, fill = party)) +
geom_col(position = "dodge", alpha = 0.9) +
geom_text(aes(label = paste0("$", round(mean_disbursements/1000, 1), "K")),
position = position_dodge(width = 0.9),
vjust = -0.5, size = 4) +
scale_fill_manual(values = party_colors) +
scale_y_continuous(labels = dollar_format(scale = 1e-3, suffix = "K")) +
labs(title = "Mean Campaign Disbursements by Party and Incumbency Status",
subtitle = "Dollar amounts in thousands",
x = "Incumbency Status",
y = "Mean Total Disbursements ($)",
fill = "Party") +
theme(axis.text.x = element_text(angle = 0, hjust = 0.5))
# Save the plot
safe_save("mean_disbursements_bar.png", p, width = 10, height = 7, dpi = 300)
return(p)
}
# 1. Density plot of log receipts by party
plot_log_receipts_density <- function() {
p <- ggplot(campaign_data, aes(x = log_receipts, fill = party)) +
geom_density(alpha = 0.6, adjust = 1.5) +
scale_fill_manual(values = party_colors) +
labs(title = "Distribution of Log Receipts by Party",
subtitle = "Natural logarithm of campaign receipts",
x = "Log(Total Receipts)",
y = "Density",
fill = "Party") +
theme(legend.position = "top")
# Save the plot
safe_save("log_receipts_density.png", p, width = 10, height = 7, dpi = 300)
return(p)
}
# 2. Density plot of log individual contributions by party
plot_log_contributions_density <- function() {
p <- ggplot(campaign_data, aes(x = log_individual_contributions, fill = party)) +
geom_density(alpha = 0.6, adjust = 1.5) +
scale_fill_manual(values = party_colors) +
labs(title = "Distribution of Log Individual Contributions by Party",
subtitle = "Natural logarithm of individual contributions",
x = "Log(Individual Contributions)",
y = "Density",
fill = "Party") +
theme(legend.position = "top")
# Save the plot
safe_save("log_contributions_density.png", p, width = 10, height = 7, dpi = 300)
return(p)
}
# 3. Density plot of log disbursements by party
plot_log_disbursements_density <- function() {
p <- ggplot(campaign_data, aes(x = log_disbursements, fill = party)) +
geom_density(alpha = 0.6, adjust = 1.5) +
scale_fill_manual(values = party_colors) +
labs(title = "Distribution of Log Disbursements by Party",
subtitle = "Natural logarithm of campaign disbursements",
x = "Log(Total Disbursements)",
y = "Density",
fill = "Party") +
theme(legend.position = "top")
# Save the plot
safe_save("log_disbursements_density.png", p, width = 10, height = 7, dpi = 300)
return(p)
}
# 4. Faceted density plots by incumbency status
plot_faceted_density <- function() {
p <- ggplot(campaign_data, aes(x = log_receipts, fill = party)) +
geom_density(alpha = 0.6, adjust = 1.5) +
facet_wrap(~ incumbency, ncol = 1) +
scale_fill_manual(values = party_colors) +
labs(title = "Distribution of Log Receipts by Party and Incumbency Status",
subtitle = "Natural logarithm of campaign receipts",
x = "Log(Total Receipts)",
y = "Density",
fill = "Party") +
theme(legend.position = "top")
# Save the plot
safe_save("faceted_density.png", p, width = 10, height = 12, dpi = 300)
return(p)
}
# Add a scatter plot comparing receipts vs disbursements
plot_receipts_vs_disbursements <- function() {
p <- ggplot(campaign_data, aes(x = log_receipts, y = log_disbursements, color = party, shape = incumbency)) +
geom_point(alpha = 0.7, size = 3) +
geom_smooth(aes(group = party), method = "lm", se = TRUE, alpha = 0.2) +
scale_color_manual(values = party_colors) +
labs(title = "Campaign Receipts vs. Disbursements",
subtitle = "Log-transformed values",
x = "Log(Total Receipts)",
y = "Log(Total Disbursements)",
color = "Party",
shape = "Incumbency Status") +
theme(legend.position = "right")
# Save the plot
safe_save("receipts_vs_disbursements.png", p, width = 10, height = 7, dpi = 300)
return(p)
}
# Function to create and save a comprehensive PDF report with error handling
create_pdf_report <- function() {
pdf_file <- file.path(plot_dir, "campaign_finance_report.pdf")
tryCatch({
pdf(pdf_file, width = 11, height = 8.5)
# Title page
plot.new()
title <- "Campaign Finance Analysis by Party and Incumbency Status"
subtitle <- paste("Generated:", format(Sys.time(), "%B %d, %Y"))
text(0.5, 0.6, title, cex = 2, font = 2)
text(0.5, 0.5, subtitle, cex = 1.2)
# Print boxplots - storing and then printing plots to avoid potential issues
p1 <- plot_receipts_boxplot()
p2 <- plot_log_receipts_boxplot()
grid.arrange(p1, p2, ncol = 1, top = "Boxplots of Campaign Receipts")
p3 <- plot_contributions_boxplot()
p4 <- plot_disbursements_boxplot()
grid.arrange(p3, p4, ncol = 1, top = "Boxplots of Contributions and Disbursements")
# Print bar charts
p5 <- plot_mean_receipts_bar()
p6 <- plot_mean_contributions_bar()
p7 <- plot_mean_disbursements_bar()
grid.arrange(p5, p6, p7, ncol = 1, top = "Mean Campaign Finance Values by Category")
# Print density plots
p8 <- plot_log_receipts_density()
p9 <- plot_log_contributions_density()
p10 <- plot_log_disbursements_density()
grid.arrange(p8, p9, p10, ncol = 1, top = "Distributions of Campaign Finance Variables")
p11 <- plot_faceted_density()
p12 <- plot_receipts_vs_disbursements()
print(p11)
print(p12)
dev.off()
cat(paste("Comprehensive report saved to", pdf_file, "\n"))
}, error = function(e) {
# If PDF creation fails, make sure the device is closed
if (dev.cur() > 1) dev.off()
cat(paste("WARNING: Failed to create PDF report:", e$message, "\n"))
cat("Individual PNG files should still be available.\n")
})
}
# Function to save all plots individually as PNG only
save_all_plots_individually <- function() {
cat("\nGenerating and saving all plots as PNG files...\n")
# Generate all plots and save them
plot_receipts_boxplot()
plot_log_receipts_boxplot()
plot_contributions_boxplot()
plot_disbursements_boxplot()
plot_mean_receipts_bar()
plot_mean_contributions_bar()
plot_mean_disbursements_bar()
plot_log_receipts_density()
plot_log_contributions_density()
plot_log_disbursements_density()
plot_faceted_density()
plot_receipts_vs_disbursements()
cat("\nAll plots have been generated and saved as PNG files.\n")
}
# Display system information
cat("System information:\n")
## System information:
cat(paste("R version:", R.version.string, "\n"))
## R version: R version 4.4.3 (2025-02-28 ucrt)
cat(paste("Operating system:", Sys.info()["sysname"], Sys.info()["release"], "\n"))
## Operating system: Windows 10 x64
cat(paste("Working directory:", getwd(), "\n\n"))
## Working directory: C:/Users/lemmi/Downloads
# Save individual plots as PNG files
save_all_plots_individually()
##
## Generating and saving all plots as PNG files...
## Successfully saved: campaign_finance_plots/receipts_boxplot.png
## Successfully saved: campaign_finance_plots/log_receipts_boxplot.png
## Successfully saved: campaign_finance_plots/contributions_boxplot.png
## Successfully saved: campaign_finance_plots/disbursements_boxplot.png
## Successfully saved: campaign_finance_plots/mean_receipts_bar.png
## Successfully saved: campaign_finance_plots/mean_contributions_bar.png
## Successfully saved: campaign_finance_plots/mean_disbursements_bar.png
## Successfully saved: campaign_finance_plots/log_receipts_density.png
## Successfully saved: campaign_finance_plots/log_contributions_density.png
## Successfully saved: campaign_finance_plots/log_disbursements_density.png
## Successfully saved: campaign_finance_plots/faceted_density.png
## `geom_smooth()` using formula = 'y ~ x'
## Warning: The following aesthetics were dropped during statistical transformation: shape.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
## the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
## variable into a factor?
## Successfully saved: campaign_finance_plots/receipts_vs_disbursements.png
##
## All plots have been generated and saved as PNG files.
# Try to create the combined PDF report
cat("\nAttempting to create comprehensive PDF report...\n")
##
## Attempting to create comprehensive PDF report...
create_pdf_report()
## Successfully saved: campaign_finance_plots/receipts_boxplot.png
## Successfully saved: campaign_finance_plots/log_receipts_boxplot.png
## Successfully saved: campaign_finance_plots/contributions_boxplot.png
## Successfully saved: campaign_finance_plots/disbursements_boxplot.png
## Successfully saved: campaign_finance_plots/mean_receipts_bar.png
## Successfully saved: campaign_finance_plots/mean_contributions_bar.png
## Successfully saved: campaign_finance_plots/mean_disbursements_bar.png
## Successfully saved: campaign_finance_plots/log_receipts_density.png
## Successfully saved: campaign_finance_plots/log_contributions_density.png
## Successfully saved: campaign_finance_plots/log_disbursements_density.png
## Successfully saved: campaign_finance_plots/faceted_density.png
## `geom_smooth()` using formula = 'y ~ x'
## Warning: The following aesthetics were dropped during statistical transformation: shape.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
## the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
## variable into a factor?
## Successfully saved: campaign_finance_plots/receipts_vs_disbursements.png
## `geom_smooth()` using formula = 'y ~ x'
## Warning: The following aesthetics were dropped during statistical transformation: shape.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
## the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
## variable into a factor?
## Comprehensive report saved to campaign_finance_plots/campaign_finance_report.pdf
cat("\nScript execution completed.\n")
##
## Script execution completed.