Introduction: Modeling Learner Growth

This report details a simulation designed to compare two distinct instructional models: a traditional Standard Learning approach and an adaptive Personalized Learning approach driven by a digital twin concept. The simulation models the proficiency growth of 8 learners across 4 different construction-related domains.

Our central question is: How does tailoring instruction to an individual’s real-time needs impact their learning trajectory and their time to achieve mastery compared to a one-size-fits-all model?


1. Simulating the Learner Population

First, we create a synthetic population of learners. Each learner is assigned an initial proficiency level and an inherent growth_rate for each domain. This growth_rate represents their natural aptitude or speed of learning in that specific subject area.

# --- Initial Data Simulation ---
set.seed(456)
learners <- 1:8
domains <- c("Building Materials and Fasteners", "Construction Plans and Documents", 
             "Principles of Site Layout", "Principles of Building Layout")

# Create initial proficiency (starting point)
learner_data <- expand.grid(learner_id = learners, domain = domains) %>%
  mutate(proficiency = runif(n(), 0.2, 0.4))

# Create an inherent growth rate (natural aptitude)
growth_rate_data <- expand.grid(learner_id = learners, domain = domains) %>%
  mutate(growth_rate = runif(n(), 0.02, 0.30))

# --- Create the Initial Digital Twin ---
get_learner_model <- function(learner_data, growth_rate_data) {
  learner_data %>%
    left_join(growth_rate_data, by = c("learner_id", "domain")) %>%
    group_by(learner_id, domain) %>%
    summarize(proficiency = mean( proficiency), growth_rate = mean(growth_rate), .groups = 'drop')
}

initial_learner_model <- get_learner_model(learner_data, growth_rate_data)

2. Defining the Instructional Models

We define functions to model how proficiency grows under each scenario. A “wave” represents one day of instruction.

Standard Learning Model

In this model, the instructional pace is tied to the group average. Learners ahead of the average are slowed down (receiving a minimal instructional boost), simulating a “teach-to-the-middle” approach where they must wait for others to catch up.

Personalized Learning Model

This model uses a digital twin approach. The instructional boost is dynamic and inversely proportional to a learner’s current proficiency. Learners who are struggling receive a larger, targeted intervention to help them catch up, while advanced learners continue to be challenged.

# --- Define the Growth Models for Each Scenario ---

# "Standard Learning" model
update_model_standard_learning <- function(learner_model, wave, max_waves) {
  learner_model %>%
    group_by(domain) %>%
    mutate(domain_avg_proficiency = mean(proficiency)) %>%
    ungroup() %>%
    mutate(
      instructional_boost = if_else(proficiency > domain_avg_proficiency, 0.005, 0.08),
      new_proficiency = proficiency + (growth_rate + instructional_boost) * (1 - proficiency),
      proficiency = pmin(new_proficiency, 1)
    ) %>%
    select(-domain_avg_proficiency, -instructional_boost, -new_proficiency)
}

# "Personalized Learning" model
update_model_personalized_learning <- function(learner_model, wave, max_waves) {
  learner_model %>%
    mutate(
      adaptive_boost = 0.40 * (1 - proficiency),
      new_proficiency = proficiency + (growth_rate + adaptive_boost) * (1 - proficiency),
      proficiency = pmin(new_proficiency, 1)
    ) %>%
    select(-adaptive_boost, -new_proficiency)
}

3. Running the Simulation

We simulate learner growth over a period of 12 days for both the Standard and Personalized Learning scenarios.

# --- Simulation Function ---
simulate_growth <- function(learner_model, waves, update_function, scenario_name) {
  growth_tracking <- list()
  growth_tracking[[1]] <- learner_model %>% mutate(wave = 0)
  current_model <- learner_model
  for (wave in 1:waves) {
    current_model <- update_function(current_model, wave, waves)
    growth_tracking[[wave + 1]] <- current_model %>% mutate(wave = wave)
  }
  bind_rows(growth_tracking) %>%
    mutate(Scenario = scenario_name)
}

# --- Run Both Simulations ---
waves <- 12 # Simulating for 12 days
standard_df <- simulate_growth(initial_learner_model, waves, update_model_standard_learning, "Standard Learning")
personalized_df <- simulate_growth(initial_learner_model, waves, update_model_personalized_learning, "Personalized Learning")

# --- Combine Results ---
combined_scenarios_df <- bind_rows(standard_df, personalized_df)

4. Analysis and Visualizations

We now analyze the results and create visualizations to compare the outcomes of the two instructional models.

Individual Growth Trajectories

This plot shows the proficiency trajectory for each of the 8 learners in both scenarios, separated by domain. In the Standard Learning group, the paths of faster learners are clearly suppressed. In the Personalized Learning group, learners progress more efficiently, with those who start behind receiving the support needed to catch up.

domain_comparison_plot <- ggplot(combined_scenarios_df, aes(x = wave, y = proficiency, color = Scenario, group = interaction(learner_id, Scenario))) +
  geom_line(alpha = 0.6) +
  facet_wrap(~ domain, ncol = 2) + 
  scale_y_continuous(labels = scales::percent) +
  scale_color_manual(values = c("Standard Learning" = "tomato", "Personalized Learning" = "steelblue")) +
  labs(
    title = "Comparing Instructional Models Across Domains",
    subtitle = "Standard Learning holds back faster learners, while Personalized targets individual needs.",
    x = "Instructional Day (Wave)",
    y = "Proficiency Level",
    color = "Instructional Scenario"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold", size = 16),
    strip.text = element_text(face = "bold"),
    legend.position = "bottom",
    panel.grid.minor = element_blank()
  )

print(domain_comparison_plot)

Average Group Proficiency

This visualization aggregates the data to show the average proficiency of the entire group over time. The advantage of the personalized model is stark: the average mastery level of the group is consistently and significantly higher at every stage of the simulation.

average_growth_df <- combined_scenarios_df %>%
  group_by(Scenario, domain, wave) %>%
  summarise(mean_proficiency = mean(proficiency), .groups = 'drop')

average_growth_plot <- ggplot(average_growth_df, aes(x = wave, y = mean_proficiency, color = Scenario, group = Scenario)) +
  geom_line(linewidth = 1.2) +
  geom_point(size = 2) +
  facet_wrap(~domain, ncol = 2) +
  scale_y_continuous(labels = scales::percent, limits = c(0, 1)) +
  scale_color_manual(values = c("Standard Learning" = "tomato", "Personalized Learning" = "steelblue")) +
  labs(
    title = "Average Proficiency Growth: Standard vs. Personalized Learning",
    subtitle = "Personalized learning shows a clear advantage in average group mastery over time.",
    x = "Instructional Day (Wave)",
    y = "Average Proficiency of Group",
    color = "Instructional Scenario"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold", size = 16),
    strip.text = element_text(face = "bold"),
    legend.position = "bottom",
    panel.grid.minor = element_blank()
  )

print(average_growth_plot)

Time to 90% Proficiency

Finally, we analyze a key metric: efficiency. This plot shows how many days it took for each learner to reach 90% proficiency. Across all domains, learners in the Personalized Learning scenario (blue bars) reach the mastery threshold significantly faster than their counterparts in the Standard Learning scenario (red bars). This demonstrates that personalized instruction is not only more effective in raising proficiency but also more time-efficient.

proficiency_threshold <- 0.90

# Calculate the first day (wave) each learner hits the threshold
time_to_proficiency_df <- combined_scenarios_df %>%
  filter(proficiency >= proficiency_threshold) %>%
  group_by(learner_id, domain, Scenario) %>%
  summarise(first_day_proficient = min(wave), .groups = 'drop')

# Create the plot
time_to_proficiency_plot <- ggplot(time_to_proficiency_df, aes(x = first_day_proficient, y = factor(learner_id), fill = Scenario)) +
  geom_col(position = "dodge", alpha = 0.8) +
  facet_wrap(~domain, ncol = 2) +
  scale_fill_manual(values = c("Standard Learning" = "tomato", "Personalized Learning" = "steelblue")) +
  geom_text(aes(label = first_day_proficient), position = position_dodge(width = 0.9), hjust = -0.2, size = 3) +
  labs(
    title = "Time to Reach 90% Proficiency",
    subtitle = "Comparing the number of days for each learner to achieve mastery.",
    x = "Days (Waves) to Reach 90% Proficiency",
    y = "Learner ID",
    fill = "Instructional Scenario"
  ) +
  theme_minimal(base_size = 12) +
  theme(
    plot.title = element_text(face = "bold", size = 16),
    strip.text = element_text(face = "bold"),
    legend.position = "bottom"
  )

print(time_to_proficiency_plot)


Conclusion

The simulation provides compelling evidence for the benefits of personalized, adaptive learning systems modeled by digital twins. Compared to a standard, group-paced model, the personalized approach leads to higher overall proficiency, more equitable outcomes (closing the gap between learners), and a faster time to mastery for every individual.