Как создать произвольное сопоставление между строками из двух data.tables(или data.frames)

В этом примере я буду использовать пакет data.table.

Предположим, у вас есть таблица тренеров

coaches <- data.table(CoachID=c(1,2,3), CoachName=c("Bob","Sue","John"), NumPlayers=c(2,3,0))
coaches
   CoachID CoachName NumPlayers
1:       1       Bob          2
2:       2       Sue          3
3:       3      John          0

и таблица игроков

players <- data.table(PlayerID=c(1,2,3,4,5,6), PlayerName=c("Abe","Bart","Chad","Dalton","Egor","Frank"))
players
   PlayerID PlayerName
1:        1        Abe
2:        2       Bart
3:        3       Chad
4:        4     Dalton
5:        5       Egor
6:        6      Frank

Вы хотите сопоставить каждого тренера с набором игроков таким образом, чтобы

  • Количество игроков, привязанных к каждому тренеру, определяется полем NumPlayers
  • Нет двух тренеров, привязанных к одному и тому же игроку
  • Игроки и тренеры сопоставляются случайным образом.

Как вы это делаете?

exampleResult <- data.table(CoachID=c(1,1,2,2,2,3), PlayerID=c(3,1,2,5,6,NA))
exampleResult

   CoachID PlayerID
1:       1        3
2:       1        1
3:       2        2
4:       2        5
5:       2        6
6:       3       NA

Ответ 1

Вы можете пробовать без замены идентификаторов проигрывателя, хватая общее количество игроков, которые вам нужны:

set.seed(144)
(selections <- sample(players$PlayerID, sum(coaches$NumPlayers)))
# [1] 1 4 3 2 6

Каждый игрок будет иметь равную вероятность включения в selections, а упорядочение этого вектора является случайным. Поэтому вы можете просто назначить этих игроков каждому слоту для тренировки:

data.frame(CoachID=rep(coaches$CoachID, coaches$NumPlayers),
           PlayerID=selections)
#   CoachID PlayerID
# 1       1        1
# 2       1        4
# 3       2        3
# 4       2        2
# 5       2        6

Если вы хотите иметь значение NA для любых тренеров без выбора игрока, вы можете сделать что-то вроде:

rbind(data.frame(CoachID=rep(coaches$CoachID, coaches$NumPlayers),
                 PlayerID=selections),
      data.frame(CoachID=coaches$CoachID[coaches$NumPlayers==0],
                 PlayerID=rep(NA, sum(coaches$NumPlayers==0))))
#   CoachID PlayerID
# 1       1        1
# 2       1        4
# 3       2        3
# 4       2        2
# 5       2        6
# 6       3       NA

Ответ 2

Получите спрос и предложение с каждой стороны, так сказать:

demand <- with(coaches,rep(CoachID,NumPlayers))
supply <- players$PlayerID

Тогда я бы сделал...

randmatch <- function(demand,supply){
  n_demand  <- length(demand)
  n_supply  <- length(supply)
  n_matches <- min(n_demand,n_supply)

  if (n_demand >= n_supply) 
    data.frame(d=sample(demand,n_matches),s=supply)
  else 
    data.frame(d=demand,s=sample(supply,n_matches))
}

Примеры:

set.seed(1)
randmatch(demand,supply)    # some players unmatched, OP example
randmatch(rep(1:3,1:3),1:4) # some coaches unmatched 

Я не уверен, что это тот случай, который OP хотел бы покрыть.


Для желаемого выхода OP...

m <- randmatch(demand,supply)
merge(m,coaches,by.x="d",by.y="CoachID",all=TRUE)
#   d  s CoachName NumPlayers
# 1 1  2       Bob          2
# 2 1  6       Bob          2
# 3 2  3       Sue          3
# 4 2  4       Sue          3
# 5 2  1       Sue          3
# 6 3 NA      John          0

Аналогично...

merge(m,players,by.x="s",by.y="PlayerID",all=TRUE)
#   s  d PlayerName
# 1 1  2        Abe
# 2 2  1       Bart
# 3 3  2       Chad
# 4 4  2     Dalton
# 5 5 NA       Egor
# 6 6  1      Frank

Ответ 3

Вот ответ, используя простой dplyr. Сначала нужно выбрать потребности тренера, затем выполнить выборку игроков и, наконец, все это сделать.

library(dplyr)

set.seed(1234)

coach_needs <- coaches %>%
  group_by( CoachID ) %>%
  do( sample_n(., size=.$NumPlayers, replace=TRUE) ) %>%
  select( -CoachID ) %>% ungroup()

player_needs <- players %>%
  sample_n( size = nrow(coach_needs))

result <- cbind(coach_needs, player_needs)

result

Что дает мне:

   CoachID CoachName NumPlayers PlayerID PlayerName
1:       1       Bob          2        4     Dalton
2:       1       Bob          2        1        Abe
3:       2       Sue          3        5       Egor
4:       2       Sue          3        2       Bart
5:       2       Sue          3        3       Chad

UPDATE: Если для тренеров с NumPlayer == 0 требуются NA, то это простой однострочный:

result <- cbind(coach_needs, player_needs) %>%
  rbind( coaches %>% filter(NumPlayers == 0), fill=TRUE )

result

который дает мне это:

   CoachID CoachName NumPlayers PlayerID PlayerName
1:       1       Bob          2        4     Dalton
2:       1       Bob          2        1        Abe
3:       2       Sue          3        5       Egor
4:       2       Sue          3        2       Bart
5:       2       Sue          3        3       Chad
6:       3      John          0       NA         NA