The dataset I will be using for this project is titled, “FloodNet: Street Flooding Events Measured by FloodNet Sensors” from the NYC Open Data website1. The dataset contains information on flood events in New York City. A flood event is defined as a series of water depth measurements greater than 10 mm. There are 3,422 rows and 13 columns where each row is one flood event per sensor. Variables include sensor name, sensor ID (ID used to identify sensor location), flood event start time, flood event end time, maximum flood depth measured, the time in minutes it took for the flood to reach max flood depth, time in minute for flood to drain, total duration of flood event, and duration of flooding greater than 4, 12, and 24 inches. In this project, I want to determine which borough experiences the most severe floods.
Loading the Data and Libraries
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.5
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.5.1 ✔ tibble 3.2.1
✔ lubridate 1.9.4 ✔ tidyr 1.3.1
✔ purrr 1.0.4
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag() masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Warning: One or more parsing issues, call `problems()` on your data frame for details,
e.g.:
dat <- vroom(...)
problems(dat)
Rows: 3422 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (6): Sensor Name, Sensor ID, Flood Start Datetime (GMT), Flood End Datet...
dbl (5): Maximum Flood Depth (inches), Time to Maximum Flood Depth (minutes)...
num (2): Time to Drain From Peak (minutes), Total Duration (minutes)
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
floodnet
# A tibble: 3,422 × 13
`Sensor Name` `Sensor ID` Flood Start Datetime…¹ Flood End Datetime (…²
<chr> <chr> <chr> <chr>
1 Q - Beach 84 St Q-beach-84… 10/30/2023 12:00:39 PM 10/30/2023 04:38:19 PM
2 BX - 1st St/Avenue… BX-1st-st-… 04/26/2025 11:17:20 PM 04/27/2025 11:55:34 AM
3 Q - Beach 35 St/Be… Q-beach-35… 02/09/2024 11:41:01 AM 02/09/2024 01:20:57 PM
4 BX - Tibbett Ave/W… BX-w-234th… 08/20/2025 09:05:19 PM 08/21/2025 12:46:19 AM
5 Q - Davenport Ct 1 Q-davenpor… 01/10/2024 12:39:25 AM 01/10/2024 02:30:40 AM
6 BX - Ditmars St/Hu… BX-ditmars… 05/30/2024 01:28:01 AM 05/30/2024 09:18:11 AM
7 Q - Beach 35 St/Be… Q-beach-35… 03/31/2025 01:21:39 AM 03/31/2025 02:35:35 AM
8 BX - Tibbett Ave/W… BX-w-234th… 05/24/2026 06:59:07 AM 05/24/2026 08:19:06 PM
9 BX - Tibbett Ave/W… BX-w-234th… 06/18/2025 09:10:48 AM 06/18/2025 02:00:47 PM
10 Q - Russell St 1 Q-russell-… 01/17/2022 12:29:33 PM 01/17/2022 04:27:05 PM
# ℹ 3,412 more rows
# ℹ abbreviated names: ¹`Flood Start Datetime (GMT)`,
# ²`Flood End Datetime (GMT)`
# ℹ 9 more variables: `Maximum Flood Depth (inches)` <dbl>,
# `Time to Maximum Flood Depth (minutes)` <dbl>,
# `Time to Drain From Peak (minutes)` <dbl>,
# `Total Duration (minutes)` <dbl>, …
Cleaning The Data
#excluding irrelevant columnstidyfloods <- floodnet |>select(-c("Time Series Depth Values (inches)", "Time Series Depth Timestamps (seconds)", "Flood End Datetime (GMT)", "Flood Start Datetime (GMT)", "Sensor ID"))#change column names by removing spacesnames(tidyfloods) <-c("sensor_name", "max_flood_depth_in", "time_to_max_depth_mins", "time_to_drain_mins", "total_duration_mins", "duration_4in_mins", "duration_12in_mins", "duration_24in_mins")tidyfloods
For my linear regression, the total duration in minutes will be the dependent variable and the maximum flood depth in inches will be the independent variable. I anticipate that how long a flood is highly affects how tall the flood gets.
#linear regression model with maximum flood depth vs. total flood durationlm <-lm(max_flood_depth_in ~ total_duration_mins, data = tidyfloods)summary(lm)
Call:
lm(formula = max_flood_depth_in ~ total_duration_mins, data = tidyfloods)
Residuals:
Min 1Q Median 3Q Max
-20.876 -3.139 -1.430 1.302 41.481
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 4.2616520 0.1038594 41.03 <2e-16 ***
total_duration_mins 0.0053741 0.0003829 14.04 <2e-16 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 5.014 on 3420 degrees of freedom
Multiple R-squared: 0.05447, Adjusted R-squared: 0.0542
F-statistic: 197 on 1 and 3420 DF, p-value: < 2.2e-16
Equation: 0.0005(total duration) + 4.26
For each additional minute elapsed, there is a predicted increase of 0.0005 inches in flood depth. The p-value is incredibly significant but, according to the adjusted R-squared, only 5% of the variation in observations may be explained by the linear model.
Visualization
In front of the sensor name, each row contains an acronym. ‘BK’ represents Brooklyn, ‘BX’ represents the Bronx, ‘M’ stands for Manhattan, ‘Q’ stands for Queens, and ‘SI’ stands for Staten Island.2 Here I’ve created a new column to assign each sensor to a borough to continue my analysis.
#sort the dataset by which borough the sensor is located intidyfloods <- tidyfloods |>mutate(borough =case_when(startsWith(sensor_name, "SI") ~"Staten Island",startsWith(sensor_name, "M") ~"Manhattan",startsWith(sensor_name, "BX") ~"Bronx",startsWith(sensor_name, "BK") ~"Brooklyn",startsWith(sensor_name, "Q") ~"Queens" ) )
To measure which borough has the most severe floods, I will calculate the average maximum flood depth according to each borough.
#pivoting to a smaller dfavgdepth <- tidyfloods |>group_by(borough) |>summarise(avg_max_depth =mean(max_flood_depth_in))avgdepth
#bar graphavgdepth |>ggplot(aes(x = borough, y = avg_max_depth)) +geom_col(aes(fill = borough)) +scale_fill_brewer(palette ="Reds") +labs(title ="Average Flood Depth in New York City",fill ="",x ="Borough",y ="Avg. Max Flood Depth (inches)") +theme_minimal()
Conclusion
In conclusion, this bar graph shows that the average maximum measured flood depth in inches was highest in Queens and lowest in Manhattan. In other words, floods in Manhattan didn’t rise as high as they did in other areas with an average of 2.3 inches. In Queens, the calculated average is more than one inch over the other boroughs. I’m sure further research would be able to help us determine why this is. I cleaned up the dataset by making the column names simpler (removing spaces, making it all lowercase) which made it much easier to complete my data analysis. I originally wanted to visualize the difference between floods over 4 inches, floods over 12 inches, and floods over 24 inches. However, there was only a little bit of data for floods over 24 inches so it wouldn’t be a fair comparison.