| Title: | Optimal Group Assignment and Workload Allocation |
|---|---|
| Description: | Integer programming models to assign students to groups by maximising diversity or topic preferences, and to allocate multi-role teaching workloads while balancing role demand, preferences, fairness, and cohort protection. |
| Authors: | Vik Gopal [aut], Kevin Lam [aut], Ju Xue [ctb], Mingyuan Zhang [aut, cre], National University of Singapore [cph] |
| Maintainer: | Mingyuan Zhang <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.7.3 |
| Built: | 2026-07-09 08:42:15 UTC |
| Source: | https://github.com/zimmy313/grouper |
From the result of [ompr::solve_model()], this function attaches the derived groupings to the original dataframe comprising students.
assign_groups( model_result, assignment = c("diversity", "preference"), dframe, params_list, group_names )assign_groups( model_result, assignment = c("diversity", "preference"), dframe, params_list, group_names )
model_result |
The output solution objection. |
assignment |
Character string indicating the type of model that this dataset is for. The argument is either 'preference' or 'diversity'. Partial matching is fine. |
dframe |
The original dataframe used in [extract_student_info()]. |
params_list |
The list of parameters from the YAML file, i.e. the output of [extract_params_yaml()]. This is only required for the preference-based assignment. |
group_names |
A character string. It denotes the column name in the original dataframe containing the self-formed groups. Note that we need the string here, not the integer position, since we are going to join with it. |
A data frame with the group assignments attached to the original group composition dataframe.
Creates one row per student and one column per course-role pair, with units allocated by the solver.
assign_job(model_result, student_df, course_codes, name_col = "Name")assign_job(model_result, student_df, course_codes, name_col = "Name")
model_result |
Result object from 'ompr::solve_model()' for a PhD or multi-role workload model. |
student_df |
A data frame that contains student name information. Every row is a unique student. |
course_codes |
Character vector of course codes in the same order as preference-matrix columns and 'd_mat' rows. |
name_col |
Student name column name in 'student_df'. |
A data frame with columns: 'Name', then all '<course>-t', all '<course>-g', all '<course>-e'.
Compute total pairwise diversity for a set of students
compute_diversity(id, dmat)compute_diversity(id, dmat)
id |
Integer vector of student indices into 'dmat'. |
dmat |
Numeric distance matrix (students × students). |
Scalar: sum of upper-triangle distances among 'id'.
Transforms raw preference ranks so that higher values indicate stronger preference. A rank of 1 maps to 'n_topics * B', rank 2 to 'n_topics * B - 1', and so on.
convert_pref_mat(pref_mat, n_topics, B)convert_pref_mat(pref_mat, n_topics, B)
pref_mat |
Numeric matrix of preference ranks (groups × columns). |
n_topics |
Integer. Number of base topics. |
B |
Integer. Number of subtopic subgroups per topic. |
Numeric matrix of the same dimensions as 'pref_mat'.
An example dataset to use with the diversity-based assignment model.
dba_gc_ex001dba_gc_ex001
## 'dba_gc_ex001' A data frame with 4 rows and 4 columns.
* id: the student id of each students, simply the integers 1 to 4. * major: the primary major of each student. * skill: the skill level of each student. * groups: the self-formed groups submitted by each student. In this case, student is in his/her own group.
This dataset was constructed by hand.
An example dataset to use with the diversity-based assignment model. It is used to demonstrate the use of a custom dissimilarity matrix.
dba_gc_ex003dba_gc_ex003
## 'dba_gc_ex003' A matrix with 4 rows and 4 columns
* id: the student id of each students, simply the integers 1 to 4. * self_groups: The self-formed groups * year, major: demographics used in computing dissimilarities
This dataset was constructed by hand.
An example dataset to use with the diversity-based assignment model. It is used to demonstrate the use of a vectors to indicate individual group size constraints for specific topics.
dba_gc_ex004dba_gc_ex004
## 'dba_gc_ex004' A matrix with 5 rows and 4 columns
* id: the student id of each students, simply the integers 1 to 4. * self_groups: The self-formed groups * python: Python skill level - 1 is lowest, 3 is highest.
This dataset was constructed by hand.
Wrapper around [extract_student_info()], [extract_phd_info()], and [extract_multirole_info()].
extract_info( assignment = c("diversity", "preference", "phd", "multirole"), ... )extract_info( assignment = c("diversity", "preference", "phd", "multirole"), ... )
assignment |
Character string indicating model type. Must be one of '"diversity"', '"preference"', '"phd"', or '"multirole"'. |
... |
Additional arguments for the underlying extraction functions. See Details. |
Explicit argument guide by assignment:
- For 'assignment = "diversity"', 'extract_info()' forwards '...' to [extract_student_info()].
Required arguments: - 'dframe' - 'self_formed_groups' - either: - 'd_mat', or - 'demographic_cols', so Gower dissimilarity is computed internally
Optional arguments: - 'skills', which can be supplied or set to 'NULL'
- For 'assignment = "preference"', 'extract_info()' forwards '...' to [extract_student_info()].
Required arguments: - 'dframe' - 'self_formed_groups' - 'pref_mat'
- For 'assignment = "phd"', 'extract_info()' forwards '...' to [extract_phd_info()].
Required arguments: - 'student_df' - 'p_mat' - 'd_mat'
Optional arguments: - 'e_mode', which uses the default from [extract_phd_info()] - 'C', which uses the default from [extract_phd_info()] - 's', which uses the default from [extract_phd_info()]
- For 'assignment = "multirole"', 'extract_info()' forwards '...' to [extract_multirole_info()].
Required arguments: - 'student_df' - 'd_mat'
Optional arguments: - 'p_ta_mat' and 'p_gr_mat' - 'e_mode', 'C', 's', and 'single_semester'
This wrapper does not parse YAML files. YAML-based parameter extraction remains available via [extract_params_yaml()].
A model input list from the corresponding extraction function.
Converts individual-level data, role-specific preference matrices, and role demand into the list expected by [prepare_multirole_model()].
extract_multirole_info( student_df, d_mat, p_ta_mat = NULL, p_gr_mat = NULL, e_mode = c("rr", "none"), C = 4, s = c(-1, 0, 1, 2), single_semester = FALSE )extract_multirole_info( student_df, d_mat, p_ta_mat = NULL, p_gr_mat = NULL, e_mode = c("rr", "none"), C = 4, s = c(-1, 0, 1, 2), single_semester = FALSE )
student_df |
A data frame with one row per individual. By default, its first four columns must be named 'student_id', 'year', 'past_ta', and 'past_gr', in that order. With 'single_semester = TRUE', only 'student_id' and 'year' are required as the first two columns. 'year' is capped to the range 1-4. |
d_mat |
Numeric demand matrix with 'Nj' rows and two or three columns. Columns are interpreted as TA, GR, and optional E. |
p_ta_mat |
Optional numeric TA preference matrix with dimensions 'Ns x Nj'. |
p_gr_mat |
Optional numeric GR preference matrix with dimensions 'Ns x Nj'. |
e_mode |
How to handle E demand when 'd_mat' has no E column. '"rr"' computes E demand by round-robin allocation from highest to lowest GR demand; '"none"' sets E demand to zero. |
C |
Semester workload capacity per individual. It is stored in the extracted input and used by [prepare_multirole_model()] to set annual workload to '2 * C'. It also determines E demand when 'e_mode = "rr"'. |
s |
Numeric vector containing E-allocation scores for Years 1, 2, 3, and 4. Larger values make E allocation more attractive when the 'phi' term is active. |
single_semester |
Logical flag. When 'TRUE', supplied past-workload columns are ignored and extraction returns synthetic prior workloads 't1 = 0' and 'g1 = C' for every individual. |
Preference matrices are optional during extraction because their objective terms can be disabled in [prepare_multirole_model()]. When 'beta_ta' or 'beta_gr' is active, the corresponding matrix must be present.
Input order must already be aligned: row 'i' in each preference matrix must correspond to row 'i' in 'student_df', and demand row 'j' must correspond to preference column 'j'.
In single-semester mode, the uniform synthetic GR workload does not change the GR workload spread. It fills the prior-semester half of annual capacity, leaving 'C' units per individual for current allocation.
A list containing 'Ns', 'Nj', 'C', 'P_ta', 'P_gr', 'd', 's', 'year', 't1', and 'g1'.
inputs <- extract_multirole_info( student_df = multirole_students_ex001, d_mat = multirole_demand_ex001, p_ta_mat = multirole_prefmat_ex001, p_gr_mat = multirole_prefmat_ex001, e_mode = "none" )inputs <- extract_multirole_info( student_df = multirole_students_ex001, d_mat = multirole_demand_ex001, p_ta_mat = multirole_prefmat_ex001, p_gr_mat = multirole_prefmat_ex001, e_mode = "none" )
The remaining parameters for the models are retrieved from a YAML file, so as not to clutter the argument list for [extract_student_info()].
extract_params_yaml(fname, assignment = c("diversity", "preference"))extract_params_yaml(fname, assignment = c("diversity", "preference"))
fname |
A YAML file containing the remaining parameters. |
assignment |
Character string indicating the type of model that this dataset is for. The argument is either 'preference' or 'diversity'. Partial matching is fine. |
For the diversity+skill-based assignment, this function returns a list containing:
* n_topics: the number of topics * R: the optimally desired number of repetitions per topic * nmin: the minimum number of students per topic, * nmax: the maximum number of students per topic, * rmin: the minimum number of repetitions per topic, * rmax: the maximum number of repetitions per topic.
For the preference-based assignment, this function returns a list containing:
* n_topics: the number of topics * R: the optimally desired number of repetitions per topic * nmin: the minimum number of students per topic, * nmax: the maximum number of students per topic, * rmin: the minimum number of repetitions per topic, * rmax: the maximum number of repetitions per topic.
Converts a dataframe with information on students to a list of parameters. This list forms one half of the inputs to prepare_model(). The remaining model parameters can come from [extract_params_yaml()] or be supplied directly to [prepare_model()] for non-YAML workflows.
extract_student_info( dframe, assignment = c("diversity", "preference"), self_formed_groups, demographic_cols, skills, pref_mat, d_mat )extract_student_info( dframe, assignment = c("diversity", "preference"), self_formed_groups, demographic_cols, skills, pref_mat, d_mat )
dframe |
A dataframe with one row for each student. The columns could possibly contain demographic variables, an overall skill measure, and a column indicating self-formed groups. It is best to have an id column to identify each student. |
assignment |
Character string indicating the type of model that this dataset is for. The argument is either 'preference' or 'diversity'. Partial matching is fine. |
self_formed_groups |
An integer column that identifies the self-formed groups, submitted by students. |
demographic_cols |
A set of integers indicating the columns corresponding to demographic information, e.g. major, year of study, gender, etc. This argument is only used by the diversity-based assignment. |
skills |
A numeric measure of overall skill level (higher means more skilled). This argument is only used by the diversity-based assignment. This argument can be set to NULL. If this is done, then the model used only maximises the diversity. |
pref_mat |
The preference matrix with dimensions equal to the num of groups x B*T, where T is the number of topics and B is the number of sub-groups per topic. This argument is only used in the preference-based assignment. See the Details section for more information. |
d_mat |
The dissimilarity matrix with number of rows equal to the number of students. This matrix should be symmetric, with diagonals equal to 0. This argument is only used in the diversity-based assignment. If it is not provided, the "Gower" distance from the cluster package is used. If this is provided, then demographic_cols is ignored. |
For the diversity-based assignment, the demographic variables are converted into an NxN dissimilarity matrix. By default, the dissimilarity metric used is the Gower distance [cluster::daisy()].
For the preference-based assignment, the preference matrix indicates the preference that each group has for the project topics. For this model, each topic has possibly B sub-groups. The number of columns of this matrix must be B*T. Suppose there are T=3 topics and B=2 sub-groups per topic. Then the order of the sub-topics should be:
T1S1, T2S1, T3S1, T1S2, T2S2, and T3S2.
Note that higher values in the preference matrix reflect a greater preference for a particular topic-subtopic combination, since the objective function is set to be maximised.
For the diversity-based assignment model, this function returns a list containing:
* N: number of students * G: number of self-formed groups * m: a (student x groups) matrix, indicating group membership for each student. * d: dissimilarity matrix, NxN * s: skills vector for each individual student (possibly NULL)
For the preference-based assignment model, this function returns a list containing:
* N: number of students * G: number of self-formed groups * m: a (student x groups) matrix, indicating group membership for each student. * n: a vector of length G, with the number of students in each self-formed group. * p: The preference matrix from the input argument.
Look up a group's preference score for a topic-subtopic combination
get_group_pref_score(group_num, topic, subtopic, pref_mat, n_topics)get_group_pref_score(group_num, topic, subtopic, pref_mat, n_topics)
group_num |
Integer. Group index (row of 'pref_mat'). |
topic |
Integer. Base topic index. |
subtopic |
Integer. Subtopic (subgroup) index. |
pref_mat |
Numeric preference matrix (groups × topic-subtopic columns). |
n_topics |
Integer. Number of base topics. |
Scalar preference score.
An example demand matrix for the multi-role workload allocation model.
multirole_demand_ex001multirole_demand_ex001
## 'multirole_demand_ex001' A matrix with 4 rows and 2 columns.
Columns are in the order 'TA', 'GR'. Row names store the course codes.
This dataset was constructed by hand.
An example preference matrix for the multi-role workload allocation model. It can be used for either TA or GR preferences.
multirole_prefmat_ex001multirole_prefmat_ex001
## 'multirole_prefmat_ex001' A matrix with 4 rows and 4 columns.
Rows correspond to individuals in 'multirole_students_ex001', and columns correspond to rows of 'multirole_demand_ex001'.
Preference scores are encoded as 3 (first choice), 2 (second choice), and 1 (third choice). Unranked courses are encoded as -99.
This dataset was constructed by hand.
An example individual table for the multi-role workload allocation model.
multirole_students_ex001multirole_students_ex001
## 'multirole_students_ex001' A data frame with 4 rows and 5 columns.
* student_id: unique individual id. * year: cohort or year, encoded from 1 to 4. * past_ta: previous-semester TA workload units. * past_gr: previous-semester GR workload units. * Name: individual name.
In this toy dataset, 'past_ta + past_gr = 4' for every individual.
This dataset was constructed by hand.
An example dataset to use with the preference-based assignment model.
pba_gc_ex002pba_gc_ex002
## 'pba_gc_ex002' A data frame with 8 rows and 2 columns.
* id: the student id of each students, simply the integers 1 to 8. * grouping: the self-formed groups submitted by each student. In this case, each self-formed group is of size 2.
This dataset was constructed by hand.
An example dataset to use with the preference-based assignment model.
pba_prefmat_ex002pba_prefmat_ex002
## 'pba_prefmat_ex002' A matrix with 4 rows and 4 columns
Each row represents the preferences of each self-formed group in the dataset 'pba_gc_ex002'.
This dataset was constructed by hand.
Prepare the diversity-based assignment model
prepare_diversity_model(df_list, yaml_list, w1 = 0.5, w2 = 0.5)prepare_diversity_model(df_list, yaml_list, w1 = 0.5, w2 = 0.5)
df_list |
The output list from [extract_student_info()] for 'assignment = "diversity"'. |
yaml_list |
The output list from [extract_params_yaml()] for 'assignment = "diversity"'. |
w1, w2
|
Numeric values between 0 and 1. Should sum to 1. These weights correspond to the importance given to the diversity- and skill-based portions in the objective function. |
An ompr model.
Initialise optimisation model (wrapper)
prepare_model( df_list, yaml_list = NULL, assignment = c("diversity", "preference", "phd", "multirole"), w1 = 0.5, w2 = 0.5, ... )prepare_model( df_list, yaml_list = NULL, assignment = c("diversity", "preference", "phd", "multirole"), w1 = 0.5, w2 = 0.5, ... )
df_list |
Model input list. |
yaml_list |
Parameter list from [extract_params_yaml()]. Optional for 'assignment = "diversity"' and 'assignment = "preference"' for backward compatibility. If supplied, this list is used directly. Ignored for 'assignment = "phd"' and 'assignment = "multirole"'. |
assignment |
Character string indicating model type. Must be one of '"diversity"', '"preference"', '"phd"', or '"multirole"'. |
w1, w2
|
Numeric values between 0 and 1. Should sum to 1. Used only for 'assignment = "diversity"'. |
... |
Additional arguments: * For 'assignment = "diversity"' when 'yaml_list' is 'NULL': supply 'n_topics', 'nmin', 'nmax', 'rmin', and 'rmax'. * For 'assignment = "preference"' when 'yaml_list' is 'NULL': supply 'n_topics', 'B', 'nmin', 'nmax', 'rmin', and 'rmax'. * For 'assignment = "phd"': passed to [prepare_phd_model()], including 'protected_year' when a cohort other than Year 1 should receive the soft TA-load protection. * For 'assignment = "multirole"': passed to [prepare_multirole_model()]. Multi-role semester capacity is supplied during extraction and read from 'df_list$C'. |
An ompr model.
Builds a mixed-integer model for assigning TA, GR, and lighter E duties while balancing role-specific workload, preferences, and cohort protection.
prepare_multirole_model( df_list, ta_protected_max = 1, gr_protected_max = 1, e_max = NULL, ta_min = NULL, ta_max = NULL, gr_min = NULL, gr_max = NULL, e_min = NULL, alpha_ta = 2, alpha_gr = NULL, beta_ta = 1, beta_gr = NULL, phi = 1, rho_ta = 10, rho_gr = NULL, protected_year_ta = 1, protected_year_gr = 1 )prepare_multirole_model( df_list, ta_protected_max = 1, gr_protected_max = 1, e_max = NULL, ta_min = NULL, ta_max = NULL, gr_min = NULL, gr_max = NULL, e_min = NULL, alpha_ta = 2, alpha_gr = NULL, beta_ta = 1, beta_gr = NULL, phi = 1, rho_ta = 10, rho_gr = NULL, protected_year_ta = 1, protected_year_gr = 1 )
df_list |
A model input list from [extract_multirole_info()]. |
ta_protected_max, gr_protected_max
|
Non-negative soft upper limits on current-semester TA or GR workload for the corresponding protected cohort. A value may be 'NULL' when the corresponding 'rho_*' term is disabled. |
e_max |
Optional upper bound on per-individual E units. |
ta_min, ta_max
|
Optional lower and upper bounds on per-individual TA units. |
gr_min, gr_max
|
Optional lower and upper bounds on per-individual GR units. |
e_min |
Optional lower bound on per-individual E units. |
alpha_ta, alpha_gr
|
Non-negative weights for annual TA and GR workload spread. |
beta_ta, beta_gr
|
Non-negative weights for TA and GR preferences. |
phi |
Non-negative weight for score-guided E allocation. |
rho_ta, rho_gr
|
Non-negative penalties for TA and GR protected-cohort slack. |
protected_year_ta, protected_year_gr
|
Whole numbers from 1 to 4 identifying the TA- and GR-protected cohorts. |
Any objective weight set to 'NULL' or zero is disabled. Disabled preference and E terms are omitted from the objective. Disabling a spread term also omits its two spread variables and fairness constraints. Disabling a protection penalty omits that role's slack variables and soft-limit constraints, and includes every individual in that role's fairness spread.
When a preference term is active, the corresponding 'P_ta' or 'P_gr' element must be present in 'df_list'. Semester capacity is read from 'df_list$C', as supplied to [extract_multirole_info()]. Annual total workload is fixed at '2 * C'.
An 'ompr' model.
inputs <- extract_multirole_info( student_df = multirole_students_ex001, d_mat = multirole_demand_ex001, p_ta_mat = multirole_prefmat_ex001, p_gr_mat = multirole_prefmat_ex001, e_mode = "rr" ) model <- prepare_multirole_model( inputs, alpha_gr = 1, beta_gr = 1, rho_gr = 10 )inputs <- extract_multirole_info( student_df = multirole_students_ex001, d_mat = multirole_demand_ex001, p_ta_mat = multirole_prefmat_ex001, p_gr_mat = multirole_prefmat_ex001, e_mode = "rr" ) model <- prepare_multirole_model( inputs, alpha_gr = 1, beta_gr = 1, rho_gr = 10 )
Prepare the preference-based assignment model
prepare_preference_model(df_list, yaml_list)prepare_preference_model(df_list, yaml_list)
df_list |
The output list from [extract_student_info()] for 'assignment = "preference"'. |
yaml_list |
The output list from [extract_params_yaml()] for 'assignment = "preference"'. |
An ompr model.
Solves an existing 'ompr' model with an ROI-backed solver, then routes the solver result through [assign_groups()] or [assign_job()] depending on the assignment type.
solve_assignment( model, assignment = c("diversity", "preference", "phd", "multirole"), solver = c("glpk", "highs", "gurobi"), dframe = NULL, params_list = NULL, group_names = NULL, student_df = NULL, course_codes = NULL, name_col = "Name", verbose = TRUE, time_limit = NULL, iteration_limit = NULL, solver_args = list() )solve_assignment( model, assignment = c("diversity", "preference", "phd", "multirole"), solver = c("glpk", "highs", "gurobi"), dframe = NULL, params_list = NULL, group_names = NULL, student_df = NULL, course_codes = NULL, name_col = "Name", verbose = TRUE, time_limit = NULL, iteration_limit = NULL, solver_args = list() )
model |
A prepared 'ompr' model, usually from [prepare_model()]. |
assignment |
Character string indicating model type. Must be one of '"diversity"', '"preference"', '"phd"', or '"multirole"'. |
solver |
Solver to use through 'ompr.roi'. Must be one of '"glpk"', '"highs"', or '"gurobi"'. |
dframe |
The original dataframe used in [extract_student_info()]. Required for 'assignment = "diversity"' and 'assignment = "preference"'. |
params_list |
The list of parameters from [extract_params_yaml()]. Required for 'assignment = "preference"'. |
group_names |
A character string denoting the self-formed group column in 'dframe'. Required for 'assignment = "diversity"' and 'assignment = "preference"'. |
student_df |
A data frame that contains individual name information. Required for 'assignment = "phd"' and 'assignment = "multirole"'. |
course_codes |
Character vector of course or task codes in model order. Required for 'assignment = "phd"' and 'assignment = "multirole"'. |
name_col |
Student name column name in 'student_df'. |
verbose |
Logical value passed to 'ompr.roi::with_ROI()'. |
time_limit, iteration_limit
|
Optional Gurobi controls. These are applied only when 'solver = "gurobi"'. |
solver_args |
Additional named arguments passed to 'ompr.roi::with_ROI()'. |
A list with two elements:
model_result: the raw result from [ompr::solve_model()]
output: the post-processed assignment table
Summarise a DBA result by topic-repetition group
summary_dba(df_result, df_list, id_col)summary_dba(df_result, df_list, id_col)
df_result |
Data frame returned by [solve_assignment()] for a diversity model. Must contain columns 'topic', 'rep', and the column named by 'id_col'. |
df_list |
Input list from [extract_student_info()]. Must contain 'd' (distance matrix) and optionally 's' (skill vector). |
id_col |
Character. Name of the student-ID column in 'df_result'. |
A grouped summary tibble with columns 'topic', 'rep', 'n', and 'total_diversity' (plus 'total_skill' when 'df_list$s' is present).
Summarise a PBA result by topic-subtopic-repetition group
summary_pba(df_result, df_list, n_topics)summary_pba(df_result, df_list, n_topics)
df_result |
Data frame returned by [solve_assignment()] for a preference model. Must contain columns 'group', 'topic2', 'subtopic', and 'rep'. |
df_list |
Input list from [extract_student_info()] for 'assignment = "preference"'. Must contain 'p' (preference matrix). |
n_topics |
Integer. Number of base topics. |
A grouped summary tibble with columns 'topic2', 'subtopic', 'rep', 'n', and 'total_pref_score'.