Pokemons: Heights and Weights (a scatterplot)

A simple scatterplot for weight and heights of different pokemons, coloured by their precribed colour, and a trend-line using geom_smooth(). The outlier pokemons are shown as images using ggimage::geom_image() and labelled with geom_text(). The title is shown as an annotation of a raster using {magick} and {grid}

#TidyTuesday
{magick}
ggimage
Images
Author

Aditya Dahiya

Published

April 1, 2025

About the Data

This week’s dataset dives into the world of Pokémon, sourced from the {pokemon} R package, available on CRAN and GitHub. Curated by Frank Hull, this dataset offers a comprehensive look at 949 Pokémon, with 22 columns detailing attributes like name, ID, height, weight, stats, types, and more, presented in both English and Brazilian Portuguese.

Figure 1: A simple scatterplot for weight and heights of different pokemons, coloured by their precribed colour, and a trend-line using geom_smooth(). The outlier pokemons are shown as images using ggimage::geom_image() and labelled with geom_text(). The title is shown as an annotation of a raster using {magick} and {grid}

How I made this graphic?

Loading required libraries

Code
# Data Import and Wrangling Tools
library(tidyverse)            # All things tidy

# Final plot tools
library(scales)               # Nice Scales for ggplot2
library(fontawesome)          # Icons display in ggplot2
library(ggtext)               # Markdown text support for ggplot2
library(showtext)             # Display fonts in ggplot2
library(colorspace)           # Lighten and Darken colours

library(magick)               # Download images and edit them
library(ggimage)              # Display images in ggplot2
library(patchwork)            # composing Plots

pokemon_df <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2025/2025-04-01/pokemon_df.csv')

pokemon_logo_url <- "https://1000logos.net/wp-content/uploads/2017/05/Pokemon-logo.jpg"

Visualization Parameters

Code
# Font for titles
font_add_google("Alegreya",
  family = "title_font"
) 

# Font for the caption
font_add_google("Saira Extra Condensed",
  family = "caption_font"
) 

# Font for plot text
font_add_google("Unkempt",
  family = "body_font"
) 

showtext_auto()

mypal <- c("yellow", "blue", "grey30")
# cols4all::c4a_gui()

# A base Colour
bg_col <- "white"
seecolor::print_color(bg_col)

# Colour for highlighted text
text_hil <- mypal[3]
seecolor::print_color(text_hil)

# Colour for the text
text_col <- mypal[3]
seecolor::print_color(text_col)

line_col <- "grey30"

# Define Base Text Size
bts <- 120

# Caption stuff for the plot
sysfonts::font_add(
  family = "Font Awesome 6 Brands",
  regular = here::here("docs", "Font Awesome 6 Brands-Regular-400.otf")
)
github <- "&#xf09b"
github_username <- "aditya-dahiya"
xtwitter <- "&#xe61b"
xtwitter_username <- "@adityadahiyaias"
social_caption_1 <- glue::glue("<span style='font-family:\"Font Awesome 6 Brands\";'>{github};</span> <span style='color: {text_hil}'>{github_username}  </span>")
social_caption_2 <- glue::glue("<span style='font-family:\"Font Awesome 6 Brands\";'>{xtwitter};</span> <span style='color: {text_hil}'>{xtwitter_username}</span>")
plot_caption <- paste0(
  "**Data:** {pokemon} by William Amorim", 
  " |  **Code:** ", 
  social_caption_1, 
  " |  **Graphics:** ", 
  social_caption_2
  )
rm(github, github_username, xtwitter, 
   xtwitter_username, social_caption_1, 
   social_caption_2)

# Add text to plot-------------------------------------------------
plot_subtitle <- "A simple scatterplot comparing heights and weights of different Pokemons. Outiers are shown with images (using {ggimage}) and labelled with geom_text(). A trendline shows general pattern."

Exploratory Data Analysis and Wrangling

Code
# library(summarytools)
# pokemon_df |> 
#   dfSummary() |> 
#   view()

pokemon_df |> 
  mutate(
    bmi = (height/100) / ((weight/1000)^2)
  ) |> 
  ggplot(aes(bmi)) +
  geom_boxplot() +
  scale_x_continuous(limits = c(0, 10))

# pokemon_df |> 
#   ggplot(
#     mapping = aes(
#       x = weight,
#       y = height,
#       colour = color_1
#     )
#   ) +
#   geom_point(
#     position = position_jitter(
#       width = 0.1,
#       height = 0.1
#     )
#   ) +
#   scale_colour_identity() +
#   scale_x_continuous(
#     transform = "sqrt"
#   ) +
#   scale_y_continuous(
#     transform = "sqrt"
#   )

df1 <- pokemon_df |> 
  arrange(desc(weight)) |> 
  mutate(
    id = row_number(),
    selected_ids = id %in% c(1:15, 22, 18, 20, 21, 68, 215, 601,
                     93, 372, 946, 848, 945, 601, 130, 92, 
                     68, 72, 42, 41, 26, 44, 29, 17, 59, 36, 
                     147, 372, 22, 601, 92, 241, 57, 72, 74,
                     43, 65, 24, 19, 30, 159, 244),
    url_icon = paste0("https:", url_icon),
    pokemon = snakecase::to_title_case(pokemon)
  )

# Check by reading one image
# df1 |> 
#   slice(10) |> 
#   select(pokemon, weight, height, id, url_icon, url_image) |> 
#   pull(url_image) |> 
#   image_read()

# Temporarily save images of selected "Pokemons"
df2 <- df1 |> 
  select(pokemon, weight, height, id, url_image, 
         selected_ids, color_1) |> 
  filter(selected_ids) |> 
  mutate(
    check_image = str_remove(url_image, "https://raw.githubusercontent.com/HybridShivam/Pokemon/master/assets/images/"),
    check_image = str_remove(check_image, ".png"),
    check_image = as.numeric(check_image),
    image_path = paste0("data_vizs/temp_pokemon_", id, ".png")
  ) |> 
  filter(
    check_image < 1000
  )

# Dont run again: Code for downloading image files
# for (i in df2$id){
#   df2 |> 
#     filter(id == i) |> 
#     pull(url_image) |> 
#     image_read() |> 
#     image_resize("x200") |> 
#     image_write(path = here::here("data_vizs", paste0("temp_pokemon_", i, ".png")))
# }

pokemon_logo <- image_read(pokemon_logo_url) |> 
  image_transparent(color = "white", fuzz = 10)


image_read(pokemon_logo_url) |> 
  image_transparent(color = "white", fuzz = 10) |> 
  image_resize("x400")

The Base Plots

Code
g_base <- df1 |> 
  ggplot(
    mapping = aes(
      x = weight,
      y = height
    )
  ) +
  geom_point(
    mapping = aes(colour = color_1),
    position = position_jitter(
      width = 0.1,
      height = 0.1
    ),
    size = 6,
    alpha = 0.7
  ) +
  
  geom_smooth(
    colour = alpha("grey20", 0.5)
  ) +
  # Display IDs to manually find out the Pokeomons whose image 
  # we want to display
  # geom_text(
  #   data = df1 |> filter(!selected_ids),
  #   mapping = aes(
  #     label = id
  #   ),
  #   size = bts / 5,
  #   check_overlap = TRUE,
  #   colour = text_col
  # ) +
  
  # Display Images for selected Pokemons
  ggimage::geom_image(
    data = df2,
    mapping = aes(image = image_path),
    size = 0.05
  ) +
  
  # Label the important Pokemons
  geom_text(
    data = df2,
    mapping = aes(
      label = pokemon,
      y = height * 0.9,
      colour = color_1
    ),
    family = "caption_font",
    size = bts / 8,
    check_overlap = TRUE,
    colour = "grey30"
  ) +
  
  # Annotate subtitle
  annotate(
    geom = "label",
    label = str_wrap(plot_subtitle, 50),
    x = 10,
    y = 11.5,
    lineheight = 0.3,
    hjust = 0, 
    vjust = 1,
    size = bts / 6,
    colour = darken("steelblue", 0.2),
    family = "body_font",
    label.size = NA,
    fill = alpha("white", 0.7),
    label.r = unit(0.05, "lines")
  ) +
  
  scale_colour_identity() +
  scale_x_continuous(
    transform = "sqrt",
    expand = expansion(c(0, 0.05)),
    breaks = c(25, 100, 250, 500, 750, 1e3)
  ) +
  scale_y_continuous(
    transform = "sqrt",
    expand = expansion(c(0, 0.05)),
    breaks = c(0.5, 2, 5, 10, 15)
  ) +
  coord_cartesian(
    clip = "off"
  ) +
  labs(
    x = "Weight",
    y = "Height",
    title = NULL,
    subtitle = NULL,
    caption = plot_caption
  ) +
  theme_minimal(
    base_family = "body_font",
    base_size = bts
  ) +
  theme(
    # Overall
    plot.margin = margin(5,10,5,5, "mm"),
    plot.title.position = "plot",
    plot.caption.position = "plot",
    
    
    # Axes
    axis.line = element_line(
      colour = text_col,
      linewidth = 0.5,
      arrow = arrow(length = unit(5, "mm"))
    ),
    axis.text = element_text(
      margin = margin(-0.5,0,0,0, "lines"),
      colour = text_col
    ),
    axis.ticks = element_line(
      linewidth = 0.5
    ),
    axis.ticks.length = unit(5, "mm"),
    axis.title = element_text(
      margin = margin(-0.5,0,-0.5,0, "lines"),
      colour = text_col
    ),
    panel.grid.major = element_line(
      linetype = 1,
      linewidth = 0.15,
      colour = "grey50"
    ),
    panel.grid.minor = element_line(
      linetype = 1,
      linewidth = 0.1,
      colour = "grey50"
    ),
    
    # Labels
    plot.title = element_text(
      margin = margin(5,0,5,0, "mm"),
      hjust = 0.5,
      size = 2 * bts,
      colour = text_hil
    ),
    plot.caption = element_textbox(
      margin = margin(5,0,5,0, "mm"),
      family = "caption_font",
      hjust = 1,
      halign = 1,
      colour = text_hil,
      size = 0.6 * bts
    )
  )


g1 <- ggplot() +
  annotation_custom(
    grid::rasterGrob(pokemon_logo),
    xmin = -Inf, xmax = Inf, 
    ymin = -Inf, ymax = Inf
  ) +
  theme_void() +
  theme(
    plot.background = element_rect(
      fill = NA,
      colour = NA
    ),
    panel.background = element_rect(
      fill = NA,
      colour = NA
    )
  )

g <- g_base +
  inset_element(
    p = g1,
    left = 0.005, right = 0.55,
    top = 1, bottom = 0.8
  )

ggsave(
  filename = here::here(
    "data_vizs",
    "tidy_pokemon.png"
  ),
  plot = g,
  width = 400,
  height = 500,
  units = "mm",
  bg = bg_col
)

Savings the thumbnail for the webpage

Code
# Saving a thumbnail

library(magick)

# Saving a thumbnail for the webpage
image_read(here::here("data_vizs", 
                      "tidy_pokemon.png")) |> 
  image_resize(geometry = "x400") |> 
  image_write(
    here::here(
      "data_vizs", 
      "thumbnails", 
      "tidy_pokemon.png"
    )
  )

# Remove temporary images
# List all files in data_vizs that start with "temp_pokemon_"
temp_files <- list.files(
  path = here::here("data_vizs"),
  pattern = "^temp_pokemon_.*",
  full.names = TRUE
)
# Remove the files (if any exist)
file.remove(temp_files)

Session Info

Code
# Data Import and Wrangling Tools
library(tidyverse)            # All things tidy

# Final plot tools
library(scales)               # Nice Scales for ggplot2
library(fontawesome)          # Icons display in ggplot2
library(ggtext)               # Markdown text support for ggplot2
library(showtext)             # Display fonts in ggplot2
library(colorspace)           # Lighten and Darken colours

library(magick)               # Download images and edit them
library(ggimage)              # Display images in ggplot2
library(patchwork)            # composing Plots


sessioninfo::session_info()$packages |> 
  as_tibble() |> 
  select(package, 
         version = loadedversion, 
         date, source) |> 
  arrange(package) |> 
  janitor::clean_names(
    case = "title"
  ) |> 
  gt::gt() |> 
  gt::opt_interactive(
    use_search = TRUE
  ) |> 
  gtExtras::gt_theme_espn()
Table 1: R Packages and their versions used in the creation of this page and graphics