Introduction

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.

Data and Methodology

Data Source

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:

  • Physical Inactivity: Percentage of adults reporting no leisure-time physical activity.
  • Obesity: Estimated percentage of adults with obesity.
  • Diabetes: Estimated percentage of adults diagnosed with diabetes.
  • Food Insecurity: Estimated percentage of adults experiencing food insecurity.
  • Housing Insecurity: Estimated percentage of adults experiencing housing insecurity.
  • Transportation Barriers: Estimated percentage of adults experiencing a lack of reliable transportation.

Data Preparation

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))

Following the Evidence

1. Where Is Physical Inactivity Most Common?

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()

What Does the Map Reveal?

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.

2. Is Physical Inactivity 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()

Interpreting the Relationships

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.

3. Is Physical Inactivity a Regional Problem?

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()

Understanding Regional Differences

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?

4. What Social Conditions Are Associated with Inactivity?

Physical inactivity does not occur in isolation from the circumstances of daily life. Access to resources, housing stability, and transportation may be related to opportunities for physical activity and broader health outcomes.

This analysis examines three social conditions: food insecurity, housing insecurity, and transportation barriers.

# Calculate county-level correlations
social_correlations = social_df.corr().loc[
    social_vars,
    health_vars
]

fig, ax = plt.subplots(figsize=(11, 6))

sns.heatmap(
    social_correlations,
    annot=True,
    fmt=".2f",
    cmap="YlOrRd",
    vmin=0,
    vmax=1,
    linewidths=1.5,
    linecolor="white",
    annot_kws={"fontsize": 13},
    cbar_kws={
        "label": "Pearson correlation coefficient"
    },
    ax=ax
)

ax.set_title(
    "How Are Social Conditions Associated with Physical Health?",
    fontsize=16,
    fontweight="bold",
    loc="left",
    pad=22
)

ax.set_xlabel(
    "Physical health indicators",
    fontsize=11,
    labelpad=12
)

ax.set_ylabel(
    "Social conditions",
    fontsize=11,
    labelpad=12
)

ax.tick_params(axis="both", length=0)

plt.xticks(rotation=0)
plt.yticks(rotation=0)
fig.text(
    0.5,
    -0.04,
    "Source: CDC PLACES 2025 | County-level age-adjusted estimates\n"
    "Correlations describe associations, not causal relationships",
    ha="center",
    fontsize=9,
    color="#666666"
)

plt.tight_layout()
plt.show()

What Do the Correlations Suggest?

The heatmap compares the strength of associations between three social conditions and three physical health indicators.

In the original analysis, physical inactivity showed strong positive correlations with food insecurity, housing insecurity, and transportation barriers, with coefficients approximately between 0.80 and 0.82.

These results suggest that counties with greater estimated social challenges also tend to have higher estimated physical inactivity.

However, the analysis cannot determine whether these social conditions directly cause inactivity. The measures are modeled county-level estimates, and other factors may contribute to the relationships.

Our final question brings the investigation together: How often do physical inactivity, obesity, and diabetes occur at elevated levels in the same counties?

5. When Do Multiple Health Challenges Overlap?

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()

Bringing the Findings Together

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.

Conclusion

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.

Limitations

  • CDC PLACES estimates are modeled rather than direct measurements of every county resident.
  • The analysis is observational and cannot establish causation.
  • County-level associations may not reflect relationships between individual residents.
  • Missing estimates limit coverage, particularly for Kentucky and Pennsylvania.
  • National county medians and tertiles are relative classifications rather than clinical thresholds.
  • Counties are treated equally in most comparisons, regardless of population size.

Data Sources

CDC PLACES 2025

County GIS Friendly Dataset

U.S. Census Bureau

2024 Cartographic Boundary Files