Roalda
Roalda

Reputation: 33

R: Random values from one column in 5 columns

I have a dataframe (df) containing approximately 100 soccer player numbers (if more players sign-up, the number increases). Each player_number consists of 6 digits (e.g. 178530).

Every player should review 5 other players, so eventually all players are reviewed by 5 others. Therefore I would like to randomly assign 5 different player numbers (from the player_number column) to each player_number. To prevent assigning reviews to themselves and/or players having to review the same player twice (or more), each player_number should only occur once in every column and in every row. The dataframe should look like this:

player_number  review1  review2  review3  review4  review5
178530         207145    655600   443274   604060   804226
245678         947821    214525   332324   174589   868954      
…

Player 178530 needs to review players 207145, 655600 etc.

For review1 column, I have used: set.seed(1) df$review1 <- sample(df$player_number, nrow(df), replace=F)

This works for review1, but applying it to the other review columns leads to duplicate player_number in several rows. Can anyone help me out so each player_number only occurs once in every column and in every row? Thanks in advance.

Edit: in a previous version I simplified the player_number too much (1:100)

Upvotes: 3

Views: 426

Answers (3)

milan
milan

Reputation: 4970

You could write a function for that. The idea is to take your vector of 100 IDs or player numbers; randomly sample 5 unique starting values for 5 new vectors and bind these to have your result where no IDs are found more than once in every row and column.

For example, if you have numbers 1 to 5 (that order), and want to assign 3 of the numbers to each number of 1 to 5; having no number more than once in a row or column.

1 3 2 5
2 4 3 1
3 5 4 2
4 1 5 3
5 2 1 4

This is the function that does that.

play <- function(v, i){
  starts <- sample(2:length(v), i, replace=F)
  v2 <- v
  for(m in 1:i){
    v2 <- cbind(v2, c(v[starts[m]:length(v)], v[0:(starts[m]-1)]) )
  }
  colnames(v2) <- c('id', paste0('R', 1:i))
  return(v2)
}

Try it.

play(1:5, 3)

This is a similar function that takes a dataframe because you are asking for that in the question.

playDF <- function(df, i){
  starts <- sample(1:nrow(df), i+1, replace=F)
  sq2 <- NULL
  for(m in 1:(i+1)){
    sq2 <- cbind(sq2, c(df[starts[m]:nrow(df),], df[0:(starts[m]-1),]) )
  }
  sq2 <- as.data.frame(sq2)
  colnames(sq2) <- c('player_number', paste0('review', 1:(i)))
  return(sq2)
}

I've added example data for your problem. Run the function and apply it to the data.

df <- data.frame(player_number=c(sample(111111:999999, 100, replace=F)))
playDF(df, 5)

Upvotes: 1

Lennyy
Lennyy

Reputation: 6132

Might not be the most efficient, but this is a solution using just base R. In here I just sample 1 number at a time, from a vector of 1:100 without the already present numbers in the current row and current column.

For row 100 this would mean numbers are sampled from a vector of length 1, which causes the sample function to behave differently. Therefore, to prevent this unexpected behaviour, I kindly bestowed the sample.vec custom function from Sampling in R from vector of varying length.

df <- data.frame(player_number = c(1:100))
df <- cbind(df, matrix(NA, 100, 5))

sample.vec <- function(x, ...) x[sample(length(x), ...)]

for(i in 1:100){
  for(j in 2:6){
    df[i,j] <- sample.vec(setdiff(c(1:100),c(df[i,], df[,j])), 1)
  }
}

UPDATE after change in question: If you like to use those custom player numbers of 6 digits, an option could be to convert alll columns to factors, using 1:100 as the levels and the actual player numbers as labels. So after the code above, you could do something like this:

set.seed(1); player_number = sort(sample(100000:999999, 100)) # in your data, just create this vector beforehand using the actual player numbers
df[] <- lapply(df, function(x) {factor(x, levels = c(1:100), labels = player_number)})

Proof:

head(df)
  player_number      1      2      3      4      5
1        112050 400373 466123 666197 888560 332198
2        120997 887728 917384 701596 682327 189514
3        153035 332198 315644 745845 469035 800949
4        155607 544171 759047 992698 450960 799685
5        163607 908546 338957 694713 267589 406304
6        175816 469035 120997 459962 875044 447493


table(apply(df, 1, function(x) {length(unique(x))}))
  6 
100 

table(apply(df, 2, function(x) {length(unique(x))}))
100 
  6

Upvotes: 1

jyjek
jyjek

Reputation: 2707

library(tidyverse)
df=data.frame(x=1:100)

  df%>%
  mutate(number = map(x, ~ glue::collapse(sample(x,5,replace=),",")))%>%
  separate(number,into=  glue::glue("review{1:5}"))

Upvotes: 0

Related Questions