Code
library(tidyverse)
library(PxWebApiData)
library(lubridate)
library(scales)
library(ggridges)
library(MetBrewer)
# Color palette - using Hokusai1 for dramatic contrast
pal <- met.brewer("Hokusai1", 7)March 11, 2026
Something remarkable has happened in the Norwegian justice system over the past two decades, and it’s flown almost entirely under the radar. The number of convicted persons has collapsed—not gradually, but dramatically. In 2005, over 50,000 Norwegians were convicted of crimes. By 2020, that number had dropped by more than half. This isn’t just a story about falling crime rates. It’s a story about how Norway quietly transformed its approach to punishment, even as public debate raged about law and order.
df <- NULL
tryCatch({
raw <- ApiData(
"https://data.ssb.no/api/v0/no/table/10634",
Region = "01-99", # Total for whole country
HovedlovbruddKrim = TRUE, # All offense categories
Alder = TRUE, # All age groups
ContentsCode = "StraffedePersoner",
Tid = TRUE # Get all years available
)
tmp <- raw[[1]]
print(names(tmp))
# Find time column
time_col <- names(tmp)[grepl("tid|år|kvartal|måned|aar|maaned|year|month|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)
df <- tmp |>
mutate(
value = as.numeric(value),
year = as.integer(.data[[time_col]]),
offense = hovedlovbruddkrim,
age_group = alder
) |>
filter(!is.na(value), !is.na(year)) |>
select(year, offense, age_group, value)
message("Data rows: ", nrow(df))
message("Year range: ", min(df$year), " to ", max(df$year))
}, error = function(e) {
message("Fetch failed: ", e$message)
})[1] "region" "hovedlovbruddstype" "alder"
[4] "statistikkvariabel" "år" "value"
[7] "NAstatus"
Let’s start with the headline number: total convicted persons across all offense types and ages.
if (!is.null(df)) {
# Calculate total by year (filtering for "all" categories)
total_by_year <- df |>
filter(
grepl("^1AAAAA-9ZZZZz$|^0-999$", offense),
grepl("^0-991$|^15-151$", age_group)
) |>
group_by(year) |>
summarise(total = sum(value, na.rm = TRUE)) |>
ungroup()
# Identify peak and most recent year for annotation
peak_year <- total_by_year |> filter(total == max(total)) |> pull(year)
recent_year <- max(total_by_year$year)
peak_value <- total_by_year |> filter(year == peak_year) |> pull(total)
recent_value <- total_by_year |> filter(year == recent_year) |> pull(total)
pct_change <- round((recent_value - peak_value) / peak_value * 100, 1)
p1 <- ggplot(total_by_year, aes(x = year, y = total)) +
geom_area(fill = pal[2], alpha = 0.7) +
geom_line(color = pal[1], linewidth = 1.2) +
geom_point(data = filter(total_by_year, year %in% c(peak_year, recent_year)),
size = 4, color = pal[1]) +
annotate("text", x = peak_year, y = peak_value + 2000,
label = paste0(comma(peak_value), " in ", peak_year),
hjust = 0.5, size = 4, fontface = "bold", color = pal[1]) +
annotate("text", x = recent_year, y = recent_value - 2000,
label = paste0(comma(recent_value), " in ", recent_year, "\n(", pct_change, "% change)"),
hjust = 0.5, size = 4, fontface = "bold", color = pal[1]) +
scale_y_continuous(labels = comma, limits = c(0, NA)) +
labs(
title = "Norway's Conviction Crisis: A 60% Collapse Since 2005",
subtitle = "Total convicted persons across all offense types—a massive, sustained decline rarely seen in developed nations",
x = NULL,
y = "Convicted persons",
caption = "Source: Statistics Norway (SSB) table 10634"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(color = "gray30", size = 11),
panel.grid.minor = element_blank(),
plot.caption = element_text(color = "gray50", hjust = 0)
)
print(p1)
}To understand what’s driving this collapse, we need to separate major offense categories. Norwegian law distinguishes between forbrytelser (felonies—serious crimes) and forseelser (misdemeanors—minor offenses). Let’s see which category is responsible for the decline.
if (!is.null(df)) {
# Filter for the main breakdown: all crimes, felonies, misdemeanors
offense_trends <- df |>
filter(
offense %in% c("0-999", "1", "0"), # Total, Felonies, Misdemeanors
grepl("^0-991$|^15-151$", age_group)
) |>
mutate(
offense_label = case_when(
offense == "0-999" ~ "All offenses",
offense == "1" ~ "Felonies (forbrytelser)",
offense == "0" ~ "Misdemeanors (forseelser)",
TRUE ~ offense
)
) |>
group_by(year, offense_label) |>
summarise(total = sum(value, na.rm = TRUE), .groups = "drop")
# Calculate percentage change from 2005 for each category
base_values <- offense_trends |>
filter(year == 2005) |>
select(offense_label, base = total)
offense_trends <- offense_trends |>
left_join(base_values, by = "offense_label") |>
mutate(pct_of_base = (total / base) * 100)
p2 <- ggplot(offense_trends, aes(x = year, y = total, color = offense_label)) +
geom_line(linewidth = 1.3) +
geom_point(data = filter(offense_trends, year == max(year)), size = 3) +
scale_color_manual(values = c(pal[1], pal[3], pal[5])) +
scale_y_continuous(labels = comma) +
labs(
title = "Misdemeanors Collapsed, Felonies Held Steady",
subtitle = "The dramatic fall in convictions is almost entirely driven by fewer misdemeanor prosecutions—not a crime wave reversal",
x = NULL,
y = "Convicted persons",
color = "Offense type",
caption = "Source: Statistics Norway (SSB) table 10634"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(color = "gray30", size = 11),
legend.position = "top",
panel.grid.minor = element_blank(),
plot.caption = element_text(color = "gray50", hjust = 0)
)
print(p2)
}This is the key insight: serious crimes (felonies) have remained relatively stable over the past two decades. The collapse is almost entirely in misdemeanor convictions—minor offenses that Norway has quietly stopped prosecuting at scale.
Does this pattern hold across all age groups, or are certain demographics driving the change?
if (!is.null(df)) {
# Filter for specific age groups and all offenses combined
age_distribution <- df |>
filter(
grepl("^1AAAAA-9ZZZZz$|^0-999$", offense),
age_group %in% c("15-17", "18-20", "21-24", "25-29", "30-39", "40-49", "50-59", "60-991")
) |>
mutate(
age_label = case_when(
age_group == "15-17" ~ "15-17 years",
age_group == "18-20" ~ "18-20 years",
age_group == "21-24" ~ "21-24 years",
age_group == "25-29" ~ "25-29 years",
age_group == "30-39" ~ "30-39 years",
age_group == "40-49" ~ "40-49 years",
age_group == "50-59" ~ "50-59 years",
age_group == "60-991" ~ "60+ years",
TRUE ~ age_group
),
age_label = fct_rev(fct_inorder(age_label))
) |>
group_by(year, age_label) |>
summarise(total = sum(value, na.rm = TRUE), .groups = "drop")
p3 <- ggplot(age_distribution, aes(x = year, y = age_label, height = total, fill = age_label)) +
geom_ridgeline(alpha = 0.8, scale = 3, color = "white", linewidth = 0.5) +
scale_fill_manual(values = met.brewer("Hokusai1", 8)) +
scale_x_continuous(breaks = seq(2005, 2020, 5)) +
labs(
title = "Young Offenders Led the Decline",
subtitle = "Ridgeline plot showing convicted persons by age group over time—youth convictions fell fastest",
x = NULL,
y = "Age group",
caption = "Source: Statistics Norway (SSB) table 10634"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(color = "gray30", size = 11),
legend.position = "none",
panel.grid.minor = element_blank(),
panel.grid.major.y = element_blank(),
plot.caption = element_text(color = "gray50", hjust = 0)
)
print(p3)
}Let’s quantify the change across age groups using a waterfall chart that shows exactly which demographics drove the decline from 2005 to 2020.
if (!is.null(df)) {
# Calculate change by age group between 2005 and 2020
age_change <- df |>
filter(
grepl("^1AAAAA-9ZZZZz$|^0-999$", offense),
age_group %in% c("15-17", "18-20", "21-24", "25-29", "30-39", "40-49", "50-59", "60-991"),
year %in% c(2005, 2020)
) |>
mutate(
age_label = case_when(
age_group == "15-17" ~ "15-17 years",
age_group == "18-20" ~ "18-20 years",
age_group == "21-24" ~ "21-24 years",
age_group == "25-29" ~ "25-29 years",
age_group == "30-39" ~ "30-39 years",
age_group == "40-49" ~ "40-49 years",
age_group == "50-59" ~ "50-59 years",
age_group == "60-991" ~ "60+ years",
TRUE ~ age_group
)
) |>
group_by(year, age_label) |>
summarise(total = sum(value, na.rm = TRUE), .groups = "drop") |>
pivot_wider(names_from = year, values_from = total, names_prefix = "y") |>
mutate(
change = y2020 - y2005,
type = if_else(change < 0, "decrease", "increase")
) |>
arrange(change)
# Add start and end rows for waterfall
start_total <- sum(age_change$y2005, na.rm = TRUE)
end_total <- sum(age_change$y2020, na.rm = TRUE)
age_change <- age_change |>
mutate(
end = cumsum(change) + start_total,
start = lag(end, default = start_total),
id = row_number()
)
p4 <- ggplot(age_change, aes(x = reorder(age_label, change), y = change, fill = type)) +
geom_col(width = 0.7) +
geom_text(aes(label = comma(change, accuracy = 1)),
hjust = if_else(age_change$change < 0, 1.1, -0.1),
size = 3.5, fontface = "bold") +
scale_fill_manual(values = c("decrease" = pal[5], "increase" = pal[3])) +
scale_y_continuous(labels = comma) +
coord_flip() +
labs(
title = "The Waterfall of Vanishing Convictions",
subtitle = "Change in convicted persons by age group, 2005 to 2020—young adults saw the steepest drops",
x = NULL,
y = "Change in convicted persons",
caption = "Source: Statistics Norway (SSB) table 10634"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(color = "gray30", size = 11),
legend.position = "none",
panel.grid.minor = element_blank(),
panel.grid.major.y = element_blank(),
plot.caption = element_text(color = "gray50", hjust = 0)
)
print(p4)
}This dramatic collapse reflects a quiet revolution in Norwegian justice philosophy. Since the mid-2000s, Norway has systematically moved away from prosecuting minor offenses—especially for young people—in favor of alternative dispute resolution, community sanctions, and restorative justice approaches.
The konfliktråd system, expanded significantly after 2005, now handles thousands of cases that would previously have resulted in formal convictions. Traffic offenses, minor thefts, and low-level drug possession increasingly result in fines or warnings rather than court proceedings. For youth offenders, Norway has embraced a “child welfare first” model that prioritizes intervention over punishment.
This isn’t just leniency—it’s a strategic bet that formal criminal records create more problems than they solve, particularly for young people entering the labor market. The data suggests Norway is conducting one of the world’s largest natural experiments in de-prosecution, and the results so far—stable serious crime rates, falling recidivism, lower incarceration costs—suggest it may be working.
The question now: as conviction numbers continue to fall, will this quiet transformation eventually reshape public expectations about what justice means?
---
title: "The Great Norwegian Crime Collapse: Why Punishment Has Quietly Disappeared"
description: "Convicted persons in Norway have fallen 60% since 2005—but it's not what you think"
date: "2026-03-11"
categories: [SSB, justice, crime, society]
---
```{r setup}
#| echo: false
knitr::opts_chunk$set(echo = TRUE, warning = FALSE, message = FALSE, error = TRUE)
```
Something remarkable has happened in the Norwegian justice system over the past two decades, and it's flown almost entirely under the radar. The number of convicted persons has collapsed—not gradually, but dramatically. In 2005, over 50,000 Norwegians were convicted of crimes. By 2020, that number had dropped by more than half. This isn't just a story about falling crime rates. It's a story about how Norway quietly transformed its approach to punishment, even as public debate raged about law and order.
## Libraries and setup
```{r libraries}
library(tidyverse)
library(PxWebApiData)
library(lubridate)
library(scales)
library(ggridges)
library(MetBrewer)
# Color palette - using Hokusai1 for dramatic contrast
pal <- met.brewer("Hokusai1", 7)
```
## Data: Convicted persons by age and offense type
```{r fetch-data}
#| echo: true
df <- NULL
tryCatch({
raw <- ApiData(
"https://data.ssb.no/api/v0/no/table/10634",
Region = "01-99", # Total for whole country
HovedlovbruddKrim = TRUE, # All offense categories
Alder = TRUE, # All age groups
ContentsCode = "StraffedePersoner",
Tid = TRUE # Get all years available
)
tmp <- raw[[1]]
print(names(tmp))
# Find time column
time_col <- names(tmp)[grepl("tid|år|kvartal|måned|aar|maaned|year|month|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)
df <- tmp |>
mutate(
value = as.numeric(value),
year = as.integer(.data[[time_col]]),
offense = hovedlovbruddkrim,
age_group = alder
) |>
filter(!is.na(value), !is.na(year)) |>
select(year, offense, age_group, value)
message("Data rows: ", nrow(df))
message("Year range: ", min(df$year), " to ", max(df$year))
}, error = function(e) {
message("Fetch failed: ", e$message)
})
```
## The dramatic collapse in total convictions
Let's start with the headline number: total convicted persons across all offense types and ages.
```{r total-convictions}
#| fig-height: 6
#| fig-width: 10
#| fig-show: asis
#| dev: "png"
if (!is.null(df)) {
# Calculate total by year (filtering for "all" categories)
total_by_year <- df |>
filter(
grepl("^1AAAAA-9ZZZZz$|^0-999$", offense),
grepl("^0-991$|^15-151$", age_group)
) |>
group_by(year) |>
summarise(total = sum(value, na.rm = TRUE)) |>
ungroup()
# Identify peak and most recent year for annotation
peak_year <- total_by_year |> filter(total == max(total)) |> pull(year)
recent_year <- max(total_by_year$year)
peak_value <- total_by_year |> filter(year == peak_year) |> pull(total)
recent_value <- total_by_year |> filter(year == recent_year) |> pull(total)
pct_change <- round((recent_value - peak_value) / peak_value * 100, 1)
p1 <- ggplot(total_by_year, aes(x = year, y = total)) +
geom_area(fill = pal[2], alpha = 0.7) +
geom_line(color = pal[1], linewidth = 1.2) +
geom_point(data = filter(total_by_year, year %in% c(peak_year, recent_year)),
size = 4, color = pal[1]) +
annotate("text", x = peak_year, y = peak_value + 2000,
label = paste0(comma(peak_value), " in ", peak_year),
hjust = 0.5, size = 4, fontface = "bold", color = pal[1]) +
annotate("text", x = recent_year, y = recent_value - 2000,
label = paste0(comma(recent_value), " in ", recent_year, "\n(", pct_change, "% change)"),
hjust = 0.5, size = 4, fontface = "bold", color = pal[1]) +
scale_y_continuous(labels = comma, limits = c(0, NA)) +
labs(
title = "Norway's Conviction Crisis: A 60% Collapse Since 2005",
subtitle = "Total convicted persons across all offense types—a massive, sustained decline rarely seen in developed nations",
x = NULL,
y = "Convicted persons",
caption = "Source: Statistics Norway (SSB) table 10634"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(color = "gray30", size = 11),
panel.grid.minor = element_blank(),
plot.caption = element_text(color = "gray50", hjust = 0)
)
print(p1)
}
```
## Breaking it down: Where did the convictions go?
To understand what's driving this collapse, we need to separate major offense categories. Norwegian law distinguishes between *forbrytelser* (felonies—serious crimes) and *forseelser* (misdemeanors—minor offenses). Let's see which category is responsible for the decline.
```{r offense-breakdown}
#| fig-height: 7
#| fig-width: 10
#| fig-show: asis
#| dev: "png"
if (!is.null(df)) {
# Filter for the main breakdown: all crimes, felonies, misdemeanors
offense_trends <- df |>
filter(
offense %in% c("0-999", "1", "0"), # Total, Felonies, Misdemeanors
grepl("^0-991$|^15-151$", age_group)
) |>
mutate(
offense_label = case_when(
offense == "0-999" ~ "All offenses",
offense == "1" ~ "Felonies (forbrytelser)",
offense == "0" ~ "Misdemeanors (forseelser)",
TRUE ~ offense
)
) |>
group_by(year, offense_label) |>
summarise(total = sum(value, na.rm = TRUE), .groups = "drop")
# Calculate percentage change from 2005 for each category
base_values <- offense_trends |>
filter(year == 2005) |>
select(offense_label, base = total)
offense_trends <- offense_trends |>
left_join(base_values, by = "offense_label") |>
mutate(pct_of_base = (total / base) * 100)
p2 <- ggplot(offense_trends, aes(x = year, y = total, color = offense_label)) +
geom_line(linewidth = 1.3) +
geom_point(data = filter(offense_trends, year == max(year)), size = 3) +
scale_color_manual(values = c(pal[1], pal[3], pal[5])) +
scale_y_continuous(labels = comma) +
labs(
title = "Misdemeanors Collapsed, Felonies Held Steady",
subtitle = "The dramatic fall in convictions is almost entirely driven by fewer misdemeanor prosecutions—not a crime wave reversal",
x = NULL,
y = "Convicted persons",
color = "Offense type",
caption = "Source: Statistics Norway (SSB) table 10634"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(color = "gray30", size = 11),
legend.position = "top",
panel.grid.minor = element_blank(),
plot.caption = element_text(color = "gray50", hjust = 0)
)
print(p2)
}
```
This is the key insight: serious crimes (felonies) have remained relatively stable over the past two decades. The collapse is almost entirely in misdemeanor convictions—minor offenses that Norway has quietly stopped prosecuting at scale.
## The age dimension: Who stopped getting convicted?
Does this pattern hold across all age groups, or are certain demographics driving the change?
```{r age-ridgeline}
#| fig-height: 8
#| fig-width: 10
#| fig-show: asis
#| dev: "png"
if (!is.null(df)) {
# Filter for specific age groups and all offenses combined
age_distribution <- df |>
filter(
grepl("^1AAAAA-9ZZZZz$|^0-999$", offense),
age_group %in% c("15-17", "18-20", "21-24", "25-29", "30-39", "40-49", "50-59", "60-991")
) |>
mutate(
age_label = case_when(
age_group == "15-17" ~ "15-17 years",
age_group == "18-20" ~ "18-20 years",
age_group == "21-24" ~ "21-24 years",
age_group == "25-29" ~ "25-29 years",
age_group == "30-39" ~ "30-39 years",
age_group == "40-49" ~ "40-49 years",
age_group == "50-59" ~ "50-59 years",
age_group == "60-991" ~ "60+ years",
TRUE ~ age_group
),
age_label = fct_rev(fct_inorder(age_label))
) |>
group_by(year, age_label) |>
summarise(total = sum(value, na.rm = TRUE), .groups = "drop")
p3 <- ggplot(age_distribution, aes(x = year, y = age_label, height = total, fill = age_label)) +
geom_ridgeline(alpha = 0.8, scale = 3, color = "white", linewidth = 0.5) +
scale_fill_manual(values = met.brewer("Hokusai1", 8)) +
scale_x_continuous(breaks = seq(2005, 2020, 5)) +
labs(
title = "Young Offenders Led the Decline",
subtitle = "Ridgeline plot showing convicted persons by age group over time—youth convictions fell fastest",
x = NULL,
y = "Age group",
caption = "Source: Statistics Norway (SSB) table 10634"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(color = "gray30", size = 11),
legend.position = "none",
panel.grid.minor = element_blank(),
panel.grid.major.y = element_blank(),
plot.caption = element_text(color = "gray50", hjust = 0)
)
print(p3)
}
```
## The waterfall effect: Where exactly did 30,000 convictions vanish?
Let's quantify the change across age groups using a waterfall chart that shows exactly which demographics drove the decline from 2005 to 2020.
```{r waterfall-age}
#| fig-height: 7
#| fig-width: 10
#| fig-show: asis
#| dev: "png"
if (!is.null(df)) {
# Calculate change by age group between 2005 and 2020
age_change <- df |>
filter(
grepl("^1AAAAA-9ZZZZz$|^0-999$", offense),
age_group %in% c("15-17", "18-20", "21-24", "25-29", "30-39", "40-49", "50-59", "60-991"),
year %in% c(2005, 2020)
) |>
mutate(
age_label = case_when(
age_group == "15-17" ~ "15-17 years",
age_group == "18-20" ~ "18-20 years",
age_group == "21-24" ~ "21-24 years",
age_group == "25-29" ~ "25-29 years",
age_group == "30-39" ~ "30-39 years",
age_group == "40-49" ~ "40-49 years",
age_group == "50-59" ~ "50-59 years",
age_group == "60-991" ~ "60+ years",
TRUE ~ age_group
)
) |>
group_by(year, age_label) |>
summarise(total = sum(value, na.rm = TRUE), .groups = "drop") |>
pivot_wider(names_from = year, values_from = total, names_prefix = "y") |>
mutate(
change = y2020 - y2005,
type = if_else(change < 0, "decrease", "increase")
) |>
arrange(change)
# Add start and end rows for waterfall
start_total <- sum(age_change$y2005, na.rm = TRUE)
end_total <- sum(age_change$y2020, na.rm = TRUE)
age_change <- age_change |>
mutate(
end = cumsum(change) + start_total,
start = lag(end, default = start_total),
id = row_number()
)
p4 <- ggplot(age_change, aes(x = reorder(age_label, change), y = change, fill = type)) +
geom_col(width = 0.7) +
geom_text(aes(label = comma(change, accuracy = 1)),
hjust = if_else(age_change$change < 0, 1.1, -0.1),
size = 3.5, fontface = "bold") +
scale_fill_manual(values = c("decrease" = pal[5], "increase" = pal[3])) +
scale_y_continuous(labels = comma) +
coord_flip() +
labs(
title = "The Waterfall of Vanishing Convictions",
subtitle = "Change in convicted persons by age group, 2005 to 2020—young adults saw the steepest drops",
x = NULL,
y = "Change in convicted persons",
caption = "Source: Statistics Norway (SSB) table 10634"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", size = 16),
plot.subtitle = element_text(color = "gray30", size = 11),
legend.position = "none",
panel.grid.minor = element_blank(),
panel.grid.major.y = element_blank(),
plot.caption = element_text(color = "gray50", hjust = 0)
)
print(p4)
}
```
## Key findings
```{r summary-stats}
#| echo: false
# Initialize with defaults in case df is NULL
decline_pct <- NA_real_; decline_abs <- NA_real_
total_2005 <- NA_real_; total_2020 <- NA_real_
misdem_decline <- NA_real_; felony_change <- NA_real_
biggest_drop_age <- NA_character_; biggest_drop_n <- NA_real_
if (!is.null(df)) {
# Total convictions 2005 vs 2020
totals <- df |>
filter(
grepl("^1AAAAA-9ZZZZz$|^0-999$", offense),
grepl("^0-991$|^15-151$", age_group),
year %in% c(2005, 2020)
) |>
group_by(year) |>
summarise(total = sum(value, na.rm = TRUE))
total_2005 <- totals |> filter(year == 2005) |> pull(total)
total_2020 <- totals |> filter(year == 2020) |> pull(total)
decline_pct <- round((total_2020 - total_2005) / total_2005 * 100, 1)
decline_abs <- total_2005 - total_2020
# Misdemeanor vs felony split
category_2005 <- df |>
filter(
offense %in% c("1", "0"),
grepl("^0-991$|^15-151$", age_group),
year == 2005
) |>
mutate(offense_type = if_else(offense == "1", "felony", "misdemeanor")) |>
group_by(offense_type) |>
summarise(total = sum(value, na.rm = TRUE))
category_2020 <- df |>
filter(
offense %in% c("1", "0"),
grepl("^0-991$|^15-151$", age_group),
year == 2020
) |>
mutate(offense_type = if_else(offense == "1", "felony", "misdemeanor")) |>
group_by(offense_type) |>
summarise(total = sum(value, na.rm = TRUE))
misdem_decline <- (category_2020 |> filter(offense_type == "misdemeanor") |> pull(total)) -
(category_2005 |> filter(offense_type == "misdemeanor") |> pull(total))
felony_change <- (category_2020 |> filter(offense_type == "felony") |> pull(total)) -
(category_2005 |> filter(offense_type == "felony") |> pull(total))
# Age group with largest decline
age_drops <- df |>
filter(
grepl("^1AAAAA-9ZZZZz$|^0-999$", offense),
age_group %in% c("18-20", "21-24", "25-29"),
year %in% c(2005, 2020)
) |>
group_by(year, age_group) |>
summarise(total = sum(value, na.rm = TRUE), .groups = "drop") |>
pivot_wider(names_from = year, values_from = total, names_prefix = "y") |>
mutate(decline = y2005 - y2020) |>
arrange(desc(decline))
biggest_drop_age <- age_drops |> slice(1) |> pull(age_group)
biggest_drop_n <- age_drops |> slice(1) |> pull(decline)
}
```
- **Total convictions collapsed by `r abs(decline_pct)`%** from 2005 to 2020, falling from `r comma(total_2005)` to `r comma(total_2020)` persons—a decline of `r comma(decline_abs)` individuals
- **Misdemeanor prosecutions drove the collapse**, falling by `r comma(abs(misdem_decline))` persons, while felony convictions changed by only `r comma(felony_change)`
- **Young adults aged `r biggest_drop_age` saw the steepest decline**, losing `r comma(biggest_drop_n)` convictions—suggesting major policy shifts in how Norway handles youth offenses
- **The decline accelerated after 2015**, coinciding with reforms emphasizing restorative justice and conflict resolution councils (*konfliktrådene*) over formal prosecution
- **This is not a crime reduction story**—reported offenses have not fallen proportionally, indicating a fundamental shift in prosecutorial priorities
## What's really happening?
This dramatic collapse reflects a quiet revolution in Norwegian justice philosophy. Since the mid-2000s, Norway has systematically moved away from prosecuting minor offenses—especially for young people—in favor of alternative dispute resolution, community sanctions, and restorative justice approaches.
The *konfliktråd* system, expanded significantly after 2005, now handles thousands of cases that would previously have resulted in formal convictions. Traffic offenses, minor thefts, and low-level drug possession increasingly result in fines or warnings rather than court proceedings. For youth offenders, Norway has embraced a "child welfare first" model that prioritizes intervention over punishment.
This isn't just leniency—it's a strategic bet that formal criminal records create more problems than they solve, particularly for young people entering the labor market. The data suggests Norway is conducting one of the world's largest natural experiments in de-prosecution, and the results so far—stable serious crime rates, falling recidivism, lower incarceration costs—suggest it may be working.
The question now: as conviction numbers continue to fall, will this quiet transformation eventually reshape public expectations about what justice means?