How does engine size relate to city and highway fuel economy across different vehicle categories?”

This project examines how engine size and number of cylinders relate to city and highway fuel economy across vehicle manufacturers and categories. I chose this topic because fuel efficiency connects data analysis to practical decisions about transportation and fuel expenses. Understanding which vehicle characteristics afftect fuel efficiency can help me evaluate vehicle specifications and make more informed comparisons. The dataset comes from the U.S. Environmental Protection Agency’s vehicle fuel economy database, available at FuelEconomy.gov. These estimates come from vehicle testing conducted by the EPA and manufacturers under EPA oversight. Each observation represents a vehicle configuration for a particular model year. The six variables I selected are make (manufacturer) and VClass (EPA vehicle size class), displ (engine displacement in liters), cylinders , city08 and highway08 (estimated city and highway fuel economy in miles per gallon). Higher miles per gallon means greater fuel efficiency. The official data dictionary defines all these variables.

Libraries

#Load packages for importing, cleaning, and plotting data. (And Plotly for mouseover interactivity.)#
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.1     ✔ readr     2.2.0
✔ forcats   1.0.1     ✔ stringr   1.6.0
✔ ggplot2   4.0.3     ✔ tibble    3.3.1
✔ lubridate 1.9.5     ✔ tidyr     1.3.2
✔ purrr     1.2.2     
── 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
library(plotly)

Attaching package: 'plotly'

The following object is masked from 'package:ggplot2':

    last_plot

The following object is masked from 'package:stats':

    filter

The following object is masked from 'package:graphics':

    layout
library(readr)
library(ggplot2)

Importing the Dataset

# Read the CSV, recognize missing values, and trim surrounding whitespace.#
vehicles<- read_csv("vehicles.csv", na=c("","NA"))
Rows: 50647 Columns: 84
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (23): drive, eng_dscr, fuelType, fuelType1, make, model, mpgData, trany,...
dbl (59): barrels08, barrelsA08, charge120, charge240, city08, city08U, city...
lgl  (2): phevBlended, tCharger

ℹ 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.
trim_ws = TRUE
show_col_types=FALSE
# Display the number of rows and columns
dim(vehicles)
[1] 50647    84

Applying the inclusion criteria

vehicles_subset<- filter(vehicles, year == 2024,
  fuelType %in% c("Regular", "Premium", "Midgrade"),
  is.na(atvType) | trimws(atvType) == ""
)
nrow(vehicles_subset)
[1] 752

Checking Vehicle ID’s

summarise(vehicles_subset,
  missing_ids = sum(is.na(id)),
  duplicate_ids = sum(duplicated(id)))
# A tibble: 1 × 2
  missing_ids duplicate_ids
        <int>         <int>
1           0             0

Selecting Variables

vehicles_selected<- select(vehicles_subset,
  make, VClass, displ, cylinders, city08, highway08
)
glimpse(vehicles_selected)
Rows: 752
Columns: 6
$ make      <chr> "Jaguar", "Jaguar", "Jaguar", "Jaguar", "Jaguar", "Jaguar", …
$ VClass    <chr> "Two Seaters", "Two Seaters", "Two Seaters", "Two Seaters", …
$ displ     <dbl> 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 1.2, 2.0, 2.0, 2.0, 2.0, 1.6, …
$ cylinders <dbl> 8, 8, 8, 8, 8, 8, 3, 4, 4, 4, 4, 4, 4, 3, 3, 4, 4, 4, 4, 4, …
$ city08    <dbl> 16, 16, 17, 17, 16, 16, 28, 20, 20, 19, 28, 25, 27, 29, 27, …
$ highway08 <dbl> 24, 24, 24, 24, 24, 24, 32, 26, 27, 23, 34, 27, 31, 38, 38, …
summarise(vehicles_selected,
  across(everything(), ~ sum(is.na(.x))))
# A tibble: 1 × 6
   make VClass displ cylinders city08 highway08
  <int>  <int> <int>     <int>  <int>     <int>
1     0      0     0         0      0         0

Checking Missing and Invalid Values

summarise(vehicles_selected,
    blank_make = sum(trimws(make) == "", na.rm = TRUE),
    blank_class = sum(trimws(VClass) == "", na.rm = TRUE),
    invalid_displ = sum(displ <= 0, na.rm = TRUE),
    invalid_cylinders = sum(
      cylinders <= 0 | cylinders != floor(cylinders),
      na.rm = TRUE
    ),
    invalid_city_mpg = sum(city08 <= 0, na.rm = TRUE),
    invalid_highway_mpg = sum(highway08 <= 0, na.rm = TRUE)
  )
# A tibble: 1 × 6
  blank_make blank_class invalid_displ invalid_cylinders invalid_city_mpg
       <int>       <int>         <int>             <int>            <int>
1          0           0             0                 0                0
# ℹ 1 more variable: invalid_highway_mpg <int>

Cleaning and Setting Variable Types

vehicles_clean <- filter(vehicles_selected,
    !is.na(make), trimws(make) != "",
    !is.na(VClass), trimws(VClass) != "",
    !is.na(displ), displ > 0,
    !is.na(cylinders), cylinders > 0,
    cylinders == floor(cylinders),
    !is.na(city08), city08 > 0,
    !is.na(highway08), highway08 > 0
  ) |>
  dplyr::mutate(
    make = factor(trimws(make)),
    VClass = factor(trimws(VClass)),
    across(
      c(displ, cylinders, city08, highway08),
      as.numeric
    )
  )
nrow(vehicles_selected) - nrow(vehicles_clean)
[1] 0
dim(vehicles_clean)
[1] 752   6
glimpse(vehicles_clean)
Rows: 752
Columns: 6
$ make      <fct> Jaguar, Jaguar, Jaguar, Jaguar, Jaguar, Jaguar, Chevrolet, J…
$ VClass    <fct> Two Seaters, Two Seaters, Two Seaters, Two Seaters, Two Seat…
$ displ     <dbl> 5.0, 5.0, 5.0, 5.0, 5.0, 5.0, 1.2, 2.0, 2.0, 2.0, 2.0, 1.6, …
$ cylinders <dbl> 8, 8, 8, 8, 8, 8, 3, 4, 4, 4, 4, 4, 4, 3, 3, 4, 4, 4, 4, 4, …
$ city08    <dbl> 16, 16, 17, 17, 16, 16, 28, 20, 20, 19, 28, 25, 27, 29, 27, …
$ highway08 <dbl> 24, 24, 24, 24, 24, 24, 32, 26, 27, 23, 34, 27, 31, 38, 38, …

Visual and Data Exploration

Categorical Variable: Manufacturer

ggplot(vehicles_clean, aes(x = make)) +
  geom_bar(fill = "steelblue") +
  coord_flip() +
  labs(
    title = "Vehicle Configurations by Manufacturer",
    x = "Manufacturer",
    y = "Count"
  ) +
  theme_minimal()

Categorical Variable: Vehicle Class

ggplot(vehicles_clean, aes(x = VClass)) +
  geom_bar(fill = "darkorange") +
  coord_flip() +
  labs(
    title = "Vehicle Configurations by Class",
    x = "Vehicle Class",
    y = "Count"
  ) +
  theme_minimal()

Quantitative Variable: City Fuel Economy

ggplot(vehicles_clean, aes(x = city08)) +
  geom_histogram(binwidth = 2, fill = "darkorange", color = "white") +
  labs(
    title = "Distribution of City Fuel Economy",
    x = "City Fuel Economy (MPG)",
    y = "Count"
  ) +
  theme_minimal()

Quantitative Variable: Highway Fuel Economy

ggplot(vehicles_clean, aes(x = highway08)) +
  geom_histogram(binwidth = 2, fill = "purple", color = "white") +
  labs(
    title = "Distribution of Highway Fuel Economy",
    x = "Highway Fuel Economy (MPG)",
    y = "Count"
  ) +
  theme_minimal()

vehicles_numeric <- vehicles_selected |>
  select(displ, cylinders, city08, highway08) |>
  pivot_longer(
    cols = everything(),
    names_to = "variable",
    values_to = "value"
  )
ggplot(vehicles_numeric, aes(x = value)) +
  geom_histogram(bins = 15, fill = "steelblue", color = "white") +
  facet_wrap(~ variable, scales = "free") +
  labs(
    title = "Distributions of Vehicle Measurements",
    x = "Value (liters, cylinder count, or MPG, depending on panel)",
    y = "Number of Vehicle Configurations",
    caption = "Source: EPA, FuelEconomy.gov"
  ) +
  theme_minimal()

vehicles_selected |>
  count(make, sort = TRUE) |>
  ggplot(aes(x = reorder(make, n), y = n)) +
  geom_col(fill = "darkorange") +
  coord_flip() +
  labs(
    title = "Number of 2024 Gasoline Vehicle Configurations by Manufacturer",
    x = "Manufacturer",
    y = "Number of Vehicle Configurations",
    caption = "Source: EPA, FuelEconomy.gov"
  ) +
  theme_minimal()

vehicles_selected |>
  count(VClass, sort = TRUE) |>
  ggplot(aes(x = reorder(VClass, n), y = n)) +
  geom_col(fill = "seagreen") +
  coord_flip() +
  labs(
    title = "Number of 2024 Gasoline Vehicle Configurations by Vehicle Class",
    x = "Vehicle Class",
    y = "Number of Vehicle Configurations",
    caption = "Source: EPA, FuelEconomy.gov"
  ) +
  theme_minimal()

Regressions

# Examine whether engine size is related to highway MPG.
ggplot(vehicles_clean, aes(x = displ, y = highway08)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = "lm", se = FALSE, color = "blue") +
  labs(
    title = "Engine Size and Highway Fuel Economy",
    x = "Engine Displacement (Liters)",
    y = "Highway Fuel Economy (MPG)"
  ) +
  theme_minimal()
`geom_smooth()` using formula = 'y ~ x'

# Examine whether cylinder count is related to highway MPG.
ggplot(vehicles_clean, aes(x = cylinders, y = highway08)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = "lm", se = FALSE, color = "blue") +
  labs(
    title = "Cylinder Count and Highway Fuel Economy",
    x = "Number of Cylinders",
    y = "Highway Fuel Economy (MPG)"
  ) +
  theme_minimal()
`geom_smooth()` using formula = 'y ~ x'

# Examine whether city MPG is related to highway MPG.
ggplot(vehicles_clean, aes(x = city08, y = highway08)) +
  geom_point(alpha = 0.5) +
  geom_smooth(method = "lm", se = FALSE, color = "blue") +
  labs(
    title = "City and Highway Fuel Economy",
    x = "City Fuel Economy (MPG)",
    y = "Highway Fuel Economy (MPG)"
  ) +
  theme_minimal()
`geom_smooth()` using formula = 'y ~ x'

# Check relationships among the response and potential predictors.
regression_variables <- dplyr::select(
  vehicles_clean,
  highway08, displ, cylinders, city08
)

round(cor(regression_variables), 2)
          highway08 displ cylinders city08
highway08      1.00 -0.70     -0.67   0.93
displ         -0.70  1.00      0.92  -0.78
cylinders     -0.67  0.92      1.00  -0.77
city08         0.93 -0.78     -0.77   1.00

Visualizations

engine_plot <- ggplot(
  vehicles_clean,
  aes(
    x = displ,
    y = highway08,
    color = factor(cylinders)
  )
) +
  geom_point(alpha = 0.7, size = 2) +
  scale_color_viridis_d(option = "D", end = 0.9) +
  labs(
    title = "Engine Size and Highway Fuel Economy of 2024 Gasoline Vehicles",
    x = "Engine Displacement (Liters)",
    y = "Highway Fuel Economy (MPG)",
    color = "Number of Cylinders",
    caption = "Source: EPA, FuelEconomy.gov"
  ) +
  theme_minimal()

ggplotly(engine_plot)
ggplot(
  vehicles_clean,
  aes(
    x = city08,
    y = highway08,
    color = displ
  )
) +
  geom_point(alpha = 0.7, size = 2) +
  scale_color_viridis_c(option = "C", end = 0.9) +
  labs(
    title = "City and Highway Fuel Economy by Engine Size in 2024",
    x = "City Fuel Economy (MPG)",
    y = "Highway Fuel Economy (MPG)",
    color = "Engine Size (Liters)",
    caption = "Source: EPA, FuelEconomy.gov"
  ) +
  theme_minimal()

# Reshape city and highway MPG into one fuel-economy column.
vehicles_mpg <- vehicles_clean |>
  pivot_longer(
    cols = c(city08, highway08),
    names_to = "driving_condition",
    values_to = "mpg"
  ) |>
  dplyr::mutate(
    driving_condition = recode(
      driving_condition,
      city08 = "City",
      highway08 = "Highway"
    )
  )
ggplot(
  vehicles_mpg,
  aes(
    x = displ,
    y = mpg,
    color = driving_condition
  )
) +
  geom_point(alpha = 0.6, size = 1.5) +
  facet_wrap(~ VClass, ncol = 3) +
  scale_color_manual(
    values = c(
      "City" = "#2166AC",
      "Highway" = "#D95F02"
    )
  ) +
  labs(
    title = "Engine Size and City/Highway Fuel Economy by Vehicle Class, 2024",
    x = "Engine Displacement (Liters)",
    y = "Fuel Economy (MPG)",
    color = "Driving Condition",
    caption = "Source: EPA, FuelEconomy.gov"
  ) +
  theme_minimal() +
  theme(strip.text = element_text(size = 8))

introduction

variables categorical quantitative

libraries

cleaning/reorganization

visual and data exploration

regression

3 visualizations

limitations and considerations

conclusion

bibliography