Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix: GH pages #23

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
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
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
packrat/lib*/
.Rproj.user
.Rhistory
report.html
slides.html
slides.md
slides.Rpres

42 changes: 20 additions & 22 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
# R for travis: see documentation at https://docs.travis-ci.com/user/languages/r

language: r
sudo: required
cache:
directories:
- "$TRAVIS_BUILD_DIR/packrat/src"
- "$TRAVIS_BUILD_DIR/packrat/lib"
packages: true
r:
- release

git:
depth: 3
sudo: required

env:
global:
- secure: GITHUB_PAT

r_packages:
- matchingR
- readr
- dplyr
- tibble
- DT
cache:
directories:
- $HOME/.local/share/renv
- $TRAVIS_BUILD_DIR/renv/library
packages: true

install:
- R -e "0" --args --bootstrap-packrat

before_script:
- chmod +x ./_build.sh
- chmod +x ./_deploy.sh

- Rscript -e "if (!requireNamespace('renv', quietly = TRUE)) install.packages('renv')"
- Rscript -e "renv::restore()"

script:
- ./_build.sh
- ./_deploy.sh
- nohup R --slave --no-restore -e 'shiny::runApp(port = 3000)' &

addons:
apt:
packages:
- libudunits2-dev
- libgdal-dev
- libgeos-dev
- libproj-dev
57 changes: 26 additions & 31 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)

### 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
3 changes: 0 additions & 3 deletions _build.sh

This file was deleted.

29 changes: 0 additions & 29 deletions _deploy.sh

This file was deleted.

5,989 changes: 5,989 additions & 0 deletions docs/index.html

Large diffs are not rendered by default.

106 changes: 74 additions & 32 deletions docs/presentation.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,39 @@ knitr::opts_chunk$set(echo = FALSE)
library(matchingR)
library(readr)
library(dplyr)
library(tidyr)
library(tibble)
library(DT)

# Set random number generator so so results are replicable
set.seed(1)

# Load data - note are moving up one directory
utility_delegates <- read_csv(file = "data/dummy_student_preferences.csv")
utility_delegates <- read_csv(file = "../data/dummy_student_preferences.csv")

# Import source code
source("scripts/main.R")
# Load functions
source("../src/functions.R")
```


```{r data-prep, include=FALSE}
utility_delegates <- utility_delegates %>%
remove_rownames() %>%
column_to_rownames(var = "X1")

# Set colleges to have no preferences
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 <- utility_sessions %>%
as_tibble() %>%
rename(`College 1` = V1,
`College 2` = V2,
`College 3` = V3,
`College 4` = V4)
```

## Author's Note
Expand Down Expand Up @@ -121,8 +143,7 @@ The **Iterative Preference algorithm** involves a number of rounds:
- For instance, we can see that for *Person 1*, they strictly prefer *Session 2* over *Session 1*, $u_{d_1}(2) > u_{d_1}(1)$.

```{r Data: delegate preferences}
utility_delegates %>%
datatable()
datatable(data = utility_delegates)
```

***
Expand All @@ -132,8 +153,7 @@ utility_delegates %>%
- We have given each session a preference of $0$ for each delegate. This is because sessions are indifferent over which delegates attend.

```{r Data: session preferences}
utility_sessions %>%
datatable()
datatable(data = utility_sessions)
```

***
Expand All @@ -145,15 +165,15 @@ utility_sessions %>%
## Analysis Approach 1 - Gale-Shapley {.smaller}
In the code below, we will run the college admissions variant of the **Gale-Shapley algorithm**.

``` {r Gale-Shapley Analysis, echo = TRUE, eval = FALSE}
galeShapley.collegeAdmissions(
``` {r Gale-Shapley Analysis, echo = TRUE}
results_galeshapley <- galeShapley.collegeAdmissions(
studentUtils = utility_delegates,
collegeUtils = utility_sessions,
slots = c(2, 1, 1, 2)
)
```

From running the algorithm we have the following allocation of delegates to sessions. `r results_galeshapley$matched.students %>% t()`.
From running the algorithm we have the following allocation of delegates to sessions. `r t(results_galeshapley$matched.students)`.

<div class="centered"; style="color:blue;">
For instance, `Person 1` and `Person 4` are allocated to `r paste0("Session ", results_galeshapley$matched.students[1,])` and `r paste0("Session ", results_galeshapley$matched.students[4,])` respectively.
Expand All @@ -169,64 +189,86 @@ rownames(results_galeshapley$matched.students) <- names
colnames(results_galeshapley$matched.students) <- "Session Allocated"

# show allocation
results_galeshapley$matched.students %>%
t()
t(results_galeshapley$matched.students)
```

In the second output is the table of delegates' initial preferences to compare our allocations against.

```{r Gale-Shapley - delegate preferences}
# show deleagate preferences
utility_delegates %>%
datatable()
# show delegate preferences
datatable(data = utility_delegates)
```


## Analysis Approach 2 - Iterative Preferences {.smaller}
In the code below, we will run Fatma's suggested algorithm, **Iterative Preferences** against the initial preferences of delegates to check that our matchings are desirable.

``` {r Iterative Preference - implementation, echo = TRUE, eval = FALSE}
results_iterativepreference <- func_iterative_preferences(
x = utility_delegates,
limits = c(2, 1, 1, 2),
with_replacement = FALSE
Before we do so, we need to reshape our data into tidy data format.
```{r Iterative Preference - wrangle, include=FALSE}
utility_delegates <- utility_delegates %>%
rownames_to_column(var = "Session") %>%
pivot_longer(cols = -"Session",
names_to = "Person",
values_to = "Preference Score") %>%
pivot_wider(id_cols = "Person",
names_from = "Session",
values_from = "Preference Score")
room_sizes <- data.frame(Room = c("Room_01","Room_02","Room_03","Room_04"),
Size = c(2, 1 , 1, 2))

```


``` {r Iterative Preference - implementation, echo = TRUE}
results_iterativepreference <- func_iterative_preferences(x = utility_delegates,
limits = room_sizes,
with_replacement = FALSE
)
```

```{r (HIDDEN) Dataframe transformation, echo = FALSE, eval = TRUE}
# convert to viewable format for slides
results_iterativepreference[[1]] <- results_iterativepreference[[1]] %>%
magrittr::set_colnames(paste0("Person ", results_iterativepreference[[2]]))
rownames(results_iterativepreference[[1]]) <- c("Delegate", "Session Allocated")
```{r Iterative Preference - helper, include = FALSE}
matchings <- results_iterativepreference[[1]]
sampling <- results_iterativepreference[[2]]
```
From running the algorithm we have the following allocation of delegates to sessions. `r results_iterativepreference[[1]][2, ]`.


From running the algorithm we have the following allocation of delegates to sessions. `r matchings$SessionPreferredColumnId`.

<div class="centered"; style="color:blue;">
For instance, `r results_iterativepreference[[1]] %>% select(1) %>% colnames()` and `r results_iterativepreference[[1]] %>% select(4) %>% colnames()` are allocated to `r paste0("Session ", results_iterativepreference[[1]][2, 1])` and `r paste0("Session ", results_iterativepreference[[1]][2, 4])` respectively.
For instance, `r pull(matchings[1,1])` and `r pull(matchings[4,1])` are allocated to `r paste0("Session ", pull(matchings[1,2]))` and `r paste0("Session ", pull(matchings[4,2]))` respectively.
</div>

## Algorithm's Output {.smaller}
In the first output is the matched allocations from using the **Iterative Preferences algorithm**.

```{r Iterative Preference - allocation}
# show allocation
results_iterativepreference[[1]][2, ]
matchings <- matchings %>%
rename("Session Allocated" = "SessionPreferredColumnId") %>%
mutate(PersonRowId = paste0('Person ', PersonRowId)) %>%
t() %>%
as_tibble()
colnames(matchings) <- matchings[1, ]
matchings <- matchings[-1, ]
datatable(data = matchings,
options = list(dom = 't'),
rownames = FALSE)
```

In the second output is the random order of delegates that our algorithm used.

```{r Iterative Preference - delegate sampling}
# show delegate sampling
paste0("Person ", results_iterativepreference[[2]])

paste0("Person ", sampling)
```

In the third output is the table of delegates' initial preferences to compare our allocations against.

```{r Iterative Preference - delegate preferences}
# show delegate preferences
utility_delegates %>%
datatable()
datatable(data = utility_delegates,
options = list(dom = 't'),
rownames = FALSE)
```

# Hope you enjoyed the talk! :)
Loading