Skip to contents

What this package is for

LASA (Longitudinal Aging Study Amsterdam) data is distributed as SPSS (.sav) files. Each file covers one “file code” – a topic or set of related questions – and one measurement “wave” – roughly, one round of data collection. The same topic is usually asked again in later waves, but with a wave-specific prefix on its variable names (e.g. blphya01 in wave B), and sometimes with slightly different numeric codes for the same answer categories. Reading a .sav file with a generic tool like haven::read_sav() gets you the raw numbers, but none of that context: you’d have to look up LASA’s own documentation by hand to know what column blphya01 even is, and repeat that work for every wave.

CleanLASA does that lookup for you. It connects three things that would otherwise all be separate, manual steps:

  1. finding the LASA topic, file code, and waves you need, and opening LASA’s own documentation for it;
  2. importing a LASA .sav file while automatically attaching the correct variable and value labels, for whichever specific file code and wave it is; and
  3. checking that the labelling worked as expected, and fixing it by hand for the rare case where it didn’t.

The main function for step 2 is read_lasa_sav(). It works out the LASA wave and file code from the file name, looks up the matching metadata in the package’s built-in label database, and applies it. If you’ve already imported the data some other way, apply_lasa_labels() does the same labelling on its own.

A typical session works through these steps roughly in order:

find topic/file code -> inspect LASA documentation -> import .sav file
        -> apply/reshape labels -> inspect matching audit -> analyse data

This vignette walks through all of it, one function at a time. The example code isn’t actually run when this vignette is built, because it needs real LASA data files and, for the documentation lookup, access to the live LASA website – neither of which is guaranteed to be available wherever the package happens to be installed.

1. Find topics, file codes, and waves

If you don’t already know the file code you need, start with lasa_topics(). It searches the package’s built-in index of LASA topics. Called with no arguments, it returns the whole index; you can narrow it down by topic name, theme, subtheme, or file code instead.

# Browse the complete LASA topic index
lasa_topics()

# Search by topic name; partial names and small typos are both fine
lasa_topics(topic = "physical act")

# Search a theme or subtheme
lasa_topics(theme = "cognitive")
lasa_topics(theme = "memory")

# Search by file code
lasa_topics("011")

# A 3-digit file code also matches processed/scaled variants that share
# the same final two digits -- this also returns the "245" row
lasa_topics("045")

Matching a topic name is forgiving on purpose: it ignores capitalization, accepts a partial name ("physical act" matches "Physical activity"), and by default also tolerates small typos through fuzzy matching. If you ever want exact matching only, turn that off with fuzzy_match = FALSE; max_edit_distance controls how many characters’ worth of typo is still considered a match (default 2).

# Exact matching only, no typo tolerance
lasa_topics(topic = "physical activity", fuzzy_match = FALSE)

# Allow slightly bigger typos than the default
lasa_topics(topic = "phisical activty", max_edit_distance = 3)

The result is a data frame with one row per topic/file-code combination, with columns theme, subtheme, topic, filecode, waves, and has_varinfo (whether LASA links a variable-information PDF for that row – see the next section). A topic that doesn’t have a file code yet, or a file code with no linked PDF, still shows up in the result rather than being left out – you’ll just see NA for filecode, or FALSE for has_varinfo.

This index is hand-maintained rather than scraped from LASA’s website (see data-raw/lasa_topic_database.R in the package source), so it only changes when the package itself is updated.

2. Open the matching LASA documentation

Once you know the topic or file code, lasa_var_info() finds and opens LASA’s own documentation for it: either the official variable-information PDF, or the topic’s page on the LASA website. Both are looked up live, by briefly visiting the LASA topic overview – no PDFs are bundled with the package itself.

# By file code
lasa_var_info("046")
lasa_var_info("LASA046")   # common variations of a file code are recognized
lasa_var_info("LASA 046")
lasa_var_info("lasa_046")

# By topic name
lasa_var_info("Physical activity")
lasa_var_info("physical act")  # fuzzy matching handles typos here too

# Open the topic's LASA webpage instead of the PDF (also accepts
# "webpage"/"website"/"browser"/"online", case-insensitively)
lasa_var_info("046", target = "web")

# Retrieve the URL without opening the document
url <- lasa_var_info("046", open = FALSE)

# Force a fresh lookup instead of using the cached link
lasa_var_info("046", refresh = TRUE)

By default, in an interactive R session, the document opens straight away in your system’s default web browser – not the RStudio Viewer, which doesn’t reliably display hosted PDFs. Set open = FALSE if you just want the URL back without opening anything.

Not every file code has a linked PDF. When that happens, lasa_var_info() automatically falls back to the topic’s LASA webpage instead and prints a message explaining why – so asking for the PDF never simply fails as long as some documentation exists. This fallback only goes one way: if you explicitly ask for the webpage (target = "web") and none can be found, that’s a normal error.

Like lasa_topics(), topic-name matching here tolerates typos through fuzzy_match (default TRUE) and max_edit_distance (default 2). The underlying link lookup itself is cached, both for the rest of your R session and on disk between sessions, so lasa_var_info() doesn’t have to re-fetch LASA’s website every time; use refresh = TRUE if you suspect a link has changed since it was cached.

3. Import and label a LASA SPSS file

read_lasa_sav() is the function you’ll use most. It reads the file with haven::read_sav(), figures out the LASA wave and file code from the file name, looks up the matching metadata in lasa_label_db(), and attaches variable and value labels.

# Regular LASA wave: LASA + wave + file code
physical_activity_b <- read_lasa_sav("LASAB046.sav")

# Information stored in a Z file
sex <- read_lasa_sav("LASAZ004.sav")

# Replenishment/migrant-cohort waves use LAS2B/LAS3B/LAS4B/LASMB
physical_activity_3b <- read_lasa_sav("LAS3B046.sav")

A Z file holds several waves side by side (boak, coak, doak, … for the same question at waves B, C, D, …). Add format_as_long = TRUE to get it in long format instead – one row per respondent per wave – as described in section 9.

The file name is what tells read_lasa_sav() which wave and file code it’s looking at, so it needs to follow LASA’s naming convention (case doesn’t matter): regular single-letter waves are named LASA[wave][filecode].sav, and the replenishment waves 2B, 3B, 4B, and MB are named LAS[wave][filecode].sav. If your file doesn’t follow this pattern, pass filecode/wave yourself (see section 6).

read_lasa_sav() always reads the file with every code intact, so CleanLASA – not whatever the .sav file happens to declare – decides which codes are missing values (see Missing-value codes below). You can pass any other haven::read_sav() argument through read_sav_args:

data <- read_lasa_sav(
  "LASAB046.sav",
  read_sav_args = list(encoding = "UTF-8")
)

(Don’t put file or user_na inside read_sav_args – those are controlled by path and keep_user_na directly, not by this list.)

Once imported, the result carries a few extra pieces of information about where it came from: which wave and file code it is, and the source file name. It also carries the full matching audit, described in section 5.

4. Choose how labelled variables are represented

By default, a single read_lasa_sav()/apply_lasa_labels() call does all of the following at once: categorical variables become R factors (to_factor), count/continuous variables become plain numbers (to_numeric), column names are standardized to one name shared across every wave and a "Wave" column is added (standardize, which also turns on add_wavecode), and the wording that’s consistent across every wave becomes each variable’s active label. Every one of these can be switched off individually if you’d rather keep the raw SPSS-style representation for some or all of them.

To see what changes, here’s the same file read the plain way first, with no CleanLASA processing at all:

data <- haven::read_sav("LASAB046.sav")
summary(data$blphya01)
#>   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
#> -5.000   4.000   4.000   3.476   4.000   4.000

attr(data$blphya01, "labels")
#> Na, interview terminated      Na, short interview           Na, wrong skip
#>                       -5                       -4                       -3
#>                  Na, see                Na, asked     Respondent bedridden
#>                       -2                       -1                        1
#> Respondent in elec. whee Respondent in mech. whee             not 1 2 or 3
#>                        2                        3                        4

This is a plain SPSS-labelled numeric vector: the values are numbers, and the labels are stored as an attribute rather than actually applied. The rest of this section shows what each CleanLASA option changes about this.

Convert labelled categories to factors

to_factor defaults to TRUE, so this already happened in the quick example above without any extra argument: documented value labels become R factor levels.

data <- read_lasa_sav("LASAB046.sav")
summary(data$lphya01)
#>           respondent bedridden respondent in elec. wheelchair
#>                             20                              5
#> respondent in mech. wheelchair                  not 1, 2 or 3
#>                             20                           2870
#>                           NA's
#>                            192

attr(data$lphya01, "labels_wave")
#> Na, interview terminated      Na, short interview           Na, wrong skip
#>                       -5                       -4                       -3
#>                  Na, see                Na, asked     Respondent bedridden
#>                       -2                       -1                        1
#> Respondent in elec. whee Respondent in mech. whee             not 1 2 or 3
#>                        2                        3                        4

The negative “Na, …” codes are missing values, so by default they become NA (see Missing-value codes below). If a non-negative value shows up in the data but LASA’s documentation has no label for it, that value is kept as its own factor level (shown as the number itself) rather than silently turned into NA – so you’ll notice an undocumented code instead of quietly losing data. Set to_factor = FALSE to leave the variable as a plain numeric/character vector instead.

Variables that mean the same thing but were coded differently

A handful of variables were coded on genuinely different numeric scales in different waves – for example a yes/no question where 0 meant “no” in one wave but 1 meant “no” in another, or an income bracket that was redefined. There’s no single set of value labels that correctly describes every wave for a variable like this, so the label database marks it var_type == "text" and doesn’t offer harmonized value labels for it at all. to_factor handles this by converting the variable to its wave-specific label text (a character vector, not a factor) instead – so even though the underlying numeric codes disagree between waves, the text itself ("no"/"yes") still lines up, and the variable can still be compared or combined across waves.

Restore count/continuous variables to plain numeric

Not every variable is categorical – some are genuine counts or continuous measures, and the label database knows the difference (a variable whose codebook has no positive/categorical codes, only negative “missing”-reason ones, is classified as numeric). to_numeric defaults to TRUE, so this also already happened above: those variables come back as ordinary numbers, with negative missing-reason codes converted to NA.

data <- haven::read_sav("LASAB046.sav")
summary(data$blphya09)
#>   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
#>  -3.00   -1.00   20.00   32.58   45.00  480.00

data <- read_lasa_sav("LASAB046.sav")
summary(data$lphya09)
#>   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
#>   1.00   15.00   30.00   44.52   60.00  480.00     799

to_factor and to_numeric work together, not against each other: in the same call, categorical variables become factors and numeric-classified variables become plain numbers.

Missing-value codes

LASA records why an answer is missing with negative codes, such as -1 = “Na, asked” or -2 = “Na, see BMOVED”. keep_user_na decides what happens to them, and it does the same thing for every variable, whatever its type:

  • keep_user_na = FALSE (the default): every missing code becomes NA, in factors, label-text variables and numeric variables alike, and factors get no level for it. This is what you want for most analyses.
  • keep_user_na = TRUE: every missing code is kept. Factors and label-text variables show the reason as text ("Na, see BMOVED"). A numeric variable can’t hold text, so it keeps the raw code (-2) as a haven_labelled_spss vector, with the reason as its value label and the code declared missing: is.na() reports it as missing, and haven::zap_missing() turns it into NA when you need to compute with it.
data <- read_lasa_sav("LASAB014.sav", keep_user_na = TRUE)

A code counts as missing when the label database flags it as one: every negative documented code, including labels such as “valid score” or “never had job” that explain why a value is missing. A negative value that isn’t documented at all counts as missing too, unless the variable is numeric and its genuine values can be negative, such as a speech-reception threshold in dB; then only its documented missing codes count, and every other negative value is kept as a measurement. Positive codes, such as “dropout” in the osteoarthritis files, are real answers and are never treated as missing.

Standardize variable names and separate the wave code

LASA variable names usually carry a wave-specific prefix – the same question might be blphya01 in wave B but clphya01 in wave C, even though it’s really the same variable. The label database’s canonical name strips that prefix off (lphya01 for both), and .standardize_names (part of standardize, described next) renames your columns to it, so the same variable has the same name whichever wave you’re working with.

standardize (default TRUE) is really three independent settings turned on together:

  • .standardize_names – rename every matched column to its canonical name, and rename the respondent-ID column (in any capitalization) to "respnr". This also inserts a "Wave" column right after respnr (the same thing add_wavecode does on its own, described below).
  • .standardize_var_labels – use the cross-wave-consistent wording as each variable’s active label, instead of that wave’s own wording.
  • .standardize_val_labels – the same idea, but for value labels: use the harmonized codebook (where LASA’s documentation defines one) instead of the wave-specific one.
data <- read_lasa_sav("LASAB046.sav") # standardize = TRUE by default

names(data)[1:4]
#> "respnr"    "Wave"      "lphya01"   "lphya02"

unique(data$Wave)
#> "B"

If you’d rather keep the original, wave-specific column names but still want to know which wave you’re looking at, add_wavecode adds the "Wave" column on its own, without renaming anything else:

data <- read_lasa_sav(
  "LASAB046.sav",
  .standardize_names = FALSE,
  add_wavecode = TRUE
)

names(data)[1:4]
#> "respnr"    "Wave"      "blphya01"  "blphya02"

unique(data$Wave)
#> "B"

Correct a known source-column name

Occasionally a source file has a typo or a nonstandard column name that doesn’t match either the wave-specific or canonical name CleanLASA expects, so it wouldn’t be matched at all on its own. If you already know which documented variable it actually is, name_corrections tells CleanLASA directly, as canonical_name = "actual name in the datafile":

data <- read_lasa_sav(
  "LASAB046.sav",
  name_corrections = c(lphya08 = "BLPYA08", 
                       lphya09 = "BLPYA9")
)

This mapping is applied before CleanLASA’s own automatic matching (exact, then case-insensitive, then fuzzy), so it always takes priority.

You often won’t need name_corrections at all: fuzzy_matching (default TRUE) already catches most typos on its own. When a documented variable can’t be found under its expected name, CleanLASA tries a best-guess (edit-distance) match against whichever of your data’s columns haven’t already been claimed by something else. A unique close match is used, and recorded as "fuzzy" in the matching audit (see the next section); if there are two equally-good candidates, CleanLASA leaves it unmatched rather than guessing.

Preserve the original coding for reference

Whatever .standardize_var_labels/.standardize_val_labels are set to, CleanLASA never throws away a matched variable’s original, wave-specific label and value labels – they’re always kept as the "wave_label" and "labels_wave" attributes. This stays true even after converting to a factor or plain number, so you can always check what R did against the original SPSS coding:

attr(data$lphya01, "wave_label")
attr(data$lphya01, "labels_wave")

5. Audit how variables were matched

Every read_lasa_sav() or apply_lasa_labels() call keeps a record of exactly how each variable was matched – or if it wasn’t. lasa_label_report() returns that record:

report <- lasa_label_report(data)
report

By default this returns the complete picture, in both directions: variables that were matched successfully, documented variables that LASA’s documentation says should be there but weren’t found in your data (direction = "documented_not_in_data"), and columns in your data that don’t correspond to anything documented (direction = "data_not_documented"). The method column records how each match was made – exact, case-insensitive exact, exact canonical, fuzzy, manual correction, not found, or undocumented column. Neither direction is ever treated as an error: an unmatched variable is simply left as-is and reported here for you to look at.

To skip straight to what might need your attention:

lasa_label_report(data, problems_only = TRUE)

Run this before you start analysing the data. It confirms the variables you expected were actually matched (and if not, is often fixable with name_corrections), and flags columns in your data that aren’t documented for this file code/wave – which might just be analysis variables you added yourself, or something the label database genuinely doesn’t cover yet. Either way, those columns were never touched by the labelling engine, so nothing is lost.

6. Apply LASA labels to an existing data frame

apply_lasa_labels() is the same labelling engine read_lasa_sav() uses, made available on its own. Use it when you’ve already imported a .sav file some other way, or need to re-apply labels after a transformation removed them.

raw <- haven::read_sav("LASAC046.sav", user_na = TRUE)

data <- apply_lasa_labels(
  raw,
  filecode = "046",
  wave = "C"
) # to_factor/to_numeric/standardize all default to TRUE

If data already carries the file-code/wave information that read_lasa_sav() (or an earlier apply_lasa_labels() call) attached to it, you can usually leave filecode/wave out entirely – CleanLASA remembers:

data <- read_lasa_sav("LASAB046.sav")

# ...perform transformations that may remove column attributes...

data <- apply_lasa_labels(data)

apply_lasa_labels() can also work out the wave from a single-valued Wave column, if one is present (the same column add_wavecode/ .standardize_names add). Still, for a data frame that didn’t originate from read_lasa_sav(), it’s most reliable to just tell it filecode and wave directly.

7. Inspect the label database

lasa_label_db() returns the metadata database CleanLASA is currently using – the bundled data shipped with the package, plus any manual corrections you’ve made (see the next section). It’s a list with four parts:

  • variables – one row per file-code/wave/variable combination: the variable’s name as it appears in that wave’s data, its canonical (shared-across-waves) name, its label for that specific wave, its cross-wave-consistent (“harmonized”) label, its type (numeric, categorical, or text), and whether its genuine values can be negative (can_be_negative).
  • value_labels – the value/code labels (what each numeric code means) exactly as documented for that one wave, with is_missing marking the missing-value codes.
  • value_labels_harmonized – the same variable’s value labels, standardized across every wave that documents it (there’s no wave column here – a row applies no matter which wave you’re looking at). Useful for combining data across waves that coded the same concept in slightly different words. A variable whose coding genuinely differs across waves (var_type == "text" in variables) has no rows here at all – see the earlier section on why.
  • manual_overrides – your own hand-authored corrections, kept separately from the bundled data (see the next section).
db <- lasa_label_db()

# Which wave-B variables are currently known for file code 046?
subset(db$variables, filecode == "046" & wave == "B")

# Inspect the corresponding value-label rows
subset(db$value_labels, filecode == "046" & wave == "B")

# The cross-wave-standardized value labels for the same variable
subset(db$value_labels_harmonized, filecode == "046" & canonical_name == "lphya01")

The package ships with a bundled snapshot of this database, hand-transcribed from LASA’s own variable-information documentation. Coverage grows, and mistakes get fixed, with package updates. If you record a manual correction (next section), CleanLASA writes a complete, updated copy of the database to a folder on your own computer, and uses that local copy in preference to the bundled one from then on – so your corrections stick around across R sessions without you having to touch the installed package itself.

8. Record a manual metadata correction

Sometimes LASA’s documentation is wrong, incomplete, or you just want a clearer label for your own use. manual_update_lasa_labels() is the escape hatch for that: it records a correction in a separate override layer, so a later package update won’t quietly wipe it out – the update only replaces the bundled data your correction sits on top of.

By default, a supplied value label is merged into the existing set for that variable, leaving everything else untouched:

manual_update_lasa_labels(
  filecode = "046",
  wave = "B",
  variable = "lphya01",
  val_labels = c(`-5` = "NA, wrong, skip",
                 `-1` = "NA, not done")
)

Set replace_val_labels = TRUE to replace the entire value-label set for that variable instead of merging into it:

manual_update_lasa_labels(
  filecode = "046",
  wave = "all",
  variable = "lphya01",
  val_labels = c(`-5` = "NA, wrong, skip",
                 `-1` = "NA, not done"),
  replace_val_labels = TRUE
)

The same function replaces a variable’s label (as opposed to its value labels) through var_label:

manual_update_lasa_labels(
  filecode = "046",
  wave = "B",
  variable = "lphya01",
  var_label = "New label"
)

var_label and val_labels can be combined in one call, and wave accepts either "all" or a vector of specific waves, so a single call can update several waves at once:

manual_update_lasa_labels(
  filecode = "046",
  wave = "all", # update every wave currently covered (B-K)
  variable = "lphya01",
  var_label = "New label",
  val_labels = c(`-5` = "NA, wrong, skip",
                 `-1` = "NA, not done")
)

manual_update_lasa_labels(
  filecode = "046",
  wave = c("B", "C", "D"), # only update these three waves
  variable = "lphya01",
  var_label = "New label",
  val_labels = c(`-5` = "NA, wrong, skip",
                 `-1` = "NA, not done")
)

Once recorded, a correction takes effect immediately: the next time you call read_lasa_sav() or apply_lasa_labels() on that file/variable, the corrected label is what gets applied.

Undo a manual correction

manual_update_lasa_labels() writes a complete local copy of the database, not just the one correction – so once that local copy exists, it’s what CleanLASA actually uses, for every file code, until you refresh it. restore_lasa_labels() is the undo button: it can discard recorded corrections (either everything, or just the filecode/wave/variable you name), and/or rebuild that local copy from whatever the currently-installed package version now ships, so a package update’s new or corrected coverage actually reaches you instead of staying hidden behind an old local copy.

# Undo one correction:
restore_lasa_labels(filecode = "046", variable = "lphya01")

# Undo every correction ever recorded locally:
restore_lasa_labels()

# Full reset: undo every correction AND rebuild the local snapshot from
# the currently-installed package's bundled database:
restore_lasa_labels(rebuild = TRUE)

9. Reshape between long and wide format

LASA data comes in two shapes. A regular wave file such as LASAB046.sav covers one wave, and read_lasa_sav() returns it in long format: one row per respondent, a "Wave" column, and column names without a wave prefix (lphya01). A Z file such as LASAZOA1.sav covers several waves in wide format: one row per respondent and a separate column for every wave – boak, coak, doak, … for knee osteoarthritis at waves B, C, D, … – sometimes next to stable variables that hold for every wave, like sex or year of birth in LASAZ004.sav.

transform_lasa_data() converts between the two. Long format is the default:

oa <- read_lasa_sav("LASAZOA1.sav")
names(oa)
#> "respnr" "Wave"   "boak"   "boah"   "coak"   "coah"   "doak"   "doah"

oa_long <- transform_lasa_data(oa)
names(oa_long)
#> "respnr" "Wave"   "Time"   "oak"    "oah"

head(oa_long, 3)
#> # A tibble: 3 x 5
#>   respnr Wave   Time oak                       oah
#>    <dbl> <chr> <int> <fct>                     <fct>
#> 1      1 B         0 no                        no
#> 2      1 C         1 dropout                   dropout
#> 3      1 D         2 dropout at previous waves dropout at previous waves

read_lasa_sav() does both steps at once with format_as_long = TRUE; the default FALSE keeps the file’s own layout:

oa_long <- read_lasa_sav("LASAZOA1.sav", format_as_long = TRUE)

In long format, "Wave" holds the LASA wave code and "Time" counts the waves since the respondent’s baseline, the wave at which their cohort joined LASA. Every baseline is Time 0, and each cohort counts its own later waves, skipping the other cohorts’ baselines:

Cohort Time 0 1 2 3 4 5 6 7 8 9 10
First B C D E F G H I J K L
Second 2B F G H I J K L
Third 3B I J K L
Migrant MB I J K L
Fourth 4B L

So wave F is Time 4 for the first cohort, but Time 1 for the second. Wave A, the LSN pilot study, isn’t part of the main cohort study: its rows keep their own "Wave" "A", without a Time. "Time" counts waves, not years, and it isn’t LASA’s own wave numbering, which LASAZ008.sav uses (t2_dat is wave B there). For time itself, use the interview dates or ages in LASAZ008.sav.

A row’s Time needs the respondent’s cohort, which the data have to show: a value at the respondent’s baseline or at a wave only their cohort takes part in (B to E for the first cohort), any row of a file of such a wave – every respondent in LASAC046.sav belongs to the first cohort, even one whose row holds nothing but missing-value codes, and every respondent in LASMB046.sav to the migrant cohort, whatever their number says – LASA’s cohort column, as in LASAZ002.sav (“cohort 1 (1992)” has baseline B, cohort 2 2B, cohort 3 3B), or the respondent number. LASA numbers its respondents with five digits, the first of which says the cohort: 1, 2, or 3 the first, 4, 5, or 6 the second, and 7, 8, or 9 the third. A 1 also numbers the LSN pilot study’s respondents, so it only counts for a respondent the data show at another wave than A, by a value there other than a missing-value code. The fourth and the migrant cohort’s numbers aren’t known yet: if they have five digits too, they say one of the first three cohorts until the rest of the data show their own. Where the rest of the data show another cohort than the number, they decide, with a warning.

The waves from F on are shared, so a file of wave F or later, read on its own, can’t tell the cohorts apart by its waves: its rows get their Time from the respondent numbers, and the rows of respondents whose number doesn’t say get no Time, with a message saying so, until the file is combined with files that do (see below), or with the cohort column:

f030 <- read_lasa_sav("LASAF030.sav", format_as_long = TRUE)  # Time by respondent number
z002 <- read_lasa_sav("LASAZ002.sav")
f030 <- transform_lasa_data(merge(f030, z002[c("respnr", "cohort")], by = "respnr"))

A respondent gets a row for each wave at which they have a value – anything but NA or blank text (which is how SPSS stores missing text). Missing-value codes become NA by default; kept with keep_user_na = TRUE, they count for the rows, but not as showing a cohort. Stable variables are repeated on each of the respondent’s rows. Each long column keeps the variable and value labels its waves share, and each wave’s own wording in its "wave_label" and "labels_wave" attributes, one entry per wave:

attr(oa_long$oak, "wave_label")
#>                          B                          C                          D
#> "symptomatic knee OA at B" "symptomatic knee OA at C" "symptomatic knee OA at D"

format = "wide" goes back to one row per respondent. prefix chooses how the wave-specific columns are named: by the wave’s letter ("Wave", the default) or by its "Time":

names(transform_lasa_data(oa_long, format = "wide"))
#> "respnr" "boak"   "boah"   "coak"   "coah"   "doak"   "doah"

names(transform_lasa_data(oa_long, format = "wide", prefix = "Time"))
#> "respnr" "cohort" "t0_oak" "t0_oah" "t1_oak" "t1_oah" "t2_oak" "t2_oah"

With prefix = "Time", the cohorts share columns: t0_oak holds each respondent’s value at their own baseline, t1_oak at their cohort’s next wave, and so on. The cohort column says which cohort each respondent belongs to, so the columns convert back to the right waves. It is LASA’s own cohort variable, coded as read_lasa_sav() reads LASAZ002.sav’s ("cohort 1 (1992)", "cohort 2 (2002)", "cohort 3 (2012)"; "4" for the fourth cohort, whose code the label database doesn’t document yet, and "migrant cohort" for the cohort that file doesn’t have), so the result merges with that file by respnr and cohort, as long as the file is read with to_factor = TRUE, the default. Data sets merged by respnr alone get cohort.x and cohort.y, which transform_lasa_data() combines again, as it combines cohort with COHORT, the name haven::read_sav() gives that file’s column:

oa_time <- transform_lasa_data(oa_long, format = "wide", prefix = "Time")
oa_time <- merge(oa_time, z002, by = c("respnr", "cohort"))
oa_long <- transform_lasa_data(oa_time)

A value needs a Time to get a Time-prefixed name, so prefix = "Time" stops with an error naming a respondent with values (other than missing-value codes) on rows without one, such as a respondent seen only at waves the cohorts share (F and later) whose number doesn’t say their cohort; prefix = "Wave" works for any data. The cohort column also needs its name: rename a cohort column of your own (birth cohorts, say) first.

Columns that came from a wide LASA file get their original type and labels back, and with prefix = "Wave" their original name too, as long as that name says which wave the column holds. LASA’s wave files give the baselines of the later cohorts (2B, 3B, 4B, and MB) the same b prefix as wave B, so in wide format these waves get the prefixes b2, b3, b4, and mb instead, as LASA’s own zoa2/zoa3 files do: the b_dm column of LASAZDC2.sav, which holds the second cohort’s baseline, becomes b2dm.

transform_lasa_data() also works after merging files – for example all 046 and 030 files of the first two cohorts. Combining files in long format is the most reliable: read each file with format_as_long = TRUE, stack the files of each file code, merge the stacks by respondent and wave, and transform the result once more, so that every row gets its Time now that the baselines show each respondent’s cohort:

read_long <- function(file) read_lasa_sav(file, format_as_long = TRUE)
waves <- c("LASAB", "LASAC", "LAS2B", "LASAF")

# The files of different waves hold different variables, so they are
# stacked with dplyr::bind_rows(); rbind() needs files with the same columns
physical_activity <- dplyr::bind_rows(lapply(paste0(waves, "046.sav"), read_long))
adl <- dplyr::bind_rows(lapply(paste0(waves, "030.sav"), read_long))
long <- merge(physical_activity, adl, by = c("respnr", "Wave"), all = TRUE)
long <- transform_lasa_data(long)
wide <- transform_lasa_data(long, format = "wide")

Reading each file with format_as_long = TRUE matters: each file’s rows get their Time while the file still shows the respondents’ cohort (every row of LASAB046.sav is a first-cohort baseline), which the stacked files may no longer show for a respondent whose rows hold nothing but missing-value codes. Merged by respondent and wave, the two "Time" columns become Time.x and Time.y, which transform_lasa_data() combines into one "Time" again. Stacking and merging also drop the columns’ labels, or keep one file’s for every row; transform_lasa_data() gives each variable the label database knows its labels back, with each wave’s own wording in "wave_label" and "labels_wave".

Files can also be merged in wide format, read without standardizing and merged by respondent – with all = TRUE, as above, or respondents missing from one of the files (such as those who dropped out after wave B) are left out:

wide <- Reduce(
  function(x, y) merge(x, y, by = "respnr", all = TRUE),
  list(
    read_lasa_sav("LASAB046.sav", standardize = FALSE),
    read_lasa_sav("LASAC046.sav", standardize = FALSE),
    read_lasa_sav("LASAB030.sav", standardize = FALSE),
    read_lasa_sav("LASAC030.sav", standardize = FALSE)
  )
)
long <- transform_lasa_data(wide)

merge() drops the attributes that say which file the data came from, though, so the values in b columns of a respondent the data show nowhere else, such as one who dropped out after wave B, are placed by their respondent number, or else by a guess (see below), which gives them no Time. Combine the baseline files of the migrant and the fourth cohort (LASMB046.sav, LAS4B046.sav), whose respondent numbers aren’t known yet, in long format, so their rows keep their wave.

To add a Z file to long data, merge its wide columns by respnr (without its "Wave" placeholder column, or read it with standardize = FALSE); transform_lasa_data() then moves each value to the row of its own wave, adding rows for waves the long data didn’t have yet – also for respondents the long data lack, whom a merge with all = TRUE gives a row without a wave. Merging data sets that each have a "Wave" column by respnr alone gives copies such as Wave.x and Wave.y, which transform_lasa_data() refuses: merge long data by respnr and "Wave". A file of stable variables only, such as LASAZ004.sav or the migrant cohort’s LASMB004.sav, has no wave dimension, so with format_as_long = TRUE it has no "Wave" column: merge it by respnr alone, and its variables are filled in on each of the respondent’s rows.

Some LASA names stand for several waves: every cohort’s baseline (B, 2B, 3B, MB, and 4B) uses the same b names, so a wide column such as blphya01 may belong to any of them. transform_lasa_data() places each respondent’s value at the wave their data show – the row it is on, the respondent’s cohort (also by their respondent number), or the file it was read from. If nothing shows it, it guesses, with a message naming the column, and a guessed wave doesn’t count as showing the respondent’s cohort (their row gets a Time only from something else, such as their respondent number) – also when the result is transformed again, as long as its columns keep their attributes (rbind(), merge(), dplyr::bind_rows(), sorting or subsetting the rows of a data frame rather than a tibble, and saving to a .sav file can drop them). To avoid guesses, combine such files in long format, where each row carries its own wave, rather than in wide format (such as LASAB046.sav and LAS2B046.sav read with standardize = FALSE).

10. A complete analysis-oriented workflow

Putting it all together, most analyses only need a handful of calls:

library(CleanLASA)

# 1. Find the relevant LASA topic and file code
lasa_topics(topic = "physical activity")

# 2. Inspect the official variable-information document if needed
lasa_var_info("046")

# 3. Import the LASA file -- to_factor/to_numeric/standardize (names,
#    variable labels, value labels) all default to TRUE, and missing-value
#    codes become NA (keep_user_na = FALSE)
data <- read_lasa_sav("LASAB046.sav")

# 4. Check only matching problems before analysis
problems <- lasa_label_report(data, problems_only = TRUE)
problems

If a file code isn’t covered by the active label database yet, read_lasa_sav() doesn’t guess or invent metadata for it – there’s no per-file fallback function it quietly switches to. Either add a deliberate correction with manual_update_lasa_labels(), or wait for a package update that adds coverage, then import (or re-apply labels to) the data again.

Function summary

Function Main purpose
lasa_topics() Search LASA topics, themes/subthemes, file codes, and available waves
lasa_var_info() Resolve and open the LASA variable-information PDF or webpage for a file code or topic
read_lasa_sav() Read a LASA .sav file, identify wave/file code, and apply database-backed labels
apply_lasa_labels() Apply the same LASA labelling engine to an existing data frame
transform_lasa_data() Reshape LASA data between wide format (Z files) and long format with Wave and Time columns
lasa_label_report() Inspect the full matching audit or only entries requiring attention
lasa_label_db() Inspect the active bundled + user-local label metadata database
manual_update_lasa_labels() Add or correct metadata in a persistent manual override layer
restore_lasa_labels() Undo manual corrections (entirely, or scoped) and/or rebuild the local database cache from the bundled one

Together, these functions make LASA’s own documentation part of your R workflow: topics and source documents stay easy to find, imported variables keep their documented meaning, every transformation stays auditable, and the metadata layer can be refreshed without you having to maintain separate labelling code for every file.