Flags of Change: Global Governance Through the Decades

This visualization tracks the global transformation of political regimes from 1950 to 2020, showcasing the decline of colonial and military rule and the steady rise of democracies. Despite these shifts, civilian dictatorships continue to maintain a strong presence.

#TidyTuesday
Governance
{ggflags}
Author

Aditya Dahiya

Published

November 6, 2024

The Democracy and Dictatorship dataset is an extensive compilation that examines political regimes worldwide from 1950 to 2020. It is an updated version of the PACL dataset, incorporating a broader set of countries. The dataset provides detailed information on various regime types, transitions between democracies and autocracies, and related political institutions. It is based on the research of C. Bjørnskov and M. Rode, as discussed in The Review of International Organizations (2020) (Bjørnskov and Rode 2019). The full dataset and its codebook are accessible for download here. This data is ideal for exploring historical regime changes and their characteristics, such as elections, regime types, and leaders.

This graphic illustrates the global evolution of political regimes over three key years: 1950, 1980, and 2020. The x-axis represents these years, while the y-axis categorizes government types in increasing libertarian order: Colony, Civilian dictatorship, Military dictatorship, Presidential democracy, Parliamentary democracy, and Mixed democracy. Each dot represents a country, displayed using its rounded flag, indicating the type of government it had in each year. The visualization highlights the significant rise in democracies, the end of colonial rule by 1980, and the decline of military dictatorships by 2020, though civilian dictatorships persist.

How I made this graphic?

Loading required libraries, data import & creating custom functions.

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

democracy_data <- readr::read_csv('https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2024/2024-11-05/democracy_data.csv')

Visualization Parameters

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

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

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

showtext_auto()

# A base Colour
base_col <- "white"
bg_col <- darken(base_col, 0.2)
seecolor::print_color(bg_col)

# Colour for the text
text_col <- darken(bg_col, 0.9)
seecolor::print_color(text_col)

# Colour for highlighted text
text_hil <- darken(bg_col, 0.7)
seecolor::print_color(text_hil)

# Define Base Text Size
bts <- 90 

# 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>")

# Add text to plot--------------------------------------------------------------
plot_title <- "Regimes Transformed: 1950 - 2020"


plot_subtitle <- str_wrap("Over the past seven decades, the number of democracies worldwide has notably increased, with colonies nearly disappearing by 1980 and a decline in military dictatorships by 2020. However, civilian dictatorships remain prevalent despite this progress.", 130)

plot_caption <- paste0(
  "**Data:** Bjørnskov and Rode (2020): PACL dataset", 
  " |  **Code:** ", 
  social_caption_1, 
  " |  **Graphics:** ", 
  social_caption_2
  )

rm(github, github_username, xtwitter, 
   xtwitter_username, social_caption_1, 
   social_caption_2)

Exploratory Data Analysis and Wrangling

Code
democracy_data |> 
  summarytools::dfSummary() |> 
  summarytools::view()

df <- democracy_data |> 
  count(regime_category) |> 
  mutate(
    regime_category = case_when(
      str_detect(regime_category, "Colony|colony|British") ~ "Colony",
      str_detect(regime_category, "Royal dictatorship") ~ "Civilian dictatorship",
      .default = regime_category
    )
  ) |> 
  count(regime_category, wt = n, sort = T) |> 
  slice_max(order_by = n, n = 6) |> 
  mutate(
    regime_category = fct(
      regime_category,
      levels = c(
        "Colony",
        "Civilian dictatorship",
        "Military dictatorship",
        "Presidential democracy",
        "Parliamentary democracy",
        "Mixed democratic"
      )
    )
  )

levels_regime <- c(
        "Colony",
        "Civilian dictatorship",
        "Military dictatorship",
        "Presidential democracy",
        "Parliamentary democracy",
        "Mixed democratic"
      )

plotdf <- democracy_data |> 
  mutate(
    regime_category = case_when(
      str_detect(regime_category, 
                 "Colony|colony|British") ~ "Colony",
      str_detect(regime_category, 
                 "Royal dictatorship") ~ "Civilian dictatorship",
      .default = regime_category
    )
  ) |> 
  filter(regime_category %in% levels_regime) |> 
  mutate(regime_category = fct(regime_category, levels = levels_regime))


plotdf |>
  filter(year %in% c(1950, 1985, 2020)) |> 
  count(year, regime_category) |> 
  ggplot(aes(x = year, y = n, fill = regime_category)) +
  geom_col(
    position = "fill"
  )

plotdf |> 
  filter(year %in% c(1950, 1985, 2020)) |> 
  mutate(year = as_factor(year)) |> 
  ggplot(
    mapping = aes(
      x = year,
      y = regime_category
    )
  ) +
  geom_point(
    position = ggbeeswarm::position_beeswarm(
      method = "hex",
      cex = 2,
      corral = "wrap"
    )
  )

# Lets manually create the hex jitter we need
# The base data we will need
df2 <- plotdf |> 
  filter(year %in% c(1950, 1980, 2020)) |> 
  mutate(
    year = case_when(
      year == 1950 ~ 1,
      year == 1980 ~ 2,
      year == 2020 ~ 3,
      .default = NA
    )
  ) |> 
  left_join(
    tibble(
      regime_category = levels_regime,
      regime_val = 1:6
    )
  ) |> 
  select(country_code, year, regime_val) |> 
  mutate(
    country_code = countrycode::countrycode(
      country_code,
      origin = "iso3c",
      destination = "iso2c"
    ),
    country_code = str_to_lower(country_code)
  ) |> 
  filter(country_code %in% names(ggflags::lflags))


# Labels for x and y axis
x_axis_labels <- c(1950, 1980, 2020)
y_axis_labels <- levels_regime

x_axis_labels

y_axis_labels

number_of_rows <- df2 |> 
  count(year, regime_val) |> 
  mutate(number_rows = (n %/% 10) + 1) |> 
  select(-n)

horizontal_gaps <- seq(from = -0.4, to = +0.4, length.out = 10)
round(horizontal_gaps, 2)

vertical_gaps <- seq(from = -0.4, to = +0.4, length.out = 8)
round(vertical_gaps, 2)

# Manually creating the jitter and flag spacing for each flag position
final_plot_df <- df2 |> 
  left_join(number_of_rows) |> 
  arrange(year, regime_val) |>
  group_by(year, regime_val) |> 
  mutate(id = row_number()) |> 
  mutate(
    each_row_value = case_when(
      id <= 10             ~ 1,
      (id > 10 & id <= 20) ~ -1,
      (id > 20 & id <= 30) ~ 2,
      (id > 30 & id <= 40) ~ -2,
      (id > 40 & id <= 50) ~ 3,
      (id > 50 & id <= 60) ~ -3,
      (id > 60 & id <= 70) ~ 4,
      (id > 70 & id <= 80) ~ -4,
      .default = NA
    )
  ) |> 
  group_by(year, regime_val, each_row_value) |> 
  mutate(row_id = row_number()) |> 
  mutate(
    x_var = case_when(
      row_id == 1  ~ year + horizontal_gaps[5],
      row_id == 2  ~ year + horizontal_gaps[6],
      row_id == 3  ~ year + horizontal_gaps[4],
      row_id == 4  ~ year + horizontal_gaps[7],
      row_id == 5  ~ year + horizontal_gaps[3],
      row_id == 6  ~ year + horizontal_gaps[8],
      row_id == 7  ~ year + horizontal_gaps[2],
      row_id == 8  ~ year + horizontal_gaps[9],
      row_id == 9  ~ year + horizontal_gaps[1],
      row_id == 10 ~ year + horizontal_gaps[10],
      .default = NA
    )
  ) |> 
  mutate(
    y_var = case_when(
      each_row_value == 1  ~ regime_val + vertical_gaps[4],
      each_row_value == -1 ~ regime_val + vertical_gaps[5],
      each_row_value == 2  ~ regime_val + vertical_gaps[3],
      each_row_value == -2 ~ regime_val + vertical_gaps[6],
      each_row_value == 3  ~ regime_val + vertical_gaps[2],
      each_row_value == -3 ~ regime_val + vertical_gaps[7],
      each_row_value == 4  ~ regime_val + vertical_gaps[1],
      each_row_value == -4 ~ regime_val + vertical_gaps[8],
      .default = NA
    )
  )  |> 
  mutate(
    country_name = countrycode::countrycode(
      sourcevar = str_to_upper(country_code),
      origin = "iso2c",
      destination = "country.name"
    )
  ) 

# A tibble to label the total number of countries in each group
plot_labels_df <- df2 |> 
  count(regime_val, year) |> 
  mutate(
    x_var = year,
    y_var = case_when(
      (regime_val == 1 & year == 1) ~ regime_val + 0.6,
      (regime_val == 2 & year == 1) ~ regime_val + 0.4,
      (regime_val == 6 & year == 1) ~ regime_val + 0.2,
      
      (regime_val == 2 & year == 2) ~ regime_val + 0.5,
      (regime_val == 3 & year == 2) ~ regime_val + 0.4,
      (regime_val == 5 & year == 2) ~ regime_val + 0.4,
      (regime_val == 6 & year == 2) ~ regime_val + 0.2,
      
      (regime_val == 2 & year == 3) ~ regime_val + 0.5,
      (regime_val == 5 & year == 3) ~ regime_val + 0.4,
      (regime_val == 1 & year == 3) ~ regime_val + 0.2,
      
      .default = regime_val + 0.3
    )
  )

The Base Plot

Code
g <- ggplot(
  data = final_plot_df,
  mapping = aes(
    x = x_var,
    y = y_var,
    label = country_name
  )
  ) +
  ggflags::geom_flag(
    mapping = aes(country = country_code),
    size = 9
  ) +
  # While testing use geom_point() instead of geom_flag() to save render time
  # geom_point(
  #   size = 3,
  #   alpha = 0.2
  # ) +
  # geom_text(
  #   size = 6,
  #   nudge_y = -0.05,
  #   family = "caption_font"
  # ) +
  geom_text(
    data = plot_labels_df,
    mapping = aes(label = n),
    family = "title_font",
    size = 35,
    fontface = "bold",
    colour = text_hil
  ) +
  scale_x_continuous(
    breaks = 1:3,
    labels = x_axis_labels,
    expand = expansion(c(0.02, 0.05))
  ) +
  scale_y_continuous(
    breaks = 1:6,
    labels = str_wrap(y_axis_labels, 10),
    name = NULL,
    expand = expansion(c(0, 0.05))
  ) +
  labs(
    title = plot_title,
    subtitle = plot_subtitle,
    caption = plot_caption,
    x = NULL
  ) +
  theme_minimal(
    base_family = "body_font",
    base_size = bts
  ) +
  theme(
    # Overall plot
    plot.margin = margin(10,5,10,5, "mm"),
    panel.grid = element_blank(),
    text = element_text(
      colour = text_col,
      family = "body_font",
      lineheight = 0.3,
      margin = margin(0,0,0,0, "mm")
    ),
    plot.title.position = "plot",
    
    # Axes
    axis.ticks = element_blank(),
    axis.ticks.length = unit(0, "mm"),
    axis.text.x = element_text(
      margin = margin(6,0,0,0, "mm"),
      size = 1.9 * bts,
      face = "bold"
    ),
    axis.text.y = element_text(
      margin = margin(0,0,0,0, "mm"),
      size = 1.2 * bts,
      family = "caption_font",
      face = "bold"
    ),
    axis.title.x = element_text(
      margin = margin(0,0,0,0, "mm"),
      size = 1.5 * bts
    ),
    
    # Labels
    plot.title = element_text(
      colour = text_hil,
      family = "title_font",
      margin = margin(0,0,5,0, "mm"),
      hjust = 0.5,
      size = 2.1 * bts,
      face = "bold"
    ),
    plot.subtitle = element_text(
      colour = text_hil,
      family = "caption_font",
      margin = margin(0,0,0,0, "mm"),
      hjust = 0.5,
      size = 0.9 * bts
    ),
    plot.caption = element_textbox(
      colour = text_hil,
      family = "caption_font",
      margin = margin(5,0,0,0, "mm"),
      hjust = 0.5
    )
    
  )


ggsave(
  filename = here::here(
    "data_vizs",
    "tidy_democracy_data.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_democracy_data.png")) |> 
  image_resize(geometry = "400") |> 
  image_write(
    here::here(
      "data_vizs", 
      "thumbnails", 
      "tidy_democracy_data.png"
    )
  )

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

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

References

Bjørnskov, Christian, and Martin Rode. 2019. “Regime Types and Regime Change: A New Dataset on Democracy, Coups, and Political Institutions.” The Review of International Organizations 15 (2): 531–51. https://doi.org/10.1007/s11558-019-09345-1.