論理の流刑地

地獄の底を爆笑しながら闊歩する

自分のコンディションの観察日記として日報をつけていく

簡単な記録を続けて自分自身に関する長期観測系列をつくっていこう、という話。

Introduction

自分の心と身体をうまく扱うために

最近、たまたまほぼ日の二つの記事を読んだ。

www.1101.com
www.1101.com

ふたつの記事とも、心の病に襲われ絶望しながらも、うまく付き合う術を体得し、自分たちの持ち場でうまくやっていくことを実行している人たちについてのものだ。
田中圭一氏と小谷野選手に共通するのは、

  • こまめに記録をつけていたことが心の病による苦しみからの好転を支えていたこと
  • 心の病を「そこから完全に抜け出すべき悪弊」として捉えずに、うまく共存していく方法を模索したこと


の2点である。

自分は精神的に強いとか健康だとか思っていても、自分の予期しない・制御できない不幸や災厄に見舞われ、心理的不調の隘路に陥ってしまう可能性自体は誰にでもある*1のではないだろうか。

だから、過去に心を壊してしまったりして不安要素をかかえている人でなくても自分なりの心の調子を平常時からこまめに情報として記録としていくことは、とても価値が高い
それはなぜかというと、いざ心の調子を崩したときに健康であったときとの「偏差」を知ることができるからだ。

少し話は脱線するが、「平均人」の概念を提唱したことで有名な19世紀の統計学者ケトレ(Wiki)は、以下のような面白い指摘をしている

歴史と統計学 ――人・時代・思想

歴史と統計学 ――人・時代・思想

  • 作者:竹内 啓
  • 発売日: 2018/07/25
  • メディア: 単行本

医者は病人を診察してどこが異常であるかを見るとき、平均人を参照せざるを得ない
しかし、それはある場合には重大な過ちを起こすかもしれない。

それは多数の人に関する一般法則を個人に適用すると本質的な誤りとなることがあるからである。
医者がわれわれ個人の身体の正常な状態を知っておくように、健康な状態にあるときにも医者にかかっておくことは有用である。

―竹内啓『歴史と統計学』(pp.191-192)

自分が心や体の健康を損なったときはじめて病院に行けば、医者は「みんなの健康」と比べて診断をせざるを得ない。
しかし本来、傷の大きさや回復の度合いを測るうえで重要なのは個々が健康であったときの状態=「彼 or 彼女自身の健康」との乖離である。

身体面の特徴においてさえ個人差は大きい。その指標化・可視化が難しい心的状態はなおさらであろう。
だから、心が正常に動いているときから、その記録をこまめにとっておくのはリスク管理としても重要である、と個人的には考えている。

生産性・効率化のための日報/週報との両立性

日報や週報自体は、たとえ仕事上で義務となっていなくても*2生産性のためにつけている人は多いと思う。
例えば、以下のような記事には日報をかくうえで抑えるべき要素・形式・要件などがまとまっている。
(探せばこのような啓蒙・指南の記事は、それを読むだけで数日・数か月かかるほど出てくるだろう)

各々が日々向き合っている課題や職業上の環境に応じて、適切なフォーマットや記録すべきものの方針は変わってくるので、その部分は各々の考えでカスタマイズをすればよい。だが、仕事に関する情報と同時に身の健康に関する情報もこまめに変化を記録・追跡しておくことの重要性というのは、看過されやすいのではないだろうか。

純粋に記録のみを目的にした日報/週報というのは、おそらく存在しない。
計画(目標と、その達成を測るための指標、そして必要なタスクの構造とスケジュールへの割り振りをしめす)とその進捗、あるいは作業によって新たに得た情報などが日報/週報に記載する主たる項目となるが、なぜそれらを記録するのが常道となっているのかを考えると、それらが生産性と関連のある変数であると思われているから、であろう。

であるならば、心身の健康状態もそれらの情報と同じく、生産性や成果を大きく左右する変数であるので、日報/週報に記録されるべき対象となるのではなかろうか、というのが私の問題提起である。

一方で、うつ病や各種の精神的な病に対する療法のひとつとして、心的状態を手帳・日記等に記録することを推奨するアプローチは旧来から存在している。
他方で、作業計画やその進捗、その他の気づきやアイディアなどを日報/週報に記し、生産性向上やスケジュール予測に役立てようとする提案は上述の通り枚挙に暇がない。
しかしその交差路にあるべき発想、すなわち、生産性や成果をあげるためにこまめに心的・身体的なコンディションを記録する、ということはあまり重視されてこなかったのではないだろうか*3

もちろん難点があることもわかる。特に、業務の一環として日報/週報を書いている場合は、社内やチーム内での共有が前提となるから、そこにとてもプライベートな性質をはらむ情報である心身の健康状態をこまやかに記録するということは、少しハードルが高いし強制されるべきものでもないだろう。

しかしそのような困難があるとしてもなお、何らかの形で、作業の遂行や生産性に関する情報と心身の健康状態に関する情報を一緒くたに並記して、自分なりのデータベースとして時間経過とともに蓄積していくことは、長期的にみて価値が大きいと考える。

職業柄、具体的な仕事の情報をprivateな情報記録媒体*4に記録していくのが難しい場合は、心的状態を記録している私的な日報に、以下のような大まかなパフォーマンス指標を併記していくとよい。
長期系列として自分の長期的な調子の変化を観測可能にするためには、なるべく記録にかかるコストは低いほうがよい。
したがって、定性的な記入形式(テキスト)ではなく、(そこまで厳密でない主観的なものでよい*5ので)数値による記入が可能な指標による記録を推奨する。

◆主観的なパフォーマンス指標(例)

  • 総合評価(10段階, 主観評価)
  • 事前に何個のKPIを設定したか?
  • KPIのうち何個を達成できたか?
  • 稼働時間(率)はどれだけか?
  • 今後につながる新しいアイディアは発見できたか?
  • 新たな発見できた場合、それは何?
  • どれだけ楽しめたか?(N段階)
  • どれだけ集中できたか?(N段階)
  • 集中できなかった場合、阻害要因は何だったか?
  • 大きなミス・失敗をしたか?
  • 解決策が見つからない新たな課題にいくつ直面したか?

※主観的な評価指標項目は、自分が細かすぎるとか粗すぎるとか思わない適当なカテゴリ数に設定すればよい。

◆ネガティブなイベントの影響の深さと持続性を記録する

あまり反芻してもどうなの、それ自体がメンタルに悪影響あるんじゃないのって言われそうではあるが、上の記録項目のなかにはいくつかのネガティブなものが含まれている。
これは、ネガティブなイベント自体を少なくしよう、という意味ではなく、仕事上のネガティブな事象が起きたときに、自分のパフォーマンスがどれくらい落ち込むか(=影響の深さ)、その落ち込みをどれくらい引きずるか(=影響の持続性)という二点を可視化しておくことが、自分の生産性を管理・予測・向上させていくうえで有用だからである。

(当然のことだが)完全に社会の影響から隔絶した状態に自分を置くのは不可能であり、ゆえに(たとえ自分が細心の注意を払っていたとしても)偶発的な要因や外圧などにより負の影響を与えるようなイベントに出くわすことは避けられない。
だが、ネガティブなイベントの生起がどれだけ生産性に影響を与えるか、そして影響がどれだけ持続するか、という点を一つの観察対象として客体化・データ化してしまうことで、必要以上にネガティブなイベントを恐れることから一歩進んで、それを「管理・分析の対象」としてしまうことができる

健康状態→仕事の生産性という影響経路だけなく、仕事→健康という逆の作用も当然あり、概して仕事と健康の関係は相互規定的だ。
だから、両方の情報を同じ時間の単位のなかで記録して、各時系列間の相互の影響過程を分析可能にしておくことは大事なのである。

◆己の状態を0/1でなく段階的に把握することの重要性

上であげた項目の中には、集中力の自己評価指標が入っている。
これは、該当の時間単位について、主観的に「どれだけ集中できたかを」N段階*6で評価するものである。
何らかの計測に基づかないという意味でこれはまったく「客観的」ではないのだが、「0か1か」ではなく、よりグラデーションをともなった段階的な把握をこまめに自分の集中状態に対して行っていく、ということはそれ自体で意味があると思われる。

大昔に読んだ*7齋藤孝氏の「"できる人"はどこが違うのか」という本のなかで印象に残っている箇所として、齋藤氏が「集中力とは"意識のコマ割り"のことである」と述べる箇所がある。

氏曰く、集中力とは、(映画やアニメにおける)コマ割りに例えられるような、"一秒間あたりの思考・判断のテンポ"のことである。
氏曰く、自分の意識のコマ割りの速度を細かく把握することを習慣化すること、自分のなかに10人の小人がいるとしてそのうち何人が働いているか、ギアはどの段階に入っているか、ということを常に意識しておくこと、により集中力のチューニングの感覚を「技化」することが重要であるという。

「集中力とは何か」という定義は各人がプラクティカルな意味で有用なものを見つければよいと思うので*8、齋藤氏の定義自体の妥当性についてはここでは論じない。
(あと、個人的にはどれだけ感覚自体を鋭敏にして可視化したところで、それが集中力の水準を意識の管理下におけるようになる、という見方には懐疑的である)
しかし、

  1. 「集中できている/集中できていない」という二分法でなくグラデーションをもって自らの意識状態をとらえること
  2. それをこまめに、継続して記録していくこと

の2点は普遍的な有用性をもつのではないか、と思っている。
それはなぜかというと、離散的ではなく連続的に集中力をとらえることで、自分の心的状態の「平均」だけでなく「分散」を可視化することができるからだ。

少し話は変わるが、DAZNで元日本代表の内田篤人氏がやっている「Football Time」という番組がある。
去年の暮れ、元鹿島の岩政氏がゲストだった回で彼ら二人が「メンタルの強さ」とは何かというテーマで討論していて、二人が一致した点があった。それは、

  • メンタルの強さとは、テンションの高低に関係なく、"振れ幅"が少ないこと。感情の起伏が小さく、平坦に近いこと

という見解である。
それが苛烈な上昇志向であれ(例:本田圭佑)、あるいはナチュラルで穏やかなテンション(例:遠藤保仁)であれ、とにかくテンションがなるべく平坦に近い状態で居続けられることが、真に重要なことであるという。
だから心の強さというのは水準の問題ではなく波の大きさの問題であると、内田氏のなかではとらえられている。

こういう見方に立った時に、集中力に限らず心の色々な調子を、0 or 100ではなくグラデーションでとらえていくことは有効だと思う。
集中できた/できなかった、という二分法にもとづく記録を蓄積していったとして、後から分かるのは「どれだけの割合で集中できたか」という点だけである。

だが、"振れ幅"こそが重要だという観点からは、「なんか今日は集中できないな~」となった時に、調子・パフォーマンスの下げ幅をどれだけ極小化できているかが重要となってくる。
だから、普段集中できている状態を10としたときに、1まで落ちてしまっているのか、4までで何とか押しとどめられているのかを、把握できるような形式で記録していくことが重要である。

したがって、(多少厳密性を欠いていたとしても)自らの心的状態をグラデーションをもった尺度で記録していくことは、とても価値が高いのである。

また、副次的な効果についても話しておきたい。
うつ傾向と完璧主義的な思考は一般に相関すると言われており、そこには「0か100か」「白か黒か」という極端な二者択一式思考形式が関わっているという研究もある。
自分の心的状態やパフォーマンスを「できたorできない」「良いor悪い」の2分法で捉えるのでなく、よい・わるいの間に多くの段階的な濃淡を見出していくことを記録の習慣化によって継続することとは、それ自体が完璧主義者の陥穽の回避につながるのではないか、ともいえるだろう。

心の状態をどうやって記録していくか

以上、心的状態を日報/週報としてつけていくことについての問題意識やそのベースとなる考え方について記してきた。
続いて、実践の具体的方法について述べていきたい。

記録すべき項目について

◆心身のコンディションの何を記録するか?

心理的なコンディションにかかわる変数として何を記録してゆくべきかについては、以下のブログ記事がとても参考になる。
tokin-kame.hatenablog.com

この記事を書かれた方は、自身のうつを克服するために記録用のアプリをつくり、なんとRで時系列分析まで行っている*9

この記事において、コンディションそのものに関する変数としては、総合的な調子の良さ、憂鬱感、不安感、気の重さ、眠気、頭痛、倦怠感、活動具合などを数段階の順序尺度として設けられている。
また、調子の高低を説明する変数として、仕事の活動具合、起床/就寝時間、曜日、天気、記憶、薬の種類・量、気圧なども同時に記録している。

心身のコンディションを形成する要素のうちのどの側面をコントロールやモニタリングしたいかというのは個々人によって違うので、上記の指標群をそのまま踏襲する必要はなくて、適宜つけたしたり削除したりすればよい。
たとえば、心的コンディションの項目としてはイライラの度合いを、関係しそうな変数としては、他人とのコミュニケーションの量やそこから受けた印象(正か負か)やその日の運動の時間・量などが追加的変数の候補として考えられるだろう。

いずれにしても、調子の良いor悪いを単一指標でとらえるのではなく、いくつかの指標をつくっていくアプローチは参考になるのではないだろうか。
また、記録自体はすぐに終わるように、なるべく文章では数値で記録できるような尺度を中心とするのが良いと思う*10

◆パフォーマンス面の指標についての基本的指針

また、仕事や学業など個々人のパフォーマンスに関する項目は上に挙げたようなもので良いと思う。
少しだけ重要な点を補足・強調しておくと、長期的な観測を前提とするなら、あえて記録すべき項目をあまりタスクの内容に即した形で定義せずに、一般性のある簡素な形式での記録していくほうがよい

厳密に考えていく方向性でいくなら、たとえば「論文をN本読んだ」と「業務上の事務連絡をN本こなした」と「プレゼン用のスライドをN枚作成した」と「データ分析でN個の仮説を検証した」とでは、それぞれタスクの性質も負荷も全然違ってくるだろう。
だから、タスクの種類を超えた領域普遍的な生産性の指標を厳密に作成・測定しようとすると、違う種類の仕事の成果の間の「変換レート」を真剣に考えなければならなくなる。

しかし、そのような指標をつくりあげるのはとてもなく難しいし、その割にリターンも少ない。
よっぽど固定的な状況に置かれた職業でない限り、従事する職務の内容というのは変容していくから、その都度指標の妥当性を再検討するのは単純にとても大変である。

ゆえに、上に挙げたパフォーマンスに関する指標では、

  • 事前に何個のKPIを設定したか?
  • KPIのうち何個を達成できたか?
  • 稼働時間(率)はどれだけか?

といった、一見「ちょっと漠然すぎるんじゃないの?」と言われそうなくらい粒度の粗い項目をおいている。
タスクの個々の差異にまで立ち入らず、「どれだけたくさん目標を立ててトライしたか」「そのうちどれだけを達成できたか」ということだけを淡々と記録していくスタイルだ。

もちろん「これだけでは何も測れていないじゃん!」と違和感が生まれてしょうがない人は、より精密でタスク特化した指標を個々人で立ててカスタマイズしていけばいいんですが、(私のような)ズボラで長期継続した記録をとっていくだけでハードルが高く感じてしまうような人はこれくらいの粒度からはじめていくことが得策かと思われる。

どの単位での「変化」をみるべきか(日内変動について)

心身の状態とその変化を記録していく時間的な単位についての話。

この記事のタイトルに「日報」と入っているので、「そりゃ一日単位じゃないのかよ」とツッコまれそうである。
しかし、心身の調子に関して、日間(between)の変動だけでなく、日内(within)の変動が大きいのであれば、その日内変動における規則性の発見も、長期観測にもとづいて取り組むべき一つの重要な課題となる

いっぽうで、あまりにも記録をとる時間単位を細かく設定すると、記録することだけで疲れてしまう、という本末転倒なことになりかねない。
だから、心身のコンディションに関しては一日を4つくらいに大まかにわけるとよい。たとえば、

  • 午前中(~AM12時)
  • 午後(AM12時~PM5時)
  • 夕方(PM5~9時)
  • 深夜(PM9時以降)

といった具合である。
あくまでもこれは一例であって、たとえばAM4~5時に起きて早め早めに仕事を済ませてしまうような超朝型な人は午前中を二つに分割したほうがいいだろうし、逆に超夜型の生活をしていてAM3時過ぎまで仕事をするのが常態化しているような人は深夜帯をさらに分割すべきかもしれない。

心身のコンディションに関する記録を一日よりも細かい粒度でつけていくことで、日ごとの調子の違いだけでなく、日内変動における規則性やその背後の要因を明らかにことに役立てられる

記録用のツールとしてなにを使うか

結論からいうとこれは「長期的に継続しやすいデジタルツールならばなんでもよい」ということになる。
ExcelでもNotionでもEvernoteでもSlackでも数多ある日報用のアプリ*11でもいいし、なんならメモ帳でもよい。

別にアナログな記録媒体を目の敵にしているわけではなくて、

  1. 心身のコンディションに関する情報を(できれば生産性に関する情報・変数とともに)記録していく
  2. 記録が活用可能なデータとして蓄積したところで、①心身の健康状態の変動における規則性やその背後の要因 ②心身の健康状態⇔仕事上の生産性の相互影響、を分析する

という二つの段階のうちの後者=蓄積したデータの活用、という段においてデジタルな形式で記録をとっているほうが格段に工数が短縮される、というだけである。
(さらにいえば、なるべくテーブル形式で記録できたほうが後々分析しやすくて望ましい)

ただ、上述のように職場で用いているPCや日報ツールに自分のプライベートな情報としての心身の健康を記録することができないor抵抗がある、という状況も考えられるだろう。
このような、リアルタイムに仕事中に開くことが許されるデジタル媒体に心身の健康状態に関する情報を記録していく難しい場合は、手帳やノートなどのアナログ媒体を補完的に活用するのはありだろう*12

自分の生活誌全体を"随時更新型アウトプット"として捉える発想

心身の健康状態に関する記録を継続してつけることについて、その効用と実現の際の留意事項について記してきた。
あとは、これを続けるために基本的な考え方のスタンス的なものについて、(蛇足気味だが)述べて終わりとする。

この節で述べることを短くまとめてしまうならば、それは「長期にわたる自分の観察記を一冊の本のように考えて、それを随時加筆・更新していく」作業として日報をつけていく、ということである。

↓随時更新型アウトプット、という考え方について書いた記事↓
ronri-rukeichi.hatenablog.com

「同じことの繰り返し」ではなく、ひとつの長編の加筆・編纂と捉える

自分はマメかズボラかでいえば圧倒的に後者に属する人間であるため、正直言って毎日記録をつけることは割と大きな苦痛である*13

その理由は複合的で一つではないのだろうけど、(継続したときに蓄積される記録自体の価値は理解していたとしても)ひとつひとつの記録をつけていく行為自体が「単調で非創造的な作業の繰り返し」に思えてしまうことが大きく作用しているように思う。

「きょうの記録」をとる作業を反復・継続する、というよりは「一つの長編観察記」を完成させるような感覚でいるほうがよい、ということである。

ドラゴン桜」15巻で、スランプになった水野(東大受験生のひとり)を主人公桜木*14が立ち直らせるために、彼女に、一年間分の学習記録を手帳に書き写させるシーンがある。

ドラゴン桜(15) (モーニング KC)

ドラゴン桜(15) (モーニング KC)

  • 作者:三田 紀房
  • 発売日: 2006/09/22
  • メディア: コミック

作品内では、水野はこれまでの学習記録を書きとりながら自分の頑張りを想起・確認しながら涙を流し、「私...頑張った」「最後までやり抜いてみせる」とスランプを脱する*15

まぁ実際にそこまで「記録の確認」という行為自体に感情面を支える劇的な効果があるかは別として、その背後にある論理については、割と納得性のある説明がなされている。要点をまとめていく。

  • 受験生の不安の根源は、「勉強してきた成果を目で確認できないこと」にある。
  • 受験生はいくら勉強しても足りているか不安を感じるので闇雲に勉強するが、それは「ザルに水を溜めようとする」ようなものである
  • その日勉強した内容をその日のうちに手帳に記録していくと、それはザルではなく「桶に水を溜める」ことに相当する。
  • ザルではなく桶という比喩は、「桶ならば溜まっていく様子とその分量を目で確かめられるし、一杯にする喜びも味わえる」という理由である。自分が費やした時間とそこで得た知識量を確認することが、(不安の解消やモチベーションの継続を介して)成長に大きく貢献する。

ドラゴン桜は受験漫画だが、これは(特に労働と成果との関連性がわかりにくいホワイトカラーの)職業生活とそこでの努力の継続についても同じことが言えるのではないだろうか。
特にフルタイムで就労していると、どうしても仕事以外のインプット/アウトプットに使える時間というのは細切れになってしまうし、なかなか万全のコンディションでないときに取り組まざるを得ないことも多くなる。

だが、たとえば長期的に観測できる(粗い)達成指標やその作業量を記録しておくことを継続して、成功までのひとつの成長譚の(文字通り)1ページを書き加えているような感覚を持てるようにすることが、そのような逆境における踏ん張りの動力源となる。
また、同じ時間の粒度で、心身のコンディションの記録をつけているのであれば、「同じくらいの心身の疲労度でも以前より高い水準(時間、アウトプットの量などで測る)で努力できている」ということも分かる。

社会人を長くやっていると立場も環境も変わっていくなかで、必要な知識や努力の種類も変容していく。
しかし、その一方でそのインプットやスループットの努力が実際の仕事上のアウトプットに活きてくるまでは時間的なラグがあることも多く*16、そのような場合にモチベーションや努力水準を継続するのは、加齢による心身の衰えなども考えると正直めっちゃ大変である。

日報をひとつの自分自身の長期観測記録や成長記として考えて、そこへの加筆・修正をこまめに行っていくことは、そういった状況をうまく乗り切るような効用もあるだろう。

"ラップを刻んでいく"感覚で日々を「更新」する

前節の最後のほうの話と若干かぶるが、同一の項目を長期間にわたって記録する、ということの効用についてどう考えていくかという話である。

一定の時間間隔において自分自身を観察していると、その単位時間における「できること」の量や質の変化が可視化できる

「できないことができるようになる」「同じことをこなせる速度が速くなっていく」という成長の実感というのは、年齢を重ねても日々の活力の源となりうる根源的な喜びである。
かつて初めて自転車に乗れた時に無上の喜びを感じたように、リフティングの回数が伸びていくたびに世界が広がっていく感覚をおぼえたように、きのうの自分よりきょうの自分がより多くの新しいことをできるようになっていく過程は、それ自体が生きることの動力源や理由になりうる

かの天才小野伸二も、インタビューで以下のようなことを言っていた。

小野:誰もが難しいと思うことを簡単にできるということが、僕のなかでは楽しみというか、どんなことでも簡単にやっちゃう、簡単にやるということがボールを操る楽しさですね。

ー 簡単なことは?
小野:簡単なことは当たり前にやる。簡単なことが一番難しいですけど、それは当たり前にやらないと次に進まないんで、簡単にできることを当たり前にやれるように努力する。そこからだんだんとステップアップして、難しいことを簡単にやる

ただ厄介なのは、大人になってからの努力はその結果との対応が分かりにくいことが多い、ということである。
大人になってからの努力とアウトプットとの間の対応性は、スポーツや音楽、学校の勉強ほどわかりやすくない*17し、自分がどれだけ前に進んでいるのかを把握しにくい。

一日やその中の数区間といった決まった時間間隔での記録を長期継続することは、この問題に対する一つの解決策になりうる。
一定時間あたりの成果や努力量を刻んでいくことは、長距離走における"ラップタイム"のようなものとして捉えられるのである。

上述のような形式で、一日を4期に区分して、心身のコンディション指標とともに稼働時間やKPIの達成度合い、作業の内容などを記録しておけば、たとえば半年前と現在を区別して、同じ「心理面のコンディションが悪い日の午前中」でもどれだけのことがやれているか、といった形で過去と現在を比較することが可能になる。
過去と現在の間に良い変化が起きているのならば、その延長線上にさらなる成長を見出していけばいいし、もし悪化しているのであれば、それは何か原因について検討すべきだとうサインになる。

長期的な自分自身の観測記録をつけていくことは、こうやって自分の成長やそこに至るまでの努力をより短期スパンでの比較により可視化する、という事にもつながるのである。

日報(記録)を長期的に継続していくことは、一つの大きな物語を少しずつ形成していくようなものとして捉えられるんだけど、ひとつひとつの歩みの変化も同時に楽しんでいきましょうよ、というマインドですね。

おわりに

中高生のときよく聴いていた曲のひとつにbump of chicken*18の「カルマ」があり、そのなかでも耳に残っている印象的な部分として、

"数えた足跡など気づけば数字でしかない
知らなきゃいけないことはどうやら1と0の間"

というフレーズがある。

たぶん自分が10代のときは字面通りに受け取ってそれはそれで何かしらの刺激を受け取っていたんだろうけど、今聴くとまた別の印象を覚える。
それはひとことでいえば、「足跡を"数えられる”ようにすること自体が結構大変だし、まず1と0で表せることは何かを知らねばならない」ということである。



Karma

Enjoy!!

*1:もちろんそういうことに縁のない人はそれで幸せなのだから、縁がないまま人生を全うできるのであればそれはそれでよい

*2:たとえばフリーランスであっても

*3:身体的状態に関してはアスリートは除く。

*4:アナログ/デジタルを問わない

*5:より具体的にいえば、比例尺度や間隔尺度でなく、順序尺度でよい

*6:個人的には7~10くらいのカテゴリ数がよいかなと思っている

*7:読んだのが昔であるだけでなく、今手元にないので細かい記述についての記憶はおぼろげであるのだが...

*8:全然別の話題として、学術的な文脈(心理学の研究とか?)で集中力をどう測っているのかは気になりますが

*9:単純にすごい。この可視化への執念と努力は尊敬に値する。

*10:テキストによる定性的な記録は義務的なものではなくあくまでoptionalな位置づけとしたほうがよい、ということ。定性的な情報だと長期的変化を把握しづらい。

*11:「おすすめ 日報 アプリ」とかで検索すると無限に出てくる

*12:手帳でスケジュールを確認しているふりをして健康状態をつけるなど。項目名をいちいち書き出す時間を短縮するために、あらかじめフォーマットをコピーして手帳とかに貼っとくとよい。

*13:大学生(20歳前後)のときや、社会人になった当初など、いくつかのタイミングで日記をつけようとしたことがあるが、だいたい三日坊主で終わっている

*14:学校再建のために東大合格生を出すことをめざしている弁護士

*15:正直若干大袈裟な感じはするが、それはフィクションなので。

*16:特により高度な仕事になってくると、複数の領域の知識があってはじめて新しい価値が生まれることも珍しくない。

*17:多くの場合に努力と成果との間に、自分の意志で動かせない他人や環境の作用といった不確定要素が絡みやすいため

*18:彼らも気が付けば長期間トップランナーで居続けている。すごい

data frameをはてな記法での表組に変える関数

ただのTechnical Notes。
いちいちスクショなり表を整形するなりまでする必要ないけど、ブログに表をのせたいとき使う
データフレームを引数にとって、ただ実行するだけであとは「貼り付け」するだけではてな記法での表組に変換できる関数をつくる

◆参考URL

関数の実装

以下のようなコードをかけばよい。Mac OSだとwrite.tableの引数fileの部分が変わってくるので、注意。

library(dplyr)
#dta: データフレーム(tableでも可)
#header: ヘッダを出力するかどうか
# digits: 小数点以下何位まで出力するか
toHatenaTbl <- function(dta , header=T, digits=0){
dta <- as.data.frame( dta)
if( header){
colnames(dta )  <- paste0("*" , colnames(dta))
colnames(dta)[1] <- paste0( "|" ,colnames(dta)[1])
colnames(dta)[ncol(dta)] <- paste0( colnames(dta)[ncol(dta)], "|" )
} #if header

num_dgt <- function(x, dgt=3){
if( class(x) %in% c( "integer" , "double" , "numeric")){
x <- round( x, dgt)
} #if num_dgt
return(x)
} #func num_dgt

dta <- dplyr::mutate_all( dta , .funs=~num_dgt(. , dgt = digits))
dta[,1] <- paste0( "|" , dta[,1] )
dta[, ncol(dta)] <- paste0( dta[, ncol(dta)] , "|")
write.table( dta , file="clipboard-16333" , sep="|" , fileEncoding="CP932" , col.names = header,row.names=F  , quote=F ) 
}# func toHatenaTbl

実行

#適当なdata frame生成
df1 <- data.frame( V1 = rnorm(10), V2  = runif(10), V3= LETTERS[ceiling(round(runif(10)*24,0))])

head( df1,3)
# V1        V2 V3
# 1 -0.9469035 0.7316705  S
# 2 -1.2841297 0.8209863  T
# 3 -0.9822657 0.5953788  G

#つくった関数を実行
toHatenaTbl( df1, digits=3)

結果

上のコードでクリップボードはてな記法での表組が記憶されているはずなので、それをCtrl+Vで張り付けるだけ。

V1 V2 V3
-0.947 0.732 S
-1.284 0.821 T
-0.982 0.595 G
-0.319 0.57 L
-0.021 0.274 T
-2.653 0.905 C
-0.217 0.426 W
1.44 0.821 R
-0.233 0.663 B
-0.357 0.625 U

しっかりと整形できている。


Mr.Children 「エソラ」 MUSIC VIDEO


Enjoy!!!

一般ファン・サポーターがJリーグを統計的に楽しむうえで「できないこと」は何か

「制約はアイディアの母」*1とは言うけれど...という話

導入:Jのデータ分析の面白さと物足りなさ

昨年の9月末くらいに少し思い立って、Jリーグのデータベースを個人的に構築*2して、いくつかデータ分析をしてみたりした。

◆分析して書いた記事
grapo.net
ronri-rukeichi.hatenablog.com
ronri-rukeichi.hatenablog.com
ronri-rukeichi.hatenablog.com


データと実感の一致/乖離がもつ特有の面白さというものはやはりどの領域でもあるもので、その例に漏れずJリーグを分析することも色々な発見があった*3
特に自分は物心ついたときから応援してきたMyクラブがあるので、そのクラブに関してはある程度個々の選手の特徴や歴史的な文脈もある程度分かっている。
つまり、もともとある程度の「思い込み」や仮説生成につながる「疑問」がある状態で分析がスタートできるので、やはりその予想が当たったり覆されたりすることを楽しめる環境にはあった。

その面白さの基盤は、やはりデータ自体にある。Jリーグに関しては、

の2つのサイトが主なデータの提供元となっている。
前者は各試合×各クラブの単位の情報を、後者は各試合×各選手の単位の情報を豊富に提供しており、Jリーグのファン・サポーターが「Footballを統計的に楽しむ」うえでの素地となっている

これらのデータがあることで、一見chaoticな集団現象にみえるfootballのなかにある規則性(regularity)をある浮き彫りにすることができるのである。

しかし、結論先取的に言ってしまうと、興味を持った人がJリーグについてデータ分析しようとしても(現状利用可能なデータの質・量を所与とする限りは)面白さのあとに物足りなさがすぐに来やすい環境が現状なのではないのかな、という認識を持っている。

その一番の理由はやはり、利用可能なデータの種類・形式の制限と、そこから導かれる「分析でできること」の幅の狭さにあるのではないか、と思っている。
その点について早速次項から述べていく。

最大制約としての「データにおける時空間情報の欠如」

なんでJに関する現在利用可能なデータを分析するのでは物足りないのか?、という点について理由を述べる。

Footballの統計的分析のふたつの役割

個人的には、footballを対象とした統計的分析には以下の二つの「役割」があると思っている。

  1. 一見乱雑で無秩序に見えるピッチ上での22人の振る舞いのなかに現れる規則性を描写し、可視化すること
  2. ひとたび可視化された規則性が「なぜ」「どのようにして」生まれるのかについての、背後のメカニズムを説明すること。

そして現状、各サッカークラブや統計データ提供会社の在籍者のようなインサイダーでない者が、現状のJリーグのオープンなデータソースから果たせることできる目的は前者(=規則性の可視化)のみ*4である、というのが私個人の見解である。

因果の同定に必要な情報の不足

ではなぜJリーグについての現状利用可能なデータからではメカニズムを描き出せないのか。
それは端的に述べれば、現状のJリーグに関するデータ群は、因果的関係を同定するのに必要な時間・空間に関する情報を十分に含んでいないから、という点に尽きる。
具体的には、①Event Sequence形式のデータの欠如、②位置・空間情報の不足、の2点が最大のボトルネックとなっている、と考えている。

カニズムの説明というのは、不可避的に因果的な言明を含む*5
したがって、因果の同定に必要な情報がデータセット内に含まれていないと、見出された規則性の背後に潜むメカニズムを明らかにするのは非常に難しくなってしまう。

例えば、哲学者のヒュームは、因果性の要件として

  1. 原因と結果の間の時間・空間的な近接性
  2. 原因と結果の間の継起性
  3. 原因と結果の間の必然的結合性


を挙げている。

3つ目の必然的結合性については、「因果の同定とは何か」に関する考え方自体の多様性や発展と絡んでくるので一旦おいとくとして、これらの基本的要件*6のうちの1-2つ目の要件(時空間的近接性と順序に関する同定)というのは、ちょうど先ほど指摘した、現状のJの利用可能なデータに欠けた情報と綺麗に対応している

だから、Jリーグについての現状分析可能なデータから、変数Yについて(あるいは変数X⇔Y間)の規則性を見つけることができたとしても、

  • Yが起きたときにN秒以内にMメートル以内に何が起きているか?
  • Yが起きたときに増えているXは、時系列的にYが起きる前に起きがちなことなのか?起きた後に共起するものなのか?


ということは分からないのである

1試合単位での集計量どうしの関係を眺めるだけでは、これを明らかにすることはできない*7
結局、分析の質や幅を大きく規定するのはデータの性質なのである。

因果に関する分析の不可能性は、それすなわちメカニズムの詳解の不可能性につながる。
これが、Jリーグを統計的に楽しもうとするファン/サポーターの前に今立ちはだかるもっとも大きな壁ではなかろうか。

詳細データが公開されないのは悪か(そうではないよ)

じゃあより粒度の細かいデータをもってる会社が公開しないのが悪いのかというと、そんなに簡単にいくものでもないよ、ということについて。

データ自体はある

www.football-lab.jp

たとえば、先日Football LABに掲載されていたスローインに関する上の記事には

  • スローインが行われる瞬間や前後3秒間の全選手の位置や移動距離
  • スローイン後5秒以内のロスト率
  • さらにロスト後5秒以内の奪還率

などの情報が使われている。
だから、Data Studium社内部には時空間に関する情報が利用可能なデータとして蓄積されていることは分かる。
Optaの日本版アカウント(@OptaJiro)の呟く内容も、内部ではかなり粒度が細かいデータが蓄積されることをうかがわせる。
これらのデータがもし公開されることとなれば上述のようなメカニズムについての分析可能性はかなり拓けてくる、だろう(たぶん)。

データのアーカイブ化・公開のコスト

一度StatsBombとかを使ってみるとわかるのだけど、時空間的に近接したさまざまなイベントを網羅するデータをきっちりつくっていくと、1試合あたりの変数の数やイベントごとに定義するテーブルの数が膨大になってしまう

↓※素晴らしすぎるsaenaiさんのStatsBomb入門記事↓
note.com

それを、共通化・標準化したアクセス対象にするのは、それだけでかなりの手間がかかるだろう。

さらにいえば、SofaScoreにしろFootball LABにしろ、データの提示の仕方にこだわっていて、それ自体がコンテンツとなっている
※LABの各試合のSummaryのページ(例:名古屋vs川崎)とかかなり凝っていて、データ好きはこれを眺めているだけで酒の肴にできる。

ゆえに、彼らにとっては公開するデータの種類を増やすということは、各種指標を提示するコンテンツ自体も同時に新規作成する、ということを意味するので、そのコストは我々が思っている以上に大きい可能性がある。

データは無形資産であり企業競争力の源泉

この種のデータがオープンになっていない理由として、もっとシンプルな経済面の理由を考えることもできる。

たとえば、RIETIの鶴先生らの著書『日本経済のマクロ分析』pp.55-57では、近年の経済学においては各企業にとっての無形資産としてのデータベースの価値の指標化の試みに注力がなされていることが記されている。

当たり前のことではあるが、企業がリソースを投入してそれなりのコストをかけながら作成したデータを公開するためには、それを内部で非公開な状態で保持しておくことを上回るメリットが公開することに見出せる環境が必要となってくる
既述の通り、データ公開プロセス自体がさらにコストを上積みしてしまうことを考えると、それはなかなか容易なことではないだろう。

現状のデータでできるはずだけど十分にやられてないこと

現状のデータでは「メカニズムの分析」ができない、という話をつらつらと書いてきた。
しかし「できないことリスト」みたいなものをただ作って眺めているだけでは芸がないし、じゃあ今利用できるものを与件とした場合の「できることリスト」を我々はちゃんと遂行できているの*8?という話もある。

というわけで、できそうだけど管見の限りあんまりやられてない、ということを自分の宿題的な意味でも書いておく

Visualisation面の努力

今使える情報をどう「わかりやすく見せて伝える」かという方向性での努力は改善できるのではないか、という話。

著書も出しておられる新進気鋭の結城康平氏は以下のようなtweetを先日していた。

なるほど確かに、データ面の環境的ハンデは一朝一夕には埋まらないが、「見せる技術」を欧州の先駆者から学ぶことはできるだろう。

たとえばアーセナルに関する情報発信で有名な山中氏は、Jon Ollington氏のイケてる可視化をたびたび紹介なさっている。

もちろん細かいヒートマップとかパスネットワーク図みたいな、そもそも原情報がJだとないっていうシリーズもあるけど、
単純に「なんか見ててかっこいいな」「スタイリッシュ」だなという図を参考にすることは大事である。

また、polestar氏の以下の記事シリーズも、とても参考になるし示唆的だ。

筆者自身は美的センスはゼロ(むしろマイナス)なのだが、上記のようなものを参考にしつつ真似しつつ...って感じで牛歩でもやれることやれたらええですね。

戦術的レビュー(レビュアー)との協業

少なくとも自分にとってはJのデータを分析するのは趣味の一環なので、分析する動機は主に「このチーム/選手のこの特徴に関するデータがどうなっているか知りたい」という欲求に求められる。
そして、そのような「知りたい」の取っ掛かりとなる疑問・仮説というのは、一次情報として試合を観戦している時だけでなく、戦術面から分析したレビューを読んでいるときに生まれることも多い

たとえば自分はグランパスサポーターなので、グラぽのレビュー/プレビューやコラムなどにおける戦術的考察を読んでいて、「あーこの方の考え方ならデータはこうなってるハズだよなぁ...」みたいに仮説が浮かんでくることが多い。

定性的な観察から仮説生成⇒定量的な検証という流れは科学の王道だが、それは別に一人でやる必要はなくて、分掌してしまえばいいのだ。
だから、別に明示的に「この人と組みます」みたいなガチガチなコラボレーションでなくていい*9ので、ゆるやかな相互言及的な関係がもっとあるといいのかなと思う。

ただ難しいのは、すでに述べたように基本的に今Jで使えるデータって基本的にクラブ・選手どっちについても「試合単位の集計量」なのでメゾ・ミクロレベルでの戦術的観察から得られる知見については、直接的に検証し得ない、という点である。

だからむしろJに関しては、よりマクロな規則性をデータ分析している側がおおまかな変数間の関係に関する「解くべき謎」を提示して、それをもとに戦術的な分析の「眼」をもつような人がメカニズムの精緻な描写・分析をする、という逆の関係(定量分析→定性分析という順番)のほうがうまくいくのかもしれない

おわりに

この記事で指摘してきた分析の限界はあくまでも現時点での環境制約によるものなので、むろん将来には状況自体が変わる可能性もある。


Jリーグ 村井満チェアマン いらっしゃい!


この記事を書いているときにちょうどアップされた、「蹴球メガネーズ」の村井チェアマン*10のインタビュー動画(上の埋込動画の24分あたりから)のなかでも、ラッキングデータや加速度のデータに関しての(リアルタイムな)整備・公開への課題感が「個々のクラブがやるよりリーグがやっていくこと」という当事者意識をもって表明されている

ので、ちょっと面白い方向に現実が変わるかもしれないですね。
いまやれることを楽しみつつ、期待しましょう。



HOME MADE 家族 『JOYRIDE』

Enjoy!!

*1:制約があってこそアイディアが生まれる、という考えについては岩田聡・宮本茂・糸井重里の鼎談を参照

*2:データ取得に関する道義的・倫理的なエチケットなどに関しては"polite"で守るWebスクレイピングのエチケット - watagusa’s diaryなどを参照

*3:割と好意的な反響も多少あり、とてもありがたく思います

*4:細かいことを言うと、もちろん可視化できる規則性の幅にも欧州リーグに関する現状と比べるとだいぶ差はあって、特に位置情報にかんする規則性の発見は現状難しい

*5:と、思ってるけど因果に言及しないメカニズムの説明もあるんでしょうか。たとえば機能的説明も実は因果への言及を含むから機能概念は冗長だって佐藤俊樹も言ってた。動機による説明とか?いやでもそれデータから実証できるんでしょうか...

*6:むろん統計的因果推論にかんする近年の統計学・科学哲学・社会科学等の学術諸領域における理論的・方法的発展も承知しているが、それでもこのヒュームの図式が批判の対象としての役割も含めて、ある種の「叩き台」を提供している(たぶん)。

*7:とはいいつつも集計量どうしの関係性については分析は可能ではあるので、集計量を扱うことを専門としたマクロ経済学ミクロ経済学とは区別されるように、一種の「マクロサッカー統計学」化した分析を進めていくことはできるのだろう。その試みに実りがあるかは別として

*8:というかそもそもリストアップできているのか

*9:もちろんそういうのもあって良いと思うのですが、コミュニケーションコストが発生するので。

*10:色んな方が言っているけど2020年のJリーグの真のMVPはこの方では...

Jリーグの各試合におけるシュート位置の座標を推定・取得する

いとマニアックな備忘録というかもはや作業メモでしかない系記事

Motivation

Football LABの各試合の詳細データ(例:川崎vs浦和)だとスタッツとして記載があるのは、シュート数/枠内シュート数/シュート数のみであり、シュート数の位置まではわからない。

だから、全シュートのうち「PA内から打たれたシュート数」がどれだけか、とかを数値として取得してくるのは不可能である。
しかし、同ページには、以下のようなシュート位置のマップが示されている

f:id:ronri_rukeichi:20210105231557p:plain
グラフ出所:Football LAB

この図から座標情報を間接的にわかるんじゃね?と考えて推定していく。

座標の指定方法をしらべる

Chromeの開発ツールで、上のグラフのシュートの位置の起点となる黒丸をクリックしてみると、以下のように座標が指定されていることがわかる。
f:id:ronri_rukeichi:20210105232528p:plain

それで、このcyとかcxを動かしながら*1、ピッチの端などキーとなる座標をおぼえておく。

結果は、

という感じでした。

ピッチの広さに関する知識

上のサイトに各寸法があったので、メモしてく。
自分も学生時代はサッカーをしていたので、練習試合を自校でやるときとか石灰を出す台車*2をつかったりしてラインも引いてたはずなんだけど、意外とほとんどおぼえてないもんで。

  • ゴールの幅:7.32m
  • ゴールエリアの幅: 7.32+5.5*2=18.32m
  • ゴールエリアの高さ: 5.5m
  • PAの幅:16.5*2 +7.32= 40.32m
  • PAの高さ:16.5m


という感じ(タッチラインゴールラインの長さは幅がある)。

座標変換のロジックをつくる

とりあえず、元のcx/cyの座標を変換してくための計算式を用意しなければならない
私たちが知りたいのは、

  • PA内から打たれたシュートか?
  • GA内から打たれたシュートか?

という二点。
だから、PAの横幅/縦幅、GAの縦幅

PAの高さ16.5mがLABのサイトのcx/cy平面では80-8 = 72となっているとするなら、だいたい概算して16.5/72≒0.223mが1cxに相当する
ちなみに横幅は302-8 = 294だが、この倍率を用いて検算すると横68mのピッチが想定されていることがわかる。だから68/294が1cyに相当する。
この計算をもとに、cx/cyの座標をリアル座標に変換する関数をかく。
x座標は中心から左なら負の値、右なら正の値をとるようにしてある。

labToRealXY <- function(cx, cy , PA_height = 71.5 , pitch_width=294){
  scl_y <- 16.5/ PA_height #調整係数の計算
  scl_x <- 68 /pitch_width
  
  y_val <- (cy - 8) * scl_y
  x_val <- (cx - 155) * scl_y
  return( c( x_val , y_val))
} #LabToRealXY

リアル座標(labToRealXY()の戻り値)のほうをもとに、PA内かGA内か、それともPA外かを判定する関数も実装する

judgeArea <- function( x, y){
  if( abs(x ) > 40.32/2 || y > 16.5){
    return("Outside_PA")
  }else if(abs(x) <= 18.32/2 && y <= 5.5){
    return("Inside_GA")
  }else{
    return("Inside_PA")
  } #if
}#func

試合ページから取得できるようにする

あとは仕上げの工程。とか思いきや他の部分は静的に生成していたFootball LABはこの部分だけ動的に生成していた(なぜだ)ので、RSeleniumの出番だった。
まぁクライアント立ち上げてちょっと待ってからgetPageSource()した内容を結局rvestに渡すだけなので、あんまりやること変わらないんだけど、実行時間は結構かかった。

ronri-rukeichi.hatenablog.com

codeは省略するが、とりあえず日付とクラブさえわかれば紐づけができるため、日付/クラブ/X座標/Y座標/エリア判定結果を変数としてもったdata frameをページから取得できるようにする。

f:id:ronri_rukeichi:20210106134233p:plain
川崎vs浦和からの取得例

これをすべての試合のURLについて行って、タテにつなげて統合する。

集計結果(やっぱ川崎すげー)

2020年に放たれた全8031本のシュートをクラブ別に集計して、その内訳をみると以下になる。

◆シュート総数

f:id:ronri_rukeichi:20210106134824p:plain
2020年に打たれたシュート総数(クラブ別)

◆シュートの内訳(%)

f:id:ronri_rukeichi:20210106135004p:plain
シュートのエリア別比率:2020年J1リーグ(クラブ別)

川崎はシュート数が1位なだけじゃなくて、ゴールエリア内で打ってる率も1位というやばいことになっている。
翻って名古屋は、シュート数が少ないだけじゃなくてPA外から打ってるのが多い。

うーん...

Conclusion

原データが手元にないのでアレなんですが、わりと正確な近似はできている気がします。
あとはこれをどう分析に使っていくか。


レミオロメン - 茜空


Enjoy!!

*1:アナログな努力だ...

*2:正式名称なんだっけ...

【データ検証】川崎フロンターレは「トータルフットボール」なのか?「キングダムサッカー」なのか?:ボールタッチ数の集中度から考える

fooball計量学シリーズ。ちょっとした思いつきによる分析。

問題提起:川崎フロンターレは「トータルフットボール」か?

2020年のJリーグは、川崎フロンターレの記録的な独走で終わった。
ベストイレブンの11人中の9人が川崎の選手で占められ、あるメディアの記事が「ベストイレブンの発表は、まるで川崎の先発メンバー紹介のような光景と化した」と評した通り、川崎の主力選手は漏れなく称賛の対象となったといってもよいだろう。

川崎の記録的優勝について、それが幾人かの強烈な個に牽引された結果でなく、組織力も含めたスカッド全体の貢献により生み出されたものとして評価されたと捉えられる選出である。

急に話は変わるが、サッカー少年だったときの私がハマっていた漫画の一つに大島司の『シュート』シリーズがある。

シュート! 新たなる伝説 1巻

シュート! 新たなる伝説 1巻

第一部~第三部では主人公田仲俊彦が所属する掛川高校が「トータルフットボール」を掲げ快進撃を繰り広げる。
最終章となる第四部ではもう一人の主人公伊東宏が登場し、彼を絶対的中心とする「キングダムサッカー」を標榜する久里浜学園高校が、掛川高校と鎬を削ることとなる。

「ファントムドリブル」や「アクセルシュート」といった(キャプテン翼の伝統を継ぐ)トンデモ必殺技も魅力的であった*1が、この『トータルフットボール vs キングダムサッカー』という対照的なスタイルどうしのアツいぶつかり合いこそが、当時のサッカー少年(私)の心を鷲掴みにした。
※スポーツ少年は漏れなくこういう「真反対スタイルどうしの真っ向勝負」モノに弱いのではないかと思います.....

今シーズンの川崎フロンターレは、記録にも記憶にも歴史上稀有なほどのインパクトを残すチームであったことは間違いがないだろう。
まさに漫画に出てくるような強烈な個性と力強さをもったチームであったのだ。

それでは、「漫画かよ」と言いたくなるような川崎のサッカーは、掛川高校のような「トータルフットボール」だったのか?それとも久里浜学園のような「キングダムサッカー」だったのか?
それを「タッチ数の平等度」という観点に焦点をあてたデータ分析から明らかにしていくのが、本記事の目的である。

データ検証の指針:タッチ数の不平等度からのアプローチ

さて、大見得を切ったは良いもの「トータルフットボール度合い」を測るための具体的な実証手段はどうすればよいのだろうか。
本稿では指標化のひとつのアプローチとして、所得や財の不平等度を測るために用いられるジニ係数を、各クラブの各試合における出場選手のボールタッチ数に適用することを試みる。
ジニ係数をfootballのデータに適用する、というアイディア自体は選手の出場機会の不平等度を計算した小中先生(@konakalab)をリスペクトしたものである。

ジニ係数Wikipedia)は、社会における所得不平等を測るために主に用いられる指標である。
均等分配線(45度線)といわゆるローレンツ曲線の間の領域の面積の二倍に等しく、完全平等状態のときには0、完全不平等=独占状態のときに1をとるような指標である。

本稿では、J1リーグ18クラブ×34試合の出場選手の(1分あたり)タッチ数から計算されたジニ係数を「タッチ数の集中係数」とする

タッチ数の集中係数が低ければ、より多くの選手が平等にボールに触っているサッカー(=「トータルフットボール」)であり、タッチ数の集中係数が高ければ、より一部の選手にボールタッチの機会が偏在したサッカー(=「キングダムサッカー」)である、という実証アプローチである。

Technical Notes:タッチ数の集中係数の計算方法

※ここは細かい方法論についての説明なので、結果だけ早めに知りたい人は次の「分析結果」まで読み飛ばすこと推奨

ジニ係数の計算方法

ジニ係数の計算ロジック自体は、以前の記事で書いたとおりである。
ronri-rukeichi.hatenablog.com

この記事から計算方法だけを再掲する。

ジニ係数を計算するには、
所得シェアを昇順にならべたベクトルをI 、対応する人数シェアをPとし、G-MatrixをGとすると
Gini=P′GI という計算を行えばよい(′は転置)

(G-Matirxとは、対角成分が0, 非対角成分の右上部分が1, 非対角成分の左下部分が-1の正方行列)

上述の記事にRのコードも掲載してあるが、この処理自体はとても簡単に実装できる。

タッチ数/パス数の取り扱い方について

分析上若干取扱いが難しいのは、途中出場・途中交代する選手のタッチ数を、どうやって90分フル出場している選手のタッチ数との比較の俎上に載せるか、である。
1分あたりの平均タッチ(パス)数 = 当該試合における総タッチ(パス)数/当試合における出場分数、を計算することで、出場時間が違う選手どうしでの比較を行う

タッチ数/パス本数(成功数ではなく出した総数を用いている)に関しては、sofascore.comの各試合のスタッツを、各選手の出場時間についてはJリーグ公式サイトを根拠としている。
また、タッチ数のデータはsofascoreから取得している(取得方法に関しては過去記事を参照のこと)。

分析結果

川崎はJ1で一番タッチ数が「不平等」に分布するクラブである

さて、上期の計算手続きによって各クラブについて34試合分の「タッチ数の集中係数」を算出した。
また、(補足的な指標として)パスを出した数についても同様の計算をおこない、「パス数の集中係数」を算出した。
各クラブごとに、その2020年のシーズン平均を計算してプロットしてみたのが以下の図である。

f:id:ronri_rukeichi:20201225222224p:plain
データ出所: sofascore.com

主たる結果は以下の通り。

  • タッチ数の集中係数のTop3は川崎(0.268)、横浜FC(0.259)、鳥栖(0.258)で、川崎フロンターレは2020年のJ1で最も「ボールタッチが一部の選手に偏っている傾向の高いクラブ」である。Bottom3は柏(0.211)、札幌(0.214)、横浜FM(0.215)である。
  • 当然だがタッチ数の集中係数とパス数の集中係数は高い相関(相関係数=0.878)にある。こちらの上位は横浜FC(0.306)、名古屋(0.301)、川崎(0.299)で、パス数の集中度合いにおいても川崎はTop3入りしている。

したがって、タッチ数やパス数のチーム内分布の偏在性の評価に基づくかぎり、川崎フロンターレはJのなかでも「キングダムサッカー」の極に位置するチームであることがわかる。
タッチ数が一部の選手に偏る傾向がJ1でもっとも高いのが、2020年の圧倒的覇者たる川崎フロンターレというクラブであるのだ。

「不平等」な川崎フロンターレにおけるタッチ数の稼ぎ手は誰か

それでは、(Jクラブのなかでは相対的に)不平等なボールタッチ分布を有する川崎のなかで、具体的に高いボールタッチ率を記録しているのは誰なのだろうか。
川崎における1分あたりのボールタッチ数の上位5傑は以下の通りである。川崎同様にタッチ数/パス数の集中係数がともに高く(それぞれ4位, 2位)、リーグ戦でも上位に位置しているクラブとして名古屋の上位選手も併置している。
ここで名古屋を併置しているのは、単に筆者が名古屋サポであるという理由も少しだけある*2

f:id:ronri_rukeichi:20201230183802p:plain
データ出所:Sofascore

川崎で多くボールタッチを記録しているのは、中村憲剛、守田英正、下田北斗、田中碧というボランチタイプの選手×4と左SBである登里である。
名古屋のほうは上位5傑にCB二枚が入っているのと比べると、ボールタッチの偏在性が高いという意味では同じでも、より重心を高めに設定できていることがわかる。
※図からは省略したが、タッチ数の集中度2-3位の横浜FC/鳥栖に関しても、チーム内タッチ数top5にCBの選手が入ってきている。

Embed from Getty Images
(本筋からは外れますが、中村憲剛選手現役生活お疲れ様でした。クラブの垣根を超えて愛される最高の選手でした)

補足:「タッチ数の集中指数」と他の変数の相関の確認

この記事では、ジニ係数に着想を得た「タッチ数の集中指数」を分析の主軸においているが、そもそもこの変数はfootballのデータの全体においてどういう位置づけを持っているのだろうか?
指標の相対的布置を理解するために、やや単純な方法ではあるが他の主要変数との相関を確認しよう*3
※指標の定義については以下を参照

f:id:ronri_rukeichi:20201230113534p:plain
データ出所: Football LAB, Sofascore

主たる知見は以下の通り。

  • タッチ数/パス数の集中係数はKAGIと(やや弱いが)正の相関がある。つまりボールタッチやパスが一部選手に偏っているチームほど、守備に転じたときには即時奪回できていたり自ゴールに近づけさせないことができている
  • パスの集中係数は、パス成功率やボール支配率と正の相関があり、パスの出し手の固定化傾向は「ボールを支配すること」自体には良い寄与を与えている可能性がある*4
  • ただしパスの集中係数は、AGIと弱い負の相関があることから、パスの出し手が固定化する傾向にあることは攻撃の効率性を低めている可能性がある。

一つめの知見が割と興味深い。
いろんな交絡要因を統制した多変量解析などまでは本稿ではふみこまないが、ひとつの仮説としてありうるのは、「タッチ数が偏在しているクラブ」というのは「困ったところの預けどころ」(=やり直しポイント)が確立しているクラブであり、パスを回しているうちにカウンターに備えた陣形整備ができているのでは?という可能性である*5

これは、「15本のパスを繋ぐことによって、相手の陣形を崩しながら自分たちの陣形を整えることが出来る」というペップ・グアルディオラの"15本のパス"理論(参考記事:ポジショナルプレー総論。現代サッカーを貫くプレー原則を読み解く | VICTORY)にも通ずるものがある(、とこじつけられなくもない)。

川崎の「不平等なボールタッチ」は効果的に機能しているか?

川崎フロンターレが「トータルフットボール」ではなく、「キングダムサッカー」寄りであると判明した(あくまでもタッチ数の平等配分度を基準とすれば、だが)。次に検証すべき課題は、川崎のボールタッチやパスの出し手の偏在性は彼らにとって効率性をあるものなのか、それでもボールの循環経路を限定された結果の不本意な帰結でしかないのか、をデータから明らかにすることにある。

そこで、各クラブごとの全34試合のデータを使って

  • ボールタッチ数の集中係数 × ゴール期待値
  • ボールタッチ数の集中係数 × ボール支配率

相関係数を計算し、プロットしてみよう。
この作業によって、「ボールタッチが一部の選手に集中するほどチャンスが増える(減る)クラブはどこか?」および「ボールタッチが一部の選手に集中するほどボールを持てる(持てなくなる)クラブはどこか?」ということが可視化される。

この二つの相関係数を描画したのが以下の図だ。

f:id:ronri_rukeichi:20201230220731p:plain
データ出所: Football LAB, Sofascore

上図から分かる通り、タッチ数の集中係数とゴール期待値の相関係数が最も高い(0.333)のは川崎である。いっぽうで、川崎にとってタッチ数の集中係数とボール支配率とはほぼ相関はない(-0.032)。
すなわち、川崎フロンターレは、2020年のJ1リーグの18クラブで「ボールを持つ選手が一部に偏るほどゴールの可能性が増える」傾向のもっとも高いクラブであったのだ
つまり、彼ら川崎フロンターレにとって「不平等なボールタッチ」戦略は効率的である、といえる。

※ちなみに我らが名古屋グランパスはJ1でもっとも「ボールタッチが一部の選手に偏るほどゴール期待値が減る」クラブであった。
同時に名古屋はJ1で四番目にボールタッチの集中係数が高い(0.249)クラブでもあるので、不本意な展開を強いられているといえる。

まとめと課題

川崎フロンターレは、2020年のJ1でもっともボールタッチが一部の選手に偏るサッカーを展開したチームであり、その意味で彼らは「トータルフットボール」というよりは「キングダムサッカー」寄りのチームであった。
そしてその戦略は彼らにとっての内部合理性も伴っており、彼らは「一部の選手がボールを持つ」展開であればあるほどチャンスが増えるチームでもあった。

無論、何度も文中で断っている通り、この分析は指標化の方法に大きく依存している。
「トータルフットボール」度や「キングダムサッカー」度を測るための指標は他にも色々考えることはできるだろう。パっと思いつくだけでも、

  • 決定機に絡んだ選手(シュートを打った選手、シュートにつながるパスを出した選手)の分散度合い
  • PAに侵入した選手の多様さ
  • 一回の攻撃に関わる選手の多さの平均値

など、色んな指標が考えられる。
それは各々の分析者がもつ、理論・概念の操作的指標化に関するアイディアや価値観に委ねられているのである*6

なので本論の分析はあくまでも一つの試論的なアプローチでしかないが、それでも「統計的にfootballを楽しむ」営みの一助となればと思う

Enjoy!!

*1:平松のダブルヒールリフトをめっちゃ練習して試合でも試して怒られてた

*2:嘘です。95%くらいはこれです

*3:単純な変数なので、第三の変数との交絡関係は統制できていない。一応ご留意を。

*4:当然だが単なる相関か因果かは識別不可

*5:「陣形整備」とひとことで言っても、川崎はどちらかといえば即時奪回=カウンタープレスへの備えであり、名古屋だと4-4ブロックの形成を意味する、という違いはあるだろうけど

*6:お互いに意見を出しながら、色んな指標や分析がどんどん蓄積されていけば良い

インプットを続けていくためにこその"随時更新型アウトプット"

自分の考えを整理するための駄文ログ的なアレ。

Introduction: 継続できない私たち

齢を重ねると増える諸々の制約

哀しいことに人は齢を重ねる。
そして加齢に付随して、インプットに集中するような機会や時間の確保を阻害する以下のような要因が増えてくる。

  • 責任の増大に伴う様々な雑務の増加
  • 近しい人に関する予期しないtroubleへの対応
  • シンプルな体力・集中力の低下
  • 健康上の不安
  • 関わる人が増大することによる時間確保の困難化

などなど....

そして(これは一般的には当てはまらなくて怠惰な私だけなのかもしれないのだけど)さらに哀しいことに、加齢が必ずしも意志の強靭さをもたらしてくれるわけではない。
阻害要因が現れたときにそれを跳ね退けてでも「やると決めたもんはやる!」と当初の意志を貫くような力というのは、案外トシを食っても身についていないものである*1

だからといって、成長しないことが許される世間様でもないので(つらい.....)、
できうるならば、軟弱者フレンドリーなインプットの継続の仕組みをつくりたい、というのが課題意識としてあった。
我々に必要なのはハードルが低い凡人用のシステムなのだ。

haruki氏による「随時更新型アウトプット」の提案

先日スマブラAdventCalendar2020において、harukiさんが以下のような記事をかいていた。

scrapbox.io

彼の主張の要点は冒頭の以下の文に集約する

ブログやYoutubeの動画だと「完成系」「結論が出たもの」しかアウトプットしずらくて、なおかつ後から訂正が難しいからアウトプットのハードルが高い
かと言って、Twitterのアウトプットは手軽にできる反面、すぐに流れてしまうので後から参照しずらい → ストックにならない
その間を取って、既存のアウトプット方法に加えて、「完成してないもの」「結論が出てないもの」も気軽にアウトプットしよう

彼は、ハードルが高い「新しいトピックが出るたびに新しいページをつくる」ような類のアウトプットだけでなく、
1トピックに対してページを作ってそれを随時更新するというアウトプットスタイル(=随時更新型アウトプット)を推奨している。

多キャラ使いかつ関西の強豪プレイヤーであるりぜあす氏のブログは、通称「りぜあすメモ」と呼ばれ多くのスマブラプレイヤーにキャラ対策の教典として参照されているが、このブログのクオリティも”随時更新型アウトプット”であることにより実現されていると、haruki氏は分析している

これだけの量の攻略情報を1人で書いているのはおそらく、りぜあすさんくらい。
彼がこれだけアウトプットできたのも、「完成形をアウトプットする」スタイルではなく、「思いついたことを随時更新する」というスタイルにしているからだと思う。

りぜあす氏のキャラ対記事は現在10万字を超えているが、確かにこれだけのボリュームや質を最初から世に出す基準として彼が設定していたら、
この記事がこれだけの質・量を実現し、界隈からの不動の評価を得ることもなかったであろう。

以上のharuki氏の記事における指摘は、「アウトプットのハードルを下げる」重要性に焦点化したものである。
だが個人的にこの記事を読んでいて感じたのは、前項で触れたようなインプットの継続ができない軟弱者にこそ、この”随時更新型アウトプット"の発想はひとつの助けになりうる、ということであった。

どのような知識獲得の行為においても、恐らくインプットそのものが最終目的になることは殆どない*2
(たとえインプットそのものが最終目的である極端なケースにおいても、知識の内容を定着しやすくするため一旦外部記録媒体を使った編集工程の活用は効果的だろう)
したがって、結局はインプットにアウトプットが付随してくることになるので、インプットにおける軟弱者フレンドリーな継続可能性を担保するうえでも、こういった「アウトプットのハードルを下げる」工夫を活かすことができる

インプットを続けるためにはインプットのハードルを下げる必要がある
⇒ インプットのハードルを下げるための打ち手として、インプットしながら作り上げるアウトプットのハードルを下げていく、という論理ですね。

随時更新型アウトプットを活かしたインプットのシステムの作り方

ということで、(構成としてスマートではないけれども)順不同で、随時更新型アウトプットを継続的なインプットの仕組みに生かしていくためのポイントを記していく。

トピックごとの理解のログとメタな理解の記述ログを柔軟に往還する

あたりまえのことなんだけど、「今は知らないこと」だからこそインプットの対象になるわけである。
新しい領域の知識を獲得するということは、全く土地勘のない場所をあちこち往来しながら、手探りでその一帯の地図を描いていくようなものだ。

基本的に知識というのは、ひとつひとつの情報単体ではあまり意味を為すことはない。
知識構造の布置連関=ひとつひとつの情報単位の関係性のネットワークを為す体系、を理解していくことが「この分野のことがわかる」「この知識はもう自信をもって使える」という感覚につながる。

だから新しいことを学ぶときというのは、
① その分野・領域のひとつひとつの知識の正しい理解を獲得する
② 断片的知識の間の関連や配置構造を理解し、自分なりの知識のネットワーク図を作成していく

という二つのの工程が必要となってくる。

ここで我々(特に完璧主義者)がしばしば陥りがちな罠は、①→②という順番を固定的に考えてしまうことにある。
各トピックについての間違いのない「正しい理解」に達してから、その「正しい理解」を順番に配置していくことで全体構造を描こうとしてしまうのである。

しかし、このやりかたには二つの問題がある。
ひとつめの問題は、学習ログをつけるハードルの高さに関してだ。
つまり、ひとつひとつの知識単位に対する理解の「正しさ」を重要視しすぎてしまうと、その「正しさ」にある程度の確証の手応えを持たない限り、いつまでも次の知識単位のインプットや知識間のネットワークの記述にすすめないような陥穽にハマるのである。
だから、各トピック単位の知識に関しての理解ログ⇔よりメタな知識間の関係性の理解についてのログ、はある程度ゆるく往還可能な形に考えておくのが望ましい。
※「ネットワーク図の作成」とか「メタな知識間の関係性の理解」とかいう言葉をつかうと高尚に聞こえてしまうかもしれないが、知識間の関連や相対的布置を記述するあらゆる作業がそれにあたる。たとえば、(wikiやNotionで学習記録をつくっているとすれば)各トピックのログへのリンクを貼っている目次のページをつくることだって、「タグ付け」だって関係性の記述である。

ふたつめの問題は、知識自体がもつ内在的性質にかかわるものだ。
前述のように、各領域におけるひとつひとつの断片的知識は、それ単体ではなく他の知識との相対的布置や関係性の理解がなってはじめてその役割・意義が理解できるようになっていることも多い
知識A, 知識B, 知識C,,,,という区分けが一応あるとして、Aの単体のトピックの解説だけ読んでいてもピンとこなかったが、他のトピックの勉強をしているときにC⇔A⇔Bといった他の知識との関連性がわかり、知識Aが何たるかが急に理解できるようになる....という経験はだれしもがしたことがあるのではなかろうか。
なので(必ずしも間違っているというわけではないが)、知悉しているわけではない領域における知識のインプットにおいて厳格な(部分→全体の)ボトムアップ方式をとることは、妥当な理解に辿り着くまでの効率性や速度を低下させてしまうおそれがある。

じゃあどないすんねん!!ってことになるけど、そこで"随時更新型アウトプット"のアイディアが重要になってくる。
つまり、理解の最終形の出力をハナから目指すのではなく、「このトピックに関しての理解はここに置いておく」という場所だけは確保しておいて、暫定的な理解をひとまず残したまま、次のトピックのインプットや各トピック間の関係性の整理に移れるような柔軟性をもたせるうえで、”随時更新型”という考え方が効力を発揮するのである。

各トピックの「箱」だけ用意しておいて、暫定的・断片的な理解をひとまずそこに突っ込んでおきつつ、その「箱」の中身をfixせずとも各トピックが全体のなかでもつ役割や意義についてのメタな理解を構築する作業も同時並行で行う、というインプットスタイルである。

疑問だけでなく、あやふやで裏付けのない「暫定的理解」もリスト化して更新していく

(若干前項の内容とも重複するが)インプットの継続性・生産性を高めるうえでの随時更新型のログの活用で大事なのは、はじめから「正しい理解」だけを書こうとしすぎないこと、なのではないかと思う。


最近たまたま目にした以下のtweetでは、そういったスループットを軽視しないことの重要性にフォーカスがあてられている。

重要だと思った部分を抜粋する。

このように難解な数学ですが、ノートにテキストを書き写すだけでも、「わかったような気になる」ことはできます。
これは、「わからない」と「わかった」の間に位置するきわめて重要な状態で、「わかったような気になる」という状態を経なければ「わかった状態」に推移することはありません

要は、「わからない」と「わかる」を0→1の変化として捉えず、その狭間に一種の中間状態みたいなものを想定しておくことが、とても重要なのである。

「新しい領域の知識を獲得するということは、全く土地勘のない場所をあちこち往来しながら、手探りでその一帯の地図を描いていくようなもの」と先ほど述べたばかりだが、いきなり正しい事項や知識の布置連関を把捉した「正しい」地図を書いていくのは、はっきり言って不可能である。
だから重要なのは、あとから消して書き直したり、破って別の場所に貼りなおしたりすることをあらかじめ織り込み済みで、とりあえず(最終的には間違っていたとしても)「いま自分に見えているかたち」で、暫定的な(仮説的)理解にもとづく、ひとつひとつの要素の性質や他の要素との関連性を気軽に描きこんでいく姿勢である、と考えている。
間違っていたら消して描きなおせばいいし、全体が見えなければとりあえず一部だけを描けばよいのだ。

具体的なhow toの実践についていえば、いろんなメモやログをとりながらインプットを進めていくとき、

  1. 「わからないこと」(=疑問や課題)のリスト
  2. 現段階ではこういう風に考えている"という暫定的理解のリスト
  3. "もうこれで間違いないだろうという"最終的理解のリスト

という三つのリストを用意して、1→2→3という流れで順番にリストからリストへ「移し替え」の作業を行っていくとよい。
この移し替えの作業が、「わからない」⇒「いまはさしあたってこういう風に理解している」⇒「最終的にこの理解でよさそう」という理解度の変容過程と対応する。
暫定的理解の内容を明示的に言語化して吐き出しておくことで、「わからない」から「わかる」に至る過程を0/1でなく漸次的なものにできるし、そういう工夫があることによって一回インプットの継続が途切れたときの再開のハードルも下げることができる

いずれにせよ、「アウトプットを最終系ではなく後から編集可能なものとして捉える」という発想がキモとなってくるのである。

記録形式自体はテンプレート化し、どんどん再利用していく

叩き台だけは早めに用意し、暫定的理解を放り込みながら自分の知識世界を拡張しつつ、より正しいor高次の理解を獲得できたら随時更新していくという(本記事推奨の)スタイルでインプットのログをとっていくうえでは、①叩き台の用意、②理解の深化にともなう更新、の双方の作業段階のハードルを下げることが望ましい。
なので、できれば中長期的に使いまわせるテンプレート的なものをつくっておくとよい*3

むろん、対象や分野の違いによってよく使う思考の筋肉の種類は異なってくる(法律を学ぶ場合、統計学を学ぶ場合、語学を学ぶ場合では全然違う頭の使い方してますよね)ので、最適なテンプレートのありかたは各々が模索する必要があるが、だいたい大まかな分野・領域ごとに以下のようなものを作っておくとよいだろう。

◆工程表(簡易WBS

領域ごとに、新トピックを学ぶたびに必要となる工程をある程度テンプレートしておく。
たとえば、統計手法を学ぶ場合であったら、「数式による理解」や「その統計手法を適用するためのコードの作成」などは手法を問わない必須工程となるだろう。

作業の洗い出し自体が分からない時は、P-WBS×F-WBSによるWBSの作成のしかた(参考URL1, 参考URL2、また下の過去記事でも触れている)を参考して、必要作業のリスト作成を行うと思考負荷が少なくて済むのでよい。
ronri-rukeichi.hatenablog.com


(ここからはめちゃめちゃ個人的な意見ですが)だいたいのアウトプットを構築する工程というのは、以下のような流れで進む。

  • 列挙:あるテーマを語るうえで必要な要素をリストアップする
  • 分類:リストアップした要素をグルーピングする
  • 関連付け:分類内/分類間の関連性を整理する
  • 構想:分類や関連の描写・説明していくことで、最終的なアウトプットを組み立てる中程度の大きさの「部品」をつくる
  • 構成:構想段階で作りあげた部品を、どういう順番・組み合わせで配置していくかのプロットをつくる
  • 最終化:完了要件を満たすように適切な形式への変形・必要な情報の追加などをして最終アウトプットにする


"随時更新型”のアウトプットをログとしてつけながらインプットをする場合は、上のおおまかな工程に該当する「お決まりの作業」を書き出していくと、割とテンプレート化しやすいのではないだろうか。

◆自問自答テンプレート

古典的な「なぜなぜ分析」*4に代表されるように、輪郭が不明瞭でふわっとした疑問や懸念点をシャープにしていく過程では、自問自答が有効である。
毎回0→1で考えるよりも、お決まりのチェックポイントを抑えて「わからないこと」「わかっていること」の輪郭を浮き彫りにするようなツールとして、いくつか思考のデバッグツールとしての自問自答用質問リストを作っておくとよい。

以下の記事では、そういったお決まりの質問がいくつか掲載されているので、そういうのを参考にするのがよいだろう。

随時更新型アウトプットを支える思想

"Done is a stepwise approach for perfect"という考え方

かのマーク・ザッカーバーグが残した(らしい)あまりにも有名な言葉のひとつに "Done is better than perfect. "がある。
しかし、この言葉は本来の文脈でもっていた意味が変質してしまった形で人口に膾炙してしまったとの指摘が以下のnote記事でなされている。

note.com

こういう話*5をするとすぐにザッカーバーグの「Done is better than perfect. (完璧を目指すよりまず終わらせろ)」を引用する輩が湧いてくるのだけど、この言葉はあまりちゃんと本来の意味が理解されていない気がする。

(中略)

世の中には完璧なものなどない。だから完璧でなくてもまずリリースすることが重要だし、リリース後に改善を繰り返して完璧に近づいていく継続的な努力こそが必要なんだ。って言ってる。
これはむしろ中途半端なやりっぱなしを否定する言葉だ
(上記noteより引用)

我々は現代の偉人の言葉の解釈学を行っているわけではなく、肝要なのはそこから自分に有用な示唆を引き出せるかどうかである。
まぁだからザッカーバーグさんの本意がどちらであるかは重要でないんだけど、"Done"さえ担保すれば"Perfect"は目指さなくていい、というわけではなく"Perfect"をめざすためにこそ漸進的に"Done"を積み重ねていく、という考え方として捉えているほうが、生産的というか健康的な感じがする。

知識の追加的インプットを続けていくためには、「更新の対象となる叩き台をまずつくる」ということがまず一歩目として重要で、叩き台をこしらえることだけは早めに"Done"しつつ、その後の更新はハードル低めに随時おこなってゆく、というstepwiseなアプローチが有効という考え方をもっておくのがよいのではなかろうか。

続けられない自分を許せる/再開のハードルを下げることの重要性

継続できる集中力や忍耐力というのはとても尊いものであるし、それ自体美徳であろう。
心身両面で無理が効いたり野望に燃えていたりする(そんでもって割と可処分時間が多かったりする)若き日というのは、より賢い自分や強い自分になるために長時間苦痛に耐え続ける意志を強い水準で保持し続けることが自然とできる*6

まぁでも冒頭に書いていたように、歳をとってくると単に体力・集中力が低下してくるだけでなく、色々な外部からの避けようがないinterruptionも増えてくる。
ゆえに、自分への苛烈な厳しさを若いときと同じ水準でもって持ち続けてしまうと、なかなかに不健康な帰結を生むこととなる。

自分が集中・継続しきれないときに自責の念が強くなりすぎると「続けられなかったこと」自体が一種のトラウマとなってしまうので、そのトピックにまた向き合うまでの再開のハードルが上がってしまう
だから現実問題としてかつて耐えられていた荷重に耐えられなくなってきているのであれば、ハードルを下げたインプットの仕方に変える必要がある。

したがって、あやふやな「暫定的理解」の保管場所をトピックごとにつくり、それを更新していくことをはじめから基本方針とするような「随時更新型アウトプット」形式でインプットの記録をとっていくことは、そういう意味でも合理的である。

箇条書き/リスト形式は本当によくないか(そんなことない)

自分がかつて学生時代にお世話になった恩師は、研究会・読書会などのレジュメが箇条書き形式で書かれていると不機嫌になる人だった。
その理由は、「ひとつひとつの情報単位の間の接続の仕方を考えながら、ひとつの文章にまとめる過程で負荷を経験することが力につながる」という信念を師がもっていたことに拠る。
それから幾年が経った今でも、師が仰っていたことは間違っていないと思っているし、論理の鎖を紡いでひとつのまとまった文章をつくることは、それによってしか涵養されない思考の筋力の基になるだろう。

いっぽう思うのは、いちいちそうやって、論理的な依存関係や接続関係を考えながら大きな「言葉の塊」をつくっていくことは単純に疲れる。ゆえに、インプットのログをとることのハードルを上げてしまうのでは、ということである。
また、ある程度のひとまとまりの文章を読むのは結構頭が疲れるし、それは自分自身が過去に書いた文章を読むときも変わらない。
視認性の面からも箇条書き/リスト形式のほうが読み返すのに頭を使わない気がする*7

だから、箇条書きやリスト多用しながらメモを作っていっても、まぁ全然いいんじゃないですかね...っていうのが現在の私の考えである。

また、かのポアンカレ先生は「科学と方法」のなかで「発見するということは、まさに、無用な組み合わせをつくらないで、有用な組合せをつくることに存する」という名言をお残しになってられるが、そもそも新しい分野を勉強しているときというのは、どのパーツとどの別のパーツの組み合わせが「有用」であるのかを判断するための勘所を掴めているときのほうが稀である。
だから、まだその分野にそこまで明るくない時点で事項どうしの関係性や接続性を固定的に考えたひとかたまりのドキュメントを作ってしまう、ということはわりとリスクも伴っているのではないかともいえる。

まぁ屁理屈かもですが。

随時更新型のインプット記録をつけるのに向いてそうなツール/サービス

別に自分はとりわけデジタル信者なわけではないし、特に色んなアイディアを出すクリエイティブな段階では手書きのメモや図などはとても有効だとも思っているが、

  • 気軽に情報単位間の視覚的配置を入れ替えられるのが望ましい
  • 消して付け足してを何度も繰り返すことになる
  • 雑然と書き散らかした情報に、(タグやカテゴリを付与することで)事後的に部品間の「つながり」に関する情報を付け加えていく

といった随時更新型アウトプットの性質を考えると、やはりデジタルな記録媒体が向いていると思っている*8

ということで、いくつか「随時更新型」のインプット記録に向いてそうなツール/サービスをリストアップしていく。

Notion

わたしの暫定的な最適解である*9

強みとしては、

  • 編集画面/閲覧用画面がシームレスで、移行の手間やストレスがない
  • データベース関連の機能やビューの種類が充実しており(参考URL)、色んな情報を整理・管理するときに「まず表にまとめる」から入る人*10にとってはとっつきやすい
  • レイアウトの自由度が非常に高く、ドラッグ&ドロップで簡単に配置を変えられる
  • Web clipperがめっちゃ優秀(勝手にデータベース化されてく)
  • UIが直感的で触ってるのが楽しい(個人差はあると思うけど)

などがある。あと、これはどちらかとというとインプットの継続という目的にとっては付随的な利点ではある*11のだけど、公開設定を変えるだけでブログやHPの代替物として使えるのもなかなか便利。

※良さについてはNPediaを運営しているノースサンド社のnote記事などを参照したほうがたぶんよい。

弱みとしては

  • 検索機能がイマイチなので、「書き散らして突っ込んどく」という用途にはたぶん向かない(後述のScrapboxのほうがそういう使い方だと強い)
  • フリープランだと、1ファイルあたり5MBまでしか添付ファイルはあげられない。
  • 最近追加されたガントチャート機能(正式にはタイムライン・ビューとよぶらしい)は、WBS的なタスクの入れ子構造に対応していない(ちなみにフリープランだと3つまでしか作れない)
  • 多機能すぎてなれるまで時間がかかる

らへんがあげられるだろうか。

Scrapbox

自分はNotionを知った後にこっちを知ったので、あまり惹かれなかった(多分単に相性の問題もあると思う)けれど、わりと有力な候補。
自動相互リンクや、検索機能の強力さから、社内・グループ内の情報共有ツールとして結構使われているらしい。
上述のharuki氏もscrapbox推しである。

個人的には(ウリであるはずの)階層性のなさが逆に自由すぎてムズムズしてしまう感じがあるのと、単にデザインがNotionのほうが好きなので、いまのところ使用予定はない。

ブログ

冒頭でふれたharuki氏の記事においては、ブログは「完成したもの」「結論が出たもの」をアウトプットするような、"ハードルの高い"ツールと代表例として挙げられている。
しかし、りぜあす氏のブログがいっぽうで成功例として挙げられているように、ひとつのトピックに対してひとつの記事を用意して「思いついたことを随時更新する」ような(一種のwiki的な)使い方をするのであれば、"随時更新型アウトプット”用のツール/サービスとして使うことも可能である。

ウィークポイントとしては、

  • Notion/Scrapboxあたりと違い、編集画面と閲覧用画面の移行が若干手間で見た目もかなり隔たりがある
  • 記事の間に階層的な関係性をもたせにくい(目次用のページをつくって、そこからリンクを飛ばすというやり方はあるっちゃあるけど面倒くさい)
  • 情報単位が細切れになったときのハコとして、ブログの1記事という単位は大きすぎる

あたりが挙げられるだろうか。

Wiki

「トピックごとにページをつくり、それを随時更新する」という点に関しては、いにしえのツールである。
haruki氏も上述の記事において、「同じトピックを随時更新する」というアウトプットの方法について「そもそも大昔のインターネットは大体こうでしたね」と言っている通り、原始的かつシンプルだが強力なツールではなかろうか。

....でも個人的にはNotionでWiki作れるからNotionでよくないですか?っていう立場です。

Conclusion

まぁ何というか、書きあがったら割とそれなりな文字数になっていまして、「偉そうに能弁垂れてんじゃなくて、このブログを書いてる時間を使って、お前インプット&アウトプットしろよ」と言われたら返す言葉もないんだけども、たまにはこういうすぐには役に立たないような考えの交通整理も大事かなと....(いう自分への言い訳)。


Base Ball Bear - The Cut -feat. RHYMESTER-


Enjoy!!

*1:というか、色々なことを知った分、目移りしてしまうのが人のサガだと思う

*2:少なくとも社会に出てからの知識獲得はそうだろう

*3:もちろん、こういうのは準備→計画→実行みたいな手続きを厳密に踏もうとするとフットワークが重くなってしまうため、必要と思った時に「そういえばこの作業毎回やってるな」「こういうことをメモしておかないと困るな」ということを書き出して”随時更新”して作っていけばよい

*4:ちなみに「なぜなぜ分析」自体はやり方を間違うと危険ですよ、という指摘もある

*5:引用注:リリースを最優先し品質改善をおそろかにする風潮への批判

*6:過去の自分を過剰に美化している可能性は存分にある

*7:好き嫌いや個人差はあるのだろうけど

*8:むろん、KJ法のようにアナログな記録媒体を用いてもカードをつかったりすることで、柔軟な編集可能性や可塑性を担保するやり方もあるが、正直そういう記録用ツールをアナログで用意すること自体「ハードルを高く」してしまっている感がある

*9:実は自分がNotionを知ったのもスマブラ64の世界最強サムス使いである丈助氏の放送だった。どうでもいいけど

*10:自分も何をはじめるにも、まず表つくるとこから始めるタイプなのでNotionとはとても相性が良い

*11:アウトプット自体が目的の場合とは違って、必ずしもその成果を公開する必要はないと考えているため

StatsBombをRで遊ぶ

個人的な備忘録でしかないメモ。

Introduction

先日saenai氏が、noteで以下のような記事を公開していた。
note.com

この記事内の最後にも少し触れられている通り、Jにおいてはこの種の詳細なイベントデータはオープンな形で公開されていないのが現状である。
したがって、主たる関心がJリーグにある自分にとっては、すぐに役立つ種の情報ではないのだが、いっぽうでJリーグに関して現在利用可能な情報からは考えられないような幅広い変数を含むデータというのはそれ自体魅力的だ。

あまりStatsbombをRで使うことに関しての日本語記事は多くは見当たらないため、
saenaiさんの記事内容を理解しつつ、同じような処理をRで行うにはどうすればいいのか?ということ*1を書き記しておくのが本記事である。

<参考URL>

Rでも実現したい処理工程の把握・書き出し

まず、saenai氏の記事(およびハンズオン用code)において行われていることを一覧化する。
※URLはsaenai氏作成のハンズオンへのリンク。

  1. データの取得・整形(URL)
    1. 大会情報(JSONデータ)の取得・整形
    2. 試合情報の取得
    3. ラインナップの取得
    4. イベントデータの取得
  2. イベントデータの基礎集計と可視化(URL)
    1. チーム別集計(例:パス本数)
    2. 個人別集計(例:タッチ位置平均)
    3. 時間帯ごとにパス本数を集計 ※本記事では割愛
  3. イベントデータのピッチ上への可視化(URL)
    1. 選手ごとのヒートマップの可視化 ※本記事では割愛
    2. プレッシャーをかけた位置のヒートマップを可視化 
    3. パスの流れの可視化 ※本記事では割愛
    4. パスマップ図の可視化
    5. ショットマップの可視化

だいたい、こんなところだろうか。なかなか夢膨らむ内容である。
以下、saenai様のハンズオン内のコードや図表等を参照しつつ、同一の処理をRで実装していく。

データの取得・整形

JSON形式でデータは公開されており(URL)、大まかに分けて
①大会情報データ②試合情報データ③イベントデータ④ラインナップデータ、の4つの種類がある。
JSONデータをラクに扱うために、jsonliteパッケージをインストールしときましょう。

大会情報の取得・整形

以下のような形で、まずは大会情報データを取得したい。

f:id:ronri_rukeichi:20201214182948p:plain
画像出所:saenai氏のハンズオン資料

以下のようにjsonlite::read_json()をつかってからJSON形式でまずデータを取得する

compe_url <- "https://raw.githubusercontent.com/statsbomb/open-data/master/data/competitions.json"
compe_json <- read_json( compe_url)

read_json()はJSONの階層構造をリストとして取得してくる。
これをデータフレームに変換する。

compe_row_l <- lapply( compe_json , function(rw){
  return(as.data.frame( rw  , stringsAsFactors =F))
})
compe_df <- do.call( rbind , compe_row_l)
head( compe_df,2)
# competition_id season_id country_name competition_name
# 1             16         4       Europe Champions League
# 2             16         1       Europe Champions League
# competition_gender season_name              match_updated
# 1               male   2018/2019 2020-10-25T12:33:27.855343
# 2               male   2017/2018           2020-07-29T05:00
# match_available
# 1 2020-10-25T12:33:27.855343
# 2           2020-07-29T05:00

簡単ですね。

試合情報の取得・整形

つぎにcompetitionやseasonのIDをkeyとして、試合情報を取得してくる処理を実装する。
具体的に「試合情報」とは何かというと、大会/シーズン/ホームチーム名/アウェイチーム名/開催地/大会ステージ、などの情報をさしている。
(元データには実は両チームの監督名や、レフェリーの名前などの情報もあるのだが、saenai氏同様に上の項目だけをデータフレーム化する)

saenai神のコードをみると、competitionは必須で、seasonに関しては指定しなければ該当のcompetitionの全seasonをとってくる、という処理(を行う関数)を書いているようなので、そうする。

getMatchData <- function( compe_id , season_id=NULL ,compe_df =NULL ){

##-- 内部関数, 生JSONデータのURLを生成する---##
makeMatchURL <- function( compe_id ,season_id ){
  base_url <- "https://raw.githubusercontent.com/statsbomb/open-data/master/data/matches/"
  ret_url <- paste0( base_url, compe_id, "/", season_id,".json")
  return( ret_url)
} #func

##-- 内部関数, 生JSONデータからデータフレーム形式へ変換する--##
fetch_mInfo <- function( match_url){
  info_list <- jsonlite::read_json(match_url)
  info_row_l  <- lapply( info_list , function( wc_row){
    wc_row$away_team$managers <- wc_row$away_team$managers[[1]]
    wc_row$home_team$managers <- wc_row$home_team$managers[[1]]
    var_list <- list()
    rapply(wc_row , function(wr){ 
      var_list <<- c(var_list ,wr) 
    })#
    tst_df <- as.data.frame( var_list, stringsAsFactors=F)
    colnames(tst_df) <- names( unlist( wc_row))
    return( tst_df)
  }) #lapply
  
  return(do.call( dplyr::bind_rows , args=info_row_l ))
} #fetch_mInfo

if( is.null(season_id) && !is.null(compe_df)){
  season_ids <- (dplyr::filter( compe_df , competition_id == compe_id ))[,"season_id"]
  match_df_l <- lapply(season_ids , function( season_id){
    target_url <- makeMatchURL(compe_id , season_id)
    ret_df <- fetch_mInfo(target_url)
    return(ret_df)
  }) #lapply
  match_df <- do.call( dplyr::bind_rows, match_df_l)
  
}else{
  target_url <- makeMatchURL( compe_id ,season_id)
  match_df <- fetch_mInfo(target_url)  
}

return( match_df)
} #function 

あまり難しいことはやっていないけれど、とりあえずURLを生成する内部関数(makeMatchURL)と
URLから試合情報をdata.frame形式で取得する関数(fetch_mInfo)をつくって、season_idが指定されなかった場合はループ処理をしている、という感じの内容。
たとえばバルサの過去試合を取得するのならば、以下の感じで。

barca_df <-  getMatchData( compe_id =11, compe_df=compe_df)

dplyr::glimpse(barca_df)
# Rows: 485
# Columns: 48
# $ match_id                        <int> 303421, 303493, 303516, 303680...
# $ match_date                      <chr> "2020-07-19", "2020-06-23", "2...
# $ kick_off                        <chr> "17:00:00.000", "22:00:00.000"...
# $ competition.competition_id      <int> 11, 11, 11, 11, 11, 11, 11, 11...
# $ competition.country_name        <chr> "Spain", "Spain", "Spain", "Sp...
# $ competition.competition_name    <chr> "La Liga", "La Liga", "La Liga...
# $ season.season_id                <int> 42, 42, 42, 42, 42, 42, 42, 42...
# $ season.season_name              <chr> "2019/2020", "2019/2020", "201...
# $ home_team.home_team_id          <int> 206, 217, 209, 901, 217, 207, ...
# $ home_team.home_team_name        <chr> "Deportivo Alavés", "Barcelona...
# $ home_team.home_team_gender      <chr> "male", "mal

barca_df <-  getMatchData( compe_id =11, season_id = 4 , compe_df=compe_df)

ラインナップの取得

次はlineupの取得である。例として16/17シーズンのクラシコ@カンプノウのデータ(URL)から取得してみよう。
取得してくる項目は、saenaiさまの元コードと同じく、選手ID/選手名/背番号/スタメンかどうか/チームID/チーム名、とする。

############------------====lineup情報の取得====---------############
getLineUp <- function( match_id ){
  json_url <- paste0("https://raw.githubusercontent.com/statsbomb/open-data/master/data/lineups/",match_id ,".json")
  json_list <- read_json( json_url)
  ## 内部関数:各チームの情報をまとめてData Frame化 ##
  getTeamData <- function( t_list){
    t_id <- t_list$team_id
    t_name <- t_list$team_name
    player_info <- t_list$lineup
    player_df_l <- lapply( player_info , function(player_row){
      ret_row <- list()
      rapply( player_row ,function(x){
        ret_row <<- c( ret_row , x) #listに順次追加をしていく。
      })#rapply
      ret_names <-  gsub( "\\.","_", names(unlist( player_row)))
      ret_df <- as.data.frame( ret_row, stringsAsFactors=F)
      colnames(ret_df) <- ret_names
      return( ret_df)
    })## lapply
    
    player_df <- do.call( dplyr::bind_rows ,args = player_df_l )
    player_df$team_id <- t_id
    player_df$team_name <- t_name
    player_df <- dplyr::select( player_df , team_id, team_name , player_id:country_name)
    return( player_df)
  } #function
  
#Home/Awayそれぞれの情報を処理してタテにつなげる
  team_df <- do.call( rbind , lapply( json_list , function(team_json){
    return( getTeamData(team_json))
  })) #
  return(team_df)
} #function

#16/17のクラシコのlineupの取得
clsc_df <- getLineUp( 267076)

head(clsc_df, 2)
# team_id   team_name player_id                    player_name
# 1     220 Real Madrid      3163             Mariano Diaz Mejia
# 2     220 Real Madrid      4926 Francisco Roman Alarcon Suarez
# player_nickname jersey_number country_id       country_name
# 1    Mariano Diaz            18         64 Dominican Republic
# 2            Isco            22        214              Spain

あまり処理自体について説明することはないけれど、一つだけ付言すると、
read_jsonするとnested list形式で取得されるため、これをflatにしてdata.frameにするためにrapply()を使っている。
※「<<-」を使っていてあまり綺麗じゃないんだけど、以下のStack Overflowを参考にした。
stackoverflow.com

unlist()すると、強制的に型変換が行われてしまい、integerがcharacterとかになってしまうのを嫌った形。
R愛してるけど、ここらへんもっと融通利かないかなぁ。

イベントデータの取得

さいごに、同じ試合を対象としてイベントデータ(URL)を整形して取得してみよう。
試合のあらゆる事象が網羅されているので、当たり前だがデータ量がすんごいことになっている。

とりあえずread_json()して、構造を見てみよう。

evnt_url <- "https://raw.githubusercontent.com/statsbomb/open-data/master/data/events/267076.json"
evnt_json <- read_json( evnt_url)

イベントの種類を$event$nameでアクセスして集計してみると、以下のようになる

f:id:ronri_rukeichi:20201215105639p:plain
イベント一覧@16/17 クラシコ

意外とイベントの種類自体は少ないことがわかる。結構見慣れない指標もある
頭を悩ませるのが、イベントの種類によってかなりデータの形式(どういう変数を含むのか)が違ってくること。
だから、すべてのデータをひとつのdata frameで保持しようとせずに

  • ID/イベントの種類/時間情報のみを保持する共通テーブル
  • 各イベントごとの詳細情報を格納するテーブル

にわけて、情報を格納していくことにしよう。


まず、以下のような関数を実装する。

############------------====Event情報の取得====---------############
getEventInfo <- function( match_id){
  evnt_url <- paste0("https://raw.githubusercontent.com/statsbomb/open-data/master/data/events/", match_id,".json")
  evnt_json <- read_json( evnt_url)
  
  ### 共通DBをつくる ###
  base_info_l <- lapply( evnt_json, function( evnt_row){
    eve_id <- evnt_row$id
    eve_idx <- evnt_row$index
    eve_period <- evnt_row$period
    eve_ts <- evnt_row$timestamp
    eve_min <- evnt_row$minute
    eve_sec <- evnt_row$second
    eve_type_id <- evnt_row$type$id
    eve_type_name <- evnt_row$type$name
    eve_row <- data.frame(event_ID=eve_id , Index = eve_idx , Period = eve_period , Minutes = eve_min , Seconds = eve_sec , TimeStamp= eve_ts, Type_ID = eve_type_id , Type_Name=eve_type_name, stringsAsFactors = F)
    return( eve_row)
  }) #base_info_l
  base_df <- do.call( dplyr::bind_rows, args = base_info_l)
  
  
  ### Eventの種類ごとのDFをつくる ###
  eve_type <- dplyr::distinct( dplyr::select( base_df , Type_Name , Type_ID))
  eve_df_l <- list()
  
  for( i in seq_along( eve_type$Type_ID)){
    #JSONから生成したリストのうち、同じ種類のイベントのものだけ取得する
    tgt_type <- eve_type$Type_ID[i]
    tgt_idx <- which( base_df$Type_ID == tgt_type)
    tgt_evnt <- evnt_json[tgt_idx]
    
    #イベントごとのDBを生成する
    tgt_evnt_l <- lapply( tgt_evnt  , function(tgt_row){
      ret_row <- list()
      rapply(tgt_row , function(x){
        ret_row <<- c( ret_row, x)
      }) #rapply
      
      ret_row <- as.data.frame( ret_row , stringsAsFactors=F)
      suppressMessages( colnames( ret_row) <- gsub( "\\.","_", names(unlist(tgt_row ))))
      return( ret_row)
    }) #lapply
    suppressMessages(
    tgt_evnt_df <-  do.call(dplyr::bind_rows, args = tgt_evnt_l)
    )
    colnames( tgt_evnt_df) <- gsub( "\\.\\.\\.","_No",colnames(tgt_evnt_df))
    eve_df_l[[gsub(" " , "_" ,eve_type$Type_Name[i] )]] <- tgt_evnt_df
  }# for

  ret_list <- list( Base = base_df, Event= eve_df_l)
  return(ret_list)
}#--func


以下のように用いる。

eve_df <- getEventInfo(267076)

head(eve_df$Base , 2)
# event_ID Index Period Minutes Seconds
# 1 29a5ab71-0bda-4291-a084-be14138bbe20     1      1       0       0
# 2 25bdcf3e-3eef-4d06-82a0-60bdae6af6ae     2      1       0       0
# TimeStamp Type_ID   Type_Name
# 1 00:00:00.000      35 Starting XI
# 2 00:00:00.000      35 Starting XI

head(eve_df$Event$Pass[,1:20],2)
# id index period    timestamp minute
# 1 ed0d68b9-f420-473b-8cb4-1dbb206ab2ad     5      1 00:00:00.233      0
# 2 adf4c9ad-a749-4f22-883d-1973430e653b     8      1 00:00:03.850      0
# second type_id type_name possession possession_team_id
# 1      0      30      Pass          2                220
# 2      3      30      Pass          2                220
# possession_team_name play_pattern_id play_pattern_name team_id
# 1          Real Madrid               9     From Kick Off     220
# 2          Real Madrid               9     From Kick Off     220
# team_name player_id    player_name position_id     position_name
# 1 Real Madrid     19677  Karim Benzema          23    Center Forward
# 2 Real Madrid      5485 Raphael Varane           3 Right Center Back
# location1
# 1      61.0
# 2      40.2

イベントの各項目の値の詳細は公式のドキュメントを参照(URL)。
これでデータの取得処理は実装し終えた(難しいことはやってないけど長かった...)ので、やっとデータの分析・可視化にうつることができる。

イベントデータの基礎集計と可視化

必要なデータが
saenai氏がハンズオンで行っている処理をRで再現してみる。
ここからは、saneai氏とおなじく10/11のカンプノウでのエル・クラシコ(ペップ vs モウリーニョの時代)のデータを使っていく

#対象試合のMatch IDを指定
lu_df <-  getLineUp(69299)
evnt_df <-  getEventInfo(69299)

チーム別集計(例:パス本数)

f:id:ronri_rukeichi:20201215221746p:plain
チーム別パス集計(saenai氏のハンズオンより)

saenai氏はチームごとに、上のような集計を行っているので、再現してみよう。
公式のdocをみると、以下のようにoutcomeの値の設定がなされている。

f:id:ronri_rukeichi:20201215222151p:plain
パスのOutcome定義(StatsBomb公式)

"Complete"がねーじゃん..って思うけどこれ以外のパスはすべて成功とみなしてとりあえず扱うこととする。
ということで、上の手順で取得したデータをもとに集計を試みる

pass_info <- evnt_df$Event$Pass
pass_info <- dplyr::mutate( pass_info , Pass_Result = as.character(case_when(
  is.na(pass_outcome_id) ~ "Complete",
  pass_outcome_id == 9 ~ "Incomplete",
  pass_outcome_id %in% 74:75 ~ "Out",
  pass_outcome_id == 76 ~ "Offside",
  TRUE ~ as.character(NA)
)))
pass_info<-  dplyr::left_join(pass_info  , dplyr::select(  lu_df, player_id ,  TeamName_byP= team_name), by ="player_id")  
xtabs(~ TeamName_byP + Pass_Result , data = pass_info)
f:id:ronri_rukeichi:20201215225457p:plain
パス数のチーム別集計結果

ちゃんとsaenai氏の表と一致している。

個人別集計(例:タッチ位置平均)

f:id:ronri_rukeichi:20201215230322p:plain
タッチ位置平均(出所:saenai氏のハンズオン資料)

StatsBombのデータのウレシイところは、位置情報についても事細かな情報を把捉できる点にもある。
選手ごとのタッチ位置の平均を算出している上のsaenai神の表を再現できるか、試みる。
ハンズオンでは、どのイベントを対象としたのがか書いていないので、location情報が取得できるイベントをすべて対象する

  • statsbombのデータは、ピッチで言うとcoordinates_xが縦、coordinates_yが横を示している
  • xとyが逆やないとも思うけれど、基本的にピッチをみるときは左右にゴールがある横視点が基本
  • またその数字はm単位でなく、0-100に変換されている

とsaenai氏のハンズオンにはあるけれど、こっちの取得したデータだとX座標は0~120 , Y座標は0~80のままであるので、それを変換しなければならない。
ちなみに上記の公式ドキュメントのAppendix 2には、座標系の解説があるけれども、これも特に変換はしていない(なぜだ?)

f:id:ronri_rukeichi:20201215234150p:plain
StatsBomb公式ドキュメントのAppendix2より

......結果から話すと悲しいことに数値は合わなかった。
おそらく原因をみるにはpythonのStatsbombパッケージの内部処理まで見る必要があるけど、そこまでやる気力はないので挫折。かなしい。

イベントデータのピッチ上への可視化

これも楽しい作業。
ピッチの描画や座標表現に関してはggsoccerパッケージをつかっていく。
名前から察することができるだろうが、ggplotを利用したパッケージであり、いくつかの代表的なfootballのデータの座標系に対応してピッチを描画することができる

以下のように使う。

gg_pitch1 <- ggplot( data = barca_coord , mapping=aes( x=X2 , y =Y2, label= player_nickname)) 
+ annotate_pitch(fill="darkolivegreen1", dimensions=pitch_statsbomb) + theme_pitch() 
+ geom_point( shape= 18 , fill = "midnightblue") + geom_text_repel( size = 3.5, family = "Meiryo" , color = "firebrick4")
+ direction_label(x_label=60)
f:id:ronri_rukeichi:20201216104752p:plain:w920
ggsoccer()による描画結果

annotate_pitch()でピッチを描画(dimensionに適切な座標系を指定)し、theme_pitch()でいらない軸や目盛りを消している。
direction_labelで攻撃方向を示す。

プレッシャーをかけた位置のヒートマップを可視化

f:id:ronri_rukeichi:20201216111402p:plain
Real Madridのプレッシャーヒートマップ(出所:Saenai氏のハンズオン資料)

上のような、各チームのプレッシャーの位置分布の可視化を実装する。
ヒートマップを使うにはgeom_tile()が便利である。
◆geom_tile()に関しての参考URL

以下のような処理で、同様のヒートマップを実装できる


#プレッシャー位置の取得
press_df <- evnt_df$Event$Pressure
press_Real <- dplyr::filter(press_df,team_name == "Real Madrid")

#縦6区画、横5区画への分割
h_sep_l <- lapply( 20* 0:5 , function(x){c( x,x+20)})
w_sep_l <-  lapply( 16* 0:4 , function(x){c( x,(x+16))})

numToArea <- function(x , area_l){
  ret_v <- c()
  for( i in seq_along(x)){
    x_i <- x[i]
    if(is.na(x_i)){
      ret_v <- c(ret_v , as.numeric(NA))
    }else{
    cnt <- 0
    flag <- FALSE
    lapply( area_l , function(area){
      cnt <<- cnt + 1
      if( x_i >= area[1] && x_i < area[2]){
        ret_v <<- c( ret_v, cnt)
        flag <<- TRUE
      }
    })# lapply
    if(!flag){ ret_v <- c(ret_v , as.numeric(NA))} #if
    
    } #if
  }#for
  return( ret_v)
} #func

press_Real$Press_AreaX <- numToArea( press_Real$location1 , h_sep_l)
press_Real$Press_AreaY <- numToArea( press_Real$location2, w_sep_l)
press_Real <- dplyr::mutate( press_Real , Press_AreaXY=interaction( Press_AreaX, Press_AreaY,sep="×"))

press_density <-  table(press_Real$Press_AreaXY) /sum(table(press_Real$Press_AreaXY))
dense_df <- data.frame( Density = as.numeric(press_density), Area = names( press_density))
xy_coord <- expand.grid( 1:6 , 1:5)
xy_coord[,1] <- xy_coord[,1]*20-10
xy_coord[,2] <- xy_coord[,2]*16 -8
dense_df  <- cbind( dense_df , xy_coord)
colnames( xy_coord) <-  paste0( c("X","Y"), "2")

press_Real <- dplyr::left_join( press_Real, dense_df , by = c("Press_AreaXY"="Area"))
press_Real$Press_Percent <- press_Real$Density * 100

#第一段階:プレス点のみ
gg_pressR <- ggplot( data = press_Real , mapping=aes( x = location1 , y=80- location2 )) +annotate_pitch(dimensions=pitch_statsbomb , fill="grey") + theme_pitch() + direction_label(x_label=60) + geom_point( color="white", shape=20 ) + ggtitle( "Pressure Map(Real Madrid @2010/11 El Clasico)") +theme(plot.title = element_text(hjust = 0.5))

#第二段階:ヒートマップ
gg_pressR2 <- gg_pressR + geom_tile(data = dense_df , mapping=aes( x= X2 , y =80-Y2, fill=Density*100, width= 20 , height = 16) ,alpha=0.5) + scale_fill_gradientn(colours=c("cyan","brown4"),name="Percentage") +geom_text(data = dense_df , mapping=aes( x= X2 , y =80-Y2 ,label=  paste0(round(Density*100 , 1),"%")) )

以下のように、saenai氏と同じような図が可視化できている。

f:id:ronri_rukeichi:20201216154221p:plain
Real Madridのプレッシャー位置のヒートマップ(データ出所:StatsBomb)

いやー面白い。

パスマップ図の可視化

f:id:ronri_rukeichi:20201216183957p:plain
パスネットワーク図(出所:saenai氏のハンズオン資料)

次は上のようなパスのネットワーク図を描いてみよう。
上の図の完全な再現をめざすではなく、以下のような考え方で簡易的に描いてみる。

  • 各ポジションの平均位置は、パスを出すイベント("Pass")に含まれている出し手/受け手の位置情報を使って取得する。
  • ポジションを表わす点の大きさは、(パスを出した回数+受けた回数)÷2とする
  • ポジション間をつなぐ線は(どちらがパスの出し手/受け手を区別せず)パスのやりとりの総数に比例する

とりあえず、データを可視化しやすいように取得・整形する

lu_df <-  getLineUp(69299)
evnt_df <-  getEventInfo(69299)

library( pipeR)
pass_df <- evnt_df$Event$Pass
rcv_df <- evnt_df$Event[["Ball_Receipt*"]]
pass_df$Team_byP <- lu_df[match(pass_df$player_id, lu_df$player_id),"team_name"]
position_info <-  dplyr::distinct(dplyr::select(pass_df , position_id , position_name ,player_name , player_id,Team_byP ))
pass_df$recipient_position <-  position_info[ match( pass_df$pass_recipient_id , position_info$player_id), "position_name"] #出し手のポジションを取得する
pass_df %>>%  dplyr::mutate( completed = if_else( is.na( pass_outcome_name), TRUE , FALSE) ) -> pass_df
pass_Real <- dplyr::filter( pass_df, Team_byP == "Real Madrid")
pass_Barca <- dplyr::filter( pass_df, Team_byP == "Barcelona")


##座標系の取得
pass_grp_Real <- ( dplyr::group_by( pass_Real, position_name , recipient_position) %>>% dplyr::filter(  completed==T, !is.na( position_name), !is.na( position_name)))
pass_grp_Real %>>% ( ~ cnt_smr_Real = dplyr::summarise( .,N=n())) %>>%   (grp~ cbind(cnt_smr_Real ,  dplyr::summarise_at(grp ,vars( contains(location) ), mean ) ) ) -> pass_smr_Real
cnt_smr_Real <- ( dplyr::summarize( pass_grp_Real, N= n()) %>>% as.data.frame)
pos_smr_Real <- (dplyr::summarize_at( pass_grp_Real, vars(contains("location")),mean)  %>>% as.data.frame)
pass_smr_Real <- dplyr::left_join( cnt_smr_Real , pos_smr_Real , by= c("position_name","recipient_position"))

##出し手としての平均位置/受け手としての平均位置と重みを取得
passer_pos <- ( pass_smr_Real %>>% dplyr::group_by(position_name) %>>% dplyr::summarise(  N = sum(N), X = mean(location1), Y = mean(location2) ) %>>%  as.data.frame)
receiver_pos <- ( pass_smr_Real %>>% dplyr::group_by(recipient_position) %>>% dplyr::summarise(  N = sum(N), X = mean(pass_end_location1), Y = mean(pass_end_location2) ) %>>%  as.data.frame)
colnames(passer_pos) <- paste0("Pass_",colnames(passer_pos) )
colnames(passer_pos)[1] <- "Position"
colnames(receiver_pos) <- paste0("Receive_",colnames(receiver_pos) )
colnames(receiver_pos)[1] <- "Position"
pos_Real <- dplyr::left_join( passer_pos , receiver_pos , by = "Position")
pos_Real <- ( pos_Real %>>% dplyr::mutate( X = (Pass_X * Pass_N + Receive_X* Receive_N) / (Pass_N + Receive_N), Y = (Pass_Y * Pass_N + Receive_Y* Receive_N) / (Pass_N + Receive_N) , Weight = Pass_N + Receive_N) )

以下のような形で、データが取得・整形できている。

f:id:ronri_rukeichi:20201217111544p:plain
綺麗にしたパスネットワーク情報

さて、あとはこれをggsoccerやggplot2を使ってプロットしていくだけだ。
線分を引くためには、geom_segment()を使う。

pass_smr_Real$from_X <- pos_Real[match( pass_smr_Real$position_name,pos_Real$Position ),"X"]
pass_smr_Real$from_Y <- pos_Real[match( pass_smr_Real$position_name,pos_Real$Position ),"Y"]
pass_smr_Real$to_X <- pos_Real[match( pass_smr_Real$recipient_position,pos_Real$Position ),"X"]
pass_smr_Real$to_Y <- pos_Real[match( pass_smr_Real$recipient_position,pos_Real$Position ),"Y"]

##描画する
gg_passNetR <- ggplot( data =pos_Real , mapping=aes( x = X , y=80- Y ,size=Weight * 3/50 )) +annotate_pitch(dimensions=pitch_statsbomb , fill="chartreuse4") + theme_pitch() + direction_label(x_label=60) + geom_point(color="firebrick3", shape=18) + ggtitle( "Pass Network(Real Madrid @2010/11 El Clasico)") +theme(plot.title = element_text(hjust = 0.5)) 

gg_passNetR2 <- gg_passNetR + geom_segment( data=pass_smr_Real, mapping=aes( x = from_X , y =80- from_Y , xend = to_X , yend = 80- to_Y , size = N *2/3* 0.3 , alpha = N /10 ) , color= "white")+ geom_text_repel(aes(label=Position), size=3.5 , color="yellow",fontface = "bold") +theme(legend.position = 'none')


これで出力されるのが下図である。

f:id:ronri_rukeichi:20201217114826p:plain
Rで出力したパスネットワーク図(レアル・マドリード@2010/11 クラシコ

いやーめっちゃかっこいいですね。テンションあがりますわ。

シュートの位置分布の可視化

シュートをどの位置から打ったのかを一覧化する。
イメージとしては、Football Labの試合結果ページ(例:名古屋vs湘南)にあるような形でシュートの位置分布を描画したい。

基本的にはFootball LABの図を踏襲するようにして、ゴールのときのみシュートの弾道を赤色にするようにしてみる。
以下のようなシンプルなコードで書ける。

#データ整形
shot_df <- evnt_df$Event$Shot
shot_Barca <-  dplyr::filter(shot_df , team_name == "Barcelona")
(shot_Barca %>>% dplyr::mutate( Shot_Result = if_else( shot_outcome_id ==97 , "Goal",if_else( shot_outcome_id %in% c(96,98,99) , "off-Target","Saved")))) -> shot_Barca

#描画
gg_shotB <- ggplot( data = shot_Barca,mapping=aes(x = location1 , y= 80-location2, fill= Shot_Result ,colour = Shot_Result ))  +annotate_pitch(dimensions=pitch_statsbomb , fill="olivedrab1") + theme_pitch() + direction_label(x_label=60) + ggtitle( "Shot Map(Barcelona@ 2010/11 El Clasico)")+theme(plot.title = element_text(hjust = 0.5), legend.position="none")  + coord_flip(xlim=c(58,121),ylim= c(-12,112))
gg_shotB2 <- gg_shotB +geom_segment( data = shot_Barca , mapping=aes(x= location1 , y =80- location2 , xend = shot_end_location1 , yend = 80- shot_end_location2, colour = Shot_Result),size= 0.7, linejoin="round",lineend="round")+ scale_colour_manual( limits= c("Goal","off-Target", "Saved"), values=c("red2", "grey55","grey55"), name="", guide=F)+ geom_point(color="midnightblue", shape=18,size=2.5)  

できあがった図は以下のようなもの。求める形で描画できている。

f:id:ronri_rukeichi:20201218094818p:plain
バルセロナ(2010-11のクラシコカンプノウ)のシュート位置分布

もうちょっと見栄えは工夫できそうだけどそれは今後の課題である。

Conclusion

とりあえずなぞってみただけど、面白かった。こういう楽しい体験のきっかけとなったsaenai氏の記事に感謝である。

これくらいの粒度のデータでマッシモ名古屋を分析して風間さんの時代との違いとかを分析できたら、超楽しいんだろう。
そういう時代が、来たらいいな。


ヒトリエ『アンチテーゼ・ジャンクガール』MV / HITORIE - Antithesis JunkGirl

Enjoy!!

*1:いやお前python勉強しろよ....って言われたらその通りなのだが。まぁPython→Rの翻訳するのも少しは勉強にはなっているだろう(希望的観測)