diff --git a/README.md b/README.md index 88c199a..f5c1e7e 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,40 @@ # Preference Allocation -[![Travis-CI Build Status](https://travis-ci.org/avisionh/Preference-Allocation.svg?branch=master)](https://travis-ci.org/avisionh/Preference-Allocation) +[![Travis-CI Build Status](https://travis-ci.org/avisionh/Preference-Allocation.svg?branch=master)](https://travis-ci.org/avisionh/Preference-Allocation) [![CodeFactor](https://www.codefactor.io/repository/github/avisionh/preferenceallocation/badge)](https://www.codefactor.io/repository/github/avisionh/preferenceallocation) [![License: MIT](https://img.shields.io/badge/License-MIT-informational.svg)](https://opensource.org/licenses/MIT) -### Collaborators +# Overview +preferenceallocation explores methods for solving *preference allocation*/*one-sided matching* problems. -- [Avision Ho](https://github.com/avisionh) -- [Le Duong](https://github.com/ledu1993) +Consider that we have to assign *x* people to *y* sessions. For each of these *y* sessions, +and individual person will have a preference ordering, meaning that they strictly prefer some sessions over others. +The task is to allocate these *x* people to their *y* sessions, accounting for their preferences +in such a way that the total utility of all *x* people is maximised. -# Update -- A Shiny app is being built for this problem. -- The project management and code development of this stage will be captured on [Azure DevOps](https://azure.microsoft.com/en-gb/services/devops/). - - Compared to GitHub, this has superior project management capabilities. -- Link to this can be found on the [public project page, Preference Allocation](https://avisionh.visualstudio.com/Preference%20Allocation). +## Methodology +We will tackle this problem in two ways: +1. **Gale-Shapley Algorithm |** Implementation of Alvin Roth and Lloyd Shapley's algorithm that assigns delegates to sessions in random order by accounting for both their preferences and ensuring that no two matching pairs will mutually want to switch their matches. +1. **Iterative Preference |** Implementation of a method suggested by a work experience student, Fatma Hussain, this takes chooses delegates and assigns them their n-th most preferred session provided the session is available. -*** +## Usage +To see how to use the bespoke *iterative preference* method proposed in this repo, access and run the `src/iterative_preference.R` script. -# Background -The problem we will tackle in this repository is of *preference allocation*/*one-sided matching*. +To see how to use [matchingR](https://github.com/jtilly/matchingR)'s implementation of Gale-Shapley algorithm, access and run the `src/galeshapley.R` script. -## Task -Consider that we have to assign *x* people to *y* sessions. For each of these *y* sessions, -and individual person will have a preference ordering, meaning that they strictly prefer some sessions over others. +> Note: Your data needs to be in tidy data format for *iterative preference* whereas for the Gale-Shapley algorithm, it does not. -The task is to allocate these *x* people to their *y* sessions, accounting for their preferences -in such a way that the total utility of all *x* people is maximised. +WIP Shiny app is being developed so you can enter your data and apply the iterative preference method on it to get matchings. +- https://avisionh.shinyapps.io/preference20allocation/ + +Documentation of how each method works is available in these slides: +- https://avisionh.github.io/preferenceallocation/ -## Aim/Motivation -- [x] Write an algorithm that automates the matching/mapping of one set to another set given pre-defined constraints. -- [ ] Write effective functions, include error-trapping and -handling. -- [ ] Build a Shiny app that makes this algorithm accessible to non-programmers. -- [ ] Host the Shiny app on a public domain, [shinyapp.io](https://www.shinyapps.io/). -- [ ] Demonstrate a consistent R coding and Git workflow usage. -- [ ] Implement a CI/CD pipeline to robustly and continuously test whether the code works on different operating systems (OS) using [travis-ci](https://travis-ci.org/) and [Azure DevOps](https://azure.microsoft.com/en-gb/services/devops/). -- [ ] Adopt an Agile project management approach using [Azure DevOps](https://azure.microsoft.com/en-gb/services/devops/) to capture and efficiently manage feature requests. -- [ ] Conduct user-research to continuously improve the algorithm and Shiny app. +## Getting help +If you encounter a clear bug, please fill a minimal reproducible example on [Issues](https://github.com/avisionh/preferenceallocation/issues). For questions and other discussion, please use the [Discussion](https://github.com/avisionh/preferenceallocation/discussions) channel. + +*** # Case Study -This algorithm was used in the [GSS Conference 2018](https://gss.civilservice.gov.uk/events/gss-conference-2018/) to allocate a set of 400 conference delegates to a series of talks that were taking place at the same time. +This algorithm was used in the 2018 and 2019 versions of the [GSS Conference](https://gss.civilservice.gov.uk/) to allocate a set ofvconference delegates to a series of talks that were taking place at the same time. These talks were delivered by internal government and external private sector companies. @@ -44,11 +42,8 @@ In total, there were four sessions of five simultaneous talks. As such, this alg **Note:** The rooms in which the speakers delivered their presentations were not pre-allocated. Instead, the decision to place more popular talks (based on people's preferences) in larger rooms was based on plotting the distribution of preferences for the five simultaneous talks for all delegates. -# Methodology -We will tackle this problem in two ways: -1. **Gale-Shapley Algorithm |** Implementation of Alvin Roth and Lloyd Shapley's algorithm that assigns delegates to sessions in random order by accounting for both their preferences and ensuring that no two matching pairs will mutually want to switch their matches. -1. **Iterative Preference |** Implementation of a method suggested by a work experience student, Fatma Hussain, this takes chooses delegates and assigns them their n-th most preferred session provided the session is available. -*** +## EARL 2019 +This project was presented at [Enterprise Application of the R Language (EARL) Conference](https://www.mango-solutions.com/earl-speaker-highlights-from-the-mango-team/) in 2019. ## References - [The Stable Marriage Problem and School Choice](http://www.ams.org/publicoutreach/feature-column/fc-2015-03) diff --git a/src/functions.R b/src/functions.R index ab299a8..4ccc7d7 100644 --- a/src/functions.R +++ b/src/functions.R @@ -52,7 +52,8 @@ func_sample <- function(x, n, replacement) { # ARGUMENTS: # 1. 'x' | (tibble/dataframe) Data to feed in # 2. 'limits' | (tibble/dataframe) Maximum capacity of each session -func_iterative_preferences <- function(x, limits, with_replacement) { +# TODO: Pass in columns names instead of relying on indexing which is brittle +func_iterative_preferences <- function(x, limits, with_replacement = FALSE) { # get number of people n_people <- nrow(x) @@ -61,10 +62,10 @@ func_iterative_preferences <- function(x, limits, with_replacement) { # create dummy tibble for storing output matchings <- tibble(PersonRowId = rep(x = -1, times = n_people), - SessionPreferredColumnId = rep(x = "dummy", times = n_people)) + SessionPreferredColumnId = rep(x = -1, times = n_people)) # convert limits from vector to tibble - limits <- limits[,2] %>% as.tibble() + limits <- limits[,2] %>% as_tibble() # generate vector of people and random sample from it people <- seq(from = 1, to = n_people, by = 1) @@ -90,7 +91,8 @@ func_iterative_preferences <- function(x, limits, with_replacement) { if(limits[preferred_session,] > 0) { # assign person number to session number - matchings[i, ] <- c(rownames(x)[sample_people[i]], preferred_session) + person_id = as.double(rownames(x)[sample_people[i]]) + matchings[i, ] <- list(person_id, preferred_session) # remove a place from session that's been allocated limits[preferred_session, 1] <- limits[preferred_session, 1] - 1 diff --git a/src/main.R b/src/galeshapley.R similarity index 69% rename from src/main.R rename to src/galeshapley.R index 3b755d1..9320991 100644 --- a/src/main.R +++ b/src/galeshapley.R @@ -11,7 +11,7 @@ library(dplyr) library(tibble) # Load custom functions -source('scripts/functions.R') +source('src/functions.R') # Set seed so we can replicate our results set.seed(1) @@ -26,11 +26,11 @@ utility_delegates <- utility_delegates %>% n_delegates <- ncol(utility_delegates) m_sessions <- nrow(utility_delegates) -utility_sessions <- matrix(data = rep(x = 0, times = n_delegates*m_sessions), - nrow = n_delegates, - ncol = m_sessions) +utility_sessions <- matrix(data = rep(x = 0, times = n_delegates*m_sessions), + nrow = n_delegates, + ncol = m_sessions) utility_sessions <- utility_sessions %>% - as.tibble() %>% + as_tibble() %>% rename(`College 1` = V1, `College 2` = V2, `College 3` = V3, @@ -45,9 +45,6 @@ results_galeshapley <- galeShapley.collegeAdmissions( collegeUtils = utility_sessions, slots = c(2, 1, 1, 2) ) -# Approach 2 - Iterative Preferences -results_iterativepreference <- func_iterative_preferences(x = utility_delegates, limits = c(2, 1, 1, 2), with_replacement = FALSE) - # convert matchings to dataframe -results_iterativepreference[[1]] <- results_iterativepreference[[1]] %>% as.data.frame() + diff --git a/src/test/benchmark_large_data.R b/src/iterative_preference.R similarity index 97% rename from src/test/benchmark_large_data.R rename to src/iterative_preference.R index b56346b..8e7efa6 100644 --- a/src/test/benchmark_large_data.R +++ b/src/iterative_preference.R @@ -13,7 +13,7 @@ library(dplyr) library(tibble) # Load custom functions -source('scripts/functions.R') +source('src/functions.R') # Set seed so we can replicate our results set.seed(1) @@ -56,7 +56,7 @@ utility_delegates <- utility_delegates %>% room_sizes <- data.frame(Room = c("Room_01","Room_02","Room_03","Room_04"), Size = c(0.2 * n_delegates, 0.3 * n_delegates ,0.1 * n_delegates, 0.4 * n_delegates)) -# Run interative preferences timed +# Run iterative preferences timed start_time <- Sys.time() results_iterativepreference <- func_iterative_preferences(x = utility_delegates, limits = room_sizes,