Skip to contents

A simulated dataset of 412 District 7 residents measured on MobilityScore — the algorithmic metric determining likelihood of district reclassification — broken down by gender and age group. The dataset is designed to illustrate two-way analysis of variance (ANOVA) with an interaction term, estimated marginal means, pairwise contrasts, and the interpretation of a disordinal interaction.

What is MobilityScore?

MobilityScore is a continuous index (0–100, approximately Gaussian) computed weekly by QuantumCorp's LifeCalc engine. It represents the algorithmically estimated probability that a resident will change their registered district within the next 12 months. Higher values indicate greater predicted mobility and unlock access to district reclassification applications, retraining programme eligibility, and cross-district employment contracts.

MobilityScore is not a neutral measure. It is trained on 14 years of historical movement data from WaszKrak districts — data in which movement patterns were themselves shaped by economic opportunity, algorithmic resource allocation, and structural inequality. The model does not predict who will move. It predicts who moved in the past, under conditions that no longer exist, and projects that pattern forward as if it were destiny.

The story

The request came from Marek Kowalski — a District 7 resident whose daughter had been approved for mobility reclassification while he had not, despite living in the same building, in the same district, with broadly similar circumstances. He wanted to understand why the same district produces different people.

Three days after Beta wrote back, 412 records arrived — neighbours, friends, relatives, people who had passed Marek's message along the corridor and down the stairs of a building where the elevator had been broken since 2044.

Beta ran the descriptives first. The district mean was 31.4, SD 11.2. Roughly Gaussian, slightly left-skewed. She split by gender. Men: 32.1. Women: 30.8. Difference of 1.3 points. The t-test returned p = 0.26. Not significant.

She split by age group instead. Under 35: 35.7. Thirty-five and over: 27.9. That was significant.

Then she built the two-way ANOVA. Gender and age group as factors. Gender alone: not significant. Age alone: highly significant. But the interaction — gender times age group — was almost as strong as the age effect itself.

She pulled the estimated marginal means.

Young women: 38.9. Young men: 32.4. Older women: 22.7. Older men: 33.1.

The lines didn't just diverge. They crossed.

"Young women are more mobile," Bit said slowly, working through it. "Because the algorithm sees them moving — for work, for partners, for whatever reason the training data said young women from poor districts move. It rewards the pattern it already knows."

"And then they stop moving," Beta said. "Or they move differently. For themselves, not for the patterns the model was trained on. And the algorithm stops seeing it as mobility."

"It's not predicting the future," Beta said. "It's photocopying the past."

The disordinal interaction

The interaction between gender and age_group is disordinal: the ranking of gender groups reverses between age categories. Among residents under 35, women have higher MobilityScore than men. Among residents 35 and over, men have higher MobilityScore than women. This reversal is visible as crossing lines in the interaction plot and is the strongest possible form of interaction — it means that no single statement about the effect of gender is true across all age groups simultaneously.

The gender main effect, examined in isolation, is not significant (F(1, 408) = 1.42, p = 0.234). A researcher stopping at main effects would conclude that gender plays no role in MobilityScore in District 7. This conclusion would be precisely wrong.

Key results (approximate)

GroupEstimated marginal mean95% CI
Female, young38.937.3, 40.5
Male, young32.430.8, 34.0
Female, older22.721.3, 24.1
Male, older33.131.7, 34.5

The contrast female older vs male older: estimate = −10.4, t(408) = −10.30, p < .0001. The system does not freeze men in place as they age. It freezes women.

Usage

mobility

Format

A data frame with 412 rows and 4 variables:

score

Numeric (0–100). MobilityScore at the time of measurement, derived from QuantumCorp's LifeCalc engine. Represents the algorithmically estimated probability of district change within 12 months, expressed as a 0–100 index. The District 7 population mean is approximately 31.4 (SD ≈ 11.2). Values above 48 are required for TierCare Tier 2 reclassification eligibility. The distribution is approximately Gaussian with a slight left skew, reflecting the structural floor imposed by prior_flag and district assignment.

district

Character. District identifier. All observations in this dataset are from "D7" (District 7, formerly Grójec — the district where Beta and Bit live and work). The variable is retained for compatibility with multi-district datasets and to allow merging with lifecalc.

gender

Factor with 2 levels: "female" and "male". Self-reported gender as recorded in the LifeCalc registration system. The gender main effect on MobilityScore is not significant when examined alone (p = 0.234), but is strongly moderated by age group — the interaction term is highly significant (F(1, 408) = 69.18, p < 0.001), producing a disordinal crossing of group means.

age_group

Factor with 2 levels: "young" (under 35) and "older" (35 and over). The cut point of 35 corresponds to the approximate age at which the LifeCalc model's treatment of gender reverses direction, as identified through the interaction plot. Below 35, young women have higher MobilityScore than young men — because the training data shows young women from poor districts moving for work and partnership, patterns the algorithm rewards. Above 35, older women have substantially lower MobilityScore than older men — because their movement history is dominated by care work and gig employment, both of which the algorithm either cannot see or classifies as instability rather than mobility.

Source

Simulated dataset generated by data-raw/generate_mobility.R. The data structure is based on the chapter "The Crossing" in Equations from District 7: A Practical Guide to Linear Models (BetaBit StatPunk universe). All values are fictional. The dataset was constructed from 412 records shared by District 7 residents at the request of Marek Kowalski, mathematics teacher, father, and the man whose building elevator has been broken since 2044.

See also

  • curiosity for the one-way ANOVA dataset

  • lifecalc for the full LifeCalc social scoring dataset including MobilityScore as one of 24 predictors

  • vignette("two-way-anova", package = "RougeLM") for a worked example including interaction plots, emmeans, and disordinal interaction interpretation

Examples

data(mobility)

# District mean and SD
mean(mobility$score)
#> [1] 56.78641
sd(mobility$score)
#> [1] 31.16666

# Main effect of gender alone — not significant
t.test(score ~ gender, data = mobility)
#> 
#> 	Welch Two Sample t-test
#> 
#> data:  score by gender
#> t = 0.021462, df = 404.97, p-value = 0.9829
#> alternative hypothesis: true difference in means between group female and group male is not equal to 0
#> 95 percent confidence interval:
#>  -5.984475  6.116588
#> sample estimates:
#> mean in group female   mean in group male 
#>             56.81735             56.75130 
#> 

# Main effect of age group alone — significant
t.test(score ~ age_group, data = mobility)
#> Error in eval(predvars, data, env): object 'age_group' not found

# Two-way ANOVA with interaction
model <- aov(score ~ gender * age_group, data = mobility)
#> Error in eval(predvars, data, env): object 'age_group' not found
summary(model)
#> Error: object 'model' not found

# Estimated marginal means — the crossing is visible here
if (requireNamespace("emmeans", quietly = TRUE)) {
  library(emmeans)
  emm <- emmeans(model, ~ gender * age_group)
  emm

  # Interaction plot
  emmip(model, gender ~ age_group, CIs = TRUE)

  # Pairwise contrasts
  contrast(emm, interaction = "pairwise")
}
#> Error: object 'model' not found

# Effect sizes
if (requireNamespace("effectsize", quietly = TRUE)) {
  effectsize::eta_squared(model, partial = TRUE)
}
#> Error: object 'model' not found

# Visualise the disordinal interaction
if (requireNamespace("ggplot2", quietly = TRUE)) {
  library(ggplot2)

  # Raw data with group means
  group_means <- aggregate(score ~ gender + age_group,
                           data = mobility, FUN = mean)

  ggplot(mobility,
         aes(x = age_group, y = score,
             colour = gender, group = gender)) +
    geom_jitter(alpha = 0.2, width = 0.08) +
    stat_summary(fun = mean, geom = "line",  linewidth = 1.4) +
    stat_summary(fun = mean, geom = "point", size = 4) +
    geom_hline(yintercept = 31.4,
               linetype = "dotted", colour = "grey50") +
    scale_colour_manual(values = c(
      "female" = "#c4521a",
      "male"   = "#a8d4f5"
    )) +
    scale_x_discrete(labels = c("young" = "Under 35",
                                 "older" = "35 and over")) +
    annotate("text", x = 1.5, y = 29,
             label = "District 7 mean (31.4)",
             colour = "grey50", size = 3.5) +
    labs(title    = "MobilityScore: Gender \u00d7 Age Group",
         subtitle = "District 7, n = 412. Lines cross \u2014 disordinal interaction.",
         x        = "Age group",
         y        = "MobilityScore",
         colour   = "Gender") +
    theme_minimal(base_size = 13) +
    theme(legend.position = "top")
}
#> Error in eval(predvars, data, env): object 'age_group' not found