adding missing data, adding preliminary exploration
This commit is contained in:
parent
9ca2f78396
commit
1631adfb02
17 changed files with 1488 additions and 14 deletions
BIN
RQ_municipalities.pdf
Normal file
BIN
RQ_municipalities.pdf
Normal file
Binary file not shown.
545
RQ_municipalities.qmd
Normal file
545
RQ_municipalities.qmd
Normal file
|
|
@ -0,0 +1,545 @@
|
||||||
|
---
|
||||||
|
title: "Educational offer and attainment across Swedish municipalities"
|
||||||
|
subtitle: "Preliminary exploration for QRM"
|
||||||
|
date: today
|
||||||
|
format:
|
||||||
|
pdf:
|
||||||
|
toc: true
|
||||||
|
number-sections: true
|
||||||
|
fig-pos: "H"
|
||||||
|
geometry: margin=2.5cm
|
||||||
|
fontsize: 11pt
|
||||||
|
execute:
|
||||||
|
echo: false
|
||||||
|
warning: false
|
||||||
|
message: false
|
||||||
|
---
|
||||||
|
|
||||||
|
```{r setup}
|
||||||
|
library(tidyverse)
|
||||||
|
library(FactoMineR)
|
||||||
|
library(ggrepel)
|
||||||
|
library(knitr)
|
||||||
|
library(kableExtra)
|
||||||
|
library(showtext)
|
||||||
|
|
||||||
|
font_add_google("Source Sans 3", "source_sans_3")
|
||||||
|
showtext_auto()
|
||||||
|
|
||||||
|
# 01 - Load preprocessed data -----------------------------------------------------
|
||||||
|
m_sample <- read_rds("data/processed/m_sample.rds")
|
||||||
|
ca <- read_rds("data/processed/ca_exploratory.rds")
|
||||||
|
var_groups <- read_rds("data/processed/ca_var_groups.rds")
|
||||||
|
edu_offer <- read_rds("data/processed/edu_offer.rds")
|
||||||
|
att_summary <- read_rds("data/processed/attainment_summary.rds")
|
||||||
|
|
||||||
|
# 02 - County lookup --------------------------------------------------------------
|
||||||
|
county_names <- c(
|
||||||
|
"01" = "Stockholm", "03" = "Uppsala", "04" = "Södermanland",
|
||||||
|
"05" = "Östergötland", "06" = "Jönköping", "07" = "Kronoberg",
|
||||||
|
"08" = "Kalmar", "09" = "Gotland", "10" = "Blekinge",
|
||||||
|
"12" = "Skåne", "13" = "Halland", "14" = "Västra Götaland",
|
||||||
|
"17" = "Värmland", "18" = "Örebro", "19" = "Västmanland",
|
||||||
|
"20" = "Dalarna", "21" = "Gävleborg", "22" = "Västernorrland",
|
||||||
|
"23" = "Jämtland", "24" = "Västerbotten", "25" = "Norrbotten"
|
||||||
|
)
|
||||||
|
|
||||||
|
m_sample <- m_sample |>
|
||||||
|
mutate(county = county_names[str_sub(code, 1, 2)])
|
||||||
|
|
||||||
|
edu_offer <- edu_offer |>
|
||||||
|
mutate(county = county_names[str_sub(code, 1, 2)])
|
||||||
|
|
||||||
|
att_summary <- att_summary |>
|
||||||
|
left_join(m_sample |> select(code, municipality, county), by = "code")
|
||||||
|
|
||||||
|
# 03 - Key municipalities for labels ----------------------------------------------
|
||||||
|
key_munis <- c("Stockholm", "Göteborg", "Malmö", "Uppsala", "Umeå",
|
||||||
|
"Lund", "Linköping", "Kiruna", "Danderyd", "Filipstad")
|
||||||
|
```
|
||||||
|
|
||||||
|
# RQ1: Educational attainment over time and its relationship to educational offer
|
||||||
|
|
||||||
|
Educational attainment varies substantially across Swedish municipalities and has risen throughout this century. The pace of change is uneven. Urban and university municipalities have consistently outpaced smaller, industrial, or peripheral ones, widening the educational gap in the population. The following analyses draw on register data from Statistics Sweden for the population aged 25--64 in all 290 municipalities, at five time-points (2000, 2005, 2010, 2015, 2022). educational offer is measured from the 2024 school-unit registry and the UKÄ register.
|
||||||
|
|
||||||
|
## Trends in post-secondary attainment
|
||||||
|
|
||||||
|
```{r fig-rq1-trends, fig.width=8, fig.height=5.5, fig.cap="Share of 25--64 year olds with post-secondary education (ISCED 5+), all 290 municipalities, 2000--2022. Grey lines = individual municipalities; thick blue line = national median; shaded band = interquartile range. Selected municipalities labelled."}
|
||||||
|
# National quartiles per year
|
||||||
|
nat_stats <- att_summary |>
|
||||||
|
group_by(year) |>
|
||||||
|
summarise(
|
||||||
|
med = median(pct_postsec, na.rm = TRUE),
|
||||||
|
q25 = quantile(pct_postsec, 0.25, na.rm = TRUE),
|
||||||
|
q75 = quantile(pct_postsec, 0.75, na.rm = TRUE)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Labels at endpoint (2022)
|
||||||
|
label_df <- att_summary |>
|
||||||
|
filter(year == 2022, municipality %in% key_munis)
|
||||||
|
|
||||||
|
ggplot() +
|
||||||
|
geom_line(
|
||||||
|
data = att_summary,
|
||||||
|
aes(x = year, y = pct_postsec, group = municipality),
|
||||||
|
colour = "grey72", linewidth = 0.3, alpha = 0.6
|
||||||
|
) +
|
||||||
|
geom_ribbon(
|
||||||
|
data = nat_stats, aes(x = year, ymin = q25, ymax = q75),
|
||||||
|
fill = "#2171b5", alpha = 0.15
|
||||||
|
) +
|
||||||
|
geom_line(
|
||||||
|
data = nat_stats, aes(x = year, y = med),
|
||||||
|
colour = "#2171b5", linewidth = 1.1
|
||||||
|
) +
|
||||||
|
geom_line(
|
||||||
|
data = filter(att_summary, municipality %in% key_munis),
|
||||||
|
aes(x = year, y = pct_postsec, group = municipality),
|
||||||
|
colour = "#cb181d", linewidth = 0.55
|
||||||
|
) +
|
||||||
|
geom_text_repel(
|
||||||
|
data = label_df,
|
||||||
|
aes(x = year, y = pct_postsec, label = municipality),
|
||||||
|
direction = "y",
|
||||||
|
nudge_x = 1.5,
|
||||||
|
segment.size = 0.3,
|
||||||
|
size = 2.8,
|
||||||
|
hjust = 0
|
||||||
|
) +
|
||||||
|
scale_x_continuous(
|
||||||
|
breaks = c(2000, 2005, 2010, 2015, 2022),
|
||||||
|
limits = c(2000, 2029)
|
||||||
|
) +
|
||||||
|
labs(
|
||||||
|
x = NULL, y = "% with post-secondary education (ISCED 5+)",
|
||||||
|
caption = "Source: SCB UF0506B. Blue line = national median; shaded = IQR."
|
||||||
|
) +
|
||||||
|
theme_minimal(base_size = 10, base_family = "source_sans_3") +
|
||||||
|
theme(panel.grid.minor = element_blank())
|
||||||
|
```
|
||||||
|
|
||||||
|
The national median post-secondary share rose from `r round(filter(nat_stats, year==2000)$med, 1)`% in 2000 to `r round(filter(nat_stats, year==2022)$med, 1)`% in 2022. The interquartile range widened over the same period, reflecting growing polarisation between knowledge-economy and peripheral municipalities. Danderyd, Lund, and Solna consistently lead; small industrial municipalities in Värmland, Bergslagen, and Norrland remain clustered at the bottom.
|
||||||
|
|
||||||
|
## Attainment and educational offer
|
||||||
|
|
||||||
|
```{r fig-rq1-scatter, fig.width=7.5, fig.height=5, fig.cap="2022 post-secondary attainment vs. number of upper-secondary units, by type of higher education presence. Point size = number of komvux units. Municipalities with no gymnasieskola (n=39) omitted. Selected municipalities labelled."}
|
||||||
|
att_22 <- att_summary |>
|
||||||
|
filter(year == 2022) |>
|
||||||
|
left_join(
|
||||||
|
edu_offer |> select(code, gymnasieskola_n_total, komvux_n_total,
|
||||||
|
university_n_total, university_college_n_total),
|
||||||
|
by = "code"
|
||||||
|
) |>
|
||||||
|
mutate(
|
||||||
|
he_cat = case_when(
|
||||||
|
university_n_total > 0 ~ "University",
|
||||||
|
university_college_n_total > 0 ~ "University college",
|
||||||
|
TRUE ~ "No HE institution"
|
||||||
|
) |> factor(levels = c("No HE institution", "University college", "University"))
|
||||||
|
) |>
|
||||||
|
filter(gymnasieskola_n_total > 0)
|
||||||
|
|
||||||
|
label_22 <- filter(att_22, municipality %in% key_munis)
|
||||||
|
|
||||||
|
ggplot(att_22, aes(x = gymnasieskola_n_total, y = pct_postsec, colour = he_cat)) +
|
||||||
|
geom_point(aes(size = komvux_n_total), alpha = 0.6) +
|
||||||
|
geom_text_repel(
|
||||||
|
data = label_22,
|
||||||
|
aes(label = municipality),
|
||||||
|
size = 2.8, max.overlaps = 15, show.legend = FALSE
|
||||||
|
) +
|
||||||
|
scale_colour_manual(
|
||||||
|
values = c("No HE institution" = "grey60",
|
||||||
|
"University college" = "#fd8d3c",
|
||||||
|
"University" = "#cb181d"),
|
||||||
|
name = NULL
|
||||||
|
) +
|
||||||
|
scale_size_continuous(name = "Komvux units", range = c(1, 6)) +
|
||||||
|
scale_x_log10(breaks = c(1, 2, 5, 10, 20, 50, 100)) +
|
||||||
|
labs(
|
||||||
|
x = "Number of gymnasieskola units (log scale)",
|
||||||
|
y = "% with post-secondary education (2022)",
|
||||||
|
caption = "Point size = number of komvux units."
|
||||||
|
) +
|
||||||
|
theme_minimal(base_size = 10, base_family = "source_sans_3") +
|
||||||
|
theme(legend.position = "bottom", panel.grid.minor = element_blank())
|
||||||
|
```
|
||||||
|
|
||||||
|
Municipalities with a university consistently show higher post-secondary attainment than those without, regardless of overall size. The relationship extends to komvux. Municipalities with a larger adult education offer tend to have higher attainment levels. These associations suggest a long-term structural co-constitution of educational offer and educated populations rather than a simple effect in one direction.
|
||||||
|
|
||||||
|
# RQ2: Distribution of educational offer across municipalities
|
||||||
|
|
||||||
|
The supply of education varies substantially by level, institution type, and geography. The following describes the landscape of educational offer using the Skolverket school-unit registry and the UKÄ register of higher education institutions. Both reflect the state of educational offer at the time of analysis; the CA in Section 3 uses 2022 population data, and the school registry is a close but not identical snapshot. Two institution types remain outside the current dataset: *yrkeshögskola* (vocational higher education; source MYH) and *folkhögskola* (folk high schools; source Folkbildningsrådet).
|
||||||
|
|
||||||
|
## Coverage by institution type
|
||||||
|
|
||||||
|
```{r tbl-coverage}
|
||||||
|
#| tbl-cap: "educational offer by institution type across 290 Swedish municipalities."
|
||||||
|
offer_vars <- tribble(
|
||||||
|
~var, ~label,
|
||||||
|
"forskoleklass", "Förskoleklass",
|
||||||
|
"grundskola", "Grundskola",
|
||||||
|
"anpassad_grundskola", "Anpassad grundskola",
|
||||||
|
"gymnasieskola", "Gymnasieskola",
|
||||||
|
"komvux", "Komvux",
|
||||||
|
"sfi", "SFI (within komvux)",
|
||||||
|
"anpassad_gymnasieskola", "Anpassad gymnasieskola",
|
||||||
|
"specialskola", "Specialskola",
|
||||||
|
"sameskola", "Sameskola",
|
||||||
|
"university_college", "University college",
|
||||||
|
"university", "University"
|
||||||
|
)
|
||||||
|
|
||||||
|
coverage_tbl <- offer_vars |>
|
||||||
|
mutate(
|
||||||
|
n_tot = map_int(var, \(v) sum(edu_offer[[paste0(v,"_n_total")]] > 0, na.rm=TRUE)),
|
||||||
|
pct = round(100 * n_tot / 290, 0),
|
||||||
|
med_n = map_dbl(var, \(v) {
|
||||||
|
x <- edu_offer[[paste0(v,"_n_total")]]; median(x[x>0], na.rm=TRUE)
|
||||||
|
}),
|
||||||
|
max_n = map_int(var, \(v) max(edu_offer[[paste0(v,"_n_total")]], na.rm=TRUE)),
|
||||||
|
pct_priv = map_dbl(var, \(v) {
|
||||||
|
tot <- sum(edu_offer[[paste0(v,"_n_total")]], na.rm=TRUE)
|
||||||
|
priv <- sum(edu_offer[[paste0(v,"_n_private")]], na.rm=TRUE)
|
||||||
|
if(tot==0 || is.na(priv)) return(NA_real_)
|
||||||
|
round(100*priv/tot, 1)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
coverage_tbl |>
|
||||||
|
transmute(
|
||||||
|
`Institution type` = label,
|
||||||
|
`Municipalities (n)` = n_tot,
|
||||||
|
`Coverage (%)` = pct,
|
||||||
|
`Median units*` = round(med_n, 1),
|
||||||
|
`Max units` = max_n,
|
||||||
|
`Private share (%)` = pct_priv
|
||||||
|
) |>
|
||||||
|
kbl(
|
||||||
|
booktabs = TRUE, linesep = ""
|
||||||
|
) |>
|
||||||
|
kable_styling(font_size = 9, latex_options = c("hold_position", "scale_down")) |>
|
||||||
|
footnote(
|
||||||
|
general = "Median units computed among municipalities with at least one unit. Yrkeshögskola and folkhögskola not included (data pending).",
|
||||||
|
general_title = "Note:",
|
||||||
|
footnote_as_chunk = TRUE
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Primary and lower secondary education is effectively universal. Every municipality has both förskoleklass and grundskola units. Gymnasieskola is present in 87% of municipalities; the remaining 13% (mostly sparsely populated northern or island municipalities) rely on inter-municipal agreements. Komvux covers 90% and SFI 87% of municipalities. Higher education is concentrated with only 11 municipalities hosting a university and 20 hosting a university college, together covering 10% of all municipalities.
|
||||||
|
|
||||||
|
## Regional distribution
|
||||||
|
|
||||||
|
```{r fig-rq2-region, fig.width=9, fig.height=6, fig.cap="educational offer coverage (\\% of municipalities with at least one unit) by county and selected institution types. Counties ordered from south to north."}
|
||||||
|
county_order <- c(
|
||||||
|
"Skåne", "Blekinge", "Halland", "Kronoberg", "Kalmar", "Gotland",
|
||||||
|
"Jönköping", "Östergötland", "Södermanland", "Västra Götaland",
|
||||||
|
"Örebro", "Västmanland", "Stockholm", "Uppsala", "Dalarna",
|
||||||
|
"Värmland", "Gävleborg", "Västernorrland", "Jämtland",
|
||||||
|
"Västerbotten", "Norrbotten"
|
||||||
|
)
|
||||||
|
|
||||||
|
sel_types <- tribble(
|
||||||
|
~var, ~label,
|
||||||
|
"gymnasieskola", "Gymnasieskola",
|
||||||
|
"komvux", "Komvux",
|
||||||
|
"sfi", "SFI",
|
||||||
|
"university_college","University college",
|
||||||
|
"university", "University"
|
||||||
|
)
|
||||||
|
|
||||||
|
county_coverage <- edu_offer |>
|
||||||
|
group_by(county) |>
|
||||||
|
summarise(
|
||||||
|
across(
|
||||||
|
all_of(paste0(sel_types$var, "_n_total")),
|
||||||
|
\(x) 100 * mean(x > 0, na.rm = TRUE)
|
||||||
|
),
|
||||||
|
n_munis = n()
|
||||||
|
) |>
|
||||||
|
pivot_longer(
|
||||||
|
-c(county, n_munis),
|
||||||
|
names_to = "var",
|
||||||
|
values_to = "pct_with"
|
||||||
|
) |>
|
||||||
|
mutate(var = str_remove(var, "_n_total")) |>
|
||||||
|
left_join(sel_types, by = "var") |>
|
||||||
|
mutate(county = factor(county, levels = county_order))
|
||||||
|
|
||||||
|
ggplot(county_coverage, aes(x = county, y = pct_with, fill = pct_with)) +
|
||||||
|
geom_col() +
|
||||||
|
geom_text(aes(label = round(pct_with), y = pmax(pct_with - 5, 3)),
|
||||||
|
size = 2.2, colour = "white", fontface = "bold") +
|
||||||
|
facet_wrap(~label, nrow = 2) +
|
||||||
|
scale_fill_distiller(palette = "YlOrRd", direction = 1, guide = "none") +
|
||||||
|
scale_y_continuous(labels = \(x) paste0(x,"%"), limits = c(0,105)) +
|
||||||
|
labs(x = NULL, y = "% of municipalities with ≥1 unit") +
|
||||||
|
theme_minimal(base_size = 9, base_family = "source_sans_3") +
|
||||||
|
theme(
|
||||||
|
axis.text.x = element_text(angle = 45, hjust = 1, size = 7),
|
||||||
|
panel.grid.major.x = element_blank(),
|
||||||
|
strip.text = element_text(face = "bold")
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Gymnasieskola coverage approaches 100% in most counties; gaps appear in sparsely populated northern counties (Jämtland, Norrbotten). SFI and komvux gaps are more pronounced in low-density counties. Higher education—universities and university colleges—is geographically clustered with Stockholm, Gothenburg, and Malmö counties accounting for the majority of institutions, while large parts of northern Sweden and smaller coastal counties have no HE institution within their borders.
|
||||||
|
|
||||||
|
## Public and private provision
|
||||||
|
|
||||||
|
```{r fig-rq2-private, fig.width=8, fig.height=4, fig.cap="Private provision by institution type. Left: share of municipalities with at least one private unit. Right: national aggregate private share (private units as \\% of all units of that type). HE institutions excluded (all classified as public)."}
|
||||||
|
priv_types <- offer_vars |>
|
||||||
|
filter(!var %in% c("university", "university_college", "specialskola", "sameskola"))
|
||||||
|
|
||||||
|
priv_df <- priv_types |>
|
||||||
|
mutate(
|
||||||
|
pct_munis_with_private = map_dbl(var, \(v) {
|
||||||
|
p <- edu_offer[[paste0(v, "_n_private")]]
|
||||||
|
100 * mean(p > 0, na.rm = TRUE)
|
||||||
|
}),
|
||||||
|
pct_national = map_dbl(var, \(v) {
|
||||||
|
tot <- sum(edu_offer[[paste0(v, "_n_total")]], na.rm = TRUE)
|
||||||
|
priv <- sum(edu_offer[[paste0(v, "_n_private")]], na.rm = TRUE)
|
||||||
|
if (tot == 0) return(NA_real_)
|
||||||
|
100 * priv / tot
|
||||||
|
})
|
||||||
|
) |>
|
||||||
|
filter(!is.na(pct_national)) |>
|
||||||
|
mutate(label = fct_reorder(label, pct_national))
|
||||||
|
|
||||||
|
p_left <- ggplot(priv_df, aes(y = label, x = pct_munis_with_private)) +
|
||||||
|
geom_col(fill = "#6baed6") +
|
||||||
|
geom_text(aes(label = paste0(round(pct_munis_with_private), "%")),
|
||||||
|
hjust = -0.15, size = 3) +
|
||||||
|
scale_x_continuous(limits = c(0, 75), labels = \(x) paste0(x, "%")) +
|
||||||
|
labs(x = "% of municipalities", y = NULL,
|
||||||
|
title = "Municipalities with ≥1 private unit") +
|
||||||
|
theme_minimal(base_size = 9, base_family = "source_sans_3") +
|
||||||
|
theme(panel.grid.minor = element_blank(),
|
||||||
|
plot.title = element_text(size = 9, face = "bold"))
|
||||||
|
|
||||||
|
p_right <- ggplot(priv_df, aes(y = label, x = pct_national)) +
|
||||||
|
geom_col(fill = "#cb181d") +
|
||||||
|
geom_text(aes(label = paste0(round(pct_national, 1), "%")),
|
||||||
|
hjust = -0.15, size = 3) +
|
||||||
|
scale_x_continuous(limits = c(0, 50), labels = \(x) paste0(x, "%")) +
|
||||||
|
labs(x = "% of all units (national)", y = NULL,
|
||||||
|
title = "National private share") +
|
||||||
|
theme_minimal(base_size = 9, base_family = "source_sans_3") +
|
||||||
|
theme(panel.grid.minor = element_blank(), axis.text.y = element_blank(),
|
||||||
|
plot.title = element_text(size = 9, face = "bold"))
|
||||||
|
|
||||||
|
library(patchwork)
|
||||||
|
p_left + p_right
|
||||||
|
```
|
||||||
|
|
||||||
|
Private provision is most common in upper secondary (gymnasieskola) and basic primary (grundskola). The two figures show different aspects of the same phenomenon. At the national level, 35% of all gymnasieskola units and 18% of all grundskola units are privately run. At the municipal level, however, the majority of municipalities have no private gymnasieskola at all: private upper-secondary schools are heavily concentrated in a handful of larger municipalities, particularly in the Stockholm region. Anpassad utbildning and adult education (komvux, SFI) remain almost exclusively municipal.
|
||||||
|
|
||||||
|
# RQ3: Educational assets in the CA space and their relationship to educational offer
|
||||||
|
|
||||||
|
Correspondence analysis (CA) is applied to a contingency table in which the 290 municipalities are rows and population-composition variables are columns—all measured as resident counts: age structure (11 groups), educational attainment of Swedish-born men and women (4 levels each), employment by activity sector, and birth country (Swedish-born vs. foreign-born). Two municipalities are close when their resident profiles are similar, regardless of size. Educational offer variables enter the analysis as *supplementary* columns. This section first describes the CA axes, then examines how educational offer relates to them.
|
||||||
|
|
||||||
|
## CA structure: eigenvalues and active variables
|
||||||
|
|
||||||
|
```{r tbl-ca-eig}
|
||||||
|
#| tbl-cap: "CA eigenvalues for the first seven dimensions."
|
||||||
|
eig_df <- as.data.frame(ca$eig[1:7, ]) |>
|
||||||
|
rownames_to_column("Dimension") |>
|
||||||
|
mutate(Dimension = str_replace(Dimension, "^dim ", "Dim ")) |>
|
||||||
|
transmute(
|
||||||
|
Dimension,
|
||||||
|
Eigenvalue = round(eigenvalue, 4),
|
||||||
|
`% variance` = round(`percentage of variance`, 1),
|
||||||
|
`Cumulative %` = round(`cumulative percentage of variance`, 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
kbl(eig_df, booktabs = TRUE, linesep = "") |>
|
||||||
|
kable_styling(font_size = 9, latex_options = "hold_position")
|
||||||
|
```
|
||||||
|
|
||||||
|
```{r tbl-ca-active}
|
||||||
|
#| tbl-cap: !expr paste0("Active variables in the CA (*n* = ", length(var_groups$active), "). All measure counts of residents.")
|
||||||
|
domain_labels <- c(
|
||||||
|
"Age" = "Age structure (11 groups)",
|
||||||
|
"Edu (men)" = "Education level, Swedish-born men (4 levels)",
|
||||||
|
"Edu (women)" = "Education level, Swedish-born women (4 levels)",
|
||||||
|
"Empl. sector" = "Employment by activity sector",
|
||||||
|
"Birth country"= "Birth country (Sweden-born vs. foreign-born)"
|
||||||
|
)
|
||||||
|
|
||||||
|
active_counts <- tibble(
|
||||||
|
Domain = names(domain_labels),
|
||||||
|
Description = unname(domain_labels),
|
||||||
|
`Variables (n)` = c(
|
||||||
|
sum(str_detect(var_groups$active, "^age_")),
|
||||||
|
sum(str_detect(var_groups$active, "^education_level_of_swedish_men_")),
|
||||||
|
sum(str_detect(var_groups$active, "^education_level_of_swedish_women_")),
|
||||||
|
sum(str_detect(var_groups$active, "^employment_by_activity_sectors_")),
|
||||||
|
sum(str_detect(var_groups$active, "^birth_country_"))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
kbl(active_counts, booktabs = TRUE, linesep = "") |>
|
||||||
|
kable_styling(font_size = 9, latex_options = "hold_position")
|
||||||
|
```
|
||||||
|
|
||||||
|
```{r tbl-ca-contrib}
|
||||||
|
#| tbl-cap: "Top six active-variable contributions (%) to Dimensions 1--3."
|
||||||
|
contrib_df <- ca$col$contrib |>
|
||||||
|
as.data.frame() |>
|
||||||
|
rownames_to_column("variable")
|
||||||
|
|
||||||
|
top_contrib <- map_dfr(1:3, \(d) {
|
||||||
|
col <- paste0("Dim ", d)
|
||||||
|
contrib_df |>
|
||||||
|
slice_max(.data[[col]], n = 6) |>
|
||||||
|
transmute(
|
||||||
|
Dimension = paste0("Dim ", d),
|
||||||
|
Variable = variable,
|
||||||
|
`Contribution (%)` = round(.data[[col]], 1)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
top_contrib |>
|
||||||
|
mutate(Variable = Variable |>
|
||||||
|
str_replace("^education_level_of_swedish_men_", "Edu men: ") |>
|
||||||
|
str_replace("^education_level_of_swedish_women_", "Edu women: ") |>
|
||||||
|
str_replace("^employment_by_activity_sectors_", "Sector: ") |>
|
||||||
|
str_replace("^birth_country_", "Birth country: ") |>
|
||||||
|
str_replace("^age_", "Age: ") |>
|
||||||
|
str_replace_all("_", " ")
|
||||||
|
) |>
|
||||||
|
kbl(booktabs = TRUE, linesep = "") |>
|
||||||
|
kable_styling(font_size = 9, latex_options = c("hold_position", "scale_down")) |>
|
||||||
|
collapse_rows(columns = 1, latex_hline = "major")
|
||||||
|
```
|
||||||
|
|
||||||
|
The first two dimensions together account for `r round(sum(ca$eig[1:2, "percentage of variance"]), 1)`% of total inertia. Dimension 1 (`r round(ca$eig[1, "percentage of variance"], 1)`%) is the dominant axis capturing a rural--urban opposition. Its inertia is distributed across five variable themes: the foreign-born share (16.4%), post-secondary attainment among Swedish-born men and women (11.2% and 6.1%), and a sectoral contrast between personal and cultural services on one side and industrial and agricultural employment on the other (8.4%, 8.0%, 6.1%). The placement of immigration and education on the same axis suggest that in Sweden, both concentrations are features of the same metropolitan geography. At the positive pole (right) sit Stockholm, Göteborg, Malmö, and university towns such as Lund and Uppsala; at the negative pole (left) sit small, peripheral municipalities.
|
||||||
|
|
||||||
|
Dimension 2 (`r round(ca$eig[2, "percentage of variance"], 1)`%) identifies a secondary opposition that is structurally distinct from Dimension 1. Here, birth-country alone contributes 48.6% of the dimension's inertia, dwarfing all other variables. This axis separates suburban municipalities with an exceptionally high concentration of foreign-born residents like Södertälje and Botkyrka from both metropolitan education centres and rural municipalities.
|
||||||
|
|
||||||
|
Dimension 3 (`r round(ca$eig[3, "percentage of variance"], 1)`%) represents a third opposition. Mining and manufacturing employment contributes 34.0% of its inertia, and a young adult age profile adds 16.3%. This axis picks out resource-extraction municipalities like Kiruna, Gällivare, and similar northern towns, whose population structure is shaped by industrial labour demand rather than either urban services or immigration patterns.
|
||||||
|
|
||||||
|
## Correlations between CA dimensions and educational offer
|
||||||
|
|
||||||
|
```{r fig-rq3-heatmap, fig.width=8, fig.height=5.5, fig.cap="Pearson correlations between CA row scores (Dimensions 1--3) and educational offer indicators. Only variables with $|r| \\geq 0.15$ on at least one dimension are shown. Labels printed where $|r| \\geq 0.25$."}
|
||||||
|
ca_coords <- as.data.frame(ca$row$coord) |>
|
||||||
|
rownames_to_column("municipality") |>
|
||||||
|
rename_with(\(x) str_replace(x, "^Dim ", "Dim_"), starts_with("Dim"))
|
||||||
|
|
||||||
|
offer_num <- edu_offer |>
|
||||||
|
select(municipality, ends_with("_n_total")) |>
|
||||||
|
rename_with(\(x) str_remove(x, "_n_total"), ends_with("_n_total"))
|
||||||
|
|
||||||
|
offer_joined <- ca_coords |>
|
||||||
|
left_join(offer_num, by = "municipality")
|
||||||
|
|
||||||
|
offer_vars_sel <- names(offer_num)[-1]
|
||||||
|
|
||||||
|
# Use case_match for exact lookup — avoids substring collision in str_replace chain
|
||||||
|
clean_label <- function(v) {
|
||||||
|
case_match(v,
|
||||||
|
"forskoleklass" ~ "Förskoleklass",
|
||||||
|
"grundskola" ~ "Grundskola",
|
||||||
|
"anpassad_grundskola" ~ "Anp. grundskola",
|
||||||
|
"anpassad_gymnasieskola" ~ "Anp. gymnasieskola",
|
||||||
|
"gymnasieskola" ~ "Gymnasieskola",
|
||||||
|
"komvux" ~ "Komvux",
|
||||||
|
"sfi" ~ "SFI",
|
||||||
|
"specialskola" ~ "Specialskola",
|
||||||
|
"sameskola" ~ "Sameskola",
|
||||||
|
"university_college" ~ "University college",
|
||||||
|
"university" ~ "University",
|
||||||
|
.default = v
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
cor_mat <- cor(
|
||||||
|
offer_joined[, paste0("Dim_", 1:3)],
|
||||||
|
offer_joined[, offer_vars_sel],
|
||||||
|
use = "pairwise.complete.obs"
|
||||||
|
)
|
||||||
|
|
||||||
|
cor_long <- as.data.frame(cor_mat) |>
|
||||||
|
rownames_to_column("dimension") |>
|
||||||
|
pivot_longer(-dimension, names_to = "variable", values_to = "r") |>
|
||||||
|
filter(!is.na(r))
|
||||||
|
|
||||||
|
keep_vars <- cor_long |>
|
||||||
|
group_by(variable) |>
|
||||||
|
summarise(max_r = max(abs(r))) |>
|
||||||
|
filter(max_r >= 0.15) |>
|
||||||
|
pull(variable)
|
||||||
|
|
||||||
|
plot_df <- cor_long |>
|
||||||
|
filter(variable %in% keep_vars) |>
|
||||||
|
mutate(
|
||||||
|
label = clean_label(variable),
|
||||||
|
dimension = str_replace(dimension, "_", " ")
|
||||||
|
)
|
||||||
|
|
||||||
|
ggplot(plot_df, aes(x = dimension, y = reorder(label, r), fill = r)) +
|
||||||
|
geom_tile(colour = "white", linewidth = 0.4) +
|
||||||
|
geom_text(
|
||||||
|
data = filter(plot_df, abs(r) >= 0.25),
|
||||||
|
aes(label = sprintf("%.2f", r)),
|
||||||
|
size = 3
|
||||||
|
) +
|
||||||
|
scale_fill_gradient2(
|
||||||
|
low = "#b2182b", mid = "white", high = "#2166ac",
|
||||||
|
midpoint = 0, limits = c(-1, 1), name = "r"
|
||||||
|
) +
|
||||||
|
labs(x = NULL, y = NULL,
|
||||||
|
caption = "Correlations with CA row scores. Blue = positive, red = negative.") +
|
||||||
|
theme_minimal(base_size = 10, base_family = "source_sans_3") +
|
||||||
|
theme(axis.text.y = element_text(size = 9))
|
||||||
|
```
|
||||||
|
|
||||||
|
```{r fig-rq3-biplot, fig.width=7.5, fig.height=6.5, fig.cap="CA biplot (Dimensions 1--2) with educational offer as vectors. Arrows represent the direction of Pearson correlations with CA row scores (arrow length proportional to $|r|$, scaled for readability). Municipalities shown as grey points; key municipalities labelled."}
|
||||||
|
scale_fac <- 3.5 # scale correlation vectors to biplot space
|
||||||
|
|
||||||
|
rows_bp <- ca_coords |>
|
||||||
|
left_join(m_sample |> select(municipality), by = "municipality")
|
||||||
|
|
||||||
|
offer_arrows <- tibble(variable = offer_vars_sel) |>
|
||||||
|
mutate(
|
||||||
|
d1 = map_dbl(variable, \(v) cor(offer_joined$Dim_1, offer_joined[[v]], use="pairwise.complete.obs")),
|
||||||
|
d2 = map_dbl(variable, \(v) cor(offer_joined$Dim_2, offer_joined[[v]], use="pairwise.complete.obs")),
|
||||||
|
label = clean_label(variable)
|
||||||
|
) |>
|
||||||
|
filter(abs(d1) >= 0.2 | abs(d2) >= 0.2) |>
|
||||||
|
mutate(d1 = d1 * scale_fac, d2 = d2 * scale_fac)
|
||||||
|
|
||||||
|
key_rows <- rows_bp |> filter(municipality %in% key_munis)
|
||||||
|
|
||||||
|
dim_pct <- round(ca$eig[1:2, "percentage of variance"], 1)
|
||||||
|
|
||||||
|
ggplot() +
|
||||||
|
geom_point(data = rows_bp,
|
||||||
|
aes(x = Dim_1, y = Dim_2),
|
||||||
|
colour = "grey80", size = 0.8, alpha = 0.7) +
|
||||||
|
geom_segment(data = offer_arrows,
|
||||||
|
aes(x = 0, y = 0, xend = d1, yend = d2),
|
||||||
|
arrow = arrow(length = unit(0.18, "cm"), type = "closed"),
|
||||||
|
colour = "#2166ac", linewidth = 0.65) +
|
||||||
|
geom_text_repel(data = offer_arrows,
|
||||||
|
aes(x = d1, y = d2, label = label),
|
||||||
|
colour = "#2166ac", size = 3,
|
||||||
|
max.overlaps = 20, force = 3) +
|
||||||
|
geom_text_repel(data = key_rows,
|
||||||
|
aes(x = Dim_1, y = Dim_2, label = municipality),
|
||||||
|
colour = "black", fontface = "bold",
|
||||||
|
size = 2.8, max.overlaps = 15) +
|
||||||
|
geom_hline(yintercept = 0, linetype = "dashed", colour = "grey60", linewidth = 0.3) +
|
||||||
|
geom_vline(xintercept = 0, linetype = "dashed", colour = "grey60", linewidth = 0.3) +
|
||||||
|
labs(
|
||||||
|
x = paste0("Dimension 1 (", dim_pct[1], "%)"),
|
||||||
|
y = paste0("Dimension 2 (", dim_pct[2], "%)"),
|
||||||
|
caption = "Arrows = educational offer variables (Pearson r × 3.5, threshold |r| ≥ 0.20)."
|
||||||
|
) +
|
||||||
|
theme_minimal(base_size = 10, base_family = "source_sans_3") +
|
||||||
|
theme(panel.grid.minor = element_blank())
|
||||||
|
```
|
||||||
|
|
||||||
|
All offer types correlate positively with Dimension 1, confirming that the volume of educational offer is related to the rural--urban opposition already captured by population composition. Municipalities at the positive pole have more units of every type (grundskola, gymnasieskola, komvux, SFI, and higher education). This reflects the structural co-location of educated populations and educational institutions in the same urban places.
|
||||||
|
|
||||||
|
Komvux and SFI correlate positively with Dimension 2, meaning that the municipalities located at the top on this axis—those with the highest foreign-born concentrations—also tend to have more adult and language education relative to their position on Dimension 1. Universities and university colleges show negligible correlation with Dimension 2, consistent with the interpretation that higher education in this space relates to educational attainment (Dimension 1) rather than immigration reception (Dimension 2). The separation of SFI and komvux from higher education suggests that adult integration education and academic provision relate to different municipal profiles.
|
||||||
BIN
data/processed/attainment_summary.rds
Normal file
BIN
data/processed/attainment_summary.rds
Normal file
Binary file not shown.
BIN
data/processed/attainment_ts.rds
Normal file
BIN
data/processed/attainment_ts.rds
Normal file
Binary file not shown.
BIN
data/processed/ca_exploratory.rds
Normal file
BIN
data/processed/ca_exploratory.rds
Normal file
Binary file not shown.
11
data/processed/ca_posthoc_cor.csv
Normal file
11
data/processed/ca_posthoc_cor.csv
Normal file
File diff suppressed because one or more lines are too long
BIN
data/processed/ca_var_groups.rds
Normal file
BIN
data/processed/ca_var_groups.rds
Normal file
Binary file not shown.
291
data/processed/edu_offer.csv
Normal file
291
data/processed/edu_offer.csv
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
code,municipality,forskoleklass_n_total,grundskola_n_total,anpassad_grundskola_n_total,specialskola_n_total,sameskola_n_total,gymnasieskola_n_total,anpassad_gymnasieskola_n_total,komvux_n_total,sfi_n_total,university_college_n_total,university_n_total,forskoleklass_n_public,grundskola_n_public,anpassad_grundskola_n_public,specialskola_n_public,sameskola_n_public,gymnasieskola_n_public,anpassad_gymnasieskola_n_public,komvux_n_public,sfi_n_public,university_college_n_public,university_n_public,forskoleklass_n_private,grundskola_n_private,anpassad_grundskola_n_private,specialskola_n_private,sameskola_n_private,gymnasieskola_n_private,anpassad_gymnasieskola_n_private,komvux_n_private,sfi_n_private,university_college_n_private,university_n_private
|
||||||
|
0114,Upplands Väsby,14,17,2,0,0,2,0,4,2,0,0,5,7,2,0,0,1,0,4,2,0,0,9,10,0,0,0,1,0,0,0,0,0
|
||||||
|
0115,Vallentuna,11,13,3,0,0,1,1,0,0,0,0,8,8,1,0,0,1,0,0,0,0,0,3,5,2,0,0,0,1,0,0,0,0
|
||||||
|
0117,Österåker,16,18,3,0,0,2,0,3,2,0,0,10,12,3,0,0,1,0,3,2,0,0,6,6,0,0,0,1,0,0,0,0,0
|
||||||
|
0120,Värmdö,17,18,1,0,0,4,1,2,2,0,0,12,13,1,0,0,2,1,2,2,0,0,5,5,0,0,0,2,0,0,0,0,0
|
||||||
|
0123,Järfälla,19,25,8,0,0,3,1,2,1,0,0,17,22,8,0,0,2,1,2,1,0,0,2,3,0,0,0,1,0,0,0,0,0
|
||||||
|
0125,Ekerö,12,14,1,0,0,1,1,1,0,0,0,9,11,1,0,0,1,1,1,0,0,0,3,3,0,0,0,0,0,0,0,0,0
|
||||||
|
0126,Huddinge,30,37,6,0,0,7,2,4,3,1,0,26,30,6,0,0,5,2,4,3,1,0,4,7,0,0,0,2,0,0,0,0,0
|
||||||
|
0127,Botkyrka,24,27,9,0,0,5,2,2,1,0,0,19,21,9,0,0,4,1,2,1,0,0,5,6,0,0,0,1,1,0,0,0,0
|
||||||
|
0128,Salem,7,7,1,0,0,1,0,1,1,0,0,4,4,1,0,0,1,0,1,1,0,0,3,3,0,0,0,0,0,0,0,0,0
|
||||||
|
0136,Haninge,28,31,6,0,0,5,3,1,1,0,0,18,19,6,0,0,3,2,1,1,0,0,10,12,0,0,0,2,1,0,0,0,0
|
||||||
|
0138,Tyresö,13,16,2,0,0,1,1,3,1,0,0,11,13,2,0,0,1,1,3,1,0,0,2,3,0,0,0,0,0,0,0,0,0
|
||||||
|
0139,Upplands-Bro,11,14,1,0,0,1,1,3,1,0,0,8,10,1,0,0,1,1,3,1,0,0,3,4,0,0,0,0,0,0,0,0,0
|
||||||
|
0140,Nykvarn,3,4,2,0,0,1,0,0,0,0,0,3,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0
|
||||||
|
0160,Täby,20,32,4,0,0,8,1,4,2,0,0,13,18,2,0,0,2,1,4,2,0,0,7,14,2,0,0,6,0,0,0,0,0
|
||||||
|
0162,Danderyd,12,16,3,0,0,4,2,0,0,0,0,8,10,2,0,0,1,1,0,0,0,0,4,6,1,0,0,3,1,0,0,0,0
|
||||||
|
0163,Sollentuna,30,31,4,0,0,14,2,2,1,0,0,15,17,2,0,0,8,0,2,1,0,0,15,14,2,0,0,6,2,0,0,0,0
|
||||||
|
0180,Stockholm,206,247,54,3,0,96,12,17,15,11,2,136,142,42,3,0,31,6,17,15,11,2,70,105,12,0,0,65,6,0,0,0,0
|
||||||
|
0181,Södertälje,30,34,10,0,0,11,4,2,1,0,0,19,21,5,0,0,3,1,2,1,0,0,11,13,5,0,0,8,3,0,0,0,0
|
||||||
|
0182,Nacka,33,38,5,0,0,14,3,1,1,0,0,19,22,4,0,0,2,3,1,1,0,0,14,16,1,0,0,12,0,0,0,0,0
|
||||||
|
0183,Sundbyberg,9,12,1,0,0,3,0,2,1,0,0,7,7,1,0,0,1,0,2,1,0,0,2,5,0,0,0,2,0,0,0,0,0
|
||||||
|
0184,Solna,16,21,5,0,0,9,6,2,2,0,1,9,10,1,0,0,1,2,2,2,0,1,7,11,4,0,0,8,4,0,0,0,0
|
||||||
|
0186,Lidingö,16,18,1,0,0,3,0,3,2,0,0,11,12,1,0,0,1,0,3,2,0,0,5,6,0,0,0,2,0,0,0,0,0
|
||||||
|
0187,Vaxholm,4,5,0,0,0,0,0,0,0,0,0,4,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0188,Norrtälje,29,34,2,0,0,4,2,2,0,0,0,20,23,1,0,0,1,1,2,0,0,0,9,11,1,0,0,3,1,0,0,0,0
|
||||||
|
0191,Sigtuna,16,20,3,0,0,8,1,4,2,0,0,13,15,3,0,0,3,1,3,2,0,0,3,5,0,0,0,5,0,1,0,0,0
|
||||||
|
0192,Nynäshamn,6,6,2,0,0,1,2,1,1,0,0,5,5,2,0,0,1,2,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0305,Håbo,8,8,1,0,0,2,1,1,1,0,0,7,6,1,0,0,2,1,1,1,0,0,1,2,0,0,0,0,0,0,0,0,0
|
||||||
|
0319,Älvkarleby,4,5,1,0,0,1,0,1,1,0,0,3,4,1,0,0,1,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0330,Knivsta,9,11,1,0,0,1,0,1,1,0,0,7,9,1,0,0,1,0,1,1,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
0331,Heby,7,9,1,0,0,0,0,3,1,0,0,6,8,1,0,0,0,0,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0360,Tierp,11,12,1,0,0,1,1,3,1,0,0,11,12,1,0,0,1,1,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0380,Uppsala,59,77,8,0,0,25,5,10,5,1,2,49,57,5,0,0,8,2,10,5,1,2,10,20,3,0,0,17,3,0,0,0,0
|
||||||
|
0381,Enköping,14,17,2,0,0,1,1,1,1,0,0,11,12,2,0,0,1,1,1,1,0,0,3,5,0,0,0,0,0,0,0,0,0
|
||||||
|
0382,Östhammar,7,9,1,0,0,3,0,3,1,0,0,7,9,1,0,0,1,0,3,1,0,0,0,0,0,0,0,2,0,0,0,0,0
|
||||||
|
0428,Vingåker,5,7,2,0,0,1,1,1,1,0,0,4,6,2,0,0,1,1,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0461,Gnesta,5,5,1,0,0,0,0,1,1,0,0,4,4,1,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0480,Nyköping,28,34,3,0,0,10,3,4,1,0,0,22,24,3,0,0,4,2,4,1,0,0,6,10,0,0,0,6,1,0,0,0,0
|
||||||
|
0481,Oxelösund,2,3,1,0,0,0,0,2,1,0,0,2,3,1,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0482,Flen,8,8,1,0,0,1,1,3,1,0,0,5,5,1,0,0,1,1,3,1,0,0,3,3,0,0,0,0,0,0,0,0,0
|
||||||
|
0483,Katrineholm,10,13,4,0,0,5,1,1,1,0,0,10,12,4,0,0,5,1,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0484,Eskilstuna,31,33,7,0,0,12,2,3,1,1,0,27,27,7,0,0,7,2,3,1,1,0,4,6,0,0,0,5,0,0,0,0,0
|
||||||
|
0486,Strängnäs,10,14,2,0,0,2,1,3,1,0,0,6,8,2,0,0,1,1,3,1,0,0,4,6,0,0,0,1,0,0,0,0,0
|
||||||
|
0488,Trosa,4,6,3,0,0,1,1,1,1,0,0,4,6,3,0,0,1,0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0
|
||||||
|
0509,Ödeshög,2,3,0,0,0,0,0,0,0,0,0,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0512,Ydre,2,3,0,0,0,0,0,0,0,0,0,2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0513,Kinda,3,6,2,0,0,0,0,1,1,0,0,3,6,2,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0560,Boxholm,2,2,2,0,0,0,0,0,0,0,0,2,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0561,Åtvidaberg,3,5,1,0,0,1,0,1,1,0,0,3,5,1,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0562,Finspång,11,13,1,0,0,2,2,1,1,0,0,10,12,1,0,0,1,1,1,1,0,0,1,1,0,0,0,1,1,0,0,0,0
|
||||||
|
0563,Valdemarsvik,4,5,0,0,0,0,0,0,0,0,0,3,4,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0580,Linköping,48,80,8,0,0,28,4,11,4,0,1,42,68,7,0,0,20,4,11,4,0,1,6,12,1,0,0,8,0,0,0,0,0
|
||||||
|
0581,Norrköping,40,65,10,0,0,29,6,9,4,0,0,33,52,9,0,0,21,6,9,4,0,0,7,13,1,0,0,8,0,0,0,0,0
|
||||||
|
0582,Söderköping,9,9,2,0,0,3,1,3,1,0,0,8,8,2,0,0,3,1,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0583,Motala,18,27,2,0,0,6,1,4,1,0,0,17,24,2,0,0,6,1,4,1,0,0,1,3,0,0,0,0,0,0,0,0,0
|
||||||
|
0584,Vadstena,1,3,0,0,0,0,0,0,0,0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0586,Mjölby,12,16,4,0,0,4,1,1,1,0,0,12,16,4,0,0,4,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0604,Aneby,5,6,4,0,0,0,0,0,0,0,0,4,5,3,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0
|
||||||
|
0617,Gnosjö,5,7,1,0,0,1,0,3,1,0,0,5,7,1,0,0,1,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0642,Mullsjö,2,4,1,0,0,0,0,0,0,0,0,2,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0643,Habo,6,7,1,0,0,0,0,0,0,0,0,6,7,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0662,Gislaved,12,17,2,0,0,5,2,2,1,0,0,11,16,2,0,0,4,2,2,1,0,0,1,1,0,0,0,1,0,0,0,0,0
|
||||||
|
0665,Vaggeryd,9,12,3,0,0,1,0,1,1,0,0,8,11,3,0,0,1,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0680,Jönköping,42,56,11,0,0,28,5,4,3,1,0,39,49,9,0,0,19,3,4,3,1,0,3,7,2,0,0,9,2,0,0,0,0
|
||||||
|
0682,Nässjö,11,16,2,0,0,3,1,3,1,0,0,11,16,2,0,0,3,1,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0683,Värnamo,14,21,2,0,0,4,1,3,1,0,0,14,21,2,0,0,4,1,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0684,Sävsjö,6,7,1,0,0,2,1,3,1,0,0,5,6,1,0,0,2,1,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0685,Vetlanda,15,18,3,0,0,6,1,5,2,0,0,14,17,3,0,0,6,1,5,2,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0686,Eksjö,7,13,3,0,0,3,1,3,1,0,0,7,12,2,0,0,3,1,3,1,0,0,0,1,1,0,0,0,0,0,0,0,0
|
||||||
|
0687,Tranås,8,12,1,0,0,3,1,2,1,0,0,7,11,1,0,0,3,1,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0760,Uppvidinge,5,7,1,0,0,1,0,2,1,0,0,4,6,1,0,0,1,0,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0761,Lessebo,4,5,1,0,0,1,1,3,1,0,0,4,5,1,0,0,1,1,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0763,Tingsryd,7,8,8,0,0,2,0,1,1,0,0,6,7,8,0,0,1,0,1,1,0,0,1,1,0,0,0,1,0,0,0,0,0
|
||||||
|
0764,Alvesta,7,8,1,0,0,1,1,1,1,0,0,6,7,1,0,0,1,1,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0765,Älmhult,12,17,2,0,0,2,1,3,1,0,0,10,15,2,0,0,2,1,3,1,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
0767,Markaryd,4,7,1,0,0,1,0,3,1,0,0,4,7,1,0,0,1,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0780,Växjö,37,50,3,0,0,21,2,3,1,1,0,33,42,3,0,0,14,2,3,1,1,0,4,8,0,0,0,7,0,0,0,0,0
|
||||||
|
0781,Ljungby,15,20,2,0,0,5,1,3,1,0,0,13,18,2,0,0,4,1,3,1,0,0,2,2,0,0,0,1,0,0,0,0,0
|
||||||
|
0821,Högsby,3,4,0,0,0,1,0,1,1,0,0,3,4,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0834,Torsås,5,5,1,0,0,2,0,1,0,0,0,5,5,1,0,0,2,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0840,Mörbylånga,7,10,2,0,0,0,0,0,0,0,0,6,8,1,0,0,0,0,0,0,0,0,1,2,1,0,0,0,0,0,0,0,0
|
||||||
|
0860,Hultsfred,6,9,2,0,0,3,1,1,1,0,0,5,8,2,0,0,3,1,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0861,Mönsterås,6,8,1,0,0,0,0,1,1,0,0,6,8,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0862,Emmaboda,5,6,2,0,0,1,0,3,1,0,0,5,6,2,0,0,1,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0880,Kalmar,21,27,1,0,0,8,1,2,1,1,0,18,21,1,0,0,3,1,2,1,1,0,3,6,0,0,0,5,0,0,0,0,0
|
||||||
|
0881,Nybro,11,15,2,0,0,3,0,3,1,0,0,10,14,2,0,0,3,0,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
0882,Oskarshamn,10,11,2,0,0,3,1,3,1,0,0,9,10,2,0,0,2,1,3,1,0,0,1,1,0,0,0,1,0,0,0,0,0
|
||||||
|
0883,Västervik,16,21,3,0,0,5,1,1,1,0,0,15,19,3,0,0,4,1,1,1,0,0,1,2,0,0,0,1,0,0,0,0,0
|
||||||
|
0884,Vimmerby,8,10,1,0,0,3,0,2,1,0,0,8,10,1,0,0,3,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0885,Borgholm,5,6,1,0,0,0,0,1,1,0,0,5,6,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
0980,Gotland,33,37,3,0,0,6,1,2,2,0,0,29,33,3,0,0,5,1,2,2,0,0,4,4,0,0,0,1,0,0,0,0,0
|
||||||
|
1060,Olofström,5,6,2,0,0,3,0,3,1,0,0,5,6,2,0,0,3,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1080,Karlskrona,27,33,1,0,0,5,1,3,1,1,0,20,26,1,0,0,4,1,3,1,1,0,7,7,0,0,0,1,0,0,0,0,0
|
||||||
|
1081,Ronneby,13,18,2,0,0,5,1,2,1,0,0,11,16,2,0,0,5,1,2,1,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
1082,Karlshamn,11,15,1,0,0,5,1,1,1,0,0,8,11,1,0,0,4,1,1,1,0,0,3,4,0,0,0,1,0,0,0,0,0
|
||||||
|
1083,Sölvesborg,8,9,1,0,0,3,1,3,1,0,0,8,9,1,0,0,3,1,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1214,Svalöv,6,9,1,0,0,1,2,3,1,0,0,5,7,1,0,0,1,2,3,1,0,0,1,2,0,0,0,0,0,0,0,0,0
|
||||||
|
1230,Staffanstorp,10,13,0,0,0,2,1,0,0,0,0,8,11,0,0,0,1,0,0,0,0,0,2,2,0,0,0,1,1,0,0,0,0
|
||||||
|
1231,Burlöv,7,10,0,0,0,1,0,3,1,0,0,7,10,0,0,0,1,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1233,Vellinge,14,15,1,0,0,1,0,1,1,0,0,9,10,1,0,0,1,0,1,1,0,0,5,5,0,0,0,0,0,0,0,0,0
|
||||||
|
1256,Östra Göinge,5,9,2,0,0,1,0,2,1,0,0,4,8,1,0,0,1,0,2,1,0,0,1,1,1,0,0,0,0,0,0,0,0
|
||||||
|
1257,Örkelljunga,6,8,1,0,0,1,0,3,1,0,0,5,7,1,0,0,1,0,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1260,Bjuv,4,4,2,0,0,1,0,3,1,0,0,4,4,2,0,0,1,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1261,Kävlinge,11,14,2,0,0,1,0,2,1,0,0,9,12,2,0,0,1,0,2,1,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
1262,Lomma,8,9,1,0,0,0,0,1,1,0,1,7,8,1,0,0,0,0,1,1,0,1,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1263,Svedala,6,9,2,0,0,2,0,1,1,0,0,5,8,2,0,0,1,0,1,1,0,0,1,1,0,0,0,1,0,0,0,0,0
|
||||||
|
1264,Skurup,7,10,1,0,0,1,1,2,1,0,0,7,9,1,0,0,1,1,2,1,0,0,0,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1265,Sjöbo,8,10,1,0,0,1,0,3,1,0,0,5,6,1,0,0,1,0,3,1,0,0,3,4,0,0,0,0,0,0,0,0,0
|
||||||
|
1266,Hörby,7,9,1,0,0,2,0,1,1,0,0,6,8,1,0,0,1,0,1,1,0,0,1,1,0,0,0,1,0,0,0,0,0
|
||||||
|
1267,Höör,8,10,1,0,0,2,0,2,1,0,0,5,7,1,0,0,1,0,2,1,0,0,3,3,0,0,0,1,0,0,0,0,0
|
||||||
|
1270,Tomelilla,6,7,2,0,0,1,1,0,0,0,0,5,6,2,0,0,0,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0
|
||||||
|
1272,Bromölla,5,7,2,0,0,1,0,1,1,0,0,5,7,2,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1273,Osby,4,6,1,0,0,2,1,3,2,0,0,4,6,1,0,0,2,1,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1275,Perstorp,1,3,0,0,0,2,0,1,1,0,0,1,3,0,0,0,1,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0
|
||||||
|
1276,Klippan,6,7,1,0,0,5,1,2,1,0,0,5,6,1,0,0,4,1,2,1,0,0,1,1,0,0,0,1,0,0,0,0,0
|
||||||
|
1277,Åstorp,6,7,1,0,0,1,0,2,1,0,0,6,7,1,0,0,1,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1278,Båstad,5,5,0,0,0,2,0,3,1,0,0,4,4,0,0,0,1,0,3,1,0,0,1,1,0,0,0,1,0,0,0,0,0
|
||||||
|
1280,Malmö,76,103,19,0,0,35,4,28,10,1,0,59,80,17,0,0,13,3,28,10,1,0,17,23,2,0,0,22,1,0,0,0,0
|
||||||
|
1281,Lund,43,52,7,1,0,19,1,4,1,0,1,33,40,6,1,0,5,1,4,1,0,1,10,12,1,0,0,14,0,0,0,0,0
|
||||||
|
1282,Landskrona,15,18,1,0,0,2,1,3,2,0,0,11,12,1,0,0,2,1,3,2,0,0,4,6,0,0,0,0,0,0,0,0,0
|
||||||
|
1283,Helsingborg,52,60,7,0,0,26,4,4,2,0,0,33,34,6,0,0,7,4,4,2,0,0,19,26,1,0,0,19,0,0,0,0,0
|
||||||
|
1284,Höganäs,9,10,1,0,0,1,0,3,2,0,0,6,7,1,0,0,1,0,3,2,0,0,3,3,0,0,0,0,0,0,0,0,0
|
||||||
|
1285,Eslöv,11,16,1,0,0,1,1,3,1,0,0,10,15,1,0,0,1,1,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1286,Ystad,12,14,1,0,0,7,1,2,2,0,0,9,10,1,0,0,5,1,2,2,0,0,3,4,0,0,0,2,0,0,0,0,0
|
||||||
|
1287,Trelleborg,14,17,2,0,0,1,1,3,1,0,0,12,14,2,0,0,1,1,3,1,0,0,2,3,0,0,0,0,0,0,0,0,0
|
||||||
|
1290,Kristianstad,39,57,7,0,0,19,2,5,1,1,0,33,51,7,0,0,11,2,5,1,1,0,6,6,0,0,0,8,0,0,0,0,0
|
||||||
|
1291,Simrishamn,7,10,1,0,0,1,1,1,1,0,0,4,6,1,0,0,1,1,1,1,0,0,3,4,0,0,0,0,0,0,0,0,0
|
||||||
|
1292,Ängelholm,10,22,2,0,0,5,2,3,2,0,0,10,22,2,0,0,5,2,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1293,Hässleholm,25,28,1,0,0,3,1,4,1,0,0,23,26,1,0,0,3,1,4,1,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
1315,Hylte,6,9,1,0,0,2,1,2,1,0,0,6,8,1,0,0,1,1,2,1,0,0,0,1,0,0,0,1,0,0,0,0,0
|
||||||
|
1380,Halmstad,31,51,5,0,0,22,2,2,2,1,0,28,47,5,0,0,16,2,2,2,1,0,3,4,0,0,0,6,0,0,0,0,0
|
||||||
|
1381,Laholm,14,18,1,0,0,1,1,3,1,0,0,11,15,1,0,0,1,1,3,1,0,0,3,3,0,0,0,0,0,0,0,0,0
|
||||||
|
1382,Falkenberg,21,24,2,0,0,8,1,1,1,0,0,18,21,2,0,0,7,1,1,1,0,0,3,3,0,0,0,1,0,0,0,0,0
|
||||||
|
1383,Varberg,22,37,2,0,0,6,1,2,1,0,0,20,34,2,0,0,3,1,2,1,0,0,2,3,0,0,0,3,0,0,0,0,0
|
||||||
|
1384,Kungsbacka,28,60,3,0,0,14,2,3,1,0,0,21,52,2,0,0,10,2,3,1,0,0,7,8,1,0,0,4,0,0,0,0,0
|
||||||
|
1401,Härryda,14,28,4,0,0,7,0,4,2,0,0,13,27,4,0,0,6,0,4,2,0,0,1,1,0,0,0,1,0,0,0,0,0
|
||||||
|
1402,Partille,10,16,1,0,0,1,1,3,1,0,0,9,15,1,0,0,1,1,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1407,Öckerö,6,11,1,0,0,1,0,2,0,0,0,5,10,1,0,0,1,0,2,0,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1415,Stenungsund,9,15,4,0,0,5,1,2,2,0,0,8,14,4,0,0,5,1,2,2,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1419,Tjörn,6,9,2,0,0,0,0,1,0,0,0,5,8,2,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1421,Orust,7,10,1,0,0,1,0,1,1,0,0,4,7,1,0,0,1,0,1,1,0,0,3,3,0,0,0,0,0,0,0,0,0
|
||||||
|
1427,Sotenäs,5,6,1,0,0,1,0,2,2,0,0,5,6,1,0,0,1,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1430,Munkedal,4,6,2,0,0,4,1,2,1,0,0,4,6,2,0,0,2,1,2,1,0,0,0,0,0,0,0,2,0,0,0,0,0
|
||||||
|
1435,Tanum,7,8,1,0,0,1,1,2,1,0,0,7,8,1,0,0,1,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1438,Dals-Ed,1,2,2,0,0,1,1,3,1,0,0,1,2,2,0,0,1,1,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1439,Färgelanda,5,7,1,0,0,0,0,1,1,0,0,3,5,1,0,0,0,0,1,1,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
1440,Ale,9,15,2,0,0,2,0,2,1,0,0,8,14,2,0,0,1,0,2,1,0,0,1,1,0,0,0,1,0,0,0,0,0
|
||||||
|
1441,Lerum,16,34,3,0,0,5,1,3,1,0,0,14,32,3,0,0,5,1,3,1,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
1442,Vårgårda,6,7,1,0,0,1,0,2,1,0,0,6,7,1,0,0,1,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1443,Bollebygd,4,6,1,0,0,1,0,0,0,0,0,3,5,1,0,0,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1444,Grästorp,1,2,2,0,0,0,0,0,0,0,0,1,2,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1445,Essunga,3,4,1,0,0,0,1,3,0,0,0,3,4,1,0,0,0,1,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1446,Karlsborg,3,4,1,0,0,1,0,1,1,0,0,2,3,1,0,0,1,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1447,Gullspång,2,3,1,0,0,0,0,1,1,0,0,2,3,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1452,Tranemo,5,7,5,0,0,1,1,2,1,0,0,5,7,5,0,0,1,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1460,Bengtsfors,3,4,2,0,0,1,0,2,1,0,0,3,4,2,0,0,1,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1461,Mellerud,4,6,1,0,0,1,0,3,2,0,0,4,6,1,0,0,1,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1462,Lilla Edet,5,6,2,0,0,0,1,1,1,0,0,4,5,1,0,0,0,0,1,1,0,0,1,1,1,0,0,0,1,0,0,0,0
|
||||||
|
1463,Mark,15,23,3,0,0,7,1,1,1,0,0,13,21,2,0,0,6,1,1,1,0,0,2,2,1,0,0,1,0,0,0,0,0
|
||||||
|
1465,Svenljunga,7,9,1,0,0,2,0,3,1,0,0,6,8,1,0,0,2,0,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1466,Herrljunga,7,10,2,0,0,1,0,0,0,0,0,7,10,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1470,Vara,8,12,1,0,0,3,0,4,1,0,0,6,9,1,0,0,2,0,4,1,0,0,2,3,0,0,0,1,0,0,0,0,0
|
||||||
|
1471,Götene,5,7,0,0,0,2,0,1,0,0,0,5,7,0,0,0,1,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0
|
||||||
|
1472,Tibro,3,4,2,0,0,2,0,0,0,0,0,3,4,2,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0
|
||||||
|
1473,Töreboda,4,6,2,0,0,2,2,2,1,0,0,3,5,2,0,0,2,2,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1480,Göteborg,136,179,45,0,0,81,10,28,19,0,2,105,132,41,0,0,44,9,28,19,0,2,31,47,4,0,0,37,1,0,0,0,0
|
||||||
|
1481,Mölndal,18,24,2,0,0,4,1,4,2,0,0,16,22,2,0,0,4,1,4,2,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
1482,Kungälv,16,23,2,0,0,5,1,1,1,0,0,12,19,2,0,0,5,1,1,1,0,0,4,4,0,0,0,0,0,0,0,0,0
|
||||||
|
1484,Lysekil,6,7,1,0,0,2,1,1,1,0,0,6,7,1,0,0,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1485,Uddevalla,16,23,3,0,0,24,2,5,2,0,0,13,20,3,0,0,22,2,5,2,0,0,3,3,0,0,0,2,0,0,0,0,0
|
||||||
|
1486,Strömstad,8,9,1,0,0,2,0,3,1,0,0,8,9,1,0,0,2,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1487,Vänersborg,17,32,2,1,0,4,1,5,2,0,0,15,30,2,1,0,2,0,5,2,0,0,2,2,0,0,0,2,1,0,0,0,0
|
||||||
|
1488,Trollhättan,19,33,8,0,0,9,1,4,1,1,0,15,28,8,0,0,4,1,4,1,1,0,4,5,0,0,0,5,0,0,0,0,0
|
||||||
|
1489,Alingsås,16,21,2,0,0,6,1,3,1,0,0,14,19,2,0,0,5,1,3,1,0,0,2,2,0,0,0,1,0,0,0,0,0
|
||||||
|
1490,Borås,36,48,4,0,0,23,3,6,1,1,0,33,44,4,0,0,18,3,6,1,1,0,3,4,0,0,0,5,0,0,0,0,0
|
||||||
|
1491,Ulricehamn,12,15,1,0,0,2,1,3,1,0,0,10,13,1,0,0,2,1,3,1,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
1492,Åmål,3,5,4,0,0,3,1,3,1,0,0,2,4,4,0,0,3,1,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1493,Mariestad,11,14,1,0,0,4,1,1,1,0,0,9,11,1,0,0,3,1,1,1,0,0,2,3,0,0,0,1,0,0,0,0,0
|
||||||
|
1494,Lidköping,17,22,3,0,0,6,1,5,2,0,0,16,20,3,0,0,6,1,5,2,0,0,1,2,0,0,0,0,0,0,0,0,0
|
||||||
|
1495,Skara,7,8,2,0,0,6,1,5,1,0,1,6,7,1,0,0,5,0,5,1,0,1,1,1,1,0,0,1,1,0,0,0,0
|
||||||
|
1496,Skövde,19,25,3,0,0,13,2,3,1,1,0,17,23,3,0,0,6,2,3,1,1,0,2,2,0,0,0,7,0,0,0,0,0
|
||||||
|
1497,Hjo,5,6,2,0,0,1,0,3,2,0,0,5,6,2,0,0,1,0,3,2,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1498,Tidaholm,5,9,2,0,0,2,0,3,1,0,0,5,9,2,0,0,2,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1499,Falköping,14,21,4,0,0,4,1,2,1,0,0,13,20,4,0,0,4,1,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1715,Kil,5,7,1,0,0,1,0,2,1,0,0,5,7,1,0,0,1,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1730,Eda,4,4,1,0,0,0,0,1,1,0,0,4,4,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1737,Torsby,5,6,3,0,0,1,1,1,1,0,0,5,6,3,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1760,Storfors,2,4,1,0,0,1,0,1,1,0,0,2,3,1,0,0,0,0,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0
|
||||||
|
1761,Hammarö,5,11,1,0,0,0,0,0,0,0,0,5,11,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1762,Munkfors,1,2,1,0,0,0,0,0,0,0,0,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1763,Forshaga,5,7,6,0,0,1,0,1,1,0,0,5,7,6,0,0,0,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0
|
||||||
|
1764,Grums,4,5,2,0,0,1,0,1,0,0,0,2,3,2,0,0,1,0,1,0,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
1765,Årjäng,5,7,1,0,0,1,1,3,1,0,0,4,6,1,0,0,1,1,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1766,Sunne,7,8,3,0,0,2,1,3,1,0,0,6,7,3,0,0,2,1,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1780,Karlstad,23,41,6,0,0,22,3,6,2,0,1,20,36,5,0,0,13,3,6,2,0,1,3,5,1,0,0,9,0,0,0,0,0
|
||||||
|
1781,Kristinehamn,7,13,1,0,0,3,1,3,1,0,0,7,13,1,0,0,3,1,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1782,Filipstad,7,8,3,0,0,1,0,1,1,0,0,6,7,3,0,0,1,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1783,Hagfors,4,7,1,0,0,2,1,3,1,0,0,4,7,1,0,0,2,1,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1784,Arvika,11,15,3,0,0,3,1,2,1,0,0,11,14,3,0,0,2,1,2,1,0,0,0,1,0,0,0,1,0,0,0,0,0
|
||||||
|
1785,Säffle,8,9,3,0,0,1,2,1,1,0,0,6,7,3,0,0,1,2,1,1,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
1814,Lekeberg,3,4,1,0,0,1,0,2,1,0,0,3,4,1,0,0,1,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1860,Laxå,2,3,1,0,0,0,0,0,0,0,0,2,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1861,Hallsberg,6,7,0,0,0,5,1,2,1,0,0,6,7,0,0,0,5,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1862,Degerfors,4,5,2,0,0,1,0,1,1,0,0,4,5,2,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1863,Hällefors,2,4,1,0,0,1,0,1,1,0,0,2,4,1,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1864,Ljusnarsberg,1,2,1,0,0,0,0,1,1,0,0,1,2,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1880,Örebro,53,68,6,3,0,36,3,18,2,0,1,45,53,6,3,0,25,2,18,2,0,1,8,15,0,0,0,11,1,0,0,0,0
|
||||||
|
1881,Kumla,8,11,2,0,0,1,1,4,2,0,0,8,11,2,0,0,1,1,4,2,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1882,Askersund,5,6,1,0,0,0,0,0,0,0,0,5,6,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1883,Karlskoga,7,12,1,0,0,2,1,3,1,0,0,6,11,1,0,0,1,1,3,1,0,0,1,1,0,0,0,1,0,0,0,0,0
|
||||||
|
1884,Nora,6,7,2,0,0,1,0,3,1,0,0,5,6,2,0,0,1,0,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1885,Lindesberg,8,11,1,0,0,4,1,3,1,0,0,6,9,1,0,0,4,1,3,1,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
1904,Skinnskatteberg,1,1,1,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1907,Surahammar,4,6,1,0,0,2,0,1,0,0,0,4,6,1,0,0,1,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0
|
||||||
|
1960,Kungsör,3,4,1,0,0,1,0,1,1,0,0,3,4,1,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1961,Hallstahammar,5,7,2,0,0,2,0,1,1,0,0,5,7,2,0,0,1,0,1,1,0,0,0,0,0,0,0,1,0,0,0,0,0
|
||||||
|
1962,Norberg,1,2,1,0,0,0,0,0,0,0,0,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1980,Västerås,49,60,12,0,0,21,5,10,4,1,0,38,42,11,0,0,7,4,10,4,1,0,11,18,1,0,0,14,1,0,0,0,0
|
||||||
|
1981,Sala,11,14,2,0,0,2,1,1,1,0,0,11,14,2,0,0,2,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1982,Fagersta,4,5,1,0,0,1,2,3,1,0,0,3,4,1,0,0,1,2,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
1983,Köping,7,12,3,0,0,4,2,2,1,0,0,7,12,3,0,0,4,2,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
1984,Arboga,6,8,1,0,0,1,0,3,1,0,0,5,7,1,0,0,1,0,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2021,Vansbro,4,6,5,0,0,1,0,1,1,0,0,3,5,5,0,0,1,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2023,Malung-Sälen,5,7,4,0,0,1,0,1,1,0,0,5,7,4,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2026,Gagnef,6,7,1,0,0,1,0,2,1,0,0,6,7,1,0,0,1,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2029,Leksand,9,10,1,0,0,1,1,1,1,0,0,7,8,1,0,0,1,1,1,1,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
2031,Rättvik,7,8,2,0,0,1,1,0,0,0,0,7,8,2,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2034,Orsa,5,5,1,0,0,0,0,0,0,0,0,4,4,1,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2039,Älvdalen,4,4,0,0,0,1,0,0,0,0,0,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0
|
||||||
|
2061,Smedjebacken,4,6,0,0,0,1,0,0,0,0,0,4,6,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2062,Mora,12,14,1,0,0,2,1,3,1,0,0,11,13,1,0,0,2,1,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2080,Falun,23,26,3,0,0,7,1,1,1,1,0,21,23,3,0,0,3,1,1,1,1,0,2,3,0,0,0,4,0,0,0,0,0
|
||||||
|
2081,Borlänge,15,20,1,0,0,7,1,3,1,1,0,14,17,1,0,0,4,1,3,1,1,0,1,3,0,0,0,3,0,0,0,0,0
|
||||||
|
2082,Säter,3,4,2,0,0,1,0,1,1,0,0,3,4,2,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2083,Hedemora,8,10,3,0,0,1,3,3,1,0,0,6,7,2,0,0,1,2,3,1,0,0,2,3,1,0,0,0,1,0,0,0,0
|
||||||
|
2084,Avesta,9,12,2,0,0,1,1,3,1,0,0,8,11,2,0,0,1,1,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2085,Ludvika,11,13,2,0,0,4,1,3,1,0,0,10,12,2,0,0,3,1,3,1,0,0,1,1,0,0,0,1,0,0,0,0,0
|
||||||
|
2101,Ockelbo,2,4,0,0,0,0,0,1,1,0,0,2,4,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2104,Hofors,3,4,1,0,0,1,0,2,1,0,0,3,4,1,0,0,1,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2121,Ovanåker,7,9,0,0,0,2,0,3,1,0,0,7,9,0,0,0,2,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2132,Nordanstig,7,7,1,0,0,1,0,1,1,0,0,5,5,1,0,0,1,0,1,1,0,0,2,2,0,0,0,0,0,0,0,0,0
|
||||||
|
2161,Ljusdal,8,10,1,0,0,3,0,3,1,0,0,6,7,1,0,0,1,0,3,1,0,0,2,3,0,0,0,2,0,0,0,0,0
|
||||||
|
2180,Gävle,29,36,7,0,0,10,1,3,1,1,0,25,29,7,0,0,4,1,3,1,1,0,4,7,0,0,0,6,0,0,0,0,0
|
||||||
|
2181,Sandviken,12,15,2,0,0,3,1,3,1,0,0,11,14,2,0,0,2,1,3,1,0,0,1,1,0,0,0,1,0,0,0,0,0
|
||||||
|
2182,Söderhamn,12,17,1,0,0,2,0,2,1,0,0,11,15,1,0,0,2,0,2,1,0,0,1,2,0,0,0,0,0,0,0,0,0
|
||||||
|
2183,Bollnäs,12,15,2,0,0,6,1,2,1,0,0,7,9,2,0,0,2,1,2,1,0,0,5,6,0,0,0,4,0,0,0,0,0
|
||||||
|
2184,Hudiksvall,19,24,2,0,0,7,1,3,1,0,0,16,20,1,0,0,6,1,3,1,0,0,3,4,1,0,0,1,0,0,0,0,0
|
||||||
|
2260,Ånge,5,6,2,0,0,1,0,3,1,0,0,4,5,2,0,0,1,0,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2262,Timrå,7,9,2,0,0,2,0,4,1,0,0,7,9,2,0,0,2,0,4,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2280,Härnösand,9,14,1,1,0,3,1,1,1,0,0,8,13,1,1,0,3,1,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2281,Sundsvall,31,40,6,0,0,13,1,12,6,1,0,24,29,6,0,0,7,1,12,6,1,0,7,11,0,0,0,6,0,0,0,0,0
|
||||||
|
2282,Kramfors,8,10,1,0,0,1,1,2,1,0,0,7,9,1,0,0,1,1,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2283,Sollefteå,12,13,1,0,0,1,1,1,1,0,0,12,13,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2284,Örnsköldsvik,30,35,1,0,0,13,1,6,1,0,0,27,31,1,0,0,10,1,6,1,0,0,3,4,0,0,0,3,0,0,0,0,0
|
||||||
|
2303,Ragunda,2,3,3,0,0,1,0,1,1,0,0,2,3,3,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2305,Bräcke,3,3,0,0,0,1,0,1,1,0,0,3,3,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2309,Krokom,14,16,1,0,0,3,1,3,1,0,0,14,16,1,0,0,2,1,3,1,0,0,0,0,0,0,0,1,0,0,0,0,0
|
||||||
|
2313,Strömsund,8,9,0,0,0,1,0,1,1,0,0,8,9,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2321,Åre,7,7,3,0,0,2,0,3,1,0,0,7,7,3,0,0,2,0,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2326,Berg,8,10,1,0,0,1,0,1,1,0,0,7,9,1,0,0,1,0,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2361,Härjedalen,8,9,0,0,0,1,0,1,1,0,0,8,9,0,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2380,Östersund,25,29,1,0,0,14,1,3,1,1,0,20,24,1,0,0,12,1,3,1,1,0,5,5,0,0,0,2,0,0,0,0,0
|
||||||
|
2401,Nordmaling,3,5,1,0,0,1,0,1,1,0,0,3,5,1,0,0,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2403,Bjurholm,1,1,0,0,0,0,0,1,1,0,0,1,1,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2404,Vindeln,4,5,2,0,0,0,0,2,1,0,0,4,5,2,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2409,Robertsfors,5,7,1,0,0,0,0,1,1,0,0,5,7,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2417,Norsjö,3,2,1,0,0,1,1,2,1,0,0,3,2,1,0,0,1,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2418,Malå,1,1,0,0,0,1,0,2,1,0,0,1,1,0,0,0,1,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2421,Storuman,5,6,0,0,0,2,0,1,1,0,0,5,6,0,0,0,2,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2422,Sorsele,3,3,1,0,0,0,0,2,1,0,0,3,3,1,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2425,Dorotea,2,2,1,0,0,0,0,1,1,0,0,2,2,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2460,Vännäs,3,7,1,0,0,4,0,4,1,0,0,2,6,1,0,0,4,0,4,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2462,Vilhelmina,4,7,2,0,0,2,0,3,1,0,0,3,6,1,0,0,2,0,3,1,0,0,1,1,1,0,0,0,0,0,0,0,0
|
||||||
|
2463,Åsele,2,2,0,0,0,0,0,2,1,0,0,2,2,0,0,0,0,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2480,Umeå,50,64,4,1,0,14,3,4,1,0,2,43,56,4,1,0,10,3,4,1,0,2,7,8,0,0,0,4,0,0,0,0,0
|
||||||
|
2481,Lycksele,9,12,1,0,0,4,1,1,1,0,0,9,12,1,0,0,4,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2482,Skellefteå,34,38,7,0,0,10,2,4,1,0,0,29,32,7,0,0,7,2,4,1,0,0,5,6,0,0,0,3,0,0,0,0,0
|
||||||
|
2505,Arvidsjaur,2,4,2,0,0,1,1,2,1,0,0,2,4,2,0,0,1,1,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2506,Arjeplog,1,1,1,0,0,1,0,2,1,0,0,1,1,1,0,0,1,0,2,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2510,Jokkmokk,3,4,2,0,1,1,1,3,1,0,0,3,3,2,0,1,1,1,3,1,0,0,0,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2513,Överkalix,2,2,1,0,0,1,0,2,1,0,0,1,1,1,0,0,1,0,2,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2514,Kalix,9,10,2,0,0,2,1,2,2,0,0,8,9,2,0,0,2,1,2,2,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2518,Övertorneå,3,4,0,0,0,1,1,1,1,0,0,2,3,0,0,0,1,1,1,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2521,Pajala,5,6,2,0,0,1,1,3,1,0,0,4,5,2,0,0,1,1,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2523,Gällivare,9,11,2,0,1,3,1,3,2,0,0,9,10,2,0,1,3,1,3,2,0,0,0,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2560,Älvsbyn,3,5,1,0,0,1,1,1,1,0,0,3,5,1,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
2580,Luleå,32,48,2,0,0,13,1,3,1,0,0,27,43,2,0,0,10,1,3,1,0,0,5,5,0,0,0,3,0,0,0,0,0
|
||||||
|
2581,Piteå,20,29,1,0,0,6,1,3,1,0,0,17,26,1,0,0,6,1,3,1,0,0,3,3,0,0,0,0,0,0,0,0,0
|
||||||
|
2582,Boden,13,15,3,0,0,5,1,1,1,0,0,9,11,3,0,0,5,1,1,1,0,0,4,4,0,0,0,0,0,0,0,0,0
|
||||||
|
2583,Haparanda,5,6,2,0,0,1,0,3,1,0,0,4,5,2,0,0,1,0,3,1,0,0,1,1,0,0,0,0,0,0,0,0,0
|
||||||
|
2584,Kiruna,13,14,2,0,2,4,1,3,1,0,0,12,12,2,0,2,3,1,3,1,0,0,1,2,0,0,0,1,0,0,0,0,0
|
||||||
|
BIN
data/processed/edu_offer.rds
Normal file
BIN
data/processed/edu_offer.rds
Normal file
Binary file not shown.
BIN
data/skolenhetsadresser.xlsx
Normal file
BIN
data/skolenhetsadresser.xlsx
Normal file
Binary file not shown.
313
skolverket.R
Normal file
313
skolverket.R
Normal file
|
|
@ -0,0 +1,313 @@
|
||||||
|
# =============================================================================
|
||||||
|
# skolverket.R · Educational offer dataset by municipality
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# Sources and coverage:
|
||||||
|
# 1. data/skolenhetsadresser.xlsx Skolverket school unit registry
|
||||||
|
# (current snapshot; all Skolverket-regulated institution types)
|
||||||
|
# Sheets: Förskoleklass, Grundskola, Anpassad grundskola, Specialskola,
|
||||||
|
# Sameskola, Gymnasieskola, Anpassad gymnasieskola, Komvux
|
||||||
|
# 2. Hardcoded list higher education institutions
|
||||||
|
# Source: UKÄ (Universitetskanslersämbetet) register, ~40 institutions.
|
||||||
|
# Each institution-municipality pair is one row; multi-campus institutions
|
||||||
|
# appear under every municipality that hosts a campus.
|
||||||
|
# 3. TODO: Yrkeshögskola MYH (Myndigheten för yrkeshögskolan)
|
||||||
|
# open data at myh.se; no REST API identified so far.
|
||||||
|
# 4. TODO: Folkhögskola Folkbildningsrådet register at
|
||||||
|
# folkbildning.se; ~155 institutions across ~100+ municipalities.
|
||||||
|
#
|
||||||
|
# Note: the Skolverket planned-educations API (api.skolverket.se) was
|
||||||
|
# explored but covers only the same types as the xlsx; it is used here
|
||||||
|
# purely as an optional check in section 03.
|
||||||
|
#
|
||||||
|
# Output:
|
||||||
|
# data/processed/edu_offer.rds : municipality × indicator/count (wide)
|
||||||
|
# data/processed/edu_offer.csv : same, plain text
|
||||||
|
|
||||||
|
library(tidyverse)
|
||||||
|
library(readxl)
|
||||||
|
|
||||||
|
# 00 – Helpers -----------------------------------------------------------------
|
||||||
|
|
||||||
|
XLSX_PATH <- "data/skolenhetsadresser.xlsx"
|
||||||
|
|
||||||
|
# Standardise municipality code column to 4-char zero-padded string
|
||||||
|
add_muni_code <- function(df) {
|
||||||
|
df |>
|
||||||
|
rename(
|
||||||
|
muni_code = `BELÄGEN I KOMMUN (KOD)`,
|
||||||
|
muni_name = `BELÄGEN I KOMMUN (NAMN)`
|
||||||
|
) |>
|
||||||
|
mutate(muni_code = str_pad(as.character(muni_code), 4, "left", "0"))
|
||||||
|
}
|
||||||
|
|
||||||
|
# Public = Kommunal or Region (state-level public body); else private
|
||||||
|
categorise_ownership <- function(df) {
|
||||||
|
mutate(
|
||||||
|
df,
|
||||||
|
ownership = if_else(
|
||||||
|
HUVUDMANNATYP %in% c("Kommunal", "Region", "Statlig"),
|
||||||
|
"public",
|
||||||
|
"private"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Count units by municipality and ownership, then pivot to n_public / n_private / n_total
|
||||||
|
count_units <- function(df, type_label) {
|
||||||
|
pivoted <- df |>
|
||||||
|
add_muni_code() |>
|
||||||
|
categorise_ownership() |>
|
||||||
|
count(muni_code, muni_name, ownership) |>
|
||||||
|
pivot_wider(names_from = ownership, values_from = n, values_fill = 0L)
|
||||||
|
# Ensure both columns exist even if one ownership type is absent
|
||||||
|
if (!"public" %in% names(pivoted)) {
|
||||||
|
pivoted$public <- 0L
|
||||||
|
}
|
||||||
|
if (!"private" %in% names(pivoted)) {
|
||||||
|
pivoted$private <- 0L
|
||||||
|
}
|
||||||
|
pivoted |>
|
||||||
|
mutate(
|
||||||
|
n_total = public + private,
|
||||||
|
type = type_label,
|
||||||
|
n_public = public,
|
||||||
|
n_private = private
|
||||||
|
) |>
|
||||||
|
select(muni_code, muni_name, type, n_total, n_public, n_private)
|
||||||
|
}
|
||||||
|
|
||||||
|
# 01 – Read all xlsx sheets ----------------------------------------------------
|
||||||
|
|
||||||
|
sheet_map <- c(
|
||||||
|
"forskoleklass" = "Förskoleklass",
|
||||||
|
"grundskola" = "Grundskola",
|
||||||
|
"anpassad_grundskola" = "Anpassad grundskola",
|
||||||
|
"specialskola" = "Specialskola",
|
||||||
|
"sameskola" = "Sameskola",
|
||||||
|
"gymnasieskola" = "Gymnasieskola",
|
||||||
|
"anpassad_gymnasieskola" = "Anpassad gymnasieskola",
|
||||||
|
"komvux" = "Komvux"
|
||||||
|
)
|
||||||
|
|
||||||
|
raw <- imap(sheet_map, \(sheet_name, type_label) {
|
||||||
|
cat("Reading sheet:", sheet_name, "\n")
|
||||||
|
read_excel(XLSX_PATH, sheet = sheet_name)
|
||||||
|
})
|
||||||
|
|
||||||
|
# 02 – Count institutions per municipality and ownership -----------------------
|
||||||
|
|
||||||
|
unit_counts <- imap_dfr(raw, \(df, type_label) count_units(df, type_label))
|
||||||
|
|
||||||
|
# Komvux: additionally extract SFI-offering units as a separate indicator.
|
||||||
|
# Column "SVENSKA FÖR INVANDRARE" = "J" means the unit offers SFI.
|
||||||
|
sfi_counts <- raw[["komvux"]] |>
|
||||||
|
add_muni_code() |>
|
||||||
|
categorise_ownership() |>
|
||||||
|
filter(`SVENSKA FÖR INVANDRARE` == "J") |>
|
||||||
|
count(muni_code, muni_name, ownership) |>
|
||||||
|
pivot_wider(names_from = ownership, values_from = n, values_fill = 0L) |>
|
||||||
|
(\(x) {
|
||||||
|
if (!"public" %in% names(x)) {
|
||||||
|
x$public <- 0L
|
||||||
|
}
|
||||||
|
x
|
||||||
|
})() |>
|
||||||
|
(\(x) {
|
||||||
|
if (!"private" %in% names(x)) {
|
||||||
|
x$private <- 0L
|
||||||
|
}
|
||||||
|
x
|
||||||
|
})() |>
|
||||||
|
mutate(
|
||||||
|
n_total = public + private,
|
||||||
|
type = "sfi",
|
||||||
|
n_public = public,
|
||||||
|
n_private = private
|
||||||
|
) |>
|
||||||
|
select(muni_code, muni_name, type, n_total, n_public, n_private)
|
||||||
|
|
||||||
|
unit_counts <- bind_rows(unit_counts, sfi_counts)
|
||||||
|
|
||||||
|
# 03 – Skolverket API cross-check (optional) -----------------------------------
|
||||||
|
# The planned-educations API returns the same institution types as the xlsx.
|
||||||
|
# This block fetches the API data and reports any discrepancies between the two.
|
||||||
|
# Comment out if offline or if the xlsx is known to be current.
|
||||||
|
|
||||||
|
api_cross_check <- tryCatch(
|
||||||
|
{
|
||||||
|
cat("\nFetching Skolverket API for cross-check...\n")
|
||||||
|
base_url <- "https://api.skolverket.se/planned-educations/school-units"
|
||||||
|
|
||||||
|
fetch_page <- function(page) {
|
||||||
|
url <- paste0(base_url, "?page=", page, "&size=100")
|
||||||
|
resp <- readLines(url, warn = FALSE) |>
|
||||||
|
paste(collapse = "") |>
|
||||||
|
jsonlite::fromJSON()
|
||||||
|
resp$body
|
||||||
|
}
|
||||||
|
|
||||||
|
first <- fetch_page(0)
|
||||||
|
n_pages <- first$page$totalPages
|
||||||
|
cat(
|
||||||
|
" API reports",
|
||||||
|
first$page$totalElements,
|
||||||
|
"units across",
|
||||||
|
n_pages,
|
||||||
|
"pages\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
all_pages <- map(0:(n_pages - 1), \(p) {
|
||||||
|
if (p %% 10 == 0) {
|
||||||
|
cat(" page", p, "/", n_pages, "\n")
|
||||||
|
}
|
||||||
|
fetch_page(p)$`_embedded`$listedSchoolUnits
|
||||||
|
})
|
||||||
|
|
||||||
|
api_df <- bind_rows(all_pages) |>
|
||||||
|
transmute(
|
||||||
|
muni_code = str_pad(as.character(geographicalAreaCode), 4, "left", "0"),
|
||||||
|
ownership = if_else(
|
||||||
|
principalOrganizerType %in% c("Kommunal", "Region", "Statlig"),
|
||||||
|
"public",
|
||||||
|
"private"
|
||||||
|
),
|
||||||
|
type = map_chr(typeOfSchooling, \(t) {
|
||||||
|
if (is.null(t) || nrow(t) == 0) {
|
||||||
|
return(NA_character_)
|
||||||
|
}
|
||||||
|
t$code[1]
|
||||||
|
})
|
||||||
|
) |>
|
||||||
|
filter(!is.na(type))
|
||||||
|
|
||||||
|
api_summary <- api_df |>
|
||||||
|
count(muni_code, type, ownership, name = "n_api") |>
|
||||||
|
mutate(
|
||||||
|
type = recode(
|
||||||
|
type,
|
||||||
|
fsk = "forskoleklass",
|
||||||
|
gr = "grundskola",
|
||||||
|
gran = "anpassad_grundskola",
|
||||||
|
sp = "specialskola",
|
||||||
|
sam = "sameskola",
|
||||||
|
gy = "gymnasieskola",
|
||||||
|
gyan = "anpassad_gymnasieskola",
|
||||||
|
vuxgy = "komvux",
|
||||||
|
vuxgr = "komvux",
|
||||||
|
sfi = "sfi"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
cat(" API cross-check complete\n")
|
||||||
|
api_summary
|
||||||
|
},
|
||||||
|
error = function(e) {
|
||||||
|
message("API cross-check skipped: ", conditionMessage(e))
|
||||||
|
NULL
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# 04 – Higher education institutions (UKÄ list, hardcoded) ---------------------
|
||||||
|
# Source: UKÄ register of accredited Swedish higher education institutions.
|
||||||
|
# Each row = one institution × one municipality (multi-campus → multiple rows).
|
||||||
|
# Verify against: https://www.uka.se/om-oss/kontakt/larosaetenas-webbplatser.html
|
||||||
|
|
||||||
|
he_institutions <- tribble(
|
||||||
|
~institution , ~muni_code , ~type_he ,
|
||||||
|
# ---- State universities ----
|
||||||
|
"Uppsala University" , "0380" , "university" ,
|
||||||
|
"Stockholm University" , "0180" , "university" ,
|
||||||
|
"Lund University" , "1281" , "university" ,
|
||||||
|
"University of Gothenburg" , "1480" , "university" ,
|
||||||
|
"Umeå University" , "2480" , "university" ,
|
||||||
|
"Linköping University" , "0580" , "university" ,
|
||||||
|
"Örebro University" , "1880" , "university" ,
|
||||||
|
"Karlstad University" , "1780" , "university" ,
|
||||||
|
# ---- State specialised universities ----
|
||||||
|
"KTH Royal Institute of Technology" , "0180" , "university" ,
|
||||||
|
"Karolinska Institutet" , "0184" , "university" , # Solna
|
||||||
|
"Chalmers University of Technology" , "1480" , "university" , # private, state-grant
|
||||||
|
"SLU – Uppsala" , "0380" , "university" ,
|
||||||
|
"SLU – Umeå" , "2480" , "university" ,
|
||||||
|
"SLU – Alnarp (Lomma)" , "1262" , "university" ,
|
||||||
|
"SLU – Skara" , "1495" , "university" ,
|
||||||
|
# ---- State university colleges ----
|
||||||
|
"Blekinge Institute of Technology" , "1080" , "university_college" , # Karlskrona
|
||||||
|
"Dalarna University – Falun" , "2080" , "university_college" ,
|
||||||
|
"Dalarna University – Borlänge" , "2081" , "university_college" ,
|
||||||
|
"University of Gävle" , "2180" , "university_college" ,
|
||||||
|
"Halmstad University" , "1380" , "university_college" ,
|
||||||
|
"Kristianstad University" , "1290" , "university_college" ,
|
||||||
|
"Linnaeus University – Växjö" , "0780" , "university_college" ,
|
||||||
|
"Linnaeus University – Kalmar" , "0880" , "university_college" ,
|
||||||
|
"Malmö University" , "1280" , "university_college" ,
|
||||||
|
"Mälardalen University – Västerås" , "1980" , "university_college" ,
|
||||||
|
"Mälardalen University – Eskilstuna" , "0484" , "university_college" ,
|
||||||
|
"Mid Sweden University – Sundsvall" , "2281" , "university_college" ,
|
||||||
|
"Mid Sweden University – Östersund" , "2380" , "university_college" ,
|
||||||
|
"Södertörn University" , "0126" , "university_college" , # Huddinge
|
||||||
|
"University of Borås" , "1490" , "university_college" ,
|
||||||
|
"University of Skövde" , "1496" , "university_college" ,
|
||||||
|
"University West" , "1488" , "university_college" , # Trollhättan
|
||||||
|
# ---- Private accredited institutions ----
|
||||||
|
"Stockholm School of Economics" , "0180" , "university_college" ,
|
||||||
|
"Jönköping University" , "0680" , "university_college" ,
|
||||||
|
# ---- Art, music, design, sport ----
|
||||||
|
"Konstfack" , "0180" , "university_college" ,
|
||||||
|
"Royal University College of Music (KMH)" , "0180" , "university_college" ,
|
||||||
|
"Stockholm University of the Arts" , "0180" , "university_college" ,
|
||||||
|
"Royal Institute of Art" , "0180" , "university_college" ,
|
||||||
|
"Beckmans College of Design" , "0180" , "university_college" ,
|
||||||
|
"Swedish School of Sport and Health Sciences" , "0180" , "university_college" ,
|
||||||
|
# ---- Defence / health ----
|
||||||
|
"Swedish Defence University" , "0180" , "university_college" ,
|
||||||
|
"Sophiahemmet University" , "0180" , "university_college" ,
|
||||||
|
"Ersta Sköndal Bräcke University College" , "0180" , "university_college" ,
|
||||||
|
"Röda Korsets Högskola" , "0180" , "university_college" ,
|
||||||
|
"Newmaninstitutet" , "0380" , "university_college"
|
||||||
|
)
|
||||||
|
|
||||||
|
he_counts <- he_institutions |>
|
||||||
|
count(muni_code, type_he, name = "n_total") |>
|
||||||
|
rename(type = type_he) |>
|
||||||
|
# All Swedish HE institutions are state-funded or receive >90% public funding;
|
||||||
|
# public/private distinction used for school units does not apply here.
|
||||||
|
mutate(n_public = n_total, n_private = 0L, muni_name = NA_character_)
|
||||||
|
|
||||||
|
# 05 – Combine all sources and reshape to wide ---------------------------------
|
||||||
|
|
||||||
|
long <- bind_rows(
|
||||||
|
unit_counts,
|
||||||
|
he_counts
|
||||||
|
)
|
||||||
|
|
||||||
|
# Load the municipality reference to fill in any missing names and ensure
|
||||||
|
# all 290 m_sample municipalities appear (with 0s for absent institution types)
|
||||||
|
munis <- readRDS("data/processed/m_sample.rds") |>
|
||||||
|
select(muni_code = code, muni_name_ref = municipality)
|
||||||
|
|
||||||
|
all_types <- unique(long$type)
|
||||||
|
|
||||||
|
wide <- munis |>
|
||||||
|
cross_join(tibble(type = all_types)) |>
|
||||||
|
left_join(
|
||||||
|
long |> select(muni_code, type, n_total, n_public, n_private),
|
||||||
|
by = c("muni_code", "type")
|
||||||
|
) |>
|
||||||
|
mutate(
|
||||||
|
n_total = replace_na(n_total, 0L),
|
||||||
|
n_public = replace_na(n_public, 0L),
|
||||||
|
n_private = replace_na(n_private, 0L)
|
||||||
|
) |>
|
||||||
|
pivot_wider(
|
||||||
|
names_from = type,
|
||||||
|
values_from = c(n_total, n_public, n_private),
|
||||||
|
names_glue = "{type}_{.value}"
|
||||||
|
) |>
|
||||||
|
rename(municipality = muni_name_ref, code = muni_code)
|
||||||
|
|
||||||
|
# 06 – Save --------------------------------------------------------------------
|
||||||
|
|
||||||
|
write_rds(wide, "data/processed/edu_offer.rds")
|
||||||
|
write_csv(wide, "data/processed/edu_offer.csv")
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
# 00-Libraries -----------------------------------------------------------------
|
# 00 - Libraries ---------------------------------------------------------------
|
||||||
library(tidyverse)
|
library(tidyverse)
|
||||||
library(readxl)
|
library(readxl)
|
||||||
|
|
||||||
# 01-Import --------------------------------------------------------------------
|
# 01 - Import ------------------------------------------------------------------
|
||||||
|
|
||||||
# Municipalities_db.xlsx has two title rows before the merged-cell header:
|
# Municipalities_db.xlsx has two title rows before the merged-cell header:
|
||||||
# Row 1: "Municipalities Database" (title)
|
# Row 1: "Municipalities Database" (title)
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,11 @@
|
||||||
|
|
||||||
library(tidyverse)
|
library(tidyverse)
|
||||||
|
|
||||||
# 00-Load ----------------------------------------------------------------------
|
# 00 - Load --------------------------------------------------------------------
|
||||||
municipalities_raw <- read_rds("data/processed/m_raw.rds")
|
municipalities_raw <- read_rds("data/processed/m_raw.rds")
|
||||||
# Panel: 17 110 rows (290 municipalities × ~59 years), 257 columns.
|
# Panel: 17 110 rows (290 municipalities × ~59 years), 257 columns.
|
||||||
|
|
||||||
# 01-Characterise each variable's availability ---------------------------------
|
# 01 - Characterise each variable's availability -------------------------------
|
||||||
# 1a. Full dataset: first and last year with any non-NA value.
|
# 1a. Full dataset: first and last year with any non-NA value.
|
||||||
availability_full <- municipalities_raw |>
|
availability_full <- municipalities_raw |>
|
||||||
pivot_longer(
|
pivot_longer(
|
||||||
|
|
@ -69,7 +69,7 @@ availability_window <- municipalities_raw |>
|
||||||
window_last_year = max(year)
|
window_last_year = max(year)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 02-Extract the 2022 slice and assess NAs -------------------------------------
|
# 02 - Extract the 2022 slice and assess NAs -----------------------------------
|
||||||
m_2022 <- municipalities_raw |> filter(year == 2022)
|
m_2022 <- municipalities_raw |> filter(year == 2022)
|
||||||
n_munic <- nrow(m_2022) # 290
|
n_munic <- nrow(m_2022) # 290
|
||||||
|
|
||||||
|
|
@ -78,7 +78,7 @@ na_2022 <- m_2022 |>
|
||||||
pivot_longer(everything(), names_to = "variable", values_to = "n_na") |>
|
pivot_longer(everything(), names_to = "variable", values_to = "n_na") |>
|
||||||
mutate(pct_na = n_na / n_munic)
|
mutate(pct_na = n_na / n_munic)
|
||||||
|
|
||||||
# 03-Classify every variable ---------------------------------------------------
|
# 03 - Classify every variable -------------------------------------------------
|
||||||
variable_plan <- na_2022 |>
|
variable_plan <- na_2022 |>
|
||||||
left_join(availability_full, by = "variable") |>
|
left_join(availability_full, by = "variable") |>
|
||||||
left_join(availability_window, by = "variable") |>
|
left_join(availability_window, by = "variable") |>
|
||||||
|
|
@ -117,7 +117,8 @@ variable_plan <- na_2022 |>
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 04-Backfill A: window [2020, 2021] Per-municipality, most recent non-NA value.
|
# 04 - Backfill A: window [2020, 2021] Per-municipality, most recent non-NA
|
||||||
|
# value.
|
||||||
vars_window <- variable_plan |>
|
vars_window <- variable_plan |>
|
||||||
filter(action == "backfill_window") |>
|
filter(action == "backfill_window") |>
|
||||||
pull(variable)
|
pull(variable)
|
||||||
|
|
@ -143,7 +144,7 @@ window_wide <- window_long |>
|
||||||
select(-year) |>
|
select(-year) |>
|
||||||
pivot_wider(names_from = variable, values_from = value)
|
pivot_wider(names_from = variable, values_from = value)
|
||||||
|
|
||||||
# 05-Backfill B: census / periodic Per-municipality, year closest to 2022 in
|
# 05 - Backfill B: census / periodic Per-municipality, year closest to 2022 in
|
||||||
# either direction.
|
# either direction.
|
||||||
# (Applies to EU parliament election variables: 2024 is 2 years away,
|
# (Applies to EU parliament election variables: 2024 is 2 years away,
|
||||||
# 2019 is 3 years away, so 2024 will be selected for all municipalities.)
|
# 2019 is 3 years away, so 2024 will be selected for all municipalities.)
|
||||||
|
|
@ -174,7 +175,7 @@ census_wide <- census_long |>
|
||||||
select(-year, -distance) |>
|
select(-year, -distance) |>
|
||||||
pivot_wider(names_from = variable, values_from = value)
|
pivot_wider(names_from = variable, values_from = value)
|
||||||
|
|
||||||
# 06-Backfill C: discontinued --------------------------------------------------
|
# 06 - Backfill C: discontinued ------------------------------------------------
|
||||||
vars_disc <- variable_plan |>
|
vars_disc <- variable_plan |>
|
||||||
filter(action == "backfill_disc") |>
|
filter(action == "backfill_disc") |>
|
||||||
pull(variable)
|
pull(variable)
|
||||||
|
|
@ -196,14 +197,14 @@ disc_wide <- disc_long |>
|
||||||
select(-year) |>
|
select(-year) |>
|
||||||
pivot_wider(names_from = variable, values_from = value)
|
pivot_wider(names_from = variable, values_from = value)
|
||||||
|
|
||||||
# 07-Apply all fills (window first, then census and disc which only touch the
|
# 07 - Apply all fills (window first, then census and disc which only touch the
|
||||||
# still-NA cells, i.e. the 100%-missing variables ------------------------------
|
# still-NA cells, i.e. the 100%-missing variables ------------------------------
|
||||||
m_2022_filled <- m_2022 |>
|
m_2022_filled <- m_2022 |>
|
||||||
rows_patch(window_wide, by = "code", unmatched = "ignore") |>
|
rows_patch(window_wide, by = "code", unmatched = "ignore") |>
|
||||||
rows_patch(census_wide, by = "code", unmatched = "ignore") |>
|
rows_patch(census_wide, by = "code", unmatched = "ignore") |>
|
||||||
rows_patch(disc_wide, by = "code", unmatched = "ignore")
|
rows_patch(disc_wide, by = "code", unmatched = "ignore")
|
||||||
|
|
||||||
# 08-Remove truly empty variables and the redundant year column ----------------
|
# 08 - Remove truly empty variables and the redundant year column --------------
|
||||||
vars_drop <- variable_plan |> filter(action == "drop") |> pull(variable)
|
vars_drop <- variable_plan |> filter(action == "drop") |> pull(variable)
|
||||||
|
|
||||||
m_sample <- m_2022_filled |>
|
m_sample <- m_2022_filled |>
|
||||||
|
|
@ -215,7 +216,7 @@ na_remaining <- m_sample |>
|
||||||
pivot_longer(everything(), names_to = "variable", values_to = "n_na_final") |>
|
pivot_longer(everything(), names_to = "variable", values_to = "n_na_final") |>
|
||||||
filter(n_na_final > 0)
|
filter(n_na_final > 0)
|
||||||
|
|
||||||
# 09-Audit ---------------------------------------------------------------------
|
# 09 - Audit -------------------------------------------------------------------
|
||||||
# Combine source-year info from all three fill paths
|
# Combine source-year info from all three fill paths
|
||||||
source_years <- bind_rows(
|
source_years <- bind_rows(
|
||||||
window_source_year,
|
window_source_year,
|
||||||
|
|
@ -304,6 +305,6 @@ sampling_audit <- variable_plan |>
|
||||||
arrange(action, fill_type, desc(years_from_2022))
|
arrange(action, fill_type, desc(years_from_2022))
|
||||||
|
|
||||||
|
|
||||||
# 10-Save ----------------------------------------------------------------------
|
# 10 - Save --------------------------------------------------------------------
|
||||||
write_rds(m_sample, "data/processed/m_sample.rds")
|
write_rds(m_sample, "data/processed/m_sample.rds")
|
||||||
write_csv(sampling_audit, "data/processed/sampling_audit.csv")
|
write_csv(sampling_audit, "data/processed/sampling_audit.csv")
|
||||||
|
|
|
||||||
196
src/municipalities/02-CA.R
Normal file
196
src/municipalities/02-CA.R
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
# =============================================================================
|
||||||
|
# 02-CA.R · Correspondence Analysis of the 2022 municipal cross-section
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# Bourdieusian framework:
|
||||||
|
# - Individuals (rows): 290 Swedish municipalities
|
||||||
|
# - Active columns: population-composition counts — variables that all
|
||||||
|
# measure "number of residents with attribute X",
|
||||||
|
# sharing a common unit (persons). The active set is
|
||||||
|
# deliberately restricted to age structure,
|
||||||
|
# educational capital (Swedish-born, by gender),
|
||||||
|
# employment sector, and national origin. These
|
||||||
|
# variables together define the compositional profile
|
||||||
|
# of the municipality's resident population.
|
||||||
|
# - col.sup (a): educational provision counts — the research object
|
||||||
|
# - col.sup (b): political vote and council counts — outcomes
|
||||||
|
# - col.sup (c): infrastructure & mobility counts — dwellings,
|
||||||
|
# vehicles, commuter flows, agricultural enterprises,
|
||||||
|
# livestock; genuine counts but measuring different
|
||||||
|
# entities (not persons), so they cannot share a
|
||||||
|
# contingency table with the active columns
|
||||||
|
# - Outside CA: rates, proportions, continuous measures, and
|
||||||
|
# count variables measuring event flows (births,
|
||||||
|
# deaths, migration) → correlated with CA row
|
||||||
|
# scores post-hoc
|
||||||
|
#
|
||||||
|
# Why restrict active variables to person-counts?
|
||||||
|
# CA chi-square distance has a clean compositional interpretation only when
|
||||||
|
# all active columns measure the same type of unit. When counts of persons,
|
||||||
|
# dwellings, farms, and vehicles share a row, the row total is meaningless
|
||||||
|
# and chi-square distance becomes a weighted average of incompatible profiles.
|
||||||
|
# Restricting active columns to person-counts ensures that chi-square distance
|
||||||
|
# directly compares "what fraction of the population has attribute X?" across
|
||||||
|
# municipalities, independently of population size.
|
||||||
|
#
|
||||||
|
# Outputs:
|
||||||
|
# data/processed/ca_exploratory.rds - CA result (FactoMineR object)
|
||||||
|
# data/processed/ca_var_groups.rds - variable group lists
|
||||||
|
|
||||||
|
library(tidyverse)
|
||||||
|
library(FactoMineR)
|
||||||
|
|
||||||
|
# 00-Load ----------------------------------------------------------------------
|
||||||
|
m_sample <- read_rds("data/processed/m_sample.rds")
|
||||||
|
all_vars <- setdiff(names(m_sample), c("code", "municipality"))
|
||||||
|
|
||||||
|
# 01-Exclude genuinely uninformative variables ---------------------------------
|
||||||
|
# Removed from every part of the analysis: redundant totals, geographic areas,
|
||||||
|
# and always-zero columns that add no discriminating information.
|
||||||
|
truly_exclude <- c(
|
||||||
|
# Redundant totals (sum of components already included elsewhere)
|
||||||
|
"population",
|
||||||
|
"livestock_total",
|
||||||
|
"agricultural_enterprises_total",
|
||||||
|
"number_of_registered_passenger_cars_by_status_total",
|
||||||
|
# Geographic areas (ha / km²) — not counts of entities
|
||||||
|
"land_area_ha",
|
||||||
|
all_vars[str_detect(all_vars, "^type_of_land_")],
|
||||||
|
all_vars[str_detect(all_vars, "^use_of_land_")],
|
||||||
|
"forest", "open_land", "total_green_space",
|
||||||
|
all_vars[str_detect(all_vars, "^inland_water")],
|
||||||
|
all_vars[str_detect(all_vars, "^seawater")],
|
||||||
|
all_vars[str_detect(all_vars, "^the_four_large")],
|
||||||
|
# Always-zero columns
|
||||||
|
"number_of_owner_occupied_dwellings_by_type_of_building_other_buildings",
|
||||||
|
"number_of_owner_occupied_dwellings_by_type_of_building_special_housing"
|
||||||
|
)
|
||||||
|
|
||||||
|
analysis_vars <- setdiff(all_vars, truly_exclude)
|
||||||
|
|
||||||
|
# 02-Define variable roles -----------------------------------------------------
|
||||||
|
|
||||||
|
# (a) Educational provision: the research object → col.sup
|
||||||
|
col_sup_edu <- analysis_vars[
|
||||||
|
str_detect(analysis_vars, "^adult_students_") |
|
||||||
|
str_detect(analysis_vars, "^admitted_students_") |
|
||||||
|
str_detect(analysis_vars, "^graduates_from_") |
|
||||||
|
str_detect(analysis_vars, "^location_of_home_vs_university_")
|
||||||
|
]
|
||||||
|
|
||||||
|
# (b) Political vote and council counts → col.sup
|
||||||
|
col_sup_politics <- analysis_vars[
|
||||||
|
str_detect(analysis_vars, "^votes_by_party") |
|
||||||
|
str_detect(analysis_vars, "^entitled_to_vote_by_education_level") |
|
||||||
|
str_detect(analysis_vars, "^elected_members")
|
||||||
|
]
|
||||||
|
|
||||||
|
# (c) Infrastructure, mobility, and event counts → col.sup
|
||||||
|
# Genuine counts but measuring dwellings, vehicles, farms, animals, or flows
|
||||||
|
# rather than resident persons. Projecting them as supplementary shows how
|
||||||
|
# they relate to the population-composition space without distorting it.
|
||||||
|
col_sup_infra <- analysis_vars[
|
||||||
|
str_detect(analysis_vars,
|
||||||
|
"^number_of_(rented|tenant_owned|owner_occupied)_dwellings") |
|
||||||
|
str_detect(analysis_vars, "^number_of_registered_passenger_cars_") |
|
||||||
|
str_detect(analysis_vars, "^workplaces_") |
|
||||||
|
str_detect(analysis_vars, "^agricultural_enterprises_") |
|
||||||
|
str_detect(analysis_vars, "^livestock_") |
|
||||||
|
analysis_vars %in% c(
|
||||||
|
"sex_men", "sex_women",
|
||||||
|
"employment_by_gender_men", "employment_by_gender_women",
|
||||||
|
"number_of_inmigrations", "number_of_outmigrations",
|
||||||
|
"births", "deaths", "marriages", "divorces",
|
||||||
|
"buildings", "buildings_for_seasonal_use",
|
||||||
|
"concentrations_of_holiday_homes", "holiday_home_areas",
|
||||||
|
"social_assistance_number_of_receiver_households",
|
||||||
|
"urban_residences_proximity_to_public_green_areas_500_meters_or_less",
|
||||||
|
"number_of_localities"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
col_sup_vars <- c(col_sup_edu, col_sup_politics, col_sup_infra)
|
||||||
|
|
||||||
|
# Active: population-composition person-counts
|
||||||
|
# All five groups answer "how many residents have attribute X?" and therefore
|
||||||
|
# share a common denominator — the resident population. No pre-processing
|
||||||
|
# or proportionalization is needed: CA row-profile normalisation already
|
||||||
|
# removes the size effect, and chi-square distance directly compares
|
||||||
|
# compositional profiles.
|
||||||
|
active_vars <- analysis_vars[
|
||||||
|
(str_detect(analysis_vars, "^age_") |
|
||||||
|
str_detect(analysis_vars, "^education_level_of_swedish_men_") |
|
||||||
|
str_detect(analysis_vars, "^education_level_of_swedish_women_") |
|
||||||
|
str_detect(analysis_vars, "^employment_by_activity_sectors_") |
|
||||||
|
str_detect(analysis_vars, "^birth_country_")) &
|
||||||
|
!analysis_vars %in% col_sup_vars
|
||||||
|
]
|
||||||
|
|
||||||
|
# Everything else → post-hoc correlations with CA dimensions
|
||||||
|
outside_ca <- setdiff(analysis_vars, c(active_vars, col_sup_vars))
|
||||||
|
|
||||||
|
cat(
|
||||||
|
"Active (person-count population composition): ", length(active_vars), "\n",
|
||||||
|
"col.sup – educational provision: ", length(col_sup_edu), "\n",
|
||||||
|
"col.sup – political vote counts: ", length(col_sup_politics), "\n",
|
||||||
|
"col.sup – infrastructure / event counts: ", length(col_sup_infra), "\n",
|
||||||
|
"Outside CA (rates / continuous / other): ", length(outside_ca), "\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 03-Build CA matrix -----------------------------------------------------------
|
||||||
|
X <- m_sample |>
|
||||||
|
select(all_of(c(active_vars, col_sup_vars))) |>
|
||||||
|
as.data.frame()
|
||||||
|
rownames(X) <- m_sample$municipality
|
||||||
|
|
||||||
|
na_count <- sum(is.na(X))
|
||||||
|
if (na_count > 0) {
|
||||||
|
cat("\nReplacing", na_count, "NAs with 0 in the CA matrix\n")
|
||||||
|
X[is.na(X)] <- 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
idx_sup <- seq(length(active_vars) + 1L, ncol(X))
|
||||||
|
|
||||||
|
# 04-Run CA --------------------------------------------------------------------
|
||||||
|
ca <- CA(X, ncp = 10, col.sup = idx_sup, graph = FALSE)
|
||||||
|
|
||||||
|
cat("\nEigenvalues (first 10 dimensions):\n")
|
||||||
|
print(round(ca$eig[1:10, ], 3))
|
||||||
|
|
||||||
|
contribs <- ca$col$contrib |>
|
||||||
|
as.data.frame() |>
|
||||||
|
rownames_to_column("variable")
|
||||||
|
|
||||||
|
for (d in paste0("Dim ", 1:5)) {
|
||||||
|
top5 <- contribs |> slice_max(.data[[d]], n = 5) |> pull(variable)
|
||||||
|
cat("\nTop contributors Dim", str_extract(d, "[0-9]+"), ":\n")
|
||||||
|
cat(paste0(" ", top5), sep = "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
# 05-Post-hoc: correlate row scores with outside-CA variables ------------------
|
||||||
|
ca_row_coords <- as.data.frame(ca$row$coord)
|
||||||
|
|
||||||
|
outside_data <- m_sample |>
|
||||||
|
select(all_of(outside_ca)) |>
|
||||||
|
mutate(across(everything(), \(x) {
|
||||||
|
m <- mean(x, na.rm = TRUE)
|
||||||
|
replace_na(x, if (is.finite(m)) m else 0)
|
||||||
|
}))
|
||||||
|
|
||||||
|
posthoc_cor <- cor(ca_row_coords, outside_data, use = "pairwise.complete.obs") |>
|
||||||
|
as.data.frame() |>
|
||||||
|
rownames_to_column("dimension")
|
||||||
|
|
||||||
|
# 06-Save ----------------------------------------------------------------------
|
||||||
|
write_rds(ca, "data/processed/ca_exploratory.rds")
|
||||||
|
write_rds(
|
||||||
|
list(
|
||||||
|
active = active_vars,
|
||||||
|
edu = col_sup_edu,
|
||||||
|
politics = col_sup_politics,
|
||||||
|
infra = col_sup_infra,
|
||||||
|
outside = outside_ca
|
||||||
|
),
|
||||||
|
"data/processed/ca_var_groups.rds"
|
||||||
|
)
|
||||||
|
write_csv(posthoc_cor, "data/processed/ca_posthoc_cor.csv")
|
||||||
117
src/municipalities/03-attainment-ts.R
Normal file
117
src/municipalities/03-attainment-ts.R
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
# =============================================================================
|
||||||
|
# 03-attainment-ts.R · Educational attainment time-series from SCB PxWeb
|
||||||
|
# =============================================================================
|
||||||
|
#
|
||||||
|
# Table: UF0506B/Utbildning
|
||||||
|
# "Befolkning 16-74 år efter region, utbildningsnivå, ålder och kön. År 1985-2025"
|
||||||
|
#
|
||||||
|
# We fetch ages 25-64 (standard working-age window for education attainment),
|
||||||
|
# both genders, all 7 SUN education levels, for selected years.
|
||||||
|
# The API imposes an undocumented cell limit (~150k per request), so we
|
||||||
|
# batch across municipalities (50 per request).
|
||||||
|
#
|
||||||
|
# Education levels (SUN):
|
||||||
|
# 1-2 = förgymnasial (pre-secondary)
|
||||||
|
# 3-4 = gymnasial (upper secondary)
|
||||||
|
# 5-6 = eftergymnasial (post-secondary, non-doctoral)
|
||||||
|
# 7 = forskarutbildning (doctoral)
|
||||||
|
#
|
||||||
|
# Output: data/processed/attainment_ts.rds
|
||||||
|
# Columns: code, year, edu_level (1-7), n
|
||||||
|
# Also a summary: code, year, pct_postsec, n_25_64
|
||||||
|
|
||||||
|
library(tidyverse)
|
||||||
|
library(httr)
|
||||||
|
library(jsonlite)
|
||||||
|
|
||||||
|
SCB_URL <- "https://api.scb.se/OV0104/v1/doris/sv/ssd/START/UF/UF0506/UF0506B/Utbildning"
|
||||||
|
|
||||||
|
YEARS <- c("2000", "2005", "2010", "2015", "2022")
|
||||||
|
LEVELS <- as.list(as.character(1:7))
|
||||||
|
AGES <- as.list(as.character(25:64))
|
||||||
|
GENDERS <- list("1", "2")
|
||||||
|
|
||||||
|
# Fetch municipality codes from table metadata
|
||||||
|
meta <- fromJSON(content(GET(SCB_URL), "text", encoding = "UTF-8"))
|
||||||
|
munis <- Filter(\(x) nchar(x) == 4, meta$variables$values[[1]])
|
||||||
|
cat("Municipalities to fetch:", length(munis), "\n")
|
||||||
|
|
||||||
|
# Batch fetch: 50 municipalities per request
|
||||||
|
fetch_batch <- function(muni_batch) {
|
||||||
|
query <- list(
|
||||||
|
query = list(
|
||||||
|
list(code = "Region",
|
||||||
|
selection = list(filter = "item", values = as.list(muni_batch))),
|
||||||
|
list(code = "Alder",
|
||||||
|
selection = list(filter = "item", values = AGES)),
|
||||||
|
list(code = "UtbildningsNiva",
|
||||||
|
selection = list(filter = "item", values = LEVELS)),
|
||||||
|
list(code = "Kon",
|
||||||
|
selection = list(filter = "item", values = GENDERS)),
|
||||||
|
list(code = "Tid",
|
||||||
|
selection = list(filter = "item", values = as.list(YEARS)))
|
||||||
|
),
|
||||||
|
response = list(format = "json")
|
||||||
|
)
|
||||||
|
resp <- POST(
|
||||||
|
SCB_URL,
|
||||||
|
body = toJSON(query, auto_unbox = TRUE),
|
||||||
|
encode = "raw",
|
||||||
|
content_type("application/json"),
|
||||||
|
timeout(60)
|
||||||
|
)
|
||||||
|
if (status_code(resp) != 200) {
|
||||||
|
stop("HTTP ", status_code(resp), " for batch starting at ", muni_batch[1])
|
||||||
|
}
|
||||||
|
d <- fromJSON(content(resp, "text", encoding = "UTF-8"))$data
|
||||||
|
# key is a list of character vectors: [region, age, edu_level, gender, year]
|
||||||
|
keys <- do.call(rbind, d$key)
|
||||||
|
tibble(
|
||||||
|
code = keys[, 1],
|
||||||
|
age = as.integer(keys[, 2]),
|
||||||
|
edu_level = as.integer(keys[, 3]),
|
||||||
|
gender = as.integer(keys[, 4]),
|
||||||
|
year = as.integer(keys[, 5]),
|
||||||
|
n = as.integer(unlist(d$values))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
batches <- split(munis, ceiling(seq_along(munis) / 50))
|
||||||
|
raw_list <- vector("list", length(batches))
|
||||||
|
|
||||||
|
for (i in seq_along(batches)) {
|
||||||
|
cat(" Fetching batch", i, "/", length(batches),
|
||||||
|
"(munis", batches[[i]][1], "–", tail(batches[[i]], 1), ")\n")
|
||||||
|
raw_list[[i]] <- tryCatch(
|
||||||
|
fetch_batch(batches[[i]]),
|
||||||
|
error = function(e) {
|
||||||
|
message(" Batch ", i, " failed: ", conditionMessage(e))
|
||||||
|
NULL
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Sys.sleep(0.3)
|
||||||
|
}
|
||||||
|
|
||||||
|
raw <- bind_rows(compact(raw_list))
|
||||||
|
cat("Total rows fetched:", nrow(raw), "\n")
|
||||||
|
|
||||||
|
# Aggregate: sum across ages and genders → n per (code, year, edu_level)
|
||||||
|
attainment <- raw |>
|
||||||
|
group_by(code, year, edu_level) |>
|
||||||
|
summarise(n = sum(n, na.rm = TRUE), .groups = "drop")
|
||||||
|
|
||||||
|
# Summary: % with post-secondary education among 25-64 year olds
|
||||||
|
attainment_summary <- attainment |>
|
||||||
|
group_by(code, year) |>
|
||||||
|
summarise(
|
||||||
|
n_total = sum(n),
|
||||||
|
n_postsec = sum(n[edu_level >= 5]),
|
||||||
|
pct_postsec = 100 * n_postsec / n_total,
|
||||||
|
.groups = "drop"
|
||||||
|
)
|
||||||
|
|
||||||
|
write_rds(attainment, "data/processed/attainment_ts.rds")
|
||||||
|
write_rds(attainment_summary, "data/processed/attainment_summary.rds")
|
||||||
|
cat("Saved attainment_ts.rds and attainment_summary.rds\n")
|
||||||
|
cat("Years:", paste(sort(unique(attainment$year)), collapse = ", "), "\n")
|
||||||
|
cat("Municipalities:", n_distinct(attainment$code), "\n")
|
||||||
Loading…
Add table
Reference in a new issue