Как создать произвольное сопоставление между строками из двух 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