# =====================================================================
# Mobility in Germany 2023 (MiD 2023) analysis: Driving Licences, Mileage and
# Travel Behaviour of the Population Aged 65+
#
# Replication code for: https://doi.org/10.1016/j.tranpol.2026.104493
#
# Data source:  MiD 2023, Standard data package (B1 v1.1) 
#               infas, DLR, IVT & infas 360 (2025) on behalf of the
#               German Federal Ministry for Transport (BMV).
#               Obtained from https://mobilithek.info/offers/897527161045086208. 
#               Not redistributable.
#
# Required input files (place in the working directory):
#   - MiD2023_Personen.csv   (person-level file)
#   - MiD2023_Wege.csv       (trip-level file)
#
# Outputs:
#   - result_modes_sm.xlsx
#   - result_modes_sm_geo.xlsx
#   - MiD_65plus_FS_RegioStaR2_DistClass_Summary.xlsx
#
# Tested with R 4.6.1, tidyverse 2.0.0, writexl 2.0.1
# =====================================================================

rm(list = ls())

library(tidyverse)
library(writexl)

#--- Set own working directory ----------------------------------------
setwd(".....") 

# =====================================================================
# 1. CONFIGURATION
# =====================================================================

# --- Paths -----------------------------------------------------------
path_persons <- "MiD2023_Personen.csv"
path_trips   <- "MiD2023_Wege.csv"
path_output  <- "."

# --- Target ages for the age-specific licence/mileage profile --------
# Ages 65-80 are available exactly; HP_ALTER == 85 pools everyone 85+,
# so 85, 90 and 95 all receive the same pooled rate.
target_ages      <- c(65, 70, 75, 80, 85, 90, 95)
ages_exact       <- c(65, 70, 75, 80)
ages_pooled_85up <- c(85, 90, 95)

# --- MiD missing-value codes for P_FS_PKW (car driving licence) ------
# 1   = yes
# 2   = no
# 9   = no answer                 -> redistribute proportionally
# 206 = proxy interview, adult    -> redistribute proportionally
# 403 = person under 16           -> redistribute ONLY within 65+ subsets,
#                                    where it cannot occur; treated as a
#                                    definite "no" for all-age subsets
#                                    (see calc_licence_share_allages()).
LICENCE_VALID   <- c(1, 2)
LICENCE_MISSING <- c(9, 206, 403)

# --- External population figures, Germany, 2024 (Destatis) -----------
population_2024_by_age <- tibble(
  age            = c(65, 70, 75, 80, 85, 90, 95),
  population_tot = c(
    1169242,  # aged 65
    949236,   # aged 70
    797432,   # aged 75
    607141,   # aged 80
    582919,   # aged 85
    233324,   # aged 90
    58459     # aged 95
  )
)

pop_65plus_2024 <- 19005074
pop_17plus_2024 <- 70395447
pop_18plus_2024 <- 69602098
pop_total_2024  <- 83577140


# =====================================================================
# 2. LOAD DATA
# =====================================================================

persons <- read.csv(path_persons, sep = ",")
trips   <- read.csv(path_trips,   sep = ",")


# =====================================================================
# 3. HELPER FUNCTIONS
# =====================================================================

# ---------------------------------------------------------------------
# calc_licence_share()
#
# Share of car-licence holders in a person-level subset.
# Among those who gave a valid answer, compute the "yes" share; assume
# the same share applies to the missings and add them back in
# proportionally. Grossed-up person weights (P_HOCH) are used so that
# the share refers to the population, not to the raw sample.
#
# Use for subsets of adults only (no one under 16).
# ---------------------------------------------------------------------
calc_licence_share <- function(person_subset) {
  
  n_yes     <- sum(person_subset$P_HOCH[person_subset$P_FS_PKW == 1], na.rm = TRUE)
  n_no      <- sum(person_subset$P_HOCH[person_subset$P_FS_PKW == 2], na.rm = TRUE)
  n_missing <- sum(person_subset$P_HOCH[person_subset$P_FS_PKW %in% LICENCE_MISSING],
                   na.rm = TRUE)
  
  n_valid   <- n_yes + n_no
  share_yes <- n_yes / n_valid
  
  licence_share <- (n_yes + share_yes * n_missing) / (n_valid + n_missing)
  
  tibble(
    licence_share     = licence_share,
    n_persons_unweigh = nrow(person_subset),
    pct_missing       = n_missing / (n_valid + n_missing) * 100
  )
}

# ---------------------------------------------------------------------
# calc_licence_share_allages()
#
# Same logic, but for subsets that include people under 16. For those,
# code 403 is not an unknown value but a definite "no" (nobody under 16
# holds a car licence), so it is added to the "no" group instead of being
# redistributed. Only codes 9 and 206 remain genuine missings.
# ---------------------------------------------------------------------
calc_licence_share_allages <- function(person_subset) {
  
  n_yes <- sum(person_subset$P_HOCH[person_subset$P_FS_PKW == 1], na.rm = TRUE)
  n_no  <- sum(person_subset$P_HOCH[person_subset$P_FS_PKW == 2],   na.rm = TRUE) +
    sum(person_subset$P_HOCH[person_subset$P_FS_PKW == 403], na.rm = TRUE)
  
  n_missing <- sum(person_subset$P_HOCH[person_subset$P_FS_PKW %in% c(9, 206)],
                   na.rm = TRUE)
  
  n_valid   <- n_yes + n_no
  share_yes <- n_yes / n_valid
  
  licence_share <- (n_yes + share_yes * n_missing) / (n_valid + n_missing)
  
  tibble(
    licence_share     = licence_share,
    n_persons_unweigh = nrow(person_subset),
    pct_missing       = n_missing / (n_valid + n_missing) * 100
  )
}

# ---------------------------------------------------------------------
# calc_mileage_rate()
#
# Average car-driver kilometres per licence holder per day.
#
# Step 1: driver share among trips where the driver/passenger role is
#         known; used to scale ambiguous car trips.
# Step 2: assign driver kilometres per trip (pkw_fmf):
#           1   driver                -> full distance
#           2   passenger             -> 0
#           3   both (driver change)  -> half distance
#           706 no car used           -> 0
#           701/9/708 role unknown but car used (W_VM_G == 1)
#                                     -> distance x observed driver share
# Step 3: gross up to national daily totals with trip weights (W_HOCH).
# Step 4: divide by the grossed-up number of licence holders in the same
#         subset. Numerator and denominator share the same grossing-up
#         base, so the resulting rate is independent of population size
#         and can be applied to any external population figure.
# ---------------------------------------------------------------------
calc_mileage_rate <- function(trip_subset, person_subset) {
  
  # Step 1: observed driver share
  n_driver    <- sum(trip_subset$W_GEW[trip_subset$pkw_fmf == 1], na.rm = TRUE)
  n_passenger <- sum(trip_subset$W_GEW[trip_subset$pkw_fmf == 2], na.rm = TRUE)
  driver_share <- n_driver / (n_driver + n_passenger)
  
  # Step 2: driver kilometres per trip
  trip_subset <- trip_subset %>%
    mutate(car_driver_km = case_when(
      pkw_fmf == 1                                  ~ wegkm_imp,
      pkw_fmf == 2                                  ~ 0,
      pkw_fmf == 3                                  ~ wegkm_imp * 0.5,
      pkw_fmf == 706                                ~ 0,
      pkw_fmf %in% c(701, 9, 708) & W_VM_G == 1     ~ wegkm_imp * driver_share,
      TRUE                                          ~ 0
    ))
  
  # Step 3: national daily car-driver kilometres
  car_km_day <- sum(trip_subset$car_driver_km * trip_subset$W_HOCH, na.rm = TRUE)
  
  # Step 4: grossed-up licence holders in the same subset
  n_yes     <- sum(person_subset$P_HOCH[person_subset$P_FS_PKW == 1], na.rm = TRUE)
  n_no      <- sum(person_subset$P_HOCH[person_subset$P_FS_PKW == 2], na.rm = TRUE)
  n_missing <- sum(person_subset$P_HOCH[person_subset$P_FS_PKW %in% LICENCE_MISSING],
                   na.rm = TRUE)
  n_valid        <- n_yes + n_no
  licence_holders <- n_yes + (n_yes / n_valid) * n_missing
  
  tibble(
    km_per_holder_day  = car_km_day / licence_holders,
    km_per_holder_year = (car_km_day / licence_holders) * 365,
    n_trips_unweigh    = nrow(trip_subset),
    driver_share       = driver_share
  )
}


# =====================================================================
# 4. AGE-SPECIFIC LICENCE SHARES L_i AND MILEAGE RATES M_i
# =====================================================================

# --- 4.1 Licence shares by age --------------------------------------

# Exact single ages 65, 70, 75, 80
licence_exact <- map_dfr(ages_exact, function(a) {
  calc_licence_share(persons %>% filter(HP_ALTER == a)) %>%
    mutate(age = a, mid_group = as.character(a), .before = 1)
})

# Pooled 85+ group, applied to ages 85, 90 and 95
licence_85plus_raw <- calc_licence_share(persons %>% filter(HP_ALTER == 85))

licence_85plus <- tibble(age = ages_pooled_85up) %>%
  mutate(
    mid_group         = "85+",
    licence_share     = licence_85plus_raw$licence_share,
    n_persons_unweigh = licence_85plus_raw$n_persons_unweigh,
    pct_missing       = licence_85plus_raw$pct_missing
  )

licence_shares <- bind_rows(licence_exact, licence_85plus)


# --- 4.2 Number of licence holders = share x 2024 population ---------

licence_holders_by_age <- population_2024_by_age %>%
  left_join(licence_shares, by = "age") %>%
  mutate(licence_holders = population_tot * licence_share)


# --- 4.3 Mileage rates by age ---------------------------------------

mileage_exact <- map_dfr(ages_exact, function(a) {
  calc_mileage_rate(
    trip_subset   = trips   %>% filter(HP_ALTER == a),
    person_subset = persons %>% filter(HP_ALTER == a)
  ) %>%
    mutate(age = a, mileage_source = "MiD exact", .before = 1)
})

mileage_85plus_raw <- calc_mileage_rate(
  trip_subset   = trips   %>% filter(HP_ALTER == 85),
  person_subset = persons %>% filter(HP_ALTER == 85)
)

mileage_85plus <- tibble(age = ages_pooled_85up) %>%
  mutate(
    mileage_source     = "MiD 85+ pooled",
    km_per_holder_day  = mileage_85plus_raw$km_per_holder_day,
    km_per_holder_year = mileage_85plus_raw$km_per_holder_year,
    n_trips_unweigh    = mileage_85plus_raw$n_trips_unweigh,
    driver_share       = mileage_85plus_raw$driver_share
  )

mileage_rates <- bind_rows(mileage_exact, mileage_85plus)


# --- 4.4 Combined age profile ---------------------------------------

age_profile <- licence_holders_by_age %>%
  left_join(mileage_rates, by = "age") %>%
  select(
    age,
    population_tot, licence_share, licence_holders, mid_group,
    km_per_holder_day, km_per_holder_year, mileage_source,
    n_persons_unweigh, pct_missing, n_trips_unweigh, driver_share
  )


# =====================================================================
# 5. AGGREGATE LICENCE HOLDER COUNTS
# =====================================================================
# HP_ALTER >= 65 covers ages 65-84 exactly plus the pooled 85+ category,
# i.e. the full 65+ population.

# --- 5.1 Total licence holders aged 65+  (L_tot_65plus) --------------
persons_65plus <- persons %>% filter(HP_ALTER >= 65)
licence_65plus <- calc_licence_share(persons_65plus)
licence_holders_65plus <- pop_65plus_2024 * licence_65plus$licence_share

# --- 5.2 Total licence holders aged 17+  (L_tot_17plus) --------------
persons_17plus <- persons %>% filter(HP_ALTER >= 17)
licence_17plus <- calc_licence_share(persons_17plus)
licence_holders_17plus <- pop_17plus_2024 * licence_17plus$licence_share

# --- 5.3 Licence share in the total population ----------------------
# Code 403 (under 16) is treated as a definite "no" here.
licence_total_pop <- calc_licence_share_allages(persons)


# =====================================================================
# 6. CAR OCCUPANCY, AGED 65+
# =====================================================================
# anzpers = number of people in the vehicle including the driver.
# Valid range 1-6 (6 = "6 or more"); 99 = no answer, 701 = not collected
# for regular business trips (rbW). Both are excluded.
# pkw_fmf == 1 restricts to trips where the respondent was the driver.
# W_GEW (not W_HOCH) is used because this is a mean, not a total.

occupancy_65plus <- trips %>%
  filter(
    HP_ALTER >= 65,
    pkw_fmf == 1,
    anzpers %in% 1:6
  ) %>%
  summarise(
    occupancy       = weighted.mean(anzpers, W_GEW),
    n_trips_unweigh = n()
  )


# =====================================================================
# 7. TOTAL ANNUAL CAR-DRIVER KILOMETRES
# =====================================================================
# The MiD grossing-up factors reproduce the survey-period population
# (~2023). To keep these totals consistent with the 2024 population base
# used elsewhere, the population-independent MiD rate (km per licence
# holder per day) is multiplied by the externally sourced 2024 number of
# licence holders rather than being taken directly from W_HOCH sums.

# --- 7.1 Aged 65+  (M_tot_65plus) ------------------------------------
trips_65plus <- trips %>% filter(HP_ALTER >= 65)

# Observed driver share among 65+ car trips
n_driver_65    <- sum(trips_65plus$W_GEW[trips_65plus$pkw_fmf == 1], na.rm = TRUE)
n_passenger_65 <- sum(trips_65plus$W_GEW[trips_65plus$pkw_fmf == 2], na.rm = TRUE)
driver_share_65 <- n_driver_65 / (n_driver_65 + n_passenger_65)

# Driver kilometres per trip (same rule set as calc_mileage_rate)
trips_65plus <- trips_65plus %>%
  mutate(car_driver_km = case_when(
    pkw_fmf == 1                                ~ wegkm_imp,
    pkw_fmf == 2                                ~ 0,
    pkw_fmf == 3                                ~ wegkm_imp * 0.5,
    pkw_fmf == 706                              ~ 0,
    pkw_fmf %in% c(701, 9, 708) & W_VM_G == 1   ~ wegkm_imp * driver_share_65,
    TRUE                                        ~ 0
  ))

# National daily car-driver kilometres on the MiD (~2023) population base
car_km_day_65plus <- sum(trips_65plus$car_driver_km * trips_65plus$W_HOCH,
                         na.rm = TRUE)

# Licence holders on the same (~2023) base, used as denominator
licence_holders_mid_65plus <- licence_65plus$licence_share *
  sum(persons_65plus$P_HOCH[persons_65plus$P_FS_PKW %in% c(1, 2, 9, 206, 403)],
      na.rm = TRUE)

# Population-independent rate
km_per_holder_day_65plus <- car_km_day_65plus / licence_holders_mid_65plus

# Rescale to the 2024 population
licence_holders_65plus_2024 <- pop_65plus_2024 * licence_65plus$licence_share
vkt_year_65plus <- licence_holders_65plus_2024 * km_per_holder_day_65plus * 365


# --- 7.2 Aged 18+  (M_tot_18plus) ------------------------------------
persons_18plus <- persons %>% filter(HP_ALTER >= 18)
trips_18plus   <- trips   %>% filter(HP_ALTER >= 18)

licence_18plus <- calc_licence_share(persons_18plus)
mileage_18plus <- calc_mileage_rate(trips_18plus, persons_18plus)

licence_holders_18plus_2024 <- pop_18plus_2024 * licence_18plus$licence_share
vkt_year_18plus <- licence_holders_18plus_2024 *
  mileage_18plus$km_per_holder_day * 365


# --- 7.3 Average mileage per licence holder, all adults 18+ ----------
mileage_all_drivers <- calc_mileage_rate(
  trip_subset   = trips   %>% filter(HP_ALTER >= 18),
  person_subset = persons %>% filter(HP_ALTER >= 18)
)


# =====================================================================
# 8. CARS PER LICENCE HOLDER, AGED 65+  (kappa)
# =====================================================================
# Each 65+ licence holder is assigned an equal share of the cars in their
# household: household cars / licence holders in that household. The
# weighted mean of these shares gives cars per licence holder.
#
# H_ID == 0 marks persons who cannot be linked to a household and is
# excluded. H_ANZAUTO is capped at 3 ("3 or more"); 99 = no answer.

# --- 8.1 Licence holders per household ------------------------------
licence_holders_per_hh <- persons %>%
  filter(H_ID != 0, P_FS_PKW == 1) %>%
  group_by(H_ID) %>%
  summarise(n_holders_in_hh = n(), .groups = "drop")

# --- 8.2 Car share per 65+ licence holder ---------------------------
cars_65plus <- persons %>%
  filter(
    HP_ALTER >= 65,
    P_FS_PKW == 1,
    H_ID != 0,
    H_ANZAUTO %in% 0:3          # valid values only
  ) %>%
  left_join(licence_holders_per_hh, by = "H_ID") %>%
  mutate(car_share = H_ANZAUTO / n_holders_in_hh)

# --- 8.3 Weighted mean ----------------------------------------------
# P_GEW (not P_HOCH): this is a mean, not a population total.
cars_per_licence_holder <- cars_65plus %>%
  summarise(
    cars_per_holder   = weighted.mean(car_share, P_GEW),
    n_persons_unweigh = n(),
    n_unlinked        = sum(is.na(n_holders_in_hh))
  )


# =====================================================================
# 9. MODE-SPECIFIC TRAVEL BEHAVIOUR, AGED 65+ WITH A LICENCE
# =====================================================================
# Modal split, mean trip distance and mean trip duration by main mode.
# All three are means/shares, so W_GEW is used throughout.
#
# hvm_diff1 (main mode, differentiated):
#   1 = walk, 2 = bicycle, 5 = car passenger, 6 = car driver,
#   8 = local public transport, 9 = taxi, 10 = long-distance public transport
# Modes not listed (motorcycle, lorry, other, no answer) are excluded.

mode_map <- tibble(
  hvm_diff1 = c(1, 2, 6, 5, 8, 9, 10),
  mode = c("Walk", "Bike", "Car Driver", "Car Passenger",
           "Public Transport", "Taxi", "Long-dist. PT")
) %>%
  mutate(mode = factor(mode, levels = c(
    "Walk", "Bike", "Car Driver", "Car Passenger",
    "Public Transport", "Taxi", "Long-dist. PT"
  )))

# --- 9.1 Trips of licence holders aged 65+ --------------------------
trips_65_lic <- trips %>%
  filter(
    HP_ALTER >= 65,
    P_FS_PKW == 1,
    hvm_diff1 %in% mode_map$hvm_diff1
  ) %>%
  left_join(mode_map, by = "hvm_diff1")

# --- 9.2 Modal split (share of trips by mode) -----------------------
modal_split <- trips_65_lic %>%
  group_by(mode) %>%
  summarise(n_weighted = sum(W_GEW, na.rm = TRUE), .groups = "drop") %>%
  mutate(
    share     = n_weighted / sum(n_weighted),
    share_pct = share * 100
  )

# --- 9.3 Mean trip distance -----------------------------------------
mean_distance <- trips_65_lic %>%
  group_by(mode) %>%
  summarise(
    mean_km = weighted.mean(wegkm_imp, W_GEW, na.rm = TRUE),
    .groups = "drop"
  )

# --- 9.4 Mean trip duration -----------------------------------------
# wegmin_imp1: imputed trip duration in minutes, including rbW trips.
mean_duration <- trips_65_lic %>%
  group_by(mode) %>%
  summarise(
    mean_min = weighted.mean(wegmin_imp1, W_GEW, na.rm = TRUE),
    .groups = "drop"
  )

# --- 9.5 Combined table ---------------------------------------------
modes_65_lic <- modal_split %>%
  left_join(mean_distance, by = "mode") %>%
  left_join(mean_duration, by = "mode") %>%
  select(mode, share_pct, mean_km, mean_min)

# Unweighted trip counts for reference
n_trips_by_mode <- trips_65_lic %>% count(mode, name = "n_trips_unweigh")

write_xlsx(modes_65_lic, file.path(path_output, "result_modes_sm.xlsx"))


# =====================================================================
# 10. IMMOBILITY RATE, AGED 65+ WITH A LICENCE
# =====================================================================
# Share of persons reporting no trips on their assigned reporting day.
# mobil: 0 = not mobile, 1 = mobile, 9 = mobility unknown (excluded).

immobility_65_lic <- persons %>%
  filter(
    HP_ALTER >= 65,
    P_FS_PKW == 1,
    mobil %in% c(0, 1)
  ) %>%
  summarise(
    n_immobile_weighted = sum(P_GEW[mobil == 0]),
    n_mobile_weighted   = sum(P_GEW[mobil == 1]),
    n_total_weighted    = n_immobile_weighted + n_mobile_weighted,
    immobility_rate     = n_immobile_weighted / n_total_weighted,
    n_persons_unweigh   = n()
  )

# =====================================================================
# 11. RegioStaR2 x DISTANCE CLASS
# =====================================================================
# Trips are split at 5 km (<=5 km vs >5 km) within each region type,
# giving four groups. The immobility rate is a person-level measure and
# therefore cannot be split by distance class; it is reported by region
# type only.

geo_map_r2 <- tibble(
  RegioStaR2 = c(1, 2),
  geo_type = c("Urban", "Rural")) %>%
  mutate(geo_type = factor(geo_type, levels = c("Urban", "Rural")))

dist_levels <- c("<=5 km", ">5 km")

# --- 11.1 Subsets ----------------------------------------------------
persons_65_lic_r2d <- persons %>%
  filter(
    HP_ALTER >= 65,
    P_FS_PKW == 1,
    RegioStaR2 %in% c(1, 2)
  ) %>%
  left_join(geo_map_r2, by = "RegioStaR2")

trips_65_lic_r2d <- trips %>%
  filter(
    HP_ALTER >= 65,
    P_FS_PKW == 1,
    RegioStaR2 %in% c(1, 2),
    hvm_diff1 %in% mode_map$hvm_diff1
  ) %>%
  left_join(geo_map_r2, by = "RegioStaR2") %>%
  left_join(mode_map,   by = "hvm_diff1") %>%
  mutate(
    dist_class = factor(ifelse(wegkm_imp <= 5, "<=5 km", ">5 km"),
                        levels = dist_levels)
  )

# --- 11.2 Sample sizes ----------------------------------------------
n_persons_r2d <- persons_65_lic_r2d %>%
  count(geo_type, name = "n_persons_unweighted")

n_trips_r2d <- trips_65_lic_r2d %>%
  count(geo_type, dist_class, name = "n_trips_unweighted")

n_trips_r2d_by_mode <- trips_65_lic_r2d %>%
  count(geo_type, dist_class, mode, name = "n") %>%
  pivot_wider(names_from = mode, values_from = n, values_fill = 0)

# --- 11.3 Modal split ------------------------------------------------
modal_split_r2d <- trips_65_lic_r2d %>%
  group_by(geo_type, dist_class, mode) %>%
  summarise(n_weighted = sum(W_GEW, na.rm = TRUE),
            n_unweigh = n(), .groups = "drop") %>%
  group_by(geo_type, dist_class) %>%
  mutate(share_pct = n_weighted / sum(n_weighted) * 100) %>%
  ungroup()

# --- 11.4 Mean distance ----------------------------------------------
mean_distance_r2d <- trips_65_lic_r2d %>%
  group_by(geo_type, dist_class, mode) %>%
  summarise(
    mean_km   = weighted.mean(wegkm_imp, W_GEW, na.rm = TRUE),
    n_unweigh = n(),
    .groups = "drop"
  )

# --- 11.5 Mean duration ----------------------------------------------
mean_duration_r2d <- trips_65_lic_r2d %>%
  group_by(geo_type, dist_class, mode) %>%
  summarise(
    mean_min  = weighted.mean(wegmin_imp1, W_GEW, na.rm = TRUE),
    n_unweigh = n(),
    .groups = "drop"
  )

# --- 11.6 Immobility rate (region type only) -------------------------
immobility_r2d <- persons_65_lic_r2d %>%
  filter(mobil %in% c(0, 1)) %>%
  group_by(geo_type) %>%
  summarise(
    n_immobile_weighted = sum(P_GEW[mobil == 0]),
    n_mobile_weighted   = sum(P_GEW[mobil == 1]),
    immobility_pct = n_immobile_weighted /
      (n_immobile_weighted + n_mobile_weighted) * 100,
    n_persons_unweighted = n(),
    .groups = "drop"
  )

# --- 11.7 Share of car-driver kilometres ------------------------------
vkt_share_r2d <- trips %>%
  filter(
    HP_ALTER >= 65,
    P_FS_PKW == 1,
    pkw_fmf == 1,
    RegioStaR2 %in% c(1, 2)
  ) %>%
  left_join(geo_map_r2, by = "RegioStaR2") %>%
  mutate(
    dist_class = factor(ifelse(wegkm_imp <= 5, "<=5 km", ">5 km"),
                        levels = dist_levels)
  ) %>%
  group_by(geo_type, dist_class) %>%
  summarise(
    km_grossed         = sum(wegkm_imp * W_HOCH, na.rm = TRUE),
    n_trips_unweighted = n(),
    .groups = "drop"
  ) %>%
  mutate(
    km_total        = sum(km_grossed),
    share_of_km_pct = km_grossed / km_total * 100
  )

# --- 11.8 Export tables ----------------------------------------------

r2d_combined_long <- modal_split_r2d %>%
  select(geo_type, dist_class, mode, share_pct) %>%
  left_join(
    mean_distance_r2d %>% select(geo_type, dist_class, mode, mean_km),
    by = c("geo_type", "dist_class", "mode")
  ) %>%
  left_join(
    mean_duration_r2d %>% select(geo_type, dist_class, mode, mean_min, n_unweigh),
    by = c("geo_type", "dist_class", "mode")
  ) %>%
  rename(n_trips_unweighted = n_unweigh)

r2d_modal_split_wide <- modal_split_r2d %>%
  select(geo_type, dist_class, mode, share_pct) %>%
  pivot_wider(names_from = mode, values_from = share_pct, values_fill = 0)

r2d_distance_wide <- mean_distance_r2d %>%
  select(geo_type, dist_class, mode, mean_km) %>%
  pivot_wider(names_from = mode, values_from = mean_km, values_fill = NA)

r2d_duration_wide <- mean_duration_r2d %>%
  select(geo_type, dist_class, mode, mean_min) %>%
  pivot_wider(names_from = mode, values_from = mean_min, values_fill = NA)

r2d_immobility <- immobility_r2d %>%
  select(geo_type, immobility_pct, n_persons_unweighted)

r2d_vkt_share <- vkt_share_r2d %>%
  select(geo_type, dist_class, share_of_km_pct, n_trips_unweighted)

r2d_sample_sizes <- n_trips_r2d %>%
  left_join(n_persons_r2d, by = "geo_type")

r2d_sample_detail <- as.data.frame(n_trips_r2d_by_mode)

write_xlsx(
  list(
    "Combined_Long"    = r2d_combined_long,
    "Modal_Split_Wide" = r2d_modal_split_wide,
    "Distance_Wide"    = r2d_distance_wide,
    "Duration_Wide"    = r2d_duration_wide,
    "Immobility"       = r2d_immobility,
    "KM_Distribution"  = r2d_vkt_share,
    "Sample_Sizes"     = r2d_sample_sizes,
    "Sample_by_Mode"   = r2d_sample_detail
  ),
  file.path(path_output, "MiD_65plus_FS_RegioStaR2_DistClass_Summary.xlsx")
)

# =====================================================================
# END OF SCRIPT
# =====================================================================

