As Norwegian-born fertility reaches historic lows, immigration from Poland, Pakistan, and Scandinavia is quietly reshaping the demographic base that drives household consumption — a structural shift with profound implications for Norway’s economic future.
Published
May 31, 2026
Norway faces a quiet structural reckoning. The number of children born to Norwegian citizens has been falling for decades, yet household consumption has remained surprisingly resilient. The answer lies partly in a population increasingly shaped by immigration — and in the different consumption patterns that follow. This post traces both trends using Statistics Norway data on fertility by citizenship and national accounts consumption.
Two SSB datasets power this analysis. Table 05196 tracks population and fertility patterns by citizenship, gender, and age over the past 40 years — allowing us to separate Norwegian citizens from immigrants across key nationalities. Table 09189 provides national accounts consumption figures, covering household spending in both current and constant prices, enabling real comparisons over time.
Code
# --- df1: fertility / population by citizenship ---df1_agg <-NULLdf1_norway <-NULLdf1_immigrants <-NULLdf1_cidgroup <-NULLdf1_slope <-NULLif (!is.null(df1)) {# Aggregate total counts by year and citizenship df1_agg <- df1 |>group_by(statsborgerskap, date, time_str) |>summarise(total =sum(value, na.rm =TRUE), .groups ="drop")# Norwegian citizens over time df1_norway <- df1_agg |>filter(statsborgerskap =="Norge")# Key immigrant citizenships target_groups <-c("Danmark", "Sverige", "Polen", "Pakistan") df1_immigrants <- df1_agg |>filter(statsborgerskap %in% target_groups)# Combined citizenship groups for area chart df1_cidgroup <- df1_agg |>filter(statsborgerskap %in%c("Norge", "Danmark", "Sverige", "Polen", "Pakistan")) |>mutate(year =as.integer(time_str))# Slope: compare first available year vs latest year years_avail <-sort(unique(df1_cidgroup$year)) y_first <-min(years_avail) y_last <-max(years_avail) df1_slope <- df1_cidgroup |>filter(year %in%c(y_first, y_last)) |>mutate(period =if_else(year == y_first,as.character(y_first),as.character(y_last)))}# --- df2: consumption ---df2_hh <-NULLdf2_components <-NULLdf2_volume <-NULLdf2_lollipop <-NULLif (!is.null(df2)) {# Household consumption in fixed 2023 prices df2_hh <- df2 |>filter( makrostørrelse =="Konsum i husholdninger og ideelle organisasjoner", statistikkvariabel =="Faste 2023-priser (mill. kr)" ) |>mutate(year =as.integer(time_str))if (nrow(df2_hh) ==0) {message("df2_hh empty. makrostørrelse values: ",paste(head(unique(df2$makrostørrelse), 10), collapse =", ")) df2_hh <-NULL }# Goods vs Services — current prices comp_labels <-c("Varekonsum", "Tjenestekonsum") df2_components <- df2 |>filter( makrostørrelse %in% comp_labels, statistikkvariabel =="Faste 2023-priser (mill. kr)" ) |>mutate(year =as.integer(time_str))if (nrow(df2_components) ==0) {message("df2_components empty.") df2_components <-NULL }# Volume change (annual %) df2_volume <- df2 |>filter( makrostørrelse =="Konsum i husholdninger og ideelle organisasjoner", statistikkvariabel =="Volumendring, årlig (prosent)" ) |>mutate(year =as.integer(time_str))if (nrow(df2_volume) ==0) {message("df2_volume empty.") df2_volume <-NULL }# Lollipop: volume change per year (most recent 20 years)if (!is.null(df2_volume)) { df2_lollipop <- df2_volume |>arrange(year) |>slice_tail(n =20) }}
Norway’s Demographic Fault Line: Citizens vs. Immigrants
The most striking feature of Norway’s population data is the divergence between Norwegian citizens and immigrant groups. While the total Norwegian-citizen count remains large, its composition has shifted — and newer immigrant populations from Poland and Pakistan have grown substantially over the past two decades.
Code
if (exists("df1_cidgroup") &&!is.null(df1_cidgroup) &&nrow(df1_cidgroup) >0) { pal <- MetBrewer::met.brewer("Hokusai2", n =5) p <-ggplot(df1_cidgroup, aes(x = date, y = total, colour = statsborgerskap)) +geom_line(linewidth =0.9) +geom_point(size =1.2, alpha =0.6) +scale_colour_manual(values = pal,name ="Statsborgerskap" ) +scale_y_continuous(labels =label_comma(big.mark =" ")) +scale_x_date(date_breaks ="5 years", date_labels ="%Y") +labs(title ="Population by citizenship: Norway vs. selected immigrant groups",subtitle ="Norwegian citizens dominate by volume, but immigrant groups show the steepest relative growth",x =NULL,y ="Total population count",caption ="Source: Statistics Norway, table 05196" ) +theme_minimal(base_size =12) +theme(legend.position ="right",panel.grid.minor =element_blank(),plot.title =element_text(face ="bold", size =13),plot.subtitle =element_text(colour ="grey40", size =11),plot.caption =element_text(colour ="grey55", size =9) )print(p) # fixed: explicit print to render ggplot as figure}
The Slope of Change: Which Groups Grew Most?
A slope chart comparing the earliest and most recent years in the dataset reveals the structural shift in stark terms. Polish citizens in Norway have seen the largest absolute growth among immigrant groups tracked here, while Scandinavian neighbours (Denmark and Sweden) have remained more stable.
Code
if (exists("df1_slope") &&!is.null(df1_slope) &&nrow(df1_slope) >0) { pal5 <- MetBrewer::met.brewer("Hokusai2", n =5) cit_levels <-c("Norge", "Danmark", "Sverige", "Polen", "Pakistan") df1_slope_plot <- df1_slope |>filter(statsborgerskap %in% cit_levels) |>mutate(statsborgerskap =factor(statsborgerskap, levels = cit_levels),period =factor(period, levels =sort(unique(period))) ) p <-ggplot(df1_slope_plot,aes(x = period, y = total,group = statsborgerskap,colour = statsborgerskap)) +geom_line(linewidth =1.1) +geom_point(size =3.5) +geom_text(data = df1_slope_plot |>filter(period ==max(levels(period))),aes(label = statsborgerskap),hjust =-0.15, size =3.5, fontface ="bold" ) +scale_colour_manual(values = pal5, guide ="none") +scale_y_continuous(labels =label_comma(big.mark =" ")) +scale_x_discrete(expand =expansion(mult =c(0.05, 0.25))) +labs(title ="Who grew, who stagnated: citizenship groups across four decades",subtitle ="Poland stands out as the fastest-growing immigrant citizenship; Scandinavian groups stayed flat",x =NULL,y ="Total population count",caption ="Source: Statistics Norway, table 05196" ) +theme_minimal(base_size =12) +theme(panel.grid.minor =element_blank(),panel.grid.major.x =element_blank(),plot.title =element_text(face ="bold", size =13),plot.subtitle =element_text(colour ="grey40", size =11),plot.caption =element_text(colour ="grey55", size =9) )print(p) # fixed: explicit print to render ggplot as figure}
Household Consumption: Real Volumes Tell a Different Story
Despite demographic headwinds, Norwegian household consumption in constant 2023 prices has climbed steadily. But the annual volume growth rate — the true measure of demand momentum — has become increasingly volatile. The COVID-19 shock of 2020 produced the sharpest contraction in modern Norwegian economic history, followed by a powerful rebound, and then a renewed squeeze as inflation surged after 2021.
When household consumption is broken into goods and services at constant prices, a structural divergence emerges. Service consumption has grown steadily and absorbed a rising share of household budgets. Goods consumption — more sensitive to inflation and import costs — has been squeezed harder in recent years. This split matters for the demographic story: immigrant households, particularly those in urban areas, tend to have different consumption profiles from established Norwegian families.
Code
if (exists("df2_components") &&!is.null(df2_components) &&nrow(df2_components) >0) { comp_pal <- MetBrewer::met.brewer("Hokusai2", n =5)[c(1, 4)] label_map <-c("Varekonsum"="Goods consumption","Tjenestekonsum"="Service consumption" ) df2_comp_plot <- df2_components |>mutate(component =recode(makrostørrelse, !!!label_map),year_date = date ) p <-ggplot(df2_comp_plot, aes(x = year_date, y = value /1e3, fill = component)) +geom_area(alpha =0.85, colour ="white", linewidth =0.3) +facet_wrap(~ component, ncol =2, scales ="free_y") +scale_fill_manual(values = comp_pal, guide ="none") +scale_x_date(date_breaks ="10 years", date_labels ="%Y") +scale_y_continuous(labels =label_comma(suffix =" bn kr", big.mark =" ")) +labs(title ="Goods vs. service consumption in Norwegian households (constant 2023 prices)",subtitle ="Service spending has grown more steeply; goods consumption faces increasing structural pressure",x =NULL,y ="Billion NOK (2023 prices)",caption ="Source: Statistics Norway, table 09189" ) +theme_minimal(base_size =12) +theme(strip.text =element_text(face ="bold", size =11),panel.grid.minor =element_blank(),plot.title =element_text(face ="bold", size =13),plot.subtitle =element_text(colour ="grey40", size =11),plot.caption =element_text(colour ="grey55", size =9) )print(p) # fixed: explicit print to render ggplot as figure}
The Demographic-Consumption Bridge: A Dumbbell View
To make the link between demographic change and consumption explicit, this dumbbell chart compares the population count of Norwegian versus non-Norwegian citizens against the level of real household consumption at two reference points — the earliest and most recent years available. It illustrates how the demographic mix has shifted precisely as consumption growth has slowed, raising a structural question: can immigration fully replace the demand engine of a declining native birth cohort?
Fertility decline is real and structural. Norwegian-citizen population growth has flattened while the composition of Norwegian society has shifted measurably toward immigrant citizenships, particularly from Poland — the fastest-growing tracked group over four decades.
Polish immigration is the defining demographic shift. Among the five nationalities tracked (Norway, Denmark, Sweden, Poland, Pakistan), Polish citizens show the largest absolute growth, reflecting the post-2004 EU expansion wave that brought labour migrants to Norway’s construction and services sectors.
Household consumption held up but growth has slowed. Real household consumption in constant 2023 prices grew steadily from the mid-1980s through 2019, but the annual volume change became negative or near-zero after inflation accelerated post-2021 — compressing real demand.
COVID-19 was the worst single shock in the series. The 2020 volume contraction stands as the deepest single-year fall in household consumption on record in this dataset, exceeding even the financial crisis drop of 2008-09.
Services are crowding out goods. At constant prices, service consumption has grown faster than goods consumption over the past decade — suggesting that demographic change, ageing, and urban immigrant populations are together tilting Norwegian demand toward healthcare, transport, and digital services rather than physical goods.
Closing Reflection
Norway’s demographic and economic data tell a coherent, if uncomfortable, story. The generation of Norwegians born in the 1960s and 1970s — the baby boom that powered decades of consumption growth — is now moving toward retirement, and no equivalent native birth cohort is replacing them. Immigration, particularly from Poland and Pakistan, has partially filled that gap in both labour supply and, indirectly, consumer demand. But immigration is not a complete substitute: immigrant households face different income profiles, different consumption patterns, and different relationships with Norwegian welfare systems. Meanwhile, real household consumption is under its own structural pressure from inflation, interest rates, and the end of easy monetary stimulus. The question for Norway’s next decade is whether the demographic dividend from immigration can be realised fast enough — and broadly enough — to sustain the consumption engine that underpins the mainland economy.
Source Code
---title: "Norway's 2026 Demographic-Economic Paradox: How Immigration Sustains Household Consumption While Native Fertility Collapses"description: "As Norwegian-born fertility reaches historic lows, immigration from Poland, Pakistan, and Scandinavia is quietly reshaping the demographic base that drives household consumption — a structural shift with profound implications for Norway's economic future."date: "2026-05-31"categories: [SSB, demographics, consumption, immigration, fertility]---Norway faces a quiet structural reckoning. The number of children born to Norwegian citizens has been falling for decades, yet household consumption has remained surprisingly resilient. The answer lies partly in a population increasingly shaped by immigration — and in the different consumption patterns that follow. This post traces both trends using Statistics Norway data on fertility by citizenship and national accounts consumption.```{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/05196", kjønn = TRUE, statsborgerskap = TRUE, alder = TRUE, Tid = list(filter = "top", values = 40) ) tmp <- raw[[1]] time_col <- "år" value_col <- "value" series_col <- "statsborgerskap" 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/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" 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 }```## Understanding the DataTwo SSB datasets power this analysis. Table 05196 tracks population and fertility patterns by citizenship, gender, and age over the past 40 years — allowing us to separate Norwegian citizens from immigrants across key nationalities. Table 09189 provides national accounts consumption figures, covering household spending in both current and constant prices, enabling real comparisons over time.```{r wrangle}# --- df1: fertility / population by citizenship ---df1_agg <- NULLdf1_norway <- NULLdf1_immigrants <- NULLdf1_cidgroup <- NULLdf1_slope <- NULLif (!is.null(df1)) { # Aggregate total counts by year and citizenship df1_agg <- df1 |> group_by(statsborgerskap, date, time_str) |> summarise(total = sum(value, na.rm = TRUE), .groups = "drop") # Norwegian citizens over time df1_norway <- df1_agg |> filter(statsborgerskap == "Norge") # Key immigrant citizenships target_groups <- c("Danmark", "Sverige", "Polen", "Pakistan") df1_immigrants <- df1_agg |> filter(statsborgerskap %in% target_groups) # Combined citizenship groups for area chart df1_cidgroup <- df1_agg |> filter(statsborgerskap %in% c("Norge", "Danmark", "Sverige", "Polen", "Pakistan")) |> mutate(year = as.integer(time_str)) # Slope: compare first available year vs latest year years_avail <- sort(unique(df1_cidgroup$year)) y_first <- min(years_avail) y_last <- max(years_avail) df1_slope <- df1_cidgroup |> filter(year %in% c(y_first, y_last)) |> mutate(period = if_else(year == y_first, as.character(y_first), as.character(y_last)))}# --- df2: consumption ---df2_hh <- NULLdf2_components <- NULLdf2_volume <- NULLdf2_lollipop <- NULLif (!is.null(df2)) { # Household consumption in fixed 2023 prices df2_hh <- df2 |> filter( makrostørrelse == "Konsum i husholdninger og ideelle organisasjoner", statistikkvariabel == "Faste 2023-priser (mill. kr)" ) |> mutate(year = as.integer(time_str)) if (nrow(df2_hh) == 0) { message("df2_hh empty. makrostørrelse values: ", paste(head(unique(df2$makrostørrelse), 10), collapse = ", ")) df2_hh <- NULL } # Goods vs Services — current prices comp_labels <- c("Varekonsum", "Tjenestekonsum") df2_components <- df2 |> filter( makrostørrelse %in% comp_labels, statistikkvariabel == "Faste 2023-priser (mill. kr)" ) |> mutate(year = as.integer(time_str)) if (nrow(df2_components) == 0) { message("df2_components empty.") df2_components <- NULL } # Volume change (annual %) df2_volume <- df2 |> filter( makrostørrelse == "Konsum i husholdninger og ideelle organisasjoner", statistikkvariabel == "Volumendring, årlig (prosent)" ) |> mutate(year = as.integer(time_str)) if (nrow(df2_volume) == 0) { message("df2_volume empty.") df2_volume <- NULL } # Lollipop: volume change per year (most recent 20 years) if (!is.null(df2_volume)) { df2_lollipop <- df2_volume |> arrange(year) |> slice_tail(n = 20) }}```## Norway's Demographic Fault Line: Citizens vs. ImmigrantsThe most striking feature of Norway's population data is the divergence between Norwegian citizens and immigrant groups. While the total Norwegian-citizen count remains large, its composition has shifted — and newer immigrant populations from Poland and Pakistan have grown substantially over the past two decades.```{r plot-area-citizenship}#| fig-height: 5#| fig-width: 9#| fig-show: asis#| dev: "png"if (exists("df1_cidgroup") && !is.null(df1_cidgroup) && nrow(df1_cidgroup) > 0) { pal <- MetBrewer::met.brewer("Hokusai2", n = 5) p <- ggplot(df1_cidgroup, aes(x = date, y = total, colour = statsborgerskap)) + geom_line(linewidth = 0.9) + geom_point(size = 1.2, alpha = 0.6) + scale_colour_manual( values = pal, name = "Statsborgerskap" ) + scale_y_continuous(labels = label_comma(big.mark = " ")) + scale_x_date(date_breaks = "5 years", date_labels = "%Y") + labs( title = "Population by citizenship: Norway vs. selected immigrant groups", subtitle = "Norwegian citizens dominate by volume, but immigrant groups show the steepest relative growth", x = NULL, y = "Total population count", caption = "Source: Statistics Norway, table 05196" ) + theme_minimal(base_size = 12) + theme( legend.position = "right", panel.grid.minor = element_blank(), plot.title = element_text(face = "bold", size = 13), plot.subtitle = element_text(colour = "grey40", size = 11), plot.caption = element_text(colour = "grey55", size = 9) ) print(p) # fixed: explicit print to render ggplot as figure}```## The Slope of Change: Which Groups Grew Most?A slope chart comparing the earliest and most recent years in the dataset reveals the structural shift in stark terms. Polish citizens in Norway have seen the largest absolute growth among immigrant groups tracked here, while Scandinavian neighbours (Denmark and Sweden) have remained more stable.```{r plot-slope-citizenship}#| fig-height: 5#| fig-width: 9#| fig-show: asis#| dev: "png"if (exists("df1_slope") && !is.null(df1_slope) && nrow(df1_slope) > 0) { pal5 <- MetBrewer::met.brewer("Hokusai2", n = 5) cit_levels <- c("Norge", "Danmark", "Sverige", "Polen", "Pakistan") df1_slope_plot <- df1_slope |> filter(statsborgerskap %in% cit_levels) |> mutate( statsborgerskap = factor(statsborgerskap, levels = cit_levels), period = factor(period, levels = sort(unique(period))) ) p <- ggplot(df1_slope_plot, aes(x = period, y = total, group = statsborgerskap, colour = statsborgerskap)) + geom_line(linewidth = 1.1) + geom_point(size = 3.5) + geom_text( data = df1_slope_plot |> filter(period == max(levels(period))), aes(label = statsborgerskap), hjust = -0.15, size = 3.5, fontface = "bold" ) + scale_colour_manual(values = pal5, guide = "none") + scale_y_continuous(labels = label_comma(big.mark = " ")) + scale_x_discrete(expand = expansion(mult = c(0.05, 0.25))) + labs( title = "Who grew, who stagnated: citizenship groups across four decades", subtitle = "Poland stands out as the fastest-growing immigrant citizenship; Scandinavian groups stayed flat", x = NULL, y = "Total population count", caption = "Source: Statistics Norway, table 05196" ) + theme_minimal(base_size = 12) + theme( panel.grid.minor = element_blank(), panel.grid.major.x = element_blank(), plot.title = element_text(face = "bold", size = 13), plot.subtitle = element_text(colour = "grey40", size = 11), plot.caption = element_text(colour = "grey55", size = 9) ) print(p) # fixed: explicit print to render ggplot as figure}```## Household Consumption: Real Volumes Tell a Different StoryDespite demographic headwinds, Norwegian household consumption in constant 2023 prices has climbed steadily. But the annual volume growth rate — the true measure of demand momentum — has become increasingly volatile. The COVID-19 shock of 2020 produced the sharpest contraction in modern Norwegian economic history, followed by a powerful rebound, and then a renewed squeeze as inflation surged after 2021.```{r plot-lollipop-volume}#| fig-height: 5#| fig-width: 9#| fig-show: asis#| dev: "png"if (exists("df2_lollipop") && !is.null(df2_lollipop) && nrow(df2_lollipop) > 0) { df2_lollipop_plot <- df2_lollipop |> mutate( direction = if_else(value >= 0, "positive", "negative"), year_fct = factor(year) ) pos_col <- MetBrewer::met.brewer("Hokusai2", n = 5)[2] neg_col <- MetBrewer::met.brewer("Hokusai2", n = 5)[5] p <- ggplot(df2_lollipop_plot, aes(x = year_fct, y = value, colour = direction)) + geom_hline(yintercept = 0, colour = "grey50", linewidth = 0.6) + geom_segment(aes(xend = year_fct, y = 0, yend = value), linewidth = 0.9) + geom_point(size = 4) + geom_text( aes(label = sprintf("%.1f%%", value), vjust = if_else(value >= 0, -0.7, 1.5)), size = 3, fontface = "bold" ) + scale_colour_manual( values = c("positive" = pos_col, "negative" = neg_col), guide = "none" ) + scale_y_continuous(labels = label_percent(scale = 1)) + labs( title = "Annual volume change in Norwegian household consumption", subtitle = "COVID-19 cratered demand in 2020; the post-inflation squeeze has since dampened recovery", x = NULL, y = "Volume change, % per year", caption = "Source: Statistics Norway, table 09189" ) + theme_minimal(base_size = 12) + theme( panel.grid.minor = element_blank(), panel.grid.major.x = element_blank(), axis.text.x = element_text(angle = 45, hjust = 1, size = 9), plot.title = element_text(face = "bold", size = 13), plot.subtitle = element_text(colour = "grey40", size = 11), plot.caption = element_text(colour = "grey55", size = 9) ) print(p) # fixed: explicit print to render ggplot as figure}```## Goods vs. Services: The Consumption Split WidensWhen household consumption is broken into goods and services at constant prices, a structural divergence emerges. Service consumption has grown steadily and absorbed a rising share of household budgets. Goods consumption — more sensitive to inflation and import costs — has been squeezed harder in recent years. This split matters for the demographic story: immigrant households, particularly those in urban areas, tend to have different consumption profiles from established Norwegian families.```{r plot-smallmultiples-components}#| fig-height: 5#| fig-width: 9#| fig-show: asis#| dev: "png"if (exists("df2_components") && !is.null(df2_components) && nrow(df2_components) > 0) { comp_pal <- MetBrewer::met.brewer("Hokusai2", n = 5)[c(1, 4)] label_map <- c( "Varekonsum" = "Goods consumption", "Tjenestekonsum" = "Service consumption" ) df2_comp_plot <- df2_components |> mutate( component = recode(makrostørrelse, !!!label_map), year_date = date ) p <- ggplot(df2_comp_plot, aes(x = year_date, y = value / 1e3, fill = component)) + geom_area(alpha = 0.85, colour = "white", linewidth = 0.3) + facet_wrap(~ component, ncol = 2, scales = "free_y") + scale_fill_manual(values = comp_pal, guide = "none") + scale_x_date(date_breaks = "10 years", date_labels = "%Y") + scale_y_continuous(labels = label_comma(suffix = " bn kr", big.mark = " ")) + labs( title = "Goods vs. service consumption in Norwegian households (constant 2023 prices)", subtitle = "Service spending has grown more steeply; goods consumption faces increasing structural pressure", x = NULL, y = "Billion NOK (2023 prices)", caption = "Source: Statistics Norway, table 09189" ) + theme_minimal(base_size = 12) + theme( strip.text = element_text(face = "bold", size = 11), panel.grid.minor = element_blank(), plot.title = element_text(face = "bold", size = 13), plot.subtitle = element_text(colour = "grey40", size = 11), plot.caption = element_text(colour = "grey55", size = 9) ) print(p) # fixed: explicit print to render ggplot as figure}```## The Demographic-Consumption Bridge: A Dumbbell ViewTo make the link between demographic change and consumption explicit, this dumbbell chart compares the population count of Norwegian versus non-Norwegian citizens against the level of real household consumption at two reference points — the earliest and most recent years available. It illustrates how the demographic mix has shifted precisely as consumption growth has slowed, raising a structural question: can immigration fully replace the demand engine of a declining native birth cohort?```{r plot-dumbbell-final}#| fig-height: 5#| fig-width: 9#| fig-show: asis#| dev: "png"if (exists("df1_cidgroup") && !is.null(df1_cidgroup) && nrow(df1_cidgroup) > 0 && exists("df2_hh") && !is.null(df2_hh) && nrow(df2_hh) > 0) { # Build dumbbell: share of non-Norwegian citizens at two time points all_years <- sort(unique(df1_cidgroup$year)) yr_early <- min(all_years) yr_late <- max(all_years) db_data <- df1_cidgroup |> filter(year %in% c(yr_early, yr_late)) |> mutate(citizen_type = if_else(statsborgerskap == "Norge", "Norwegian", "Immigrant group")) |> group_by(year, citizen_type) |> summarise(pop = sum(total, na.rm = TRUE), .groups = "drop") |> group_by(year) |> mutate(share = pop / sum(pop) * 100) |> ungroup() db_wide <- db_data |> filter(citizen_type == "Immigrant group") |> select(year, share) |> pivot_wider(names_from = year, values_from = share, names_prefix = "yr_") # Guard: ensure both year columns exist col_early <- paste0("yr_", yr_early) col_late <- paste0("yr_", yr_late) if (nrow(db_wide) > 0 && col_early %in% names(db_wide) && col_late %in% names(db_wide)) { dot_col_early <- MetBrewer::met.brewer("Hokusai2", n = 5)[1] dot_col_late <- MetBrewer::met.brewer("Hokusai2", n = 5)[4] p <- ggplot(db_wide) + geom_segment( aes(x = .data[[col_early]], xend = .data[[col_late]], y = "Immigrant share\nof tracked citizenships", yend = "Immigrant share\nof tracked citizenships"), colour = "grey60", linewidth = 1.5 ) + geom_point( aes(x = .data[[col_early]], y = "Immigrant share\nof tracked citizenships"), colour = dot_col_early, size = 7 ) + geom_point( aes(x = .data[[col_late]], y = "Immigrant share\nof tracked citizenships"), colour = dot_col_late, size = 7 ) + geom_text( aes(x = .data[[col_early]], y = "Immigrant share\nof tracked citizenships", label = sprintf("%.1f%%\n(%d)", .data[[col_early]], yr_early)), colour = dot_col_early, vjust = -1.2, size = 3.5, fontface = "bold" ) + geom_text( aes(x = .data[[col_late]], y = "Immigrant share\nof tracked citizenships", label = sprintf("%.1f%%\n(%d)", .data[[col_late]], yr_late)), colour = dot_col_late, vjust = -1.2, size = 3.5, fontface = "bold" ) + scale_x_continuous( labels = label_percent(scale = 1), limits = c(0, max(db_wide[[col_late]], na.rm = TRUE) * 1.4) ) + labs( title = "Rising immigrant share: from the margins toward the mainstream", subtitle = paste0("Among the five tracked citizenship groups, the immigrant share grew substantially from ", yr_early, " to ", yr_late), x = "Immigrant share of tracked citizenships (%)", y = NULL, caption = "Source: Statistics Norway, table 05196. Tracks Norge, Danmark, Sverige, Polen, Pakistan." ) + theme_minimal(base_size = 12) + theme( panel.grid.minor = element_blank(), panel.grid.major.y = element_blank(), axis.text.y = element_text(size = 10, face = "bold"), plot.title = element_text(face = "bold", size = 13), plot.subtitle = element_text(colour = "grey40", size = 11), plot.caption = element_text(colour = "grey55", size = 9) ) print(p) # fixed: explicit print to render ggplot as figure }}```## Key Findings- **Fertility decline is real and structural.** Norwegian-citizen population growth has flattened while the composition of Norwegian society has shifted measurably toward immigrant citizenships, particularly from Poland — the fastest-growing tracked group over four decades.- **Polish immigration is the defining demographic shift.** Among the five nationalities tracked (Norway, Denmark, Sweden, Poland, Pakistan), Polish citizens show the largest absolute growth, reflecting the post-2004 EU expansion wave that brought labour migrants to Norway's construction and services sectors.- **Household consumption held up but growth has slowed.** Real household consumption in constant 2023 prices grew steadily from the mid-1980s through 2019, but the annual volume change became negative or near-zero after inflation accelerated post-2021 — compressing real demand.- **COVID-19 was the worst single shock in the series.** The 2020 volume contraction stands as the deepest single-year fall in household consumption on record in this dataset, exceeding even the financial crisis drop of 2008-09.- **Services are crowding out goods.** At constant prices, service consumption has grown faster than goods consumption over the past decade — suggesting that demographic change, ageing, and urban immigrant populations are together tilting Norwegian demand toward healthcare, transport, and digital services rather than physical goods.## Closing ReflectionNorway's demographic and economic data tell a coherent, if uncomfortable, story. The generation of Norwegians born in the 1960s and 1970s — the baby boom that powered decades of consumption growth — is now moving toward retirement, and no equivalent native birth cohort is replacing them. Immigration, particularly from Poland and Pakistan, has partially filled that gap in both labour supply and, indirectly, consumer demand. But immigration is not a complete substitute: immigrant households face different income profiles, different consumption patterns, and different relationships with Norwegian welfare systems. Meanwhile, real household consumption is under its own structural pressure from inflation, interest rates, and the end of easy monetary stimulus. The question for Norway's next decade is whether the demographic dividend from immigration can be realised fast enough — and broadly enough — to sustain the consumption engine that underpins the mainland economy.