• Part I. The Bad Vis
  • Part II. A Better Vis

Optional:

  • Part III. Code Implementation

Part I. The Bad Vis

The Bad Vis

Bad Visualization
  • A vis from Visual Capitalist website (link)
  • MSCI indexes aggregate the performance of these markets, allowing investors to quantify and compare returns at a global level
  • The term “global equity return” essentially refers to the overall performance of equity markets around the world

Spotting problems

Bad Visualization
  • Color-Coding Overload
  • Alignment Issues
  • Lack of Context for the Data
  • Difficult to analyze trends

Part II. A Better Vis

A Better Vis

Why makes it better?

  • Improved Readability (Gradient Background)

  • Clarity in Trends (Distinct Line Styles and Colors)

  • Focus on Key Insights (Max and Min Annotations)

  • Better Use of Symbols (Flags for Regions)

  • Enhanced Aesthetic and Accessibility (color choices)

Thanks

Part III. Code Implementation (Optional)

Libraries

library(ggplot2)
library(ggflags)
library(dplyr)
library(tidyr)
library(grid)

Data Preparation and Transformation

# Load the dataset (update the path if necessary)
data <- read.csv("data/transposed_dataset.csv", header = TRUE)

# Ensure the first column is correctly named as "Year"
colnames(data)[1] <- "Year"

# Reshape the data into long format using tidyr
data_long <- pivot_longer(data, 
                          cols = -Year, 
                          names_to = "Region", 
                          values_to = "Equity_Return")

# Add ISO country codes for flags (ensure lowercase for ggflags)
data_long <- data_long %>%
  mutate(Country = case_when(
    Region == "USA" ~ "us",  # United States
    Region == "UK"  ~ "gb",  # United Kingdom
    Region == "EM"  ~ "un",  # Emerging Markets (custom, replace if needed)
    Region == "JAP" ~ "jp",  # Japan
    Region == "EUR" ~ "eu"   # Europe
  ))

# Ensure Year is numeric for proper plotting
data_long <- data_long %>%
  mutate(Year = as.numeric(Year))

Data Aggregation and Scaling

# Calculate max and min values per year
annotations <- data_long %>%
  group_by(Year) %>%
  summarise(
    max_val = max(Equity_Return),
    max_region = Region[which.max(Equity_Return)],
    max_country = Country[which.max(Equity_Return)],
    min_val = min(Equity_Return),
    min_region = Region[which.min(Equity_Return)],
    min_country = Country[which.min(Equity_Return)]
  )

# Set symmetric y-domain for the plot
y_abs_max <- max(abs(data_long$Equity_Return))  # Find the maximum absolute value
y_min <- -y_abs_max  # Set y_min to negative absolute maximum
y_max <- max(data_long$Equity_Return) + 10  # Extend y_max beyond dataset maximum

Generating the plot (I)

# Create the plot
ggplot(data_long, aes(x = Year, y = Equity_Return)) +
  # Add the gradient background with a smoother and symmetric transition
  annotation_raster(
    raster = as.raster(matrix(colorRampPalette(c("#98FB98", "gray", "#E35335"))(100), nrow = 100, ncol = 1)),
    xmin = -Inf, xmax = Inf,
    ymin = y_min, ymax = y_max  # Adjusted y-axis domain
  ) +
  # Add lines for each region with proper linetypes and colors
  geom_line(data = filter(data_long, Region == "EM"), aes(x = Year, y = Equity_Return, linetype = "Emerging Markets", color = "Emerging Markets"),
            alpha = 0.8, linewidth = 0.6) +
  geom_line(data = filter(data_long, Region == "USA"), aes(x = Year, y = Equity_Return, linetype = "USA", color = "USA"),
            alpha = 0.7, linewidth = 1.5) +
  geom_line(data = filter(data_long, Region == "EUR"), aes(x = Year, y = Equity_Return, linetype = "EUR", color = "EUR"),
            alpha = 0.9, linewidth = 1.3) +
  geom_line(data = filter(data_long, Region == "JAP"), aes(x = Year, y = Equity_Return, linetype = "JPN", color = "JPN"),
            alpha = 1, linewidth = 0.6) +
  geom_line(data = filter(data_long, Region == "UK"), aes(x = Year, y = Equity_Return, linetype = "UK", color = "UK"),
            alpha = 0.4, linewidth = 0.6) +

Generating the plot (II)

  # Add flags for valid country codes (on top of curves)
  geom_flag(data = filter(data_long, Country != "un"), aes(country = Country), size = 10, show.legend = FALSE) +
  # Add custom points for 'EM'
  geom_point(data = filter(data_long, Country == "un"), aes(x = Year, y = Equity_Return),
             color = "black", size = 3, shape = 21, fill = "gray", show.legend = FALSE) +
  # Add text annotations for max and min values per year
  geom_text(data = annotations, aes(x = Year, y = max_val + 5, label = max_val), size = 5, color = "black") +  # Max above dot
  geom_text(data = annotations, aes(x = Year, y = min_val - 5, label = min_val), size = 5, color = "black") +  # Min below dot
  # Add text annotation close to x-axis
  annotate("text", x = 2010, y = y_min + 25, 
           label = "2008 Global Crisis", color = "black", size = 4, hjust = 0.5) +
  # Add labels and theme
  labs(
    title = "30+ years of Global Equity Returns by Region",
    x = "Year",
    y = "Global Equity Returns (%)",
    color = "Legend",
    linetype = "Legend"
  ) +

Generating the plot (III)

  # Customize theme with gridlines and axis on top of background
  theme_minimal() +
  theme(
    panel.background = element_blank(),  # Transparent background to allow gradient visibility
    panel.grid.major = element_line(color = "black", size = 0.1), # Major gridlines on top
    panel.grid.minor = element_blank(),  # Remove minor gridlines
    panel.ontop = TRUE,  # Place axes and gridlines on top
    axis.line = element_line(color = "black"),  # Add x and y axis lines
    axis.ticks = element_line(color = "black"), # Add axis ticks
    plot.title = element_text(hjust = 0.5, size = 18, face = "bold", color = "black"), # Black title text
    axis.text = element_text(color = "black"),  # Black axis text
    axis.title = element_text(color = "black"), # Black axis labels
    legend.position = "top", # Legend at the top
    legend.title = element_blank(),
    legend.text = element_text(color = "black", size = 14), # Black legend text
    legend.background = element_rect(fill = "gray85", color = "gray85")  # Legend background
  ) +

Generating the plot (IV)

  # Customize legend colors and linetypes with updated labels
  scale_color_manual(
    values = c(
      "USA" = "black",
      "UK" = "firebrick",
      "EUR" = "blue",
      "JPN" = "white",
      "Emerging Markets" = "darkgreen"
    )
  ) +
  scale_linetype_manual(
    values = c(
      "USA" = "twodash",
      "UK" = "longdash",
      "EUR" = "dotted",
      "JPN" = "dashed",
      "Emerging Markets" = "solid"
    )
  )