Generate n random pairs of each items within a vector

Viewed 96

Suppose I have a character vector of even length (e.g. players = c("Chris", "Fred", "Brian", "James", "Bill", "Hicham")), I would like to randomly generate n = unique pairs per player.

For now I figured out how to generate all possible pairs:

matchup <- data.frame(t(combn(players, 2)))

I'm looking for a solution to pick n number of random pairs per players in that vector.

The expected output would be that each player in the vector has n number of matchups, so if n = 3, it could be something like:

      X1     X2
1  James  Bill
2  Chris  Hicham
3  Fred   Bill
4  Chris  Bill
5  James  Brian
6  Brian  Hicham
7  Chris  Brian
8  James  Fred
9  Fred   Hicham

James : 3 matchup
Chris : 3 matchup
Hicham : 3 matchup
Fred : 3 matchup
Brian : 3 matchup
Bill : 3 matchup

3 Answers

You can use the sample_degseq function from the igraph package to generate a random graph with given degree (here 3) and no multiples.

players <- c("Chris", "Fred", "Brian", "James", "Bill", "Hicham")
p <- length(players)
n <- 3 # the degree

set.seed(123) # Omit this to generate a purely random network graph

library(igraph)

g = sample_degseq(rep(n, p), method = "simple.no.multiple")
V(g)$name <- players  # add the players names to the vertices
g
#[1] Chris--Brian  Chris--James  Chris--Bill   Fred --Brian  Fred --Bill   Fred --Hicham
#[7] Brian--Hicham James--Bill   James--Hicham

You can also visualise this using an adjacency matrix:

as_adj(g)
#6 x 6 sparse Matrix of class "dgCMatrix"
#       Chris Fred Brian James Bill Hicham
#Chris      .    .     1     1    1      .
#Fred       .    .     1     .    1      1
#Brian      1    1     .     .    .      1
#James      1    .     .     .    1      1
#Bill       1    1     .     1    .      .
#Hicham     .    1     1     1    .      .

Or with a plot.

plot(g)

enter image description here

You can see that each player is connected to three other players. To convert the graph into a data frame containing your player columns.

as_data_frame(g)
#   from     to
#1 Chris  Brian
#2 Chris  James
#3 Chris   Bill
#4  Fred  Brian
#5  Fred   Bill
#6  Fred Hicham
#7 Brian Hicham
#8 James   Bill
#9 James Hicham

The following is a brute-force approach that works for any mathematically valid combination of values (i.e. either n is even, or the number of players is even):

each_appears_n_times <- function(players, combo, n) {
  for (player in players) {
    times_appears <- sum(apply(combo, 1, function(row) grepl(player, row)))
    if (times_appears != n) {
        return(FALSE)
    }
  }
  return(TRUE)
}

find_combo <- function(players, n) {
  size <- n * length(players) / 2
  matchup <- data.frame(t(combn(players, 2)))
  while(TRUE) {
    combo <- sample(matchup[sample(nrow(matchup), size), ])
    if (each_appears_n_times(players, combo, n)) {
      return(combo)
    }
  }
}

my_players <- c("Chris", "Fred", "Brian", "James", "Bill", "Hicham")
my_n <- 3

find_combo(my_players, my_n)

#        X2    X1
# 15 Hicham  Bill
# 12 Hicham Brian
# 14 Hicham James
# 1    Fred Chris
# 2   Brian Chris
# 13   Bill James
# 8    Bill  Fred
# 6   Brian  Fred
# 3   James Chris

We could do a group_by slice

n <- 3
matchup %>%
   group_by(X1) %>%
   slice(if(n() < n) row_number() else sample(row_number(), n))
Related