An R function I built to automate my family’s secret santa matching process
Every year my extended family does a gift exchange where each member of the family is randomly paired with another member—the one caveat being that each person must be matched with someone outside of their immediate family. Traditionally, the matches are assigned using the good old “pick a name from a hat” method. If you draw a name from within your immediate family, you try again until you get someone who isn’t. This process seemed like an excellent candidate for automation, and with a couple weeks of R under my belt it felt like a perfect project to apply what I had learned so far.
tidyverse, here, and gtFirstly, I read in a CSV that contains two variables we care about: Person and Group. Each family member is listed in the Person column, and the label created for their family unit under Group.
xmas_names <- read_csv(here("data", "xmas_names.csv")) %>%
select(Person, Group)
I want to create a function that outputs a dataframe with two columns, Giver and Recipient, that lists each member of the family and who they will be giving a gift to. Let’s call the function match_maker.
The function will first create the framework of the aforementioned ouput_df and a vector that we’ll populate with the names of those that have already been matched.
match_maker <- function() {
output_df <- data.frame(Giver = seq(1:nrow(xmas_names)), Recipient = seq(1:nrow(xmas_names)))
already_matched <- vector(mode = "character")Within our function we’ll have a for loop that iterates through each row in our xmas_names dataframe and randomly pairs the Person in each row with another person from outside of their immediate family unit (Group).
The loop will iterate along the sequence 1:nrow(xmas_names).
In order to adjust our pool of “allowable” matches within each iteration, we’re going to create a temporary dataframe—temp_names—that is first assigned to xmas_names and then filtered based on our two criteria: the person isn’t in the same Group and hasn’t been matched already. We’ll use dplyr::filter() to subset temp_names to meet these parameters.
for (i in 1:nrow(xmas_names)) {
temp_names <- xmas_names %>%
filter(Group != xmas_names$Group[i]) %>%
filter(!Person %in% already_matched)Next, before the loop goes any further we’ll need to add an if statement that saves our function from hitting a dead end. Depending on the order in which matches are randomly assigned, it’s possible for only members of a single family unit to remain in the final iterations. When this happens, our temp_names dataframe will be empty because there are no longer any viable matches, and our code will break. The if statement will restart the function if temp_names becomes empty before all people have been matched.
if (nrow(temp_names) == 0) {
return(match_maker())Next we’re going to generate a random number using the sample() function and assign it to sample_number. We’ll use this number to index temp_names$Person and assign that value to match. sample_number has to be in the range 1:nrow(temp_names) because temp_names is our temporary dataframe of viable match candidates. The person being matched we can call matchee.
And we need to append() our current match to our vector already_matched which is used to filter temp_names so that person can’t be selected as a recipient again.
}
sample_number <- sample(1:nrow(temp_names), 1)
match <- temp_names$Person[sample_number]
matchee <- xmas_names[[i, 1]]
already_matched <- append(already_matched, match)Lastly, we’ll add matchee and match to the output_df, and instruct the function to return output_df once the for loop has iterated through all names on the list.
output_df$Giver[i] <- matchee
output_df$Recipient[i] <- match
}
return(output_df)
}match_maker <- function() {
output_df <- data.frame(Giver = seq(1:nrow(xmas_names)), Recipient = seq(1:nrow(xmas_names)))
already_matched <- vector(mode = "character")
for (i in 1:nrow(xmas_names)) {
temp_names <- xmas_names %>%
filter(Group != xmas_names$Group[i]) %>%
filter(!Person %in% already_matched)
if (nrow(temp_names) == 0) {
return(match_maker())
}
sample_number <- sample(1:nrow(temp_names), 1)
match <- temp_names$Person[sample_number]
matchee <- xmas_names[[i, 1]]
already_matched <- append(already_matched, match)
output_df$Giver[i] <- matchee
output_df$Recipient[i] <- match
}
return(output_df)
}
Now let’s make some matches! (I used gt() here just to create a nice table for the output)
match_maker() %>% gt()
| Giver | Recipient |
|---|---|
| Anna | Laura |
| Phil | Pam |
| Holt | Claire |
| Casey B | Jason |
| Peter | Justin |
| Robbie | Anna |
| Laurie | Casey B |
| Austin | Barbara |
| Casey O | Phil |
| Jesse | James |
| Kevin | Rachel |
| Steve | Casey O |
| Pam | Kate |
| Justin | Austin |
| Hilary | Holt |
| Adam | Luke |
| Claire | Tommy |
| Tommy | Adam |
| Barbara | David |
| Jason | Laurie |
| Laura | Jesse |
| Luke | Hilary |
| Rachel | Kevin |
| James | Robbie |
| Kate | Peter |
| David | Steve |
For attribution, please cite this work as
Menzies (2021, Aug. 23). Peter Menzies: SecRet Santa. Retrieved from https://petermenzies.github.io/posts/2021-08-23-secret-santa/
BibTeX citation
@misc{2021-23-08-secret-santa-r-script,
author = {Menzies, Peter},
title = {Peter Menzies: SecRet Santa},
url = {https://petermenzies.github.io/posts/2021-08-23-secret-santa/},
year = {2021}
}