Is it possible to "dynamically" modify a dataframe as a R simmer simulation runs? I need it for appointment scheduling simulation

huangapple go评论51阅读模式
英文:

Is it possible to "dynamically" modify a dataframe as a R simmer simulation runs? I need it for appointment scheduling simulation

问题

我正在为一家诊所构建一个预约排班模拟。思路是,在模拟运行时,当患者来到系统时,他们会请求与医生预约。然后他们必须等待预约时间。

以下是问题的详细信息:

  • 模拟运行时间为21个时间单位(小时)
  • 有6个患者在时间1,2,3,4,5和6时到达
  • 医生需要1个时间单位(小时)来检查一个患者
  • 可用的预约时间段为1,2,3,4,5,11,12,14,15,19和20

我的想法是使用名为df_appointment_slots的tibble来跟踪将每个患者分配给时间段的情况。这不仅仅是一个监控活动,因为患者只能选择尚未分配的时间段。

首先的代码定义了模拟期间可用的预约时间段。理想情况下,随着模拟的进行,表示时间段的每一行都会将其assigned列更新为TRUE,并在患者到达后将其patient_id属性更新为相应的患者ID。

在这个函数中,我有几种不同的预约分配方案,为了示例的目的,我想要将每个患者随机分配到一个可用的预约时间段。在我的实际应用程序中,遵循更复杂的规则,因此我需要它在一个可以修改的函数中。

最后但并非最不重要的,这是模拟的代码。

当然,我收到以下错误:

错误信息为:

Error in `mutate()`:
i In argument: `patient_id = p_id`.
Caused by error:
! `patient_id` must be a vector, not a function.

它告诉我在assign_appointment函数中的p_id = function(){get_attribute(env, "patient_id")}被保存为一个函数,并且没有被动态地评估,以便我可以将患者ID保存在df_appointment_slots tibble中。

我还担心我对R Simmer库及其功能有基本的误解。如果有更好的方式以完全可自定义的方式动态预约患者到时间段,我愿意听取建议。

英文:

I'm building an appointment scheduling simulation for a clinic. The idea is that as the simulation runs, when patients arrive to the system they request an appointment with a doctor. Then they have to wait until the time of the appointment.

Is it possible to "dynamically" modify a dataframe as a R simmer simulation runs? I need it for appointment scheduling simulation

For the details of the problem, I have that:

Simulation runs for 21 time units (hours)
There are 6 patient arrivals, at times 1,2,3,4,5 and 6
The doctor takes exactly 1 time unit (hour) to check a patient
Available appointment slots are at times 1,2,3,4,5,11,12,14,15,19 and 20

My idea is to use a tibble called df_appointment_slots to keep track of the assignemnt of slots to each patient. It's not a mere monitoring activity because patients can only choose time slots that are not assigned.

This first code defines the appointment slots available through the simulation. Ideally as the simulation goes, each row that represents a time slot gets it's assigned column updated to TRUE and it's patient_id after a patient id attribute.

library(simmer)
library(tidyverse)

df_appointment_slots = tibble(sim_time = df_appointment_slots = tibble(
  sim_time = c(1,2,3,4,5,11,12,14,15,19,20),
  assigned = rep(FALSE, length(sim_time)),
  patient_id = rep(NA, length(sim_time)))

Here in this function I have several appointment assignment schemes, For the sake of the example i want to assign each patient randomly to a appointment slot that is available. In my real application, follows more complex rules so I need it to be in a function I can modify.

assign_appointment = function(env, env_now ,df = df_appointment_slots){
 p_id = function(){get_attribute(env, "patient_id")}
  
 #Randomly select an available time slot after current simulation time.
 df_assigned = df %>% 
   filter(assigned == FALSE,
          sim_time >= env_now) %>% 
   slice_sample() %>% 
    mutate(assigned = TRUE, patient_id = p_id) 
 
  #If there are no available time slots then the patient takes other    trajectory and leaves the system
  
  if(nrow(df_assigned) == 0){
    return(-1)
#We will check later that if this function returns a value of -1
#instead of using it for a timeout, we decide to change trajectory
#and make the patient leave the system. 
  }
   
  #df_updated switches the assigned time slot into the original df_appointment slots. If there is a better way to do this i'm open to ideas.

  df_updated = df %>% 
    anti_join(df_assigned, by="sim_time") %>% 
    rbind(df_assigned) %>% 
    arrange(sim_time)
  
  
  #Results of the function
  #We want to update de dataframe that is outside of this scope and of the simulation. That way next patient can't be assigned to a time_slot that is already assigned.
                    
  df_appointment_slots <<- df_updated 
  
  # We return the appointment time
  return(df_assigned$sim_time)
}

Last but not least, here is the simulation

clinic = simmer("Clinic") 

patient_trajectory <-
  trajectory("Patient_trajectory") %>% 
  
  #We assign a id to each patient
  set_attribute("patient_id", 1, mod = "+") %>% 

  #Appointment assignment
  set_attribute("appointment_time",
                assign_appointment(env = clinic,
                                   env_now = simmer::now(clinic),
                                   df = df_appointment_slots)) %>% 
  
  #If assign_appointment returns -1 then there are no more time_slots available and the patient leaves the system
  
  branch(
    option = function() {ifelse(get_attribute("appointment_time") == -1, 1,2)},
    continue = c(T, F),
    trajectory("leave_system") %>% 
      log_("There was no appointment slot available")
    ) %>% 
  
  #If the appointment was succesfully scheduled then the patient waits until the appointment time
  
  log_("The patient waits for the appointment") %>% 
  timeout(
    function(){get_attribute("appointment_time") - simmer::now(clinic)}) %>%
  
  #After waiting, the patient seizes the doctor for 1 hour
  seize("doctor") %>% 
  timeout(1) %>% 
  release("doctor")
  
clinic %>%
  add_generator("Patient", patient_trajectory, at(1,2,3,4,5,6)) %>% 
  run(until = 21)

Of course I get the following error:

Error in `mutate()`:
i In argument: `patient_id = p_id`.
Caused by error:
! `patient_id` must be a vector, not a function.

It's telling me that p_id = function(){get_attribute(env, "patient_id")} in the assign_appointment function is getting saved as a function and it's not being evaluated dynamically as the simulation goes so that I can save the patient id in the df_appointment_slots tibble.

I'm also worried I'm fundamentally misunderstanding the R Simmer library and it's capabilities. If there is a better way to dynamically appoint patients to time slots in a fully customizable way, I'm open to suggestions.

答案1

得分: 2

I was having troubles with the evaluation of functions inside the simulation. I just confirmed that enclosing assign_appointment() in a nameless function at the set_attribute() works as intended. Every time assign_appointment is called, the dataframe called df_appointment_slots is updated as intended.

So yeah, here is the small correction I had to make.

# Appointment assignment
set_attribute("appointment_time", function(){
    assign_appointment(p_id = get_attribute(clinic, "patient_id"),
                       env_now = simmer::now(clinic),
                       df = df_appointment_slots)}) %>%

So to answer my own question: Yes, it's possible to update a dataframe as a simulation runs.

英文:

I was having troubles with the evaluation of functions inside the simulation. I just confirmed that enclosing assign_appointment() in a nameless function at the set_attribute() works as intended. Everytime assign_appointment is called the dataframe called df_appointment_slots is updated as intended.

So yeah, here is the small correction i had to make.

#Appointment assignment
  set_attribute("appointment_time",function(){
                assign_appointment(p_id = get_attribute(clinic, "patient_id"),
                                   env_now = simmer::now(clinic),
                                   df = df_appointment_slots)}) %>% 

So to answer my own question: Yes, it's posible to update a dataframe as a simulation runs.

huangapple
  • 本文由 发表于 2023年6月12日 13:07:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/76453770.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定