Homework 5

Author

Sophia Wang

Setup packages and working environment

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/ruoleiw0921/homework5
library(dplyr)
library(readr)
library(reshape2)

Attaching package: 'reshape2'

The following object is masked from 'package:tidyr':

    smiths
path_to_sheet <- here("data-raw", "CM_Data_Explorer.xlsx")

path_to_sheet
[1] "/Users/ruoleiw0921/homework5/data-raw/CM_Data_Explorer.xlsx"
read_key_minerals_sheet <- partial(
  .f = read_excel,
  path = path_to_sheet,
  sheet = "2.3 EV",
  col_names = FALSE
)

sheet_header <- read_key_minerals_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
# A tibble: 2 × 23
  ...1   ...2 ...3  ...4    ...5  ...6  ...7  ...8  ...9 ...10 ...11 ...12 ...13
  <lgl> <dbl> <lgl> <chr>  <dbl> <dbl> <dbl> <dbl> <dbl> <lgl> <chr> <dbl> <dbl>
1 NA       NA NA    State…    NA    NA    NA    NA    NA NA    Anno…    NA    NA
2 NA     2022 NA    2025    2030  2035  2040  2045  2050 NA    2025   2030  2035
# ℹ 10 more variables: ...14 <dbl>, ...15 <dbl>, ...16 <dbl>, ...17 <lgl>,
#   ...18 <chr>, ...19 <dbl>, ...20 <dbl>, ...21 <dbl>, ...22 <dbl>,
#   ...23 <dbl>
sheet_header_processed <- sheet_header |> 
  # transpose the data
  t() |>
  # turn it back into a tibble
  as_tibble() |>
  # make them meaningful
  rename(scenario = V1, year = V2) |>
  # fill scenario down
  fill(scenario) |>
  #insert "Current" at top
  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`.
sheet_header_processed
# A tibble: 23 × 2
   scenario                 year 
   <chr>                    <chr>
 1 Current Year             <NA> 
 2 Current Year             2022 
 3 Current Year             <NA> 
 4 Stated policies scenario 2025 
 5 Stated policies scenario 2030 
 6 Stated policies scenario 2035 
 7 Stated policies scenario 2040 
 8 Stated policies scenario 2045 
 9 Stated policies scenario 2050 
10 Stated policies scenario <NA> 
# ℹ 13 more rows
division_name <- read_key_minerals_sheet(range = "A7") |> 
  pull()
New names:
• `` -> `...1`
division_name
[1] "Constrained nickel supply"
division_info<- read_key_minerals_sheet(range = "A8:W20")
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`
division_info
# A tibble: 13 × 23
   ...1         ...2 ...3      ...4     ...5     ...6     ...7     ...8     ...9
   <chr>       <dbl> <lgl>    <dbl>    <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
 1 Copper    3.81e+2 NA     617.    1389.    1736.    2233.    2418.    2313.   
 2 Cobalt    6.35e+1 NA      61.2     38.0     29.1     35.4     40.9     47.4  
 3 Graphite  5.57e+2 NA     936.    1590.    1782.    1691.    1492.    1077.   
 4 Lithium   6.96e+1 NA     123.     219.     307.     407.     450.     413.   
 5 Mangane…  7.45e+1 NA      53.9    156.     336.     533.     712.     858.   
 6 Nickel    3.13e+2 NA     554.     513.     589.     630.     692.     688.   
 7 Silicon   8.70e+0 NA      40.5    139.     224.     352.     374.     373.   
 8 Neodymi…  3.96e+0 NA       7.25    12.2     15.1     18.8     21.8     22.9  
 9 Dyspros…  4.13e-1 NA       0.741    1.21     1.48     1.84     2.13     2.23 
10 Praseod…  5.94e-1 NA       1.09     1.83     2.27     2.83     3.27     3.43 
11 Terbium   8.06e-2 NA       0.148    0.252    0.314    0.391    0.452    0.474
12 Total EV  1.47e+3 NA    2394.    4059.    5022.    5906.    6207.    5799.   
13 <NA>     NA       NA      NA       NA       NA       NA       NA       NA    
# ℹ 14 more variables: ...10 <lgl>, ...11 <dbl>, ...12 <dbl>, ...13 <dbl>,
#   ...14 <dbl>, ...15 <dbl>, ...16 <dbl>, ...17 <lgl>, ...18 <dbl>,
#   ...19 <dbl>, ...20 <dbl>, ...21 <dbl>, ...22 <dbl>, ...23 <dbl>
division_info_col_names <- names(division_info)

division_info_col_names
 [1] "...1"  "...2"  "...3"  "...4"  "...5"  "...6"  "...7"  "...8"  "...9" 
[10] "...10" "...11" "...12" "...13" "...14" "...15" "...16" "...17" "...18"
[19] "...19" "...20" "...21" "...22" "...23"
sheet_headers_and_col_names <- sheet_header_processed |> 
  add_column(division_info_col_names = division_info_col_names)

sheet_headers_and_col_names
# A tibble: 23 × 3
   scenario                 year  division_info_col_names
   <chr>                    <chr> <chr>                  
 1 Current Year             <NA>  ...1                   
 2 Current Year             2022  ...2                   
 3 Current Year             <NA>  ...3                   
 4 Stated policies scenario 2025  ...4                   
 5 Stated policies scenario 2030  ...5                   
 6 Stated policies scenario 2035  ...6                   
 7 Stated policies scenario 2040  ...7                   
 8 Stated policies scenario 2045  ...8                   
 9 Stated policies scenario 2050  ...9                   
10 Stated policies scenario <NA>  ...10                  
# ℹ 13 more rows
division_info_long <- division_info |> 
  rename(mineral = `...1`) |> 
  pivot_longer(cols = -mineral,
               names_to = "division_info_col_names") |> 
  add_column(division_name)

division_info_long
# A tibble: 286 × 4
   mineral division_info_col_names value division_name            
   <chr>   <chr>                   <dbl> <chr>                    
 1 Copper  ...2                     381. Constrained nickel supply
 2 Copper  ...3                      NA  Constrained nickel supply
 3 Copper  ...4                     617. Constrained nickel supply
 4 Copper  ...5                    1389. Constrained nickel supply
 5 Copper  ...6                    1736. Constrained nickel supply
 6 Copper  ...7                    2233. Constrained nickel supply
 7 Copper  ...8                    2418. Constrained nickel supply
 8 Copper  ...9                    2313. Constrained nickel supply
 9 Copper  ...10                     NA  Constrained nickel supply
10 Copper  ...11                    732. Constrained nickel supply
# ℹ 276 more rows
combined_data <- division_info_long |> 
  left_join(sheet_headers_and_col_names, by = join_by(division_info_col_names)) |> 
  filter(!is.na(mineral)) |> 
  filter(!is.na(year)) |>
   mutate(unit = case_when(
   division_name == "Share of EV market in total demand" ~ "Percent",
    .default = "kiloton"
    ),
    year = as.integer(year)
  ) |> 
  select(division_name, mineral, scenario, unit, year, value)

combined_data
# A tibble: 228 × 6
   division_name             mineral scenario                  unit   year value
   <chr>                     <chr>   <chr>                     <chr> <int> <dbl>
 1 Constrained nickel supply Copper  Current Year              kilo…  2022  381.
 2 Constrained nickel supply Copper  Stated policies scenario  kilo…  2025  617.
 3 Constrained nickel supply Copper  Stated policies scenario  kilo…  2030 1389.
 4 Constrained nickel supply Copper  Stated policies scenario  kilo…  2035 1736.
 5 Constrained nickel supply Copper  Stated policies scenario  kilo…  2040 2233.
 6 Constrained nickel supply Copper  Stated policies scenario  kilo…  2045 2418.
 7 Constrained nickel supply Copper  Stated policies scenario  kilo…  2050 2313.
 8 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2025  732.
 9 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2030 2113.
10 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2035 3587.
# ℹ 218 more rows
read_iea_division_table <-
  function(division_name_range, division_info_range) {
    division_name <- read_key_minerals_sheet(range = division_name_range) |>
    pull()
    
    division_info <- read_key_minerals_sheet(range = division_info_range)
    
    division_info_col_names <- names(division_info)
    
    division_info_long <- division_info |> 
      rename(mineral= `...1`) |> 
      pivot_longer(cols = -mineral,
                   names_to = "division_info_col_names") |> 
      add_column(division_name)
    
    combined_data <- division_info_long |> 
      left_join(sheet_headers_and_col_names, by = join_by(division_info_col_names)) |> 
      filter(!is.na(mineral)) |> 
      filter(!is.na(year)) |>
      mutate(unit = case_when(
        division_name == "Share of EV market in total demand" ~ "Percent",
        .default = "kiloton"
        ),
        year = as.integer(year)
        ) |> 
      select(division_name, mineral, scenario, unit, year, value)
    
    combined_data
    }
nickel_supply_table <- read_iea_division_table(
  division_name_range = "A7",
  division_info_range = "A8:W20"
)
New names:
New names:
• `` -> `...1`
silicon_rich_anodes_table <- read_iea_division_table(
  division_name_range = "A22",
  division_info_range = "A23:W35"
)
New names:
New names:
• `` -> `...1`
solid_state_batteries_table <- read_iea_division_table(
  division_name_range = "A37",
  division_info_range = "A38:W50"
)
New names:
New names:
• `` -> `...1`
lower_battery_sizes_table <- read_iea_division_table(
  division_name_range = "A52",
  division_info_range = "A53:W65"
)
New names:
New names:
• `` -> `...1`
size_reduction_table<- read_iea_division_table(
  division_name_range = "A67",
  division_info_range = "A68:W80"
)
New names:
New names:
• `` -> `...1`
base_case_table<- read_iea_division_table(
  division_name_range = "A82",
  division_info_range = "A83:W95"
)
New names:
New names:
• `` -> `...1`
final_iea_minerals_table <- nickel_supply_table |> 
  bind_rows(silicon_rich_anodes_table) |> 
  bind_rows(solid_state_batteries_table) |>
  bind_rows(lower_battery_sizes_table) |>
  bind_rows(size_reduction_table) |>
  bind_rows(base_case_table)

final_iea_minerals_table
# A tibble: 1,368 × 6
   division_name             mineral scenario                  unit   year value
   <chr>                     <chr>   <chr>                     <chr> <int> <dbl>
 1 Constrained nickel supply Copper  Current Year              kilo…  2022  381.
 2 Constrained nickel supply Copper  Stated policies scenario  kilo…  2025  617.
 3 Constrained nickel supply Copper  Stated policies scenario  kilo…  2030 1389.
 4 Constrained nickel supply Copper  Stated policies scenario  kilo…  2035 1736.
 5 Constrained nickel supply Copper  Stated policies scenario  kilo…  2040 2233.
 6 Constrained nickel supply Copper  Stated policies scenario  kilo…  2045 2418.
 7 Constrained nickel supply Copper  Stated policies scenario  kilo…  2050 2313.
 8 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2025  732.
 9 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2030 2113.
10 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2035 3587.
# ℹ 1,358 more rows
write_csv(final_iea_minerals_table,here("data", "iea_ev_market_for_critical_minerals.csv"))
our_cleaned_data <- here("data", "iea_ev_market_for_critical_minerals.csv") |> 
  read_csv()
Rows: 1368 Columns: 6
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (4): division_name, mineral, scenario, unit
dbl (2): year, value

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
our_cleaned_data
# A tibble: 1,368 × 6
   division_name             mineral scenario                  unit   year value
   <chr>                     <chr>   <chr>                     <chr> <dbl> <dbl>
 1 Constrained nickel supply Copper  Current Year              kilo…  2022  381.
 2 Constrained nickel supply Copper  Stated policies scenario  kilo…  2025  617.
 3 Constrained nickel supply Copper  Stated policies scenario  kilo…  2030 1389.
 4 Constrained nickel supply Copper  Stated policies scenario  kilo…  2035 1736.
 5 Constrained nickel supply Copper  Stated policies scenario  kilo…  2040 2233.
 6 Constrained nickel supply Copper  Stated policies scenario  kilo…  2045 2418.
 7 Constrained nickel supply Copper  Stated policies scenario  kilo…  2050 2313.
 8 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2025  732.
 9 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2030 2113.
10 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2035 3587.
# ℹ 1,358 more rows

Data Visualization 1

mineral_data <- our_cleaned_data |>
   filter(mineral != "Total EV")

mineral_data
# A tibble: 1,254 × 6
   division_name             mineral scenario                  unit   year value
   <chr>                     <chr>   <chr>                     <chr> <dbl> <dbl>
 1 Constrained nickel supply Copper  Current Year              kilo…  2022  381.
 2 Constrained nickel supply Copper  Stated policies scenario  kilo…  2025  617.
 3 Constrained nickel supply Copper  Stated policies scenario  kilo…  2030 1389.
 4 Constrained nickel supply Copper  Stated policies scenario  kilo…  2035 1736.
 5 Constrained nickel supply Copper  Stated policies scenario  kilo…  2040 2233.
 6 Constrained nickel supply Copper  Stated policies scenario  kilo…  2045 2418.
 7 Constrained nickel supply Copper  Stated policies scenario  kilo…  2050 2313.
 8 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2025  732.
 9 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2030 2113.
10 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2035 3587.
# ℹ 1,244 more rows
ggplot(mineral_data) +
  aes(x = mineral, y = value) +
  geom_boxplot(fill = "steelblue") +
  theme_minimal() +
  theme(axis.text = element_text(size = 5),
        axis.title = element_text(size = 11)) +
  labs(title = "Projected Supply under Different Mineral",  
       x = "Mineral", 
       y = "Supply (kiloton)") 
Warning: Removed 132 rows containing non-finite values (`stat_boxplot()`).

The boxplot indicates that the demand for copper in EV markets is significantly higher than for cobalt, suggesting that policy and technological scenarios will likely impact copper demand more substantially due to its broader range of potential outcomes. Cobalt shows a consistently lower and tighter demand distribution, implying that changes in policies or technologies might result in less drastic fluctuations in its market demand.

Data Visualization 2

copper_data <- our_cleaned_data |>
   filter(mineral== "Copper")

copper_data
# A tibble: 114 × 6
   division_name             mineral scenario                  unit   year value
   <chr>                     <chr>   <chr>                     <chr> <dbl> <dbl>
 1 Constrained nickel supply Copper  Current Year              kilo…  2022  381.
 2 Constrained nickel supply Copper  Stated policies scenario  kilo…  2025  617.
 3 Constrained nickel supply Copper  Stated policies scenario  kilo…  2030 1389.
 4 Constrained nickel supply Copper  Stated policies scenario  kilo…  2035 1736.
 5 Constrained nickel supply Copper  Stated policies scenario  kilo…  2040 2233.
 6 Constrained nickel supply Copper  Stated policies scenario  kilo…  2045 2418.
 7 Constrained nickel supply Copper  Stated policies scenario  kilo…  2050 2313.
 8 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2025  732.
 9 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2030 2113.
10 Constrained nickel supply Copper  Announced pledges scenar… kilo…  2035 3587.
# ℹ 104 more rows
ggplot(copper_data, aes(x = year, y = value, color = scenario, group = scenario)) +
  geom_point() +
  theme_minimal() +
  labs(title = "Projected Copper  Demand under Different Scenarios",
       x = "Year",
       y = "Demand (kiloton)",
       color = "Scenario") +
  theme(legend.position = "bottom")
Warning: Removed 12 rows containing missing values (`geom_point()`).

The ‘Net Zero Emissions by 2050 scenario’ indicates a sharp rise in copper demand, hinting at a significant impact of stringent climate policies on copper utilization. The ‘Stated policies scenario’ shows a more modest growth, suggesting that less aggressive policies will lead to a slower increase in demand.

Data Visualization 3

cobalt_data <- our_cleaned_data |>
   filter(mineral== "Cobalt")

cobalt_data
# A tibble: 114 × 6
   division_name             mineral scenario                  unit   year value
   <chr>                     <chr>   <chr>                     <chr> <dbl> <dbl>
 1 Constrained nickel supply Cobalt  Current Year              kilo…  2022  63.5
 2 Constrained nickel supply Cobalt  Stated policies scenario  kilo…  2025  61.2
 3 Constrained nickel supply Cobalt  Stated policies scenario  kilo…  2030  38.0
 4 Constrained nickel supply Cobalt  Stated policies scenario  kilo…  2035  29.1
 5 Constrained nickel supply Cobalt  Stated policies scenario  kilo…  2040  35.4
 6 Constrained nickel supply Cobalt  Stated policies scenario  kilo…  2045  40.9
 7 Constrained nickel supply Cobalt  Stated policies scenario  kilo…  2050  47.4
 8 Constrained nickel supply Cobalt  Announced pledges scenar… kilo…  2025  71.2
 9 Constrained nickel supply Cobalt  Announced pledges scenar… kilo…  2030  58.4
10 Constrained nickel supply Cobalt  Announced pledges scenar… kilo…  2035  61.8
# ℹ 104 more rows
ggplot(cobalt_data, aes(x = year, y = value, color = scenario, group = scenario)) +
  geom_point() +
  theme_minimal() +
  labs(title = "Projected Cobalt Demand under Different Scenarios",
       x = "Year",
       y = "Demand (kiloton)",
       color = "Scenario") +
  theme(legend.position = "bottom")
Warning: Removed 12 rows containing missing values (`geom_point()`).

The graph suggests that achieving net zero emissions by 2050 could significantly increase the supply of cobalt, reflecting a need for robust policy support and technological advancements to secure sufficient supply.

Data Visualization 4

heatmap <- ggplot(copper_data, aes(x=year, y=scenario, fill=value)) + 
  geom_tile() + 
  scale_fill_gradient(low="lightblue", high="indianred") + 
  theme_minimal() + 
  xlab('Year') + 
  ylab('Scenario') +
  ggtitle('Heatmap of Copper Demand by Scenario')

heatmap

Future demand is expected to grow regardless of the scenario, but the rate of growth varies: Even the “Stated policies scenario” and the “Announced pledges scenario” depict a noticeable increase in demand, suggesting that technological advancements and policy measures will likely result in a greater need for copper.

Data Visualization 5

heatmap1 <- ggplot(cobalt_data, aes(x=year, y=scenario, fill=value)) + 
  geom_tile() + 
  scale_fill_gradient(low="lightblue", high="indianred") + 
  theme_minimal() + 
  xlab('Year') + 
  ylab('Scenario') +
  ggtitle('Heatmap of Cobalt Demand by Scenario')

heatmap1

The “Net Zero Emissions by 2050” scenario shows the highest demand, suggesting that aggressive climate policies will greatly increase cobalt needs, likely due to its role in renewable technologies and batteries.