Day 21: Fossils

Code
library(rio)
library(data.table)
library(ggplot2)
library(forcats)
library(waffle)
library(ggtext)
library(showtext)
library(patchwork)
library(ggsankey)
library(dplyr) # somehow ggsankey needs dplyr and does not import it

# get data
dat <- import(
  "https://nyc3.digitaloceanspaces.com/owid-public/data/energy/owid-energy-data.csv"
)
setDT(dat)

# round down to the nearest decade
round_to_decade <- function(year) {
  return(year - year %% 10)
}

# energy sources and colors
energy_sources <- c(
  "Coal" = "coal_consumption",
  "Oil" = "oil_consumption",
  "Gas" = "gas_consumption",
  "Hydro" = "hydro_consumption",
  "Nuclear" = "nuclear_consumption",
  "Biofuel" = "biofuel_consumption",
  "Solar" = "solar_consumption",
  "Wind" = "wind_consumption",
  "Other Renewables" = "other_renewable_consumption"
)
energy_colors = c(
  "Coal" = "#444239FF", # very dark coal grey
  "Oil" = "#035F72FF",
  "Gas" = "#D77186FF",
  "Hydro" = "#A4B7E1FF",
  "Nuclear" = "#E69F00",
  "Biofuel" = "#B0986CFF",
  "Solar" = "#F8D564FF",
  "Wind" = "#56B4E9",
  "Other Renewables" = "#1BB6AFFF"
)


#------ Fonts
font_add_google("Roboto Condensed", "Roboto Condensed")
showtext_auto()
showtext_opts(dpi = 600)
body_font <- "Roboto Condensed"
title_font <- "Roboto Condensed"

#------ WAFFLE PLOT (original)

# sum consumption for France by source, every 10 years from 1960 to 2020
dat_sum <- dat[
  year >= 1960 & year <= 2020 & country == "France",
  lapply(.SD, function(x) sum(x, na.rm = TRUE)),
  .SDcols = c(
    "biofuel_consumption",
    "coal_consumption",
    "gas_consumption",
    "hydro_consumption",
    "nuclear_consumption",
    "oil_consumption",
    "other_renewable_consumption",
    "solar_consumption",
    "wind_consumption"
  ),
  by = .(country, decade = round_to_decade(year))
]

# pivot longer
dat_sum <- melt(dat_sum, id.vars = c("country", "decade"))

# Ccnsumption per year as perc of total
dat_sum[, perc := (value / sum(value)) * 100, by = .(country, decade)]
# Rrmove NA
dat_sum <- na.omit(dat_sum, cols = "perc")

# ensure sum=100 for each decade
dat_sum[, perc_int := as.integer(round(perc))][,
  perc_int := {
    current_sum <- sum(perc_int)
    if (current_sum != 100) {
      diff <- 100 - current_sum
      perc_int[which.max(value)] <- perc_int[which.max(value)] + diff
    }
    perc_int
  },
  by = decade
][
  perc_int > 0
]
    country decade                    variable     value       perc perc_int
     <char>  <num>                      <fctr>     <num>      <num>    <int>
 1:  France   2010         biofuel_consumption   312.373  1.1008956        1
 2:  France   2020         biofuel_consumption    30.800  1.2616927        1
 3:  France   1960            coal_consumption  2384.688 32.1562993       32
 4:  France   1970            coal_consumption  3588.287 17.0100900       17
 5:  France   1980            coal_consumption  2907.669 12.1153299       12
 6:  France   1990            coal_consumption  1938.677  6.6809346        7
 7:  France   2000            coal_consumption  1535.992  4.9443497        5
 8:  France   2010            coal_consumption  1143.226  4.0290694        4
 9:  France   2020            coal_consumption    55.982  2.2932493        2
10:  France   1960             gas_consumption   319.189  4.3041006        4
11:  France   1970             gas_consumption  1709.201  8.1023795        8
12:  France   1980             gas_consumption  2718.536 11.3272730       11
13:  France   1990             gas_consumption  3500.476 12.0630982       12
14:  France   2000             gas_consumption  4509.500 14.5160554       15
15:  France   2010             gas_consumption  4366.272 15.3880448       15
16:  France   2020             gas_consumption   405.828 16.6243576       17
17:  France   1960           hydro_consumption   732.836  9.8819190       10
18:  France   1970           hydro_consumption  1709.349  8.1030811        8
19:  France   1980           hydro_consumption  1967.835  8.1993412        8
20:  France   1990           hydro_consumption  1956.482  6.7422929        7
21:  France   2000           hydro_consumption  1741.802  5.6068509        6
22:  France   2010           hydro_consumption  1576.686  5.5567117        6
23:  France   2020           hydro_consumption   162.052  6.6383059        7
24:  France   1970         nuclear_consumption   514.926  2.4409802        2
25:  France   1980         nuclear_consumption  5488.373 22.8683009       23
26:  France   1990         nuclear_consumption 10397.571 35.8313899       36
27:  France   2000         nuclear_consumption 12001.427 38.6325267       39
28:  France   2010         nuclear_consumption 10915.617 38.4698900       38
29:  France   2020         nuclear_consumption   891.721 36.5285018       37
30:  France   1960             oil_consumption  3938.314 53.1061521       54
31:  France   1970             oil_consumption 13527.223 64.1251052       65
32:  France   1980             oil_consumption 10865.304 45.2722585       46
33:  France   1990             oil_consumption 11135.164 38.3732319       38
34:  France   2000             oil_consumption 10987.830 35.3697636       35
35:  France   2010             oil_consumption  9127.721 32.1688112       32
36:  France   2020             oil_consumption   728.389 29.8377619       30
37:  France   2010 other_renewable_consumption   221.419  0.7803466        1
38:  France   2020 other_renewable_consumption    28.128  1.1522367        1
39:  France   2010           solar_consumption   171.060  0.6028665        1
40:  France   2020           solar_consumption    33.396  1.3680353        1
41:  France   2010            wind_consumption   540.069  1.9033642        2
42:  France   2020            wind_consumption   104.869  4.2958587        4
    country decade                    variable     value       perc perc_int
Code
# add levels and order by energy_sources
dat_sum <- dat_sum[,
  variable := factor(
    fct_recode(variable, !!!energy_sources),
    levels = names(energy_sources)
  )
]
setorder(dat_sum, decade, variable)


# Create waffle plot
waffle_plot <- ggplot(dat_sum, aes(fill = variable, values = perc_int)) +
  geom_waffle(
    color = "white",
    size = 0.2,
    n_rows = 5,
    flip = TRUE,
    make_proportional = FALSE
  ) +
  facet_wrap(~decade, nrow = 1, strip.position = "bottom") +
  theme_minimal() +
  labs(
    title = "A Proportional Picture: France's Energy Mix by Decade ...",
    y = "Percentage (%)"
  ) +
  scale_x_discrete() +
  scale_y_continuous(
    labels = function(x) x * 5,
    expand = c(0, 0)
  ) +
  scale_fill_manual(
    values = energy_colors,
    breaks = names(energy_sources),
    name = "Energy Source",
    drop = FALSE
  ) +
  theme(
    text = element_text(family = body_font),
    panel.grid = element_blank(),
    axis.title = element_blank(),
    axis.text.x = element_text(family = body_font, size = 10),
    strip.text = element_text(family = body_font, size = 10),
    legend.position = "none",
    plot.title = element_textbox_simple(
      hjust = 0.5,
      size = 12,
      family = title_font,
      margin = margin(b = 30)
    ),
    plot.background = element_rect(color = "white", fill = "white"),
    plot.margin = unit(c(30, 30, 30, 30), "pt")
  )


#------ YEARLY FOSSIL ENERGY PLOTS

# prep yearly data for all sources
yearly_all <- dat[
  year >= 1960 & year <= 2020 & country == "France",
  lapply(.SD, function(x) sum(x, na.rm = TRUE)),
  .SDcols = energy_sources,
  by = .(year)
]

# melt to long format
yearly_long <- melt(
  yearly_all,
  id.vars = "year",
  measure.vars = energy_sources,
  variable.name = "energy_type",
  value.name = "consumption"
)
yearly_long[,
  energy_type := factor(
    energy_type,
    levels = energy_sources,
    labels = names(energy_sources)
  )
]

# assign color: fossil in color, others grey
fossil_types <- c("Coal", "Oil", "Gas", "Nuclear")

# factor for energy_type with non-fossils first, fossils last
yearly_long$energy_type <- factor(
  yearly_long$energy_type,
  levels = c(
    setdiff(levels(yearly_long$energy_type), fossil_types),
    fossil_types
  )
)

# reorder the data so non-fossils come first, fossils last
yearly_long <- yearly_long[order(yearly_long$energy_type), ]

color_map <- setNames(
  ifelse(
    names(energy_sources) %in% fossil_types,
    energy_colors[names(energy_sources)],
    "grey80"
  ),
  names(energy_sources)
)

# calculate percentage of each source in total per year
yearly_long[, perc := 100 * consumption / sum(consumption), by = year]

# area chart (actually a sankey): absolute consumption by type (fossil in color, rest grey)
area_plot <- ggplot(
  yearly_long[year %in% c(1960, 1970, 1980, 1990, 2000, 2010, 2020)],
  aes(
    x = year,
    node = energy_type,
    fill = energy_type,
    value = consumption
  )
) +
  geom_sankey_bump(
    space = 0,
    type = "alluvial",
    color = "transparent",
    smooth = 6,
    alpha = 0.8
  ) +
  scale_fill_manual(values = color_map) +
  scale_x_continuous(breaks = scales::pretty_breaks(), expand = c(0, 0)) +
  scale_y_continuous(labels = scales::comma, expand = c(0, 0)) +
  labs(
    title = "... Eventually, Proportions Hinder Gross Total Consumption Increase (or Not-So-Much Decrease)",
    x = NULL,
    y = "Consumption (TWh)"
  ) +
  theme_minimal() +
  theme(
    text = element_text(family = body_font),
    axis.text = element_text(size = 10),
    panel.grid.minor = element_blank(),
    plot.title = element_textbox_simple(
      hjust = 0.5,
      size = 12,
      family = title_font,
      margin = margin(b = 20)
    ),
    legend.position = "none",
    plot.margin = unit(c(30, 30, 30, 30), "pt")
  )

# line chart: share of each energy source in total (fossil + nuclear in color, rest grey)
line_plot <- ggplot(yearly_long, aes(x = year, y = perc, color = energy_type)) +
  geom_line(size = 1) +
  scale_color_manual(values = color_map) +
  scale_x_continuous(breaks = scales::pretty_breaks(), expand = c(0, 0)) +
  labs(
    title = "... Visualizing The Proportional Shift  Differently: The Decline of Coal and Oil, the Rise of Gas, and the Rapid Expansion of Nuclear ...",
    x = NULL,
    y = "Percentage (%)"
  ) +
  theme_minimal() +
  theme(
    text = element_text(family = body_font),
    axis.text = element_text(size = 10),
    panel.grid.minor = element_blank(),
    plot.title = element_textbox_simple(
      hjust = 0.5,
      size = 12,
      family = title_font,
      margin = margin(b = 20)
    ),
    legend.position = "none",
    plot.margin = unit(c(30, 30, 30, 30), "pt")
  )


#------ Title and text

# old subtitle
subtitle_text <- glue::glue(
  "In the 1960s, only <span style='color:{energy_colors['Coal']};'><strong>coal</strong></span>,
    <span style='color:{energy_colors['Oil']};'><strong>oil</strong></span>,
    <span style='color:{energy_colors['Gas']};'><strong>gas</strong></span>, and
    <span style='color:{energy_colors['Hydro']};'><strong>hydro</strong></span> were part of the French primary energy consumption mix. By the 2020s, <span style='color:{energy_colors['Nuclear']};'><strong>nuclear</strong></span> energy became a player for almost 40%, and other sources appeared such as
    <span style='color:{energy_colors['Biofuel']};'><strong>biofuel</strong></span>,
    <span style='color:{energy_colors['Solar']};'><strong>solar</strong></span>,
    <span style='color:{energy_colors['Wind']};'><strong>wind</strong></span>, and
    <span style='color:{energy_colors['Other Renewables']};'><strong>other renewable energy</strong></span>. The French model for energy is singular. In 1973, nuclear power already accounted for 8% of the production of French electricity."
)

# caption
caption_text <- glue::glue(
  "
  <span style='font-size:11pt; color:#777777'>
  Each square on the right chart represents 1% of total energy consumption for each decade from 1960 to 2020. These are proportions of total terawatt hours (TWh) consumed. Data: Our World in Data | Viz: @gnoblet
  </span>"
)

# rectangles param
ymin_1stline <- 0.20
ymax_1stline <- 0.24
ymin_2ndline <- 0.08
ymax_2ndline <- 0.13
text_y_1stline <- 0.18
text_y_2ndline <- 0.07
text_size <- 3.5

# overall title and text panel
text_panel <- ggplot() +
  geom_textbox(
    aes(
      x = 0,
      y = 1,
      halign = 0,
      valign = 1,
      label = glue::glue(
        "<span style='font-size:22pt; font-weight:bold'>60 Years of France's Primary Energy Consumption </span>
        <br><br>
        <span style='font-size:14pt'>
        Primary energy consumption measures the total energy used within a country, based on energy sources at the point of extraction or generation, and covers direct uses like electricity, heating, and transport. It does <b>not</b> include the energy embedded in imported goods and services.
        <br><br>
        {subtitle_text}
        <br><br>
        {caption_text}
        <br><br>
        <span style='font-size:12pt;font-weight:bold'>Primary energy sources:</span>"
      ),
      box.color = NA
    ),
    box.padding = unit(c(0, 0, 0, 0), "pt"),
    box.margin = grid::unit(c(0, 0, 0, 0), "pt"),
    box.r = unit(0, "pt"),
    fill = NA,
    hjust = 0,
    vjust = 1,
    lineheight = 1.1,
    width = unit(0.95, "npc"),
    family = body_font
  ) +
  # Add rectangle legends - Row 1
  annotate(
    "rect",
    xmin = 0.1,
    xmax = 0.17,
    ymin = ymin_1stline,
    ymax = ymax_1stline,
    fill = energy_colors["Coal"]
  ) +
  annotate(
    "rect",
    xmin = 0.25,
    xmax = 0.32,
    ymin = ymin_1stline,
    ymax = ymax_1stline,
    fill = energy_colors["Oil"]
  ) +
  annotate(
    "rect",
    xmin = 0.4,
    xmax = 0.47,
    ymin = ymin_1stline,
    ymax = ymax_1stline,
    fill = energy_colors["Gas"]
  ) +
  annotate(
    "rect",
    xmin = 0.55,
    xmax = 0.62,
    ymin = ymin_1stline,
    ymax = ymax_1stline,
    fill = energy_colors["Hydro"]
  ) +
  annotate(
    "rect",
    xmin = 0.7,
    xmax = 0.77,
    ymin = ymin_1stline,
    ymax = ymax_1stline,
    fill = energy_colors["Nuclear"]
  ) +
  # Row 2
  annotate(
    "rect",
    xmin = 0.1,
    xmax = 0.17,
    ymin = ymin_2ndline,
    ymax = ymax_2ndline,
    fill = energy_colors["Biofuel"]
  ) +
  annotate(
    "rect",
    xmin = 0.25,
    xmax = 0.32,
    ymin = ymin_2ndline,
    ymax = ymax_2ndline,
    fill = energy_colors["Solar"]
  ) +
  annotate(
    "rect",
    xmin = 0.4,
    xmax = 0.47,
    ymin = ymin_2ndline,
    ymax = ymax_2ndline,
    fill = energy_colors["Wind"]
  ) +
  annotate(
    "rect",
    xmin = 0.55,
    xmax = 0.62,
    ymin = ymin_2ndline,
    ymax = ymax_2ndline,
    fill = energy_colors["Other Renewables"]
  ) +
  # Labels - Row 1
  annotate(
    "text",
    x = 0.135,
    y = text_y_1stline,
    label = "Coal",
    color = energy_colors["Coal"],
    size = text_size,
    fontface = "bold",
    family = body_font,
    vjust = 1
  ) +
  annotate(
    "text",
    x = 0.285,
    y = text_y_1stline,
    label = "Oil",
    color = energy_colors["Oil"],
    size = text_size,
    fontface = "bold",
    family = body_font,
    vjust = 1
  ) +
  annotate(
    "text",
    x = 0.435,
    y = text_y_1stline,
    label = "Gas",
    color = energy_colors["Gas"],
    size = text_size,
    fontface = "bold",
    family = body_font,
    vjust = 1
  ) +
  annotate(
    "text",
    x = 0.585,
    y = text_y_1stline,
    label = "Hydro",
    color = energy_colors["Hydro"],
    size = text_size,
    fontface = "bold",
    family = body_font,
    vjust = 1
  ) +
  annotate(
    "text",
    x = 0.735,
    y = text_y_1stline,
    label = "Nuclear",
    color = energy_colors["Nuclear"],
    size = text_size,
    fontface = "bold",
    family = body_font,
    vjust = 1
  ) +
  # Labels - Row 2
  annotate(
    "text",
    x = 0.135,
    y = text_y_2ndline,
    label = "Biofuel",
    color = energy_colors["Biofuel"],
    size = text_size,
    fontface = "bold",
    family = body_font,
    vjust = 1
  ) +
  annotate(
    "text",
    x = 0.285,
    y = text_y_2ndline,
    label = "Solar",
    color = energy_colors["Solar"],
    size = text_size,
    fontface = "bold",
    family = body_font,
    vjust = 1
  ) +
  annotate(
    "text",
    x = 0.435,
    y = text_y_2ndline,
    label = "Wind",
    color = energy_colors["Wind"],
    size = text_size,
    fontface = "bold",
    family = body_font,
    vjust = 1
  ) +
  annotate(
    "text",
    x = 0.585,
    y = text_y_2ndline,
    label = "Other\nRenewables",
    color = energy_colors["Other Renewables"],
    size = text_size,
    fontface = "bold",
    family = body_font,
    vjust = 1
  ) +
  theme_void() +
  theme(
    plot.background = element_rect(fill = "white", color = NA),
    plot.margin = unit(c(0, 0, 0, 0), "pt"),
    axis.title = element_blank(),
    axis.text = element_blank(),
    axis.ticks = element_blank()
  ) +
  coord_cartesian(xlim = c(0, 1), ylim = c(0, 1), expand = FALSE)


#------ Combine all plots with patchwork

# 2x2 layout
combined_plot <- (text_panel + waffle_plot) /
  (line_plot + area_plot)
plot_annotation(
  theme = theme(
    plot.background = element_rect(fill = "white", color = NA)
  )
)
$title
list()
attr(,"class")
[1] "waiver"

$subtitle
list()
attr(,"class")
[1] "waiver"

$caption
list()
attr(,"class")
[1] "waiver"

$tag_levels
list()
attr(,"class")
[1] "waiver"

$tag_prefix
list()
attr(,"class")
[1] "waiver"

$tag_suffix
list()
attr(,"class")
[1] "waiver"

$tag_sep
list()
attr(,"class")
[1] "waiver"

$theme
List of 1
 $ plot.background:List of 5
  ..$ fill         : chr "white"
  ..$ colour       : logi NA
  ..$ linewidth    : NULL
  ..$ linetype     : NULL
  ..$ inherit.blank: logi FALSE
  ..- attr(*, "class")= chr [1:2] "element_rect" "element"
 - attr(*, "class")= chr [1:2] "theme" "gg"
 - attr(*, "complete")= logi FALSE
 - attr(*, "validate")= logi TRUE

attr(,"class")
[1] "plot_annotation"
Code
# display
#combined_plot

# save
ggsave(
  "day_21.png",
  combined_plot,
  height = 12,
  width = 16,
  dpi = 600
)

Final Plot

Notes

This visualization presents France’s primary energy consumption over 60 years (1960-2020) using multiple complementary chart types to highlight different aspects of the same data.

Data source: Our World in Data - Energy Consumption

Tools used: - data.table (for data manipulation) - ggplot2 (for base visualization framework) - waffle (for proportional squares visualization) - ggsankey (for flow diagrams) - patchwork (for combining multiple plots) - ggtext (for rich text annotations)

The visualization combines four different panels to tell a complete story about energy consumption trends: a text panel explaining the context, a waffle chart showing proportional energy mix by decade, a line chart tracking the percentage contribution of each energy source over time, and an area chart displaying absolute consumption values. This multi-chart approach addresses the limitations of proportional visualizations by showing both relative and absolute changes simultaneously, highlighting how the French energy model’s transition to nuclear power has evolved since the 1970s.