The cost of disability in South Carolina
Exploring which disabilities carry the steepest toll on working life
Introduction
In South Carolina, about one in seven working-age adults reports a disability. We usually talk about that group as a single category: “people with disabilities.” But that one word flattens enormous differences in how disability actually shapes economic life.
This report draws on the U.S. Census Bureau’s 2024 American Community Survey (the Public Use Microdata Sample: roughly 57,000 anonymized South Carolina records, weighted to represent the state) to ask a sharper question: which disabilities carry the steepest toll on working life, and does that toll fall unevenly?
We asked four questions:
- Which disabilities carry the steepest toll on employment?
- Does the disadvantage deepen when disabilities co-occur?
- Does the disadvantage compound by gender?
- Does pay follow the same pattern as employment?
‘Disability’ is not one thing
Our tendency to flatten disabilities into a single plane causes us to miss the nuance behind it. We see a single box checked, a group named. But “disability” covers a person who is deaf, a person who is blind, and a person who can’t dress or bathe without help, and varying levels of each disability. And the economic distance between them is enormous.
So we asked the question: what share of working-age South Carolinians with each kind of disability actually holds a job, and found the single category splinters into a nearly 40-point range.
Because the ACS records each type separately, a person can appear in more than one category; every bar counts everyone who reports that type, measured against people who report none.
At one end, South Carolinians who are deaf or hard of hearing are employed at about 60%, within striking distance of the 79% no-disability baseline. At the other, fewer than one in five people with a self-care disability holds a job. Vision and hearing sit high; self-care and independent-living difficulties sit at the bottom. The takeaway isn’t that disability lowers employment. Everyone knows that. The real finding is how unevenly it does so.
Even further, type is only one axis of that unevenness. The other is how many disabilities a person carries at once. And that’s common: about four in ten working-age South Carolinians with a disability report two or more. Each one a person adds pulls the odds of holding a job further down.
The result is a staircase. About 57% of people with a single disability hold a job; that falls to roughly 35% with two, and to about 20% with three or more. Disability doesn’t only vary in kind; it accumulates.
Leveled in hiring, stacked in pay
Disability, we’ve seen, falls unevenly across types. We asked: does it also fall unevenly across gender? Given the pay gaps between non-disabled men and women, do disabled women pay the steepest price of all? The answer, it turns out, splits in two, and which half you get depends on whether you’re asking who gets hired or who gets paid.
In hiring, the result is almost counterintuitive. Among South Carolinians without a disability, men out-work women by about nine points. Among those with a disability, that gap closes to nothing. Disabled men and women are employed at nearly the same rate, both near 45%. Disability levels the field, but by the cruelest route: it pulls men down to where disabled women already stand.
Median wages for the smallest disability groups are directional, not precise — their confidence intervals are wide and, for medians of clustered wage data, sometimes one-sided.*
Pay is where it flips. The gender gap that vanished in hiring returns in wages, and instead of replacing the disability disadvantage, it stacks on top of it. A disabled woman earns about 61 cents for every dollar a non-disabled man makes.
The gender pay gap, South Carolina (median wage, employed adults 18–64, ACS 2024):
- Non-disabled women earn 80¢ per non-disabled man’s dollar ($40,000 vs $50,000).
- Disabled women earn 76¢ per disabled man’s dollar ($30,500 vs $40,000) — the gender gap is slightly wider among disabled workers.
- Disabled women earn 61¢ per non-disabled man’s dollar ($30,500 vs $50,000) — gender and disability penalties compounding.
We also explored: does the compounding effect we saw earlier where each added disability deepened the disadvantage, land harder on one gender? As it turns out, it doesn’t. The staircase descends at about the same rate for men and women. Co-occurrence isn’t a gendered mechanism; the gender story lives entirely in pay.
Pay: the noisier, secondary story
If employment is where the disability penalty cuts sharpest, pay is where it gets murky. Among those who do hold a job, disabled South Carolinians earn less, but the pattern is looser, the samples thinner, and the signal harder to trust than the employment numbers were.
Self-care sits with a very wide confidence interval (roughly $20,000–$40,000): few people with a self-care disability are employed, so the earner sample is thin — read its estimate as directional.
Pay loosely tracks employment across types, with telling exceptions. Independent-living difficulty is worst on both counts with the lowest median wage, around $22,000, and near-lowest employment. Hearing is best on both, about $42,000 against a $45,000 no-disability baseline. Cognitive disability is the outlier: middling employment but the second-lowest pay, near $30,000 — relatively employable, but poorly paid.
Where employment fell in a clean staircase as disabilities accumulated, pay does not. Median wage drops from about $37,000 with a single disability to $26,000 with two — then edges back up to $28,000 with three or more, the two intervals overlapping. Pay falls once disabilities co-occur, but it doesn’t keep falling. The wage signal, in short, is real but noisy, and for the hardest-hit groups, too thin to count on.
Plotted as individuals rather than medians, the same gap shows in the raw cloud: at nearly every age, disabled earners concentrate lower on the wage scale, and there are far fewer of them to begin with.
The through-line: an employment penalty
We decided to step back and put the two measures side by side. If employment and pay told the same story, the disabilities that keep the most people out of work would also be the ones that pay the least. But they don’t line up.
If we plot each disability type by its employment rate and its median wage, the points scatter instead of lining up. Cognitive disability sits low on pay but mid-pack on employment; hearing sits high on both. The two measures are related, loosely, but they are not the same disadvantage.
This is the through-line of the whole picture. The cost of disability in South Carolina is, first and most consistently, an employment disadvantage. Cut the data by type, by how many disabilities a person carries, or by gender, and the sharpest, steadiest divide is over who holds a job at all. Pay matters, but it is the softer, noisier half of the story, and for the hardest-hit groups the numbers are too thin to carry much weight.
For anyone in South Carolina working to close that gap, the implication is pointed: the biggest lever isn’t pay. It’s employment. Getting disabled residents hired in the first place is the primary hurdle to cross.
Analysis and limitations
A note on the wage confidence intervals. Wages here are reported as medians, and a median of clustered, round-number wage data — many people report exactly $40,000 or $45,000 — produces confidence intervals that can look lopsided. Sometimes the lower bound lands right on the estimate, so the interval appears one-sided. That is a real property of computing an interval for a median on discrete data under replicate weights, not a plotting error. It is most pronounced for the hardest-hit disability groups, where few people are employed and the earner samples are small, so the wage figures for those groups should be read as directional rather than precise.
On the gender pay-gap figures. These compare median wage income for employed 18–64-year-olds in this sample; they are not the national “full-time, year-round” gender-gap figure. Different population, different measure.
What these numbers can and can’t say. Everything here is associational, not causal: the figures describe how employment and pay differ across groups, not why. Disability is self-reported through the ACS’s six functional-difficulty questions, and because a person can report more than one, the type categories overlap. The data is a single year (2024) — a snapshot, not a trend. And for the hardest-hit groups, especially self-care and independent-living difficulties, the number of employed people in the sample is small, which is why several wage estimates carry wide margins and should be read as directional.
Every chart in this report renders inline in the narrative above. The code that produced them including data pull, recoding, the weighted survey design, and each figure, is collected here in run order, for both transparency and reproducibility.
Show the analysis code
library(tidyverse)
library(srvyr)
library(tidycensus) # to_survey() for the ACS replicate-weight design
library(showtext) # pulls the brand font from Google — no local font install needed
library(ggtext) # element_textbox_simple() so chart subtitles wrap to the plot width
font_add_google("Source Sans 3", "Source Sans") # chart titles + axis text (P&C sans stand-in)
showtext_auto()
showtext_opts(dpi = 300) # match fig-dpi so text scales correctly
knitr::opts_chunk$set(results = "hide") # hide stray table/number output; charts still render
pums <- read_csv("data/pums_sc_nc_2024.csv", show_col_types = FALSE)
both <- pums |>
mutate(
state = factor(STATE_label),
sex = factor(SEX_label),
any_disab = factor(DIS_label),
employed = ESR %in% c("1", "2", "4", "5"), # employed, civ or armed forces
working_age = AGEP >= 18 & AGEP <= 64,
schl_num = suppressWarnings(as.integer(SCHL)),
educ = factor(case_when(
schl_num <= 15 ~ "No HS diploma",
schl_num <= 17 ~ "HS / GED",
schl_num <= 20 ~ "Some college / assoc.",
schl_num >= 21 ~ "Bachelor's+"
), levels = c("No HS diploma", "HS / GED",
"Some college / assoc.", "Bachelor's+"))
)
nrow(both) # total person records (SC + NC)
# small shared helpers for the charts
# --- Post & Courier brand palette -------------------------------------------
pc_red <- "#F15062" # the accent, "with disability"
pc_ink <- "#222222" # near-black text
pc_slate <- "#4A4A4A" # charcoal — baseline / "without disability"
pc_ground <- "#F2F1EF" # warm newsprint ground
pc_rule <- "#DDDDDD" # hairline
clean_state <- function(x) str_remove(x, "/.*$") # "South Carolina/SC" -> "South Carolina"
disab_cols <- c("With disability" = pc_red, "Without disability" = pc_slate)
relabel <- function(d) if_else(d == "With a disability",
"With disability", "Without disability")
theme_report <- function(base_size = 13) {
theme_minimal(base_size = base_size, base_family = "Source Sans") + # sans for titles, axis, ticks, labels
theme(
plot.title.position = "plot", # titles flush-left to the plot edge
plot.title = element_text(family = "Source Sans", face = "bold",
size = rel(1.4), color = pc_ink,
margin = margin(b = 8)),
plot.subtitle = ggtext::element_textbox_simple( # wraps to plot width so nothing clips
color = pc_slate, size = rel(0.95),
lineheight = 1.15, width = unit(1, "npc"),
margin = margin(t = 2, b = 10)),
axis.title.x = element_text(face = "bold", margin = margin(t = 15)), # bold + ~20px from axis
axis.title.y = element_text(face = "bold", margin = margin(r = 15)),
plot.background = element_rect(fill = pc_ground, color = NA),
panel.background = element_rect(fill = pc_ground, color = NA),
panel.grid.minor = element_blank(),
panel.grid.major = element_line(color = "#E4E2DD")
)
}
sc <- both |> filter(STATE == 45) # 45 = South Carolina
sc_svy <- to_survey(sc, type = "person", design = "rep_weights")
nrow(sc) # SC person records
type_vars <- c(
"Hearing" = "DEAR_label",
"Vision" = "DEYE_label",
"Cognitive" = "DREM_label",
"Ambulatory" = "DPHY_label",
"Self-care" = "DDRS_label",
"Independent living" = "DOUT_label"
)
emp_by_type <- imap_dfr(type_vars, function(col, label) {
sc_svy |>
filter(working_age, .data[[col]] == "Yes") |>
summarise(emp_rate = survey_mean(employed, vartype = "ci")) |>
mutate(group = label)
})
baseline <- sc_svy |>
filter(working_age, any_disab == "Without a disability") |>
summarise(emp_rate = survey_mean(employed, vartype = "ci")) |>
mutate(group = "No disability (baseline)")
emp_by_type <- bind_rows(baseline, emp_by_type) |>
arrange(emp_rate)
emp_by_type
emp_by_type |>
mutate(group = fct_reorder(group, emp_rate),
is_base = group == "No disability (baseline)") |>
ggplot(aes(emp_rate, group)) +
geom_errorbar(aes(xmin = emp_rate_low, xmax = emp_rate_upp),
height = 0.2, color = "grey60") +
geom_point(aes(color = is_base), size = 4.5) +
scale_x_continuous(labels = scales::percent, limits = c(0, 0.9)) +
scale_color_manual(values = c(`TRUE` = "#4A4A4A", `FALSE` = "#F15062"),
guide = "none") +
labs(
title = "In South Carolina, 'disability' hides a 40-point range",
subtitle = "Employment rate, working-age adults (18–64), by disability type vs. no-disability baseline\nACS 2024, weighted; bars = 95% CI",
x = NULL, y = NULL
) +
theme_report() +
theme(panel.grid.minor = element_blank())
sc <- sc |>
mutate(
n_types = (DEAR_label == "Yes") + (DEYE_label == "Yes") + (DREM_label == "Yes") +
(DPHY_label == "Yes") + (DDRS_label == "Yes") + (DOUT_label == "Yes"),
n_types_grp = factor(case_when(
n_types == 0 ~ "No disability",
n_types == 1 ~ "1 type",
n_types == 2 ~ "2 types",
n_types >= 3 ~ "3+ types"
), levels = c("No disability", "1 type", "2 types", "3+ types"))
)
# rebuild the weighted design so it carries the new columns
sc_svy <- to_survey(sc, type = "person", design = "rep_weights")
count(sc, n_types_grp)
sc_svy |>
filter(working_age, any_disab == "With a disability") |>
summarise(
one_type = survey_mean(n_types == 1, vartype = "ci"),
two_types = survey_mean(n_types == 2, vartype = "ci"),
three_plus = survey_mean(n_types >= 3, vartype = "ci")
)
emp_by_ntypes <- sc_svy |>
filter(working_age, n_types >= 1) |>
group_by(n_types_grp) |>
summarise(emp_rate = survey_mean(employed, vartype = "ci"))
emp_by_ntypes
emp_by_ntypes |>
ggplot(aes(emp_rate, fct_rev(n_types_grp))) +
geom_errorbar(aes(xmin = emp_rate_low, xmax = emp_rate_upp),
width = 0.2, color = "grey60") +
geom_point(color = "#F15062", size = 4.5) +
scale_x_continuous(labels = scales::percent, limits = c(0, 0.9)) +
labs(
title = "Each additional disability deepens the employment disadvantage",
subtitle = "Employment rate, working-age South Carolinians (18–64), by number of co-occurring disability types\nACS 2024, weighted; bars = 95% CI",
x = NULL, y = NULL
) +
theme_report() +
theme(panel.grid.minor = element_blank())
emp_sex <- sc_svy |>
filter(working_age) |>
group_by(sex, any_disab) |>
summarise(emp_rate = survey_mean(employed, vartype = "ci")) |>
arrange(sex, any_disab)
emp_sex
emp_sex |>
select(sex, any_disab, emp_rate) |>
pivot_wider(names_from = any_disab, values_from = emp_rate) |>
mutate(disability_gap = `Without a disability` - `With a disability`)
wage_sex <- sc_svy |>
filter(working_age, WAGP > 0) |>
group_by(sex, any_disab) |>
summarise(med_wage = survey_median(WAGP, vartype = "ci")) |>
arrange(sex, any_disab)
wage_sex |>
select(sex, any_disab, med_wage) |>
pivot_wider(names_from = any_disab, values_from = med_wage) |>
mutate(disability_wage_gap = `Without a disability` - `With a disability`)
emp_sex |>
mutate(disab = relabel(any_disab)) |>
ggplot(aes(emp_rate, sex)) +
geom_line(aes(group = sex), color = "grey75", linewidth = 1.3) +
geom_point(aes(color = disab), size = 4.5) +
scale_x_continuous(labels = scales::percent, limits = c(0.3, 0.9)) +
scale_color_manual(values = disab_cols) +
labs(
title = "Disability: the great gender leveler",
subtitle = "Among South Carolinians without a disability, men out-work women by 9 points.\nAmong those with one, the gap vanishes. Both sit near 45%. ACS 2024, weighted.",
x = NULL, y = NULL, color = NULL
) +
theme_report() +
theme(legend.position = "top", panel.grid.minor = element_blank())
wage_sex |>
mutate(disab = relabel(any_disab)) |>
ggplot(aes(med_wage, sex)) +
geom_line(aes(group = sex), color = "grey75", linewidth = 1.3) +
geom_errorbar(aes(xmin = med_wage_low, xmax = med_wage_upp, color = disab),
width = 0.15, linewidth = 0.6) +
geom_point(aes(color = disab), size = 4.5) +
scale_x_continuous(labels = scales::dollar) +
scale_color_manual(values = disab_cols) +
labs(
title = "But in pay, the gap doesn't level. It stacks",
subtitle = "Median wages, employed adults 18–64, by sex and disability status\nACS 2024, weighted; bars = 95% CI",
x = NULL, y = NULL, color = NULL
) +
theme_report() +
theme(legend.position = "top", panel.grid.minor = element_blank())
emp_ntypes_sex <- sc_svy |>
filter(working_age, n_types >= 1) |>
group_by(sex, n_types_grp) |>
summarise(emp_rate = survey_mean(employed, vartype = "ci")) |>
arrange(n_types_grp, sex)
emp_ntypes_sex
emp_ntypes_sex |>
ggplot(aes(emp_rate, fct_rev(n_types_grp))) +
geom_errorbar(aes(xmin = emp_rate_low, xmax = emp_rate_upp),
width = 0.2, color = "grey60") +
geom_point(color = "#F15062", size = 4) +
facet_wrap(~ sex) +
scale_x_continuous(labels = scales::percent, limits = c(0, 0.9)) +
labs(
title = "Does the co-occurrence disadvantage fall harder on one gender?",
subtitle = "Employment rate, working-age South Carolinians (18–64), by number of co-occurring disabilities and gender\nACS 2024, weighted; bars = 95% CI",
x = NULL, y = NULL
) +
theme_report() +
theme(panel.grid.minor = element_blank())
wage_by_type <- imap_dfr(type_vars, function(col, label) {
sc_svy |>
filter(working_age, WAGP > 0, .data[[col]] == "Yes") |>
summarise(med_wage = survey_median(WAGP, vartype = "ci")) |>
mutate(group = label)
})
wage_baseline <- sc_svy |>
filter(working_age, WAGP > 0, any_disab == "Without a disability") |>
summarise(med_wage = survey_median(WAGP, vartype = "ci")) |>
mutate(group = "No disability (baseline)")
wage_by_type <- bind_rows(wage_baseline, wage_by_type) |>
arrange(med_wage)
wage_by_type
wage_by_type |>
mutate(group = fct_reorder(group, med_wage),
is_base = group == "No disability (baseline)") |>
ggplot(aes(med_wage, group)) +
geom_errorbar(aes(xmin = med_wage_low, xmax = med_wage_upp),
height = 0.2, color = "grey60") +
geom_point(aes(color = is_base), size = 4.5) +
scale_x_continuous(labels = scales::dollar) +
scale_color_manual(values = c(`TRUE` = "#4A4A4A", `FALSE` = "#F15062"),
guide = "none") +
labs(
title = "The pay disadvantage by disability type",
subtitle = "Median wages, employed adults 18–64, by disability type vs. no-disability baseline ACS 2024, weighted; bars = 95% CI",
x = NULL, y = NULL
) +
theme_report() +
theme(panel.grid.minor = element_blank())
wage_by_ntypes <- sc_svy |>
filter(working_age, WAGP > 0, n_types >= 1) |>
group_by(n_types_grp) |>
summarise(med_wage = survey_median(WAGP, vartype = "ci"))
wage_by_ntypes
wage_by_ntypes |>
ggplot(aes(med_wage, fct_rev(n_types_grp))) +
geom_errorbar(aes(xmin = med_wage_low, xmax = med_wage_upp),
width = 0.2, color = "grey60") +
geom_point(color = "#F15062", size = 4.5) +
scale_x_continuous(labels = scales::dollar) +
labs(
title = "Pay by number of co-occurring disabilities",
subtitle = "Median wages, employed South Carolinians 18–64, by number of co-occurring types ACS 2024, weighted; bars = 95% CI",
x = NULL, y = NULL
) +
theme_report() +
theme(panel.grid.minor = element_blank())
sc |>
filter(working_age, WAGP > 0, WAGP < 150000) |> # drop a few extreme earners for readability
mutate(disab = relabel(any_disab)) |>
ggplot(aes(AGEP, WAGP, weight = PWGTP)) + # weight = population-representative density
geom_hex(bins = 22) +
facet_wrap(~ disab, ncol = 1) + # stacked so the two share an x-axis
scale_y_continuous(labels = scales::dollar) +
scale_fill_gradient(
low = "#FCDCE1", high = pc_red, trans = "sqrt", # single-hue red; sqrt lifts the sparse cells
name = "Weighted\ncount", labels = scales::comma
) +
labs(
title = "Disabled earners cluster lower at every age",
subtitle = "Working-age earners (18–64), by disability status. Hex shade = weighted density. ACS 2024.",
x = "Age", y = "Annual wages"
) +
theme_report() +
theme(strip.text = element_text(face = "bold", color = pc_ink, hjust = 0))
# A join matches two tables on a shared key. Both tables have `group`
# (the disability type), so we join on it — employment + pay in one table.
type_combined <- emp_by_type |>
select(group, emp_rate) |>
inner_join(select(wage_by_type, group, med_wage), by = "group")
type_combined
ggplot(type_combined, aes(emp_rate, med_wage, label = group)) +
geom_point(size = 3, color = "#F15062") +
geom_text(vjust = -0.8, size = 3.5, check_overlap = TRUE) +
scale_x_continuous(labels = scales::percent, limits = c(0, 0.9)) +
scale_y_continuous(labels = scales::dollar) +
labs(
title = "Employment and pay don't always move together",
subtitle = "Each point is a disability type (plus the no-disability baseline). SC working-age, ACS 2024, weighted.",
x = "Employment rate", y = "Median wage (earners)"
) +
theme_report() +
theme(panel.grid.minor = element_blank())Data. U.S. Census Bureau, American Community Survey, 2024 1-Year Public Use Microdata Sample (PUMS), accessed via the tidycensus R package. Estimates are weighted using ACS person and replicate weights; error bars and margins reflect 95% confidence intervals.