Critical Minerals

Author

Lexi

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.4.4     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(readxl)
library(here)
here() starts at /Users/lexi/Desktop/L7/Study/SAIS/Sustainable Finance - Application and Methods/Assignment/Critical Minerals
path_to_sheet <- here("data-raw", "CM_Data_Explorer.xlsx")

read_ev_sheet <- partial(
  .f = read_excel,
  path = path_to_sheet,
  sheet = "2.3 EV",
  col_names = FALSE
)

sheet_header <- read_ev_sheet(range = "A4:W5")
New names:
• `` -> `...1`
• `` -> `...2`
• `` -> `...3`
• `` -> `...4`
• `` -> `...5`
• `` -> `...6`
• `` -> `...7`
• `` -> `...8`
• `` -> `...9`
• `` -> `...10`
• `` -> `...11`
• `` -> `...12`
• `` -> `...13`
• `` -> `...14`
• `` -> `...15`
• `` -> `...16`
• `` -> `...17`
• `` -> `...18`
• `` -> `...19`
• `` -> `...20`
• `` -> `...21`
• `` -> `...22`
• `` -> `...23`
sheet_header_processed <- sheet_header |> 
  t() |>
  as_tibble() |>
  rename(scenario = V1, year = V2) |>
  fill(scenario) |>
  replace_na(list(scenario = "Current Year"))
Warning: The `x` argument of `as_tibble.matrix()` must have unique column names if
`.name_repair` is omitted as of tibble 2.0.0.
ℹ Using compatibility `.name_repair`.
ev_type <- read_ev_sheet(range = "A7") |> 
  pull()
New names:
• `` -> `...1`
mineral_info <- read_ev_sheet(range = "A8:W19")
New names:
• `` -> `...1`
• `` -> `...2`
• `` -> `...3`
• `` -> `...4`
• `` -> `...5`
• `` -> `...6`
• `` -> `...7`
• `` -> `...8`
• `` -> `...9`
• `` -> `...10`
• `` -> `...11`
• `` -> `...12`
• `` -> `...13`
• `` -> `...14`
• `` -> `...15`
• `` -> `...16`
• `` -> `...17`
• `` -> `...18`
• `` -> `...19`
• `` -> `...20`
• `` -> `...21`
• `` -> `...22`
• `` -> `...23`
mineral_info_col_names <- names(mineral_info)

sheet_headers_and_col_names <- sheet_header_processed |> 
  add_column(mineral_info_col_names = mineral_info_col_names)

mineral_info_long <- mineral_info |> 
  rename(mineral = `...1`) |> 
  pivot_longer(cols = -mineral,
               names_to = "mineral_info_col_names") |> 
  add_column(ev_type)

combined_data <- mineral_info_long |> 
  left_join(sheet_headers_and_col_names, by = join_by(mineral_info_col_names)) |>
  filter(!is.na(year)) |>
  mutate(
    year = as.integer(year)
  ) |> 
  select(ev_type, mineral, scenario, year, value)

read_iea_ev_table <-
  function(ev_type_range, mineral_info_range) {
    ev_type <-
      read_ev_sheet(range = ev_type_range) |>
      pull()
    
    mineral_info <- read_ev_sheet(range = mineral_info_range)
    
    mineral_info_col_names <- names(mineral_info)
    
    mineral_info_long <- mineral_info |>
      rename(mineral = `...1`) |>
      pivot_longer(cols = -mineral,
                   names_to = "mineral_info_col_names") |>
      add_column(ev_type)
    
    combined_data <- mineral_info_long |>
      left_join(sheet_headers_and_col_names, by = join_by(mineral_info_col_names)) |>
      filter(!is.na(year)) |>
      mutate(
        year = as.integer(year)
      ) |>
      select(ev_type, mineral, scenario, year, value)
    
    combined_data
  }

constrained_nickel_table <- read_iea_ev_table(
  ev_type_range = "A7",
  mineral_info_range = "A8:W19"
)
New names:
New names:
• `` -> `...1`
silicon_rich_anodes_table <- read_iea_ev_table(
  ev_type_range = "A22",
  mineral_info_range = "A23:W34"
)
New names:
New names:
• `` -> `...1`
solid_state_batteries_table <- read_iea_ev_table(
  ev_type_range = "A37",
  mineral_info_range = "A38:W49"
)
New names:
New names:
• `` -> `...1`
lower_battery_sizes_table <- read_iea_ev_table(
  ev_type_range = "A52",
  mineral_info_range = "A53:W64"
)
New names:
New names:
• `` -> `...1`
limited_battery_size_reduction_table <- read_iea_ev_table(
  ev_type_range = "A67",
  mineral_info_range = "A68:W79"
)
New names:
New names:
• `` -> `...1`
base_table <- read_iea_ev_table(
  ev_type_range = "A82",
  mineral_info_range = "A83:W94"
)
New names:
New names:
• `` -> `...1`
final_iea_ev_table <- constrained_nickel_table |> 
  bind_rows(silicon_rich_anodes_table) |> 
  bind_rows(solid_state_batteries_table) |>
  bind_rows(lower_battery_sizes_table) |>
  bind_rows(limited_battery_size_reduction_table) |>
  bind_rows(base_table)

final_iea_ev_table
# A tibble: 1,368 × 5
   ev_type                   mineral scenario                    year value
   <chr>                     <chr>   <chr>                      <int> <dbl>
 1 Constrained nickel supply Copper  Current Year                2022  381.
 2 Constrained nickel supply Copper  Stated policies scenario    2025  617.
 3 Constrained nickel supply Copper  Stated policies scenario    2030 1389.
 4 Constrained nickel supply Copper  Stated policies scenario    2035 1736.
 5 Constrained nickel supply Copper  Stated policies scenario    2040 2233.
 6 Constrained nickel supply Copper  Stated policies scenario    2045 2418.
 7 Constrained nickel supply Copper  Stated policies scenario    2050 2313.
 8 Constrained nickel supply Copper  Announced pledges scenario  2025  732.
 9 Constrained nickel supply Copper  Announced pledges scenario  2030 2113.
10 Constrained nickel supply Copper  Announced pledges scenario  2035 3587.
# ℹ 1,358 more rows
write_csv(final_iea_ev_table,here("data", "iea_total_demand_for_critical_minerals.csv"))

copper_cobalt_iea_ev_table <- final_iea_ev_table |>
  filter(mineral %in% c("Copper", "Cobalt"))

write_csv(copper_cobalt_iea_ev_table,here("data", "iea_total_demand_for_critical_minerals.csv"))

1

Under Net Zero Emissions by 2050 scenario:

Copper demand increases when nickel supply decreases.

Copper demand decreases when battery size decreases.

Copper demand is not affected by the development of solid state battery and silicon-rich anode.

Copper demand flattens after 2035.

copper_cobalt_iea_ev_table %>%
 filter(mineral %in% "Copper") %>%
 filter(scenario %in% "Net Zero Emissions by 2050 scenario") %>%
 ggplot() +
  aes(x = year, y = value, colour = ev_type) +
  geom_line() +
  scale_color_brewer(palette = "Set3", direction = 1) +
  labs(
    x = "Year",
    y = "Kiloton (kt)",
    title = "Copper Demand by EV Development under Net Zero Scenario",
    subtitle = "Only affected by nickel supply and battery size",
    caption = "Source: IEA | Insight: Lexi",
    color = "EV Development"
  ) +
  theme_minimal()

2

Under Net Zero Emissions by 2050 scenario:

Cobalt demand keeps increasing unless nickel supply decreases.

Cobalt demand changes as battery size decreases.

Cobalt demand is not affected by the development of solid state battery and silicon-rich anode.

copper_cobalt_iea_ev_table %>%
 filter(mineral %in% "Cobalt") %>%
 filter(scenario %in% "Net Zero Emissions by 2050 scenario") %>%
 ggplot() +
  aes(x = year, y = value, colour = ev_type) +
  geom_line() +
  scale_color_brewer(palette = "Set3", direction = 1) +
  labs(
    x = "Year",
    y = "Kiloton (kt)",
    title = "Cobalt Demand by EV Development under Net Zero Scenario",
    subtitle = "Cobalt demand keeps increasing unless nickel supply decreases",
    caption = "Source: IEA | Insight: Lexi",
    color = "EV Development"
  ) +
  theme_minimal()

3

With constrained nickel supply:

Copper demand increases significantly compared to base case, especially under Net Zero Emissions by 2050 and announced pledges scenarios.

copper_cobalt_iea_ev_table %>%
 filter(ev_type %in% c("Constrained nickel supply", "STEPS - Base case"
)) %>%
 filter(mineral %in% "Copper") %>%
 filter(!(scenario %in% "Current Year")) %>%
 ggplot() +
  aes(x = year, y = value, colour = scenario) +
  geom_line() +
  scale_color_brewer(palette = "Accent", direction = 1) +
  labs(
    x = "Year",
    y = "Kiloton (kt)",
    title = "Copper Demand under Constrained Nickel Supply",
    subtitle = "Copper demand increases significantly compared to base case",
    caption = "Source: IEA | Insight: Lexi",
    color = "Policy Scenario"
  ) +
  theme_minimal() +
  theme(legend.position = "top") +
  facet_wrap(vars(ev_type))

4

With constrained nickel supply:

Cobalt demand decreases significantly compared to base case.

Cobalt demand declines overall.

Cobalt demand stops declining between 2030-2040.

copper_cobalt_iea_ev_table %>%
 filter(ev_type %in% c("Constrained nickel supply", "STEPS - Base case"
)) %>%
 filter(mineral %in% "Cobalt") %>%
 filter(!(scenario %in% "Current Year")) %>%
 ggplot() +
  aes(x = year, y = value, colour = scenario) +
  geom_line() +
  scale_color_brewer(palette = "Accent", direction = 1) +
  labs(
    x = "Year",
    y = "Kiloton (kt)",
    title = "Cobalt Demand under Constrained Nickel Supply",
    subtitle = "Cobalt demand declines overall",
    caption = "Source: IEA | Insight: Lexi",
    color = "Policy Scenario"
  ) +
  theme_minimal() +
  theme(legend.position = "top") +
  facet_wrap(vars(ev_type))

5

Under Announced Pledges Scenario:

Copper demand is significantly larger than cobalt demand.

Copper demand continues to grow larger as EV evolves, while cobalt demand flattens or declines.

copper_cobalt_iea_ev_table %>%
 filter(!(ev_type %in% "Limited battery size reduction")) %>%
 filter(scenario %in% 
 "Announced pledges scenario") %>%
 mutate(ev_type = factor(ev_type, levels = c(
   "Wider use of silicon-rich anodes", 
   "Faster uptake of solid state batteries", 
   "Lower battery sizes",
   "Constrained nickel supply",
   "STEPS - Base case",
   "Limited battery size reduction"))) %>%
 ggplot() +
  aes(x = year, y = value, colour = mineral) +
  geom_line() +
  scale_color_hue(direction = 1) +
  labs(
    x = "Year",
    y = "Kiloton (kt)",
    title = "Copper and Cobalt Demand under Announced Pledges Scenario",
    subtitle = "Copper demand grows larger as EV evolves",
    caption = "Source: IEA | Insights: Lexi",
    color = "Mineral"
  ) +
  theme_minimal() +
  facet_wrap(vars(ev_type))