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"
)| 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 |
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)
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
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
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
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
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
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
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"
)
pFigure 8 — Interactive NEE vs. Temperature Explorer
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