Physical activity is often discussed as an individual lifestyle choice. However, patterns of physical inactivity may also reflect the social and geographic conditions of the communities where people live.
Physical inactivity is an important public health concern because it is a recognized risk factor for chronic conditions, including type 2 diabetes and cardiovascular disease. But do communities with higher inactivity estimates also experience higher levels of obesity and diabetes? And are these patterns associated with broader social challenges?
This report explores physical inactivity across U.S. counties, examining its relationship with obesity, diabetes, and three social conditions: food insecurity, housing insecurity, and transportation barriers.
Research Question:
How is physical inactivity associated with obesity and diabetes across U.S. counties, and what social conditions are linked to higher inactivity rates?
Using the CDC PLACES 2025 dataset, five visualizations investigate the geographic distribution of physical inactivity, its relationship with chronic disease, regional disparities, social conditions, and overlapping health challenges.
The analysis uses the CDC PLACES 2025 County GIS Friendly dataset, containing modeled county-level estimates of health outcomes and related measures.
The dataset includes 3,143 county records and provides age-adjusted prevalence estimates for the indicators examined in this report.
The primary variables are:
The analysis removes missing observations when the relevant variables are required, standardizes county identifiers for geographic mapping, and uses national county-level tertiles to compare inactivity across regions.
Pearson correlation coefficients are used to assess associations between county-level measures. These results describe relationships between estimates and do not establish causation.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns
import geopandas as gpd
from matplotlib.patches import Patch
# Load CDC PLACES 2025 County Data
url = "https://data.cdc.gov/api/views/i46a-9kgh/rows.csv?accessType=DOWNLOAD"
df = pd.read_csv(url, dtype={"CountyFIPS": str})
# Select and rename health indicators
health_df = df[[
"StateDesc",
"StateAbbr",
"CountyName",
"CountyFIPS",
"TotalPop18plus",
"LPA_AdjPrev",
"OBESITY_AdjPrev",
"DIABETES_AdjPrev"
]].rename(columns={
"LPA_AdjPrev": "Physical Inactivity",
"OBESITY_AdjPrev": "Obesity",
"DIABETES_AdjPrev": "Diabetes"
}).copy()
health_clean = health_df.dropna(
subset=[
"Physical Inactivity",
"Obesity",
"Diabetes"
]
).copy()
health_clean["CountyFIPS"] = (
health_clean["CountyFIPS"]
.astype(str)
.str.zfill(5)
)
# Social conditions
social_df = df[[
"FOODINSECU_AdjPrev",
"HOUSINSECU_AdjPrev",
"LACKTRPT_AdjPrev",
"LPA_AdjPrev",
"OBESITY_AdjPrev",
"DIABETES_AdjPrev"
]].rename(columns={
"FOODINSECU_AdjPrev": "Food Insecurity",
"HOUSINSECU_AdjPrev": "Housing Insecurity",
"LACKTRPT_AdjPrev": "Transportation Barriers",
"LPA_AdjPrev": "Physical Inactivity",
"OBESITY_AdjPrev": "Obesity",
"DIABETES_AdjPrev": "Diabetes"
})
social_vars = [
"Food Insecurity",
"Housing Insecurity",
"Transportation Barriers"
]
health_vars = [
"Physical Inactivity",
"Obesity",
"Diabetes"
]
print("Original dataset:", df.shape)
print("Counties with complete health estimates:", len(health_clean))
The investigation begins with geography. If physical inactivity were distributed evenly across the country, county-level differences would be relatively small. However, mapping inactivity estimates allows us to identify whether certain areas experience a greater burden.
# Download U.S. Census county boundaries
counties_url = (
"https://www2.census.gov/geo/tiger/GENZ2024/shp/"
"cb_2024_us_county_500k.zip"
)
counties = gpd.read_file(counties_url)
# Merge health estimates with county boundaries
map_df = counties.merge(
health_clean[["CountyFIPS", "Physical Inactivity"]],
left_on="GEOID",
right_on="CountyFIPS",
how="left",
validate="one_to_one"
)
# Exclude Alaska, Hawaii, and territories
excluded = ["02", "15", "60", "66", "69", "72", "78", "74"]
us_map = map_df[
~map_df["STATEFP"].isin(excluded)
].copy()
us_map = us_map.to_crs("EPSG:5070")
# State boundaries
state_boundaries = us_map.dissolve(by="STATEFP")
# Set color scale using available data
vmin = us_map["Physical Inactivity"].quantile(0.02)
vmax = us_map["Physical Inactivity"].quantile(0.98)
fig, ax = plt.subplots(figsize=(15, 9))
us_map.plot(
column="Physical Inactivity",
ax=ax,
cmap="YlOrRd",
vmin=vmin,
vmax=vmax,
linewidth=0,
missing_kwds={"color": "#D9D9D9"}
)
state_boundaries.boundary.plot(
ax=ax,
color="#555555",
linewidth=0.6
)
# Color bar
norm = mpl.colors.Normalize(vmin=vmin, vmax=vmax)
sm = mpl.cm.ScalarMappable(cmap="YlOrRd", norm=norm)
sm.set_array([])
fig.subplots_adjust(bottom=0.19)
cbar_ax = fig.add_axes([0.30, 0.15, 0.40, 0.025])
cbar = fig.colorbar(
sm,
cax=cbar_ax,
orientation="horizontal"
)
cbar.set_label(
"Age-adjusted physical inactivity prevalence (%)",
fontsize=11,
labelpad=8
)
# Missing data legend
ax.legend(
handles=[
Patch(
facecolor="#D9D9D9",
label="Data unavailable"
)
],
loc="lower left",
bbox_to_anchor=(0.05, -0.04),
frameon=False,
fontsize=10
)
ax.set_title(
"Where Is Physical Inactivity Most Prevalent?",
fontsize=17,
fontweight="bold",
loc="left",
pad=20
)
ax.set_axis_off()
fig.text(
0.5, 0.055,
"Source: CDC PLACES 2025 | County-level age-adjusted estimates\n"
"Contiguous United States only",
ha="center",
va="top",
fontsize=9,
color="#666666"
)
plt.show()
The map highlights geographic variation in physical inactivity across U.S. counties. Darker counties have higher estimated inactivity prevalence, while lighter counties have lower estimates.
Rather than treating physical inactivity as a uniform national problem, the map encourages us to examine where it is more concentrated.
The next question is whether these geographic differences are also associated with chronic disease.
Physical inactivity is a recognized risk factor for several chronic health conditions. However, county-level data allow us to investigate whether places with higher inactivity estimates also tend to report higher obesity and diabetes prevalence.
We use two scatterplots to compare these relationships.
sns.set_style("whitegrid")
fig, axes = plt.subplots(1, 2, figsize=(13, 6))
relationships = [
("Obesity", "#4477AA"),
("Diabetes", "#C45A48")
]
for ax, (outcome, color) in zip(axes, relationships):
x = health_clean["Physical Inactivity"]
y = health_clean[outcome]
# County-level scatterplot
ax.scatter(
x,
y,
alpha=0.25,
s=16,
color=color,
edgecolors="none"
)
# Linear trend line
slope, intercept = np.polyfit(x, y, 1)
x_line = np.linspace(x.min(), x.max(), 100)
y_line = slope * x_line + intercept
ax.plot(
x_line,
y_line,
color="#222222",
linewidth=2
)
# Pearson correlation
correlation = x.corr(y)
ax.text(
0.05,
0.95,
f"Pearson r = {correlation:.2f}",
transform=ax.transAxes,
va="top",
fontsize=12,
fontweight="bold",
bbox={
"facecolor": "white",
"edgecolor": "none",
"alpha": 0.85
}
)
ax.set_xlabel(
"Physical inactivity prevalence (%)",
fontsize=11
)
ax.set_ylabel(
f"{outcome} prevalence (%)",
fontsize=11
)
ax.set_title(
f"Physical Inactivity vs. {outcome}",
fontsize=13,
fontweight="bold"
)
ax.spines[["top", "right"]].set_visible(False)
fig.suptitle(
"Do Less Active Counties Have Higher Chronic Disease Estimates?",
fontsize=16,
fontweight="bold",
y=1.04
)
fig.text(
0.5,
-0.04,
"Source: CDC PLACES 2025 | Each point represents one county\n"
"Trend lines show linear associations, not causal effects",
ha="center",
fontsize=9,
color="#666666"
)
plt.tight_layout()
plt.show()
Each point represents a county. The trend lines summarize the overall direction of the relationships, while Pearson correlation coefficients indicate their strength.
An upward trend would indicate that counties with higher physical inactivity estimates also tend to have higher obesity or diabetes estimates.
However, these relationships should not be interpreted as evidence that inactivity alone causes chronic disease. Other demographic, economic, environmental, and health-related factors may contribute.
If inactivity and chronic disease are associated, the next question is whether inactivity itself is concentrated in particular regions.
The county map provides geographic detail, but regional comparisons help reveal broader patterns.
To investigate this, counties are divided into three national physical inactivity categories: lower, middle, and upper thirds. We then compare the percentage of counties in each category across the four U.S. Census regions.
# U.S. Census regions
regions = {
"Northeast": [
"CT", "ME", "MA", "NH", "RI", "VT",
"NJ", "NY", "PA"
],
"Midwest": [
"IL", "IN", "MI", "OH", "WI", "IA",
"KS", "MN", "MO", "NE", "ND", "SD"
],
"South": [
"DE", "DC", "FL", "GA", "MD", "NC",
"SC", "VA", "WV", "AL", "KY", "MS",
"TN", "AR", "LA", "OK", "TX"
],
"West": [
"AK", "AZ", "CA", "CO", "HI", "ID",
"MT", "NV", "NM", "OR", "UT", "WA",
"WY"
]
}
state_to_region = {
state: region
for region, states in regions.items()
for state in states
}
regional_df = health_clean[
["StateAbbr", "Physical Inactivity"]
].copy()
regional_df["Region"] = (
regional_df["StateAbbr"].map(state_to_region)
)
# National tertile thresholds
q33, q67 = regional_df[
"Physical Inactivity"
].quantile([1/3, 2/3])
regional_df["Inactivity Level"] = pd.cut(
regional_df["Physical Inactivity"],
bins=[-np.inf, q33, q67, np.inf],
labels=[
"Lower third",
"Middle third",
"Upper third"
]
)
# Percentage distribution by region
regional_counts = pd.crosstab(
regional_df["Region"],
regional_df["Inactivity Level"]
).reindex([
"Northeast",
"Midwest",
"South",
"West"
])
regional_percent = regional_counts.div(
regional_counts.sum(axis=1),
axis=0
) * 100
fig, ax = plt.subplots(figsize=(11, 7))
colors = {
"Lower third": "#7BA7C7",
"Middle third": "#B9A0D2",
"Upper third": "#704A91"
}
bottom = np.zeros(len(regional_percent))
for category in colors:
values = regional_percent[category].values
ax.bar(
regional_percent.index,
values,
bottom=bottom,
color=colors[category],
width=0.65,
label=category
)
for i, value in enumerate(values):
if value >= 5:
ax.text(
i,
bottom[i] + value / 2,
f"{value:.1f}%",
ha="center",
va="center",
fontsize=11,
fontweight="bold"
)
bottom += values
# County counts
for i, region in enumerate(regional_percent.index):
n = regional_counts.loc[region].sum()
ax.text(
i,
102,
f"n = {n:,}",
ha="center",
fontsize=10,
color="#666666"
)
ax.set_ylim(0, 110)
ax.set_yticks([0, 20, 40, 60, 80, 100])
ax.set_ylabel(
"Percentage of counties (%)",
fontsize=11
)
ax.set_xlabel(
"U.S. Census Region",
fontsize=11
)
ax.set_title(
"How Does Physical Inactivity Differ Across U.S. Regions?",
fontsize=16,
fontweight="bold",
loc="left",
pad=20
)
ax.legend(
title="Physical inactivity category",
loc="upper center",
bbox_to_anchor=(0.5, -0.12),
ncol=3,
frameon=False
)
ax.spines[["top", "right"]].set_visible(False)
ax.grid(axis="y", alpha=0.15)
ax.set_axisbelow(True)
fig.subplots_adjust(bottom=0.27)
fig.text(
0.5,
0.04,
"Source: CDC PLACES 2025 | National county-level tertiles\n"
f"Thresholds: {q33:.1f}% and {q67:.1f}%\n"
"Percentages represent counties, not population shares",
ha="center",
va="top",
fontsize=9,
color="#666666"
)
plt.show()
The stacked bar chart compares the distribution of counties across inactivity categories, rather than simply comparing regional averages.
A region with a larger upper-third segment contains a greater proportion of counties with relatively high physical inactivity estimates.
These differences can help identify regional patterns, but they do not explain why those patterns occur.
That brings us to a broader question: What social conditions are associated with physical inactivity?
So far, the analysis has explored where inactivity is concentrated, its relationship with chronic disease, and the social conditions associated with it.
The final visualization examines whether counties frequently experience multiple above-median health challenges simultaneously.
# National county medians
indicators = [
"Physical Inactivity",
"Obesity",
"Diabetes"
]
medians = health_clean[indicators].median()
# Count indicators above the national county median
health_clean["Elevated Indicators"] = (
health_clean[indicators]
.gt(medians)
.sum(axis=1)
)
challenge_counts = (
health_clean["Elevated Indicators"]
.value_counts()
.reindex([0, 1, 2, 3], fill_value=0)
)
challenge_counts.index = [
"None",
"One",
"Two",
"All three"
]
challenge_percent = (
challenge_counts / challenge_counts.sum() * 100
)
fig, ax = plt.subplots(figsize=(10, 8))
colors = [
"#367F87",
"#A5BFA0",
"#E8A087",
"#B95342"
]
wedges, texts, autotexts = ax.pie(
challenge_counts,
colors=colors,
startangle=90,
counterclock=False,
autopct="%1.1f%%",
pctdistance=0.82,
wedgeprops={
"width": 0.38,
"edgecolor": "white",
"linewidth": 3
},
textprops={
"fontsize": 12,
"fontweight": "bold"
}
)
# Improve label contrast
for i, text in enumerate(autotexts):
text.set_color(
"white" if i in [0, 3] else "#222222"
)
# Center count
ax.text(
0,
0.08,
f"{len(health_clean):,}",
ha="center",
va="center",
fontsize=26,
fontweight="bold"
)
ax.text(
0,
-0.12,
"Counties",
ha="center",
fontsize=12,
color="#666666"
)
ax.legend(
wedges,
challenge_counts.index,
title="Indicators above median",
loc="center left",
bbox_to_anchor=(1.0, 0.5),
frameon=False,
fontsize=11
)
ax.set_title(
"How Often Do Physical Health Challenges Overlap?",
fontsize=16,
fontweight="bold",
pad=25
)
fig.text(
0.5,
0.03,
"Source: CDC PLACES 2025 | County-level age-adjusted estimates\n"
"Indicators: physical inactivity, obesity, diabetes\n"
"Categories based on national county medians",
ha="center",
fontsize=9,
color="#666666"
)
plt.tight_layout(rect=[0, 0.10, 1, 1])
plt.show()
The donut chart shows how many of the three health indicators exceed their respective national county medians in each county.
The original analysis found that 31.3% of counties were above the median on all three indicators, while 32.4% were above the median on none.
This highlights how frequently physical inactivity, obesity, and diabetes coexist at relatively elevated levels within the same counties.
An above-median estimate is not a clinical diagnosis or an official high-risk threshold. It is a relative comparison across counties.
Physical inactivity is not distributed evenly across U.S. counties, and its geographic patterns provide an important starting point for understanding broader physical health disparities.
The investigation explores five connected questions: where inactivity is concentrated, how it relates to obesity and diabetes, whether regional patterns exist, how social conditions are associated with health outcomes, and how often multiple health challenges overlap.
Together, the visualizations show that physical inactivity is associated with broader patterns of chronic disease and social disadvantage at the county level.
The findings support the importance of examining physical inactivity as part of a wider public health context rather than only as an individual behavior.
However, these are ecological, observational relationships. The analysis cannot establish that physical inactivity causes the observed chronic disease patterns or that the social conditions directly cause inactivity.
Further research using individual-level and longitudinal data would be needed to investigate these potential causal relationships.
CDC PLACES 2025
U.S. Census Bureau