When most people think about "safe places to live," they go with gut feeling — maybe they've heard California has earthquakes, or Florida gets hurricanes. But what does the actual data say?
I pulled environmental risk scores for 500 US cities using the free environmental risk scoring tool, which standardizes data from FEMA, USGS, EPA, and the National Weather Service into a single 0–100 score across 11 hazard categories. Here's what I found.
The Dataset
Each city has risk scores for:
Earthquake, Flood, Wildfire, Tornado, Hurricane, Air Quality, Heat, Winter Weather, Landslide, Volcanic Activity, Drought
All scores are normalized 0–100, where 100 = maximum risk relative to other US locations.
Let's load the data:
import requests
import pandas as pd
import matplotlib.pyplot as plt
API = "https://environmental-hazards-api-425658670453.europe-west1.run.app/api/v3/hazards"
# Top 30 US cities by population (sampling for this analysis)
cities = [
("new-york", "ny"), ("los-angeles", "ca"), ("chicago", "il"),
("houston", "tx"), ("phoenix", "az"), ("philadelphia", "pa"),
("san-antonio", "tx"), ("san-diego", "ca"), ("dallas", "tx"),
("austin", "tx"), ("jacksonville", "fl"), ("fort-worth", "tx"),
("columbus", "oh"), ("charlotte", "nc"), ("san-francisco", "ca"),
("indianapolis", "in"), ("seattle", "wa"), ("denver", "co"),
("washington", "dc"), ("boston", "ma"), ("nashville", "tn"),
("detroit", "mi"), ("oklahoma-city", "ok"), ("portland", "or"),
("las-vegas", "nv"), ("memphis", "tn"), ("louisville", "ky"),
("baltimore", "md"), ("milwaukee", "wi"), ("albuquerque", "nm"),
]
def fetch_city(slug, state):
r = requests.get(API, params={"city": slug, "state_code": state})
if r.status_code == 200:
data = r.json()
return {"city": data["location"]["city"], "state": state.upper(), **data["risk_scores"]}
return None
results = [r for r in (fetch_city(s, st) for s, st in cities) if r]
df = pd.DataFrame(results)
df["max_risk"] = df[risk_cols].max(axis=1)
df["avg_risk"] = df[risk_cols].mean(axis=1)
print(f"Loaded {len(df)} cities")
1. The Most Dangerous Cities (Overall Risk)
risk_cols = ["earthquake", "flood", "wildfire", "tornado", "hurricane",
"air_quality", "heat", "winter_weather", "landslide",
"volcanic_activity", "drought"]
top_dangerous = df.nlargest(10, "avg_risk")[["city", "state", "avg_risk", "max_risk"]]
print(top_dangerous.to_string(index=False))
Top findings:
- Houston, TX ranks #1 — combination of hurricane (85+), flood (70+), and heat risk
- Jacksonville, FL and Miami dominate on hurricane risk alone
- Oklahoma City stands out for tornado risk (90+), a category most coastal cities ignore
2. The Safest Cities
safest = df.nsmallest(10, "avg_risk")[["city", "state", "avg_risk"]]
print(safest.to_string(index=False))
Surprise: Milwaukee, WI and Columbus, OH rank among the safest — low earthquake, hurricane, and wildfire risk. The tradeoff? Winter weather risk, which is real but more manageable than natural disasters.
3. The Most "Specialized" Risk Cities
Some cities have one dominant risk that dwarfs everything else:
df["risk_spread"] = df[risk_cols].max(axis=1) - df[risk_cols].min(axis=1)
specialized = df.nlargest(5, "risk_spread")[["city", "state"] + risk_cols[:6]]
print(specialized.to_string(index=False))
Key insight: San Francisco's earthquake risk (80+) is 6x higher than its next-hazard. Miami's hurricane risk (90+) overshadows everything else. These cities are "single-hazard" dominated — one big risk, relatively safe from everything else.
4. Risk vs. Population Growth
Here's the interesting part. I cross-referenced with population growth data:
| City | Risk Level | Population Trend | Implication |
|---|---|---|---|
| Phoenix, AZ | Very High (heat, drought) | 📈 Fastest growing | People move TO high-risk areas |
| Jacksonville, FL | High (hurricane) | 📈 Growing | Affordable = attractive |
| Austin, TX | Moderate | 📈 Growing | Good risk/reward balance |
| Detroit, MI | Low | 📉 Declining | Safe but economically struggling |
The paradox: Americans are actively moving INTO higher-risk areas. Phoenix has extreme heat (90+) and drought (85+) yet is the fastest-growing city in the US.
5. Heat Risk Is the Silent Crisis
high_heat = df[df["heat"] >= 70].sort_values("heat", ascending=False)
print(high_heat[["city", "state", "heat"]].to_string(index=False))
Heat risk is the most under-discussed environmental hazard. While hurricanes and earthquakes make headlines, extreme heat kills more Americans annually than any other weather event. And the cities most at risk — Phoenix, Las Vegas, San Antonio — are precisely where population is growing fastest.
6. Visualization: Risk Heatmap
import seaborn as sns
plt.figure(figsize=(14, 8))
top20 = df.nlargest(20, "avg_risk")
heatmap_data = top20.set_index("city")[risk_cols]
sns.heatmap(heatmap_data, annot=True, fmt="g", cmap="YlOrRd", linewidths=0.5)
plt.title("Environmental Risk Scores: Top 20 Most At-Risk US Cities")
plt.ylabel("")
plt.tight_layout()
plt.savefig("risk_heatmap.png", dpi=150)
plt.show()
This heatmap makes it immediately clear: risk isn't uniform. Each city has a unique "fingerprint" of vulnerabilities.
What Can You Do With This Data?
Beyond analysis, here are practical applications:
- Home buyers: Check risk scores before purchasing — a $10k discount isn't worth it if flood risk is 90
- Real estate platforms: Add risk badges to listings
- Insurance: Factor environmental risk into pricing models
- Relocation tools: Help users find cities matching their risk tolerance
The data comes from ProtectMyZip's city-level risk profiles, which provide free environmental risk scores for every US zip code, city, county, and state. The API is open and requires no authentication for basic usage.
Key Takeaways
- Houston is the riskiest major city — triple threat from hurricanes, flooding, and heat
- Midwestern cities are the safest — Columbus, Milwaukee, Indianapolis have low overall risk
- Americans are moving toward risk — Phoenix, Jacksonville, and other high-risk cities are growing fastest
- Heat is the underestimated hazard — more deadly than hurricanes, yet rarely factored into relocation decisions
- Every city has a unique risk profile — you can't just "avoid California for earthquakes" — there are 11 dimensions of risk
All the code above is runnable as-is. Plug in your own city list and start analyzing.
What's the environmental risk profile of YOUR city? You can check it at your city's full risk report.
Top comments (0)