Data Introduction:

Data Source: https://www.kaggle.com/datasets/austinreese/craigslist-carstrucks-data/data

In this report, I have explored a data set containing information on used car sale listings from Craigslist. The data was collected through web scraping of Craigslist listings by a Kaggle user, capturing a diverse range of used cars advertised for sale during the period of April 4, 2021 to May 5, 2021. The data set includes essential information such as car make, model, year, condition, price, mileage, location of listing, and more.

The used car market is a dynamic sector of our economy and by analyzing such data one can come to valuable insights in regards to pricing dynamics, popular car models, geographical variations, and strength of economy. It’s important to note that while the data set offers valuable insights, it may also have limitations, such as potential biases in the scraped data, missing information, and incorrect data altogether due to human error upon creating a listing. With this in mind, I have been able to develop 5 visualizations that offer interesting insights into the used car market during the month of April in 2021.

Quick Visualization Navigation:

  1. Craigslist Car Listing Location in the United States
  2. Listings by Transmission Type and Model Year
  3. German Manufacturers: Average Listing Price for Model Year
  4. Electric & Hybrid Car Mileage vs Listing Price
  5. Fuel Type Distribution in Listings by Model Year


location_df <- df %>%
  select(state, lat, long) %>%
  filter(!is.na(state) & !is.na(lat) & !is.na(long)) %>%
  filter(lat >= 24.396308 & lat <= 49.384358 & long >= -125.000000 & long <= -66.934570) %>%
  data.frame()

us_map <- map_data("state")

ggplot() +
  geom_map(data = us_map, map = us_map,
           aes(map_id = region),
           fill = "white", color = "black", size = .75) +
  geom_point(data = location_df, aes(x = long, y = lat), color = "purple1", size = .3) +
  coord_fixed() +
  labs(title = "Craigslist Car Listing Location in the United States",
       caption = "* Each dot is an individual car listing") + 
  theme_dark() +
  theme_void() +
  theme(
    plot.title = element_text(hjust = 0.5, margin = margin(b = 20), size = 18),
    panel.background = element_rect(fill = "azure4")
  )


This visualization utilizes the geographical data (specifically the longitude and latitude coordinates) found in the dataset for each Craigslist listing, to visualize where in the United States people are trying to sell their cars. As the dataset contained coordinates outside of the United States, it was necessary to filter out coordinates that did not fit into the ranges below (rough U.S. border boundary)

Latitude: 24.396308 - 49.384358
Longitude: -125.000000 - -66.934570

Each purple dot on the map represents the location of a single Craigslist listing and by taking a look at the visualization one can instantly spot some regional disparities as there are clear hotspots for listings. The Mid-Atlantic & North East regions of the US have significantly more listings for used cars than any of the other regions. This could hint toward the fact that the used car market is bigger on the east coast of the country, but more in-depth analysis needs to be done as population and financial class within the regions could be playing a role.


year_labels <- c("1900-1925", "1925-1950", "1950-1975", "1975-2000")
transmission_df <- df %>%
  select(transmission, price, year) %>%
  filter(!is.na(transmission) & !is.na(price) & !is.na(year)) %>%
  filter(transmission != "other" & year <= 2000) %>%
  mutate(yearRange = cut(as.numeric(year), 
                         breaks = c(1900, 1925, 1950, 1975, 2000), 
                         labels = year_labels,
                         include.lowest = TRUE),
         transmissionType = ifelse(transmission == "manual", "Manual", "Automatic")) %>%
  group_by(yearRange, transmissionType) %>%
  summarise(n=length(transmissionType), .groups="keep") %>%
  data.frame()

ggplot(transmission_df, aes(x = reorder(transmissionType, n, sum), y = n, fill = as.factor(yearRange))) + 
  geom_bar(stat="identity") + 
  coord_flip() +
  scale_y_continuous(breaks = seq(0, 20000, by = 2500), labels = scales::comma_format(), ) +
  labs(title = "Listings by Transmission Type and Model Year",
       y = "Listing Count",
       x = "Transmission Type",
       fill = "Car Model Year") +
  theme_dark() + 
  scale_fill_brewer(palette="RdGy") + 
  theme(plot.title = element_text(hjust = 0.5, margin = margin(b = 20), size = 18),
        axis.title.x = element_text(size = 14, margin = margin(t = 25)),
        axis.text.y = element_text(size = 11),
        axis.title.y = element_text(size = 14, margin = margin(r = 25)),
        panel.spacing = unit(1.5, "lines"),
        legend.title = element_text(size = 12),
        legend.text = element_text(size = 10),
        legend.key.size = unit(1.4, "lines"))

This bar chart visualizes the amount of “Automatic” and “Manual” transmissions on the used car market specifically by car model year. Since automatic transmissions have long surpassed their manual counterpart in recent years, the Car Model Year has been limited to the years 1900-2000, a time range where manuals had more popularity, thus allowing for better visualizations. Based on this visualization, it is clear that automatic transmissions are continuing to dominate manual transmissions with the total automatic listing being a little more than double that of manual listings. The most interesting part of my analysis from this visualization is that there were more automatic transmission cars for sale than manual ones from the years 1900-1950, and I expected the opposite.


manufacturers = c("bmw", "audi","mercedes-benz")

manufacturer_df <- df %>%
  select(price, manufacturer, year, odometer) %>%
  filter(!is.na(price) & !is.na(manufacturer) & manufacturer %in% manufacturers) %>%
  filter(odometer < 35000 & year >= 2000 & year <= 2021) %>%
  mutate(manufacturer = ifelse(manufacturer == "audi", "Audi", 
                               ifelse(manufacturer == "bmw", "BMW", "Mercedes-Benz"))) %>%
  group_by(year, manufacturer) %>%
  summarise(avg_price = mean(price, na.rm = TRUE)) %>%
  data.frame

ggplot(manufacturer_df, aes(x = year, y = avg_price, color = manufacturer)) +
  geom_line() +
  scale_x_continuous(lim = c(2000, 2021), breaks = seq(2000, 2021, by = 1)) +
  scale_y_continuous(breaks = seq(0, max(manufacturer_df$avg_price), by = 7500)) +
  labs(title = "German Manufacturers: Average Listing Price for Model Year",
       x = "Car Model Year",
       y = "Average Price") +
  theme_dark() +
  scale_color_brewer(palette="Accent") + 
  theme(plot.title = element_text(hjust = 0.5, margin = margin(b = 20), size = 18),
        legend.position = "top",
        legend.title = element_blank(),
        legend.text = element_text(size = 12, margin = margin(r = 15)),
        legend.key.size = unit(2, "lines"),
        legend.background = element_rect(fill = "transparent"),
        legend.key = element_rect(fill = "transparent", color = NA),
        axis.title.x = element_text(size = 14, margin = margin(t = 25)),
        axis.title.y = element_text(size = 14, margin = margin(r = 25)))


This line chart visualizes the average price in dollars for a German-manufactured car for a specific model year between 2000 - 2021. The manufacturers in question are Audi, BMW, and Mercedes-Benz with all models considered. It is important to mention, that this visualization is getting a more general overview of the perceived value of a car brand rather than the condition of a car as odometer reading and general car condition are not being considered. As expected, the line chart grows diagonally to the right showing that most recent model years are being listed for more, but what is interesting is the yearly variations between the manufacturers themselves. Based on my experiences, I would have thought that BMW would be outperforming in terms of listed sale price across all model years but this is not the case as Mercedes Benz has a clear win with the highest average listed price. However, this may not be the case in the coming model years as one can clearly see a trend for the Audi average listed price increasing year over year and taking the top spot for 2021. Perhaps this is showing a consumer demand shift from Mercedes Benz to Audi in the coming years.


min_odometer = 0
max_odometer = 150000
max_price = 100000

odom_labels = c("0 - 25,000 Miles", "25,001 - 50,000 Miles", "50,001 - 75,000 Miles", "75,001 - 100,000 Miles", "100,001 - 125,000 Miles", "125,001 - 150,000 Miles")

odom_df <- df %>%
  select(price, odometer, fuel) %>%
  filter(fuel %in% c("electric", "hybrid")) %>%
  filter(odometer <= max_odometer & price <= max_price) %>%
  filter(!is.na(fuel) & !is.na(odometer)) %>%
  mutate(odomRange = cut(as.numeric(odometer),
                         breaks = seq(min_odometer, max_odometer, by = 25000),
                         labels = odom_labels,
                         include.lowest = TRUE),
         fuelType = ifelse(fuel == "electric", "Electric", "Hybrid")) %>%
  data.frame()

ggplot(data = odom_df, aes(x=odometer, y=price, color=fuelType)) +
  geom_point() +
  scale_y_continuous(labels = scales::comma_format()) +
  scale_x_continuous(labels = scales::comma_format()) + 
  labs(title = "Electric & Hybrid Car Mileage vs Listing Price",
       x = "Odometer (miles)",
       y = "Price ($)",
       color = "Fuel Type") +
  facet_wrap(~odomRange, 
             scales = "free",
             ncol=2,
             nrow=3) + 
  theme_dark() +
  theme(
    plot.title = element_text(hjust = 0.5, size = 18, margin = margin(b = 20)),
    axis.title.x = element_text(size = 14, margin = margin(t = 35)),
    axis.title.y = element_text(size = 14, margin = margin(r = 35)),
    panel.spacing = unit(1.5, "lines"),
    legend.title = element_text(size = 12),
    legend.text = element_text(size = 10),
    legend.key.size = unit(1.4, "lines"))


There has been a big emphasis on electric and hybrid car production over recent years, and I thought it would be interesting to compare the mileage of these cars with their listing price. To better visualize the data I decided to split one big scatter plot into 6 different ones based on the odometer reading range and this allowed for some interesting insights. In general, regardless of mileage, there are more hybrid vehicles on the resale market than electric vehicles and they tend to be listed for less than their electric counterpart. Looking at the spread by mileage range, most electric cars that are listed for resale are concentrated in the 0 - 50,000 mile range, which can mean that perhaps not many people like their electric purchases as they are listed on the market very soon after purchase. There is also the idea that electric cars have not been around for a long enough time for us to see used resale vehicles with mileage over 100,000 miles.


year_labels <- c("1900-1935", "1935-1970", "1970-2005", "2005-2022")

fuel_df <- df %>%
  select(fuel, year) %>%
  filter(!is.na(fuel) & !is.na(year)) %>%
  mutate(fuelType = ifelse(fuel=="gas", "Gas", 
                      ifelse(fuel=="diesel", "Diesel", 
                      ifelse(fuel=="electric", "Electric",
                      ifelse(fuel=="hybrid", "Hybrid", "Other"))))) %>%
  mutate(yearRange = cut(as.numeric(year), 
                          breaks = c(1900, 1935, 1970, 2005, 2022), 
                          labels = year_labels,
                          include.lowest = TRUE)) %>%
  group_by(yearRange, fuelType) %>%
  summarise(n=length(fuelType), .groups="keep") %>%
  group_by(yearRange) %>%
  mutate(percent = round(100*n/sum(n),1)) %>%
  ungroup() %>%
  data.frame()

ggplot(data = fuel_df, aes(x="", y = n, fill = fuelType)) +
  geom_bar(stat="identity", position="fill") + 
  coord_polar(theta="y", start=0) +
  labs(fill= "Fuel Type", 
        x= NULL, y= NULL, 
        title = "Fuel Type Distribution in Listings by Model Year",
        caption = "Slices under 5% are not labeled") +
  theme_dark() + 
  theme(plot.title = element_text(hjust = 0.5, size = 18, margin = margin(b = 20)),
        plot.caption.position = "plot",
        axis.text = element_blank(),
        axis.ticks = element_blank(),
        panel.grid = element_blank(),
        legend.margin = margin(l = 35),
        legend.title = element_text(size = 13),
        legend.text = element_text(size = 10),
        legend.key.size = unit(1.4, "lines")) +
  facet_wrap(~yearRange, ncol=2, nrow=2) +
  scale_fill_brewer(palette="Set3") + 
  geom_text(aes(x=1.6, label=ifelse(percent > 5, paste0(percent, "%"), "")),
            size=3,
            position= position_fill(vjust = 0.5))


This visualization shows the distribution of fuel type by car model year in the listings. Intuitively, one can predict that listings with “Diesel” and “Gas” would be in the majority but what is hard to predict is the proportion of listings that are “Hybrid” and “Electric”. Over the model years, one can see a small decline in gas and diesel vehicles and an in increase in hybrid/electric vehicles. The most interesting portion of this visualization for me is the huge jump of diesel vehicles on the market between the model year ranges 1930-1970 and 1970-2005, I would have thought that the older vehicle years would have a bigger distribution of diesel.