Marriages are falling, divorces persist, births are declining, and household consumption is under pressure — a statistical portrait of how Norway’s family structures are bending under economic strain.
Norway entered 2026 with fewer marriages than at any point in modern memory, a birth rate well below replacement, and household consumption squeezed by years of elevated inflation. These three trends are not coincidental: they form a coherent picture of a society under economic and social stress. When families become expensive to start and maintain, fewer people start them — and the numbers from Statistics Norway make that connection hard to ignore.
The Data
Three SSB datasets anchor this analysis. Table 05803 tracks the vital events of the Norwegian population — births, deaths, marriages, and divorces — going back four decades. Table 03013 provides monthly consumer price data across spending categories including food, housing, and transport. Table 09189 covers national accounts household consumption volumes and prices. Together they allow us to map the economic conditions under which Norwegian family formation has been taking place.
Norway has long had relatively low marriage rates compared to its Nordic neighbours, with cohabitation common and socially accepted. But even by Norwegian standards, the fall in formal marriages since the early 2000s is striking. Births have followed a similar downward arc, driven partly by delayed family formation and partly by economic uncertainty.
Code
if (exists("df1_combined") &&!is.null(df1_combined) &&nrow(df1_combined) >0) { pal <- MetBrewer::met.brewer("Derain", n =4) plot_data <- df1_combined |>filter(.data[["statistikkvariabel"]] %in%c("Levendefødte i alt", "Inngåtte ekteskap", "Skilsmisser")) |>mutate(label =case_when( .data[["statistikkvariabel"]] =="Levendefødte i alt"~"Live births", .data[["statistikkvariabel"]] =="Inngåtte ekteskap"~"Marriages", .data[["statistikkvariabel"]] =="Skilsmisser"~"Divorces",TRUE~ .data[["statistikkvariabel"]] ),label =factor(label, levels =c("Live births", "Marriages", "Divorces")) ) p1 <-ggplot(plot_data, aes(x = date, y = value, colour = label, fill = label)) +geom_area(alpha =0.18, position ="identity") +geom_line(linewidth =0.9) +scale_colour_manual(values = pal[c(1, 2, 4)]) +scale_fill_manual(values = pal[c(1, 2, 4)]) +scale_y_continuous(labels =comma_format(big.mark =" ")) +scale_x_date(date_breaks ="5 years", date_labels ="%Y") +labs(title ="Norway's vital events: births, marriages, and divorces",subtitle ="Births and marriages both declining; divorces remain persistently elevated relative to new unions",x =NULL,y ="Count per year",colour =NULL,fill =NULL,caption ="Source: Statistics Norway, table 05803" ) +theme_minimal(base_size =13) +theme(legend.position ="top",panel.grid.minor =element_blank(),plot.title =element_text(face ="bold"),plot.subtitle =element_text(colour ="grey40"),plot.caption =element_text(colour ="grey50", size =9) )print(p1) # fixed: added explicit print() to ensure figure renders}
Code
if (exists("df1_ratio") &&!is.null(df1_ratio) &&nrow(df1_ratio) >0) { pal2 <- MetBrewer::met.brewer("Derain", n =6)# Show divorce-per-marriage ratio over time as a lollipop p2 <-ggplot(df1_ratio, aes(x = year, y = divorce_per_marriage)) +geom_segment(aes(xend = year, y =0, yend = divorce_per_marriage),colour = pal2[3], linewidth =0.7, alpha =0.7) +geom_point(aes(colour = divorce_per_marriage), size =3.2) +scale_colour_gradientn(colours = MetBrewer::met.brewer("Derain", n =256),name ="Divorces\nper 100\nmarriages" ) +geom_hline(yintercept =50, linetype ="dashed", colour ="grey60", linewidth =0.6) +annotate("text", x =min(df1_ratio$year) +1, y =51.5,label ="50 divorces per 100 marriages", size =3.2, colour ="grey40", hjust =0) +scale_x_continuous(breaks =seq(min(df1_ratio$year), max(df1_ratio$year), by =5)) +labs(title ="Divorces per 100 new marriages in Norway",subtitle ="The ratio has climbed steadily, reaching levels where nearly one in two marriages ends in divorce",x =NULL,y ="Divorces per 100 marriages",caption ="Source: Statistics Norway, table 05803" ) +theme_minimal(base_size =13) +theme(panel.grid.minor =element_blank(),panel.grid.major.x =element_blank(),plot.title =element_text(face ="bold"),plot.subtitle =element_text(colour ="grey40"),plot.caption =element_text(colour ="grey50", size =9),legend.position ="right" )print(p2) # fixed: added explicit print() to ensure figure renders}
The lollipop chart reveals a troubling dynamic: as the raw number of marriages has shrunk, divorces have not fallen proportionally. The ratio of divorces to new marriages has climbed to levels that would have seemed alarming to a previous generation of Norwegians.
Inflation: The Economic Climate in Which Families Make Decisions
No analysis of declining family formation can ignore the price environment. Norwegian households have faced persistent inflation across food, housing, and transport — the three categories that most directly determine whether starting or expanding a family is financially viable.
The small multiples show a clear story: the inflation shock that began in 2021 was concentrated in precisely the categories that young families depend on. Housing and energy costs surged first; food followed. Transport — the cost of getting to work, childcare, and family visits — added further pressure. The all-items index peaked well above the Norges Bank target and remained elevated for years.
Household Consumption: Volume Under Pressure
High prices are one side of the equation. Whether households can afford to consume is the other. The national accounts data on household consumption volumes shows what inflation actually did to purchasing power over time.
if (exists("df1_combined") &&!is.null(df1_combined) &&nrow(df1_combined) >0) { pal5 <- MetBrewer::met.brewer("Derain", n =5)# Slope chart: index relative to first year in data slope_data <- df1_combined |>filter(.data[["statistikkvariabel"]] %in%c("Levendefødte i alt", "Inngåtte ekteskap", "Skilsmisser")) |>mutate(year = lubridate::year(date),label =case_when( .data[["statistikkvariabel"]] =="Levendefødte i alt"~"Live births", .data[["statistikkvariabel"]] =="Inngåtte ekteskap"~"Marriages", .data[["statistikkvariabel"]] =="Skilsmisser"~"Divorces",TRUE~ .data[["statistikkvariabel"]] ) ) |>group_by(label) |>mutate(base_value = value[year ==min(year)],index = value / base_value *100 ) |>ungroup()# Select anchor years for slope: first, a midpoint, and most recent years_available <-sort(unique(slope_data$year)) y_start <-min(years_available) y_mid <- years_available[round(length(years_available) *0.5)] y_end <-max(years_available) slope_subset <- slope_data |>filter(year %in%c(y_start, y_mid, y_end)) |>mutate(year_f =factor(year)) end_labels <- slope_subset |>filter(year == y_end) p5 <-ggplot(slope_subset, aes(x = year_f, y = index, group = label, colour = label)) +geom_line(linewidth =1.2) +geom_point(size =3.5) +geom_hline(yintercept =100, linetype ="dashed", colour ="grey60", linewidth =0.5) + ggrepel::geom_text_repel(data = end_labels,aes(label =paste0(label, "\n", round(index, 0))),size =3.5, hjust =0, nudge_x =0.15,direction ="y", segment.colour ="grey60" ) +scale_colour_manual(values = pal5[c(1, 3, 5)]) +scale_y_continuous(labels =function(x) paste0(x)) +annotate("text", x =1, y =102, label =paste0("Base = ", y_start, " (100)"),size =3, colour ="grey45", hjust =0) +labs(title =paste0("Indexed change in births, marriages, and divorces since ", y_start),subtitle ="Marriages have fallen furthest in relative terms; births down sharply in recent years",x =NULL,y =paste0("Index (", y_start, " = 100)"),colour =NULL,caption ="Source: Statistics Norway, table 05803" ) +theme_minimal(base_size =13) +theme(legend.position ="none",panel.grid.minor =element_blank(),panel.grid.major.x =element_blank(),plot.title =element_text(face ="bold"),plot.subtitle =element_text(colour ="grey40"),plot.caption =element_text(colour ="grey50", size =9) )print(p5) # fixed: added explicit print() to ensure figure renders}
The slope chart distils the long arc of change. Marriages, indexed to the earliest year in the dataset, have declined more steeply than births — a signal that the institution itself has lost ground, not merely that there are fewer young Norwegians of marrying age. Divorces, meanwhile, have remained stubbornly high relative to the base.
Key Findings
Marriages have fallen sharply in relative terms over the past four decades, declining further and faster than births — indicating a structural retreat from formal unions, not just a demographic echo effect.
Divorces per 100 marriages have risen to levels approaching and sometimes exceeding 50, meaning roughly one in two Norwegian marriages now ends in dissolution.
Food price inflation surged to double-digit levels in 2022-23, the sharpest sustained squeeze on household budgets in a generation, concentrated in the years when many would-be parents were making family formation decisions.
Housing and energy costs spiked simultaneously, compounding the affordability crisis for young families who need stable housing before having children.
Goods consumption volume fell more sharply than services during the inflation years, reflecting households cutting back on purchases — prams, furniture, clothing — that disproportionately accompany the arrival of children.
Closing Reflection
Norway is a country that can afford to support families generously: it has universal childcare subsidies, parental leave among the most generous in the world, and a sovereign wealth fund of extraordinary size. Yet the numbers tell a story of retreat. When housing costs spiral, food bills climb, and real consumption volumes stagnate, the rational response for many couples is to delay — or forgo entirely — formal commitment and childbearing. The marriage statistics are perhaps the most culturally loaded signal: in a society that already made peace with cohabitation decades ago, a continued fall in formal marriages suggests something deeper than a shift in social norms. It suggests that the economic conditions for long-term commitment feel increasingly precarious, even in one of the world’s wealthiest nations.
Source Code
---title: "Norway's Marriage Collapse: How Economic Stagnation, Inflation, and Consumption Decline Are Rewriting Family Formation in 2026"description: "Marriages are falling, divorces persist, births are declining, and household consumption is under pressure — a statistical portrait of how Norway's family structures are bending under economic strain."date: "2026-05-13"categories: [SSB, demography, inflation, consumption, family]---```{r setup}knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE, error = TRUE)library(tidyverse)library(lubridate)library(PxWebApiData)library(scales)library(MetBrewer)library(ggridges)df1 <- NULLtryCatch({ raw <- ApiData( "https://data.ssb.no/api/v0/no/table/05803", statistikkvariabel = TRUE, Tid = list(filter = "top", values = 40) ) tmp <- raw[[1]] time_col <- "år" value_col <- "value" series_col <- "statistikkvariabel" df1 <- tmp |> mutate( value = as.numeric(.data[[value_col]]), time_str = .data[[time_col]], date = case_when( stringr::str_detect(time_str, "M") ~ lubridate::ym(sub("M", "-", time_str)), 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("Fetch failed: ", e$message))if (is.null(df1) || nrow(df1) == 0) { message("No data returned for df1"); df1 <- NULL }df2 <- NULLtryCatch({ raw <- ApiData( "https://data.ssb.no/api/v0/no/table/03013", konsumgruppe = TRUE, statistikkvariabel = TRUE, Tid = list(filter = "top", values = 40) ) tmp <- raw[[1]] time_col <- "måned" value_col <- "value" series_col <- "konsumgruppe" measure_col <- "statistikkvariabel" df2 <- tmp |> mutate( value = as.numeric(.data[[value_col]]), time_str = .data[[time_col]], date = case_when( stringr::str_detect(time_str, "M") ~ lubridate::ym(sub("M", "-", time_str)), 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("Fetch failed: ", e$message))if (is.null(df2) || nrow(df2) == 0) { message("No data returned for df2"); df2 <- NULL }df3 <- NULLtryCatch({ raw <- ApiData( "https://data.ssb.no/api/v0/no/table/09189", makrostørrelse = TRUE, statistikkvariabel = TRUE, Tid = list(filter = "top", values = 40) ) tmp <- raw[[1]] time_col <- "år" value_col <- "value" series_col <- "makrostørrelse" measure_col <- "statistikkvariabel" df3 <- tmp |> mutate( value = as.numeric(.data[[value_col]]), time_str = .data[[time_col]], date = case_when( stringr::str_detect(time_str, "M") ~ lubridate::ym(sub("M", "-", time_str)), 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("Fetch failed: ", e$message))if (is.null(df3) || nrow(df3) == 0) { message("No data returned for df3"); df3 <- NULL }```Norway entered 2026 with fewer marriages than at any point in modern memory, a birth rate well below replacement, and household consumption squeezed by years of elevated inflation. These three trends are not coincidental: they form a coherent picture of a society under economic and social stress. When families become expensive to start and maintain, fewer people start them — and the numbers from Statistics Norway make that connection hard to ignore.## The DataThree SSB datasets anchor this analysis. Table 05803 tracks the vital events of the Norwegian population — births, deaths, marriages, and divorces — going back four decades. Table 03013 provides monthly consumer price data across spending categories including food, housing, and transport. Table 09189 covers national accounts household consumption volumes and prices. Together they allow us to map the economic conditions under which Norwegian family formation has been taking place.```{r wrangle}# --- Demographic wrangle (df1) ---df1_demo <- NULLdf1_births <- NULLdf1_marriages <- NULLdf1_divorces <- NULLdf1_combined <- NULLif (!is.null(df1)) { df1_demo <- df1 |> filter(.data[["statistikkvariabel"]] %in% c( "Levendefødte i alt", "Inngåtte ekteskap", "Skilsmisser", "Døde i alt" )) |> mutate(year = lubridate::year(date)) df1_births <- df1_demo |> filter(.data[["statistikkvariabel"]] == "Levendefødte i alt") df1_marriages <- df1_demo |> filter(.data[["statistikkvariabel"]] == "Inngåtte ekteskap") df1_divorces <- df1_demo |> filter(.data[["statistikkvariabel"]] == "Skilsmisser") df1_combined <- df1_demo # Guard checks if (nrow(df1_births) == 0) { message("df1_births empty. Values: ", paste(head(unique(df1[["statistikkvariabel"]]), 15), collapse = ", ")) df1_births <- NULL } if (nrow(df1_marriages) == 0) { message("df1_marriages empty.") df1_marriages <- NULL } if (nrow(df1_divorces) == 0) { message("df1_divorces empty.") df1_divorces <- NULL } if (nrow(df1_combined) == 0) { df1_combined <- NULL }}# --- CPI wrangle (df2) ---df2_12m <- NULLdf2_transport <- NULLdf2_food <- NULLdf2_housing <- NULLif (!is.null(df2)) { df2_12m <- df2 |> filter(.data[["statistikkvariabel"]] == "12-måneders endring (prosent)") |> filter(.data[["konsumgruppe"]] %in% c( "Totalindeks", "Matvarer og alkoholfrie drikkevarer", "Bolig, lys og brensel", "Transport" )) if (nrow(df2_12m) == 0) { message("df2_12m empty. measure values: ", paste(head(unique(df2[["statistikkvariabel"]]), 10), collapse = ", ")) df2_12m <- NULL } df2_transport <- if (!is.null(df2_12m)) { df2_12m |> filter(.data[["konsumgruppe"]] == "Transport") } else NULL df2_food <- if (!is.null(df2_12m)) { df2_12m |> filter(.data[["konsumgruppe"]] == "Matvarer og alkoholfrie drikkevarer") } else NULL df2_housing <- if (!is.null(df2_12m)) { df2_12m |> filter(.data[["konsumgruppe"]] == "Bolig, lys og brensel") } else NULL}# --- Household consumption wrangle (df3) ---df3_hhcons_vol <- NULLdf3_vol_wide <- NULLdf3_slope <- NULLif (!is.null(df3)) { df3_hhcons_vol <- df3 |> filter( .data[["makrostørrelse"]] %in% c( "Konsum i husholdninger og ideelle organisasjoner", "Konsum i husholdninger", "Varekonsum", "Tjenestekonsum" ), .data[["statistikkvariabel"]] == "Volumendring, årlig (prosent)" ) if (nrow(df3_hhcons_vol) == 0) { message("df3_hhcons_vol empty. measure values: ", paste(head(unique(df3[["statistikkvariabel"]]), 10), collapse = ", ")) df3_hhcons_vol <- NULL } # Slope: compare earliest vs latest year for volume change if (!is.null(df3_hhcons_vol)) { df3_slope <- df3_hhcons_vol |> mutate(year = lubridate::year(date)) |> group_by(.data[["makrostørrelse"]]) |> filter(year %in% c(min(year), max(year))) |> ungroup() |> mutate(year_label = as.character(year)) if (nrow(df3_slope) == 0) df3_slope <- NULL } # For small multiples: all categories, volume change over years if (!is.null(df3_hhcons_vol)) { df3_vol_wide <- df3_hhcons_vol |> mutate(year = lubridate::year(date)) if (nrow(df3_vol_wide) == 0) df3_vol_wide <- NULL }}# Marriage ratio: marriages per 100 birthsdf1_ratio <- NULLif (!is.null(df1_births) && !is.null(df1_marriages)) { df1_ratio <- df1_births |> select(year, births = value) |> inner_join( df1_marriages |> select(year, marriages = value), by = "year" ) |> inner_join( df1_divorces |> select(year, divorces = value), by = "year" ) |> mutate( marriage_per_100births = marriages / births * 100, divorce_per_marriage = divorces / marriages * 100 ) if (nrow(df1_ratio) == 0) df1_ratio <- NULL}```## The Collapse of Marriages and BirthsNorway has long had relatively low marriage rates compared to its Nordic neighbours, with cohabitation common and socially accepted. But even by Norwegian standards, the fall in formal marriages since the early 2000s is striking. Births have followed a similar downward arc, driven partly by delayed family formation and partly by economic uncertainty.```{r plot-area-demo}#| fig-height: 5#| fig-width: 9#| fig-show: asis#| dev: "png"if (exists("df1_combined") && !is.null(df1_combined) && nrow(df1_combined) > 0) { pal <- MetBrewer::met.brewer("Derain", n = 4) plot_data <- df1_combined |> filter(.data[["statistikkvariabel"]] %in% c("Levendefødte i alt", "Inngåtte ekteskap", "Skilsmisser")) |> mutate( label = case_when( .data[["statistikkvariabel"]] == "Levendefødte i alt" ~ "Live births", .data[["statistikkvariabel"]] == "Inngåtte ekteskap" ~ "Marriages", .data[["statistikkvariabel"]] == "Skilsmisser" ~ "Divorces", TRUE ~ .data[["statistikkvariabel"]] ), label = factor(label, levels = c("Live births", "Marriages", "Divorces")) ) p1 <- ggplot(plot_data, aes(x = date, y = value, colour = label, fill = label)) + geom_area(alpha = 0.18, position = "identity") + geom_line(linewidth = 0.9) + scale_colour_manual(values = pal[c(1, 2, 4)]) + scale_fill_manual(values = pal[c(1, 2, 4)]) + scale_y_continuous(labels = comma_format(big.mark = " ")) + scale_x_date(date_breaks = "5 years", date_labels = "%Y") + labs( title = "Norway's vital events: births, marriages, and divorces", subtitle = "Births and marriages both declining; divorces remain persistently elevated relative to new unions", x = NULL, y = "Count per year", colour = NULL, fill = NULL, caption = "Source: Statistics Norway, table 05803" ) + theme_minimal(base_size = 13) + theme( legend.position = "top", panel.grid.minor = element_blank(), plot.title = element_text(face = "bold"), plot.subtitle = element_text(colour = "grey40"), plot.caption = element_text(colour = "grey50", size = 9) ) print(p1) # fixed: added explicit print() to ensure figure renders}``````{r plot-lollipop-ratio}#| fig-height: 5#| fig-width: 9#| fig-show: asis#| dev: "png"if (exists("df1_ratio") && !is.null(df1_ratio) && nrow(df1_ratio) > 0) { pal2 <- MetBrewer::met.brewer("Derain", n = 6) # Show divorce-per-marriage ratio over time as a lollipop p2 <- ggplot(df1_ratio, aes(x = year, y = divorce_per_marriage)) + geom_segment(aes(xend = year, y = 0, yend = divorce_per_marriage), colour = pal2[3], linewidth = 0.7, alpha = 0.7) + geom_point(aes(colour = divorce_per_marriage), size = 3.2) + scale_colour_gradientn( colours = MetBrewer::met.brewer("Derain", n = 256), name = "Divorces\nper 100\nmarriages" ) + geom_hline(yintercept = 50, linetype = "dashed", colour = "grey60", linewidth = 0.6) + annotate("text", x = min(df1_ratio$year) + 1, y = 51.5, label = "50 divorces per 100 marriages", size = 3.2, colour = "grey40", hjust = 0) + scale_x_continuous(breaks = seq(min(df1_ratio$year), max(df1_ratio$year), by = 5)) + labs( title = "Divorces per 100 new marriages in Norway", subtitle = "The ratio has climbed steadily, reaching levels where nearly one in two marriages ends in divorce", x = NULL, y = "Divorces per 100 marriages", caption = "Source: Statistics Norway, table 05803" ) + theme_minimal(base_size = 13) + theme( panel.grid.minor = element_blank(), panel.grid.major.x = element_blank(), plot.title = element_text(face = "bold"), plot.subtitle = element_text(colour = "grey40"), plot.caption = element_text(colour = "grey50", size = 9), legend.position = "right" ) print(p2) # fixed: added explicit print() to ensure figure renders}```The lollipop chart reveals a troubling dynamic: as the raw number of marriages has shrunk, divorces have not fallen proportionally. The ratio of divorces to new marriages has climbed to levels that would have seemed alarming to a previous generation of Norwegians.## Inflation: The Economic Climate in Which Families Make DecisionsNo analysis of declining family formation can ignore the price environment. Norwegian households have faced persistent inflation across food, housing, and transport — the three categories that most directly determine whether starting or expanding a family is financially viable.```{r plot-small-multiples-cpi}#| fig-height: 6#| fig-width: 10#| fig-show: asis#| dev: "png"if (exists("df2_12m") && !is.null(df2_12m) && nrow(df2_12m) > 0) { cpi_labels <- df2_12m |> mutate( category = case_when( .data[["konsumgruppe"]] == "Totalindeks" ~ "All items", .data[["konsumgruppe"]] == "Matvarer og alkoholfrie drikkevarer" ~ "Food & beverages", .data[["konsumgruppe"]] == "Bolig, lys og brensel" ~ "Housing, energy", .data[["konsumgruppe"]] == "Transport" ~ "Transport", TRUE ~ .data[["konsumgruppe"]] ), category = factor(category, levels = c("All items", "Food & beverages", "Housing, energy", "Transport")) ) pal3 <- MetBrewer::met.brewer("Derain", n = 4) p3 <- ggplot(cpi_labels, aes(x = date, y = value, colour = category, fill = category)) + geom_hline(yintercept = 0, linetype = "dashed", colour = "grey70", linewidth = 0.5) + geom_area(alpha = 0.15) + geom_line(linewidth = 0.85) + facet_wrap(~ category, nrow = 2, scales = "free_y") + scale_colour_manual(values = pal3) + scale_fill_manual(values = pal3) + scale_x_date(date_breaks = "1 year", date_labels = "%Y") + labs( title = "12-month price changes across key spending categories", subtitle = "Housing and energy inflation hit households hardest; food costs surged in 2022-23", x = NULL, y = "12-month change (%)", caption = "Source: Statistics Norway, table 03013", colour = NULL, fill = NULL ) + theme_minimal(base_size = 12) + theme( legend.position = "none", panel.grid.minor = element_blank(), strip.text = element_text(face = "bold", size = 11), axis.text.x = element_text(angle = 45, hjust = 1, size = 8), plot.title = element_text(face = "bold"), plot.subtitle = element_text(colour = "grey40"), plot.caption = element_text(colour = "grey50", size = 9) ) print(p3) # fixed: added explicit print() to ensure figure renders}```The small multiples show a clear story: the inflation shock that began in 2021 was concentrated in precisely the categories that young families depend on. Housing and energy costs surged first; food followed. Transport — the cost of getting to work, childcare, and family visits — added further pressure. The all-items index peaked well above the Norges Bank target and remained elevated for years.## Household Consumption: Volume Under PressureHigh prices are one side of the equation. Whether households can afford to consume is the other. The national accounts data on household consumption volumes shows what inflation actually did to purchasing power over time.```{r plot-dumbbell-consumption}#| fig-height: 5#| fig-width: 10#| fig-show: asis#| dev: "png"if (exists("df3_hhcons_vol") && !is.null(df3_hhcons_vol) && nrow(df3_hhcons_vol) > 0) { pal4 <- MetBrewer::met.brewer("Derain", n = 6) # Find two comparison periods: pre-inflation (around 2019-2020) vs recent years dumbbell_data <- df3_hhcons_vol |> mutate(year = lubridate::year(date)) |> filter(year >= 2015) |> group_by(.data[["makrostørrelse"]]) |> summarise( avg_early = mean(value[year %in% 2015:2018], na.rm = TRUE), avg_late = mean(value[year %in% 2021:2024], na.rm = TRUE), .groups = "drop" ) |> filter(!is.na(avg_early), !is.na(avg_late)) |> mutate( label = case_when( .data[["makrostørrelse"]] == "Konsum i husholdninger og ideelle organisasjoner" ~ "Households & NGOs", .data[["makrostørrelse"]] == "Konsum i husholdninger" ~ "Households only", .data[["makrostørrelse"]] == "Varekonsum" ~ "Goods consumption", .data[["makrostørrelse"]] == "Tjenestekonsum" ~ "Services consumption", .data[["makrostørrelse"]] == "Konsum i offentlig forvaltning" ~ "Public consumption", TRUE ~ .data[["makrostørrelse"]] ) ) if (nrow(dumbbell_data) > 0) { dumbbell_data <- dumbbell_data |> mutate(label = fct_reorder(label, avg_late)) p4 <- ggplot(dumbbell_data, aes(y = label)) + geom_segment(aes(x = avg_early, xend = avg_late, yend = label), colour = "grey70", linewidth = 1.8) + geom_point(aes(x = avg_early), colour = pal4[2], size = 4.5) + geom_point(aes(x = avg_late), colour = pal4[5], size = 4.5) + geom_vline(xintercept = 0, linetype = "dashed", colour = "grey40") + annotate("text", x = min(dumbbell_data$avg_early, na.rm = TRUE) - 0.2, y = nrow(dumbbell_data) + 0.5, label = "2015-2018\naverage", colour = pal4[2], size = 3.2, hjust = 1, fontface = "bold") + annotate("text", x = max(dumbbell_data$avg_late, na.rm = TRUE) + 0.2, y = nrow(dumbbell_data) + 0.5, label = "2021-2024\naverage", colour = pal4[5], size = 3.2, hjust = 0, fontface = "bold") + labs( title = "Household consumption volume growth: before and after the inflation shock", subtitle = "Goods consumption bore the sharpest volume decline; services held better through the inflation years", x = "Average annual volume change (%)", y = NULL, caption = "Source: Statistics Norway, table 09189" ) + theme_minimal(base_size = 13) + theme( panel.grid.minor = element_blank(), panel.grid.major.y = element_blank(), plot.title = element_text(face = "bold"), plot.subtitle = element_text(colour = "grey40"), plot.caption = element_text(colour = "grey50", size = 9) ) print(p4) # fixed: added explicit print() to ensure figure renders }}``````{r plot-slope-births-marriages}#| fig-height: 6#| fig-width: 9#| fig-show: asis#| dev: "png"if (exists("df1_combined") && !is.null(df1_combined) && nrow(df1_combined) > 0) { pal5 <- MetBrewer::met.brewer("Derain", n = 5) # Slope chart: index relative to first year in data slope_data <- df1_combined |> filter(.data[["statistikkvariabel"]] %in% c("Levendefødte i alt", "Inngåtte ekteskap", "Skilsmisser")) |> mutate( year = lubridate::year(date), label = case_when( .data[["statistikkvariabel"]] == "Levendefødte i alt" ~ "Live births", .data[["statistikkvariabel"]] == "Inngåtte ekteskap" ~ "Marriages", .data[["statistikkvariabel"]] == "Skilsmisser" ~ "Divorces", TRUE ~ .data[["statistikkvariabel"]] ) ) |> group_by(label) |> mutate( base_value = value[year == min(year)], index = value / base_value * 100 ) |> ungroup() # Select anchor years for slope: first, a midpoint, and most recent years_available <- sort(unique(slope_data$year)) y_start <- min(years_available) y_mid <- years_available[round(length(years_available) * 0.5)] y_end <- max(years_available) slope_subset <- slope_data |> filter(year %in% c(y_start, y_mid, y_end)) |> mutate(year_f = factor(year)) end_labels <- slope_subset |> filter(year == y_end) p5 <- ggplot(slope_subset, aes(x = year_f, y = index, group = label, colour = label)) + geom_line(linewidth = 1.2) + geom_point(size = 3.5) + geom_hline(yintercept = 100, linetype = "dashed", colour = "grey60", linewidth = 0.5) + ggrepel::geom_text_repel( data = end_labels, aes(label = paste0(label, "\n", round(index, 0))), size = 3.5, hjust = 0, nudge_x = 0.15, direction = "y", segment.colour = "grey60" ) + scale_colour_manual(values = pal5[c(1, 3, 5)]) + scale_y_continuous(labels = function(x) paste0(x)) + annotate("text", x = 1, y = 102, label = paste0("Base = ", y_start, " (100)"), size = 3, colour = "grey45", hjust = 0) + labs( title = paste0("Indexed change in births, marriages, and divorces since ", y_start), subtitle = "Marriages have fallen furthest in relative terms; births down sharply in recent years", x = NULL, y = paste0("Index (", y_start, " = 100)"), colour = NULL, caption = "Source: Statistics Norway, table 05803" ) + theme_minimal(base_size = 13) + theme( legend.position = "none", panel.grid.minor = element_blank(), panel.grid.major.x = element_blank(), plot.title = element_text(face = "bold"), plot.subtitle = element_text(colour = "grey40"), plot.caption = element_text(colour = "grey50", size = 9) ) print(p5) # fixed: added explicit print() to ensure figure renders}```The slope chart distils the long arc of change. Marriages, indexed to the earliest year in the dataset, have declined more steeply than births — a signal that the institution itself has lost ground, not merely that there are fewer young Norwegians of marrying age. Divorces, meanwhile, have remained stubbornly high relative to the base.## Key Findings- **Marriages have fallen sharply** in relative terms over the past four decades, declining further and faster than births — indicating a structural retreat from formal unions, not just a demographic echo effect.- **Divorces per 100 marriages** have risen to levels approaching and sometimes exceeding 50, meaning roughly one in two Norwegian marriages now ends in dissolution.- **Food price inflation** surged to double-digit levels in 2022-23, the sharpest sustained squeeze on household budgets in a generation, concentrated in the years when many would-be parents were making family formation decisions.- **Housing and energy costs** spiked simultaneously, compounding the affordability crisis for young families who need stable housing before having children.- **Goods consumption volume** fell more sharply than services during the inflation years, reflecting households cutting back on purchases — prams, furniture, clothing — that disproportionately accompany the arrival of children.## Closing ReflectionNorway is a country that can afford to support families generously: it has universal childcare subsidies, parental leave among the most generous in the world, and a sovereign wealth fund of extraordinary size. Yet the numbers tell a story of retreat. When housing costs spiral, food bills climb, and real consumption volumes stagnate, the rational response for many couples is to delay — or forgo entirely — formal commitment and childbearing. The marriage statistics are perhaps the most culturally loaded signal: in a society that already made peace with cohabitation decades ago, a continued fall in formal marriages suggests something deeper than a shift in social norms. It suggests that the economic conditions for long-term commitment feel increasingly precarious, even in one of the world's wealthiest nations.