Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 27 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,49 @@
# 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.

In total, there were four sessions of five simultaneous talks. As such, this algorithm was run four times to produce matchings between delegates and these five simultaneous talks.

**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)
Expand Down
10 changes: 6 additions & 4 deletions src/functions.R
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand Down
15 changes: 6 additions & 9 deletions src/main.R → src/galeshapley.R
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand All @@ -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()



Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down