Tanya Shapiro
  • Home
  • About
  • Talks
  • Blog
  • Projects

US Tornado Outbreaks

Observable
interactive
TidyTuesday
Part of the TidyTuesday series. Project exploring tornado outbreaks reported across the United States. Data source NOAA.

Where do tornado outbreaks usually occur?

Density map tracking tornado outbreaks countrywide from 1950 - 2022. This visual shows hotspots of tornado outbreaks over the past 70 years, drawing focus to what meteorologists commonly refer to as Tornado Alley: a region in the central United States known for its higher frequency and severity of tornadoes due to the collision of contrasting air masses and favorable atmospheric conditions.

viewof form = Inputs.form({
  bins: Inputs.range([0, 50], {label: "Bins", step: 1, value:11}),
  minYear: Inputs.range([1950, 2022], {label: "Min Year", step: 1, value:1950}),
  maxYear: Inputs.range([1950, 2022], {label: "Max Year", step: 1, value:2022}),
})
tornados = FileAttachment("data/tornados.csv").csv({typed: true});
usGeo = d3.json('https://raw.githubusercontent.com/codeforgermany/click_that_hood/main/public/data/united-states.geojson');

function getColor(palette) {
 return  palette === "YlOrRd" ? d3.schemeYlOrRd[5][2] :   
   palette === "YlGnBu" ? d3.schemeYlGnBu[5][2] : 
   palette === "BuPu" ? d3.schemeBuPu[5][2] : 
   palette === "Reds" ? d3.schemeReds[5][2] : 
     "black";
}
cities = [
  { name: "Houston", lat: 29.7604, lon: -95.3698, color:'black'},
  { name: "Tampa", lat: 27.9506, lon: -82.4572, color:'black'},
  {name: "Denver", lat: 39.7392,lon: -104.9903, color:'black'},
  {name: "Oklahoma City", lat:35.4676, lon:-97.5164, color:'white'},
  {name: "Jackson", lat:32.2988, lon:-90.1848, color:'black'},
  {name:"Dallas", lat:32.7767, lon:-96.7970, color:'black'},
  {name:"Tulia", lat:34.5359, lon:-101.7585, color:'black'},
  {name:"Great Bend", lat:38.3645, lon:-98.7648, color:'black'}
];
Plot = import("https://esm.sh/@observablehq/plot")
main_map = Plot.plot({
  width:1000,
  marginTop:50,
  projection: "albers",
  color: {scheme: 'YlOrRd'},
  style: {background:"transparent"},
  marks: [
    Plot.geo(usGeo, {fill:"#DDD"}),
    Plot.density(filtered, {x: "slon", y: "slat", bandwidth: form.bins, fill: "density", opacity:.4}),
    Plot.geo(usGeo, {fill:"transparent", stroke:"#FFF", opacity:0.6}),
    Plot.dot(cities, {x:"lon", y:"lat", text:"name", fill:"black", r:2}),
    Plot.text(cities, {x:"lon", y:"lat",  text:"name", fontSize:12, dx: -10, dy:10, fill:"black"})

  ]
})

Outbreaks by Decade

Have the outbreak patterns remained consistent countrywide or do they vary? Beginning, appears outbreaks are more concentrated in mid central territory spanning between Texas and Nebraska. Later decades, south east grows in frequency of tornado outbreaks too (Mississippi, Alabama, and Florida most notable).

decade_map = Plot.plot({
  width:1200,
  projection: "albers",
  color: {scheme: 'YlOrRd'},
  style: {backgroundColor:'transparent'},
  facet: {data:filtered, y:"row_index", x:"col_index", label:null},
  marks: [
    Plot.geo(usGeo, {fill:"#DDD"}),
    Plot.density(filtered, {x: "slon", y: "slat", bandwidth: 5, fill: "density", opacity:.4}),
    Plot.geo(usGeo, {fill:"transparent", stroke:"#FFF", opacity:0.6}),
    Plot.text(decades, {y: d=> 52, x: d=> -98, 
                        text: "decade",
                        fx:"col_index", fy:"row_index", fontWeight:"bold", fontSize:14})
  ]
})

When do tornado outbreaks usually occur?

April, May, and June appear to be peak season for tornadoes. The highest count of tornadoes occurred in April 2011, also known as the 2011 Super Outbreak. It is considered one of the largest, deadliest, and most intense tornado outbreaks in American history. The outbreak lasted for four days, from April 25 to April 28, and affected multiple states across the southeastern and central United States.

Plot.plot({
  color: {scheme: 'YlOrRd'},
   style: {background:"transparent"},
   marginBottom:100,
  x: {label:null},
  y: {label: null},
  width:1200,
  height:450,
  marks: [
    Plot.cell(calendar, {y:"month", x:"year", fill:"tornados",  inset: 0.2, stroke:"#DDD", strokeWidth:0.2}),
    Plot.text(calendar, {x:"year", y:"month", fontSize:11,
                         text: d=> d.tornados>250 ? d.tornados : '', 
                         fill: d=> d.tornados>350 ? "white" : "black"}),
    Plot.axisY({tickSize:0, fontSize:12,
                ticks:d3.range(1, 13),
                text:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}
                ),
    Plot.axisX({tickSize:0, fontSize:12,
                text:["1992","'93","'94","'95","'96","'97","'98","'99","2000","'01","'02",
                     "'03","'04","'05","'06","'07","'08","'09","'10","'11","'12","'13","'14","'15","'16",
                     "'17","'18","'19","'20","'21","'22"]})
  ]
})
Plot.plot({
  width:1200,
  y: {tickSize:0, grid:true},
  x: {tickSize:0, label:"Year"},
  style: {backgroundColor:'transparent'},
  marks: [
    Plot.areaY(tornados, Plot.groupX({y:"count"}, {x:"yr",opacity:0.5, fill:getColor('YlOrRd')})),
    Plot.line(tornados, Plot.groupX({y:"count"}, {x:"yr", strokeWidth:2, stroke:getColor('YlOrRd')})),
    Plot.ruleY([0]),
    Plot.axisX({label: null, fontSize:12,tickSize:0, tickFormat: d3.format("d")}),
    Plot.axisY({fontSize:12})

  ]
})
db = DuckDBClient.of({ 
    tornados: await FileAttachment("data/tornados.csv").csv(),
})
filtered = db.sql`SELECT t. *
  ,floor(yr::int/10)*10 as decade
  ,CASE WHEN floor(yr::int/10)*10<=1970 THEN 1 
  WHEN floor(yr::int/10)*10<=2000 THEN 2
  ELSE 3 END AS row_index
  ,CASE 
  WHEN floor(yr::int/10)*10 IN (1950,1980, 2010) THEN 1
  WHEN floor(yr::int/10)*10 IN (1960,1990, 2020) THEN 2
  WHEN floor(yr::int/10)*10 IN (1970,2000) THEN 3
  ELSE 4 end as col_index
FROM tornados t
WHERE yr>=${form.minYear} and yr<=${form.maxYear}`
decades = db.sql`SELECT 
distinct SUBSTRING(cast(floor(yr::int/10)*10 as char),1,4) || 's' as decade
  ,CASE WHEN floor(yr::int/10)*10<=1970 THEN 1 
  WHEN floor(yr::int/10)*10<=2000 THEN 2
  ELSE 3 END AS row_index
  ,CASE 
  WHEN floor(yr::int/10)*10 IN (1950,1980, 2010) THEN 1
  WHEN floor(yr::int/10)*10 IN (1960,1990, 2020) THEN 2
  WHEN floor(yr::int/10)*10 IN (1970,2000) THEN 3
  ELSE 4 end as col_index
  from tornados`
calendar = db.sql`SELECT 
  year as year
  , month as month
  , COALESCE(tornados,0) as tornados
FROM 
  (SELECT distinct mo as month from tornados) m
CROSS JOIN 
  (SELECT distinct yr as year from tornados where yr>=1992) y
LEFT JOIN
  (SELECT yr, mo, count(*) as tornados from tornados group by 1,2) t on t.yr=y.year and m.month = t.mo
ORDER BY year, month`