年俸データは扱いを間違えやすいデータです. 欠けを数えてから使う, 時代をまたぐときは名目額で並べない の2つを 確かめてから, ランキングを作ります.
library(data.table)
library(dplyr)
library(ggplot2)
library(knitr)
library(Lahman)
sal <- as.data.table(Salaries)
head(sal, 5) |> kable()
| yearID | teamID | lgID | playerID | salary |
|---|---|---|---|---|
| 2004 | SFN | NL | aardsda01 | 300000 |
| 2007 | CHA | AL | aardsda01 | 387500 |
| 2008 | BOS | AL | aardsda01 | 403250 |
| 2009 | SEA | AL | aardsda01 | 419000 |
| 2010 | SEA | AL | aardsda01 | 2750000 |
26,428行, 1985年から2016年, 5,149人分です. 単位はドルです.
MLBの球団数は1985-92年が26, 1993-97年が28, 1998年以降が30です.
expected_teams <- function(y) fifelse(y <= 1992, 26L, fifelse(y <= 1997, 28L, 30L))
by_year <- sal[, .(球団数 = uniqueN(teamID), 人数 = .N), by = yearID][order(yearID)]
by_year[, 本来 := expected_teams(yearID)]
by_year[球団数 != 本来] |> kable()
| yearID | 球団数 | 人数 | 本来 |
|---|
球団は毎年すべて揃っています. ここで安心してはいけません. 球団が揃っていても, 中の選手が揃っているとは 限りません.
支配下選手は40人, 出場登録は26人です. 1球団あたりの人数を見ます.
per_team <- sal[, .(人数 = .N), by = .(yearID, teamID)]
per_team |>
ggplot(aes(x = factor(yearID), y = 人数)) +
geom_boxplot(outlier.size = 0.7) +
geom_hline(yintercept = 20, linetype = "dashed", colour = "firebrick") +
xlab("年") +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, size = 7)) +
ggtitle("1球団あたりの登録人数 (赤破線は20人)")

MIN_ROSTER <- 20
thin <- per_team[人数 < MIN_ROSTER][order(人数)]
thin |> head(10) |> kable()
| yearID | teamID | 人数 |
|---|---|---|
| 1987 | TEX | 4 |
| 1987 | MIN | 9 |
| 1987 | SEA | 9 |
| 1987 | BOS | 15 |
| 1991 | MON | 17 |
| 1985 | SEA | 18 |
| 1985 | PHI | 19 |
| 1985 | PIT | 19 |
| 1985 | MIN | 19 |
| 1985 | ML4 | 19 |
10件の球団年が20人未満です. 最少は1987年のTEXで4人しかありません. 25人でプレーする競技で4人分しか年俸が載っていないので, この球団年の総額はまったく使えません.
該当するのは1985年から1991年に集中しています.
sal[, 薄い := paste(yearID, teamID) %in% thin[, paste(yearID, teamID)]]
sal[, .(球団年 = uniqueN(paste(yearID, teamID)),
`20人未満` = uniqueN(paste(yearID, teamID)[薄い])), by = .(年代 = (yearID %/% 10) * 10)] |>
arrange(年代) |> kable()
| 年代 | 球団年 | 20人未満 |
|---|---|---|
| 1980 | 130 | 9 |
| 1990 | 278 | 1 |
| 2000 | 300 | 0 |
| 2010 | 210 | 0 |
以下, 球団総額を出すときは20人未満の球団年を除きます. 個人の年俸そのものは欠けていても値は正しいので, 個人ランキングには全部使います.
trend <- sal[, .(平均 = mean(salary), 中央値 = as.numeric(median(salary)),
最高 = as.numeric(max(salary))), by = yearID][order(yearID)]
trend |>
tidyr::pivot_longer(-yearID, names_to = "統計量", values_to = "ドル") |>
ggplot(aes(x = yearID, y = ドル, colour = 統計量)) +
geom_line(linewidth = 0.8) +
scale_y_log10(labels = scales::dollar) +
xlab("年") + ggtitle("年俸の推移 (対数軸)")

平均年俸は1985年の $476,299から 2016年の$4,396,410へ, 9.2倍になりました.
平均と中央値の開き方にも注目してください. 中央値はあまり動いていません. 上の方だけが伸びています.
sal[, 年平均比 := salary / mean(salary), by = yearID]
金額そのままの上位です.
name_of <- as.data.table(People)[, .(playerID, 選手 = paste(nameFirst, nameLast))]
top_nominal <-
sal[order(-salary)][1:10] |>
merge(name_of, by = "playerID", sort = FALSE) |>
_[order(-salary), .(選手, 年 = yearID, 球団 = teamID,
年俸 = scales::dollar(salary), 年平均比 = round(年平均比, 1))]
kable(top_nominal)
| 選手 | 年 | 球団 | 年俸 | 年平均比 |
|---|---|---|---|---|
| Clayton Kershaw | 2016 | LAN | $33,000,000 | 7.5 |
| Alex Rodriguez | 2009 | NYA | $33,000,000 | 10.1 |
| Alex Rodriguez | 2010 | NYA | $33,000,000 | 10.1 |
| Clayton Kershaw | 2015 | LAN | $32,571,000 | 7.6 |
| Alex Rodriguez | 2011 | NYA | $32,000,000 | 9.6 |
| Zack Greinke | 2016 | ARI | $31,799,030 | 7.2 |
| David Price | 2016 | BOS | $30,000,000 | 6.8 |
| Alex Rodriguez | 2012 | NYA | $30,000,000 | 8.7 |
| Alex Rodriguez | 2013 | NYA | $29,000,000 | 7.8 |
| Miguel Cabrera | 2016 | DET | $28,000,000 | 6.4 |
その年の平均に対する倍率で並べ直します.
top_relative <-
sal[order(-年平均比)][1:10] |>
merge(name_of, by = "playerID", sort = FALSE) |>
_[order(-年平均比), .(選手, 年 = yearID, 球団 = teamID,
年俸 = scales::dollar(salary), 年平均比 = round(年平均比, 1))]
kable(top_relative)
| 選手 | 年 | 球団 | 年俸 | 年平均比 |
|---|---|---|---|---|
| Gary Sheffield | 1998 | FLO | $14,936,667 | 11.7 |
| Alex Rodriguez | 2009 | NYA | $33,000,000 | 10.1 |
| Alex Rodriguez | 2010 | NYA | $33,000,000 | 10.1 |
| Alex Rodriguez | 2005 | NYA | $26,000,000 | 9.9 |
| Alex Rodriguez | 2001 | TEX | $22,000,000 | 9.6 |
| Alex Rodriguez | 2011 | NYA | $32,000,000 | 9.6 |
| Cecil Fielder | 1995 | DET | $9,237,500 | 9.6 |
| Alex Rodriguez | 2002 | TEX | $22,000,000 | 9.2 |
| Manny Ramirez | 2004 | BOS | $22,500,000 | 9.0 |
| Cecil Fielder | 1996 | DET | $9,237,500 | 9.0 |
上位10人のうち共通なのは3人だけです. 名目額で並べると新しい年ばかりになります.
payroll <- sal[薄い == FALSE, .(総額 = sum(salary), 人数 = .N), by = .(yearID, teamID)]
payroll |>
ggplot(aes(x = yearID, y = 総額 / 1e6, group = teamID)) +
geom_line(alpha = 0.35) +
scale_y_continuous(labels = scales::comma) +
xlab("年") + ylab("年俸総額 (百万ドル)") +
ggtitle("球団年俸総額 (20人未満の球団年を除く)")

その年の平均総額に対する倍率で見ると, 球団間の開きが分かります.
payroll[, 比 := 総額 / mean(総額), by = yearID]
payroll |>
ggplot(aes(x = factor(yearID), y = 比)) +
geom_boxplot(outlier.size = 0.7) +
geom_hline(yintercept = 1, linetype = "dashed") +
xlab("年") + ylab("その年の平均総額に対する倍率") +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, size = 7)) +
ggtitle("球団間の年俸格差")

payroll |>
merge(as.data.table(Teams)[, .(yearID, teamID, name)], by = c("yearID", "teamID"),
all.x = TRUE) |>
_[order(-比)][1:10, .(球団 = name, 年 = yearID,
総額 = scales::dollar(総額), 人数,
平均の何倍 = round(比, 2))] |>
kable()
| 球団 | 年 | 総額 | 人数 | 平均の何倍 |
|---|---|---|---|---|
| New York Yankees | 2005 | $208,306,817 | 26 | 2.86 |
| New York Yankees | 2004 | $184,193,950 | 29 | 2.67 |
| New York Yankees | 2006 | $194,663,079 | 28 | 2.52 |
| New York Yankees | 2008 | $207,896,789 | 30 | 2.32 |
| New York Yankees | 2013 | $231,978,886 | 31 | 2.29 |
| New York Yankees | 2007 | $189,259,045 | 28 | 2.29 |
| New York Yankees | 2010 | $206,333,389 | 25 | 2.27 |
| New York Yankees | 2009 | $201,449,189 | 26 | 2.27 |
| Los Angeles Dodgers | 2013 | $223,362,196 | 32 | 2.21 |
| New York Yankees | 2011 | $202,275,028 | 29 | 2.18 |
年俸データで名前を選手の識別子に使うと壊れます.
dup <- name_of[, .N, by = 選手][N > 1][order(-N)]
tibble::tibble(`同じ名前の playerID が複数ある名前` = nrow(dup),
`のべ人数` = sum(dup$N)) |> kable()
| 同じ名前の playerID が複数ある名前 | のべ人数 |
|---|---|
| 750 | 1706 |
dup |> head(5) |> kable()
| 選手 | N |
|---|---|
| NA Smith | 16 |
| NA Jones | 13 |
| NA Johnson | 12 |
| NA Thompson | 7 |
| NA Brown | 6 |
750件の名前が複数人で共有されています.
LahmanにはplayerIDがあるので, ここでは問題になりません.
名前しか無いデータでは, これが黙って混ざります.
実際に名前で束ねると, 生涯年俸がどれくらい膨らむか見てみます.
career_id <- sal[, .(生涯年俸 = sum(salary)), by = playerID] |>
merge(name_of, by = "playerID")
career_nm <- career_id[, .(生涯年俸 = sum(生涯年俸), 人数 = .N), by = 選手]
merge(career_nm[人数 > 1], career_id[, .(選手, 単独 = 生涯年俸)], by = "選手") |>
_[order(-生涯年俸)][, .(選手, まとめた額 = scales::dollar(生涯年俸),
束ねた人数 = 人数)] |>
unique(by = "選手") |> head(5) |> kable()
| 選手 | まとめた額 | 束ねた人数 |
|---|---|---|
| Ken Griffey | $156,308,682 | 2 |
| Pedro Martinez | $146,785,585 | 2 |
| Kevin Brown | $131,160,502 | 3 |
| Jose Bautista | $88,183,000 | 2 |
| Chris Carpenter | $86,574,956 | 2 |
もとはNPBの年俸データ (1980-2014年, スクレイピングで集めたもの) を 扱っていました. 取得元だった monespo.com はドメインが失効して 別のサイトに変わっており, 同じデータを取り直す方法がありません. 手元のファイルが無いと再現できない分析はリポジトリに置かない方針にしたので, 今も取得できるLahmanの年俸データに載せ替えました.
当時のNPB版で見つけた問題 (スクレイプ失敗で1球団まるごと欠けていた, 同姓同名が混ざっていた, 名目額で並べていた) は, データを変えても 同じ形で出てきます. 確かめる順番は変わりません.