投稿者「rintaromasuda」のアーカイブ

Day 1 から12年間、寝る前の読み聞かせを続けました

まだ長女が奥さんのお腹の中にいた頃に「読み聞かせをするお父さんになろう」と考え、生まれる前から絵本を買い集めました。そして誕生後、文字通り Day 1 から読み聞かせを始めました。そして彼女が12歳を迎えるまで続けることができました。その後はやはり、緩やかにですが、読み聞かせはしなくなっていきました。

ここまで続けられたことは自慢ですし、長女が読書好きなのはこのお陰だと密かに思っています。読み聞かせをしていると頭が良くなるとか、読解力が高くなるとか、情緒豊かな子に育つだとか、そういう情報が頭の片隅になかったと言ったら嘘になりますが、そういったモチベーションだったらここまで続けられなかったと思います。

私と長女にとっては、どこか儀式的な時間だったと言っていいと思います。世話しない毎日の最後に訪れる静かな時間。その日に読みたい絵本を選んで、静かな部屋でその物語を楽しむ。私も疲れていてサボりたいなという気持ちになったことも何度もありましたが、それでも続けられたのはやはり純粋にその時間が楽しかったからだと思います。

長女と読み聞かせをしなくなったのはもちろん寂しいですが、まだ次女は12歳まで数年ありますので、まだ少しだけこの時間を楽しむことはできそうです。気が付けば、私の方が絵本や児童書のファンになっていたような気がします。

そう言えば最近本棚を移動したのですが、子供たちが大きくなったら、こういう大量の絵本や児童書はどうしたらいいのでしょうね。普通に考えればブックオフやメルカリで売るか、図書館にでも寄付するか、もしくは資源ごみということになりますが、思い入れが多い本も多く、なかなか難しい問題です。

ブログを書く理由を言語化しておきたい

前回の投稿をもう少し深堀して、「そもそも私はなんでブログなんて書くのだろうか」ということについて考えてみたいと思います。

「言語化は大事だよね」と言語化しておきたい | Never Too Late (rintaromasuda.me)

ブログを書くことにはそれなりの手間がかかります。近年は音声によるテキスト入力機能の進化だったり、ChatGPTをはじめとする生成AIの登場でブログの記述コストもかなり下がったと思いますが、それでもそれなりのコストをかけています。そのコストをかけるほどの明確な理由があるのか、それこそ自分の中で整理しておきたいところです。

まずは前回の記事でも書いたように、思考の整理や言語化です。ブログを書けば強制的に自分の思考をアウトプットせざるを得ません。そして同時にそのアウトプットを自分の中に戻す、つまりインプットをせざるを得ません。そのプロセスの繰り返しそのものが言語化であり、私にとってブログの記述が言語化の有効なひとつの手段となっています。

次の理由は情報収集です。 よく言われるように、情報を集める最善の方法は情報を発信することです。私も少なからず、情報の発信者になることによって情報を得てきました。これは読者からのコメントやフィードバックということもありましたし、リコメンドエンジンなり自分で探したりなりで、自分に役に立つ別の情報源に出会えたということがたくさんありました。

三つ目の理由は人との出会いです。第二の理由とも深く関係しますが、ブログを通して同じような思考の持ち主、同じような領域で働く人、同じような志の持ち主、そういった人たちに出会うことができ、それはインターネットの外、つまり現実の世界ではなかなかありえなかった出会いであったりします。そのように人同士のクラスタリングを行う力が、ブログおよびインターネットにはあります。

そして最後に、それは何よりも私は書くことが結局好きだという理由が挙げられます。小学生の頃、読書感想文と言えば不人気な宿題という存在でしたが、私は実はあれが大好きでした。頼まれてもいないのに、勝手に読書感想文を書いて先生に提出したことさえあった気がします。当時の私が読書感想文の記述のどこに喜びを見出していたのか、楽しみを見出していたのかは分からないのですが、読書感想文を書いていたモチベーションと、ブログのそれとの間には多くの共通点があると感じています。

「言語化は大事だよね」と言語化しておきたい

ブログでもX(Twitter)でもFacebookでもそれこそ日記でも何でもいいのですが、自分の考えを書き記す訓練というのはしておくべきだと思います。今さら「言語化が大事」だなんてもはやカビが生えたような助言なのですが、言語化というのは自分の思考のアウトプットを試み、そのアウトプットしたものを再びインプットし、再度またアウトプットを試みる…といった試行錯誤の中でしか成しえないものなので、普段から定期的に自分の考えをアウトプットする場はあった方が良いという話です。

確か小説家の村上春樹さんもそのような事を仰っていたと思うけれど、書くことを通してものを考える、もう少し抽象化すると何かしらのアウトプットにより自分の思考が整理されていくという経験は多くの人にあると思う。例えば年に数十回も講演をこなす人の話す言葉に強い説得力があるのは、その人が講演をお願いされるような実績があるという理由だけではなく、数多くのアウトプットをこなすことで思考、そして話す内容がどんどんブラッシュアップされていくからというのも大きく関係していると考えます。

なぜ今このような内容を書いているかというと、最近あまり自分自身がアウトプットをしていなかったことを反省しているからです。殴り書きでもこのブログにどんどんアウトプットをして、整理された思考を取り戻したいからです。もちろん口頭のコミュニケーションでもそういった機会を設けていこうと思いますが、圧倒的に効率的なのはひとりでいつでもできる、こういった執筆活動です。

西の魔女が死んだ(梨木果歩)

今年亡くなった私の母と長女には何か特別な結び付きがあったように思っているのですが、今年ふたりに起きたことについて話す機会があり、そのときに「まるで”西の魔女が死んだ”みたいですね」と教えて頂きました。印象的なタイトルだったので書籍自体は認識していたのですが、内容はまるで知りませんでしたし、タイトルそのままにファンタジー小説なのかと思い込んでいました。おばあちゃんと孫の結び付き、というのはありふれたテーマと言っていいと思います。内容もかなりあっさりとしていて、文章量もかなり少なめ。先日母の生まれ故郷まで飛行機で行ってきたのですが、1時間ちょいのフライト往復でさくっと読める書籍でした。個人的にはケンジさんの存在が生み出すおばあちゃんと「私」の不協和音の描写が好きですね。読むかどうかは分かりませんが、概要を伝えて長女の部屋に置いておきました。

インターネットに墓を建てる

先日母が亡くなりました。春に癌が発覚してから亡くなるまであっという間の4か月でした。

母が亡くなってからそれはたくさんの事を考えましたが、そのうちのひとつが「母がブログをやっていたらよかったのにな」というものです。母は優しい人でしたが、世間一般の優しいお母さんイメージにはあまり当てはまらないような人で、どちらかというと私にとっては人生の師匠みたいな人でした。考えを押し付けるようなところは一切ありませんでしたが、人が生きる上で何を大事にするべきなのか、どのように考えて行動するべきなのか、そんな類の事をよく語ってくれる人で、私はもろにその思想の影響を受けて育ったと思います。

母が亡くなってから、そしてきっとこれからも、要所要所で母の考えに触れたくなることが出てきます。そんなとき、母がブログにでもその思想を書いていれば便利だったのにな、と思った次第です。

亡くなった母の事を今更どうこう言っても遅いのですが、私ももう人の親なので、私がそれを実践することにし、ここに新しいブログを立ち上げました。まずは二十代の頃に書いていた青臭いブログをこちらに復活させました。他のブログもこちらにどんどん集約し、最終的にはここを自分の墓にしたいと思います。千の風になるのではなく、千の記事になっていこうと思います。

とはいえもちろん技術的に課題があります。結局のところブログサービスも何処かの企業にホストされているわけで、その企業が消え去ったり、もしくは私が亡くなって料金を払えなくなればこれのブログは埋もれていってしまいます。これはどうにかしないといけません。個人的にはWEB3に期待する事のひとつがこれで、国家も含む複数の機関・組織で運営される分散型の公共ブログサービスのようなものが運営されれば(技術的にはとっくに可能)、そこに各人が生きた証を残せます。将来戸籍や住民票が分散型のデータベースに乗ることがあれば、そこで可能にしてもらえると嬉しいですね。

scikit-learn/scipyを使い、コサイン類似度ベースで階層型クラスタリングをするときのメモ

scikit-learn, scipyを使い、文章をコサイン類似度を用いて階層型クラスタリングを行おうとしたときに少し詰まったのでメモです。

まずは以下のような3つの文章を用意します。Chat GPTに作らせたそれぞれバスケットボール、野球、交通渋滞に関する50単語のニュースです。

doc1 = "Last night, the City Hawks clinched a nail-biting victory against the Mountain Lions, 102-99. Star player, Jordan Mitchell, secured the win with a last-second three-pointer. Fans are eagerly anticipating next week's match, as playoff implications heat up. Basketball enthusiasts, mark your calendars!"
doc2 = "Yesterday, the Bay Breeze clinched a 5-4 win over the Sunset Sluggers. Ace pitcher, Liam Rodriguez, delivered 8 strong innings with 10 strikeouts. The highlight was shortstop Alex Torres' game-winning home run in the 9th. The league title race intensifies as the season's end approaches."
doc3 = "Heavy gridlock paralyzed California's I-5 highway yesterday, with delays stretching for miles. A combination of roadwork and multiple minor accidents exacerbated the rush-hour congestion. Commuters are urged to seek alternative routes or use public transport today as authorities work to clear the backlog and ensure smoother traffic flow."

scikit-learnを用い、それぞれの文章のTF-IDFベクトルをを求めます。なお本当なら文章にステミングを施したり、ストップワードを除去したり等の作業が必要ですが、ここでは省略します。

import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer()
docs = np.array([doc1, doc2, doc3])
vector_array = tfidf.fit_transform(docs).toarray()
print(vector_array)

配列の中身は以下のようになっています。どうやら全体で113個の単語が検出されたようです。

[[0.   0.15 0.15 0.   0.   0.   0.15 0.   0.   0.   0.15 0.   0.12 0.09
0.   0.   0.15 0.   0.15 0.   0.15 0.   0.15 0.   0.12 0.   0.   0.
0.   0.   0.15 0.   0.   0.15 0.   0.15 0.   0.   0.   0.   0.15 0.15
0.   0.   0.   0.   0.   0.15 0.   0.   0.   0.15 0.3  0.   0.   0.15
0.15 0.15 0.   0.   0.15 0.15 0.   0.15 0.15 0.15 0.   0.   0.   0.
0.   0.15 0.15 0.15 0.   0.   0.   0.   0.   0.   0.   0.   0.15 0.15
0.   0.   0.   0.   0.15 0.   0.   0.   0.   0.27 0.15 0.   0.   0.
0.   0.   0.   0.15 0.   0.   0.15 0.   0.15 0.12 0.   0.09 0.   0.
0.15]
[0.15 0.   0.   0.15 0.   0.15 0.   0.15 0.   0.   0.   0.15 0.   0.09
0.   0.   0.   0.15 0.   0.15 0.   0.   0.   0.   0.11 0.   0.   0.
0.   0.15 0.   0.15 0.   0.   0.   0.   0.   0.   0.15 0.   0.   0.
0.   0.15 0.   0.15 0.   0.   0.15 0.15 0.15 0.   0.   0.15 0.15 0.
0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.15 0.
0.15 0.   0.   0.   0.   0.15 0.   0.15 0.   0.15 0.   0.15 0.   0.
0.   0.15 0.15 0.   0.   0.   0.15 0.15 0.15 0.52 0.   0.15 0.   0.
0.15 0.   0.   0.   0.   0.   0.   0.15 0.   0.11 0.15 0.09 0.   0.11
0.  ]
[0.   0.   0.   0.   0.14 0.   0.   0.   0.14 0.29 0.   0.   0.11 0.09
0.14 0.14 0.   0.   0.   0.   0.   0.14 0.   0.14 0.   0.14 0.14 0.14
0.14 0.   0.   0.   0.14 0.   0.14 0.   0.14 0.14 0.   0.14 0.   0.
0.14 0.   0.14 0.   0.14 0.   0.   0.   0.   0.   0.   0.   0.   0.
0.   0.   0.14 0.14 0.   0.   0.14 0.   0.   0.   0.14 0.14 0.   0.14
0.   0.   0.   0.   0.14 0.   0.14 0.   0.14 0.   0.14 0.   0.   0.
0.14 0.   0.   0.14 0.   0.14 0.   0.   0.   0.17 0.   0.   0.29 0.14
0.   0.14 0.14 0.   0.14 0.14 0.   0.   0.   0.   0.   0.09 0.14 0.11
0.  ]]

次にscipyのlinkageを用いてベクトル間のコサイン類似度と、その値に応じた階層構造を求めます。

from scipy.cluster.hierarchy import linkage
hierarchy = linkage(vector_array, metric='cosine')
print(hierarchy)

出力結果は以下のようになりました。

[[0.   1.   0.82 2.  ]
[2.   3.   0.89 3.  ]]

まずですが、TF-IDFのベクトルは負数を返さないはずなので、コサイン類似度は0から1に収まるはずです。またscipyの実装では、コサイン類似度の値は1から引いた値が返されるので0と1の関係が逆転しており、0が最も遠く、1が最も近いという意味になりますpdistのドキュメントにそう書いてありました。

よって上記の出力の意味は、doc1とdoc2(出力の中では0と1)が距離0.82で最も近く、その2つとdoc3の距離(出力の中では2と3)は0.89ということになります。なおこの(doc1/doc2)とdoc3のように、複数の要素を含みだしたクラスターと別のクラスターの距離を測るアルゴリズムというのはいくつかあり、linkageのmethodパラメータで指定できます。ユークリッド距離を用いる場合は色々な選択肢があるのですが、コサイン類似度を用いる場合にはそのサブセットを使用することになります。methodを指定しない場合は単連結法というアルゴリズムが利用されるのですが、それを確認してみましょう。

以下のようにすると、各文章間のコサイン類似度を確認することが出来ます。

from scipy.spatial.distance import pdist, squareform
pd.DataFrame(squareform(pdist(vector_array, metric='cosine')),index=['doc1','doc2','doc3'], columns=['doc1','doc2','doc3'])
doc1doc2doc3
doc10.000.820.93
doc20.820.000.89
doc30.930.890.00

というわけで、先のコサイン類似度0.89というのはdoc2とdoc3の距離だったことが分かります。doc1とdoc3の距離が0.93とこれより遠い為、0.89の方が採用と思われます。method=’complete’, ‘average’, ‘weighted’といったアルゴリズムも指定可能です。詳しくはlinkageのドキュメントを参照ください。

Google BigQuery MLを使ってみたけど、これが便利に使えるシーンが分からない

野暮用でGoogle BigQueryを勉強し始めました。そしてBigQueryは機械学習をサポートしているというので早速少し使ってみました。「SQLと機械学習」と聞くとどこか奇妙な組み合わせのような響きですが、機械学習は大抵データフレームを使ってモデルを作り、そのモデルを使ってデータフレームを変更する(大抵は列を足す)という処理になので、そう考えればSQLで使うのは自然かもしれません。

ちょうどこちらも野暮用でバスケットボール選手の成績を使ったクラスタリングをしていたので、そのCSVファイルをBigQueryにアップロードしデータセットを作成、そのデータセットを使ってk-means法によるクラスタリングを試してみました。トレーニングはこんな感じで書けます。ちなみに簡略化の為にパラメータは2つのみ使用しています。

CREATE OR REPLACE MODEL
`mydataset1.bleague_player_clustering` OPTIONS(model_type='kmeans', num_clusters=10) AS
SELECT
(PPG - PPG_AVG) / PPG_STD AS PPG_STDED,
(RPG - RPG_AVG) / RPG_STD AS RPG_STDED
FROM
(
SELECT
PPG,
AVG(PPG) OVER() AS PPG_AVG,
STDDEV(PPG) OVER() AS PPG_STD,
RPG,
AVG(RPG) OVER() RPG_AVG,
STDDEV(RPG) OVER() RPG_STD
FROM
`mydataset1.bleague_players`
)

下部のSELECT文で抽出されたデータがモデルのトレーニングに使われるという構図です。CREATE OR REPLACE MODELのOPTIONSでは、モデルのハイパーパラメータ等が指定し、ここでは分類するクラスター数を10と指定しました。mydataset1.bleague_player_clusteringというのがモデルの名前です。

Big Query MLの文法的はそれなりにシンプルだと思います。ですがSQLには大抵データの前処理に適した関数などが少なく、それをする為に複雑な副問い合わせなどが必要になりそうです。例えばk-means法だと各パラメータに標準化を施す必要があったりしますが、それだけの為に上述のような副問い合わせが必要になってしまいます。

作成したモデルを使い、実際にクラスタリングを行うためには以下のように記述します。

SELECT
PLAYER,
TEAM,
CENTROID_ID
FROM
ML.PREDICT
(
MODEL `myproject1-401904.mydataset1.bleague_player_clustering`,
(
SELECT
*,
(PPG - PPG_AVG) / PPG_STD AS PPG_STDED,
(RPG - RPG_AVG) / RPG_STD AS RPG_STDED
FROM
(
SELECT
*,
AVG(PPG) OVER() AS PPG_AVG,
STDDEV(PPG) OVER() AS PPG_STD,
AVG(RPG) OVER() RPG_AVG,
STDDEV(RPG) OVER() RPG_STD
FROM
`mydataset1.bleague_players`
)
)
)

下部が再びモデルに与えるデータで(今回の場合はトレーニングのデータと同じ)、それに伴いML.PREDICTが新たなカラムを追加し(例えばCENTROID_ID)、最上部でそれをSELECTしています。こちらも文法だけ言えばとてもシンプルです。

Googleのインフラを用いて動くこともあり、BigQuery MLにはそれなりの速度的なメリットはあるのではないかと推測します。ただし上述のように前処理をデータに施すときにも面倒が起きそうですし、トレーニングデータとテストデータを分けるといった、機械学習の前に行うあれこれがいちいち面倒になりそうです。トレーニングデータとテストデータを分ける場合、乱数の入ったひとつのカラムを用意し、その値でどの行を取得するのかを決定する感じでしょうか。

タイトルにもつけたのですが、Google BigQueryが便利に使えるシーンが今のところ分かりません。前処理の終わったデータをアップロードしてから使用してもよいかもしれませんが、速度的メリットを享受したい程の大量データだとそれも大変ですし、かといってバックエンドで何処かから流してきたデータを使うのであればやはり前処理は必須でしょうし、その辺の丁度いいシチュエーションが何なのか、頭の片隅に入れておいて考えたいと思います。

機械学習でB1の選手をクラスタリングしてみた(2022-23バージョン)

以前にも似たようなネタをやっているのですが、B1全選手を2022-23シーズンの総計スタッツを利用し、機械学習でクラスタリングを行ってみました。

利用したスタッツの項目は以下です。すべての標準化(standardization)をしてから利用しスケールを揃えています。

  • 出場試合数
  • スターター出場試合数
  • プレイ時間
  • 平均プレイ時間
  • 平均得点
  • 平均2点フィールドゴール成功数
  • 平均2点フィールドゴール試投数
  • 平均3点フィールドゴール成功数
  • 平均3点フィールドゴール試投数
  • 平均フリースロー成功数
  • 平均フリースロー試投数
  • 平均オフェンスリバウンド数
  • 平均ディフェンスリバウンド数
  • 平均リバウンド数
  • 平均アシスト数
  • 平均ターンオーバー数
  • 平均スティール数
  • 平均ブロックショット数
  • 平均被ブロックショット数
  • 平均ファウル数
  • 平均被ファウル数
  • +/-(プラスマイナス)

今回はいわゆる凝縮型の階層的クラスタリングを行いました。簡単に説明すると

  1. 選手ひとりひとりをひとつのクラスタと見做す
  2. 上述のスタッツ項目を使い各クラスタ間の距離を計測する
  3. 一番距離が近かったクラスタをひとつのクラスタと見做す
  4. 2に戻る

というような手法でクラスタを作っていきます。専門的な話ですが、距離はユークリッド距離を用い、クラスタの連結にはウォード連結法を使用しました。

出来上がったクラスタを、デンドログラムという樹形図で表現します。トーナメント表のようですが、繋がっている選手同士はスタッツが似ている、そしてトーナメント表の高さが低いほど似ている度が高い、という意味です。

以下、とても大きくなってしまいましたが全体像です。

いくつかの箇所をピックアップしてみます。まずは河村勇輝を見てみましょう。

これの意味するところは、河村と一番スタッツの類似度が高いのがDJ・ニュービルで、その二人に一番近いのがマイルズ・ヘゾンだということです。その下にダーラム、エバンス、ガードナー、トレイ・ジョーンズ、ファジーカス、ビュフォードなどMVPクラスの選手たちがクラスタを成していますが、これらのクラスタが河村・ニュービル・ヘゾンのクラスタに近いということです。

ちなみにもっと大きく見ても、河村は外国籍選手が織りなす大きなクラスタに唯一属する日本人選手であり、やはりスタッツの上からも別格の存在だったことが伺えます。

次にこちらを見てみましょう。

こちらではエース級の日本人選手たちがひとつのクラスターを成していました。富樫勇樹と安藤誓哉が近いというのは感覚的に納得する人も多そうな結果です。

その少し下にはこんなクラスタもありました。

サーディー・ラベナと岡田侑大、キーファー・ラベナと佐々木隆成、鵤誠司と中東泰斗、ベンドラメ礼生と寺嶋良、納得できるような、できないような、そんな組み合わせが出来上がっていて面白いです。

データ仕事をRからPythonに乗り換えるときのあれこれメモ

ずっとデータの仕事をRで行っていたのですが、思い立ってPythonに乗り換えることにしました。Pythonという言語自体には以前から定期的に使っていたこともあり全く抵抗はありませんが、以降に際して乗り越えなければならない大きなハードルが大きく分けて4つあります*1

  1. Rのdata.frame、dplyrで行っていた作業をpandasで出来るようにならなくてはならない
  2. ggplotで行っていた視覚化の作業をMatplotlibで出来るようにならなくてはならない
  3. RStudioではなくJupyter Notebookで作業を出来るようにならなくてはならない
  4. Rでやっていた統計処理(検定や分布に従った乱数の生成)をSciPyで出来るようにならなくてはならない

完全に移行するまでには頭も手も色々な事に慣れていく必要があると思います。この投稿にはそこで得た知識をメモしていく予定です。

Rのdataframe、dplyrからPythonのpandas等への移行

データに全体ランクやグループ毎ランクを付ける

データをいじっていると割とデータにランク付けをしたりします(例えば得点で順位を付けるとか。)それをデータ全体に対して行ったり、あるグループ毎に行ったりします。これdplyrだとやり方が美しくて好きなんですよね。

R

library(dplyr)
df <- data.frame(Name = c("Cameron", "Bob", "Cameron", "Cameron", "Bob", "Alice", "Alice", "Alice", "Bob"),
Score = c(160, 98, 109, 84, 91, 120, 98, 107, 82))
df %>%
mutate(OverallRank = dense_rank(desc(Score))) %>%
group_by(Name) %>%
mutate(PersonalRank = dense_rank(desc(Score)))

Python

import numpy as np
import pandas as pd
df = pd.DataFrame({'Name': ["Cameron", "Bob", "Cameron", "Cameron", "Bob", "Alice", "Alice", "Alice", "Bob"],
'Score': [160, 98, 109, 84, 91, 120, 98, 107, 82]})
df['OverallRank'] = df.Score.rank(method='dense', ascending=False).astype(int)
df['PersonalRank'] = df.groupby(['Name'])['Score'].rank(method='dense', ascending=False).astype(int)

いくつかの列の値を使って新しい列の値を作る

これもしばしばやります。列Aと列Bの値を使って列Cの値を作るという感じです。

R

df <- data.frame(A = c(10,20,30), B = c(5,25,35))
df["C"] = ifelse(df["A"] > df["B"], "A is bigger", "B is bigger or equal")

Python

df = pd.DataFrame({'A': [10,20,30], 'B': [5,25,35]})
df['C'] = ["A is bigger" if b else "B is bigger or equal" for b in df['A'] > df['B']]

Pythonで美しく書く方法が分かりませんでした。lambdaを使うと更にごちゃごちゃしてしまいます。

df['C'] = list(map(lambda b: "A is bigger " if b else "B is bigger or equal", df['A'] > df['B']))

RのggplotからPythonのMatplotlib等への移行

普通の棒グラフを書く

とりえあず普通に棒グラフをmatplotlibで書いてみます。pandasにビルトインされているようなので、使える限りはそれを使おうと思います。

import numpy as np
import pandas as pd
df = pd.DataFrame({'City':["Tokyo", "Beijing", "New York", "Paris"],
'Value1':[250,500,200,125],
'Value2':[40, 30, 20, 55]})
df.plot.bar(x='City', y=['Value1','Value2'], stacked=False)

グラフを画像で保存する

Rのときはggsave()を使っていました。ggsave()を使うとかなり綺麗な画像が出力出来て良かったんですよね。Pythonでpandasのdataframeを使ってやる場合、以下のようにget_figure()した後にsavefig()を使うのが良さそうです。

fig = df.plot.bar(x='City', y=['Value1','Value2'], stacked=False).get_figure()
fig.savefig("MyFile.jpg")

データフレームのひとつ前(またはひとつ先)の行の値を取得する

割とよくする作業なのですが、データフレームのひとつ前、またはひとつ先のデータを取得する作業です。

Rだとこのように書けます。

df <- data.frame(A=c(10,20,30,40,50))
df %>%
mutate(PrevA = lag(A))

pandasだとlead(), lag()に値する関数のようなものが見つからないので、shift()を使ってずらしたデータフレームを作り、それをmerge/concatするしかなさそうです。

df = pd.DataFrame({'A':[10,20,30,40,50]}, index=range(5))
df = pd.concat([df, df.shift(1).rename(columns={'A':'PrevA'})], axis=1)

RのRStudioからPythonのJupyter Notebookへの移行

コードブロックを実行する

むしろR Studioの方が特殊なんでしょうけれど、R StudioがCTRL+ENTERでコードブロックを実行できるのに対し、Jupyter NotebookはSHIFT+ENTERで実行です。こういう手癖は地味に生産性に差が出てくるのでしっかりと切り替えられるようにしたいです。

dataframeの中身を目視する

R Studioだとdata.frameの中身を目視したいときにView()という便利な関数があって、それっぽいUIを出してくれるんですよね。今のところJupyter Notebookであれば

from IPython.display import HTML
HTML(df.to_html(index=False))

というやり方でテーブルをレンダリングするのが良さそう。ただ大きなdataframeをレンダリングしようとしたらブラウザ止まりました。

Rの統計処理からSciPy等への移行

カイ二乗検定をする

検定は統計処理をする上で避けて通れません。ここではカイ二乗検定をしてみたいと思います。Wikipediaに載っていた例を用いたいと思います。

R

m <- matrix(c(90,60,104,95,30,50,51,20,30,40,45,35), nrow = 3, byrow = TRUE)
colnames(m) <- c("A", "B", "C", "D")
rownames(m) <- c("White collar", "Blue collar", "No collar")
chisq.test(m, correct=FALSE)

Python

import numpy as np
import pandas as pd
from scipy import stats
df = pd.DataFrame({'A':[90,30,30], 'B':[60,50,40], 'C':[104,51,45], 'D':[95,20,35]}, index=['White collar','Blue collar', 'No collar'])
stats.chi2_contingency(df, correction=False)

*1:移行先は断定的に書いていますが、もちろん代替が存在することも理解しています。

書評「Unguarded」(Scottie Pippen)

最近はバスケ関連本の感想ブログと化している当ブログですが、とりえあずこの『Unguarded』で書評はひとまずお休みにしようかなと思っております。NBA関連の本をちょっと原著で読んでみようかなと最初に考えたとき、この本が実は第一候補だったのですが、ペーパーバック版がまだ発売されていなかったので後回しにしていました。ハードカバーって実は読み辛くてちょっと苦手です。

本書の冒頭で説明されているのですが、この本はNetflixでヒットしたマイケル・ジョーダンの『THE LAST DANCE』のカウンターパンチ的に著されたものです。ピッペンに纏わる逸話にはいくつかそのような話がありますが、『THE LAST DANCE』でもジョーダンの視点からブルズの栄光が描写されており、自分が得るべき賞賛が十分に表現されていないと感じたことがきっかけになっているようです。

私が本書を通して感じたのは、例えばジョーダンの陰に隠れて過小評価されていたり、ブルズとの契約の絡みで得るべき額のサラリーを得なかったりといった話は、むしろピッペンを特別な存在として際立たせるストーリーだなということです。ピッペンはもちろんNBA史上に名を残す偉大なプレーヤーですが、そういったストーリーが更に彼というプレーヤーの存在を特別なものにした、そのように感じました。サラリーに関しても当然 underpaid だったことは間違いないですが、誤解を恐れずに言えばその話題が今なおこうして話題に出来る事を考えれば「元が取れた」と言えるかもしれません。

それに私のような90年代NBAファンからすれば、ピッペンは彼が思うほどには過小評価されていないと感じます。ライト層からは『ジョーダンの右腕』くらいの印象を持たれているかもしれませんが、ドリームチームでの活躍を含め、当時のNBAを沸かせたバークリー、ロビンソン、ユーイング、オラジュワン、ストックトン、マローン等々に比肩するどころか、6度のチャンピオンに輝いた正真正銘のスーパースターです。おそらく世界中の多くのバスケファンの中で同じ認識でしょう。

ピッペンがNBA入りしてからの話は大枠で知っていることが多かったのですが、ピッペンの幼少期についてはまったく知識がありませんでした。以前に読んだヤニスの生い立ちと同様、かなり厳しい暮らしをしていた少年時代について知ることができたのは良かったです。今ではピッペンの長男さんがNBA入りを目指してGリーグでプレーしているのですから、時が流れるのは早いものです。