The Automobile Dataset from the UCI Machine Learning Repository contains 205 car samples with 26 attributes, including numerical features like engine displacement, horsepower, curb weight, and miles per gallon (MPG), plus categorical features like car make or fuel type. For PCA, we’ll focus on numerical features, and you can use a categorical feature fuel-type (gas vs. diesel) for visualization.
Source: https://archive.ics.uci.edu/dataset/10/automobile
Download the dataset and import it here
library(tidyverse)
library(broom)
#Set working directory
# Load data, treating "?" as NA
auto_data <- read_csv("automobile.csv", na = "?")
Clean & filter: Keep 8 numerical columns relevant to car. Features chosen: wheel_base, length, width, curb_weight, engine_size, horsepower, city_mpg, highway_mpg
auto_clean <- auto_data |>
select(fuel_type, wheel_base, length, width, curb_weight, engine_size,
horsepower, city_mpg, highway_mpg) |>
mutate(fuel_type = as.factor(fuel_type)) |> # Ensure fuel_type is a factor
drop_na() # Drop rows with NAs to ensure PCA works
# Step 1: Standardize data
numeric_data <- auto_clean |>
select(where(is.numeric)) |>
scale()
# Step 2 +3 : Find Directions of Maximum Variation and rank them
pca_fit <- prcomp(numeric_data)
pca_fit
## Standard deviations (1, .., p=8):
## [1] 2.4609703 1.0025191 0.6944628 0.4193810 0.3290435 0.2952943 0.2552724
## [8] 0.1406752
##
## Rotation (n x k) = (8 x 8):
## PC1 PC2 PC3 PC4 PC5
## wheel_base 0.3144622 -0.58325043 -0.1400494 -0.181761340 0.061969778
## length 0.3632918 -0.34403449 -0.1619823 -0.177442376 -0.589394926
## width 0.3628955 -0.25507029 0.1376091 0.809456560 0.293278038
## curb_weight 0.3911193 -0.09103041 0.1278216 -0.143468257 -0.003281696
## engine_size 0.3498786 0.11092441 0.6233518 -0.440961264 0.380154518
## horsepower 0.3324751 0.46636804 0.3311621 0.233287261 -0.553362040
## city_mpg -0.3495267 -0.38328744 0.4566544 0.005625945 -0.063787053
## highway_mpg -0.3596409 -0.30232298 0.4605478 0.103790754 -0.328564753
## PC6 PC7 PC8
## wheel_base 0.70494325 0.03057073 -0.081003965
## length -0.44153531 -0.36223845 0.135568383
## width -0.15608310 -0.13669176 0.005588183
## curb_weight -0.31120431 0.83061977 -0.122595116
## engine_size -0.07765246 -0.35955977 -0.049355129
## horsepower 0.42475591 0.06042025 0.132635371
## city_mpg 0.01025487 0.16044025 0.701742046
## highway_mpg -0.01840879 -0.02366389 -0.668975943
pca_fit$sdev^2
## [1] 6.05637465 1.00504446 0.48227854 0.17588046 0.10826966 0.08719873 0.06516400
## [8] 0.01978950
#Plot the scatter plot with arrows
library(broom)
pca_fit |>
# Add PCs to the original dataset
augment(auto_clean) |>
ggplot(aes(.fittedPC1, .fittedPC2)) +
geom_point(aes(color = fuel_type)) + # Use fuel_type for coloring
# Add the PCA1 and PCA2 arrows
geom_segment(aes(x = -4.9, y = 0, xend = 5, yend = 0),
arrow = arrow(type = "closed", length = unit(0.1, "inches")),
color = "black") + # PC1 arrow
geom_segment(aes(x = 0, y = -2.5, xend = 0, yend = 4),
arrow = arrow(type = "closed", length = unit(0.1, "inches")),
color = "black") + # PC2 arrow
# Add text labels
geom_text(aes(x = 5, y = 0, label = "PC1"),
vjust = -0.5, color = "black") +
geom_text(aes(x = 0, y = 4, label = "PC2"),
hjust = -0.5, color = "black") +
# Customize the plot
xlab("PC1") +
ylab("PC2") +
theme_minimal()
## Warning in geom_segment(aes(x = -4.9, y = 0, xend = 5, yend = 0), arrow = arrow(type = "closed", : All aesthetics have length 1, but the data has 203 rows.
## ℹ Please consider using `annotate()` or provide this layer with data containing
## a single row.
## Warning in geom_segment(aes(x = 0, y = -2.5, xend = 0, yend = 4), arrow = arrow(type = "closed", : All aesthetics have length 1, but the data has 203 rows.
## ℹ Please consider using `annotate()` or provide this layer with data containing
## a single row.
## Warning in geom_text(aes(x = 5, y = 0, label = "PC1"), vjust = -0.5, color = "black"): All aesthetics have length 1, but the data has 203 rows.
## ℹ Please consider using `annotate()` or provide this layer with data containing
## a single row.
## Warning in geom_text(aes(x = 0, y = 4, label = "PC2"), hjust = -0.5, color = "black"): All aesthetics have length 1, but the data has 203 rows.
## ℹ Please consider using `annotate()` or provide this layer with data containing
## a single row.
#Plot the rotation arrows
#Rotation Matrix
arrow_style <- arrow(
angle = 20, length = grid::unit(8, "pt"),
ends = "first", type = "closed"
)
pca_fit |>
# extract rotation matrix
tidy(matrix = "rotation") |>
pivot_wider(
names_from = "PC", values_from = "value",
names_prefix = "PC"
) |>
ggplot(aes(PC1, PC2)) +
geom_segment(
xend = 0, yend = 0,
arrow = arrow_style
) +
geom_text(aes(label = column), hjust = 1) +
xlim(-1, 0.5) + ylim(-1, 0.7) +
coord_fixed()+
theme_minimal()
Describe the pattern captured by the first principal component (PC1). For example, does it reflect differences in car performance (e.g., horsepower, engine size) or efficiency (e.g., MPG)? Use the rotation plot to support your interpretation.
PC1 reflects a gradient between small, efficient and low-performing cars and large, inefficient and high-performance cars. Small PC1 values mean small width, length, horsepower, and engine size along with high highway and city MPG. High PC1 values mean large vehicles with large, powerful engines and low highway and city MPG. PC1 generally separates vehicles by gross size.
Explain what the second principal component (PC2) highlights. For instance, does it separate cars based on size (e.g., length, width) or other characteristics? Refer to the rotation plot for evidence.
PC2 reflects a gradient between fuel-efficient cars with large wheel bases and low-horsepower engines at low values and high horsepower cars with small wheel bases and low fuel efficiency at high values. PC2 might separate more modest cars with high-performance muscle and super cars.
Based on the PC1 vs. PC2 scatter plot, do gas and diesel cars form distinct clusters? What do the clusters (or overlap) suggest about similarities or differences in their features (e.g., fuel efficiency, performance)? Use the rotation plot to identify key driving features.
Diesel cars have lower horsepower and engine size than most gas-powered cars but larger wheel bases. They cluster at the bottom half of the graph but spread across the left and right. This shows that gas cars generally have higher horsepower, smaller wheel bases and lower mpg than diesel cars. Both fuel types run the gamut of measurements better separated by PC1 such as width, length, and engine size. It is worth noting that gas cars exist with higher PC1 values than any diesel cars, so the graph also shows that the largest cars out there are gas cars.
#How much variation is explained by PC1 and PC2?
pca_fit |>
tidy(matrix = "eigenvalues")
## # A tibble: 8 × 4
## PC std.dev percent cumulative
## <dbl> <dbl> <dbl> <dbl>
## 1 1 2.46 0.757 0.757
## 2 2 1.00 0.126 0.883
## 3 3 0.694 0.0603 0.943
## 4 4 0.419 0.0220 0.965
## 5 5 0.329 0.0135 0.978
## 6 6 0.295 0.0109 0.989
## 7 7 0.255 0.00815 0.998
## 8 8 0.141 0.00247 1
Report the percentage of variation explained by PC1 and PC2 using the PCA output. What does this tell you about how well these components summarize the dataset?
PC1 explains about 76% of variation, and PC2 explains about 13% of variation. Cumulatively, PC1 and PC2 explain about 88% of the variation.
This means that by looking at only the first two principal components, you can account for over three-fourths of the important information in 8 columns of data, making it easier to analyze and interpret.