Project Overview

This report examines carbon and methane flux dynamics in Arctic and Boreal ecosystems — one of the most climate-sensitive regions on Earth. As permafrost thaws and growing seasons lengthen due to climate change, high-latitude landscapes shift between acting as carbon sinks (absorbing CO₂ through photosynthesis) and carbon sources (releasing CO₂ and methane through respiration and decomposition).

The eight visualisations below explore how key carbon flux variables — Net Ecosystem Exchange (NEE), Gross Primary Production (GPP), Ecosystem Respiration (RECO), and methane flux (CH₄) — vary across biome type, permafrost presence, geography, and time.

Dataset: Arctic Boreal CO₂ Flux V2 – Terrestrial (NASA/ORNL DAAC, 2024 release).
Citation: https://daac.ornl.gov/cgi-bin/dsviewer.pl?ds_id=2137

summary_tbl <- tibble(
  Attribute = c(
    "Rows (observations)", "Columns (variables)", "Year range",
    "Countries represented", "Biomes covered",
    "Sites with permafrost (Yes)", "Latitude range"
  ),
  Value = c(
    format(nrow(flux), big.mark = ","),
    as.character(ncol(flux)),
    paste0(min(flux$year, na.rm = TRUE), " – ", max(flux$year, na.rm = TRUE)),
    paste(sort(unique(na.omit(flux$country))), collapse = ", "),
    paste(sort(unique(na.omit(flux$biome))),   collapse = ", "),
    format(sum(flux$permafrost == "Yes", na.rm = TRUE), big.mark = ","),
    paste0(round(min(flux$latitude, na.rm = TRUE), 1), "°N – ",
           round(max(flux$latitude, na.rm = TRUE), 1), "°N")
  )
)

knitr::kable(
  summary_tbl,
  col.names = c("Dataset Attribute", "Details"),
  caption   = "Table 1 — Overview of the Arctic Boreal CO₂ Flux V2 dataset"
)
Table 1 — Overview of the Arctic Boreal CO₂ Flux V2 dataset
Dataset Attribute Details
Rows (observations) 16,018
Columns (variables) 93
Year range 1990 – 2024
Countries represented Canada, Estonia, Finland, Greenland, Iceland, Japan, Mongolia, Norway, Russia, Sweden, USA
Biomes covered Boreal, Temperate, Tundra
Sites with permafrost (Yes) 6,238
Latitude range 43.3°N – 79.4°N

Figure 1 — Inter-annual Trend of NEE by Biome (Line Plot)

A multi-line plot showing mean annual Net Ecosystem Exchange for Boreal, Tundra, and Temperate biomes from 1990 to 2024. Negative values indicate net carbon uptake (sink); positive values indicate net carbon release (source).

flux %>%
  filter(!is.na(nee), !is.na(biome), !is.na(year)) %>%
  group_by(year, biome) %>%
  summarise(mean_nee = mean(nee, na.rm = TRUE), .groups = "drop") %>%
  ggplot(aes(x = year, y = mean_nee, color = biome, group = biome)) +
  geom_hline(yintercept = 0, linetype = "dashed",
             color = "grey50", linewidth = 0.6) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  scale_color_manual(values = biome_cols) +
  labs(
    title    = "Figure 1 — Inter-annual Trend of Net Ecosystem Exchange by Biome",
    subtitle = "Negative values = carbon sink  |  Positive values = carbon source",
    x        = "Year",
    y        = "Mean NEE (g C m⁻² month⁻¹)",
    color    = "Biome",
    caption  = "Source: NASA/ORNL DAAC Arctic Boreal CO₂ Flux V2"
  ) +
  theme_bw(base_size = 12) +
  theme(
    legend.position = "bottom",
    plot.title      = element_text(face = "bold"),
    plot.subtitle   = element_text(color = "grey40")
  )
Figure 1 — Inter-annual Trend of Net Ecosystem Exchange by Biome (1990–2024)

Figure 1 — Inter-annual Trend of Net Ecosystem Exchange by Biome (1990–2024)


Figure 2 — Seasonal Cycle of GPP and RECO by Biome (Line Plot)

A paired line plot of monthly mean GPP and RECO across the calendar year, faceted by biome. This illustrates how growing-season carbon uptake and respiratory release shift month by month — and how the balance differs between biome types.

month_labels <- c("Jan","Feb","Mar","Apr","May","Jun",
                  "Jul","Aug","Sep","Oct","Nov","Dec")

flux %>%
  filter(!is.na(biome), !is.na(month), between(month, 1, 12)) %>%
  select(biome, month, gpp, reco) %>%
  pivot_longer(cols = c(gpp, reco),
               names_to  = "flux_type",
               values_to = "value") %>%
  filter(!is.na(value)) %>%
  group_by(biome, month, flux_type) %>%
  summarise(mean_flux = mean(value, na.rm = TRUE), .groups = "drop") %>%
  mutate(
    flux_type = recode(flux_type,
                       "gpp"  = "GPP (Photosynthesis)",
                       "reco" = "RECO (Respiration)"),
    month_lab = factor(month_labels[month], levels = month_labels)
  ) %>%
  ggplot(aes(x = month_lab, y = mean_flux,
             color = flux_type, group = flux_type)) +
  geom_line(linewidth = 1) +
  geom_point(size = 2) +
  facet_wrap(~ biome, ncol = 3) +
  scale_color_manual(values = c("GPP (Photosynthesis)" = "#2a9d8f",
                                "RECO (Respiration)"   = "#e76f51")) +
  labs(
    title   = "Figure 2 — Seasonal Cycle of GPP and Ecosystem Respiration by Biome",
    x       = "Month",
    y       = "Mean Flux (g C m⁻² month⁻¹)",
    color   = NULL,
    caption = "Source: NASA/ORNL DAAC Arctic Boreal CO₂ Flux V2"
  ) +
  theme_bw(base_size = 11) +
  theme(
    legend.position = "bottom",
    plot.title      = element_text(face = "bold"),
    axis.text.x     = element_text(angle = 45, hjust = 1)
  )
Figure 2 — Seasonal Cycle of GPP and Ecosystem Respiration by Biome

Figure 2 — Seasonal Cycle of GPP and Ecosystem Respiration by Biome


Figure 3 — CH₄ Flux by Permafrost Presence and Biome (Box Plot)

Side-by-side box plots comparing methane emissions at permafrost vs. non-permafrost sites, split by biome. Permafrost thaw releases large quantities of methane; this figure tests whether the data support that pattern. Extreme outliers beyond 3 × IQR are removed for readability.

flux %>%
  filter(!is.na(ch4_flux_total),
         !is.na(biome),
         permafrost %in% c("Yes", "No")) %>%
  group_by(biome, permafrost) %>%
  mutate(
    iqr_val = IQR(ch4_flux_total, na.rm = TRUE),
    lo      = quantile(ch4_flux_total, 0.25, na.rm = TRUE) - 3 * iqr_val,
    hi      = quantile(ch4_flux_total, 0.75, na.rm = TRUE) + 3 * iqr_val
  ) %>%
  filter(ch4_flux_total >= lo, ch4_flux_total <= hi) %>%
  ungroup() %>%
  ggplot(aes(x = permafrost, y = ch4_flux_total, fill = permafrost)) +
  geom_boxplot(alpha = 0.7, outlier.size = 0.8) +
  facet_wrap(~ biome, ncol = 3) +
  scale_fill_manual(values = c("Yes" = "#457b9d", "No" = "#e63946")) +
  labs(
    title    = "Figure 3 — CH₄ Flux Distribution by Permafrost Presence and Biome",
    subtitle = "Outliers beyond 3 × IQR removed for readability",
    x        = "Permafrost Present",
    y        = "CH₄ Flux (mg CH₄ m⁻² day⁻¹)",
    fill     = "Permafrost",
    caption  = "Source: NASA/ORNL DAAC Arctic Boreal CO₂ Flux V2"
  ) +
  theme_bw(base_size = 11) +
  theme(
    legend.position = "bottom",
    plot.title      = element_text(face = "bold"),
    plot.subtitle   = element_text(color = "grey40")
  )
Figure 3 — CH₄ Flux Distribution by Permafrost Presence and Biome

Figure 3 — CH₄ Flux Distribution by Permafrost Presence and Biome


Figure 4 — Mean NEE by Country (Bar Chart)

A ranked horizontal bar chart showing mean annual NEE for each country with at least 30 observations. Countries are sorted from largest carbon sink to largest source. Error bars show ± 1 standard error.

flux %>%
  filter(!is.na(nee), !is.na(country)) %>%
  group_by(country) %>%
  summarise(
    mean_nee = mean(nee, na.rm = TRUE),
    se_nee   = sd(nee, na.rm = TRUE) / sqrt(n()),
    n        = n(),
    .groups  = "drop"
  ) %>%
  filter(n >= 30) %>%
  mutate(country = fct_reorder(country, mean_nee)) %>%
  ggplot(aes(x = mean_nee, y = country,
             fill = mean_nee > 0)) +
  geom_col(alpha = 0.85) +
  geom_errorbarh(aes(xmin = mean_nee - se_nee,
                     xmax = mean_nee + se_nee),
                 height = 0.4, color = "grey30", linewidth = 0.5) +
  geom_vline(xintercept = 0, color = "grey30", linewidth = 0.6) +
  scale_fill_manual(values = c("FALSE" = "#2a9d8f", "TRUE" = "#e76f51"),
                    labels = c("FALSE" = "Sink (NEE < 0)",
                               "TRUE"  = "Source (NEE > 0)")) +
  labs(
    title    = "Figure 4 — Mean Net Ecosystem Exchange by Country",
    subtitle = "Countries with ≥ 30 observations; error bars = ±1 SE",
    x        = "Mean NEE (g C m⁻² month⁻¹)",
    y        = NULL,
    fill     = NULL,
    caption  = "Source: NASA/ORNL DAAC Arctic Boreal CO₂ Flux V2"
  ) +
  theme_bw(base_size = 11) +
  theme(
    legend.position = "bottom",
    plot.title      = element_text(face = "bold"),
    plot.subtitle   = element_text(color = "grey40")
  )
Figure 4 — Mean NEE by Country

Figure 4 — Mean NEE by Country


Figure 5 — NEE vs. Air Temperature by Biome (Scatter Plot)

A scatter plot with LOESS smoothing lines showing the relationship between air temperature and NEE, colour-coded by biome. Warmer temperatures may shift the sink–source balance; this figure quantifies the temperature sensitivity of NEE. The y-axis is capped at ± 150 g C m⁻² month⁻¹ for legibility.

set.seed(42)

flux %>%
  filter(!is.na(tair), !is.na(nee), !is.na(biome)) %>%
  slice_sample(n = 3000) %>%
  ggplot(aes(x = tair, y = nee, color = biome)) +
  geom_point(alpha = 0.25, size = 0.8) +
  geom_smooth(method = "loess", span = 0.5,
              se = TRUE, linewidth = 1.2) +
  geom_hline(yintercept = 0, linetype = "dashed",
             color = "grey40", linewidth = 0.6) +
  scale_color_manual(values = biome_cols) +
  coord_cartesian(ylim = c(-150, 150)) +
  labs(
    title    = "Figure 5 — NEE vs. Air Temperature by Biome",
    subtitle = "LOESS smooth with 95% confidence band; y-axis capped at ±150",
    x        = "Air Temperature (°C)",
    y        = "NEE (g C m⁻² month⁻¹)",
    color    = "Biome",
    caption  = "Source: NASA/ORNL DAAC Arctic Boreal CO₂ Flux V2"
  ) +
  theme_bw(base_size = 11) +
  theme(
    legend.position = "bottom",
    plot.title      = element_text(face = "bold"),
    plot.subtitle   = element_text(color = "grey40")
  )
Figure 5 — NEE vs. Air Temperature by Biome

Figure 5 — NEE vs. Air Temperature by Biome


Figure 6 — GPP vs. Ecosystem Respiration Coloured by Month (Scatter Plot)

A scatter plot of GPP against RECO, coloured by month of year. Points falling above the 1:1 dashed line indicate that respiration exceeds photosynthesis (net carbon source); points below indicate net carbon uptake (sink). The seasonal colour gradient reveals how the carbon balance shifts through the year.

set.seed(42)

flux %>%
  filter(!is.na(gpp), !is.na(reco), !is.na(month),
         between(month, 1, 12)) %>%
  slice_sample(n = 4000) %>%
  mutate(month_f = factor(month.abb[month], levels = month.abb)) %>%
  ggplot(aes(x = gpp, y = reco, color = month_f)) +
  geom_abline(slope = 1, intercept = 0,
              linetype = "dashed", color = "grey30", linewidth = 0.8) +
  geom_point(alpha = 0.35, size = 0.9) +
  scale_color_viridis_d(option = "H") +
  coord_cartesian(xlim = c(0, 400), ylim = c(0, 400)) +
  annotate("text", x = 305, y = 340,
           label = "GPP = RECO\n(NEE = 0)",
           size = 3, color = "grey30", fontface = "italic") +
  labs(
    title    = "Figure 6 — GPP vs. Ecosystem Respiration, Coloured by Month",
    subtitle = "Points above dashed line: respiration > photosynthesis (carbon source)",
    x        = "GPP (g C m⁻² month⁻¹)",
    y        = "RECO (g C m⁻² month⁻¹)",
    color    = "Month",
    caption  = "Source: NASA/ORNL DAAC Arctic Boreal CO₂ Flux V2"
  ) +
  theme_bw(base_size = 11) +
  theme(
    plot.title    = element_text(face = "bold"),
    plot.subtitle = element_text(color = "grey40")
  ) +
  guides(color = guide_legend(override.aes = list(alpha = 1, size = 3),
                              nrow = 2))
Figure 6 — GPP vs. Ecosystem Respiration, Coloured by Month

Figure 6 — GPP vs. Ecosystem Respiration, Coloured by Month


Figure 7 — Measurement Sites Coloured by Mean NEE (Map)

A map of the Arctic and Boreal region plotting each research site as a point, sized by number of observations and coloured by mean NEE (diverging scale: blue = sink, red = source). This provides spatial context for where carbon sinks and sources occur across the Northern Hemisphere.

world <- map_data("world")

site_summary <- flux %>%
  filter(!is.na(latitude), !is.na(longitude), !is.na(nee)) %>%
  group_by(site_name, latitude, longitude) %>%
  summarise(
    mean_nee = mean(nee, na.rm = TRUE),
    n        = n(),
    .groups  = "drop"
  )

ggplot() +
  geom_polygon(data  = world,
               aes(x = long, y = lat, group = group),
               fill  = "grey85", color = "white", linewidth = 0.2) +
  geom_point(data = site_summary,
             aes(x = longitude, y = latitude,
                 color = mean_nee, size = n),
             alpha = 0.85) +
  scale_color_gradient2(
    low      = "#2166ac",
    mid      = "white",
    high     = "#d6604d",
    midpoint = 0,
    name     = "Mean NEE\n(g C m⁻² mo⁻¹)"
  ) +
  scale_size_continuous(range = c(1.5, 7), name = "Observations") +
  coord_quickmap(xlim = c(-170, 170), ylim = c(42, 82)) +
  labs(
    title    = "Figure 7 — Measurement Sites Coloured by Mean NEE",
    subtitle = "Blue = carbon sink  |  Red = carbon source",
    x = NULL, y = NULL,
    caption  = "Source: NASA/ORNL DAAC Arctic Boreal CO₂ Flux V2"
  ) +
  theme_void(base_size = 11) +
  theme(
    plot.title      = element_text(face = "bold",   hjust = 0.5),
    plot.subtitle   = element_text(color = "grey40", hjust = 0.5),
    legend.position = "right"
  )
Figure 7 — Measurement Sites Across the Arctic-Boreal Region Coloured by Mean NEE

Figure 7 — Measurement Sites Across the Arctic-Boreal Region Coloured by Mean NEE


Figure 8 — Interactive: NEE vs. Temperature Explorer (plotly)

An interactive scatter plot built with plotly, allowing exploration of the relationship between air temperature and NEE across all three biomes. Hover over any point to see the site name, year, biome, temperature, and NEE value. Use the legend to toggle biomes on and off. Zoom and pan are supported.

set.seed(42)

# Sample data with hover labels
plot_data <- flux %>%
  filter(!is.na(tair), !is.na(nee), !is.na(biome),
         !is.na(site_name), !is.na(year)) %>%
  slice_sample(n = 2000) %>%
  mutate(
    label = paste0(
      "<b>Site:</b> ", site_name, "<br>",
      "<b>Year:</b> ", year, "<br>",
      "<b>Biome:</b> ", biome, "<br>",
      "<b>Tair:</b> ", round(tair, 1), " °C<br>",
      "<b>NEE:</b> ", round(nee, 1), " g C m⁻² mo⁻¹"
    )
  )

# Pre-compute LOESS smooth per biome (avoids summarise/reframe issues)
biome_list   <- c("Boreal", "Tundra", "Temperate")
biome_colors <- c("Boreal" = "#2a9d8f", "Tundra" = "#e9c46a", "Temperate" = "#e76f51")

smooth_df <- bind_rows(lapply(biome_list, function(b) {
  sub <- plot_data %>% filter(biome == b) %>% arrange(tair)
  if (nrow(sub) < 10) return(NULL)
  lo       <- loess(nee ~ tair, data = sub, span = 0.5)
  tair_seq <- seq(min(sub$tair), max(sub$tair), length.out = 120)
  data.frame(biome = b,
             tair  = tair_seq,
             nee   = predict(lo, newdata = data.frame(tair = tair_seq)))
}))

# Build the plotly figure trace by trace
p <- plot_ly()

for (b in biome_list) {
  # Scatter points
  sub_pts <- plot_data %>% filter(biome == b)
  p <- add_trace(p,
    data        = sub_pts,
    x           = ~tair,
    y           = ~nee,
    text        = ~label,
    name        = b,
    legendgroup = b,
    type        = "scatter",
    mode        = "markers",
    hoverinfo   = "text",
    marker      = list(size = 5, opacity = 0.55, color = biome_colors[b])
  )
  # LOESS trend line
  sub_sm <- smooth_df %>% filter(biome == b)
  p <- add_trace(p,
    data        = sub_sm,
    x           = ~tair,
    y           = ~nee,
    name        = paste(b, "(trend)"),
    legendgroup = b,
    showlegend  = FALSE,
    type        = "scatter",
    mode        = "lines",
    hoverinfo   = "skip",
    line        = list(color = biome_colors[b], width = 2.5)
  )
}

p <- layout(p,
  title = list(
    text = "<b>Figure 8 — Interactive: NEE vs. Air Temperature by Biome</b>",
    font = list(size = 14)
  ),
  xaxis = list(
    title     = "Air Temperature (°C)",
    zeroline  = FALSE,
    gridcolor = "#e8e8e8"
  ),
  yaxis = list(
    title         = "NEE (g C m⁻² month⁻¹)",
    range         = c(-200, 200),
    zeroline      = TRUE,
    zerolinecolor = "#888888",
    zerolinewidth = 1,
    gridcolor     = "#e8e8e8"
  ),
  legend = list(
    title       = list(text = "<b>Biome</b>"),
    orientation = "h",
    x = 0.25, y = -0.15
  ),
  annotations = list(list(
    x         = 0.5,  y = -0.22,
    text      = "Source: NASA/ORNL DAAC Arctic Boreal CO₂ Flux V2",
    showarrow = FALSE,
    xref = "paper", yref = "paper",
    font = list(size = 10, color = "grey"),
    xanchor = "center"
  )),
  plot_bgcolor  = "#fafafa",
  paper_bgcolor = "#ffffff",
  hovermode     = "closest"
)

p

Figure 8 — Interactive NEE vs. Temperature Explorer


Summary

The eight figures above deliver a multi-dimensional view of carbon flux dynamics across the Arctic and Boreal region:

Figure Type Key finding
1 Line plot Boreal sites trend toward weaker sinks since ~2010
2 Line plot Growing-season GPP peaks in summer; Tundra season is shorter
3 Box plot Permafrost sites emit significantly more CH₄
4 Bar chart Large inter-country variation in mean NEE
5 Scatter NEE turns strongly negative above ~5 °C in Boreal sites
6 Scatter Winter months cluster near the 1:1 line; summer diverges
7 Map Sink sites (blue) dominate high-latitude Siberia and Canada
8 Interactive User-explorable temperature–NEE relationship by biome

Data source: Virkkala, A.-M. et al. (2024). Arctic Boreal CO₂ Flux V2 – Terrestrial. NASA/ORNL DAAC. https://daac.ornl.gov/cgi-bin/dsviewer.pl?ds_id=2137