StatsBeginner: 初学者の統計学習ノート

統計学およびR、Pythonでのプログラミングの勉強の過程をメモっていくノート。たまにMacの話題。

「ウマ娘」にはなぜ90年代の馬が多いのか

「ウマ娘」というゲームとアニメが流行っているらしく、プレイする気や観る気はないのですが、ツイッターでたびたび流れてくるウマ娘の育成?報告をみていて、「なぜそんな昔の馬ばかりなの?」と不思議に思っていました。
で、下記の記事がウマ娘に登場する競走馬のデビュー年と引退年の一覧を作ってくれているので、集計してグラフにしてみました。


『ウマ娘』登場するウマ娘の年代を調べて一覧にしてみた!(2021年3月更新) | ウマ娘プラス


f:id:midnightseminar:20210831143542j:plain


やっぱり、90年代に活躍したウマが多いですね。
私はこの頃に競馬をやっていて(中高生だったので馬k)、2004年ぐらいからほとんど見なくなったので、ツイッターに流れてくる馬の名前が自分の観戦歴と合致していて懐かしいなと思っていました。


なんでなのかなと考えたのですが、このAmazonの異様に詳しいレビューに書いてあるように、当時の競馬を詳しくしっている人が制作チームにいるんでしょうね。
制作者の競馬愛


しかしそれだけでは、ゲーム化・アニメ化してウケるのが不思議な気もします。
他に考えられる理由としては、1988-90年頃に活躍したオグリキャップが国民的人気ホースになって、おそらくその頃からテレビが競馬を盛んに取り上げるようになった結果、要するに90年代は競馬ブームだったのでしょう。当時観ていて「ブーム」という認識はなかったけど、歴史的に振り返るとそういうことなんじゃないかと。
そのおかげで、まず90年代には「多くの人に知られている馬」が多かったんだろうと思います。そういえば、高速道路でみかける馬運車には往年の名馬の名前が書かれてあるのですが、あれも90年代頃の馬が多い気がしますね。


ただ、そうだとしても、若い人がなぜウマ娘を楽しそうにやっているのかはよく分かりません。ブームだった90年代は情報が多くて、物語や設定が作りやすいんですかね?
若い人にとっては実在した馬のことはどうでもよくて、単にゲームの内容が面白いだけなのかもしれませんが、情報が多くて製作者サイドが「人の心に響く設定」を作りやすいのなら、90年代の馬が多くなるのも分かります。


ほとんど何も調べずに印象論で書いてるので、実は「なぜ90年代の馬が多いのか」は既にどこかで答えが解説されてるのかも知れません。もしそうなら、ぜひ教えて下さい。


ちなみに自分が2000年代に入ると競馬をあまり観なくなった理由は、いろいろ勉強したりしてると他のことに興味がなくなったってのもあるのですが、一時的にスター性のある馬があまりいない状況になったんですよね。特に、スペシャルウィークからテイエムオペラオーへの流れが個人的にはあまり盛り上がれませんでした。どちらも強い馬でしたけど、なんかこう、スター性が欠けてたんです。
スペシャルウィークは個人的にあまり推してなかった(なんか前からみるとヒョロっと細くて好きなタイプではなかった)ってのが大きいだけで、まだエアグルーヴとかエルコンドルパサーとかと走ってたからいいとしても、オペラオーは良いライバルがいなかったですからね。


あとは、サンデーサイレンス産駒一強、社台グループ一強みたいな感じになったのも面白みを削いだし、短距離馬とか外国産馬にスポットが当たるようになって(エルコンドルパサーとかタイキシャトルとか好きでしたけど)、伝統的な長距離レースのプレミア感がなくなったのも大きい。どんな距離でも、牡馬でも牝馬でも、GIはGIなのだみたいな風潮になって、大レース間の序列がどんどんなくなっていきました。
はるか昔は、ダービーや天皇賞はテレビの前で正座してみるという人もいたと聞きます。私の時代にはそこまではなかったですが、GIにも由緒あるレースとそうでもないレースがあるというぐらいの意識はあった。それが、まぁ要するにテレビを盛り上げるための都合でしょうけど、どんどん平準化していったんです。それで、「ダービーに特別な思い入れがあるのは、ジョッキーや調教師などの競馬関係者と、古いファンだけ」という感じになりましたよね。


この話をしだすと色々言いたくなって切りがないのですが(笑)、特に藤沢調教師なんかの方針がそうだったと思いますけど、「レースの格」よりも「馬の適性」を重視して、「勝てるレースを確実に勝つ」ことで賞金を稼ぐほうがよいという感じになっていった。さらに、「早めに引退して種牡馬としてさらに稼ぐ」みたいな風潮も出てきて、ファンからすると「いやもう一年走ってよ」って思う場面は多かったと思います。
そうやって、レースの格や序列を平準化し、名誉よりも利益という流れになったような感じがしたから、2000年代になって競馬を続ける気がなくなったのだと、今から振り返ると思いますね。


現在進行系の競馬ファンの人にも考えてみてほしいのですが、「凱旋門賞を勝つ」ことは今も日本競馬会の悲願という認識が共有されていると思います。要するに凱旋門賞には、まだ、圧倒的な威厳がある。これが平準化されて、「見込みのない凱旋門賞なんかに挑戦するより、香港あたりのGIで稼いどきゃいいじゃん?」ってなったら、面白くないでしょう。それに似たような変化が、平成の時代に国内であったのです。


逆に言えば、90年代というのは、古き良き「名誉」を重んじる競馬文化と、マスコミによるブーム化がうまく融合して、競馬がとりわけ面白かった時代だったのでしょう。しかしマスコミ主導のブームには、自滅的なメカニズムがあって、その面白さは続かなかったということかなと思います。
もっとも、馬券の売り上げは増え続けているし、後にディープインパクトやウォッカのようなスターも出てきたわけだから、私が言っている話は「2000年代前半にたまたまそうなっただけ」かも知れません。その後のことは、よく知らないですね。

Rで必要なオブジェクト以外をワークスペースから削除する時

単なるメモですが、

rm(list=subset(ls(), !(ls() %in% c('hoge', 'hage'))))
rm(list=ls()[-which(ls() %in% c('hoge', 'hage'))])

'hoge', 'hage'のところに、消したくないオブジェクト名を書いておく。
後者のほうが2文字短い。

Rでファイルをダウンロードするかどうか確認させる関数

ネット上にあるcsvファイルをダウンロードしてきて使う場合、read.table()にurlを与えて直接データフレームをつくってしまう場合もあれば、ファイルとしてダウンロードして置いておきたい場合もあります(実行のたびにダウンロードしたくない等の理由で)。
で、実行するときに、すでにダウンロード済みなのであれば再ダウンロード(上書き)はしたくないという場合があるので、確認しながらダウンロードできると便利です。

簡単な処理ではありますが、コードの使い回しのためにここに書いておきます。
そのファイルがすでに存在するかどうかをメッセージで表示した上で、ダウンロードするかどうか訊いてくるので、コンソールでyを入力したらダウンロード、nを入力したら中止ということにしておきます。
ファイルがすでに存在する場合、そのファイルの更新日時を確認してから(たとえば古かったら)ダウンロードしたいという場合もあるので、更新日時も取得して表示するようにしておきます。
処理確認ダイアログを出す関数askYesNo()の使い方は、askYesNoのドキュメントをみてください。

confirm_file_dl <- function(url,dst){
  # dstには保存するときのパス&ファイル名を与える
  
  if(file.exists(dst)) {
    message(paste('\n\"', dst, '\"', ' already exists!', sep=''))
    mtime <- file.info(dst)$mtime  # ファイルの更新日時の取得
    message(paste('(Last modified at ', mtime, ')\n', sep=''))
  } else {
    message(paste('\n\"', dst, '\"', ' doesn\'t exist!\n', sep=''))
  }

    dl_or_not <- askYesNo(msg='Do you want to download the file?', default = FALSE, prompts = 'y/n/na')
  if(dl_or_not==TRUE) {
    download.file(url, destfile = dst)
  }
}


たとえばGoogleのモビリティ・レポートのファイルは、国別データは1ファイルにまとまってるのですが、現時点で600MBもあって、外出中にテザリングで作業してる時なんかは、必要ないならダウンロードしたくないですね。


ファイルがすでに存在する場合。

> confirm_file_dl(url='https://www.gstatic.com/covid19/mobility/Global_Mobility_Report.csv',
+                 dst='source/Global_Mobility_Report.csv')

"source/Global_Mobility_Report.csv" already exists!
(Last modified at 2021-08-17 18:13:17)

Do you want to download the file? (y/n/na) 


ファイルが存在しない場合。(nを入力してダウンロードを実行)

> confirm_file_dl(url='https://www.gstatic.com/covid19/mobility/Global_Mobility_Report.csv',
+                 dst='source/Global_Mobility_Report.csv')

"source/Global_Mobility_Report.csv" doesn't exist!

Do you want to download the file? (y/n/na) y
trying URL 'https://www.gstatic.com/covid19/mobility/Global_Mobility_Report.csv'
Content type 'text/csv' length 637979115 bytes (608.4 MB)
==================================================
downloaded 608.4 MB

Rのsource()関数で呼び出すスクリプトに引数を渡すとき

学生に説明する必要が発生したためエントリ起こしておく。
コマンドライン引数みたいな感じで、source()で呼び出されるスクリプトに呼ぶ側のスクリプトから引数を渡すときは、以下のようにやればよい。
source1.Rからsource3.Rまでのスクリプトを準備してあり、それをreader.Rからsource()関数で呼び出す。
その時に、commandArgs()という関数を使う。

#呼ばれる側のスクリプト1(source1.R)
# 文字列が1つ渡される想定

print(commandArgs())
#呼ばれる側のスクリプト2(source2.R)
# ベクトルが一つ渡される想定

mean(commandArgs())
#呼ばれる側のスクリプト3(source3.R)
# 文字列と数値がリストで渡される想定

args <- commandArgs()
print(rep(args[[1]], args[[2]]))
# 呼ぶ側のスクリプト(reader.R)

# 渡す引数が1個なら単にそれを書けばいい
commandArgs <- function(...) {'ばか'}
source('source1.R')

# ベクトルを渡すこともできる
commandArgs <- function(...) {c(1,2,5,7,8)}
source('source2.R')

# 複数の引数をリストで渡す
commandArgs <- function(...) {list('あほ', 5)}
source('source3.R')


なおここで、function(...)の三点ドットは、定義されていない引数を表す記号。
以下、実行結果。

> # 渡す引数が1個なら単にそれを書けばいい
> commandArgs <- function(...) {'ばか'}
> source('source1.R')
[1] "ばか"
> 
> # ベクトルを渡すこともできる
> commandArgs <- function(...) {c(1,2,5,7,8)}
> source('source2.R')
[1] 4.6
> 
> # 複数の引数をリストで渡す
> commandArgs <- function(...) {list('あほ', 5)}
> source('source3.R')
[1] "あほ" "あほ" "あほ" "あほ" "あほ"

researchmapのcsv

researchmapのcsv取り込みにクセがありすぎる。
アクションがinsertなのに"invalid_delete_reason,,削除理由が無効です。"というエラーが出るのはバグ?
ちなみに削除理由にmineとか設定すると通る。意味がわからん。
そもそも、csvなのにヘッダーの上に"published papers"とか書かせるフォーマットなのが狂気。

Stanでよく忘れる、よく間違える書き方

  • 文末の;の忘れ。
  • forループの範囲の1:NのところのNを、intじゃなくてrealで宣言してしまっている。
  • transformed parametersのブロックでforループを複数かくとコンパイルエラーになる理由がわからないときある。1つのループにまとめると通る。
  • 定義の順序を間違えててまだ存在しない変数を参照してる。
  • 自作関数を作って、つかうときに、引数の指定をRみたいにmy_function(x=1)みたいに"引数名="をつけるとエラーになる。

相関係数の差の検定と、回帰係数の差の検定を、Rでやる

たまに、2つの相関係数が有意に異なるのかや、1つの重回帰モデル中の2つの回帰係数が有意に異なるかを示せると、主張が通りやすいという場面がある。
まぁ、あまり必要になることはないのだが、相関係数の差の検定や回帰係数の差の検定について、日本語でググるとRでパッと使える方法がまとまっているわけではなさそうだったので、ここに書いておこう。

相関係数の差の検定

相関係数の差の検定は、Rの{psych}パッケージのr.test()関数で簡単にできる。
下記に関数の解説があるが、
https://www.rdocumentation.org/packages/psych/versions/2.0.12/topics/r.testr.test function - RDocumentation
Test4のtable1が崩れていたり、「Case A: where Masculinity at time 1 (M1) correlates with Verbal Ability .5 (r12), femininity at time 1 (F1) correlates with Verbal ability r13 =.4」とあるところはM1とF1が逆になっちゃってたり、出力の説明が不足していたりするので要注意。そこから参照されている論文Steiger(1980)をみると、いくつかの疑問は解決する。


1) この関数は1つの相関係数が有意かどうか検定したい場合にも使え、たとえば変数1・2ともにサンプルサイズが100で相関係数が0.66だとすると、

library(psych)
r.test(n=100, r12=0.66)

というふうにする。多くの場合両側で検定すると思うが、片側で検定したい場合は
twotailed = F
という引数を付ければよい。


2) ここから2つの相関係数の差の話に移る。まずは、2つの独立した(サンプルを共有していない)相関係数の差を検定する場合。サンプルサイズは異なっても良い。
変数1と2はサンプルサイズが100で相関係数が0.3、変数3と4はサンプルサイズが120で相関係数が0.25だった場合、

r.test(n=100, r12=0.30, r34=0.25, n2=120)

サンプルサイズが等しいのであれば、n2は付けなくてもよい(デフォルトでnと同じになる)。


3) サンプルを共有している変数が3つ(1・2・3)あって、1と2、1と3というふうに1つの変数を共有する形で相関係数同士の差を検定したい場合。

r.test(n, r12, r23, r13)

というふうにする。この場合、r12とr13を比べた検定結果が返される。このとき、r23も引数として与える点に注意。(使い方の説明が不親切だが、冒頭の解説リンクのtest4のCaseAと同じことで、そちらについては論文Steiger(1980)と照合すると分かる。)


4) サンプルは共有してるけど変数は共有していないような、2組の相関係数を比べる場合。(冒頭の解説リンクでいうとtest4のCaseBに相当)

r.test(n, r12, r34, r23, r13, r14, r24)

冒頭の解説リンクは使い方の説明が不親切なのだが、論文Steiger(1980)と照合すると分かる。差を検定したい関心のある相関係数がr12とr34で、この2つの相関係数の差の検定結果を返している。


他に参考になるページ
http://www.snap-tck.com/room04/c01/stat/stat05/stat0501.html統計学入門−第5章
母相関係数の差の検定 :: 株式会社アイスタット|統計分析研究所

回帰係数の差の検定

重回帰分析の回帰係数の差の検定については、たとえば複数の回帰係数の信頼区間を出して、大きい方の下限値と小さい方の上限値が重なるかどうかという基準で検定すると、(たとえば5%水準の検定をしたいときに95%信頼区間を用いると)厳しすぎる検定になるらしい。*1


で、使えるケースが多少限られるものの、心理統計の分野で分散分析の延長で共分散分析を習うときに出てくる「平行性の検定」の考え方で、「交互作用が有意になるかどうか」という観点で検定すると簡単にできる。*2

d <- airquality  # 練習用データセットの読み込み
d <- d[complete.cases(d),]  # 欠損値削除
summary(lm(Ozone~Wind+Temp, data = d))


このようにすると以下のような結果が得られる。

Coefficients:
            Estimate Std. Error t value        Pr(>|t|)    
(Intercept) -67.3220    23.6210  -2.850         0.00524 ** 
Wind         -3.2948     0.6711  -4.909 0.0000032617283 ***
Temp          1.8276     0.2506   7.294 0.0000000000529 ***


この-3.29と1.83が有意に異なるかを知りたいという話で、まあ標準誤差とかをみても余裕で異なりそうではあるが、以下のように(従属変数であるOzoneは残して)ロング型にデータを変更し、交互作用の検定をすればいい。これは要するに、2つの変数を1つの変数にまとめて、変数名を新たな変数(ダミー変数)として設け、いわば「値」と「変数名」の交互作用をみるような感じ。

d2 <- d %>%
  select(Ozone, Wind, Temp) %>%  # 変数を限定
  pivot_longer(-Ozone, names_to = 'Variable', values_to = 'Value')   # ロング型に変換

summary(lm(Ozone~Variable*Value, data = d2))


以下のような結果が得られる。

Coefficients:
                    Estimate Std. Error t value             Pr(>|t|)    
(Intercept)        -147.6461    19.7613  -7.471      0.0000000000019 ***
VariableWind        246.6873    21.0072  11.743 < 0.0000000000000002 ***
Value                 2.4391     0.2522   9.673 < 0.0000000000000002 ***
VariableWind:Value   -8.1679     0.7210 -11.329 < 0.0000000000000002 ***


「VariableWind:Value」のところが交互作用(Tempが基準=0になって要するにWindダミーになってるという意味)で、有意になってるので、さっきの回帰係数の差は有意ってことになります。

*1:何かで読んだけど出典を忘れてしまった。

*2:回帰係数の差は交互作用で検定すればいいよっていうやり方自体は、以前、SPSSのマニュアルかなにかで読んだのだが、出典は忘れてしまった。でもまあ心理統計では共分散分析とかの解説でよく出てくる内容だと思います。