Synopsis

Every year, extreme weather claims hundreds of lives and causes billions of dollars in damage. Using comprehensive data from the National Oceanic and Atmospheric Administration, we can identify which types of atmospheric events have the greatest negative impact on both population health and economic well-being. To do that, this analysis will extract a subset of the official NOAA data set, clean it (including correcting typos), compile resultant health and economic findings, and then parse them on an aggregate and per-event basis. Our results show the top 5 most harmful event types for both population health and economic damage, both in-total and per-event.

Dependencies

The imports below cover all the packages that are necessary for this analysis:

library(dplyr, warn.conflicts = FALSE)
library(stringdist, warn.conflicts = FALSE)
## Warning: package 'stringdist' was built under R version 4.0.3
library(scales)
library(reshape2)
## Warning: package 'reshape2' was built under R version 4.0.3
library(ggplot2)
library(gt)
## Warning: package 'gt' was built under R version 4.0.3

Data Processing

Before any analysis can begin, we must download and read in the required data. The following code snippet does just that.

# Download data + supporting documentation:
data_path <- 'storm_data.csv.bz2'
if (!file.exists(data_path)) {
  dat_url <- 'https://d396qusza40orc.cloudfront.net/repdata%2Fdata%2FStormData.csv.bz2'
  download.file(dat_url, data_path)
  rm(dat_url)
}

documentation_path <- 'storm_data_documentation.pdf'
if (!file.exists(documentation_path)) {
  doc_url <- 'https://d396qusza40orc.cloudfront.net/repdata%2Fpeer2_doc%2Fpd01016005curr.pdf'
  download.file(doc_url, documentation_path, mode = 'wb')
  rm(doc_url)
}

# Load data:
raw_data <- read.csv(data_path)
str(raw_data)
## 'data.frame':    902297 obs. of  37 variables:
##  $ STATE__   : num  1 1 1 1 1 1 1 1 1 1 ...
##  $ BGN_DATE  : chr  "4/18/1950 0:00:00" "4/18/1950 0:00:00" "2/20/1951 0:00:00" "6/8/1951 0:00:00" ...
##  $ BGN_TIME  : chr  "0130" "0145" "1600" "0900" ...
##  $ TIME_ZONE : chr  "CST" "CST" "CST" "CST" ...
##  $ COUNTY    : num  97 3 57 89 43 77 9 123 125 57 ...
##  $ COUNTYNAME: chr  "MOBILE" "BALDWIN" "FAYETTE" "MADISON" ...
##  $ STATE     : chr  "AL" "AL" "AL" "AL" ...
##  $ EVTYPE    : chr  "TORNADO" "TORNADO" "TORNADO" "TORNADO" ...
##  $ BGN_RANGE : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ BGN_AZI   : chr  "" "" "" "" ...
##  $ BGN_LOCATI: chr  "" "" "" "" ...
##  $ END_DATE  : chr  "" "" "" "" ...
##  $ END_TIME  : chr  "" "" "" "" ...
##  $ COUNTY_END: num  0 0 0 0 0 0 0 0 0 0 ...
##  $ COUNTYENDN: logi  NA NA NA NA NA NA ...
##  $ END_RANGE : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ END_AZI   : chr  "" "" "" "" ...
##  $ END_LOCATI: chr  "" "" "" "" ...
##  $ LENGTH    : num  14 2 0.1 0 0 1.5 1.5 0 3.3 2.3 ...
##  $ WIDTH     : num  100 150 123 100 150 177 33 33 100 100 ...
##  $ F         : int  3 2 2 2 2 2 2 1 3 3 ...
##  $ MAG       : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ FATALITIES: num  0 0 0 0 0 0 0 0 1 0 ...
##  $ INJURIES  : num  15 0 2 2 2 6 1 0 14 0 ...
##  $ PROPDMG   : num  25 2.5 25 2.5 2.5 2.5 2.5 2.5 25 25 ...
##  $ PROPDMGEXP: chr  "K" "K" "K" "K" ...
##  $ CROPDMG   : num  0 0 0 0 0 0 0 0 0 0 ...
##  $ CROPDMGEXP: chr  "" "" "" "" ...
##  $ WFO       : chr  "" "" "" "" ...
##  $ STATEOFFIC: chr  "" "" "" "" ...
##  $ ZONENAMES : chr  "" "" "" "" ...
##  $ LATITUDE  : num  3040 3042 3340 3458 3412 ...
##  $ LONGITUDE : num  8812 8755 8742 8626 8642 ...
##  $ LATITUDE_E: num  3051 0 0 0 0 ...
##  $ LONGITUDE_: num  8806 0 0 0 0 ...
##  $ REMARKS   : chr  "" "" "" "" ...
##  $ REFNUM    : num  1 2 3 4 5 6 7 8 9 10 ...

As we can see, the raw data is quite large and contains many fields which we will not need for our analysis. As such, we can reduce the size of our data set (and thereby improve run times) by selecting only those columns which we actually need to answer our proposed research questions. Nominally, these are: starting dates (BGN_DATE), event types (EVTYPE), fatalities (FATALITIES), injuries (INJURIES), property damage (PROPDMG + PROPDMGEXP), and crop damage (CROPDMG + CROPDMGEXP).

While we’re doing this, we can also perform some preliminary data transformations to simplify our downstream analysis and enhance data readability. The transformations performed will be as follows: (1) convert BGN_DATE to proper “Date” format and (2) scale PROPDMG and CROPDMG values according to the letter exponents found in PROPDMGEXP, CROPDMGEXP respectively (‘k’ = 10^3, ‘m’ = 10^6, ‘b’ = 10^9).

# create a conversion map for exponent calculations:
exp_conv_map <- data.frame(K = c('k', 'm', 'b'), V = c(10^3, 10^6, 10^9))
calculate_damage <- Vectorize(function(value, exponent) {
  exponent <- tolower(exponent)
  if (exponent %in% c('k', 'm', 'b')) {
    # multiply value by appropriate power of 10
    value <- exp_conv_map[exp_conv_map$K == exponent, ]$V * value
  }
  value
})

# Extract and format columns of interest:
storm_data <- raw_data %>%
  mutate(Date = as.Date(BGN_DATE, format = '%m/%d/%Y'), 
         Event.Type = toupper(trimws(EVTYPE)), 
         Fatalities = FATALITIES, 
         Injuries = INJURIES, 
         Property.Damage = calculate_damage(PROPDMG, PROPDMGEXP),
         Crop.Damage = calculate_damage(CROPDMG, CROPDMGEXP)) %>%
  select(Date, Event.Type, Fatalities, Injuries, Property.Damage, Crop.Damage)

At this point, we have a usable storm_data data set that contains only the information we need to answer the questions we are interested in. However, if we look at the number of levels contained in the Event.Type column, we see it’s considerably more than the 48 we expect to see according to the provided documentation.

length(unique(storm_data$Event.Type))
## [1] 890

All of this is down to typos! So how might we go about correcting this discrepancy? One way is to use the approximate string matching function amatch, found in the stringdist package. This will allow us to do a fuzzy replacement from the given, typo-heavy list of event types to a curated list of expected values. Just such a list can be found in section 2.1.1 of the provided documentation.

Before doing so, however, we will perform a few more preliminary cuts first to correct as much of the low-hanging fruit as possible and remove any standard abbreviations that might be present in the Event.Type variable.

# Reject summary and county records:
storm_data <- storm_data %>%
  filter(!grepl('.*\\bsummary\\b.*', Event.Type, ignore.case = TRUE)) %>%
  filter(!grepl('.*\\bcounty\\b.*', Event.Type, ignore.case = TRUE))

# Remove non-alphabetical chars (excl. '/', '?') + extra spaces from Event.Type:
storm_data <- storm_data %>%
  mutate(Event.Type = gsub('[^[:alpha:][:space:]/?]+', '', Event.Type)) %>%
  mutate(Event.Type = trimws(gsub('[:space:][:space:]+', ' ', Event.Type)))

# Remove common abbreviations:
storm_data <- storm_data %>%
  mutate(Event.Type = gsub('TSTM', 'THUNDERSTORM', Event.Type))

At this point, we can again check the number of unique event types to see how effective our efforts have been thus far.

length(unique(storm_data$Event.Type))
## [1] 732

Pretty good so far. Now it’s time to perform our fuzzy replacement described above.

# Fuzzy match valid event types
expected <- toupper(c(
  'Astronomical Low Tide',
  'Avalanche',
  'Blizzard',
  'Coastal Flood',
  'Cold/Wind Chill',
  'Debris Flow',
  'Dense Fog',
  'Dense Smoke',
  'Drought',
  'Dust Devil',
  'Dust Storm',
  'Excessive Heat',
  'Extreme Cold/Wind Chill',
  'Flash Flood',
  'Flood',
  'Frost/Freeze',
  'Funnel Cloud',
  'Freezing Fog',
  'Hail',
  'Heat',
  'Heavy Rain',
  'Heavy Snow',
  'High Surf',
  'High Wind',
  'Hurricane (Typhoon)',
  'Ice Storm',
  'Lake-Effect Snow',
  'Lakeshore Flood',
  'Lightning',
  'Marine Hail',
  'Marine High Wind',
  'Marine Strong Wind',
  'Marine Thunderstorm Wind',
  'Rip Current',
  'Seiche',
  'Sleet',
  'Storm Surge/Tide',
  'Strong Wind',
  'Thunderstorm Wind',
  'Tornado',
  'Tropical Depression',
  'Tropical Storm',
  'Tsunami',
  'Volcanic Ash',
  'Waterspout',
  'Wildfire',
  'Winter Storm',
  'Winter Weather'
))
i <- amatch(storm_data$Event.Type, 
            expected,
            method = 'jw',
            p = 0.1,
            maxDist = 0.08)
replace_typos <- Vectorize(function(index) {
  suggestion <- expected[index]
  if (is.na(suggestion)) {
    suggestion <- 'OTHER'
  }
  suggestion
})
typo_map <- data.frame(before = storm_data$Event.Type, after = replace_typos(i)) %>%
  group_by(before) %>%
  summarize(replacements = unique(after), .groups = 'drop')

After running the above code, we’re left with a data.frame called typo_map, which contains the proposed replacement for each of the (potentially malformed) event types present in the original storm_data data set. Let’s look at this now to see if our replacement algorithm is working as intended:

typo_map[typo_map$replacements != 'OTHER', ]
## # A tibble: 157 x 2
##    before                replacements         
##    <chr>                 <chr>                
##  1 ASTRONOMICAL LOW TIDE ASTRONOMICAL LOW TIDE
##  2 AVALANCE              AVALANCHE            
##  3 AVALANCHE             AVALANCHE            
##  4 BLIZZARD              BLIZZARD             
##  5 COASTAL FLOOD         COASTAL FLOOD        
##  6 COASTAL FLOODING      COASTAL FLOOD        
##  7 COASTALFLOOD          COASTAL FLOOD        
##  8 COLD/WIND CHILL       COLD/WIND CHILL      
##  9 DENSE FOG             DENSE FOG            
## 10 DENSE SMOKE           DENSE SMOKE          
## # ... with 147 more rows

It seems so from the results above. Note, however, that we only displayed those event types which we were successfully able to categorize. The others were shoved into a single ‘OTHER’ category. How much of our original data falls into this category?

storm_data <- storm_data %>%
  mutate(Event.Type = as.factor(replace_typos(i)))

paste(sum(storm_data$Event.Type == 'OTHER'),
      percent(mean(storm_data$Event.Type == 'OTHER'),
              accuracy = 0.1),
      sep = ' - ')
## [1] "13508 - 1.5%"

So approximately 1.5% of the records in our original data set could not be categorized through the above method. How much does this 1.5% matter for the variables we are interested in?

count_other <- function(x) {
  percent(sum(x[storm_data$Event.Type == 'OTHER']) / sum(x),
          accuracy = 0.1)
}

data.frame(Fatalities = count_other(storm_data$Fatalities),
           Injuries = count_other(storm_data$Injuries),
           Property.Damage = count_other(storm_data$Property.Damage),
           Crop.Damage = count_other(storm_data$Crop.Damage))
##   Fatalities Injuries Property.Damage Crop.Damage
## 1       7.1%     2.5%            7.3%       22.2%

As we can see, anywhere between 2.5-7.3% of our total fatilities, injuries, and property damage are contained in these ‘OTHER’ records. The big outlier is crop damage, where our typo correction algorithm fails to categorize over 20% of our overall data. As a result, we might consider referencing the raw data itself when answering questions about this facet of our analysis. For now, however, we have arrived at our final data set.

Across the United States, which types of events are most harmful with respect to population health?

From the above, cleaned storm_data data set, we can extract a pop_health_total data frame that contains only the sum total of fatalities and injuries for each event type. Since we’re only interested in the top contributors, we can set an overall public health impact (injuries + fatalities) cut at the 75th percentile, meaning we will be left with only those event types which fall in the top 25% of overall impact.

pop_health_total <- storm_data %>%
  group_by(Event.Type) %>%
  summarize(Fatalities = sum(Fatalities), 
            Injuries = sum(Injuries), 
            .groups = 'drop') %>%
  filter(Injuries + Fatalities > quantile(Injuries + Fatalities, probs = 0.75)) %>%
  arrange(desc(Injuries + Fatalities))
head(pop_health_total)
## # A tibble: 6 x 3
##   Event.Type        Fatalities Injuries
##   <fct>                  <dbl>    <dbl>
## 1 TORNADO                 5633    91364
## 2 THUNDERSTORM WIND        711     9508
## 3 EXCESSIVE HEAT          1903     6525
## 4 FLOOD                    476     6791
## 5 LIGHTNING                817     5230
## 6 OTHER                   1074     3469

These are the results of doing so, arranged according to their overall impact. As we can see just from this step alone, tornadoes stand head and shoulders above the others as far as overall impact on population health is concerned. We can also observe that the proportion of injuries to fatalities varies based on event type, with some events producing a significantly higher amount of fatalities than others. We can visualize our results with a simple bar chart:

m_pop_health_total <- melt(pop_health_total, id.vars = c('Event.Type'))
m_pop_health_total <- m_pop_health_total %>%
  group_by(Event.Type) %>%
  arrange(desc(sum(value)))
# do a level switcharoo to prevent ggplot from reordering our event types
levels <- unique(droplevels(m_pop_health_total$Event.Type))
m_pop_health_total$Event.Type <- factor(m_pop_health_total$Event.Type,
                                         levels = levels)

g <- ggplot(data = m_pop_health_total)
g + 
  geom_col(aes(x = Event.Type, 
               y = value,
               fill = variable)) +
  coord_cartesian(ylim = c(0, 15000)) + 
  labs(x = 'Event Type',
       y = 'Total Health Impact',
       title = 'Total Health Impact (Injuries + Fatalities) By Event Type Since 1950') + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
        legend.title = element_blank())

And these are the results of doing so, showing the total health impact of the top 25% most deadly weather events since 1950 (i.e. all those contained in our storm_data data set). I’ve cropped the y-axis in this plot in order to remove the TORNADO event peak. This was done because it is such a large outlier that it would obscure the activity of the lower-impact events.

In addition to total health impact, we may also be interested in the average impact of a single instance of an event type (for instance, how deadly, on average, is a single tornado?). We can easily determine such a thing by switching our summarization metric from sum to mean, as below:

pop_health_avg <- storm_data %>%
  group_by(Event.Type) %>%
  summarize(Fatalities = mean(Fatalities),
            Injuries = mean(Injuries),
            .groups = 'drop') %>%
  filter(Injuries + Fatalities > quantile(Injuries + Fatalities, probs = 0.75)) %>%
  arrange(desc(Injuries + Fatalities))
head(pop_health_avg)
## # A tibble: 6 x 3
##   Event.Type          Fatalities Injuries
##   <fct>                    <dbl>    <dbl>
## 1 HURRICANE (TYPHOON)     0.727    14.5  
## 2 TSUNAMI                 1.65      6.45 
## 3 EXCESSIVE HEAT          1.13      3.88 
## 4 HEAT                    1.22      2.74 
## 5 TORNADO                 0.0928    1.51 
## 6 RIP CURRENT             0.739     0.683

So when we look at population health impact on a per-event basis, we see that rarer, more extreme events (tsunamis, hurricanes, and heatwaves) tend to have greater detrimental effects on population health. This makes intuitive sense, since such events effect larger geographic areas, contain much more energy, and are typically longer duration than their less severe alternatives. Can we make another plot showing this relationship graphically?

m_pop_health_avg <- melt(pop_health_avg, id.vars = c('Event.Type'))
m_pop_health_avg <- m_pop_health_avg %>%
  group_by(Event.Type) %>%
  arrange(desc(sum(value)))
# level switcharoo to prevent ggplot shenanigans:
levels <- unique(droplevels(m_pop_health_avg$Event.Type))
m_pop_health_avg$Event.Type <- factor(m_pop_health_avg$Event.Type,
                                      levels = levels)

g <- ggplot(data = m_pop_health_avg)
g + 
  geom_col(aes(x = Event.Type, 
               y = value,
               fill = variable)) +
  labs(x = 'Event Type',
       y = 'Average Health Impact',
       title = 'Average Health Impact (Injuries + Fatalities) By Event Type Since 1950') + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
        legend.title = element_blank())

Indeed we can, in the same fashion as our total health impact plot, the only difference here being that the plotted results depict the typical health impact of a single instance of its respective event type (i.e. a single hurricane, or a single tornado).

So, to answer our original research question, we can see that, in aggregate, the casualty crown is taken by tornadoes, while individually, hurricanes and tsunamis come out on top.

Across the United States, which types of events are most harmful with respect to economic damage?

From our cleaned, storm_data data set, we can follow the same approach as above to extract subsets describing the economic impacts (property damage, crop damage) of the various event types in that data set. We’ll call these data sets econ_damage_total and econ_damage_avg for in-total and per-event measures, respectively. Since we are only interested in worst offenders, we can similarly restrict ourselves to the top 25% of event types.

econ_damage_total <- storm_data %>%
  group_by(Event.Type) %>%
  summarize(Property.Damage = sum(Property.Damage), 
            Crop.Damage = sum(Crop.Damage), 
            .groups = 'drop') %>%
  filter(Property.Damage + Crop.Damage > quantile(Property.Damage + Crop.Damage, probs = 0.75)) %>%
  arrange(desc(Property.Damage + Crop.Damage))

econ_damage_avg <- storm_data %>%
  group_by(Event.Type) %>%
  summarize(Property.Damage = mean(Property.Damage),
            Crop.Damage = mean(Crop.Damage),
            .groups = 'drop') %>%
  filter(Property.Damage + Crop.Damage > quantile(Property.Damage + Crop.Damage, probs = 0.75)) %>%
  arrange(desc(Property.Damage + Crop.Damage))

head(econ_damage_total)
## # A tibble: 6 x 3
##   Event.Type          Property.Damage Crop.Damage
##   <fct>                         <dbl>       <dbl>
## 1 FLOOD                 144771964813   5670873950
## 2 HURRICANE (TYPHOON)    69305840000   2607872800
## 3 TORNADO                56941932479.   414961470
## 4 STORM SURGE/TIDE       47964724000       855000
## 5 OTHER                  31042386851. 10892264160
## 6 HAIL                   15732819048.  3026044473
head(econ_damage_avg)
## # A tibble: 6 x 3
##   Event.Type          Property.Damage Crop.Damage
##   <fct>                         <dbl>       <dbl>
## 1 HURRICANE (TYPHOON)      787566364.   29634918.
## 2 STORM SURGE/TIDE         117273164.       2090.
## 3 TROPICAL STORM            11067992.     996981.
## 4 TSUNAMI                    7203100        1000 
## 5 DROUGHT                     420461.    5615983.
## 6 FLOOD                      5688486.     222824.

As before with the population health impacts, we can make a few immediate observations. First, floods are our clear winners for overall economic damage, and that the impact signatures of different event types varies even more than for our population health data set. Storm surges, for instance, generate very little crop damage, but lots of property damage. This makes sense when one considers that farmland is usually not located on shorelines, where storm surges primarily affect, but rather are set inland a ways, where their damaging effects are limited by distance and can be mitigated somewhat by irrigation infrastructure.

On a per-event basis, we see the same (expected) relationship between event severity and economic damage as we saw with population health. This time, again, we see that hurricanes are the heavyweights, causing the most damage by what appears close to an order of magnitude! As we did with population health, let’s make another pair of plots showing this relationship.

econ_damage_total$Metric <- rep('Total', times = nrow(econ_damage_total))
econ_damage_avg$Metric <- rep('Average', times = nrow(econ_damage_avg))
econ_damage <- rbind(econ_damage_total, econ_damage_avg)
m_econ_damage <- melt(econ_damage, id.vars = c('Event.Type', 'Metric'))

# level re-ordering
levels <- unique(droplevels(m_econ_damage$Event.Type))
m_econ_damage$Event.Type <- factor(m_econ_damage$Event.Type, levels = levels)

g <- ggplot(data = m_econ_damage)
g + 
  facet_wrap(. ~ Metric, scales = 'free') + 
  geom_col(aes(x = Event.Type, 
               y = value,
               fill = variable)) +
  labs(x = 'Event Type',
       y = 'Economic Impact',
       title = 'Economic Impact (Property + Crop Damage) By Event Type Since 1950') + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1),
        legend.position = 'top', 
        legend.title = element_blank())

These plots show just how extreme the in-total vs aggregate measures of economic damage really are. Hurricanes are several orders of magnitude ahead of almost every other event type as far as per-event (average) economic damage is concerned, but they fall considerably behind common flooding when taken in total. Buy flood insurance!

In answer to our stated research question, it’s clear that flooding has, in aggregate, the greatest overall economic impact, while hurricanes have the greatest on a per-event basis.

Results

Our results report the top 5 most harmful events, both in-total and per-event, to population health and economic security. They are listed below:

options(scipen = 999)
# Health Results:
pop_health_results <- rbind(head(pop_health_total, 5),
                            head(pop_health_avg, 5))
pop_health_results$Fatalities <- round(pop_health_results$Fatalities,
                                      digits = 2)
pop_health_results$Injuries <- round(pop_health_results$Injuries,
                                     digits = 2)
names(pop_health_results) <- c('Event Type (health)', 
                               'Fatalities', 
                               'Injuries')
# Economic Results
econ_damage_results <- subset(rbind(head(econ_damage_total, 5),
                                    head(econ_damage_avg, 5)), 
                              select = -Metric)
econ_damage_results$Property.Damage <- paste0('$',
  prettyNum(round(econ_damage_results$Property.Damage, digits = 0),
  big.mark = ',', 
  scientific = FALSE))
econ_damage_results$Crop.Damage <- paste0('$',
  prettyNum(round(econ_damage_results$Crop.Damage, digits = 0),
  big.mark = ',',
  scientific = FALSE))
names(econ_damage_results) <- c('Event Type (econ)', 
                                'Property Damage', 
                                'Crop Damage')

ranks <- append(1:5, 1:5)
results <- cbind(Rank = ranks, pop_health_results, econ_damage_results)

tab <- gt(data = results)
tab %>%
  tab_header(title = 'Most Harmful Event Types') %>%
  cols_align(align = 'left') %>%
  tab_spanner(label = 'Population Health', 
              columns = c('Event Type (health)', 
                          'Fatalities', 
                          'Injuries')) %>%
  tab_spanner(label = 'Economic Damage',
              columns = c('Event Type (econ)', 
                          'Property Damage', 
                          'Crop Damage')) %>%
  tab_row_group(group = 'In-Total', rows = 1:5) %>%
  tab_row_group(group = 'Per-Event', rows = 6:10) %>%
  tab_style(style = cell_text(color = 'dimgray', align = 'center'),
            locations = cells_row_groups()) %>%
  tab_style(style = cell_text(weight = 'bold'),
            locations = cells_column_labels(c('Rank',
                                              'Event Type (health)',
                                              'Fatalities',
                                              'Injuries',
                                              'Event Type (econ)',
                                              'Property Damage',
                                              'Crop Damage'))) %>%
  tab_style(style = cell_text(weight = 500),
            locations = cells_body(columns = c('Event Type (health)',
                                               'Event Type (econ)'))) %>%
  tab_style(style = cell_text(weight = 'bold'),
            locations = cells_title())
Most Harmful Event Types
Rank Population Health Economic Damage
Event Type (health) Fatalities Injuries Event Type (econ) Property Damage Crop Damage
Per-Event
1 HURRICANE (TYPHOON) 0.73 14.49 HURRICANE (TYPHOON) $787,566,364 $29,634,918
2 TSUNAMI 1.65 6.45 STORM SURGE/TIDE $117,273,164 $2,090
3 EXCESSIVE HEAT 1.13 3.88 TROPICAL STORM $11,067,992 $996,981
4 HEAT 1.22 2.74 TSUNAMI $7,203,100 $1,000
5 TORNADO 0.09 1.51 DROUGHT $420,461 $5,615,983
In-Total
1 TORNADO 5633.00 91364.00 FLOOD $144,771,964,813 $5,670,873,950
2 THUNDERSTORM WIND 711.00 9508.00 HURRICANE (TYPHOON) $69,305,840,000 $2,607,872,800
3 EXCESSIVE HEAT 1903.00 6525.00 TORNADO $56,941,932,479 $414,961,470
4 FLOOD 476.00 6791.00 STORM SURGE/TIDE $47,964,724,000 $855,000
5 LIGHTNING 817.00 5230.00 OTHER $31,042,386,851 $10,892,264,160