はじめに
去年、一昨年とこの記事を書いてきたので、今年もアドベンドカレンダーでこの記事を書きます。
Pythonでのデータ分析の実施
まずは、採点結果のcsvファイルを用意。
,大吉,塙,哲夫,若林,石田,山内,柴田,海原,礼二
令和ロマン,96,93,90,94,96,96,95,97,93
ヤーレンズ,92,92,91,92,92,91,91,94,90
真空ジェシカ,97,94,90,93,95,97,94,95,94
マユリカ,93,91,88,91,91,90,89,96,91
ダイタク,90,93,89,92,90,92,88,94,92
ジョックロック,89,91,91,90,89,93,88,95,93
バッテリィズ,95,93,95,95,97,96,96,97,97
ママタルト,88,89,89,89,90,93,89,92,93
エバース,94,94,93,94,96,94,93,94,96
トム・ブラウン,95,95,92,93,88,90,87,94,89
ここから先はJupyter Notebookを使用します。(※google colabでも可能ですが、グラフの日本語表示に少し手を加える必要があります。)
ライブラリはpandas, matplotlib, seaborn, scikit-learnを使うのでpipでインストールしておいてください。
まずはpandasでcsvファイルの読み込み
import pandas as pd
file_path = 'M1_2024_score.csv'
m1_data = pd.read_csv(file_path)
m1_data
正しくcsvファイルを読めていることを確認。
次にpandasのdescribe()で基本的な統計量の要約を表示します。
m1_data.describe()
ここで、注目するのは注目するのは標準偏差(std)です。標準偏差はデータの散らばり具合を同じ単位で表したもので、標準偏差も大きくなると、データが平均値から離れて広がっていることを意味します。今年のデータだと、石田、柴田、大吉が大きいバラツキで点数をつけていて、海原、塙、若林が小さいバラツキで点数をつけていることが分かります。
次に箱ひげ図で可視化してみます。
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.family'] = 'MS Gothic'
plt.title('M1_2024の採点分布')
plt.xlabel('審査員')
plt.ylabel('得点')
sns.boxplot(data=m1_data)
箱(四分位範囲)の高さやヒゲの長さが大きいほど、スコアのばらつきが大きいことを示すので、やはり石田、柴田、大吉に幅広い点数をつけている傾向があり、海原、塙、若林に関してはスコアの分散は比較的小さいと考えられます。
また、箱ひげ図の箱の中の太い線が各審査員のスコアの中央値を示すので、海原は比較的に高い点数、柴田、哲夫は低い点数をつける傾向があります。
次に各審査員がどこに最高点と最低点をつけたのかを見てみます。
m1_data.rename(columns={m1_data.columns[0]: "コンビ名"}, inplace=True)
highest_scorers_list = []
lowest_scorers_list = []
judges = m1_data.columns[1:10]
for judge in judges:
max_score = m1_data[judge].max()
min_score = m1_data[judge].min()
highest_scorers = m1_data[m1_data[judge] == max_score][["コンビ名"]]
lowest_scorers = m1_data[m1_data[judge] == min_score][["コンビ名"]]
highest_scorers_list.append((judge, ", ".join(highest_scorers["コンビ名"]), max_score))
lowest_scorers_list.append((judge, ", ".join(lowest_scorers["コンビ名"]), min_score))
# dataframeに変換
highest_scorers_df = pd.DataFrame(highest_scorers_list, columns=["審査員", "最高得点者", "最高得点"])
lowest_scorers_df = pd.DataFrame(lowest_scorers_list, columns=["審査員", "最低得点者", "最低得点"])
# 2つのdataframeを結合
judge_summary_complete = pd.merge(highest_scorers_df, lowest_scorers_df, on="審査員")
judge_summary_complete
1stステージ1位のバッテリィズは9人中6人が最高得点をつけていますね。トム・ブラウンに最高得点をつけている人が1人、最低得点をつけている人が4人となっているので、審査員によって好き嫌いが分かれるネタだった事がわかります。(たしかにトム・ブラウンのネタはメッチャ笑ったんだけど、途中で「一体、何をみせられてるんだろう?」ってなりました。)
次は、審査結果が近い人と遠い人がいそうなので、PCAで次元消滅させて可視化してみましょう。
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
import numpy as np
# 審査員スコアデータを抽出
judge_scores = m1_data.iloc[:, 1:10].T # Transpose to analyze judges
judge_names = judge_scores.index
# データを標準化
scaler = StandardScaler()
scaled_data = scaler.fit_transform(judge_scores)
# PCAを実行
pca = PCA(n_components=2) # Reduce to 2 dimensions for visualization
pca_result = pca.fit_transform(scaled_data)
# PCA結果をデータフレームに格納
pca_df = pd.DataFrame({
"審査員": judge_names,
"PC1": pca_result[:, 0],
"PC2": pca_result[:, 1]
})
# PCA結果をプロット
plt.figure(figsize=(8, 6))
plt.scatter(pca_df["PC1"], pca_df["PC2"], color='blue', edgecolor='k')
for i, txt in enumerate(judge_names):
plt.annotate(txt, (pca_df["PC1"][i], pca_df["PC2"][i]), fontsize=10)
plt.title("審査員の採点傾向のPCA結果", fontsize=14)
plt.xlabel("主成分1 (PC1)", fontsize=12)
plt.ylabel("主成分2 (PC2)", fontsize=12)
plt.axhline(0, color='gray', linestyle='--', linewidth=0.7)
plt.axvline(0, color='gray', linestyle='--', linewidth=0.7)
plt.grid()
plt.show()
この結果を見ると、関西吉本系の石田、山内、礼二と非関西吉本系の塙、若林、大吉が比較的に近い採点をしていて、離れたところに哲夫、柴田、海原が独特の採点をしていますね。
なので、今年の審査員のキーマンとしては他の審査員と違った観点を持ちバラツキの大きい哲夫、柴田の2人だったのかな、と思います。
最後に今までやってなかった試みとして、各審査員毎の順位と最終順位の差分が一番小さい審査員をちょっと調べてみたいと思います。(※やり方が正しいか若干自信がありません。)
# 行ごと(参加者ごと)の合計点を計算して新しい列として追加
m1_data['合計点'] = m1_data.iloc[:, 1:].sum(axis=1)
# 合計点を基に順位を計算して新しい列として追加(降順でランク付け)
m1_data['順位'] = m1_data['合計点'].rank(ascending=False, method='min').astype(int)
# 各審査員の採点順位を計算
judges = m1_data.columns[1:-2] # 審査員の列名を取得
for judge in judges:
m1_data[f'{judge}_順位'] = m1_data[judge].rank(ascending=False, method='min').astype(int)
# 最終順位との差を計算して列を追加
for judge in judges:
m1_data[f'{judge}_順位差'] = (m1_data[f'{judge}_順位'] - m1_data['順位']).abs()
# 各審査員の順位差の合計を計算
judge_ranking_diff = {judge: m1_data[f'{judge}_順位差'].sum() for judge in judges}
# 最終順位に最も近い審査員を特定
closest_judge = min(judge_ranking_diff, key=judge_ranking_diff.get)
# 最も最終順位に近い審査員を表示
print(f'最も最終順位に近い審査をした審査員: {closest_judge}')
出力
最も最終順位に近い審査をした審査員: 若林