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.1152730        1
 2:  France   2020         biofuel_consumption    24.974  1.0373205        1
 3:  France   1960            coal_consumption  2384.688 32.3538183       32
 4:  France   1970            coal_consumption  3588.287 17.1036601       17
 5:  France   1980            coal_consumption  2907.669 12.2355996       12
 6:  France   1990            coal_consumption  1938.677  6.7599177        7
 7:  France   2000            coal_consumption  1535.992  5.0025586        5
 8:  France   2010            coal_consumption  1143.226  4.0816879        4
 9:  France   2020            coal_consumption    55.982  2.3252694        2
10:  France   1960             gas_consumption   319.189  4.3305384        4
11:  France   1970             gas_consumption  1709.201  8.1469495        8
12:  France   1980             gas_consumption  2718.536 11.4397196       11
13:  France   1990             gas_consumption  3500.476 12.2057102       12
14:  France   2000             gas_consumption  4509.500 14.6869502       15
15:  France   2010             gas_consumption  4366.272 15.5890082       16
16:  France   2020             gas_consumption   405.828 16.8564793       17
17:  France   1960           hydro_consumption   688.422  9.3400396        9
18:  France   1970           hydro_consumption  1605.752  7.6538572        8
19:  France   1980           hydro_consumption  1848.572  7.7788727        8
20:  France   1990           hydro_consumption  1837.909  6.4085526        6
21:  France   2000           hydro_consumption  1636.652  5.3303973        5
22:  France   2010           hydro_consumption  1480.823  5.2870187        5
23:  France   2020           hydro_consumption   152.355  6.3282201        6
24:  France   1970         nuclear_consumption   504.087  2.4027433        2
25:  France   1980         nuclear_consumption  5372.828 22.6090976       23
26:  France   1990         nuclear_consumption 10178.674 35.4917289       35
27:  France   2000         nuclear_consumption 11751.510 38.2733879       39
28:  France   2010         nuclear_consumption 10683.890 38.1449550       37
29:  France   2020         nuclear_consumption   873.661 36.2883995       37
30:  France   1960             oil_consumption  3938.314 53.4323548       55
31:  France   1970             oil_consumption 13527.223 64.4778481       65
32:  France   1980             oil_consumption 10865.304 45.7216794       46
33:  France   1990             oil_consumption 11135.164 38.8268867       40
34:  France   2000             oil_consumption 10987.830 35.7861653       36
35:  France   2010             oil_consumption  9127.721 32.5889266       33
36:  France   2020             oil_consumption   735.704 30.5582150       31
37:  France   2010 other_renewable_consumption   216.690  0.7736536        1
38:  France   2020 other_renewable_consumption    27.539  1.1438604        1
39:  France   2010           solar_consumption   160.685  0.5736976        1
40:  France   2020           solar_consumption    31.398  1.3041479        1
41:  France   2010            wind_consumption   516.978  1.8457793        2
42:  France   2020            wind_consumption   100.108  4.1580877        4
    country decade                    variable     value       perc perc_int
     <char>  <num>                      <fctr>     <num>      <num>    <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
<theme> List of 1
 $ plot.background: <ggplot2::element_rect>
  ..@ fill         : chr "white"
  ..@ colour       : logi NA
  ..@ linewidth    : NULL
  ..@ linetype     : NULL
  ..@ linejoin     : NULL
  ..@ inherit.blank: logi FALSE
 @ complete: logi FALSE
 @ 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.