How to Map Median Broadband Download Speed by County

On this page

Problem statement

Which US counties have the fastest home internet and which the slowest? Ookla's open Speedtest tiles can answer it for every county in the lower 48, once the tiles are joined to counties and averaged correctly. That means weighting by tests, flagging counties with too few, and choosing whether to map a median speed or the share of tests above a benchmark. This page maps the tests-weighted median fixed-broadband download for April–June 2026 and ranks the fastest and slowest counties.

Quick answer

Join tile centres to county polygons, compute each county's tests-weighted median of avg_d_kbps (divided by 1,000 for Mbps), and drop or mark counties with fewer than 100 tests. Use quantile classes for the map.

by = fixed.groupby("GEOID").apply(lambda g: pd.Series({"tests": g.tests.sum(), "mbps": wmedian(g.avg_d_kbps, g.tests) / 1000}), include_groups=False)
m = counties.merge(by, left_on="GEOID", right_index=True, how="left")
print(f"counties with tests: {m.mbps.notna().sum():,} of {len(m):,}; median of county medians {m.mbps.median():.0f} Mbps; range {m.mbps.min():.0f} to {m.mbps.max():.0f}")
counties with tests: 3,108 of 3,109; median of county medians 285 Mbps; range 7 to 724

3,108 of 3,109 counties have tests. The median county's median is 285 Mbps, lower than the national figure because small rural counties count once each, and county medians range from 7 to 724 Mbps.

Map of United States counties shaded by tests-weighted median fixed-broadband download, dark green fast counties in the East and around cities, dark red slow counties across Montana, the Dakotas, Nebraska and parts of the South, and grey counties with under 100 tests in the northern Plains. Map of United States counties shaded by tests-weighted median fixed-broadband download, dark green fast counties in the East and around cities, dark red slow counties across Montana, the Dakotas, Nebraska and parts of the South, and grey counties with under 100 tests in the northern Plains.
Median fixed-broadband download by county, April-June 2026; grey counties have fewer than 100 tests.

Weighting, thin counties and rankings

1. Weight by tests

one = fixed[fixed.GEOID == by.tests.idxmax()]
print(f"busiest county {m.set_index('GEOID').loc[by.tests.idxmax(), 'NAME']}: plain median of tiles {one.avg_d_kbps.median() / 1000:.0f} Mbps, tests-weighted {wmedian(one.avg_d_kbps, one.tests) / 1000:.0f} Mbps")
busiest county Los Angeles: plain median of tiles 379 Mbps, tests-weighted 395 Mbps

In Los Angeles County, the busiest, the plain median of tile averages is 379 Mbps and the tests-weighted median 395. Weighting usually raises the figure, because busy urban tiles are faster than the many one-test tiles.

2. Mark thin counties

print(f"counties with fewer than 100 tests: {(m.tests < 100).sum()}; fewer than 30: {(m.tests < 30).sum()}")
counties with fewer than 100 tests: 201; fewer than 30: 54

201 counties have fewer than 100 tests and 54 fewer than 30. Those are left grey on the map here rather than coloured from a handful of tests.

Scatter plot of county median download against number of tests on a log scale: counties with few tests spread from near zero to 700 Mbps, counties with many tests cluster between 300 and 450 Mbps; a red line marks 100 tests. Scatter plot of county median download against number of tests on a log scale: counties with few tests spread from near zero to 700 Mbps, counties with many tests cluster between 300 and 450 Mbps; a red line marks 100 tests.
Small counties scatter widely because their medians rest on few tests.

3. Fastest and slowest

ok = m[m.tests >= 100].sort_values("mbps")
print("slowest:", ok.head(4)[["NAME", "STUSPS", "tests", "mbps"]].round(0).to_dict("records"))
print("fastest:", ok.tail(4)[["NAME", "STUSPS", "tests", "mbps"]].round(0).to_dict("records"))
slowest: [{'NAME': 'Garrard', 'STUSPS': 'KY', 'tests': 2963.0, 'mbps': 7.0}, {'NAME': 'Toole', 'STUSPS': 'MT', 'tests': 389.0, 'mbps': 14.0}, {'NAME': 'Phillips', 'STUSPS': 'MT', 'tests': 100.0, 'mbps': 17.0}, {'NAME': 'Newton', 'STUSPS': 'AR', 'tests': 378.0, 'mbps': 46.0}]
fastest: [{'NAME': 'Crockett', 'STUSPS': 'TN', 'tests': 300.0, 'mbps': 640.0}, {'NAME': 'Kanabec', 'STUSPS': 'MN', 'tests': 841.0, 'mbps': 663.0}, {'NAME': 'Trimble', 'STUSPS': 'KY', 'tests': 675.0, 'mbps': 674.0}, {'NAME': 'Kimball', 'STUSPS': 'NE', 'tests': 759.0, 'mbps': 724.0}]

The slowest county with 100 or more tests is Garrard County, Kentucky, at 7 Mbps on 2,963 tests, followed by Toole and Phillips in Montana and Newton County, Arkansas. The fastest include Kimball County, Nebraska (724 Mbps), Trimble County, Kentucky and Kanabec County, Minnesota, small rural counties rather than big cities.

The 100 Mbps share and states

The data used on this page

Ookla's Speedtest open data performance tiles for fixed broadband (April–June 2026), read from Amazon S3, and Census Bureau 2023 county boundaries.

from pathlib import Path

import duckdb
import geopandas as gpd
import numpy as np
import pandas as pd
import requests

Path("data").mkdir(exist_ok=True)
# Ookla Speedtest open data (AWS Open Data, keyless): one Parquet file per quarter and network type, zoom-16 web
# Mercator tiles with average speeds, latency, tests and devices. Published quarterly under fixed names.
BASE = "https://ookla-open-data.s3.amazonaws.com/parquet/performance"


def tiles(kind, year, q):
    """One quarter's tiles inside a box around the contiguous US, cached locally as Parquet."""
    path = f"data/ookla-{kind}-{year}q{q}-conus.parquet"
    if not Path(path).exists():
        url = f"{BASE}/type={kind}/year={year}/quarter={q}/{year}-{(q - 1) * 3 + 1:02d}-01_performance_{kind}_tiles.parquet"
        con = duckdb.connect()
        con.sql("INSTALL httpfs; LOAD httpfs;")
        con.sql(f"""COPY (SELECT quadkey, tile_x, tile_y, avg_d_kbps, avg_u_kbps, avg_lat_ms, avg_lat_down_ms, avg_lat_up_ms, tests, devices
                         FROM read_parquet('{url}') WHERE tile_x BETWEEN -125 AND -66 AND tile_y BETWEEN 24 AND 50
                         ORDER BY quadkey) TO '{path}' (FORMAT parquet)""")
    return pd.read_parquet(path)


for name in ["cb_2023_us_county_20m.zip"]:
    if not Path(f"data/{name}").exists():
        Path(f"data/{name}").write_bytes(requests.get(f"https://www2.census.gov/geo/tiger/GENZ2023/shp/{name}", timeout=300).content)
counties = gpd.read_file("data/cb_2023_us_county_20m.zip")
counties = counties[~counties.STUSPS.isin(["AK", "HI", "PR"])].to_crs(4326)


def in_counties(df):
    """Attach the county each tile's centre falls in; tiles outside the lower 48 (Canada, Mexico, sea) drop out."""
    pts = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df.tile_x, df.tile_y), crs=4326)
    j = gpd.sjoin(pts, counties[["GEOID", "STUSPS", "NAME", "geometry"]], predicate="within")
    return pd.DataFrame(j.drop(columns=["geometry", "index_right"]))


def wmedian(values, weights):
    """Median of tile averages, each tile counted once per test."""
    o = np.argsort(values.to_numpy())
    v, w = values.to_numpy()[o], weights.to_numpy()[o]
    c = np.cumsum(w)
    return v[np.searchsorted(c, c[-1] / 2)]


fixed = in_counties(tiles("fixed", 2026, 2))
print(f"Ookla fixed broadband, April-June 2026: {len(fixed):,} tiles in the lower 48, {fixed.tests.sum():,} tests")
Ookla fixed broadband, April-June 2026: 1,548,841 tiles in the lower 48, 15,134,971 tests

Example 1: share of tests meeting 100 Mbps

fixed["fast"] = fixed.avg_d_kbps >= 100_000
sh = fixed.groupby("GEOID").apply(lambda g: (g.fast * g.tests).sum() / g.tests.sum(), include_groups=False)
print(f"counties where at least half of tests are in tiles averaging 100 Mbps or more: {(sh >= 0.5).mean():.1%}")
counties where at least half of tests are in tiles averaging 100 Mbps or more: 95.8%

In 95.8% of counties, at least half of tests are in tiles averaging 100 Mbps or more, the FCC's current download benchmark. The benchmark shows where fast service exists, not that every household has it.

Horizontal bars of the eight fastest and eight slowest counties with 100 or more tests, from Kimball, Nebraska at 724 Mbps down to Garrard, Kentucky at 7. Horizontal bars of the eight fastest and eight slowest counties with 100 or more tests, from Kimball, Nebraska at 724 Mbps down to Garrard, Kentucky at 7.
The fastest and slowest counties with 100 or more tests, with each county's test count.

Example 2: by state

s = m[m.tests >= 100].groupby("STUSPS").mbps.median().sort_values()
print("state median of county medians, lowest:", s.head(4).round(0).to_dict(), "highest:", s.tail(4).round(0).to_dict())
state median of county medians, lowest: {'MT': 180.0, 'ID': 183.0, 'NM': 203.0, 'AZ': 215.0} highest: {'NJ': 366.0, 'DE': 374.0, 'FL': 396.0, 'CT': 405.0}

By state, the median county is slowest in Montana (180 Mbps), Idaho, New Mexico and Arizona, large western states with many sparse counties, and fastest in Connecticut (405 Mbps), Florida, Delaware and New Jersey.

What a county median speed shows

A county's median is the speed of the median test run there, so it reflects the plans people buy and the technology available: fibre and cable in towns, DSL, fixed wireless and satellite in the countryside. Counties at the bottom are typically served by old copper or satellite, and counties at the top by recent fibre builds, which in rural America are often co-operatives or projects funded by federal grants. The ranking changes quarter to quarter as networks are built, and quickly in small counties where one new fibre network moves the median.

Map details

  • Use a minimum test count; 100 a quarter is used here, and 54 counties have fewer than 30.
  • Classes: speeds are skewed, so quantile classes work better than equal intervals.
  • Upload matters for video calls and is far lower on cable and DSL; map it too.
  • The tile centre decides the county, so a few tiles on borders land on the other side.

FAQ

Which US county has the slowest internet?

Among counties with at least 100 fixed-broadband tests in April–June 2026, Garrard County, Kentucky, with a tests-weighted median of 7 Mbps.

What is the median internet speed of a US county?

The median of county medians is 285 Mbps for fixed broadband in April–June 2026, against 381.2 Mbps nationally weighted by tests.