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 gt
Firstly, 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.
<- function() {
match_maker
<- data.frame(Giver = seq(1:nrow(xmas_names)), Recipient = seq(1:nrow(xmas_names)))
output_df <- vector(mode = "character") already_matched
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)) {
<- xmas_names %>%
temp_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(1:nrow(temp_names), 1)
sample_number
<- temp_names$Person[sample_number]
match
<- xmas_names[[i, 1]]
matchee
<- append(already_matched, match) already_matched
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.
$Giver[i] <- matchee
output_df$Recipient[i] <- match
output_df
}
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} }