Format tables

Translate codes to text with DST’s SAS format files

Published

October 1, 2026

The format tables on the server are the authority, not this page. The examples and code lists here are a starting point. DST updates the format catalogue without this guide knowing, and a code can change meaning while keeping its number. Load the current table from E:/Formater/ and check the values in your own delivered data before you rely on a mapping.

Several of the same code lists are also published outside DST, as classifications with a CSV download, which is the version Find a variable shows.

Prerequisite: This page uses left_join() to attach format tables to your data. If you are not familiar with joins yet, read Phase 12 - Assemble and prepare the dataset first.

Many variables in DST registers are stored as codes - municipality numbers, education codes, employment codes. To give them meaningful labels you use DST’s format tables: SAS files on the server that translate codes to text.

A format table always has at least two columns:

Column Contents Example
START The raw code from your register 101 (municipality number)
Label column The corresponding text label "København"

You join the format table onto your dataset with START as the key - exactly like a dictionary lookup.

Rows in your dataset without a match in the format table get NA in the label column - no error message. Always check with sum(is.na(data$label_column)) after the join.

Where are the format tables?

E:/Formater/SAS formater i Danmarks Statistik/SAS_datasaet/

The Star (*) on the Windows desktop inside the DST server opens an HTML guide for finding the right table. DST’s written guides sit here:

E:/Formater/SAS formater i Danmarks Statistik/Vejledning mv/

The one worth reading is Brugen af SAS formater i Danmarks Statistik.pdf (~2 MB, DST Consulting). It explains the naming systematics used below and has a section per topic - Uddannelserne, Brancherne, DISCO, Sundhed, Geokoder, DREAM. The same folder holds dated older editions (2012-2019) and a set of Allokering af formatbibliotek (*).sas scripts for pointing SAS at the format catalog - those are for SAS users and are not needed from R.

Two more folders one level up are worth knowing:

E:/Formater/SAS formater i Danmarks Statistik/HTML_oversigter/   ← browsable code lists
E:/Formater/SAS formater i Danmarks Statistik/PDF_lister/        ← the same as PDF

They let you look up what a code means without loading anything into R - useful when you just need to check a single value.

Subfolders are organised by topic - including:

Geokoder/                ← municipalities, regions
Times_personstatistik/   ← employment (socio13)
Uddannelser/             ← education (hfaudd)
Brancher/                ← industries
Sundhed/                 ← health
Disced/                  ← education, SAS format catalog (see note)
... and more

Education sits in two places, and the naming is confusing. DST’s guide says the formats for the current classification, DISCED-15 (used from 2015), are in the format catalog called DISCED, while the catalog called Uddannelser holds the older classifications it replaced (Forspalte, ISCED1997 and DUN). Look in both SAS_datasaet/Disced/ and SAS_datasaet/Uddannelser/ before you pick a file; see Education level for how to tell them apart by name.

Find the specific filenames in the folder yourself. The base path and subfolder structure above are confirmed on DST. But the specific filenames and column names further down the page (e.g. c_kom_v4_t.sas7bdat, KOM_V4_T) are illustrative examples - naming varies, and you must find the right file in the relevant subfolder yourself. Use the Star guide / PDF guide to find the file, and names()/head() to see the actual column names.

Load a format table

Format tables are SAS files and are loaded with haven::read_sas():

library(haven) # read_sas - loading SAS files

mun_table <- read_sas(
  # fetch format table as data frame
  "E:/Formater/SAS formater i Danmarks Statistik/SAS_datasaet/Geokoder/c_kom_v4_t.sas7bdat"
)

Format tables are loaded directly into R’s memory (not lazy evaluation). You must collect() DuckDB data before joining with them.

Two of these lookups already exist in R. The heaven package ships them as data, so you do not have to read a SAS format file at all:

  • data(edu_code): 4,621 education codes with hfaudd mapped to categories in English and Danish, a 5-level grouping and ISCED codes.
  • kommuneRegion(kommune): converts a numeric 3-digit municipality code straight to one of the five regions (valid from 2007). It gives you the region, not the municipality name - for the name you still need the format table below.

heaven is pre-installed on DST, and elsewhere it installs from GitHub with pak::pak("tagteam/heaven") (it is not on CRAN, so install.packages() will not find it). The format tables below are still the route for every code heaven does not cover, and they are what DST itself publishes.

The tables you will use most often

Municipality names

mun_table <- read_sas(
  "E:/Formater/SAS formater i Danmarks Statistik/SAS_datasaet/Geokoder/c_kom_v4_t.sas7bdat"
)
# Columns: START (municipality number), KOM_V4_T (municipality name)
# Use for years after the 2007 municipal reform (V4)

bef_with_mun <- bef_data %>%
  collect() %>% # collect BEFORE joining with SAS file
  left_join(mun_table, by = c("kom" = "START")) # attach municipality name via the kom code

Employment status (socio13)

To get the raw code labels from DST’s format table:

socio13_table <- read_sas(
  # fetch label table from format folder
  "E:/Formater/SAS formater i Danmarks Statistik/SAS_datasaet/Times_personstatistik/n_socio13_kt.sas7bdat"
)
# Columns: START (socio13 code), N_SOCIO13_KT (label)

akm_with_labels <- akm_data %>%
  left_join(socio13_table, by = c("socio13" = "START")) # attach label per code

For analysis you usually want SEPLINE’s four main groups rather than DST’s labels (students count as working there):

library(dplyr) # mutate, case_when

akm_categorised <- akm_data %>%
  mutate(
    occupation_cat = case_when(
      # SEPLINE Table 6 main groups
      socio13 %in% c(110:114, 120, 131:135, 139, 310) ~ "A: Working",
      socio13 %in% c(210, 410) ~ "B: Unemployed",
      socio13 %in% c(220, 321, 330) ~ "C: Outside workforce",
      socio13 %in% c(322, 323) ~ "D: Retired",
      TRUE ~ "Missing/other" # 0, 420 (children under 15), no record, any other code
    )
  )

The full employment recipe, with the subgroups, is in Socioeconomic variables.

Education level (hfaudd)

hfaudd is the four-digit code for the specific education. Its level - the thing almost every analysis actually needs - lives in a separate lookup, and the filename tells you exactly which lookup you are getting.

These files are dictionaries, not data. A .sas7bdat in the format folders holds no people and no register records. It is a two-column translation table - one row per code, with the code and what it means - and they are tiny, a few hundred KB. You read one into R and left_join() it onto your own data, exactly like the municipality example above. Nothing about your cohort changes; you are only adding a column that says what a code stands for.

What on this page has not been checked on the server. The naming rules below come from DST’s own guide (Brugen af SAS formater i Danmarks Statistik) and DST’s published DISCED-15 classification. The exact filenames in the education folders have not been listed and checked for this section, so treat every filename here as a pattern to look for, not a path to copy. Run list.files() on the folder first, as in the code below.

How to read the file names. DST’s guide gives the name for turning hfaudd into the main area as AUDD_HOVED_L1L5_KT ($AUDD_HOVED_L1L5_KT in SAS, where $ marks the character version). The files you read from R carry the same parts, in lower case, with c_ or n_ in front and usually a version year:

Part Means What to pick
c_ / n_ the character or numeric version the one matching the type of hfaudd in your data, or simply c_ after converting hfaudd to text (see Character or numeric)
audd / udd afsluttet (completed) or igangværende/afbrudt (ongoing or interrupted) education audd for hfaudd (see why)
2015, 2023, … which version of the format the newest only; it covers older years too (see Four things to check)
hoved, type, niveau, fag which of the four DISCED-15 dimensions you group by (see below) hoved for short/medium/long, as SEPLINE does (see The four dimensions)
l1l5 from level 1 (the four-digit code) to level 5 (the coarsest grouping) l1l5 to go from hfaudd to the two-digit main area (see The five levels)
_k / _t / _kt what you get back: the code, the text, or both _k for analysis, _t for table labels (see Which variant to pick)

Why there are several files for what looks like one lookup. DST’s guide says it makes up to six variants of every format: character or numeric, each as _k, _t and _kt, “where possible”. Seeing five or six files with the same stem is therefore normal. They hold the same mapping and differ only in the type they expect and what they return.

Character or numeric: how to tell. Ask R what type hfaudd is in your delivery. head(0) returns the column types with zero rows, so no data is printed:

udda %>% head(0) %>% collect() %>% str()
# hfaudd: chr        -> use a c_ file
# hfaudd: num or int -> use an n_ file

The simpler rule that always works: convert hfaudd to text with as.character() and always use the c_ file, as the code further down does. Text is also the safer type for education codes, because some DISCED-15 codes start with a zero (0001 Ingen uddannelse), and stored as a number that code becomes 1 and no longer matches the lookup.

hfaudd is a completed education, so you want an audd file, not a udd one. DISCED-15 distinguishes between the code for an ongoing or interrupted education (UDD) and for a completed one (AUDD) - DST documents the two separately - and there are format files for both. The name gives it away: hfaudd is højest fuldførte AUDD, the highest completed education. A udd lookup joined to hfaudd will match some rows, miss others, and never tell you.

The four dimensions. DISCED-15 for completed education groups the same four-digit codes in four different ways, and each has its own format. Pick the one that answers your question:

Dimension (Danish) Groups education by Top level, for example Use it for
Hovedområde (hoved) the Danish main areas of education 10 Grundskole, 20 Gymnasiale uddannelser, 30 Erhvervsfaglige uddannelser, 40 KVU, 50 MVU, 60 Bachelor, 70 LVU, 80 Ph.d., 90 Uoplyst short/medium/long education; what SEPLINE uses
Uddannelsesniveau (niveau) level, lined up with the international ISCED 10 Grundskole til og med 6. klasse, 30 Gymnasiale og erhvervsfaglige, 50 KVU, 60 MVU / Bachelorer, 70 LVU comparing with ISCED-based studies; SEPLINE does not recommend its ISCED format for categorising, because e.g. 15 becomes ISCED 3
Fagområde (fag) subject, regardless of level 25 Humanistisk, 35 Samfundsvidenskab, 45 Naturvidenskab what field someone is trained in
Uddannelsestype (type) type of programme 50 Bacheloruddannelser, 55 Professionsbachelor, 85 Voksenuddannelser og kurser ordinary vs adult education

The same number means different things in different dimensions (50 is MVU in hoved and bachelor programmes in type), so a code is only meaningful together with the file it came from. Record the filename in your analysis script.

The five levels. Within a dimension, DST groups every education in a tree with five levels. Level 1 is the four-digit code you have in hfaudd; each level above it is a broader group, and level 5 is the two-digit main area. The l1l5 in a filename means “give me a level-1 code, and I return its level-5 group”. A file named l1l2 would instead return the level just above the code, which is still very detailed. For short, medium and long education you want the two-digit main areas, so an L1 → L5 file.

TipExample: one education through all five levels

Sygeplejerske, prof.bach. in the Hovedområde dimension, from DST’s DISCED-15 AUDD classification:

Level Code What it is
L1 5166 Sygeplejerske, prof.bach. - the value in hfaudd
L2 50893510 Sygepleje og sundhedspleje, MVU (eight digits)
L3 508935 Sygepleje og sundhedspleje, MVU (six digits)
L4 5089 a broader group within MVU (four digits)
L5 50 Mellemlange videregående uddannelser, MVU

An l1l5 file turns 5166 into 50; an l1l2 file would turn it into 50893510. Note that the level-4 group has four digits, like an education code, so a four-digit value is not by itself proof that you are looking at hfaudd.

library(haven) # read_sas()
library(dplyr)

# Look first - the version and the exact names vary, so do not copy a filename
# from this page. Check both folders (see the note under "Where are the format
# tables?").
fmt_root <- "E:/Formater/SAS formater i Danmarks Statistik/SAS_datasaet/"
list.files(paste0(fmt_root, "Disced/"), pattern = "audd", ignore.case = TRUE)
list.files(paste0(fmt_root, "Uddannelser/"), pattern = "audd", ignore.case = TRUE)

# Then read the newest completed-education, main-area, level 1 -> 5 file in
# the type that matches hfaudd (c_ for character, n_ for numeric), code only.
# The filename below is the pattern to look for, not a confirmed name.
udda_level <- read_sas(paste0(fmt_root, "Disced/c_audd2023_hoved_l1l5_k.sas7bdat")) # (example) EDIT to the newest file
names(udda_level) # find the START column and the label column
head(udda_level)

# Join it on, then check how many found no match
udda_data <- udda_data %>%
  mutate(hfaudd = as.character(hfaudd)) %>% # match the type in the format table
  left_join(udda_level, by = c("hfaudd" = "START"))

udda_data %>% summarise(unmatched = sum(is.na(.data[[names(udda_level)[2]]])), total = n())

Four things to check before you trust the result.

  • Use the newest version, and only that one. The folders hold several versions side by side and it is tempting to think you need one per study year. You do not. DST’s guide says the education statistics are updated every year with the newest codes backwards in time as well, so the latest version already covers the older codes. One file, for the whole study period.
  • DISCED-15 or an older classification. From 2015 DST uses DISCED-15, which replaced Forspalte, ISCED1997 and DUN. A file with no dimension in its name (for example c_audd2015_l1l5_kt) may belong to one of the older classifications, whose levels and codes are not the ones in the table above. Check the top-level codes with head() against the table before you use it.
  • Character or numeric. c_ expects a character variable, n_ a numeric one. Pick the one matching your column, or convert - a type mismatch produces a join that matches nothing and fills the column with NA without any error.
  • The unmatched count. A high number means a type mismatch or the wrong file (udd instead of audd, or an old classification).

Paths on DST. The E:/Formater/... path above is the same for everyone working on a DST research machine, so it should work as written - but check it yourself before you rely on it, and expect it to differ if you are on a hosted machine. DST’s own guide gives the network paths as \\srvfsenas1\data\formater\SAS formater i Danmarks Statistik (researcher machine) and \\srvfsenas5\formater\SAS formater i Danmarks Statistik (hosted). The full guide is the PDF “Brugen af SAS formater i Danmarks Statistik” under E:/Formater/SAS formater i Danmarks Statistik/Vejledning mv/.

Why the level has to be looked up at all - and what it costs to guess it from the digits - is in Overview of registers. The full extraction, from opening UDDA to a categorised variable, is in Socioeconomic variables, which also covers the heaven shortcut.

Which variant to pick: _k, _t or _kt

Every format comes in up to three output variants. DST’s guide illustrates them with a municipality code turned into a region, using Copenhagen municipality (101):

Suffix Returns Copenhagen (101) becomes
_k the code only 84
_t the text only Hovedstaden
_kt code and text together 84 Hovedstaden

Use _k for the analysis variable. The code is short, it sorts correctly, and it does not change if DST rewords a label in a later version. You can filter and group on it directly (%in% c("40", "50")), and a model or a case_when() written against codes keeps working.

Use _t for the labels in a finished table. Join it on at the end, when you build Table 1 or a figure, so the text never becomes something your code depends on.

Use _kt for looking, not for analysing. It is the most readable while you explore, because you see what each code means and the values still sort by code. But "40 Korte videregående uddannelser, KVU" is code and text glued into one value: filtering on it means matching the whole string, and it breaks if the wording changes.

Whichever you pick, the key is the same. All three join on START, the code in your own data. Only the returned column differs.

Tips for finding the right table

  1. Open the Star on the desktop or navigate to E:/Formater/... in File Explorer
  2. Use list.files() on the subfolder, and expect several files per format (c_/n_ times _k/_t/_kt)
  3. Load the table and look at the columns with names() and head()
names(mun_table) # see what the columns are called - find START and the label column
head(mun_table) # see the first rows and understand the structure

See also

Back to top