Mallem_Bachelor_Thesis_R_Code

Drivers of Future Biodiversity Loss In Boreal And Tundra Biomes: A Scenario-Based Assessment Under the Shared Socioeconomic Pathway 2 (SSP2)

This document contains the code used for data analysis.

What are the dominant drivers of biodiversity loss in the boreal and tundra biomes under the SSP2 scenario?

  1. Which biome will experience the most biodiversity loss?

  2. How much variation exists across high-latitude biomes?

Section 1: Data cleanup, organization, and data analysis set up

Packages

library(terra)
Warning: package 'terra' was built under R version 4.3.3
terra 1.8.29
library(sf)
Warning: package 'sf' was built under R version 4.3.3
Linking to GEOS 3.11.2, GDAL 3.8.2, PROJ 9.3.1; sf_use_s2() is TRUE
library(dplyr)

Attaching package: 'dplyr'
The following objects are masked from 'package:terra':

    intersect, union
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
library(readr)
library(ggplot2)
Warning: package 'ggplot2' was built under R version 4.3.3
library(tidyr)
Warning: package 'tidyr' was built under R version 4.3.3

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

    extract
library(tidyverse)
Warning: package 'tidyverse' was built under R version 4.3.3
Warning: package 'forcats' was built under R version 4.3.3
Warning: package 'lubridate' was built under R version 4.3.3
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ lubridate 1.9.3     ✔ tibble    3.2.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ tidyr::extract() masks terra::extract()
✖ 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(viridis)
Warning: package 'viridis' was built under R version 4.3.3
Loading required package: viridisLite
library(forcats)
library(RColorBrewer)
library(ggplot2)

Uploading Data

Biome data includes WWF biome classification, and ecoregion type. The layer was clipped to only include biomes 6 (Boreal) and 11 (Tundra) in arcgis pro. Then the shapefiles are exported into R for analysis.

The data for MSA is derived from the ‘MSA tool’ (Abrosio et al., 2024). The output was a shapefile for the whole world, and in Arcgis pro, only the areas that fall under the biomes defined above are included. This is raster data in the form of a tif file, which is then imported into R.

#biome shapefiles
msa_2020 <- rast("R_analysis/msa_2020/msa_2020.tif")
msa_2050 <- rast("R_analysis/msa_2050/msa_2050.tif")
msa_2100 <- rast("R_analysis/msa_2100/msa_2100.tif")
biomes <- st_read("R_analysis/biome.shp")
Reading layer `biome' from data source 
  `C:\Users\maari\OneDrive\Desktop\thesis\R_analysis\biome.shp' 
  using driver `ESRI Shapefile'
Simple feature collection with 2687 features and 22 fields
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: -180 ymin: -54.75187 xmax: 180 ymax: 83.62313
Geodetic CRS:  WGS 84
biomes <- st_transform(biomes, crs(msa_2020))
biomes_v <- vect(biomes)

Biome specific biodiversity loss

#nodata values
msa_2020[msa_2020 == 0] <- NA
msa_2050[msa_2050 == 0] <- NA
msa_2100[msa_2100 == 0] <- NA

msa_2020_vals <- terra::extract(msa_2020, biomes_v, fun = mean, na.rm = TRUE)
msa_2050_vals <- terra::extract(msa_2050, biomes_v, fun = mean, na.rm = TRUE)
msa_2100_vals <- terra::extract(msa_2100, biomes_v, fun = mean, na.rm = TRUE)

names(msa_2020_vals)[2] <- "msa_2020"
names(msa_2050_vals)[2] <- "msa_2050"
names(msa_2100_vals)[2] <- "msa_2100"

#biome join
biomes_data <- biomes %>%
  st_drop_geometry() %>%
  mutate(ID = row_number()) %>%
  left_join(msa_2020_vals, by = "ID") %>%
  left_join(msa_2050_vals, by = "ID") %>%
  left_join(msa_2100_vals, by = "ID") %>%
  filter(!(is.na(msa_2020) & is.na(msa_2050) & is.na(msa_2100)))  

Visualize

msa change per biome in each year, biodiversiy loss is 1-msa

#colors
biome_colors <- c("Boreal" = "darkgreen", "Tundra" = "darkseagreen")
msa_long <- biomes_data %>%
  filter(BIOME %in% c(6, 11)) %>%
  mutate(
    BIOME_NAME = case_when(
      BIOME == 6 ~ "Boreal",
      BIOME == 11 ~ "Tundra"
    )
  ) %>%
  pivot_longer(
    cols = c(msa_2020, msa_2050, msa_2100),
    names_to = "Year",
    values_to = "MSA"
  ) %>%
  mutate(
    Year = as.integer(gsub("msa_", "", Year)),
    MSA_Loss = 1 - MSA
  )
#msa loss 
ggplot(msa_long, aes(x = factor(Year), y = MSA_Loss, fill = BIOME_NAME)) +
  stat_summary(
    fun = mean,
    geom = "bar",
    position = position_dodge(width = 0.9),
    width = 0.7
  ) +
  stat_summary(
    fun.data = mean_cl_normal,  
    geom = "errorbar",
    width = 0.2,
    position = position_dodge(width = 0.9)
  ) +
  scale_fill_manual(values = biome_colors) +
  labs(
    title = "Mean MSA Loss in Boreal and Tundra Biomes (with 95% CI)",
    x = "Year",
    y = "Mean MSA Loss",
    fill = "Biome"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(size = 18, face = "bold", hjust = 0.5),
    legend.title = element_text(size = 13),
    legend.text = element_text(size = 12)
  )

#colors
biome_colors <- c("Boreal" = "darkgreen", "Tundra" = "darkseagreen")

#CI 95%
p <- ggplot(msa_long, aes(x = factor(Year), y = MSA_Loss, fill = BIOME_NAME)) +
  stat_summary(
    fun = mean,
    geom = "bar",
    position = position_dodge(width = 0.9),
    width = 0.7
  ) +
  stat_summary(
    fun.data = mean_cl_normal,  
    geom = "errorbar",
    width = 0.2,
    position = position_dodge(width = 0.9)
  ) +
  scale_fill_manual(values = biome_colors) +
  labs(
    title = "Mean MSA Loss in Boreal and Tundra Biomes (with 95% CI)",
    x = "Year",
    y = "Mean MSA Loss",
    fill = "Biome"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(size = 18, face = "bold", hjust = 0.5),
    legend.title = element_text(size = 13),
    legend.text = element_text(size = 12)
  )
ggsave("msa_loss_barplot.pdf", plot = p, width = 8, height = 6)




#line graphs
msa_summary <- msa_long %>%
  group_by(BIOME_NAME, Year) %>%
  summarise(
    mean_loss = mean(MSA_Loss, na.rm = TRUE),
    se = sd(MSA_Loss) / sqrt(n()),
    .groups = "drop"
  )

ggplot(msa_summary, aes(x = Year, y = mean_loss, color = BIOME_NAME)) +
  geom_line(size = 1.2) +
  geom_point(size = 3) +
  geom_errorbar(aes(ymin = mean_loss - 1.96 * se, ymax = mean_loss + 1.96 * se),
                width = 4) +
  scale_color_manual(values = c("Boreal" = "darkgreen", "Tundra" = "darkseagreen")) +
  labs(
    title = "Trend in Mean MSA Loss Over Time",
    x = "Year",
    y = "Mean MSA Loss",
    color = "Biome"
  ) +
  theme_minimal(base_size = 14)
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.

###adding points to the grapgh
ggplot() +
  geom_jitter(data = msa_long %>% filter(Year %in% c(2020, 2050, 2100)),
              aes(x = Year, y = 1 - MSA, color = BIOME_NAME),
              width = 2, height = 0.01, alpha = 0.15, size = 1.2, show.legend = FALSE) +

#error bars
  geom_errorbar(data = msa_summary,
                aes(x = Year, ymin = mean_loss - se, ymax = mean_loss + se, group = BIOME_NAME),
                color = "black", width = 2, size = 0.7) +

  geom_line(data = msa_summary,
            aes(x = Year, y = mean_loss, color = BIOME_NAME, group = BIOME_NAME, linetype = BIOME_NAME),
            size = 1.2) +
  geom_point(data = msa_summary,
             aes(x = Year, y = mean_loss, fill = BIOME_NAME, shape = BIOME_NAME),
             color = "black", size = 3.5, stroke = 1.1) +

  scale_fill_manual(values = biome_colors) +  
  scale_color_manual(values = biome_colors) +
  scale_x_continuous(breaks = c(2020, 2050, 2100)) +

  labs(
    title = "Projected MSA Loss in Boreal and Tundra Biomes (SSP2)",
    subtitle = "Mean MSA Loss with Standard Error and Underlying Ecoregion Data",
    x = "Year",
    y = "MSA Loss (1 - MSA)",
    color = "Biome",
    fill = "Biome",
    shape = "Biome",
    linetype = "Biome",
    caption = "Each point = an ecoregion's MSA loss"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
    plot.subtitle = element_text(size = 13, hjust = 0.5),
    legend.position = "right"
  )

#inkscape
ggsave("msa_loss_biome.svg", width = 14, height = 6)
shell.exec("msa_loss_biome.svg")

Changes in Ecoregions

two ways to visualize: violin plot or line graph with raw data. the final report used the line graph with the raw data.

#colors
biome_colors <- c(
  "Boreal Forests/Taiga" = "forestgreen", 
  "Tundra" = "#1E90FF"                 
)

eco_summary <- msa_long %>%
  group_by(ECO_NAME, Year, BIOME_NAME) %>%
  summarise(
    mean_loss = mean(MSA_Loss, na.rm = TRUE),
    se_loss = sd(MSA_Loss, na.rm = TRUE) / sqrt(n()),
    .groups = "drop"
  )

top_ecoregions_all_years <- eco_summary %>%
  group_by(Year) %>%
  slice_max(order_by = mean_loss, n = 6) %>%
  ungroup() %>%
  mutate(BIOME = BIOME_NAME)

#facets
ggplot(top_ecoregions_all_years, 
       aes(x = reorder(ECO_NAME, mean_loss), y = mean_loss, fill = BIOME)) +
  geom_col(width = 0.4) +
  geom_errorbar(
    aes(ymin = mean_loss - se_loss, ymax = mean_loss + se_loss),
    width = 0.2,
    color = "black"
  ) +
  geom_text(
    aes(label = round(mean_loss, 2), y = mean_loss + se_loss + 0.03),
    hjust = 0,
    size = 4
  ) +
  coord_flip() +
  facet_wrap(~ Year, ncol = 1, scales = "free_y") +
  scale_fill_manual(values = biome_colors) +
  labs(
    title = "Top 6 Ecoregions by Biodiversity Loss (2020, 2050, 2100)",
    x = "Ecoregion",
    y = "Mean MSA Loss (1 - MSA)",
    fill = "Biome"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(size = 18, face = "bold", hjust = 0.5),
    legend.title = element_text(size = 13),
    legend.text = element_text(size = 12),
    axis.text.y = element_text(size = 11),
    axis.text.x = element_text(size = 12),
    axis.title = element_text(size = 13),
    strip.text = element_text(size = 14, face = "bold")
  )

ggsave("msa_ecoregions.svg", width = 14, height = 6)
shell.exec("msa_ecoregions.svg")
msa_violin_data <- biomes_data %>%
  filter(BIOME %in% c(6, 11)) %>%
  mutate(
    msa_loss_2020 = 1 - msa_2020,
    msa_loss_2050 = 1 - msa_2050,
    msa_loss_2100 = 1 - msa_2100
  ) %>%
  select(ECO_NAME, BIOME, msa_loss_2020, msa_loss_2050, msa_loss_2100) %>%
  pivot_longer(
    cols = starts_with("msa_loss_"),
    names_to = "Year",
    values_to = "MSA_Loss"
  ) %>%
  mutate(
    Year = as.integer(gsub("msa_loss_", "", Year)),
    BIOME = factor(BIOME, levels = c(6, 11), labels = c("Boreal", "Tundra"))
  )

#violin graphs
ggplot(msa_violin_data, aes(x = BIOME, y = MSA_Loss)) +
  geom_violin(aes(fill = MSA_Loss), scale = "width", color = "black", alpha = 0.8) +
  geom_jitter(aes(color = MSA_Loss), width = 0.15, height = 0, size = 1.2, alpha = 0.7) +
    scale_fill_gradientn(
    colors = c("#007439", "#FF8C00", "#A62800"),
    values = scales::rescale(c(0, 0.5, 1)),
    guide = "colorbar"
  ) +
  scale_color_gradientn(
    colors = c("#007439", "#FF8C00", "#A62800"),
    values = scales::rescale(c(0, 0.5, 1)),
    guide = "colorbar"
  ) +
  facet_wrap(~ Year, nrow = 1) +
  labs(
    title = "MSA Loss in Boreal and Tundra Biomes (2020–2100)",
    x = "Biome",
    y = "MSA Loss (1 - MSA)",
    fill = "MSA Loss",
    color = "MSA Loss"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(size = 16, face = "bold", hjust = 0.5),
    strip.text = element_text(size = 14),
    axis.text.x = element_text(size = 12),
    axis.title = element_text(size = 13)
  )
Warning: The following aesthetics were dropped during statistical transformation: fill.
ℹ This can happen when ggplot fails to infer the correct grouping structure in
  the data.
ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
  variable into a factor?
The following aesthetics were dropped during statistical transformation: fill.
ℹ This can happen when ggplot fails to infer the correct grouping structure in
  the data.
ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
  variable into a factor?
The following aesthetics were dropped during statistical transformation: fill.
ℹ This can happen when ggplot fails to infer the correct grouping structure in
  the data.
ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
  variable into a factor?

ggsave("msa_overtime.svg", width = 14, height = 6)
Warning: The following aesthetics were dropped during statistical transformation: fill.
ℹ This can happen when ggplot fails to infer the correct grouping structure in
  the data.
ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
  variable into a factor?
The following aesthetics were dropped during statistical transformation: fill.
ℹ This can happen when ggplot fails to infer the correct grouping structure in
  the data.
ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
  variable into a factor?
The following aesthetics were dropped during statistical transformation: fill.
ℹ This can happen when ggplot fails to infer the correct grouping structure in
  the data.
ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
  variable into a factor?
shell.exec("msa_overtime.svg")
shell.exec("msa_loss_2050.svg")

Which biome will experience the most biodiversity loss?

Significance is tested

–> boreal has more loss

—> is it significant?

—> t test

msa_long <- msa_long %>%
  mutate(MSA_Loss = 1 - MSA)
library(dplyr)

msa_summary <- msa_long %>%
  filter(Year %in% c(2050, 2100)) %>%
  group_by(BIOME_NAME, Year) %>%
  summarise(mean_loss = mean(MSA_Loss, na.rm = TRUE), .groups = "drop")

print(msa_summary)
# A tibble: 4 × 3
  BIOME_NAME  Year mean_loss
  <chr>      <int>     <dbl>
1 Boreal      2050     0.320
2 Boreal      2100     0.401
3 Tundra      2050     0.203
4 Tundra      2100     0.287
##box plot
biome_colors <- c("Boreal" = "darkgreen", "Tundra" = "darkseagreen")
ggplot(msa_long %>% filter(Year %in% c(2050, 2100)), 
       aes(x = BIOME_NAME, y = MSA_Loss, fill = BIOME_NAME)) +
  geom_boxplot(alpha = 0.7) +
  scale_fill_manual(values = biome_colors) +  # <<-- apply custom colors
  facet_wrap(~ Year) +
  labs(
    title = "MSA Loss by Biome in 2050 and 2100",
    x = "Biome",
    y = "MSA Loss",
    fill = "Biome"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title = element_text(size = 18, face = "bold", hjust = 0.5),
    axis.title = element_text(size = 14),
    axis.text = element_text(size = 12),
    legend.title = element_text(size = 13),
    legend.text = element_text(size = 12)
  )

###STATITICALLY SIGNIFICNAT there is a difference between biomes

____________________________________________________________________________________________

Pressures

select relevant regions

#pressure file
pressure_data <- read_csv("SSP2/pressure and region/gpotveg_pressure_impact_plants.csv")
Rows: 152 Columns: 5
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr (2): region, pressure
dbl (3): 2020, 2050, 2100

ℹ 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.
pressure_data <- pressure_data %>%
  mutate(
    biome = case_when(
      region %in% c("ice", "tundra") ~ "Tundra",
      region %in% c("wooded tundra", "Boreal forest", "Cool conifer forest") ~ "Boreal",
      TRUE ~ NA_character_
    )
  ) %>%
  filter(!is.na(biome))
pressure_long <- pressure_data %>%
  pivot_longer(
    cols = c(`2020`, `2050`, `2100`),
    names_to = "year",
    values_to = "msa_loss"
  )

#loss in each year
driver_summary_pct <- pressure_long %>%
  group_by(biome, year, pressure) %>%
  summarise(total_loss = sum(msa_loss, na.rm = TRUE), .groups = "drop") %>%
  group_by(biome, year) %>%
  mutate(
    percent = total_loss / sum(total_loss, na.rm = TRUE) * 100,
    label = paste0(round(percent, 1), "%")
  ) %>%
  filter(percent > 0.09)

#top 8 affected ecoregions in each year
top_pressures <- driver_summary_pct %>%
  group_by(biome, year) %>%
  slice_max(total_loss, n = 8) %>%
  ungroup()

#boreal forest top drivers
boreal_plot <- ggplot(top_pressures %>% filter(biome == "Boreal"),
                      aes(x = reorder(pressure, total_loss), y = total_loss, fill = pressure)) +
  geom_col(show.legend = FALSE) +
  geom_text(aes(label = label), hjust = -0.1, size = 3) +
  facet_wrap(~ year, scales = "free_y") +
  coord_flip() +
  labs(
    title = "Top Drivers of Biodiversity Loss in Boreal Biome",
    x = "Driver",
    y = "Total MSA Loss"
  ) +
  theme_minimal()

# top drivers in tundra
tundra_plot <- ggplot(top_pressures %>% filter(biome == "Tundra"),
                      aes(x = reorder(pressure, total_loss), y = total_loss, fill = pressure)) +
  geom_col(show.legend = FALSE) +
  geom_text(aes(label = label), hjust = -0.1, size = 3) +
  facet_wrap(~ year, scales = "free_y") +
  coord_flip() +
  labs(
    title = "Top Drivers of Biodiversity Loss in Tundra Biome",
    x = "Driver",
    y = "Total MSA Loss"
  ) +
  theme_minimal()

print(boreal_plot)

print(tundra_plot)

# Convert to long format
pressure_long <- pressure_data %>%
  pivot_longer(cols = c(`2020`, `2050`, `2100`),
               names_to = "year",
               values_to = "msa_loss") %>%
  filter(!is.na(biome)) %>%
  mutate(year = as.integer(year)) %>%
  # Drop very small or negative values (optional)
  filter(msa_loss > 1e-4)

# Aggregate total loss per pressure/biome/year
pressure_summary <- pressure_long %>%
  group_by(biome, year, pressure) %>%
  summarise(total_loss = sum(msa_loss, na.rm = TRUE), .groups = "drop")

#stacked bar chart 
ggplot(pressure_summary, aes(x = factor(year), y = total_loss, fill = pressure)) +
  geom_bar(stat = "identity") +
  facet_wrap(~ biome, scales = "free_y") +
  labs(
    title = "Total MSA Loss by Pressure in Boreal and Tundra Biomes",
    x = "Year",
    y = "Total MSA Loss",
    fill = "Pressure"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
    legend.position = "bottom"
  )

pressure_pct <- pressure_summary %>%
  group_by(biome, year) %>%
  mutate(percent = total_loss / sum(total_loss) * 100) %>%
  ungroup()




#
pressure_long <- pressure_data %>%
  pivot_longer(cols = c(`2020`, `2050`, `2100`),
               names_to = "year",
               values_to = "msa_loss") %>%
  filter(!is.na(biome)) %>%
  filter(pressure != "Land use - bioenergy") %>%
  mutate(year = as.integer(year)) %>%
  filter(msa_loss > 1e-4)  # Optional filter for readability

# Step 2: Calculate total + percent
pressure_summary <- pressure_long %>%
  group_by(biome, year, pressure) %>%
  summarise(total_loss = sum(msa_loss, na.rm = TRUE), .groups = "drop") %>%
  group_by(biome, year) %>%
  mutate(
    percent = total_loss / sum(total_loss, na.rm = TRUE) * 100,
    label = ifelse(percent >= 5, paste0(round(percent, 1), "%"), NA)  # label only > 5%
  ) %>%
  ungroup()

#
ggplot(pressure_summary, aes(x = factor(year), y = total_loss, fill = pressure)) +
  geom_bar(stat = "identity", position = "stack") +
  geom_text(
    aes(label = label),
    position = position_stack(vjust = 0.5),
    color = "black",
    size = 3.5,
    na.rm = TRUE
  ) +
  facet_wrap(~ biome, scales = "free_y") +
  labs(
    title = "Total MSA Loss by Pressure in Boreal and Tundra Biomes",
    subtitle = "With Percent Contribution (excluding Bioenergy)",
    x = "Year",
    y = "Total MSA Loss",
    fill = "Pressure"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
    plot.subtitle = element_text(size = 13, hjust = 0.5),
    legend.position = "bottom"
  )

ggplot(pressure_summary, aes(x = factor(year), y = percent, fill = pressure)) +
  geom_bar(stat = "identity", position = "stack") +
  geom_text(
    aes(label = label),
    position = position_stack(vjust = 0.5),
    color = "black",  
    size = 3.5,
    na.rm = TRUE
  ) +
  facet_wrap(~ biome) +  
  labs(
    title = "Relative MSA Loss by Pressure in Boreal and Tundra Biomes",
    subtitle = "Each bar sums to 100% (excluding bioenergy)",
    x = "Year",
    y = "Percent of Total MSA Loss",
    fill = "Pressure"
  ) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", size = 16, hjust = 0.5),
    plot.subtitle = element_text(size = 13, hjust = 0.5),
    legend.position = "bottom"
  )

ggsave("pressure_data.svg", width = 14, height = 6)

shell.exec("pressure_data.svg")
shell.exec("msa_loss_2050.svg")

Anova

to test if there’s a significant interaction between biome and pressure

A two-way ANOVA revealed significant main effects of biome (p = 0.005) and pressure (p < 0.001) on total biodiversity loss, as well as a significant interaction (p < 0.001) between biome and pressure. This indicates that the effect of a given pressure varies depending on the biome, confirming that certain drivers disproportionately impact either Boreal or Tundra ecosystems.

anova_data <- pressure_long %>%
  group_by(biome, pressure, year) %>%
  summarise(loss = sum(msa_loss, na.rm = TRUE), .groups = "drop")
anova_result <- aov(loss ~ biome * pressure, data = anova_data)
summary(anova_result)
               Df Sum Sq Mean Sq F value   Pr(>F)    
biome           1 0.0265 0.02651   8.904  0.00645 ** 
pressure        5 0.7625 0.15250  51.229 5.01e-12 ***
biome:pressure  5 0.3574 0.07148  24.011 1.29e-08 ***
Residuals      24 0.0714 0.00298                     
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1