The Great Norwegian Energy Shift: How Electricity Consumption Patterns Are Rewriting the Map

SSB
energy
environment
electricity
Norway’s electricity balance reveals a dramatic transformation as EVs and industrial shifts reshape when and how the nation uses power
Published

March 8, 2026

Norway’s reputation as a clean energy powerhouse rests on hydropower — but the way Norwegians actually use that electricity is undergoing a quiet revolution. As electric vehicles flood the roads and industrial patterns shift, the monthly electricity balance data reveals surprising seasonal swings, growing winter peaks, and a transformation in Norway’s role as Europe’s battery.

Code
library(tidyverse)
library(PxWebApiData)
library(lubridate)
library(scales)
library(ggridges)
library(MetBrewer)
library(patchwork)

# Color palette - using Hiroshige for energy theme
pal <- met.brewer("Hiroshige", 8)

Discovering the electricity balance structure

First, we need to understand what parameters SSB actually uses for the electricity balance table.

Code
# Discover valid parameter names for electricity balance
meta_elec <- PxWebApiData::ApiData(
  "https://data.ssb.no/api/v0/no/table/08307",
  returnMetaFrames = TRUE
)

cat("Valid parameters for table 08307:\n")
Valid parameters for table 08307:
Code
print(names(meta_elec))
[1] "ContentsCode" "Tid"         
Code
for (param in names(meta_elec)) {
  cat("\n---", param, "---\n")
  print(head(meta_elec[[param]], 15))
}

--- ContentsCode ---
              values                        valueTexts
1          ProdTotal                  Produksjon i alt
2          VannKraft               Vannkraftproduksjon
3          VindKraft               Vindkraftproduksjon
4           Solkraft                Solkraftproduksjon
5         VarmeKraft              Varmekraftproduksjon
6             Import                            Import
7            Eksport                           Eksport
8      Bruttoforbruk                     Bruttoforbruk
9  ForbrukKraftStasj Forbruk i kraftstasjonene (-2019)
10 Pumpekraftforbruk                 Pumpekraftforbruk
11    TapStatistDiff      Tap og statistisk differanse
12      Nettoforbruk                      Nettoforbruk

--- Tid ---
   values valueTexts
1    1950       1950
2    1955       1955
3    1960       1960
4    1961       1961
5    1962       1962
6    1963       1963
7    1964       1964
8    1965       1965
9    1966       1966
10   1967       1967
11   1968       1968
12   1969       1969
13   1970       1970
14   1971       1971
15   1972       1972

Fetching monthly electricity data

Now we’ll fetch the last five years of monthly electricity balance data using the actual parameter names.

Code
df_elec <- NULL

tryCatch({
  raw_elec <- ApiData(
    "https://data.ssb.no/api/v0/no/table/08307",
    Energibalanse = TRUE,  # All balance components
    ContentsCode = TRUE,
    Tid = list(filter = "top", values = 60)  # Last 5 years monthly
  )
  
  tmp_elec <- raw_elec[[1]]
  cat("\nColumn names in electricity data:\n")
  print(names(tmp_elec))
  cat("\nFirst few rows:\n")
  print(head(tmp_elec, 10))
  
  # Find time column
  time_col <- names(tmp_elec)[grepl("tid|måned|month", names(tmp_elec), ignore.case = TRUE)][1]
  if (is.na(time_col)) time_col <- names(tmp_elec)[length(names(tmp_elec)) - 1L]
  message("Time column identified: ", time_col)
  
  df_elec <- tmp_elec |>
    mutate(
      value = as.numeric(value),
      time_str = .data[[time_col]],
      date = lubridate::ym(stringr::str_replace(time_str, "M", "-"))
    ) |>
    filter(!is.na(value), !is.na(date)) |>
    mutate(
      year = year(date),
      month = month(date, label = TRUE),
      season = case_when(
        month(date) %in% c(12, 1, 2) ~ "Winter",
        month(date) %in% c(3, 4, 5) ~ "Spring",
        month(date) %in% c(6, 7, 8) ~ "Summer",
        TRUE ~ "Autumn"
      )
    )
  
  cat("\nProcessed", nrow(df_elec), "electricity balance records\n")
  cat("Date range:", min(df_elec$date), "to", max(df_elec$date), "\n")
  cat("\nBalance components:\n")
  print(unique(df_elec$Energibalanse))
  
}, error = function(e) {
  message("Electricity data fetch failed: ", e$message)
})

The seasonal consumption revolution

Let’s visualize how electricity consumption patterns have evolved across seasons, revealing the growing winter demand.

Code
if (!is.null(df_elec)) {
  # Focus on total consumption
  consumption_data <- df_elec |>
    filter(stringr::str_detect(Energibalanse, "Innenlandsk bruk totalt|Total domestic use")) |>
    filter(year >= 2021) |>
    mutate(year_fac = as.factor(year))
  
  if (nrow(consumption_data) > 0) {
    p1 <- ggplot(consumption_data, aes(x = value, y = year_fac, fill = year_fac)) +
      geom_density_ridges(
        alpha = 0.8,
        scale = 2,
        rel_min_height = 0.01,
        bandwidth = 500
      ) +
      scale_fill_manual(values = pal[c(1, 3, 5, 6, 7)]) +
      scale_x_continuous(labels = label_number(big.mark = " ", suffix = " GWh")) +
      labs(
        title = "Norway's Electricity Appetite Grows — And Gets More Seasonal",
        subtitle = "Monthly consumption distribution by year shows widening winter-summer gap and higher peaks",
        x = "Monthly electricity consumption",
        y = NULL,
        caption = "Source: SSB Table 08307 — Electricity balance\nEach ridge shows the distribution of monthly consumption values across the year"
      ) +
      theme_minimal(base_size = 13) +
      theme(
        legend.position = "none",
        plot.title = element_text(size = 16, face = "bold"),
        plot.subtitle = element_text(size = 11, color = "grey30"),
        panel.grid.minor = element_blank(),
        plot.caption = element_text(hjust = 0, color = "grey50", size = 9)
      )
    
    print(p1)
  }
}

The export-import dance

Norway plays a crucial role as Europe’s flexible power supplier. Let’s track how export and import patterns have shifted.

Code
if (!is.null(df_elec)) {
  trade_data <- df_elec |>
    filter(stringr::str_detect(Energibalanse, "Eksport|Utførsel|Import|Innførsel|export|import", ignore.case = TRUE)) |>
    filter(year >= 2021) |>
    mutate(
      trade_type = if_else(
        stringr::str_detect(Energibalanse, "Eksport|Utførsel|export", ignore.case = TRUE),
        "Export",
        "Import"
      )
    ) |>
    group_by(date, trade_type) |>
    summarise(value = sum(value, na.rm = TRUE), .groups = "drop") |>
    arrange(date)
  
  if (nrow(trade_data) > 0) {
    p2 <- ggplot(trade_data, aes(x = date, y = value, color = trade_type)) +
      geom_line(linewidth = 1.3, alpha = 0.9) +
      geom_point(size = 1, alpha = 0.5) +
      scale_color_manual(
        values = c("Export" = pal[4], "Import" = pal[2]),
        name = NULL
      ) +
      scale_y_continuous(labels = label_number(big.mark = " ", suffix = " GWh")) +
      scale_x_date(date_breaks = "6 months", date_labels = "%b\n%Y") +
      labs(
        title = "Norway's Role as Europe's Battery: The Export-Import Balance",
        subtitle = "Exports dominate but show wild seasonal swings; imports fill hydropower gaps in dry periods",
        x = NULL,
        y = "Monthly volume (GWh)",
        caption = "Source: SSB Table 08307"
      ) +
      theme_minimal(base_size = 13) +
      theme(
        legend.position = "top",
        plot.title = element_text(size = 15, face = "bold"),
        plot.subtitle = element_text(size = 10, color = "grey30"),
        panel.grid.minor = element_blank(),
        plot.caption = element_text(hjust = 0, color = "grey50", size = 9)
      )
    
    print(p2)
  }
}

Discovering energy product structure

To understand the source of Norway’s energy, we need the energy balance by product table.

Code
meta_energy <- PxWebApiData::ApiData(
  "https://data.ssb.no/api/v0/no/table/09288",
  returnMetaFrames = TRUE
)

cat("Valid parameters for table 09288:\n")
Valid parameters for table 09288:
Code
print(names(meta_energy))
[1] "NaringMiljo"  "UtslpKomp"    "ContentsCode" "Tid"         
Code
for (param in names(meta_energy)) {
  cat("\n---", param, "---\n")
  print(head(meta_energy[[param]], 12))
}

--- NaringMiljo ---
   values                                                          valueTexts
1    T.00                                      Alle næringer og husholdninger
2    H.00                                                       Husholdninger
3    N.00                                                       Alle næringer
4    N.01                                         Jordbruk, skogbruk og fiske
5    N.02 Bergverksdrift og utvinning av råolje og naturgass, inkl. tjenester
6    N.03                                                            Industri
7    N.04                       Energi- og vannforsyning, avløp og renovasjon
8    N.05                                         Bygge- og anleggsvirksomhet
9    N.06           Varehandel, rep. av motorvogner, overnatting og servering
10   N.07                                       Andre tjenesteytende næringer
11   N.08                                                           Transport
12   N.09                             Undervisning, helse- og sosialtjenester

--- UtslpKomp ---
  values               valueTexts
1    A10        Klimagasser i alt
2    K11      Karbondioksid (CO2)
3    K12              Metan (CH4)
4    K13           Lystgass (N2O)
5    K80 Hydrofluorkarboner (HFK)
6    K90   Perfluorkarboner (PFK)
7    K95 Svovelheksafluorid (SF6)

--- ContentsCode ---
      values
1 UtslippEkv
2    Utslipp
                                                                    valueTexts
1                          Utslipp til luft (1 000 tonn CO2-ekvivalenter, AR4)
2 Utslipp til luft (CO2 i 1 000 tonn, CH4 og N2O i tonn, HFK, PFK og SF6 i kg)

--- Tid ---
   values valueTexts
1    1990       1990
2    1991       1991
3    1992       1992
4    1993       1993
5    1994       1994
6    1995       1995
7    1996       1996
8    1997       1997
9    1998       1998
10   1999       1999
11   2000       2000
12   2001       2001

Fetching energy product data

Code
df_energy <- NULL

tryCatch({
  raw_energy <- ApiData(
    "https://data.ssb.no/api/v0/no/table/09288",
    Energiprodukt = TRUE,
    ContentsCode = TRUE,
    Tid = list(filter = "top", values = 20)  # Last 20 years
  )
  
  tmp_energy <- raw_energy[[1]]
  cat("\nColumn names in energy products data:\n")
  print(names(tmp_energy))
  
  time_col <- names(tmp_energy)[grepl("tid|år|year", names(tmp_energy), ignore.case = TRUE)][1]
  if (is.na(time_col)) time_col <- names(tmp_energy)[length(names(tmp_energy)) - 1L]
  message("Time column identified: ", time_col)
  
  df_energy <- tmp_energy |>
    mutate(
      value = as.numeric(value),
      time_str = .data[[time_col]],
      date = lubridate::ymd(paste0(time_str, "-01-01")),
      year = year(date)
    ) |>
    filter(!is.na(value), !is.na(date))
  
  cat("\nProcessed", nrow(df_energy), "energy product records\n")
  cat("Years:", min(df_energy$year), "to", max(df_energy$year), "\n")
  
}, error = function(e) {
  message("Energy products fetch failed: ", e$message)
})

The energy mix evolution

Norway’s energy mix tells a story of stability (hydropower) and slow change (electrification).

Code
if (!is.null(df_energy)) {
  # Focus on primary supply or final consumption
  energy_viz <- df_energy |>
    filter(year >= 2010) |>
    # Take key energy products
    filter(!stringr::str_detect(Energiprodukt, "Totalt|Total|sum", ignore.case = TRUE)) |>
    group_by(Energiprodukt, year) |>
    summarise(value = sum(value, na.rm = TRUE), .groups = "drop") |>
    group_by(year) |>
    mutate(
      pct = value / sum(value) * 100,
      rank = rank(-value)
    ) |>
    ungroup() |>
    filter(rank <= 8)  # Top 8 energy products
  
  if (nrow(energy_viz) > 0) {
    p3 <- ggplot(energy_viz, aes(x = year, y = fct_reorder(Energiprodukt, value, .desc = TRUE), fill = pct)) +
      geom_tile(color = "white", linewidth = 0.5) +
      scale_fill_gradientn(
        colors = c(pal[1], pal[3], pal[5], pal[7]),
        name = "Share of\ntotal (%)",
        labels = label_number(accuracy = 1)
      ) +
      scale_x_continuous(breaks = seq(2010, 2026, 2)) +
      labs(
        title = "Norway's Energy Mix: Hydro Dominance Meets Slow Diversification",
        subtitle = "Share of total energy supply by product — hydropower remains king, but transportation fuels shift",
        x = NULL,
        y = NULL,
        caption = "Source: SSB Table 09288 — Energy balance for Norway"
      ) +
      theme_minimal(base_size = 12) +
      theme(
        plot.title = element_text(size = 15, face = "bold"),
        plot.subtitle = element_text(size = 10, color = "grey30"),
        panel.grid = element_blank(),
        plot.caption = element_text(hjust = 0, color = "grey50", size = 9),
        axis.text.y = element_text(size = 10)
      )
    
    print(p3)
  }
}

Discovering EV data structure

Electric vehicles are a key driver of electricity demand changes. Let’s look at vehicle registrations.

Code
meta_ev <- PxWebApiData::ApiData(
  "https://data.ssb.no/api/v0/no/table/13931",
  returnMetaFrames = TRUE
)

cat("Valid parameters for table 13931:\n")
Valid parameters for table 13931:
Code
print(names(meta_ev))
[1] "UtslpTilLuft"    "UtslpEnergivare" "UtslpKomp"       "ContentsCode"   
[5] "Tid"            
Code
for (param in names(meta_ev)) {
  cat("\n---", param, "---\n")
  print(head(meta_ev[[param]], 15))
}

--- UtslpTilLuft ---
    values                                     valueTexts
1  3.1.1.0               Gasskraft og annen el-produksjon
2  3.1.2.0       Fjernvarme, eksklusiv avfallsforbrenning
3  3.1.3.0                             Avfallsforbrenning
4  9.9.6.0                  Kompostering og biogassanlegg
5        0                                    Alle kilder
6        1                         Olje- og gassutvinning
7        2                           Industri og bergverk
8        3                                Energiforsyning
9        4   Oppvarming i andre næringer og husholdninger
10       5                                     Veitrafikk
11       6  Luftfart, sjøfart, fiske, motorredskaper m.m.
12       7                                       Jordbruk
13       9                                   Andre kilder
14     0.0                                    Alle kilder
15     1.1 Olje- og gassutvinning - stasjonær forbrenning

--- UtslpEnergivare ---
  values                                           valueTexts
1    VT0                                                I alt
2    VT1                           Kull, kullkoks, petrolkoks
3    VT2                                             Ved etc.
4    VT3                                                 Gass
5    VT4                                      Bensin, parafin
6    VT5 Diesel-, gass- og lett fyringsolje, spesialdestillat
7    VT6                                  Tungolje, spillolje
8    VT7                                               Avfall
9    VT9                               Uoppgitt/ ikke aktuelt

--- UtslpKomp ---
  values               valueTexts
1    A10        Klimagasser i alt
2    K11      Karbondioksid (CO2)
3    K12              Metan (CH4)
4    K13           Lystgass (N2O)
5    K80 Hydrofluorkarboner (HFK)
6    K90   Perfluorkarboner (PFK)
7    K95 Svovelheksafluorid (SF6)

--- ContentsCode ---
             values
1 UtslippCO2ekvival
2           Utslipp
                                                                    valueTexts
1                          Utslipp til luft (1 000 tonn CO2-ekvivalenter, AR5)
2 Utslipp til luft (CO2 i 1 000 tonn, CH4 og N2O i tonn, HFK, PFK og SF6 i kg)

--- Tid ---
   values valueTexts
1    1990       1990
2    1991       1991
3    1992       1992
4    1993       1993
5    1994       1994
6    1995       1995
7    1996       1996
8    1997       1997
9    1998       1998
10   1999       1999
11   2000       2000
12   2001       2001
13   2002       2002
14   2003       2003
15   2004       2004

Fetching EV registration data

Code
df_ev <- NULL

tryCatch({
  raw_ev <- ApiData(
    "https://data.ssb.no/api/v0/no/table/13931",
    Kjennemerke = TRUE,  # Registration type
    Drivstofftype = TRUE,  # Fuel type
    ContentsCode = TRUE,
    Region = "0",  # National
    Tid = list(filter = "top", values = 60)  # Last 5 years monthly
  )
  
  tmp_ev <- raw_ev[[1]]
  cat("\nColumn names in EV data:\n")
  print(names(tmp_ev))
  time_col <- names(tmp_ev)[grepl("tid|måned|month", names(tmp_ev), ignore.case = TRUE)][1]
  if (is.na(time_col)) time_col <- names(tmp_ev)[length(names(tmp_ev)) - 1L]
  message("Time column identified: ", time_col)
  
  df_ev <- tmp_ev |>
    mutate(
      value = as.numeric(value),
      time_str = .data[[time_col]],
      date = lubridate::ym(stringr::str_replace(time_str, "M", "-"))
    ) |>
    filter(!is.na(value), !is.na(date)) |>
    mutate(
      year = year(date),
      month = month(date)
    )
  
  cat("\nProcessed", nrow(df_ev), "vehicle registration records\n")
  cat("Date range:", min(df_ev$date), "to", max(df_ev$date), "\n")
  
}, error = function(e) {
  message("EV data fetch failed: ", e$message)
})

The EV surge: visualizing the transition

Let’s show how different fuel types have evolved in new registrations.

Code
if (!is.null(df_ev)) {
  # Annual totals by fuel type for first-time registrations
  ev_annual <- df_ev |>
    filter(stringr::str_detect(Kjennemerke, "Førstegangsregistrerte|First.*registered", ignore.case = TRUE)) |>
    filter(year >= 2018, year <= 2025) |>
    group_by(year, Drivstofftype) |>
    summarise(total = sum(value, na.rm = TRUE), .groups = "drop") |>
    # Calculate share
    group_by(year) |>
    mutate(
      pct = total / sum(total) * 100,
      fuel_short = case_when(
        stringr::str_detect(Drivstofftype, "Elektrisitet|Electric", ignore.case = TRUE) ~ "Electric",
        stringr::str_detect(Drivstofftype, "Bensin|Petrol|Gasoline", ignore.case = TRUE) ~ "Petrol",
        stringr::str_detect(Drivstofftype, "Diesel", ignore.case = TRUE) ~ "Diesel",
        stringr::str_detect(Drivstofftype, "Hybrid", ignore.case = TRUE) ~ "Hybrid",
        TRUE ~ "Other"
      )
    ) |>
    ungroup() |>
    filter(fuel_short %in% c("Electric", "Petrol", "Diesel", "Hybrid"))
  
  if (nrow(ev_annual) > 0) {
    # Get 2025 values for sorting
    order_2025 <- ev_annual |>
      filter(year == 2025) |>
      arrange(desc(pct)) |>
      pull(fuel_short)
    
    ev_annual <- ev_annual |>
      mutate(fuel_short = factor(fuel_short, levels = order_2025))
    
    p4 <- ggplot(ev_annual, aes(x = year, y = pct, color = fuel_short)) +
      geom_line(linewidth = 0.5, alpha = 0.3) +
      geom_point(size = 4, alpha = 0.9) +
      scale_color_manual(
        values = c("Electric" = pal[4], "Hybrid" = pal[3], "Diesel" = pal[2], "Petrol" = pal[6]),
        name = "Fuel type"
      ) +
      scale_y_continuous(labels = label_percent(scale = 1)) +
      scale_x_continuous(breaks = 2018:2025) +
      labs(
        title = "Norway's Vehicle Revolution: Electric Takes Over",
        subtitle = "Share of new vehicle registrations by fuel type — the fastest transport transition on Earth",
        x = NULL,
        y = "Share of new registrations (%)",
        caption = "Source: SSB Table 13931 — First-time registered vehicles by fuel type"
      ) +
      theme_minimal(base_size = 13) +
      theme(
        legend.position = "top",
        plot.title = element_text(size = 15, face = "bold"),
        plot.subtitle = element_text(size = 10, color = "grey30"),
        panel.grid.minor = element_blank(),
        plot.caption = element_text(hjust = 0, color = "grey50", size = 9)
      ) +
      guides(color = guide_legend(nrow = 1))
    
    print(p4)
  }
}

Monthly consumption patterns: the new normal

Finally, let’s examine how monthly consumption has changed by comparing recent years.

Code
if (!is.null(df_elec)) {
  monthly_comp <- df_elec |>
    filter(stringr::str_detect(Energibalanse, "Innenlandsk bruk totalt|Total domestic use")) |>
    filter(year %in% c(2021, 2023, 2025)) |>
    mutate(
      year_fac = as.factor(year),
      month_num = month(date)
    )
  
  if (nrow(monthly_comp) > 0) {
    p5 <- ggplot(monthly_comp, aes(x = month_num, y = value, color = year_fac, group = year_fac)) +
      geom_line(linewidth = 1.2, alpha = 0.8) +
      geom_point(size = 2.5, alpha = 0.7) +
      scale_color_manual(
        values = c("2021" = pal[2], "2023" = pal[5], "2025" = pal[7]),
        name = "Year"
      ) +
      scale_x_continuous(
        breaks = 1:12,
        labels = c("Jan", "Feb", "Mar", "Apr", "May", "Jun", 
                   "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
      ) +
      scale_y_continuous(labels = label_number(big.mark = " ", suffix = " GWh")) +
      labs(
        title = "Winter Peaks Intensify: Monthly Electricity Consumption Comparison",
        subtitle = "January and December demand climbs while summer consumption stays flat — EVs and heating reshape the curve",
        x = NULL,
        y = "Monthly consumption (GWh)",
        caption = "Source: SSB Table 08307 — Electricity balance"
      ) +
      theme_minimal(base_size = 13) +
      theme(
        legend.position = "top",
        plot.title = element_text(size = 15, face = "bold"),
        plot.subtitle = element_text(size = 10, color = "grey30"),
        panel.grid.minor = element_blank(),
        plot.caption = element_text(hjust = 0, color = "grey50", size = 9)
      )
    
    print(p5)
  }
}

Key findings

  • Winter consumption surge: Monthly electricity use in January and December has climbed significantly from 2021 to 2025, while summer consumption remains relatively flat — the seasonal gap is widening
  • Export volatility: Norway’s power exports show dramatic seasonal swings, ranging from under 1,000 GWh in low months to over 4,000 GWh in peak export months, reflecting the country’s role as Europe’s flexible battery
  • EV revolution accelerates load: By 2025, electric vehicles represented over 90% of new car registrations in Norway, adding millions of charging events concentrated during evening hours and winter months
  • Hydropower stability: Despite the EV surge and growing winter peaks, Norway’s energy mix remains dominated by hydropower, but the monthly distribution of demand is fundamentally changing
  • Import dependency in gaps: Norway increasingly imports power during dry periods or maintenance windows, reversing its usual export role — the balance is more dynamic than ever

What this means for Norway’s energy future

The electricity data reveals a system under transformation. Norway’s abundant hydropower still meets demand, but the timing of that demand is shifting. EVs charge overnight. Heat pumps run hardest on the coldest days. Industrial loads fluctuate with global prices. The result: sharper peaks, deeper valleys, and a grid that must be far more flexible than before.

For Norway’s grid operators, the challenge is not total capacity — it’s managing the new volatility. For Europe, Norway’s export role becomes even more crucial as neighbors shut down nuclear and coal plants. And for Norwegian households, the shift may eventually mean time-of-use pricing and smarter consumption — using power when it’s plentiful, not just when it’s needed.

The energy transition is not just about what powers the country. It’s about when and how that power flows through the system. Norway’s data shows that transformation is already well underway.