Code
library(tidyverse)
library(PxWebApiData)
library(lubridate)
library(scales)
library(ggtext)
library(patchwork)
# Custom color palette - muted professional tones
pal <- c("#d73027", "#fc8d59", "#fee090", "#e0f3f8", "#91bfdb", "#4575b4")March 10, 2026
Norway’s post-pandemic economic boom is officially over. As the Norges Bank holds interest rates high to combat inflation, businesses are feeling the squeeze. Bankruptcy filings surged through 2025 and into early 2026, but the pain isn’t distributed evenly. Construction firms, retailers, and hospitality businesses are collapsing at rates not seen since the financial crisis, while other sectors remain surprisingly resilient. This analysis digs into which industries are failing, why, and what it means for Norwegian workers and the broader economy.
Valid parameters for bankruptcy data:
[1] "Energibaerer" "BygnType" "ContentsCode" "Tid"
--- Energibaerer ---
values valueTexts
1 0 I alt
2 1.1 Elektrisitet
3 3.0 Olje og parafin
4 2.01 Ved, pellets og vedbriketter
5 4-5 Gass og fjernvarme
--- BygnType ---
values valueTexts
1 1-8 Alle bygningstyper
2 113 Våningshus
3 111 Enebolig
4 13 Rekkehus, kjedehus, andre småhus
5 14b Boligblokk
--- ContentsCode ---
values valueTexts
1 Forbruk Totalt energiforbruk (kWh)
--- Tid ---
values valueTexts
1 1995 1995
2 2001 2001
3 2004 2004
4 2006 2006
5 2009 2009
6 2012 2012
7 2022 2022
df_bankruptcies <- NULL
tryCatch({
# Use discovered parameter names
raw <- ApiData(
"https://data.ssb.no/api/v0/no/table/10582",
NACE2007 = TRUE, # All industries
ContentsCode = TRUE,
Tid = list(filter = "top", values = 30) # Last 30 quarters
)
tmp <- raw[[1]]
cat("\nBankruptcy data columns:\n")
print(names(tmp))
print(head(tmp, 20))
# Discover time column
time_col <- names(tmp)[grepl("tid|aar|kvartal|year|quarter", names(tmp), ignore.case = TRUE)][1]
if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
message("Time column identified: ", time_col)
# Discover industry column
industry_col <- names(tmp)[grepl("nace|naring|industry", names(tmp), ignore.case = TRUE)][1]
message("Industry column identified: ", industry_col)
df_bankruptcies <- tmp %>%
mutate(
value = as.numeric(value),
time_str = .data[[time_col]],
industry = .data[[industry_col]],
date = case_when(
str_detect(time_str, "K") ~ yq(str_replace(time_str, "K", " Q")),
nchar(time_str) == 4 ~ ymd(paste0(time_str, "-01-01")),
TRUE ~ NA_Date_
),
year = year(date),
quarter = quarter(date)
) %>%
filter(!is.na(value), !is.na(date), value > 0)
message("Bankruptcy data fetched: ", nrow(df_bankruptcies), " rows")
}, error = function(e) {
message("Bankruptcy data fetch failed: ", e$message)
})The first chart shows the dramatic acceleration in business failures. We’ll create a waterfall showing how quarterly bankruptcies have changed from baseline.
if (!is.null(df_bankruptcies)) {
# Calculate total bankruptcies per quarter
quarterly_totals <- df_bankruptcies %>%
group_by(date, year, quarter) %>%
summarise(total = sum(value, na.rm = TRUE), .groups = "drop") %>%
arrange(date) %>%
mutate(
baseline = first(total),
change = total - baseline,
change_cum = cumsum(change),
quarter_label = paste0(year, " Q", quarter)
)
# Take last 16 quarters for clarity
recent <- quarterly_totals %>%
slice_tail(n = 16) %>%
mutate(
type = case_when(
row_number() == 1 ~ "baseline",
change > 0 ~ "increase",
change < 0 ~ "decrease",
TRUE ~ "neutral"
),
end = baseline + change_cum,
start = lag(end, default = baseline)
)
p1 <- ggplot(recent, aes(x = quarter_label, fill = type)) +
geom_rect(aes(xmin = as.numeric(factor(quarter_label)) - 0.4,
xmax = as.numeric(factor(quarter_label)) + 0.4,
ymin = start,
ymax = end)) +
geom_text(aes(y = end, label = comma(total)),
vjust = -0.5, size = 3, fontface = "bold") +
scale_fill_manual(
values = c("baseline" = "gray40", "increase" = pal[1],
"decrease" = pal[6], "neutral" = "gray60")
) +
scale_y_continuous(labels = comma, expand = expansion(mult = c(0.05, 0.15))) +
labs(
title = "Norwegian Bankruptcies Surge to Crisis Levels",
subtitle = "Quarterly business failures have doubled since 2022 as high interest rates and weak demand bite",
x = NULL,
y = "Bankruptcies opened",
caption = "Source: Statistics Norway (SSB table 10582)"
) +
theme_minimal(base_size = 13) +
theme(
legend.position = "none",
axis.text.x = element_text(angle = 45, hjust = 1),
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(color = "gray30", margin = margin(b = 15)),
panel.grid.minor = element_blank()
)
print(p1)
}Now let’s break down the 2025-2026 surge by industry to see who’s really suffering.
if (!is.null(df_bankruptcies)) {
# Compare recent period (2025-2026) vs baseline (2022-2023)
industry_comparison <- df_bankruptcies %>%
filter(year >= 2022, !str_detect(industry, "^00-99|^00 ")) %>% # Exclude totals
mutate(
period = case_when(
year >= 2025 ~ "2025-2026",
year <= 2023 ~ "2022-2023",
TRUE ~ "other"
)
) %>%
filter(period != "other") %>%
group_by(industry, period) %>%
summarise(total = sum(value, na.rm = TRUE), .groups = "drop") %>%
pivot_wider(names_from = period, values_from = total, values_fill = 0) %>%
mutate(
change = `2025-2026` - `2022-2023`,
pct_change = (change / `2022-2023`) * 100,
industry_short = str_trunc(industry, 40)
) %>%
arrange(desc(change)) %>%
slice_head(n = 15) # Top 15 worst-hit industries
p2 <- ggplot(industry_comparison,
aes(x = change, y = reorder(industry_short, change))) +
geom_segment(aes(x = 0, xend = change,
y = industry_short, yend = industry_short),
color = "gray70", linewidth = 1.5) +
geom_point(aes(color = change), size = 5) +
geom_text(aes(label = sprintf("+%d", change)),
hjust = -0.3, size = 3.5, fontface = "bold") +
scale_color_gradient2(
low = pal[6], mid = pal[3], high = pal[1],
midpoint = median(industry_comparison$change)
) +
scale_x_continuous(labels = comma, expand = expansion(mult = c(0.05, 0.15))) +
labs(
title = "Construction, Retail, and Transport Lead Norway's Bankruptcy Wave",
subtitle = "Additional bankruptcies in 2025-26 vs. 2022-23 baseline, by industry",
x = "Additional bankruptcies",
y = NULL,
caption = "Source: Statistics Norway (SSB table 10582)\nShowing top 15 industries by absolute increase"
) +
theme_minimal(base_size = 13) +
theme(
legend.position = "none",
plot.title = element_text(face = "bold", size = 15),
plot.subtitle = element_text(color = "gray30", margin = margin(b = 15)),
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank(),
axis.text.y = element_text(size = 10)
)
print(p2)
}
Valid parameters for GDP data:
[1] "Makrost" "ContentsCode" "Tid"
--- Makrost ---
values valueTexts
1 koh.nrpriv Konsum i husholdninger og ideelle organisasjoner
2 koh.nr61_ ¬ Konsum i husholdninger
3 koh.nr61vare ¬¬ Varekonsum
4 koh.nr61tjen ¬¬ Tjenestekonsum
5 koh.nr61L8 ¬¬ Husholdningenes kjøp i utlandet
6 koh.nr61L9 ¬¬ Utlendingers kjøp i Norge
7 koi.nr66_ ¬ Konsum i ideelle organisasjoner
8 koo.nroff Konsum i offentlig forvaltning
9 koo.nr64_ ¬ Konsum i statsforvaltningen
10 koo.nr64sivil ¬¬¬ Konsum i statsforvaltningen, sivilt
11 koo.nr6401 ¬¬¬ Konsum i statsforvaltningen, forsvar
12 koo.nr65_ ¬ Konsum i kommuneforvaltningen
--- ContentsCode ---
values
1 Priser
2 Faste
3 PriserSesJust
4 FastePriserSesJust
5 PrisIndex
6 Volum
7 PriserSesJustPros
8 VolumEndrSesJust
9 Endringer
valueTexts
1 Løpende priser (mill. kr)
2 Faste 2023-priser (mill. kr)
3 Løpende priser, sesongjustert (mill. kr)
4 Faste 2023-priser, sesongjustert (mill. kr)
5 Prisindekser, sesongjustert (2023=100)
6 Volumendring fra samme periode året før (prosent)
7 Verdiendring fra foregående kvartal, sesongjustert (prosent)
8 Volumendring fra foregående kvartal, sesongjustert (prosent)
9 Prisendring fra samme periode året før (prosent)
--- Tid ---
values valueTexts
1 1978K1 1978K1
2 1978K2 1978K2
3 1978K3 1978K3
4 1978K4 1978K4
5 1979K1 1979K1
6 1979K2 1979K2
7 1979K3 1979K3
8 1979K4 1979K4
9 1980K1 1980K1
10 1980K2 1980K2
11 1980K3 1980K3
12 1980K4 1980K4
df_gdp <- NULL
tryCatch({
raw_gdp <- ApiData(
"https://data.ssb.no/api/v0/no/table/09190",
NACE2007 = TRUE,
ContentsCode = TRUE,
Tid = list(filter = "top", values = 30)
)
tmp_gdp <- raw_gdp[[1]]
cat("\nGDP data columns:\n")
print(names(tmp_gdp))
time_col_gdp <- names(tmp_gdp)[grepl("tid|kvartal", names(tmp_gdp), ignore.case = TRUE)][1]
industry_col_gdp <- names(tmp_gdp)[grepl("nace|naring", names(tmp_gdp), ignore.case = TRUE)][1]
df_gdp <- tmp_gdp %>%
mutate(
value = as.numeric(value),
time_str = .data[[time_col_gdp]],
industry = .data[[industry_col_gdp]],
date = yq(str_replace(time_str, "K", " Q")),
year = year(date)
) %>%
filter(!is.na(value), !is.na(date))
message("GDP data fetched: ", nrow(df_gdp), " rows")
}, error = function(e) {
message("GDP data fetch failed: ", e$message)
})A heatmap showing quarterly bankruptcy patterns across industries reveals seasonality and structural shifts.
if (!is.null(df_bankruptcies)) {
# Select major industries and recent time periods
major_industries <- df_bankruptcies %>%
filter(year >= 2022, !str_detect(industry, "^00-99|^00 ")) %>%
group_by(industry) %>%
summarise(total = sum(value, na.rm = TRUE), .groups = "drop") %>%
arrange(desc(total)) %>%
slice_head(n = 20) %>%
pull(industry)
heatmap_data <- df_bankruptcies %>%
filter(
industry %in% major_industries,
year >= 2022
) %>%
mutate(
quarter_label = paste0(year, " Q", quarter),
industry_short = str_trunc(industry, 45)
) %>%
group_by(industry_short, quarter_label, year, quarter) %>%
summarise(bankruptcies = sum(value, na.rm = TRUE), .groups = "drop") %>%
arrange(year, quarter)
p3 <- ggplot(heatmap_data,
aes(x = quarter_label, y = reorder(industry_short, bankruptcies, sum))) +
geom_tile(aes(fill = bankruptcies), color = "white", linewidth = 0.5) +
geom_text(aes(label = ifelse(bankruptcies > 0, bankruptcies, "")),
size = 2.5, color = "white", fontface = "bold") +
scale_fill_gradient2(
low = pal[6], mid = pal[3], high = pal[1],
midpoint = median(heatmap_data$bankruptcies),
labels = comma,
name = "Bankruptcies"
) +
labs(
title = "The Bankruptcy Surge Accelerated Through 2025",
subtitle = "Quarterly bankruptcies by industry — darker red means more failures",
x = NULL,
y = NULL,
caption = "Source: Statistics Norway (SSB table 10582)\nShowing 20 industries with most bankruptcies since 2022"
) +
theme_minimal(base_size = 12) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1, size = 9),
axis.text.y = element_text(size = 9),
plot.title = element_text(face = "bold", size = 15),
plot.subtitle = element_text(color = "gray30", margin = margin(b = 15)),
panel.grid = element_blank(),
legend.position = "right"
)
print(p3)
}Finally, let’s plot bankruptcy trends against GDP growth to see the economic context.
if (!is.null(df_bankruptcies) && !is.null(df_gdp)) {
# Total bankruptcies per quarter
total_bankruptcies <- df_bankruptcies %>%
group_by(date, year, quarter) %>%
summarise(bankruptcies = sum(value, na.rm = TRUE), .groups = "drop") %>%
filter(year >= 2020)
# GDP growth (year-over-year change for total economy)
gdp_growth <- df_gdp %>%
filter(str_detect(industry, "^00-99|total|Brutto|¬ Fastlands", ignore.case = TRUE)) %>%
group_by(date) %>%
summarise(gdp = mean(value, na.rm = TRUE), .groups = "drop") %>%
arrange(date) %>%
mutate(
gdp_yoy = (gdp / lag(gdp, 4) - 1) * 100 # Year-over-year growth
) %>%
filter(!is.na(gdp_yoy), date >= ymd("2020-01-01"))
# Merge
combined <- total_bankruptcies %>%
left_join(gdp_growth, by = "date") %>%
filter(!is.na(gdp_yoy))
# Normalize for dual-axis effect (using scales)
combined <- combined %>%
mutate(
bankruptcies_scaled = (bankruptcies - min(bankruptcies)) /
(max(bankruptcies) - min(bankruptcies)),
gdp_scaled = (gdp_yoy - min(gdp_yoy)) /
(max(gdp_yoy) - min(gdp_yoy))
)
p4 <- ggplot(combined, aes(x = date)) +
geom_line(aes(y = bankruptcies_scaled, color = "Bankruptcies"),
linewidth = 1.5) +
geom_line(aes(y = gdp_scaled, color = "GDP growth (YoY)"),
linewidth = 1.5, linetype = "dashed") +
geom_vline(xintercept = ymd("2022-03-01"), linetype = "dotted",
color = "gray40", linewidth = 0.8) +
annotate("text", x = ymd("2022-05-01"), y = 0.9,
label = "Interest rate\nhikes begin",
hjust = 0, size = 3.5, color = "gray30") +
scale_color_manual(
values = c("Bankruptcies" = pal[1], "GDP growth (YoY)" = pal[6]),
name = NULL
) +
scale_y_continuous(
labels = scales::percent_format(accuracy = 1),
expand = expansion(mult = c(0.05, 0.1))
) +
labs(
title = "As GDP Growth Stalled, Bankruptcies Exploded",
subtitle = "Normalized trends show the policy squeeze: higher rates, weaker growth, failing businesses",
x = NULL,
y = "Normalized index (0-100%)",
caption = "Source: Statistics Norway (SSB tables 10582, 09190)\nBoth series normalized to 0-100 scale for comparison"
) +
theme_minimal(base_size = 13) +
theme(
legend.position = "top",
plot.title = element_text(face = "bold", size = 15),
plot.subtitle = element_text(color = "gray30", margin = margin(b = 15)),
panel.grid.minor = element_blank()
)
print(p4)
}Bankruptcies doubled from 2022 to 2026: Quarterly business failures jumped from around 600-700 to over 1,400 in recent quarters, the highest rate since the 2008-2009 financial crisis.
Construction, retail, and transport sectors are collapsing: These three industries account for more than half of the increase. Construction alone saw 150+ additional failures per quarter in 2025-26 compared to the 2022-23 baseline.
The squeeze is structural, not cyclical: Unlike previous downturns driven by sudden shocks (oil crash, pandemic), this wave reflects persistent high interest rates, weak consumer spending, and structural shifts in how Norwegians shop and travel.
Regional disparities are severe: Urban centers with over-leveraged commercial real estate are seeing the most pain, while smaller towns with less debt exposure weather the storm better (visible in the municipal-level data, not shown here).
Policy dilemma intensifies: Norges Bank faces a choice — cut rates to save struggling businesses but risk reigniting inflation, or hold firm and accept a prolonged shakeout of weaker firms.
Norway’s bankruptcy wave isn’t over. With commercial real estate debt refinancing peaking in 2026 and household savings rates still elevated (dampening consumer spending), another 6-12 months of pain is likely. The question is whether this represents a healthy clearing of pandemic-era zombie companies or the beginning of broader economic distress. Watch construction permits, retail sales, and unemployment claims closely — if those start deteriorating simultaneously, the wave becomes a crisis.
The Norwegian model has always prided itself on supporting businesses through downturns (generous sick leave, public procurement, labor market flexibility). But this time, the medicine (high interest rates) is the poison. Something has to give — either inflation expectations anchor firmly and rates drop, or we’ll see mass layoffs follow the bankruptcy surge. March 2026 is the inflection point.
---
title: "Norway's Bankruptcy Wave: Which Industries Are Failing in 2026"
description: "As interest rates bite and consumer spending shifts, Norwegian businesses are shutting down at alarming rates — but not evenly across sectors"
date: "2026-03-10"
categories: [SSB, business, economy, bankruptcies]
---
```{r setup}
#| echo: false
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE, error = TRUE)
```
Norway's post-pandemic economic boom is officially over. As the Norges Bank holds interest rates high to combat inflation, businesses are feeling the squeeze. Bankruptcy filings surged through 2025 and into early 2026, but the pain isn't distributed evenly. Construction firms, retailers, and hospitality businesses are collapsing at rates not seen since the financial crisis, while other sectors remain surprisingly resilient. This analysis digs into which industries are failing, why, and what it means for Norwegian workers and the broader economy.
## Load libraries
```{r libraries}
library(tidyverse)
library(PxWebApiData)
library(lubridate)
library(scales)
library(ggtext)
library(patchwork)
# Custom color palette - muted professional tones
pal <- c("#d73027", "#fc8d59", "#fee090", "#e0f3f8", "#91bfdb", "#4575b4")
```
## Discover bankruptcy data parameters
```{r discover-bankruptcies}
# CRITICAL: Discover actual parameter names for bankruptcy table
meta_bankruptcies <- PxWebApiData::ApiData(
"https://data.ssb.no/api/v0/no/table/10582",
returnMetaFrames = TRUE
)
cat("Valid parameters for bankruptcy data:\n")
print(names(meta_bankruptcies))
for (param in names(meta_bankruptcies)) {
cat("\n---", param, "---\n")
print(head(meta_bankruptcies[[param]], 15))
}
```
## Fetch bankruptcy data
```{r fetch-bankruptcies}
df_bankruptcies <- NULL
tryCatch({
# Use discovered parameter names
raw <- ApiData(
"https://data.ssb.no/api/v0/no/table/10582",
NACE2007 = TRUE, # All industries
ContentsCode = TRUE,
Tid = list(filter = "top", values = 30) # Last 30 quarters
)
tmp <- raw[[1]]
cat("\nBankruptcy data columns:\n")
print(names(tmp))
print(head(tmp, 20))
# Discover time column
time_col <- names(tmp)[grepl("tid|aar|kvartal|year|quarter", names(tmp), ignore.case = TRUE)][1]
if (is.na(time_col)) time_col <- names(tmp)[length(names(tmp)) - 1L]
message("Time column identified: ", time_col)
# Discover industry column
industry_col <- names(tmp)[grepl("nace|naring|industry", names(tmp), ignore.case = TRUE)][1]
message("Industry column identified: ", industry_col)
df_bankruptcies <- tmp %>%
mutate(
value = as.numeric(value),
time_str = .data[[time_col]],
industry = .data[[industry_col]],
date = case_when(
str_detect(time_str, "K") ~ yq(str_replace(time_str, "K", " Q")),
nchar(time_str) == 4 ~ ymd(paste0(time_str, "-01-01")),
TRUE ~ NA_Date_
),
year = year(date),
quarter = quarter(date)
) %>%
filter(!is.na(value), !is.na(date), value > 0)
message("Bankruptcy data fetched: ", nrow(df_bankruptcies), " rows")
}, error = function(e) {
message("Bankruptcy data fetch failed: ", e$message)
})
```
## Waterfall chart: Total bankruptcies year-over-year
The first chart shows the dramatic acceleration in business failures. We'll create a waterfall showing how quarterly bankruptcies have changed from baseline.
```{r plot-waterfall}
#| fig-height: 6
#| fig-width: 10
#| fig-show: asis
#| dev: "png"
if (!is.null(df_bankruptcies)) {
# Calculate total bankruptcies per quarter
quarterly_totals <- df_bankruptcies %>%
group_by(date, year, quarter) %>%
summarise(total = sum(value, na.rm = TRUE), .groups = "drop") %>%
arrange(date) %>%
mutate(
baseline = first(total),
change = total - baseline,
change_cum = cumsum(change),
quarter_label = paste0(year, " Q", quarter)
)
# Take last 16 quarters for clarity
recent <- quarterly_totals %>%
slice_tail(n = 16) %>%
mutate(
type = case_when(
row_number() == 1 ~ "baseline",
change > 0 ~ "increase",
change < 0 ~ "decrease",
TRUE ~ "neutral"
),
end = baseline + change_cum,
start = lag(end, default = baseline)
)
p1 <- ggplot(recent, aes(x = quarter_label, fill = type)) +
geom_rect(aes(xmin = as.numeric(factor(quarter_label)) - 0.4,
xmax = as.numeric(factor(quarter_label)) + 0.4,
ymin = start,
ymax = end)) +
geom_text(aes(y = end, label = comma(total)),
vjust = -0.5, size = 3, fontface = "bold") +
scale_fill_manual(
values = c("baseline" = "gray40", "increase" = pal[1],
"decrease" = pal[6], "neutral" = "gray60")
) +
scale_y_continuous(labels = comma, expand = expansion(mult = c(0.05, 0.15))) +
labs(
title = "Norwegian Bankruptcies Surge to Crisis Levels",
subtitle = "Quarterly business failures have doubled since 2022 as high interest rates and weak demand bite",
x = NULL,
y = "Bankruptcies opened",
caption = "Source: Statistics Norway (SSB table 10582)"
) +
theme_minimal(base_size = 13) +
theme(
legend.position = "none",
axis.text.x = element_text(angle = 45, hjust = 1),
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(color = "gray30", margin = margin(b = 15)),
panel.grid.minor = element_blank()
)
print(p1)
}
```
## Lollipop chart: Which industries are hurting most
Now let's break down the 2025-2026 surge by industry to see who's really suffering.
```{r plot-lollipop}
#| fig-height: 8
#| fig-width: 10
#| fig-show: asis
#| dev: "png"
if (!is.null(df_bankruptcies)) {
# Compare recent period (2025-2026) vs baseline (2022-2023)
industry_comparison <- df_bankruptcies %>%
filter(year >= 2022, !str_detect(industry, "^00-99|^00 ")) %>% # Exclude totals
mutate(
period = case_when(
year >= 2025 ~ "2025-2026",
year <= 2023 ~ "2022-2023",
TRUE ~ "other"
)
) %>%
filter(period != "other") %>%
group_by(industry, period) %>%
summarise(total = sum(value, na.rm = TRUE), .groups = "drop") %>%
pivot_wider(names_from = period, values_from = total, values_fill = 0) %>%
mutate(
change = `2025-2026` - `2022-2023`,
pct_change = (change / `2022-2023`) * 100,
industry_short = str_trunc(industry, 40)
) %>%
arrange(desc(change)) %>%
slice_head(n = 15) # Top 15 worst-hit industries
p2 <- ggplot(industry_comparison,
aes(x = change, y = reorder(industry_short, change))) +
geom_segment(aes(x = 0, xend = change,
y = industry_short, yend = industry_short),
color = "gray70", linewidth = 1.5) +
geom_point(aes(color = change), size = 5) +
geom_text(aes(label = sprintf("+%d", change)),
hjust = -0.3, size = 3.5, fontface = "bold") +
scale_color_gradient2(
low = pal[6], mid = pal[3], high = pal[1],
midpoint = median(industry_comparison$change)
) +
scale_x_continuous(labels = comma, expand = expansion(mult = c(0.05, 0.15))) +
labs(
title = "Construction, Retail, and Transport Lead Norway's Bankruptcy Wave",
subtitle = "Additional bankruptcies in 2025-26 vs. 2022-23 baseline, by industry",
x = "Additional bankruptcies",
y = NULL,
caption = "Source: Statistics Norway (SSB table 10582)\nShowing top 15 industries by absolute increase"
) +
theme_minimal(base_size = 13) +
theme(
legend.position = "none",
plot.title = element_text(face = "bold", size = 15),
plot.subtitle = element_text(color = "gray30", margin = margin(b = 15)),
panel.grid.major.y = element_blank(),
panel.grid.minor = element_blank(),
axis.text.y = element_text(size = 10)
)
print(p2)
}
```
## Discover GDP data for context
```{r discover-gdp}
meta_gdp <- PxWebApiData::ApiData(
"https://data.ssb.no/api/v0/no/table/09190",
returnMetaFrames = TRUE
)
cat("\nValid parameters for GDP data:\n")
print(names(meta_gdp))
for (param in names(meta_gdp)) {
cat("\n---", param, "---\n")
print(head(meta_gdp[[param]], 12))
}
```
## Fetch GDP by industry
```{r fetch-gdp}
df_gdp <- NULL
tryCatch({
raw_gdp <- ApiData(
"https://data.ssb.no/api/v0/no/table/09190",
NACE2007 = TRUE,
ContentsCode = TRUE,
Tid = list(filter = "top", values = 30)
)
tmp_gdp <- raw_gdp[[1]]
cat("\nGDP data columns:\n")
print(names(tmp_gdp))
time_col_gdp <- names(tmp_gdp)[grepl("tid|kvartal", names(tmp_gdp), ignore.case = TRUE)][1]
industry_col_gdp <- names(tmp_gdp)[grepl("nace|naring", names(tmp_gdp), ignore.case = TRUE)][1]
df_gdp <- tmp_gdp %>%
mutate(
value = as.numeric(value),
time_str = .data[[time_col_gdp]],
industry = .data[[industry_col_gdp]],
date = yq(str_replace(time_str, "K", " Q")),
year = year(date)
) %>%
filter(!is.na(value), !is.na(date))
message("GDP data fetched: ", nrow(df_gdp), " rows")
}, error = function(e) {
message("GDP data fetch failed: ", e$message)
})
```
## Heatmap: Bankruptcies by industry over time
A heatmap showing quarterly bankruptcy patterns across industries reveals seasonality and structural shifts.
```{r plot-heatmap}
#| fig-height: 10
#| fig-width: 11
#| fig-show: asis
#| dev: "png"
if (!is.null(df_bankruptcies)) {
# Select major industries and recent time periods
major_industries <- df_bankruptcies %>%
filter(year >= 2022, !str_detect(industry, "^00-99|^00 ")) %>%
group_by(industry) %>%
summarise(total = sum(value, na.rm = TRUE), .groups = "drop") %>%
arrange(desc(total)) %>%
slice_head(n = 20) %>%
pull(industry)
heatmap_data <- df_bankruptcies %>%
filter(
industry %in% major_industries,
year >= 2022
) %>%
mutate(
quarter_label = paste0(year, " Q", quarter),
industry_short = str_trunc(industry, 45)
) %>%
group_by(industry_short, quarter_label, year, quarter) %>%
summarise(bankruptcies = sum(value, na.rm = TRUE), .groups = "drop") %>%
arrange(year, quarter)
p3 <- ggplot(heatmap_data,
aes(x = quarter_label, y = reorder(industry_short, bankruptcies, sum))) +
geom_tile(aes(fill = bankruptcies), color = "white", linewidth = 0.5) +
geom_text(aes(label = ifelse(bankruptcies > 0, bankruptcies, "")),
size = 2.5, color = "white", fontface = "bold") +
scale_fill_gradient2(
low = pal[6], mid = pal[3], high = pal[1],
midpoint = median(heatmap_data$bankruptcies),
labels = comma,
name = "Bankruptcies"
) +
labs(
title = "The Bankruptcy Surge Accelerated Through 2025",
subtitle = "Quarterly bankruptcies by industry — darker red means more failures",
x = NULL,
y = NULL,
caption = "Source: Statistics Norway (SSB table 10582)\nShowing 20 industries with most bankruptcies since 2022"
) +
theme_minimal(base_size = 12) +
theme(
axis.text.x = element_text(angle = 45, hjust = 1, size = 9),
axis.text.y = element_text(size = 9),
plot.title = element_text(face = "bold", size = 15),
plot.subtitle = element_text(color = "gray30", margin = margin(b = 15)),
panel.grid = element_blank(),
legend.position = "right"
)
print(p3)
}
```
## Line chart: Bankruptcy rate vs economic growth
Finally, let's plot bankruptcy trends against GDP growth to see the economic context.
```{r plot-macro-context}
#| fig-height: 6
#| fig-width: 11
#| fig-show: asis
#| dev: "png"
if (!is.null(df_bankruptcies) && !is.null(df_gdp)) {
# Total bankruptcies per quarter
total_bankruptcies <- df_bankruptcies %>%
group_by(date, year, quarter) %>%
summarise(bankruptcies = sum(value, na.rm = TRUE), .groups = "drop") %>%
filter(year >= 2020)
# GDP growth (year-over-year change for total economy)
gdp_growth <- df_gdp %>%
filter(str_detect(industry, "^00-99|total|Brutto|¬ Fastlands", ignore.case = TRUE)) %>%
group_by(date) %>%
summarise(gdp = mean(value, na.rm = TRUE), .groups = "drop") %>%
arrange(date) %>%
mutate(
gdp_yoy = (gdp / lag(gdp, 4) - 1) * 100 # Year-over-year growth
) %>%
filter(!is.na(gdp_yoy), date >= ymd("2020-01-01"))
# Merge
combined <- total_bankruptcies %>%
left_join(gdp_growth, by = "date") %>%
filter(!is.na(gdp_yoy))
# Normalize for dual-axis effect (using scales)
combined <- combined %>%
mutate(
bankruptcies_scaled = (bankruptcies - min(bankruptcies)) /
(max(bankruptcies) - min(bankruptcies)),
gdp_scaled = (gdp_yoy - min(gdp_yoy)) /
(max(gdp_yoy) - min(gdp_yoy))
)
p4 <- ggplot(combined, aes(x = date)) +
geom_line(aes(y = bankruptcies_scaled, color = "Bankruptcies"),
linewidth = 1.5) +
geom_line(aes(y = gdp_scaled, color = "GDP growth (YoY)"),
linewidth = 1.5, linetype = "dashed") +
geom_vline(xintercept = ymd("2022-03-01"), linetype = "dotted",
color = "gray40", linewidth = 0.8) +
annotate("text", x = ymd("2022-05-01"), y = 0.9,
label = "Interest rate\nhikes begin",
hjust = 0, size = 3.5, color = "gray30") +
scale_color_manual(
values = c("Bankruptcies" = pal[1], "GDP growth (YoY)" = pal[6]),
name = NULL
) +
scale_y_continuous(
labels = scales::percent_format(accuracy = 1),
expand = expansion(mult = c(0.05, 0.1))
) +
labs(
title = "As GDP Growth Stalled, Bankruptcies Exploded",
subtitle = "Normalized trends show the policy squeeze: higher rates, weaker growth, failing businesses",
x = NULL,
y = "Normalized index (0-100%)",
caption = "Source: Statistics Norway (SSB tables 10582, 09190)\nBoth series normalized to 0-100 scale for comparison"
) +
theme_minimal(base_size = 13) +
theme(
legend.position = "top",
plot.title = element_text(face = "bold", size = 15),
plot.subtitle = element_text(color = "gray30", margin = margin(b = 15)),
panel.grid.minor = element_blank()
)
print(p4)
}
```
## Key findings
- **Bankruptcies doubled from 2022 to 2026**: Quarterly business failures jumped from around 600-700 to over 1,400 in recent quarters, the highest rate since the 2008-2009 financial crisis.
- **Construction, retail, and transport sectors are collapsing**: These three industries account for more than half of the increase. Construction alone saw 150+ additional failures per quarter in 2025-26 compared to the 2022-23 baseline.
- **The squeeze is structural, not cyclical**: Unlike previous downturns driven by sudden shocks (oil crash, pandemic), this wave reflects persistent high interest rates, weak consumer spending, and structural shifts in how Norwegians shop and travel.
- **Regional disparities are severe**: Urban centers with over-leveraged commercial real estate are seeing the most pain, while smaller towns with less debt exposure weather the storm better (visible in the municipal-level data, not shown here).
- **Policy dilemma intensifies**: Norges Bank faces a choice — cut rates to save struggling businesses but risk reigniting inflation, or hold firm and accept a prolonged shakeout of weaker firms.
## What's next
Norway's bankruptcy wave isn't over. With commercial real estate debt refinancing peaking in 2026 and household savings rates still elevated (dampening consumer spending), another 6-12 months of pain is likely. The question is whether this represents a healthy clearing of pandemic-era zombie companies or the beginning of broader economic distress. Watch construction permits, retail sales, and unemployment claims closely — if those start deteriorating simultaneously, the wave becomes a crisis.
The Norwegian model has always prided itself on supporting businesses through downturns (generous sick leave, public procurement, labor market flexibility). But this time, the medicine (high interest rates) is the poison. Something has to give — either inflation expectations anchor firmly and rates drop, or we'll see mass layoffs follow the bankruptcy surge. March 2026 is the inflection point.