This homework has two parts. Part 1 uses lubridate and factors on NYC flight data. Part 2 does a full EDA on the built-in airquality dataset.


Part 1 — NYC Flights: Dates and Factors

# install.packages(c("nycflights13", "lubridate", "zoo", "forcats"))  # if needed
library(nycflights13)
## Warning: package 'nycflights13' was built under R version 4.4.3
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(lubridate)
## 
## Attaching package: 'lubridate'
## The following objects are masked from 'package:base':
## 
##     date, intersect, setdiff, union
library(zoo)
## Warning: package 'zoo' was built under R version 4.4.3
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
library(forcats)
data(flights)

Quick look:

str(flights)
## tibble [336,776 × 19] (S3: tbl_df/tbl/data.frame)
##  $ year          : int [1:336776] 2013 2013 2013 2013 2013 2013 2013 2013 2013 2013 ...
##  $ month         : int [1:336776] 1 1 1 1 1 1 1 1 1 1 ...
##  $ day           : int [1:336776] 1 1 1 1 1 1 1 1 1 1 ...
##  $ dep_time      : int [1:336776] 517 533 542 544 554 554 555 557 557 558 ...
##  $ sched_dep_time: int [1:336776] 515 529 540 545 600 558 600 600 600 600 ...
##  $ dep_delay     : num [1:336776] 2 4 2 -1 -6 -4 -5 -3 -3 -2 ...
##  $ arr_time      : int [1:336776] 830 850 923 1004 812 740 913 709 838 753 ...
##  $ sched_arr_time: int [1:336776] 819 830 850 1022 837 728 854 723 846 745 ...
##  $ arr_delay     : num [1:336776] 11 20 33 -18 -25 12 19 -14 -8 8 ...
##  $ carrier       : chr [1:336776] "UA" "UA" "AA" "B6" ...
##  $ flight        : int [1:336776] 1545 1714 1141 725 461 1696 507 5708 79 301 ...
##  $ tailnum       : chr [1:336776] "N14228" "N24211" "N619AA" "N804JB" ...
##  $ origin        : chr [1:336776] "EWR" "LGA" "JFK" "JFK" ...
##  $ dest          : chr [1:336776] "IAH" "IAH" "MIA" "BQN" ...
##  $ air_time      : num [1:336776] 227 227 160 183 116 150 158 53 140 138 ...
##  $ distance      : num [1:336776] 1400 1416 1089 1576 762 ...
##  $ hour          : num [1:336776] 5 5 5 5 6 5 6 6 6 6 ...
##  $ minute        : num [1:336776] 15 29 40 45 0 58 0 0 0 0 ...
##  $ time_hour     : POSIXct[1:336776], format: "2013-01-01 05:00:00" "2013-01-01 05:00:00" ...
head(flights)
## # A tibble: 6 × 19
##    year month   day dep_time sched_dep_time dep_delay arr_time sched_arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>          <int>
## 1  2013     1     1      517            515         2      830            819
## 2  2013     1     1      533            529         4      850            830
## 3  2013     1     1      542            540         2      923            850
## 4  2013     1     1      544            545        -1     1004           1022
## 5  2013     1     1      554            600        -6      812            837
## 6  2013     1     1      554            558        -4      740            728
## # ℹ 11 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## #   tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## #   hour <dbl>, minute <dbl>, time_hour <dttm>

Note: I had to exclude dep_time from the make_datetime function because affected the dep_datetime dates when I ran my chunk

# Q1. Create a column dep_datetime by combining year, month, day, and dep_time into a
#     POSIXct datetime using lubridate's make_datetime().
#     Hint: hour = dep_time %/% 100, minute = dep_time %% 100
#     Show the first 5 rows with year, month, day, dep_time, and dep_datetime.
flights1 <- flights |>
  mutate(dep_datetime = make_datetime(year, month, day))
head(flights1)
## # A tibble: 6 × 20
##    year month   day dep_time sched_dep_time dep_delay arr_time sched_arr_time
##   <int> <int> <int>    <int>          <int>     <dbl>    <int>          <int>
## 1  2013     1     1      517            515         2      830            819
## 2  2013     1     1      533            529         4      850            830
## 3  2013     1     1      542            540         2      923            850
## 4  2013     1     1      544            545        -1     1004           1022
## 5  2013     1     1      554            600        -6      812            837
## 6  2013     1     1      554            558        -4      740            728
## # ℹ 12 more variables: arr_delay <dbl>, carrier <chr>, flight <int>,
## #   tailnum <chr>, origin <chr>, dest <chr>, air_time <dbl>, distance <dbl>,
## #   hour <dbl>, minute <dbl>, time_hour <dttm>, dep_datetime <dttm>
# Q2. After creating dep_datetime, use lubridate's month() to filter flights that
#     departed in JUNE 2013. How many flights are there?
#     (Hint: filter(month(dep_datetime) == 6))
flights2 <- flights1 |>
  filter(month(month) == 6)


# Q3. The carrier column is a character. Convert it to a factor and check its levels.
flights1$carrier <- as.factor(flights1$carrier)

# Q4. Use fct_collapse() to keep "UA", "AA", and "DL" as their own levels and lump
#     everything else into "Other". Then count flights per recoded carrier level.
#     (Hint: see the fct_collapse demo from the Wrangling Activity Part H)
flights1 |>
  mutate(carrier2 = fct_collapse(carrier, 
                                 UA = "UA", 
                                 AA = "AA",
                                 DL = "DL",
                                 other_level = "other")) |>
  count(carrier2)
## # A tibble: 4 × 2
##   carrier2      n
##   <fct>     <int>
## 1 AA        32729
## 2 DL        48110
## 3 UA        58665
## 4 other    197272
# Q5. Missing data: how many flights have NA for dep_delay? Filter them out and report
#     the remaining row count.
flights1 |>
  filter(is.na(dep_delay)) |>
  count()
## # A tibble: 1 × 1
##       n
##   <int>
## 1  8255

Part 2 — Airquality EDA

library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.4.3
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ ggplot2 3.5.1     ✔ stringr 1.5.1
## ✔ purrr   1.0.4     ✔ tibble  3.2.1
## ✔ readr   2.1.5     ✔ tidyr   1.3.1
## ── 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
data("airquality")
# Q6. For Ozone, Temp, and Wind: compute mean, median, sd, min, max
#     (use na.rm = TRUE where needed).
mean(airquality$Ozone, na.rm = TRUE)
## [1] 42.12931
median(airquality$Ozone, na.rm = TRUE)
## [1] 31.5
sd(airquality$Ozone, na.rm = TRUE)
## [1] 32.98788
min(airquality$Ozone, na.rm = TRUE)
## [1] 1
max(airquality$Ozone, na.rm = TRUE)
## [1] 168
mean(airquality$Temp, na.rm = TRUE)
## [1] 77.88235
median(airquality$Temp, na.rm = TRUE)
## [1] 79
sd(airquality$Temp, na.rm = TRUE)
## [1] 9.46527
min(airquality$Temp, na.rm = TRUE)
## [1] 56
max(airquality$Temp, na.rm = TRUE)
## [1] 97
mean(airquality$Wind, na.rm = TRUE)
## [1] 9.957516
median(airquality$Wind, na.rm = TRUE)
## [1] 9.7
sd(airquality$Wind, na.rm = TRUE)
## [1] 3.523001
min(airquality$Wind, na.rm = TRUE)
## [1] 1.7
max(airquality$Wind, na.rm = TRUE)
## [1] 20.7

Q7. Compare the mean and median for each variable. What does the relationship between mean and median suggest about distribution skewness? What does the standard deviation tell you about variability?

# Q8. Make a histogram of Ozone.
ggplot(airquality, aes(x = Ozone)) +
  geom_histogram(fill = "green", color = "white", binwidth = 10) +
  labs(title = "Ozone")
## Warning: Removed 37 rows containing non-finite outside the scale range
## (`stat_bin()`).

Q9. Describe the shape of the Ozone distribution. Any outliers or unusual features? Unimodal, skewed right Outlier about 170. This seems to be the reason the mean is much higher at 42.13 as compared to the median score is at 31.5.

# Q10. Create a new column month_name with full month names (May–September) using case_when.

airquality2 <- airquality |>
  mutate(month_name = case_when(Month == 5~"May",
                                Month == 6~"June",
                                Month == 7~"July",
                                Month == 8~"August",
                                Month == 9~"September"))
#      Then make a boxplot of Ozone by month_name.
#      (Hint: case_when was covered in the Wrangling Activity.)
ggplot(airquality2, aes(x = month_name, Ozone, fill = month_name)) +
  geom_boxplot()
## Warning: Removed 37 rows containing non-finite outside the scale range
## (`stat_boxplot()`).

Q11. Which month has the highest median Ozone? Are there outliers in any month? July is the month with the highest median Ozone. It is the only month without any outliers. The largest outlier (170) was in the month of August. May, June, and September have outliers between 75 and 120.

# Q12. Scatterplot of Temp vs Ozone, colored by Month.
ggplot(airquality2, aes(x = Temp, y = Ozone, color = Month)) +
  geom_point(alpha = 0.5) +
  labs(title = "Temp vs Ozone", x = "Temp", y = "Ozone") + theme_minimal()
## Warning: Removed 37 rows containing missing values or values outside the scale range
## (`geom_point()`).

Q13. Is there a visible relationship between temperature and ozone? There appears to be a positive linear relationship between temperature and ozone. As the temperatures increase, the ozone levels also increase.

# Q14. Compute the correlation matrix for Ozone, Temp, and Wind.
#      (Hint: cor(airquality[, c("Ozone","Temp","Wind")], use = "complete.obs"))

airquality3 <- airquality |>
  select(Ozone, Wind, Temp) |>
  filter(!is.na(Ozone))
cor_matrix <- cor(airquality3)
cor_matrix
##            Ozone       Wind       Temp
## Ozone  1.0000000 -0.6015465  0.6983603
## Wind  -0.6015465  1.0000000 -0.5110750
## Temp   0.6983603 -0.5110750  1.0000000

Q15. Which pair has the strongest correlation? What does that suggest? Ozone and temperature have the strongest correlation at about 0.7. These two variables appear to be associated.

# Q16. Generate a summary table grouped by Month with: count, mean Ozone, mean Temp,
#      mean Wind for each month.
airquality |>
  group_by(Month) |>
  filter(!is.na(Ozone) & !is.na(Temp) & !is.na(Wind)) |>
  summarise(count = n(), mean_Ozone = mean(Ozone), mean_Temp = mean(Temp), mean_Wind = mean(Wind)) 
## # A tibble: 5 × 5
##   Month count mean_Ozone mean_Temp mean_Wind
##   <int> <int>      <dbl>     <dbl>     <dbl>
## 1     5    26       23.6      66.7     11.5 
## 2     6     9       29.4      78.2     12.2 
## 3     7    26       59.1      83.9      8.52
## 4     8    26       60.0      84.0      8.57
## 5     9    29       31.4      76.9     10.1

Q17. Which month has the highest average ozone? How do temperature and wind vary across months? The month of August had the highest average ozone. July and August both had the lowest wind on average and also recorded the highest temperatures on average.