ランダムマッチとスイスドローでの対戦結果の違いの検証、など
はじめに
前回、ポケカデータバンクさんのデータを使わせていただき、JCSの結果データを元に9万件のデータをRで作りシミュレーションする、ということを行ってみました。
その際に行ったシミュレーションは完全ランダムなマッチングでのシミュレーションでした。
しかし、実際の大会はスイスドロー形式で行われるため、それによる差異があるのではないか、という仮説が残りました。
それを検証したかったのですが、スイスドロー形式のマッチングのプログラムをどうやったらいいか全然思いつかなかったので、とりあえずChatGPT先生に聞いてみたところ、あっさりできてしまったのでスイスドローでのデータを作ってシミュレーションしてみました。
まじすげぇChatGPT
方法
詳しいやり方は後述しますが基本的にChatGPTで作っています。
同時にランダムマッチの方もChatGPTに聞いてみたらもっとよさげなのができたのでそちらを使っています。
前回は10,000人のプレイヤーが9戦する、90,000件のデータを使いましたが、今回は5倍に増やして50,000人のプレイヤーが9戦する、450,000件の対戦データを生成して分析しました。
(だいたいそれくらいがRの処理の限界でした)
結果
結果はこんな感じでした。
最後の7勝以上する割合だけ合計を記載しているのは、ランダムマッチ形式とスイスドロー形式で母数が同じでも7勝以上する人数が変わってくるからです。
実際の少数の分布は次のようになりました。
ランダムマッチでは多少ばらつきが大きくなっていることが分かります。
で、肝心の結果ですが、見事にほとんど差が出ませんでした。
平均や分散の検定も行ってみたのですが、全然有意な差は出ませんでした。
同じデッキを使っても勝率が高い人、低い人がいるのでそういう要素を入れれば差が出るのかも知れませんが、デッキタイプの相性だけではサンプル数を多くすると差が出ないということですね。
公式結果を踏まえた検証
ここまでやって、差が出なかったのでこれでは全く面白くない記事になってしまうなー、とか思っていたのですが、
そんな時に公式チャンネルから実際の使用率や勝率のデータが出ていました。
ということでデッキタイプごとの勝率データをこちらに書き換えてみたらどうなるかを検証してみました。
今回使用した勝率表と、公式チャンネルで発表された勝率表を比べてみます。
まずはPDBから取ってきた勝率表です。
次に、公式チャンネルで出されていたデータです。
(公式でカビゴンの勝率が出されていないのは勝率がバグってて大騒ぎになるからだと邪推している)
これだけだとわかりにくいので両者の差を表した表です。数字が大きいほど公式から発表された勝率の方が大きいものです。
これを見てみると
全体的に大きく勝率を上げているのはリザードン
全体的に大きく勝率を下げているのはロスギラ
というように見えますね。タケルライコ、ルギアも若干下げているでしょうか。
勝率をこのデータに書き換えてみてシミュレーションしてみました。
結果は次の通りです。
この場合、「タケルライコ」、「リザードン」、「ロスギラ」、「サーナイト」、「ディアルガ」が変更前後で平均勝数に差がある、という検定結果が出ました。
「1%水準で有意」というのは
ということですがこれだと分かりにくいので、
「99%以上の確率で平均値に差がある」
と考えてだいたい問題ないです。
今回数値を変更しなかったデッキタイプは平均値に差は出ないという結果になりました。
リザードン、サーナイトは勝率も7勝以上する確率も上がって、
ロスギラ、タケルライコは逆に下がっているという結果になりました。
特にリザードンの増加とロスギラの低下は顕著でしたね。
ここから得られる考察としては
元々のデータは変幻の仮面が出てからナイトワンダラーが出るまでの全期間の勝率のデータを使用している。それに対して直前で研究が進み、対策がされて勝率が本番では変わった。
ということが考えられます。
なので今度は
変幻の仮面発売からJCS1週間前までの勝率を使用したデータ
JCS直前の1週間だけの勝率を使用したデータ
で比較してみると面白いかもしれません。
こんな遊びができるようになったのも、PDBとChatGPTのおかげです。
マジ神です。
おまけ
スイスドローをシミュレーションするプログラムについて、ChatGPTに聞いてみた内容です。
まずこんな感じで聞いてみました。
「スイスドローの対戦をRでシミュレーションすることはできますか?」
すると返ってきた回答が以下の通りです。
ーーーーーーー以下ChatGPTの回答ーーーーーーー
はい、Rを使用してスイスドローの対戦をシミュレーションすることは可能です。以下に、簡単なスイスドローのシミュレーションを行う方法を示します。この例では、プレイヤーのリストを作成し、各ラウンドで対戦相手を決定し、勝者をランダムに決めて順位を更新します。
スイスドローのシミュレーション手順
プレイヤーリストの作成
各プレイヤーの初期スコアを0に設定
指定されたラウンド数だけ対戦を繰り返す
各ラウンドで同じスコアのプレイヤー同士を対戦させる
勝者をランダムに決定し、スコアを更新
最終順位を表示
以下に、具体的なRコードの例を示します。
# ライブラリの読み込み
library(dplyr)
# プレイヤーのリストを作成
players <- data.frame(
ID = 1:8,
Name = c("Alice", "Bob", "Charlie", "David", "Eve", "Frank", "Grace", "Heidi"),
Score = 0
)
# スイスドローのシミュレーション関数
swiss_draw_simulation <- function(players, rounds) {
for (round in 1:rounds) {
# 現在のスコアでソート
players <- players %>% arrange(desc(Score))
# 対戦ペアを作成
pairings <- split(players, rep(1:(nrow(players)/2), each = 2))
for (pair in pairings) {
if (nrow(pair) == 2) {
# 勝者をランダムに決定
winner <- sample(pair$ID, 1)
players$Score[players$ID == winner] <- players$Score[players$ID == winner] + 1
}
}
}
return(players)
}
# シミュレーションの実行
set.seed(123) # 結果の再現性を持たせるためのシード値
rounds <- 5 # ラウンド数
final_standings <- swiss_draw_simulation(players, rounds)
# 最終順位の表示
final_standings <- final_standings %>% arrange(desc(Score))
print(final_standings)
説明
プレイヤーリストの作成:
プレイヤーのID、名前、スコアを含むデータフレームを作成します。
スイスドローのシミュレーション関数:
`swiss_draw_simulation`関数は、指定されたラウンド数だけ対戦を繰り返します。
各ラウンドでプレイヤーをスコア順に並べ替え、スコアの近いプレイヤー同士を対戦させます。
勝者はランダムに決定され、勝者のスコアが1増加します。
シミュレーションの実行:
シミュレーションを実行し、最終順位を表示します。
このシミュレーションは非常に基本的なものであり、実際のスイスドロー大会ではもっと複雑な条件やルールが適用される場合があります。しかし、このコードは基本的な概念を示しており、必要に応じて拡張や修正が可能です。
ーーーーーーーここまでーーーーーーー
これだけだとサンプリングを出現率に応じて行うことができなかったり、勝率を対面ごとに設定できなかったのでここから色々と聞いて修正してもらいました。
質問はこんな感じで聞いています。
(毎回それに応じたコードが返ってくるのですが、そのコードは省略します)
「勝者を決めるための勝率を対戦相手ごとに設定するにはどのようにしたら良いですか?」
「勝率が正規化されたものではなく、例えばAliceはBobに70%で勝つが、Davidには20%しか勝てない、といったように相性に差がある場合にはどのようにしたら良いですか?」
「IDはユニークだが、Nameは重複することもあるようにしたい」
「勝率は名前によって同じままで良いのですが、勝率のマトリックスをまとめたい。
この場合はwin_rate_matrixを4×4のマトリックスで収められないか。」
「りんご、みかん、スイカ、メロンの4種類が40%,30%,20%,10%の確率で発生するように100個のデータをサンプリングしたいです、重複はあっても良いです。」
「スイスドローで同じ相手とはマッチングしないようにしたいです」
結果、最終的に返ってきた回答とコードは次のような感じです。
ーーーーーーーここからーーーーーーー
スイスドロー形式で、同じ相手とは二度と対戦しないようにするためには、対戦履歴を記録しておき、ペアリングの際にその履歴を確認して重複を避けるようにします。以下はその実装例です。
スイスドローシミュレーションのコード (同じ相手と再度対戦しない)
# ライブラリの読み込み
library(dplyr)
# プレイヤーのリストを作成 (名前に重複がある場合も考慮)
players <- data.frame(
ID = 1:8,
Name = c("Alice", "Bob", "Charlie", "David", "Alice", "Bob", "Charlie", "David"),
Score = 0
)
# 名前ベースの勝率マトリックスの作成
name_win_rate_matrix <- matrix(c(
0.5, 0.7, 0.6, 0.2, # Alice vs others
0.3, 0.5, 0.4, 0.6, # Bob vs others
0.4, 0.6, 0.5, 0.7, # Charlie vs others
0.8, 0.4, 0.3, 0.5 # David vs others
), nrow = 4, byrow = TRUE)
colnames(name_win_rate_matrix) <- c("Alice", "Bob", "Charlie", "David")
rownames(name_win_rate_matrix) <- c("Alice", "Bob", "Charlie", "David")
# ペアリング履歴の初期化
pairing_history <- matrix(0, nrow = nrow(players), ncol = nrow(players))
rownames(pairing_history) <- players$ID
colnames(pairing_history) <- players$ID
# スイスドローのシミュレーション関数
swiss_draw_simulation <- function(players, name_win_rate_matrix, pairing_history, rounds) {
pairings_list <- list()
for (round in 1:rounds) {
# 現在のスコアでソート
players <- players %>% arrange(desc(Score))
round_pairings <- c()
used_ids <- c()
for (i in seq(1, nrow(players), by = 2)) {
# プレイヤーを1対1でペアリングする
player1 <- players[i, ]
# すでにペアリングに使われたプレイヤーはスキップ
if (player1$ID %in% used_ids) next
for (j in (i+1):nrow(players)) {
player2 <- players[j, ]
# すでにペアリングに使われたプレイヤーはスキップ
if (player2$ID %in% used_ids) next
# 過去に対戦していないペアを探す
if (pairing_history[player1$ID, player2$ID] == 0) {
# ペアリングを保存
pairing_id <- paste(player1$ID, "(", player1$Score, ")", "_", player2$ID, "(", player2$Score, ")", sep = "")
round_pairings <- c(round_pairings, pairing_id)
# ペアリング履歴を更新
pairing_history[player1$ID, player2$ID] <- 1
pairing_history[player2$ID, player1$ID] <- 1
# 使用されたIDを記録
used_ids <- c(used_ids, player1$ID, player2$ID)
# 勝率に基づいて勝者を決定
win_prob <- name_win_rate_matrix[player1$Name, player2$Name]
winner_id <- if (runif(1) < win_prob) player1$ID else player2$ID
# 勝者のスコアを更新
players$Score[players$ID == winner_id] <- players$Score[players$ID == winner_id] + 1
break
}
}
}
pairings_list[[paste("Round", round)]] <- round_pairings
}
return(list(players = players, pairings_list = pairings_list, pairing_history = pairing_history))
}
# シミュレーションの実行
set.seed(123) # 結果の再現性を持たせるためのシード値
rounds <- 5 # ラウンド数
results <- swiss_draw_simulation(players, name_win_rate_matrix, pairing_history, rounds)
# 最終順位の表示
final_standings <- results$players %>% arrange(desc(Score))
print(final_standings)
# ペアリングリストの表示
pairings_list <- results$pairings_list
print(pairings_list)
説明
ペアリング履歴の初期化:
`pairing_history` マトリックスを作成し、プレイヤー間の対戦履歴を記録します。このマトリックスはプレイヤーのIDを行と列に持ち、対戦した場合には1、対戦していない場合には0が格納されます。
スイスドローのシミュレーション関数:
ペアリングの際に、過去に対戦したことがないペアを探します。`pairing_history` を参照し、すでに対戦済みのペアはスキップします。
対戦履歴の更新:
対戦ペアが決定したら、`pairing_history` マトリックスを更新し、対戦済みとして記録します。
結果の表示:
シミュレーションの結果として、最終順位と各ラウンドのペアリングリストを表示します。
このコードにより、スイスドロー形式で同じ相手と二度と対戦しないようにすることができます。
ーーーーーーーここまでーーーーーーー
あとはパラメーターの部分を今回変えれば、今回行ったシミュレーションができました。
コードの基本的なことが理解できて、適切な質問を投げることができればだいたいのコードはChatGPTが作ってくれると思います。
RもChatGPTもこのレベルを行うのであれば無料でできますので、夏休みの自由研究とかにいいかも。
この記事が気に入ったらサポートをしてみませんか?