Average EV Range Comes Out Too Low: Zero Means Not Researched
On this page
Problem statement
Average the Electric Range column of Washington's EV registrations and you get 36.1 miles, absurdly low for a fleet that is four-fifths battery-electric. 65.7% of rows have a range of 0, and a zero does not mean the car cannot drive on electricity. It means the state has not looked up that model's range. Since model year 2021 almost every new vehicle has been left at 0. This page shows how the zeros follow model year, how the eligibility column gives them away, and how to fill them from vehicles of the same make and model.
Quick answer
Replace Electric Range == 0 with missing, not zero. Where you need a value, fill it from the median range of the same make, model and vehicle type with a known range, and say how many rows remain without one. Never average the raw column.
r = ev.range_mi
print(f"mean electric range of all rows: {r.mean():.1f} miles; rows with range 0: {(r == 0).mean():.1%}; "
f"mean where range > 0: {r[r > 0].mean():.1f} miles")
mean electric range of all rows: 36.1 miles; rows with range 0: 65.7%; mean where range > 0: 105.3 miles
The raw mean is 36.1 miles because 65.7% of rows are 0. Among rows with a recorded range the mean is 105.3 miles, which still mixes short-range plug-in hybrids with battery-electric cars.
Finding the zeros and filling them
1. Zero follows the model year, not the car
z = ev.assign(zero=ev.range_mi == 0).groupby("model_year").zero.mean()
print(z.loc[2015:2027].round(3).to_dict())
{2015: 0.0, 2016: 0.0, 2017: 0.0, 2018: 0.0, 2019: 0.0, 2020: 0.006, 2021: 0.754, 2022: 0.832, 2023: 0.879, 2024: 0.781, 2025: 0.795, 2026: 0.884, 2027: 0.951}
No vehicle up to model year 2019 has a zero range, and only 0.6% of 2020 models. From 2021 the share jumps to 75.4% and stays near 80β95% (88.4% for 2026, 95.1% for 2027). The zeros mark when the state stopped researching ranges for new models, not anything about the cars.
2. The eligibility column says so
print(pd.crosstab(ev.range_mi == 0, ev.cafv).to_string())
cafv Clean Alternative Fuel Vehicle Eligible Eligibility unknown as battery range has not been researched Not eligible due to low battery range
range_mi
False 78933 0 24124
True 0 197085 0
The eligibility column confirms it. Every one of the 197,085 zero-range rows is "Eligibility unknown as battery range has not been researched", and no row with a range has that label. The two columns are generated together: a zero range is a missing range.
3. Fill from the same model where you can
known = ev[ev.range_mi > 0].groupby(["make", "model", "kind"]).range_mi.median()
key = pd.MultiIndex.from_frame(ev[["make", "model", "kind"]])
filled = ev.range_mi.where(ev.range_mi > 0, pd.Series(known.reindex(key).values, index=ev.index))
print(f"after filling from the same make and model: {filled.notna().mean():.1%} have a range, mean {filled.mean():.1f} miles")
after filling from the same make and model: 71.0% have a range, mean 184.0 miles
Filling each zero from the median range of the same make, model and type gives 71.0% of rows a range, with a mean of 184.0 miles, a plausible figure for a mostly battery-electric fleet. The rest are models introduced after 2020 that never had a researched range on any vehicle, such as most 2021-and-later Tesla Model Ys.
One model, and missing values
The data used on this page
The Washington State Department of Licensing's Electric Vehicle Population Data, saved on this site on 15 October 2026.
from pathlib import Path
import geopandas as gpd
import numpy as np
import pandas as pd
import requests
Path("data").mkdir(exist_ok=True)
# Washington State Department of Licensing, via data.wa.gov: Electric Vehicle Population Data (f6w7-q2d2), one row per
# registered battery-electric or plug-in hybrid vehicle, and Electric Vehicle Population Size History By County (3d5d-sdqb),
# monthly counts since 2017. Both are replaced monthly, so they are read from copies saved on this site on 15 October 2026.
FILES = ["wa-ev-population-2026-10-15.zip", "wa-ev-population-history-2026-10-15.zip"]
for name in FILES:
if not Path(f"data/{name}").exists():
Path(f"data/{name}").write_bytes(requests.get(f"https://data.spatialworkflow.io/{name}", timeout=600).content)
NAMES = {"VIN (1-10)": "vin10", "County": "county", "City": "city", "State": "state", "Postal Code": "zip", "Model Year": "model_year",
"Make": "make", "Model": "model", "Electric Vehicle Type": "ev_type", "Clean Alternative Fuel Vehicle (CAFV) Eligibility": "cafv",
"Electric Range": "range_mi", "Legislative District": "district", "DOL Vehicle ID": "dol_id", "Vehicle Location": "location",
"Electric Utility": "utility", "2020 GEOID": "tract"}
ev = pd.read_csv("data/wa-ev-population-2026-10-15.zip", dtype={"Postal Code": str, "2020 GEOID": str}).rename(columns=NAMES)
ev["kind"] = ev.ev_type.str.extract(r"\((BEV|PHEV)\)")[0]
hist = pd.read_csv("data/wa-ev-population-history-2026-10-15.zip")
hist["date"] = pd.to_datetime(hist.Date, format="%B %d %Y")
print(f"{len(ev):,} electric vehicles registered with Washington DOL; history: {hist.date.min():%b %Y} to {hist.date.max():%b %Y}")
300,142 electric vehicles registered with Washington DOL; history: Jan 2017 to Sep 2026
Example 1: the same model, two answers
my = ev[(ev.make == "TESLA") & (ev.model == "MODEL Y")]
print(my.groupby("model_year").range_mi.agg(lambda s: f"{(s == 0).mean():.0%} zero, max {s.max()}").loc[2020:2026].to_dict())
{2020: '0% zero, max 291.0', 2021: '100% zero, max 0.0', 2022: '100% zero, max 0.0', 2023: '100% zero, max 0.0', 2024: '100% zero, max 0.0', 2025: '100% zero, max 0.0', 2026: '100% zero, max 0.0'}
The Tesla Model Y shows the pattern exactly. Its 2020 model year has a researched range (up to 291 miles) and no zeros. From 2021 every Model Y is 0. Filling across model years would give 2026 cars the 2020 range, which is wrong in a different way. For range-dependent analysis, a separate table of EPA ranges by model and year is the reliable source.
Example 2: missing, not zero
clean = ev.range_mi.replace(0, np.nan)
print(f"blank ranges: {ev.range_mi.isna().sum()}; zeros turned to missing: {clean.isna().sum() - ev.range_mi.isna().sum():,}")
print(clean.groupby(ev.kind).median().to_dict())
blank ranges: 35; zeros turned to missing: 197,085
{'BEV': 215.0, 'PHEV': 32.0}
35 rows are blank, distinct from the zeros. Treating the 197,085 zeros as missing gives median ranges of 215 miles for BEVs and 32 for PHEVs, the figures to quote.
Why the range stopped being filled
The range column fed Washington's Clean Alternative Fuel Vehicle eligibility, which required 30 miles of all-electric range for a sales-tax exemption. The state looked up each model's EPA range to decide eligibility. For nearly all models from 2021 on, that lookup has not been done, and the file records them as 0 with the "not researched" label rather than leaving the field blank. The data is correct for its original purpose and misleading for any other.
Related traps
- Grouping by kind first. Fill BEVs from BEVs; a model sold in both versions (some Kia and Hyundai models) has very different ranges.
- Model year changes range. Battery upgrades mean a 2018 and a 2024 car of the same model differ.
- Range is EPA-rated, not real-world, and falls in cold weather.
- Any average of the raw column published from this file is wrong; check the source before quoting one.
Internal links
- Battery electric against plug-in hybrid β range by kind
- How to analyse Washington's EV registrations β the file
- Counting EVs by VIN goes wrong β another column that is not what it looks like
FAQ
Why do so many EVs in the Washington data have an electric range of 0?
Because the state has not researched the range for most models since model year 2021. All 197,085 zero-range rows are labelled "battery range has not been researched".
What is the average range of electric vehicles in Washington?
The median recorded range is 215 miles for battery-electric vehicles and 32 for plug-in hybrids, once the zeros are treated as missing.