SecRet Santa

R

An R function I built to automate my family’s secret santa matching process

Peter Menzies true
08-23-2021

A litle context…

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.

Packages used: tidyverse, here, and gt

Reading in data

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) 

Creating the function

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")

For loop

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)
  
}

Function in full:

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)
  
}

The result:

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

Citation

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}
}