scatteplot
misc
Author

Ana Luisa Bodevan

Published

December 9, 2025

This week challenge dataset in on Qatar Cars. Check the TidyTuesday GitHub repo for the data.

1. SETUP

Code
library(pacman)

pacman::p_load(
  tidytuesdayR,
  tidyverse,
  dplyr,
  janitor,
  ggtext,
  showtext,
  scales,
  glue,
  skimr,
  ggrepel,
  ggbranding,
  ggnewscale)

tuesdata <- tidytuesdayR::tt_load('2025-12-09')

qatarcars <- tuesdata$qatarcars |>
   janitor::clean_names()

rm(tuesdata)

2. DATA ANALYSIS

Code
skimr::skim(qatarcars)
Data summary
Name qatarcars
Number of rows 105
Number of columns 15
_______________________
Column type frequency:
character 5
numeric 10
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
origin 0 1 2 11 0 8 0
make 0 1 2 10 0 31 0
model 0 1 1 20 0 105 0
type 0 1 3 9 0 4 0
enginetype 0 1 6 8 0 3 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
length 0 1.0 4.66 0.35 3.60 4.50 4.68 4.85 5.470e+00 ▁▂▇▇▂
width 0 1.0 1.91 0.30 1.59 1.82 1.88 1.98 4.630e+00 ▇▁▁▁▁
height 0 1.0 1.57 0.19 1.12 1.46 1.54 1.69 2.000e+00 ▂▇▇▇▃
seating 0 1.0 5.01 1.20 2.00 5.00 5.00 5.00 8.000e+00 ▁▁▇▁▂
trunk 0 1.0 437.65 264.69 0.00 284.00 448.00 542.00 1.233e+03 ▅▇▆▂▁
economy 10 0.9 8.70 3.58 1.20 6.50 7.60 10.65 2.250e+01 ▂▇▃▁▁
horsepower 0 1.0 317.78 289.88 76.00 154.00 248.00 380.00 1.973e+03 ▇▂▁▁▁
price 0 1.0 796573.15 3530840.76 35000.00 91000.00 164000.00 310000.00 3.300e+07 ▇▁▁▁▁
mass 0 1.0 1776.41 471.56 945.00 1428.00 1701.00 2055.00 2.746e+03 ▅▇▇▅▃
performance 0 1.0 7.10 2.84 2.40 4.80 6.90 8.80 1.450e+01 ▇▇▇▃▂

3. DATA TIDYING

Code
# normalize prices
qatarcars <- qatarcars |>
  mutate(
    price_usd = price / 3.64,
    price_eur = price / 4.15
  )

# identify most expensive cars by engine type
most_expensive <- qatarcars |>
  group_by(enginetype) |>
  slice_max(order_by = price_usd, n = 1) |>
  ungroup()

dollar_labels <- function(x) {
  case_when(
    x >= 1e6 ~ paste0("$", round(x / 1e6, 1), "M"),
    x >= 1e3 ~ paste0("$", round(x / 1e3, 1), "k"),
    TRUE ~ paste0("$", x)
  )
}

4. PLOT

Code
# DARK MODE COLORS
background_dark <- "#111215" # near-black NYT dark
panel_dark <- "#18191c"
grid_dark <- "#2a2c31"
title_dark <- "#f2f2f2"
subtitle_dark <- "#d4d4d4"
text_dark <- "#c7c7c7"
caption_dark <- "#9a9a9a"

col_dark <- c(
  "Electric" = "#6ccff6",
  "Hybrid" = "#c7b8ea",
  "Petrol" = "#8ce0b1"
)

col_dark_labels <- c(
  "Electric" = "#9cdfff",
  "Hybrid" = "#dfd4fb",
  "Petrol" = "#baf2d3"
)
Code
font_add_google("Roboto Condensed", "robotoc")
font_add_google("Roboto", "roboto")
showtext_auto()

title_font <- "robotoc"
body_font <- "roboto"

title <- str_glue("Does Higher Power Drives Higher Prices?")
subtitle <- str_glue(
  "Examining the horsepower-price relationship by engine type"
)
Code
ggplot() +

  # ====================================================
  # LAYER 1 — SCATTER
  # ====================================================
  geom_point(
    data = qatarcars,
    aes(x = price_usd, y = horsepower, color = enginetype),
    alpha = 0.8,
    size = 3
  ) +
  scale_color_manual(values = col_dark, name = "Engine Type") +

  # Independent color scale for labels
  ggnewscale::new_scale_color() +

  # ====================================================
  # LAYER 2 — LABELS
  # ====================================================
  geom_text_repel(
    data = most_expensive,
    aes(
      x = price_usd,
      y = horsepower,
      label = glue("{model}\n{dollar(price_usd, accuracy = 1)}"),
      color = enginetype
    ),
    size = 3.7,
    fontface = "bold",
    box.padding = 0.5,
    point.padding = 0.3,
    segment.color = caption_dark,
    segment.size = 0.35,
    min.segment.length = 0,
    family = body_font,
    seed = 1234,
    bg.color = panel_dark,
    bg.r = 0.15
  ) +

  scale_color_manual(values = col_dark_labels, guide = "none") +

  # ====================================================
  # SCALES
  # ====================================================
  scale_x_log10(
    labels = dollar_labels,
    breaks = c(1e4, 2.5e4, 5e4, 1e5, 2.5e5, 5e5, 1e6, 3e6, 1e7)
  ) +
  scale_y_continuous(
    labels = label_comma(),
    limits = c(0, 2050)
  ) +

  # ====================================================
  # LABELS
  # ====================================================
  labs(
    title = title,
    subtitle = subtitle,
    x = "Price (USD)",
    y = "Horsepower"
  ) +

  # ====================================================
  # DARK MODE THEME
  # ====================================================
  theme_minimal(base_family = body_font) +
  theme(
    plot.background = element_rect(fill = background_dark, color = NA),
    panel.background = element_rect(fill = panel_dark, color = NA),

    plot.title = element_text(
      size = 22,
      face = "bold",
      family = title_font,
      color = title_dark,
      margin = margin(b = 6)
    ),
    plot.subtitle = element_text(
      size = 13,
      color = subtitle_dark,
      margin = margin(b = 15)
    ),

    legend.position = "top",
    legend.title = element_text(size = 10, face = "bold", color = text_dark),
    legend.text = element_text(size = 9.5, color = text_dark),

    axis.title = element_text(size = 11, face = "bold", color = text_dark),
    axis.text = element_text(size = 9.5, color = text_dark),

    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = grid_dark, linewidth = 0.2),

    plot.margin = margin(t = 15, r = 25, b = 15, l = 25),
    plot.caption = element_markdown(color = caption_dark)
  ) +

  # ====================================================
  # BRANDING — DARK MODE
  # ====================================================
  add_branding(
    github = "anabodevan",
    bluesky = "1141bode",
    additional_text = "{qatarcars} | #TidyTuesday 2025 W48",
    line_spacing = 1L,
    icon_color = caption_dark,
    text_color = caption_dark,
    icon_size = "12pt",
    text_size = "12pt",
    text_family = "roboto",
    caption_margin = margin(t = 20, b = 5, unit = "pt")
  )