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.
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
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.
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.
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.
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 |