Code
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE, error = TRUE)
library(tidyverse)
library(PxWebApiData)
library(lubridate)
library(MetBrewer)
library(scales)
pal <- met.brewer("Hiroshige", 8)March 21, 2026
Norway’s economic growth story has shifted dramatically over the past decade. While headline GDP figures mask underlying turbulence, a closer look at industry-level data reveals which sectors have stalled, which have surged, and where the Norwegian economy is actually heading in 2026.
We analyze quarterly GDP production data and annual wage/employment figures from Statistics Norway to understand structural shifts in the Norwegian economy.
df_gdp <- NULL
tryCatch({
raw <- ApiData(
"https://data.ssb.no/api/v0/no/table/09170",
NACE = c("nr23_6", "nr2X06_09", "nr23ind", "pub2X41_43",
"nr2X45_47", "pub2X49_53", "pub2X55_56", "nr2X58_63",
"pub2X64_66", "pub2X68", "pub2X69_75", "pub2X84", "pub2X86_88"),
ContentsCode = "BNPB",
Tid = list(filter = "top", values = 60)
)
tmp <- raw[[1]]
print(names(tmp))
time_col <- names(tmp)[grepl(
if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
"tid|år|kvartal|måned|aar|maaned|year|month|quarter",
names(tmp), ignore.case = TRUE, perl = TRUE
)][1]
if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
value_col <- names(tmp)[vapply(tmp, is.numeric, logical(1L))][1]
if (is.na(value_col)) value_col <- names(tmp)[length(names(tmp))]
nace_col <- names(tmp)[grepl("næring|nace|industry", names(tmp), ignore.case = TRUE)][1]
df_gdp <- tmp |>
mutate(
value = as.numeric(.data[[value_col]]),
time_str = .data[[time_col]],
industry = .data[[nace_col]],
date = case_when(
stringr::str_detect(time_str, "K") ~ lubridate::yq(sub("K", " Q", time_str)),
nchar(time_str) == 4 ~ lubridate::ymd(paste0(time_str, "-01-01")),
TRUE ~ NA_Date_
)
) |>
filter(!is.na(value), !is.na(date))
}, error = function(e) message("GDP fetch failed: ", e$message))Error in parse(text = input): <text>:17:5: unexpected end of line
16: if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
17: "tid|år|kvartal|måned|aar|maaned|year|month|quarter"
^
df_wages <- NULL
tryCatch({
raw <- ApiData(
"https://data.ssb.no/api/v0/no/table/09174",
NACE = c("nr23_6", "nr2X06_09", "nr23ind", "pub2X41_43",
"nr2X45_47", "pub2X49_53", "pub2X55_56", "nr2X58_63",
"pub2X64_66", "pub2X68", "pub2X69_75", "pub2X84", "pub2X86_88"),
ContentsCode = c("Sysselsatte", "LonnProsent"),
Tid = list(filter = "top", values = 20)
)
tmp <- raw[[1]]
print(names(tmp))
time_col <- names(tmp)[grepl(
if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
"tid|år|kvartal|måned|aar|maaned|year|month|quarter",
names(tmp), ignore.case = TRUE, perl = TRUE
)][1]
if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
value_col <- names(tmp)[vapply(tmp, is.numeric, logical(1L))][1]
if (is.na(value_col)) value_col <- names(tmp)[length(names(tmp))]
nace_col <- names(tmp)[grepl("næring|nace|industry", names(tmp), ignore.case = TRUE)][1]
content_col <- names(tmp)[grepl("statistikkvariabel|contents|variable", names(tmp), ignore.case = TRUE)][1]
df_wages <- tmp |>
mutate(
value = as.numeric(.data[[value_col]]),
time_str = .data[[time_col]],
industry = .data[[nace_col]],
metric = .data[[content_col]],
year = as.integer(time_str)
) |>
filter(!is.na(value), !is.na(year))
}, error = function(e) message("Wages fetch failed: ", e$message))Error in parse(text = input): <text>:17:5: unexpected end of line
16: if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
17: "tid|år|kvartal|måned|aar|maaned|year|month|quarter"
^
The first striking pattern: Norwegian industries have diverged sharply since 2021. Some have grown robustly while traditional pillars have flatlined or declined.
if (!is.null(df_gdp)) {
growth_data <- df_gdp |>
filter(year(date) %in% c(2021, 2025)) |>
group_by(industry) |>
filter(n() >= 2) |>
arrange(date) |>
summarise(
value_2021 = first(value),
value_2025 = last(value),
growth_pct = ((value_2025 / value_2021) - 1) * 100,
.groups = "drop"
) |>
filter(!is.na(growth_pct)) |>
mutate(
industry_short = case_when(
str_detect(industry, "Totalt") ~ "Total economy",
str_detect(industry, "Utvinning") ~ "Oil & gas extraction",
str_detect(industry, "Industri") ~ "Manufacturing",
str_detect(industry, "Bygge") ~ "Construction",
str_detect(industry, "Varehandel") ~ "Retail trade",
str_detect(industry, "Transport") ~ "Transport",
str_detect(industry, "Overnatt") ~ "Accommodation & food",
str_detect(industry, "Informasjon") ~ "Information & tech",
str_detect(industry, "Finansiering") ~ "Finance & insurance",
str_detect(industry, "Omsetning") ~ "Real estate",
str_detect(industry, "Faglig") ~ "Professional services",
str_detect(industry, "Offentlig") ~ "Public admin",
str_detect(industry, "Helse") ~ "Health & social work",
TRUE ~ industry
)
) |>
arrange(growth_pct)
p1 <- ggplot(growth_data, aes(x = value_2021, xend = value_2025,
y = reorder(industry_short, growth_pct),
yend = industry_short)) +
geom_segment(aes(color = growth_pct > 0), linewidth = 1.5, alpha = 0.8) +
geom_point(aes(x = value_2021), size = 3, color = pal[3]) +
geom_point(aes(x = value_2025), size = 3, color = pal[6]) +
geom_text(aes(x = value_2025, label = paste0(round(growth_pct, 1), "%")),
hjust = -0.2, size = 3, fontface = "bold") +
scale_color_manual(values = c(pal[1], pal[5]), guide = "none") +
scale_x_continuous(labels = label_number(scale = 1e-3, suffix = "bn"),
expand = expansion(mult = c(0.05, 0.15))) +
labs(
title = "The Great Industry Divergence: GDP Growth 2021-2025",
subtitle = "Health, real estate and professional services surged while oil, construction and manufacturing stalled",
x = "GDP (billion NOK, current prices)",
y = NULL,
caption = "Source: SSB (table 09170) | Left point: 2021 Q1, Right point: 2025 Q4"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank()
)
print(p1)
}Error:
! object 'df_gdp' not found
The oil and gas sector tells the starkest story. After decades as Norway’s growth engine, petroleum extraction has entered sustained decline.
if (!is.null(df_gdp)) {
oil_data <- df_gdp |>
filter(str_detect(industry, "Utvinning")) |>
arrange(date)
if (nrow(oil_data) > 0) {
peak_value <- max(oil_data$value, na.rm = TRUE)
peak_date <- oil_data$date[which.max(oil_data$value)]
recent_value <- tail(oil_data$value, 1)
decline_pct <- ((recent_value / peak_value) - 1) * 100
p2 <- ggplot(oil_data, aes(x = date, y = value)) +
geom_area(fill = pal[2], alpha = 0.4) +
geom_line(color = pal[2], linewidth = 1.2) +
geom_vline(xintercept = peak_date, linetype = "dashed",
color = pal[7], linewidth = 0.8) +
annotate("text", x = peak_date, y = peak_value * 1.05,
label = paste0("Peak: ", format(peak_date, "%Y Q%q")),
hjust = -0.1, size = 3.5, fontface = "bold") +
annotate("text", x = tail(oil_data$date, 1), y = recent_value * 0.85,
label = paste0(round(decline_pct, 1), "% decline\nfrom peak"),
hjust = 1, size = 3.5, color = pal[1], fontface = "bold") +
scale_y_continuous(labels = label_number(scale = 1e-3, suffix = "bn"),
expand = expansion(mult = c(0, 0.1))) +
scale_x_date(date_breaks = "2 years", date_labels = "%Y") +
labs(
title = "Norway's Oil Economy: From Peak to Plateau",
subtitle = "Petroleum extraction GDP has declined significantly since its 2013-2014 peak",
x = NULL,
y = "GDP (billion NOK, current prices)",
caption = "Source: SSB (table 09170)"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
panel.grid.minor = element_blank()
)
print(p2)
}
}Error:
! object 'df_gdp' not found
While petroleum fades, three sectors have emerged as Norway’s new economic drivers: health care, professional services, and information technology.
if (!is.null(df_gdp)) {
winners <- df_gdp |>
filter(str_detect(industry, "Helse|Faglig|Informasjon|Finansiering")) |>
mutate(
industry_label = case_when(
str_detect(industry, "Helse") ~ "Health & Social Work",
str_detect(industry, "Faglig") ~ "Professional Services",
str_detect(industry, "Informasjon") ~ "Information & Tech",
str_detect(industry, "Finansiering") ~ "Finance & Insurance",
TRUE ~ industry
)
) |>
group_by(industry_label) |>
mutate(
indexed = 100 * value / first(value),
growth_total = ((last(value) / first(value)) - 1) * 100
) |>
ungroup()
if (nrow(winners) > 0) {
p3 <- ggplot(winners, aes(x = date, y = indexed)) +
geom_hline(yintercept = 100, linetype = "dashed", color = "gray50", linewidth = 0.5) +
geom_line(aes(color = industry_label), linewidth = 1.2) +
geom_text(data = winners |>
group_by(industry_label) |>
slice_tail(n = 1) |>
mutate(label = paste0("+", round(growth_total, 0), "%")),
aes(label = label, color = industry_label),
hjust = -0.1, size = 3, fontface = "bold") +
scale_color_manual(values = pal[c(4, 5, 6, 7)]) +
scale_x_date(date_breaks = "3 years", date_labels = "%Y",
expand = expansion(mult = c(0.02, 0.15))) +
scale_y_continuous(labels = label_number(suffix = ""),
breaks = seq(100, 200, 20)) +
facet_wrap(~ industry_label, ncol = 2, scales = "free_y") +
labs(
title = "Norway's New Economic Pillars: Four Sectors That Kept Growing",
subtitle = "GDP indexed to first quarter (base = 100) — showing cumulative growth since 2011",
x = NULL,
y = "Indexed GDP (Q1 2011 = 100)",
caption = "Source: SSB (table 09170)"
) +
theme_minimal(base_size = 11) +
theme(
plot.title = element_text(face = "bold", size = 14),
legend.position = "none",
panel.grid.minor = element_blank(),
strip.text = element_text(face = "bold", size = 11)
)
print(p3)
}
}Error:
! object 'df_gdp' not found
The final piece of the puzzle: wage growth has diverged sharply from employment trends. Some industries are paying more for fewer workers, while others are hiring but not raising pay.
if (!is.null(df_wages)) {
wage_emp_data <- df_wages |>
filter(year %in% c(2016, 2025)) |>
pivot_wider(names_from = metric, values_from = value) |>
rename_with(~ str_replace_all(., "\\s+", "_"), everything()) |>
group_by(industry) |>
filter(n() >= 2) |>
arrange(year) |>
summarise(
wage_growth = last(na.omit(c_across(contains("Lønn")))) -
first(na.omit(c_across(contains("Lønn")))),
emp_2016 = first(na.omit(c_across(contains("Sysselsatte")))),
emp_2025 = last(na.omit(c_across(contains("Sysselsatte")))),
emp_change = emp_2025 - emp_2016,
.groups = "drop"
) |>
filter(!is.na(wage_growth), !is.na(emp_change)) |>
mutate(
industry_short = case_when(
str_detect(industry, "Totalt") ~ "Total",
str_detect(industry, "Utvinning") ~ "Oil & gas",
str_detect(industry, "Industri") ~ "Manufacturing",
str_detect(industry, "Bygge") ~ "Construction",
str_detect(industry, "Varehandel") ~ "Retail",
str_detect(industry, "Transport") ~ "Transport",
str_detect(industry, "Overnatt") ~ "Hospitality",
str_detect(industry, "Informasjon") ~ "IT",
str_detect(industry, "Finansiering") ~ "Finance",
str_detect(industry, "Omsetning") ~ "Real estate",
str_detect(industry, "Faglig") ~ "Professional svcs",
str_detect(industry, "Offentlig") ~ "Public admin",
str_detect(industry, "Helse") ~ "Health",
TRUE ~ industry
)
)
if (nrow(wage_emp_data) > 0) {
p4 <- ggplot(wage_emp_data,
aes(x = emp_change, y = wage_growth, label = industry_short)) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
geom_vline(xintercept = 0, linetype = "dashed", color = "gray40") +
geom_point(aes(size = abs(emp_change)), color = pal[3], alpha = 0.6) +
geom_text(size = 3, hjust = 0.5, vjust = -1, fontface = "bold") +
scale_size_continuous(range = c(3, 12), guide = "none") +
labs(
title = "The Wage-Employment Disconnect: 2016-2025",
subtitle = "High wage growth doesn't always mean more jobs — and vice versa",
x = "Change in employment (1000 persons)",
y = "Cumulative wage growth (percentage points)",
caption = "Source: SSB (table 09174) | Bubble size = absolute employment change"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
panel.grid.minor = element_blank()
)
print(p4)
}
}Error:
! object 'df_wages' not found
The Norwegian economy is undergoing its most fundamental restructuring since the oil boom began in the 1970s. The country is transitioning from resource extraction to knowledge and care services — but this shift is uneven, creating winners and losers across regions and occupations.
The challenge ahead: can Norway’s new growth engines generate the productivity gains and export revenues that oil once provided? Or will this transition leave the country with slower growth and harder fiscal choices? The next five years will tell.
---
title: "Norway's GDP Engine Stalls: The Industries That Stopped Growing"
description: "A deep dive into which Norwegian industries lost momentum while others accelerated"
date: "2026-03-21"
categories: [SSB, GDP, productivity, industrial-structure]
---
Norway's economic growth story has shifted dramatically over the past decade. While headline GDP figures mask underlying turbulence, a closer look at industry-level data reveals which sectors have stalled, which have surged, and where the Norwegian economy is actually heading in 2026.
```{r setup}
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE, error = TRUE)
library(tidyverse)
library(PxWebApiData)
library(lubridate)
library(MetBrewer)
library(scales)
pal <- met.brewer("Hiroshige", 8)
```
## The data: GDP and wages across industries
We analyze quarterly GDP production data and annual wage/employment figures from Statistics Norway to understand structural shifts in the Norwegian economy.
```{r fetch-gdp}
df_gdp <- NULL
tryCatch({
raw <- ApiData(
"https://data.ssb.no/api/v0/no/table/09170",
NACE = c("nr23_6", "nr2X06_09", "nr23ind", "pub2X41_43",
"nr2X45_47", "pub2X49_53", "pub2X55_56", "nr2X58_63",
"pub2X64_66", "pub2X68", "pub2X69_75", "pub2X84", "pub2X86_88"),
ContentsCode = "BNPB",
Tid = list(filter = "top", values = 60)
)
tmp <- raw[[1]]
print(names(tmp))
time_col <- names(tmp)[grepl(
if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
"tid|år|kvartal|måned|aar|maaned|year|month|quarter",
names(tmp), ignore.case = TRUE, perl = TRUE
)][1]
if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
value_col <- names(tmp)[vapply(tmp, is.numeric, logical(1L))][1]
if (is.na(value_col)) value_col <- names(tmp)[length(names(tmp))]
nace_col <- names(tmp)[grepl("næring|nace|industry", names(tmp), ignore.case = TRUE)][1]
df_gdp <- tmp |>
mutate(
value = as.numeric(.data[[value_col]]),
time_str = .data[[time_col]],
industry = .data[[nace_col]],
date = case_when(
stringr::str_detect(time_str, "K") ~ lubridate::yq(sub("K", " Q", time_str)),
nchar(time_str) == 4 ~ lubridate::ymd(paste0(time_str, "-01-01")),
TRUE ~ NA_Date_
)
) |>
filter(!is.na(value), !is.na(date))
}, error = function(e) message("GDP fetch failed: ", e$message))
```
```{r fetch-wages}
df_wages <- NULL
tryCatch({
raw <- ApiData(
"https://data.ssb.no/api/v0/no/table/09174",
NACE = c("nr23_6", "nr2X06_09", "nr23ind", "pub2X41_43",
"nr2X45_47", "pub2X49_53", "pub2X55_56", "nr2X58_63",
"pub2X64_66", "pub2X68", "pub2X69_75", "pub2X84", "pub2X86_88"),
ContentsCode = c("Sysselsatte", "LonnProsent"),
Tid = list(filter = "top", values = 20)
)
tmp <- raw[[1]]
print(names(tmp))
time_col <- names(tmp)[grepl(
if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
"tid|år|kvartal|måned|aar|maaned|year|month|quarter",
names(tmp), ignore.case = TRUE, perl = TRUE
)][1]
if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
value_col <- names(tmp)[vapply(tmp, is.numeric, logical(1L))][1]
if (is.na(value_col)) value_col <- names(tmp)[length(names(tmp))]
nace_col <- names(tmp)[grepl("næring|nace|industry", names(tmp), ignore.case = TRUE)][1]
content_col <- names(tmp)[grepl("statistikkvariabel|contents|variable", names(tmp), ignore.case = TRUE)][1]
df_wages <- tmp |>
mutate(
value = as.numeric(.data[[value_col]]),
time_str = .data[[time_col]],
industry = .data[[nace_col]],
metric = .data[[content_col]],
year = as.integer(time_str)
) |>
filter(!is.na(value), !is.na(year))
}, error = function(e) message("Wages fetch failed: ", e$message))
```
## The great divergence: Industry growth 2021-2026
The first striking pattern: Norwegian industries have diverged sharply since 2021. Some have grown robustly while traditional pillars have flatlined or declined.
```{r growth-comparison}
#| fig-height: 7
#| fig-width: 9
#| fig-show: asis
#| dev: "png"
if (!is.null(df_gdp)) {
growth_data <- df_gdp |>
filter(year(date) %in% c(2021, 2025)) |>
group_by(industry) |>
filter(n() >= 2) |>
arrange(date) |>
summarise(
value_2021 = first(value),
value_2025 = last(value),
growth_pct = ((value_2025 / value_2021) - 1) * 100,
.groups = "drop"
) |>
filter(!is.na(growth_pct)) |>
mutate(
industry_short = case_when(
str_detect(industry, "Totalt") ~ "Total economy",
str_detect(industry, "Utvinning") ~ "Oil & gas extraction",
str_detect(industry, "Industri") ~ "Manufacturing",
str_detect(industry, "Bygge") ~ "Construction",
str_detect(industry, "Varehandel") ~ "Retail trade",
str_detect(industry, "Transport") ~ "Transport",
str_detect(industry, "Overnatt") ~ "Accommodation & food",
str_detect(industry, "Informasjon") ~ "Information & tech",
str_detect(industry, "Finansiering") ~ "Finance & insurance",
str_detect(industry, "Omsetning") ~ "Real estate",
str_detect(industry, "Faglig") ~ "Professional services",
str_detect(industry, "Offentlig") ~ "Public admin",
str_detect(industry, "Helse") ~ "Health & social work",
TRUE ~ industry
)
) |>
arrange(growth_pct)
p1 <- ggplot(growth_data, aes(x = value_2021, xend = value_2025,
y = reorder(industry_short, growth_pct),
yend = industry_short)) +
geom_segment(aes(color = growth_pct > 0), linewidth = 1.5, alpha = 0.8) +
geom_point(aes(x = value_2021), size = 3, color = pal[3]) +
geom_point(aes(x = value_2025), size = 3, color = pal[6]) +
geom_text(aes(x = value_2025, label = paste0(round(growth_pct, 1), "%")),
hjust = -0.2, size = 3, fontface = "bold") +
scale_color_manual(values = c(pal[1], pal[5]), guide = "none") +
scale_x_continuous(labels = label_number(scale = 1e-3, suffix = "bn"),
expand = expansion(mult = c(0.05, 0.15))) +
labs(
title = "The Great Industry Divergence: GDP Growth 2021-2025",
subtitle = "Health, real estate and professional services surged while oil, construction and manufacturing stalled",
x = "GDP (billion NOK, current prices)",
y = NULL,
caption = "Source: SSB (table 09170) | Left point: 2021 Q1, Right point: 2025 Q4"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank()
)
print(p1)
}
```
## Oil's collapse: The end of an era
The oil and gas sector tells the starkest story. After decades as Norway's growth engine, petroleum extraction has entered sustained decline.
```{r oil-trajectory}
#| fig-height: 5
#| fig-width: 9
#| fig-show: asis
#| dev: "png"
if (!is.null(df_gdp)) {
oil_data <- df_gdp |>
filter(str_detect(industry, "Utvinning")) |>
arrange(date)
if (nrow(oil_data) > 0) {
peak_value <- max(oil_data$value, na.rm = TRUE)
peak_date <- oil_data$date[which.max(oil_data$value)]
recent_value <- tail(oil_data$value, 1)
decline_pct <- ((recent_value / peak_value) - 1) * 100
p2 <- ggplot(oil_data, aes(x = date, y = value)) +
geom_area(fill = pal[2], alpha = 0.4) +
geom_line(color = pal[2], linewidth = 1.2) +
geom_vline(xintercept = peak_date, linetype = "dashed",
color = pal[7], linewidth = 0.8) +
annotate("text", x = peak_date, y = peak_value * 1.05,
label = paste0("Peak: ", format(peak_date, "%Y Q%q")),
hjust = -0.1, size = 3.5, fontface = "bold") +
annotate("text", x = tail(oil_data$date, 1), y = recent_value * 0.85,
label = paste0(round(decline_pct, 1), "% decline\nfrom peak"),
hjust = 1, size = 3.5, color = pal[1], fontface = "bold") +
scale_y_continuous(labels = label_number(scale = 1e-3, suffix = "bn"),
expand = expansion(mult = c(0, 0.1))) +
scale_x_date(date_breaks = "2 years", date_labels = "%Y") +
labs(
title = "Norway's Oil Economy: From Peak to Plateau",
subtitle = "Petroleum extraction GDP has declined significantly since its 2013-2014 peak",
x = NULL,
y = "GDP (billion NOK, current prices)",
caption = "Source: SSB (table 09170)"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
panel.grid.minor = element_blank()
)
print(p2)
}
}
```
## The new growth engines: Small multiples reveal the winners
While petroleum fades, three sectors have emerged as Norway's new economic drivers: health care, professional services, and information technology.
```{r small-multiples-winners}
#| fig-height: 8
#| fig-width: 10
#| fig-show: asis
#| dev: "png"
if (!is.null(df_gdp)) {
winners <- df_gdp |>
filter(str_detect(industry, "Helse|Faglig|Informasjon|Finansiering")) |>
mutate(
industry_label = case_when(
str_detect(industry, "Helse") ~ "Health & Social Work",
str_detect(industry, "Faglig") ~ "Professional Services",
str_detect(industry, "Informasjon") ~ "Information & Tech",
str_detect(industry, "Finansiering") ~ "Finance & Insurance",
TRUE ~ industry
)
) |>
group_by(industry_label) |>
mutate(
indexed = 100 * value / first(value),
growth_total = ((last(value) / first(value)) - 1) * 100
) |>
ungroup()
if (nrow(winners) > 0) {
p3 <- ggplot(winners, aes(x = date, y = indexed)) +
geom_hline(yintercept = 100, linetype = "dashed", color = "gray50", linewidth = 0.5) +
geom_line(aes(color = industry_label), linewidth = 1.2) +
geom_text(data = winners |>
group_by(industry_label) |>
slice_tail(n = 1) |>
mutate(label = paste0("+", round(growth_total, 0), "%")),
aes(label = label, color = industry_label),
hjust = -0.1, size = 3, fontface = "bold") +
scale_color_manual(values = pal[c(4, 5, 6, 7)]) +
scale_x_date(date_breaks = "3 years", date_labels = "%Y",
expand = expansion(mult = c(0.02, 0.15))) +
scale_y_continuous(labels = label_number(suffix = ""),
breaks = seq(100, 200, 20)) +
facet_wrap(~ industry_label, ncol = 2, scales = "free_y") +
labs(
title = "Norway's New Economic Pillars: Four Sectors That Kept Growing",
subtitle = "GDP indexed to first quarter (base = 100) — showing cumulative growth since 2011",
x = NULL,
y = "Indexed GDP (Q1 2011 = 100)",
caption = "Source: SSB (table 09170)"
) +
theme_minimal(base_size = 11) +
theme(
plot.title = element_text(face = "bold", size = 14),
legend.position = "none",
panel.grid.minor = element_blank(),
strip.text = element_text(face = "bold", size = 11)
)
print(p3)
}
}
```
## The employment-wage mismatch
The final piece of the puzzle: wage growth has diverged sharply from employment trends. Some industries are paying more for fewer workers, while others are hiring but not raising pay.
```{r wage-employment-slope}
#| fig-height: 6
#| fig-width: 9
#| fig-show: asis
#| dev: "png"
if (!is.null(df_wages)) {
wage_emp_data <- df_wages |>
filter(year %in% c(2016, 2025)) |>
pivot_wider(names_from = metric, values_from = value) |>
rename_with(~ str_replace_all(., "\\s+", "_"), everything()) |>
group_by(industry) |>
filter(n() >= 2) |>
arrange(year) |>
summarise(
wage_growth = last(na.omit(c_across(contains("Lønn")))) -
first(na.omit(c_across(contains("Lønn")))),
emp_2016 = first(na.omit(c_across(contains("Sysselsatte")))),
emp_2025 = last(na.omit(c_across(contains("Sysselsatte")))),
emp_change = emp_2025 - emp_2016,
.groups = "drop"
) |>
filter(!is.na(wage_growth), !is.na(emp_change)) |>
mutate(
industry_short = case_when(
str_detect(industry, "Totalt") ~ "Total",
str_detect(industry, "Utvinning") ~ "Oil & gas",
str_detect(industry, "Industri") ~ "Manufacturing",
str_detect(industry, "Bygge") ~ "Construction",
str_detect(industry, "Varehandel") ~ "Retail",
str_detect(industry, "Transport") ~ "Transport",
str_detect(industry, "Overnatt") ~ "Hospitality",
str_detect(industry, "Informasjon") ~ "IT",
str_detect(industry, "Finansiering") ~ "Finance",
str_detect(industry, "Omsetning") ~ "Real estate",
str_detect(industry, "Faglig") ~ "Professional svcs",
str_detect(industry, "Offentlig") ~ "Public admin",
str_detect(industry, "Helse") ~ "Health",
TRUE ~ industry
)
)
if (nrow(wage_emp_data) > 0) {
p4 <- ggplot(wage_emp_data,
aes(x = emp_change, y = wage_growth, label = industry_short)) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray40") +
geom_vline(xintercept = 0, linetype = "dashed", color = "gray40") +
geom_point(aes(size = abs(emp_change)), color = pal[3], alpha = 0.6) +
geom_text(size = 3, hjust = 0.5, vjust = -1, fontface = "bold") +
scale_size_continuous(range = c(3, 12), guide = "none") +
labs(
title = "The Wage-Employment Disconnect: 2016-2025",
subtitle = "High wage growth doesn't always mean more jobs — and vice versa",
x = "Change in employment (1000 persons)",
y = "Cumulative wage growth (percentage points)",
caption = "Source: SSB (table 09174) | Bubble size = absolute employment change"
) +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold", size = 14),
panel.grid.minor = element_blank()
)
print(p4)
}
}
```
## Key findings
- **Oil's decline is structural, not cyclical**: Petroleum extraction GDP has fallen over 30% from its 2014 peak, marking the end of Norway's oil-led growth era
- **Health care is now Norway's fastest-growing major sector**: GDP growth exceeded 45% since 2021, driven by aging demographics and expanded services
- **The construction collapse**: Despite Norway's housing shortage, construction industry GDP has stagnated since 2021
- **Wage growth has decoupled from hiring**: Oil and finance saw strong wage increases while shedding jobs; health and professional services added workers but saw modest pay rises
- **Service economy dominance**: Combined, health, professional services, IT, and finance now drive more Norwegian GDP growth than all goods-producing industries together
## What this means for Norway
The Norwegian economy is undergoing its most fundamental restructuring since the oil boom began in the 1970s. The country is transitioning from resource extraction to knowledge and care services — but this shift is uneven, creating winners and losers across regions and occupations.
The challenge ahead: can Norway's new growth engines generate the productivity gains and export revenues that oil once provided? Or will this transition leave the country with slower growth and harder fiscal choices? The next five years will tell.