I first map the landscape of U.S. institutions by type and size and what published costs look like across regions then examine how net price changes with family income, how graduates’ earnings compare with state high-school benchmarks across regions, and finally which Mid East schools offer the highest estimated return on investment (ROI).
The data come from the U.S. Department of Education’s College Scorecard, in a cleaned version published in August 2026 on Kaggle (https://www.kaggle.com/datasets/mariashatrova/u-s-college-value-analysis-ready-data/data). Each row is one institution (1,934 in total). I focus on enrollment size, institution type, published and net prices, region, family-income brackets, earnings advantage over the state high-school benchmark, and ROI.
Net price and earnings figures are reported for students who received federal aid, and for public schools, the numbers are for in-state students only.
Missing values were read in as ‘NA’, and each chart drops those rows as needed. Of the available 174 fields, these charts focus on 16 columns with some highlighted below.
I excluded the regions of ‘Outlying Areas’ and ‘U.S. Service Schools’ from all assessments because there was very little data for these regions. This dropped the total number of schools from 1,934 to 1,888.
### Libraries
library(dplyr)
library(tidyverse)
library(ggplot2)
library(scales)
library(plotly)
library(leaflet)
### Data
file_path = "C:/Users/geroc/OneDrive - Loyola University Maryland/2026-08 Data Vis/R Project/college_value_tableau_ready.csv"
df = read.csv("C:/Users/geroc/OneDrive - Loyola University Maryland/2026-08 Data Vis/R Project/college_value_tableau_ready.csv", na.strings = c("NA", "")) %>%
filter(!region_label %in% c("Outlying Areas", "U.S. Service Schools"))
df %>%
select(INSTNM, institution_control_label, region_label, undergraduate_enrollment_size_band,
net_price_overall, earnings_median_4yr_primary, earnings_advantage_pct_vs_state) %>%
arrange(desc(earnings_median_4yr_primary)) %>% head(5)
## INSTNM institution_control_label region_label
## 1 Massachusetts Institute of Technology Private nonprofit New England
## 2 California Institute of Technology Private nonprofit Far West
## 3 Samuel Merritt University Private nonprofit Far West
## 4 Harvey Mudd College Private nonprofit Far West
## 5 Stanford University Private nonprofit Far West
## undergraduate_enrollment_size_band net_price_overall
## 1 1,000 to 4,999 20111
## 2 Fewer than 1,000 16075
## 3 Fewer than 1,000 NA
## 4 Fewer than 1,000 35924
## 5 5,000 to 9,999 13807
## earnings_median_4yr_primary earnings_advantage_pct_vs_state
## 1 161961 2.946034
## 2 152843 3.133573
## 3 148182 3.007518
## 4 137100 2.707810
## 5 136959 2.703997
The charts show that value depends less on a school’s sticker price than on who is paying, geography, and institution type.
Public, private nonprofit, and private for-profit schools differ in number and size. The inner ring shows institution type and the outer ring shows undergraduate enrollment bands. Private nonprofit makes up the largest share (62.0%), and 1,000 to 4,999 students is the most common enrollment size (40.1%).
pie_df = df %>%
filter(
!is.na(institution_control_label),
!is.na(undergraduate_enrollment_size_band)) %>%
data.frame
sizeband = c('Fewer than 1,000', '1,000 to 4,999', '5,000 to 9,999', '10,000 to 19,999', '20,000 or more')
ring1 = pie_df %>% dplyr::count(institution_control_label) %>%
arrange(desc(institution_control_label))
ring2 = pie_df %>% dplyr::count(undergraduate_enrollment_size_band) %>%
mutate(undergraduate_enrollment_size_band =
factor(undergraduate_enrollment_size_band, levels = sizeband)) %>%
arrange(undergraduate_enrollment_size_band)
type_cols = c("Public" = "#008cba",
"Private nonprofit" = "#f1c400",
"Private for-profit" = "#6a3d9a")
size_cols = c("Fewer than 1,000" = "#1b3a57",
"1,000 to 4,999" = "#5aa02c",
"5,000 to 9,999" = "#b9a3d6",
"10,000 to 19,999" = "#8a9199",
"20,000 or more" = "#c2185b")
p1 = plot_ly(hole = 0.65) %>%
layout(title = "U.S. College Landscape") %>%
add_pie(data = ring2,
labels = ~undergraduate_enrollment_size_band,
values = ~n,
sort = FALSE,
marker = list(colors = unname(size_cols[ring2$undergraduate_enrollment_size_band])),
textposition = "inside",
hovertemplate = "Undergraduate Enrollment: %{label}<br>Number of institutions in size band: %{value}<br>Percent: %{percent}<br><extra></extra>",
legendgroup = "undergraduate_enrollment_size_band",
legendgrouptitle = list(text = "Enrollment size")) %>%
add_pie(data = ring1,
labels = ~institution_control_label,
values = ~n,
sort = FALSE,
marker = list(colors = unname(type_cols[ring1$institution_control_label])),
textposition = "inside",
hovertemplate = "Number of %{label} institutions: %{value}<br>Percent: %{percent}<extra></extra>",
legendgroup = "institution_control_label",
legendgrouptitle = list(text = "Institution Type"),
domain = list(x = c(0.19, 0.81), y = c(0.19, 0.81)))
p1
Net price rises with family income, but private for-profit schools are the flattest. For families earning $0 to $30K, private for-profit schools average $27,812 while public schools average $10,346 for in-state students.
avg_price_income_by_control = df %>%
group_by(institution_control_label) %>%
summarise(across(c(net_price_income_0_30k,
net_price_income_30_48k,
net_price_income_48_75k,
net_price_income_75_110k,
net_price_income_over_110k),
~ mean(.x, na.rm = TRUE))) %>%
data.frame
public = avg_price_income_by_control %>%
filter(institution_control_label == "Public")
public_df = data.frame(income_bracket = c("$0-30K", "$30-48K", "$48-75K", "$75-110K", "Over $110K"),
avg_net_price = c(public$net_price_income_0_30k,
public$net_price_income_30_48k,
public$net_price_income_48_75k,
public$net_price_income_75_110k,
public$net_price_income_over_110k),
institution_control_label = "Public")
nonprofit = avg_price_income_by_control %>%
filter(institution_control_label == "Private nonprofit")
nonprofit_df = data.frame(income_bracket = c("$0-30K", "$30-48K", "$48-75K", "$75-110K", "Over $110K"),
avg_net_price = c(nonprofit$net_price_income_0_30k,
nonprofit$net_price_income_30_48k,
nonprofit$net_price_income_48_75k,
nonprofit$net_price_income_75_110k,
nonprofit$net_price_income_over_110k),
institution_control_label = "Private nonprofit")
forprofit = avg_price_income_by_control %>%
filter(institution_control_label == "Private for-profit")
forprofit_df = data.frame(income_bracket = c("$0-30K", "$30-48K", "$48-75K", "$75-110K", "Over $110K"),
avg_net_price = c(forprofit$net_price_income_0_30k,
forprofit$net_price_income_30_48k,
forprofit$net_price_income_48_75k,
forprofit$net_price_income_75_110k,
forprofit$net_price_income_over_110k),
institution_control_label = "Private for-profit")
avg_price_stacked = rbind(forprofit_df, nonprofit_df, public_df)
p2 = ggplot(avg_price_stacked, aes(x=income_bracket, y=avg_net_price,
color = institution_control_label,
group = institution_control_label)) +
geom_line(linewidth = 1) +
geom_point(shape = 16, size = 3) +
labs(x = "Income Brackets", y = "Average Net Price",
title = "Average Net Price by Income Bracket") +
scale_y_continuous(labels = scales::dollar) +
theme_light() +
theme(plot.title = element_text(face = "bold", hjust=0.5, margin = margin(0, 0, 15, 0)),
plot.margin = unit(c(25, 25, 25, 25), "pt"),
axis.title.y = element_text(face = "bold", margin = margin(0, 15, 0, 0)),
axis.title.x = element_text(face = "bold", margin = margin(15, 0, 5, 0)),
legend.position = "bottom",
legend.title = element_text(size = 10, face = "bold"),
legend.text = element_text(size = 10)) +
scale_color_manual(values = c("Public" = "#008cba",
"Private nonprofit" = "#f1c400",
"Private for-profit" = "#6a3d9a"),
guide = guide_legend(title = "Institution Type", nrow = 1, override.aes = list(size = 5)))
p2
Published college costs vary widely across the country. Each panel below groups states by region, and each bar shows the average published cost per year for institutions in that state.
Average yearly costs are highest in New England, where states such as Vermont and Massachusetts exceed $60,000, and lowest in the Rocky Mountains and Southwest, where averages are closer to $32,000 to $33,000. Costs also vary within regions: in the Mid East, for example, D.C. is roughly $34,000 more expensive than Delaware.
Note that these are published prices, not what students actually pay after grants and scholarships. Additionally, several states have a small number of schools:regions = c('Far West', 'Southwest', 'Rocky Mountains', 'Plains', 'Great Lakes', 'New England', 'Mid East', 'Southeast')
state_avg_cost = df %>%
select(STABBR, region_label, net_price_overall) %>%
group_by(region_label, STABBR) %>%
summarise(avg_cost = mean(net_price_overall, na.rm = TRUE),
.groups = "drop") %>%
group_by(region_label) %>%
mutate(region_avg = mean(avg_cost)) %>%
ungroup() %>%
mutate(region_label = reorder(region_label, -region_avg)) %>%
data.frame
region_colors = c('#008cba', '#f1c400', '#6a3d9a', '#1b3a57',
'#5aa02c', '#c2185b','#b9a3d6', '#8a9199')
p3 = ggplot(state_avg_cost, aes(x = reorder(STABBR, -avg_cost),
y = avg_cost, fill = region_label)) +
geom_bar(stat = "identity") +
labs(title = "Net Costs by Region & State",
x="State", y="Average Net Cost per Year") +
theme_light() +
theme(plot.title = element_text(hjust = 0.5, margin = margin(0, 0, 15, 0)),
plot.margin = unit(c(25, 25, 25, 25), "pt"),
axis.title.y = element_text(face = "bold", margin = margin(0, 15, 0, 0)),
axis.title.x = element_text(face = "bold", margin = margin(15, 0, 0, 0)),
legend.position = "none") +
scale_y_continuous(labels = scales::dollar,
breaks = seq(0, 70000, by = 10000)) +
scale_fill_manual(values = region_colors) +
facet_wrap(~region_label, scales = "free_x", ncol=3, nrow=3)
p3
Each tile shows the average earnings advantage over the average state high-school benchmark for a region and enrollment size. Darker tiles mean a bigger advantage.
The Far West (83.1%) leads slightly over the Mid East (82.4%) while the Rocky Mountains region trails (62.7%).
Note that some regions have very few schools in certain size bands:sizeband = c('Fewer than 1,000', '1,000 to 4,999', '5,000 to 9,999', '10,000 to 19,999', '20,000 or more')
regions = c('Far West', 'Southwest', 'Rocky Mountains', 'Plains', 'Great Lakes', 'New England', 'Mid East', 'Southeast')
earnings_adv_per = df %>%
select(earnings_advantage_pct_vs_state, region_label, undergraduate_enrollment_size_band) %>%
filter(undergraduate_enrollment_size_band %in% sizeband,
!is.na(earnings_advantage_pct_vs_state)) %>%
group_by(region_label, undergraduate_enrollment_size_band) %>%
summarise(n = n(),
earnings_advantage_pct_vs_state = mean(earnings_advantage_pct_vs_state),
.groups = "drop") %>%
mutate(region_label = factor(region_label, levels = regions),
undergraduate_enrollment_size_band = factor(undergraduate_enrollment_size_band,
levels = sizeband)) %>%
data.frame
p4 = ggplot(earnings_adv_per, aes(x = region_label,
y = undergraduate_enrollment_size_band,
fill = earnings_advantage_pct_vs_state,
text = paste0("Region: ", region_label,
"<br>Enrollment: ", undergraduate_enrollment_size_band,
"<br>Number of schools: ", n))) +
geom_tile(color = "black") +
geom_text(aes(label = percent(earnings_advantage_pct_vs_state, accuracy = 0.1))) +
labs(title = "Earnings Advantage over State High School Benchmarks",
x = "U.S. Region",
y = "Undergraduate Enrollment",
fill = "Earnings Advantage") +
theme_minimal() +
scale_fill_gradient(low = "#d6f1f9", high = "#008cba",
limits = c(0.4, 1.2),
labels = percent,
oob = scales::squish) +
theme(plot.title = element_text(hjust = 0.5, face = "bold", margin = margin(0, 0, 2, 0)),
axis.title.x = element_text(face = "bold", margin = margin(15, 0, 0, 0)),
axis.title.y = element_text(face = "bold", margin = margin(0, 10, 0, 0)),
legend.title = element_text(hjust = 0.5, face = "bold", margin = margin(0, 0, 15, 0)))
ggplotly(p4, tooltip = "text") %>%
layout(xaxis = list(tickangle = -45)) %>%
style(hoverlabel = list(bgcolor="white"))
Within the U.S. Mid East, what students pay depends on both where the school is and what type it is. In Pennsylvania, New York, and Maryland, public schools average well below private nonprofits; the gap is largest in New York ($12,771 vs. $23,206). New Jersey is the exception, where public schools average slightly more than nonprofit schools ($19,929 vs. $17,738). D.C.’s private nonprofits are the most expensive group ($32,084).
States are ordered by average net price across all schools, and numbers above each bar show the count of schools; Delaware, D.C., and the for-profit bars have very few schools.
type_cols = c("Public" = "#008cba",
"Private nonprofit" = "#f1c400",
"Private for-profit" = "#6a3d9a")
mid_east = df %>%
filter(!is.na(net_price_overall),
region_label == "Mid East")
state_order = mid_east %>%
group_by(STABBR) %>%
summarise(avg_all = mean(net_price_overall)) %>%
arrange(desc(avg_all)) %>%
pull(STABBR)
state_type_price = mid_east %>%
group_by(STABBR, institution_control_label) %>%
summarise(n = n(),
avg_net_price = mean(net_price_overall),
.groups = "drop") %>%
complete(STABBR, institution_control_label, fill = list(n = 0)) %>% # keeps bar slots aligned
mutate(STABBR = factor(STABBR, levels = state_order),
institution_control_label = factor(institution_control_label,
levels = names(type_cols))) %>%
data.frame
state_bands = data.frame(idx = seq_along(state_order)) %>%
filter(idx %% 2 == 0)
p5 = ggplot(state_type_price, aes(x = STABBR, y = avg_net_price,
fill = institution_control_label)) +
geom_rect(data = state_bands,
aes(xmin = idx - 0.5, xmax = idx + 0.5, ymin = -Inf, ymax = Inf),
fill = "#f0f0f0", alpha = 0.6, inherit.aes = FALSE) +
geom_col(position = position_dodge(width = 0.7), width = 0.65, na.rm = TRUE) +
geom_text(aes(label = ifelse(n == 0, "", n)),
position = position_dodge(width = 0.7),
vjust = -0.5, size = 3, na.rm = TRUE) +
labs(title = "Average Net Price of Mid East Schools by State and Institution Type",
x = "State", y = "Average Net Price per Year",
fill = "Institution Type") +
theme_light() +
theme(plot.title = element_text(hjust = 0.5, face = "bold", margin = margin(0, 0, 15, 0)),
plot.margin = unit(c(25, 25, 25, 25), "pt"),
axis.title.y = element_text(face = "bold", margin = margin(0, 15, 0, 0)),
axis.title.x = element_text(face = "bold", margin = margin(15, 0, 0, 0)),
legend.title = element_text(hjust = 0.5)) +
scale_fill_manual(values = type_cols) +
scale_y_continuous(labels = scales::dollar,
expand = expansion(mult = c(0, 0.1)))
p5
ROI is calculated as:
(10 years of earnings advantage over the
state high school benchmark minus 4 years of net price) divided by 4
years of net price.
This excludes consideration for loan
interest and students who do not finish. The ROI analysis keeps only
schools with more than 1,000 undergraduates and a positive net
price.
The 50 top-ranked schools range from 379% to 3,774%, and New York dominates: 26 of the 50 are in the state, including 11 City University of New York (CUNY) campuses. CUNY schools combine very low net prices (roughly $3,000 to $6,000 a year) with substantial earnings advantages. Schools such as Johns Hopkins (#12) and Columbia (#14) earn larger advantages in dollars but rank below the CUNY campuses because they cost more. Princeton (#3) is an the exception since its net price is only $6,128, compared to an average of about $22,000 in the Mid East.
None of the top 50 are for-profit institutions (31 public, 19 private nonprofit).
roi_df = df %>%
filter(!is.na(earnings_advantage_vs_state),
!is.na(net_price_overall),
net_price_overall > 0,
undergraduate_enrollment_primary > 1000,
region_label == "Mid East") %>%
mutate(total_cost = net_price_overall * 4,
benefit = earnings_advantage_vs_state * 10,
roi = (benefit - total_cost) / total_cost)
roi_map = roi_df %>%
select(INSTNM, institution_control_label, undergraduate_enrollment_primary,
LATITUDE, LONGITUDE, roi)
top50 = roi_map %>% slice_max(roi, n = 50, with_ties = FALSE) %>% mutate(group = "Top 50")
cirpal = colorFactor(palette = c("#008cba", "#f1c400", "#6a3d9a"),
levels = c("Public", "Private nonprofit", "Private for-profit"))
p6 = leaflet(top50) %>%
addProviderTiles(providers$Esri.WorldGrayCanvas) %>%
setView(lng = -76, lat = 40.75, zoom = 6.25) %>%
addCircles(lng = ~LONGITUDE,lat = ~LATITUDE,
fillColor = ~cirpal(institution_control_label),
color = ~cirpal(institution_control_label),
weight = 1.5,
opacity = 1,
fillOpacity = .5,
popup = ~paste0(INSTNM, "<br>",
institution_control_label, "<br>",
"Enrollment: ", scales::comma(undergraduate_enrollment_primary), "<br>",
"ROI: ", scales::percent(roi, accuracy = 1)),
label = ~INSTNM,
radius = ~sqrt(roi) * 1500) %>%
addLegend(colors = c("#008cba", "#f1c400", "#6a3d9a"),
labels = c("Public", "Private nonprofit", "Private for-profit"),
title = "50 Mid East Colleges<br>with Top ROI",
position = "bottomright") %>%
addControl(html = "<div style='background: rgba(255,255,255,0.85); padding: 6px 10px;
font-size: 10px; max-width: 300px;'>
Click each circle for more information.<br>Circle size represents ROI. </div>",
position = "bottomleft")
p6
The analyzed data provide answers to the questions outlined at the start.
Who can afford college? College costs depend heavily on income and school type. Families earning under $30K pay about $10K a year at public schools, $19K at private nonprofits, and $28K at for-profits, an amount close to a lowest-bracket family’s whole annual income. Net price rises with income everywhere, but public prices more than double across the brackets while for-profit prices rise only about 25%, so for-profits are the least responsive to a family’s ability to pay.
Is geography a factor? It changes earnings more than it changes value. The Far West and Mid East show the largest earnings advantage (about 82–83% on average), while the Rocky Mountains and Plains show the smallest (63–65%). The type of school you attend is a greater factor than the region it is in.
Is college worth the money? For most schools, yes. Graduates, on average, earn more than the typical high-school graduate in their state. Worth depends on what you pay, though: ROI at the median public school is roughly 2 times what it is at the median private nonprofit because public schools cost far less for a similar earnings advantage.
Does institution type matter? Yes, more than any other factor in this analysis. Across all institutions, the average net price is about $15,000 at public schools, $24,000 at private nonprofits, and $30,000 at for-profits. Public schools earn a similar earnings advantage over state high-school benchmarks, so students get more for their money there: the median public school’s ROI is about 325%, compared with 148% at nonprofits. For-profit graduates show the largest earnings advantage (a median of about 87%), but their high prices erase much of it, leaving a median ROI of 181%.
What does “best value” look like? Best value is not the lowest price or the highest earnings alone. In the Mid East, it is a large, low-cost public school. CUNY campuses combine net prices of about $3,000 to $6,000 with substantial earnings advantages, and 11 of them appear in the top 50 ROI schools. A few selective private schools such as Princeton (#3) also rank high, because aid keeps their net price low.
Note: