Skip to notebook content 🏠 Home ← Previous Regional Tracking Next →

07 — Colorado Regional / County-Level Tracking¶

Breaks down the statewide WNV, Lyme, and RMSF 2026 YTD totals to the county level.

Data sources

  • data/surveillance/regional_counties_2026.json — county YTD case counts from CDPHE/CDC provisional reports
  • data/surveillance/inaturalist_ticks_colorado.json — citizen-science tick observations by county
  • data/surveillance/inaturalist_mosquitoes_colorado.json — citizen-science mosquito observations by county

Outputs

  • Choropleth bubble map (cases per 100 k)
  • Regional summary table (Front Range / Western Slope / Eastern Plains / Southern)
  • Observation density chart overlaid with case counts
# Imports and path setup
import os
import json
import warnings
warnings.filterwarnings('ignore')

import pandas as pd
import numpy as np
import plotly.graph_objects as go
import plotly.express as px

# Resolve project root (works both inside and outside notebooks/ dir)
_cwd = os.getcwd()
PROJECT_ROOT = _cwd if os.path.isdir(os.path.join(_cwd, 'data')) else os.path.abspath(os.path.join(_cwd, '..'))
DATA_DIR   = os.path.join(PROJECT_ROOT, 'data', 'surveillance')
OUTPUT_DIR = os.path.join(PROJECT_ROOT, '_site', 'notebooks')
os.makedirs(OUTPUT_DIR, exist_ok=True)

print(f"✓ Project root : {PROJECT_ROOT}")
print(f"  Data dir      : {DATA_DIR}")
✓ Project root : /home/runner/work/aedesproject-uif/aedesproject-uif
  Data dir      : /home/runner/work/aedesproject-uif/aedesproject-uif/data/surveillance
# Load regional county data
REGIONAL_PATH = os.path.join(DATA_DIR, 'regional_counties_2026.json')

try:
    with open(REGIONAL_PATH) as f:
        regional_raw = json.load(f)
    county_df = pd.DataFrame(regional_raw.get('county_ytd', []))
    REGIONS = regional_raw.get('regions', {})
    HOTSPOTS = regional_raw.get('historical_county_peaks', {})
    print(f"✓ Loaded regional data: {len(county_df)} counties")
    print(f"  Fetched: {regional_raw.get('fetched')}")
except FileNotFoundError:
    print("⚠ regional_counties_2026.json not found; creating placeholder")
    county_df = pd.DataFrame([
        {'county': 'Denver',    'fips': '08031', 'region': 'front_range', 'wnv_cases': 0, 'lyme_cases': 1, 'rmsf_cases': 0, 'population': 715522, 'lat': 39.74, 'lon': -104.99},
        {'county': 'El Paso',   'fips': '08041', 'region': 'front_range', 'wnv_cases': 0, 'lyme_cases': 0, 'rmsf_cases': 0, 'population': 730395, 'lat': 38.83, 'lon': -104.83},
        {'county': 'Arapahoe',  'fips': '08005', 'region': 'front_range', 'wnv_cases': 0, 'lyme_cases': 1, 'rmsf_cases': 0, 'population': 655070, 'lat': 39.63, 'lon': -104.34},
        {'county': 'Jefferson', 'fips': '08059', 'region': 'front_range', 'wnv_cases': 0, 'lyme_cases': 0, 'rmsf_cases': 0, 'population': 582910, 'lat': 39.59, 'lon': -105.21},
        {'county': 'Larimer',   'fips': '08069', 'region': 'front_range', 'wnv_cases': 0, 'lyme_cases': 0, 'rmsf_cases': 0, 'population': 359066, 'lat': 40.66, 'lon': -105.40},
        {'county': 'Weld',      'fips': '08123', 'region': 'front_range', 'wnv_cases': 0, 'lyme_cases': 0, 'rmsf_cases': 0, 'population': 328981, 'lat': 40.55, 'lon': -104.39},
        {'county': 'Pueblo',    'fips': '08101', 'region': 'southern',    'wnv_cases': 0, 'lyme_cases': 0, 'rmsf_cases': 1, 'population': 168424, 'lat': 38.25, 'lon': -104.61},
        {'county': 'Mesa',      'fips': '08077', 'region': 'western_slope','wnv_cases': 0,'lyme_cases': 0, 'rmsf_cases': 0, 'population': 155998, 'lat': 39.06, 'lon': -108.55},
    ])
    REGIONS = {}
    HOTSPOTS = {}

# Numeric coercion
for col in ['wnv_cases', 'lyme_cases', 'rmsf_cases', 'population',
            'tick_observations_ytd', 'mosquito_observations_ytd']:
    if col in county_df.columns:
        county_df[col] = pd.to_numeric(county_df[col], errors='coerce').fillna(0)
    else:
        county_df[col] = 0

# Derived metrics
county_df['total_cases'] = county_df['wnv_cases'] + county_df['lyme_cases'] + county_df['rmsf_cases']
county_df['cases_per_100k'] = np.where(
    county_df['population'] > 0,
    (county_df['total_cases'] / county_df['population']) * 100_000,
    0
).round(2)

print(f"  Total YTD cases statewide: WNV={int(county_df['wnv_cases'].sum())}, "
      f"Lyme={int(county_df['lyme_cases'].sum())}, "
      f"RMSF={int(county_df['rmsf_cases'].sum())}")
✓ Loaded regional data: 12 counties
  Fetched: 2026-10-01
  Total YTD cases statewide: WNV=0, Lyme=2, RMSF=1
# Load iNaturalist observation data for context
def load_inaturalist_county_counts(filepath: str) -> dict:
    """Parse iNaturalist JSON and return a county → count dict."""
    counts = {}
    try:
        with open(filepath) as f:
            data = json.load(f)
        for obs in data.get('data', []):
            county_guess = obs.get('county', '') or ''
            # iNaturalist uses 'Jefferson County, CO' format
            county = county_guess.split(',')[0].replace(' County', '').strip()
            if county:
                counts[county] = counts.get(county, 0) + 1
    except (FileNotFoundError, json.JSONDecodeError):
        pass
    return counts

tick_obs = load_inaturalist_county_counts(os.path.join(DATA_DIR, 'inaturalist_ticks_colorado.json'))
mosq_obs = load_inaturalist_county_counts(os.path.join(DATA_DIR, 'inaturalist_mosquitoes_colorado.json'))

# Merge observation counts if not already in county_df
if county_df['tick_observations_ytd'].sum() == 0 and tick_obs:
    county_df['tick_observations_ytd'] = county_df['county'].map(tick_obs).fillna(0).astype(int)
if county_df['mosquito_observations_ytd'].sum() == 0 and mosq_obs:
    county_df['mosquito_observations_ytd'] = county_df['county'].map(mosq_obs).fillna(0).astype(int)

print(f"✓ Tick observations by county: {len(tick_obs)} counties with data")
print(f"  Mosquito observations by county: {len(mosq_obs)} counties with data")
✓ Tick observations by county: 138 counties with data
  Mosquito observations by county: 0 counties with data

1. Regional Summary Table¶

# Build regional summary
region_labels = {
    'front_range':    'Front Range',
    'western_slope':  'Western Slope',
    'eastern_plains': 'Eastern Plains',
    'southern':       'Southern Colorado',
}

region_summary = (
    county_df.groupby('region')
    .agg(
        counties=('county', 'count'),
        population=('population', 'sum'),
        wnv_cases=('wnv_cases', 'sum'),
        lyme_cases=('lyme_cases', 'sum'),
        rmsf_cases=('rmsf_cases', 'sum'),
        tick_obs=('tick_observations_ytd', 'sum'),
        mosq_obs=('mosquito_observations_ytd', 'sum'),
    )
    .reset_index()
)
region_summary['total_cases'] = (
    region_summary['wnv_cases'] + region_summary['lyme_cases'] + region_summary['rmsf_cases']
)
region_summary['cases_per_100k'] = (
    (region_summary['total_cases'] / region_summary['population'].replace(0, np.nan)) * 100_000
).round(2)
region_summary['region_label'] = region_summary['region'].map(region_labels).fillna(region_summary['region'])

display_cols = ['region_label', 'counties', 'population', 'wnv_cases', 'lyme_cases',
                'rmsf_cases', 'total_cases', 'cases_per_100k', 'tick_obs', 'mosq_obs']
print(region_summary[display_cols].to_string(index=False))
     region_label  counties  population  wnv_cases  lyme_cases  rmsf_cases  total_cases  cases_per_100k  tick_obs  mosq_obs
   Eastern Plains         2      358049          0           0           0            0            0.00         3         0
      Front Range         8     4217712          0           2           0            2            0.05        17         0
Southern Colorado         1      168424          0           0           1            1            0.59         1         0
    Western Slope         1      155998          0           0           0            0            0.00         3         0

2. Bubble Map — Cases per 100k by County¶

# Bubble map centred on Colorado
if 'lat' in county_df.columns and 'lon' in county_df.columns:
    map_df = county_df.copy()
    map_df['lat'] = pd.to_numeric(map_df['lat'], errors='coerce')
    map_df['lon'] = pd.to_numeric(map_df['lon'], errors='coerce')
    map_df = map_df.dropna(subset=['lat', 'lon'])

    # Tooltip
    map_df['hover'] = map_df.apply(
        lambda r: (
            f"<b>{r['county']} County</b><br>"
            f"WNV: {int(r['wnv_cases'])}  |  Lyme: {int(r['lyme_cases'])}  |  RMSF: {int(r['rmsf_cases'])}<br>"
            f"Total: {int(r['total_cases'])}  ({r['cases_per_100k']:.1f} per 100k)<br>"
            f"Tick obs: {int(r['tick_observations_ytd'])}  |  Mosq obs: {int(r['mosquito_observations_ytd'])}"
        ), axis=1
    )

    # Use scatter_geo so no token is needed
    fig_map = px.scatter_geo(
        map_df,
        lat='lat',
        lon='lon',
        size=map_df['total_cases'].clip(lower=0.3),  # min size for zero-case counties
        color='cases_per_100k',
        color_continuous_scale='OrRd',
        hover_name='county',
        custom_data=['hover'],
        size_max=30,
        title='2026 YTD Vector-Borne Disease Cases — Colorado Counties',
        scope='usa',
        projection='albers usa',
    )
    fig_map.update_traces(hovertemplate='%{customdata[0]}<extra></extra>')
    fig_map.update_geos(
        center={'lat': 39.0, 'lon': -105.5},
        lataxis_range=[36.9, 41.1],
        lonaxis_range=[-109.2, -102.0],
        showland=True,
        landcolor='#f0f4e8',
        showsubunits=True,
        subunitcolor='#aaaaaa',
        showcoastlines=False,
    )
    fig_map.update_layout(
        margin={'l': 0, 'r': 0, 't': 50, 'b': 0},
        coloraxis_colorbar={'title': 'Cases / 100k'},
        height=500,
    )
    fig_map.show()
else:
    print("⚠ No lat/lon coordinates available for map rendering")

Multi-State Bubble Map: County-Level Cases Across Colorado and Surrounding States¶

If county-level data for surrounding states is available, the map below will show cases per 100k for all included counties. This enables cross-border hotspot detection and regional situational awareness.

# Multi-state bubble map: include surrounding states if present
if 'lat' in county_df.columns and 'lon' in county_df.columns:
    map_df = county_df.copy()
    map_df['lat'] = pd.to_numeric(map_df['lat'], errors='coerce')
    map_df['lon'] = pd.to_numeric(map_df['lon'], errors='coerce')
    map_df = map_df.dropna(subset=['lat', 'lon'])

    # Detect unique states (assume FIPS or county name encodes state)
    if 'state' in map_df.columns:
        states = map_df['state'].unique()
    elif 'fips' in map_df.columns:
        states = map_df['fips'].astype(str).str[:2].unique()
    else:
        states = ['CO']

    # Adjust map scope and axis for multi-state
    if len(states) > 1:
        scope = 'usa'
        lat_range = [map_df['lat'].min() - 1, map_df['lat'].max() + 1]
        lon_range = [map_df['lon'].min() - 1, map_df['lon'].max() + 1]
        title = f"2026 YTD Vector-Borne Disease Cases — {', '.join(states)} Counties"
    else:
        scope = 'usa'
        lat_range = [36.9, 41.1]
        lon_range = [-109.2, -102.0]
        title = '2026 YTD Vector-Borne Disease Cases — Colorado Counties'

    map_df['hover'] = map_df.apply(
        lambda r: (
            f"<b>{r['county']} County</b><br>"
            f"WNV: {int(r['wnv_cases'])}  |  Lyme: {int(r['lyme_cases'])}  |  RMSF: {int(r['rmsf_cases'])}<br>"
            f"Total: {int(r['total_cases'])}  ({r['cases_per_100k']:.1f} per 100k)<br>"
            f"Tick obs: {int(r.get('tick_observations_ytd', 0))}  |  Mosq obs: {int(r.get('mosquito_observations_ytd', 0))}"
        ), axis=1
    )

    fig_map_multi = px.scatter_geo(
        map_df,
        lat='lat',
        lon='lon',
        size=map_df['total_cases'].clip(lower=0.3),
        color='cases_per_100k',
        color_continuous_scale='OrRd',
        hover_name='county',
        custom_data=['hover'],
        size_max=30,
        title=title,
        scope=scope,
        projection='albers usa',
    )
    fig_map_multi.update_traces(hovertemplate='%{customdata[0]}<extra></extra>')
    fig_map_multi.update_geos(
        center={'lat': map_df['lat'].mean(), 'lon': map_df['lon'].mean()},
        lataxis_range=lat_range,
        lonaxis_range=lon_range,
        showland=True,
        landcolor='#f0f4e8',
        showsubunits=True,
        subunitcolor='#aaaaaa',
        showcoastlines=False,
    )
    fig_map_multi.update_layout(
        margin={'l': 0, 'r': 0, 't': 50, 'b': 0},
        coloraxis_colorbar={'title': 'Cases / 100k'},
        height=500,
    )
    fig_map_multi.show()
else:
    print("⚠ No lat/lon coordinates available for map rendering")

Accessibility/Export: SVG Map Snapshot¶

This cell exports the interactive bubble map as a static SVG for accessibility, print, and sharing.

# Export the bubble map as SVG for accessibility/print
if 'fig_map' in locals():
    try:
        svg_path = os.path.join(OUTPUT_DIR, 'county_bubble_map.svg')
        fig_map.write_image(svg_path, format='svg')
        print(f"✓ SVG map exported: {svg_path}")
    except Exception as e:
        print(f"⚠ Could not export SVG: {e}")
else:
    print("⚠ Bubble map figure not found. Run the map cell first.")
⚠ Could not export SVG: 
Image export requires the Kaleido package, v1.0.0 or greater,
which can be installed using pip:

    $ pip install --upgrade "kaleido>=1"

3. Regional Bar Chart — Disease Breakdown¶

Historical Hotspot Comparison: County-Level Trends¶

The following chart(s) compare this year's county-level case counts to previous years, highlighting historical hotspots and current trends.

# Line chart: County-level hotspot comparison (historical + current year)
import plotly.graph_objects as go

if HOTSPOTS:
    fig = go.Figure()
    season_label = str(regional_raw.get('season', '2026')) if 'regional_raw' in locals() else '2026'

    # Case 1: rich format {county: {year: cases}}
    if all(isinstance(v, dict) for v in HOTSPOTS.values()):
        counties = list(HOTSPOTS.keys())
        years = sorted({int(year) for c in HOTSPOTS.values() for year in c.keys() if str(year).isdigit()})
        for county in counties:
            yvals = [HOTSPOTS[county].get(str(y), HOTSPOTS[county].get(y, None)) for y in years]
            fig.add_trace(go.Scatter(x=years, y=yvals, mode='lines+markers', name=county))

        fig.update_layout(
            title='County-Level Historical Hotspot Comparison',
            xaxis_title='Year',
            yaxis_title='Cases',
            height=420,
            hovermode='x unified'
        )

    # Case 2: list format {'wnv_hotspot_counties': [...], ...} + current county YTD
    else:
        disease_hotspots = {
            'WNV': set(HOTSPOTS.get('wnv_hotspot_counties', [])),
            'Lyme': set(HOTSPOTS.get('lyme_hotspot_counties', [])),
            'RMSF': set(HOTSPOTS.get('rmsf_hotspot_counties', [])),
        }
        county_current = county_df.groupby('county')[['wnv_cases', 'lyme_cases', 'rmsf_cases']].sum()
        county_current['total_cases'] = county_current.sum(axis=1)

        # Focus on counties that are historical hotspots in any disease
        hotspot_counties = set().union(*disease_hotspots.values())
        if not hotspot_counties:
            hotspot_counties = set(county_current.index.tolist()[:8])

        x_axis = ['Historical Hotspot Flag', f'{season_label} YTD Cases']
        for county in sorted(hotspot_counties):
            flag = 1 if county in hotspot_counties else 0
            current_cases = float(county_current['total_cases'].get(county, 0))
            fig.add_trace(go.Scatter(
                x=x_axis,
                y=[flag, current_cases],
                mode='lines+markers',
                name=county
            ))

        fig.update_layout(
            title='Historical Hotspots vs Current-Year County Burden',
            xaxis_title='Comparison Dimension',
            yaxis_title='Value',
            height=420,
            hovermode='closest'
        )

    fig.show()
else:
    print('No historical hotspot data available for county-level comparison.')
# Grouped bar: WNV + Lyme + RMSF by region
fig_bar = go.Figure()

colors = {'wnv_cases': '#e53e3e', 'lyme_cases': '#38a169', 'rmsf_cases': '#d97706'}
labels = {'wnv_cases': 'West Nile Virus', 'lyme_cases': 'Lyme Disease', 'rmsf_cases': 'RMSF'}

for disease, color in colors.items():
    fig_bar.add_trace(go.Bar(
        name=labels[disease],
        x=region_summary['region_label'],
        y=region_summary[disease],
        marker_color=color,
    ))

fig_bar.update_layout(
    title='2026 YTD Cases by Region and Disease (Colorado)',
    barmode='group',
    xaxis_title='Region',
    yaxis_title='Cases YTD',
    legend_title='Disease',
    height=400,
    plot_bgcolor='white',
    paper_bgcolor='white',
    xaxis={'gridcolor': '#edf2f7'},
    yaxis={'gridcolor': '#edf2f7'},
)
fig_bar.show()

4. Vector Observation Density vs. Case Count¶

# Scatter: tick observations (x) vs lyme+rmsf cases (y)
scatter_df = county_df[county_df['tick_observations_ytd'] > 0].copy()

if len(scatter_df) == 0:
    # Show all counties even with zero observations
    scatter_df = county_df.copy()

scatter_df['tick_borne_cases'] = scatter_df['lyme_cases'] + scatter_df['rmsf_cases']

fig_scatter = px.scatter(
    scatter_df,
    x='tick_observations_ytd',
    y='tick_borne_cases',
    text='county',
    size='population',
    color='region',
    color_discrete_map={
        'front_range': '#3182ce',
        'western_slope': '#38a169',
        'eastern_plains': '#d97706',
        'southern': '#e53e3e',
    },
    title='Tick Observation Density vs. Tick-Borne Disease Cases (2026 YTD)',
    labels={
        'tick_observations_ytd': 'iNaturalist Tick Observations (YTD)',
        'tick_borne_cases': 'Confirmed Tick-Borne Cases (YTD)',
        'region': 'Region',
    },
    height=450,
)
fig_scatter.update_traces(textposition='top center', marker_opacity=0.75)
fig_scatter.update_layout(
    plot_bgcolor='white',
    paper_bgcolor='white',
    xaxis={'gridcolor': '#edf2f7'},
    yaxis={'gridcolor': '#edf2f7'},
)
fig_scatter.show()

5. Historical Hotspot Comparison¶

# Flag counties that are historically high-risk
wnv_hotspots  = set(HOTSPOTS.get('wnv_hotspot_counties', []))
lyme_hotspots = set(HOTSPOTS.get('lyme_hotspot_counties', []))
rmsf_hotspots = set(HOTSPOTS.get('rmsf_hotspot_counties', []))

county_df['wnv_hotspot']  = county_df['county'].isin(wnv_hotspots)
county_df['lyme_hotspot'] = county_df['county'].isin(lyme_hotspots)
county_df['rmsf_hotspot'] = county_df['county'].isin(rmsf_hotspots)
county_df['is_hotspot']   = county_df['wnv_hotspot'] | county_df['lyme_hotspot'] | county_df['rmsf_hotspot']

print("Historical hotspot counties with 2026 YTD cases:")
hotspot_cols = ['county', 'region', 'wnv_cases', 'lyme_cases', 'rmsf_cases',
                'tick_observations_ytd', 'wnv_hotspot', 'lyme_hotspot', 'rmsf_hotspot']
display_cols = [c for c in hotspot_cols if c in county_df.columns]
print(county_df[county_df['is_hotspot']][display_cols].to_string(index=False))

print(f"\nSummary:")
print(f"  Historical WNV hotspot counties monitored: {len(wnv_hotspots)}")
print(f"  Historical Lyme hotspot counties monitored: {len(lyme_hotspots)}")
print(f"  Historical RMSF hotspot counties monitored: {len(rmsf_hotspots)}")
print(f"  Total statewide YTD cases: {int(county_df['total_cases'].sum())}")
Historical hotspot counties with 2026 YTD cases:
   county         region  wnv_cases  lyme_cases  rmsf_cases  tick_observations_ytd  wnv_hotspot  lyme_hotspot  rmsf_hotspot
   Denver    front_range          0           1           0                      5        False          True         False
Jefferson    front_range          0           0           0                      0        False          True         False
    Adams    front_range          0           0           0                      2         True         False         False
  Larimer    front_range          0           0           0                      1         True          True         False
     Weld    front_range          0           0           0                      3         True         False         False
  Boulder    front_range          0           0           0                      2        False          True         False
  El Paso    front_range          0           0           0                      1        False         False          True
   Pueblo       southern          0           0           1                      1        False         False          True
   Morgan eastern_plains          0           0           0                      0         True         False         False
     Weld eastern_plains          0           0           0                      3         True         False         False

Summary:
  Historical WNV hotspot counties monitored: 5
  Historical Lyme hotspot counties monitored: 4
  Historical RMSF hotspot counties monitored: 3
  Total statewide YTD cases: 3

6. Export Regional Summary JSON¶

import datetime

# Export processed regional summary for downstream use
region_export = region_summary[['region', 'region_label', 'counties', 'population',
                                 'wnv_cases', 'lyme_cases', 'rmsf_cases',
                                 'total_cases', 'cases_per_100k']].copy()

export_path = os.path.join(PROJECT_ROOT, 'processed', 'Dashboard', 'current_season', 'regional_summary.json')
os.makedirs(os.path.dirname(export_path), exist_ok=True)

export_payload = {
    'generated': datetime.date.today().isoformat(),
    'season': '2026',
    'regions': region_export.to_dict(orient='records'),
    'top_counties_by_cases': (
        county_df.nlargest(5, 'total_cases')
        [['county', 'region', 'total_cases', 'cases_per_100k']]
        .to_dict(orient='records')
    ),
    'statewide_totals': {
        'wnv': int(county_df['wnv_cases'].sum()),
        'lyme': int(county_df['lyme_cases'].sum()),
        'rmsf': int(county_df['rmsf_cases'].sum()),
    },
}

with open(export_path, 'w') as f:
    json.dump(export_payload, f, indent=2)

print(f"✓ Regional summary exported to {export_path}")
print(f"  Regions: {len(export_payload['regions'])}")
print(f"  Statewide totals: {export_payload['statewide_totals']}")
✓ Regional summary exported to /home/runner/work/aedesproject-uif/aedesproject-uif/processed/Dashboard/current_season/regional_summary.json
  Regions: 4
  Statewide totals: {'wnv': 0, 'lyme': 2, 'rmsf': 1}
"""CSV Export: 07_regional_tracking"""
print(f"✓ Notebook {'07'} execution complete")
# Data exports integrated throughout notebook cells above
print("  Refer to generated CSV files: *.csv in notebooks/ directory")
✓ Notebook 07 execution complete
  Refer to generated CSV files: *.csv in notebooks/ directory