2022年 9月 27日(火)は、「LiveView JP#10:LivebookでキャッチーにElixirを楽しもう」が開催されました。@torifukukaiou さんは 「LiveView JP#10:LivebookでキャッチーにElixirを楽しもう」レポートにイベントを記録してくれました。
私の Lightning Talkでは 3ヶ月前の「Explorer: LivebookでTidyverse」の記事を再訪して、裏の考え方をちっょと説明して、結果がどのように変わったかを調べました。
ExplorerはRのTidyverseだぁ!
NxやMLの話題にはあんまり興味がありませんでしたが、Explorerを初めて聞いたときから「やっとElixirでデータサイエンスができる」と思いました。今まで R Studioやちょっと間にβ版のExploratoryでデータを分析しました。特にExploratoryを利用したときにデータフレームのパイプする方法を勉強になりました。これがTidyverseの基本機能でした。Elixirを初めて使ったときに、「あっ、Rのパイプと同じ使い方ですね。」と思いました。
では、話せるの日本語がちょっと弱いですので、概念をよく理解するために日本語でよく説明するRのTidyverse/dplyrを読むことをお勧めします。
ExplorerのHexdocsを見れば、dyplrと同じ動詞を利用する関数がいっぱいあります。しかも、*_with
が追加されているExplorer関数は拡張する幅が広がります。バージョンがまだ0.3.1ですが、早くRの成熟した機能を超えている感じがあります。
Explorerを使ってデータ分析をしましょう
これからの説明は「LiveView JP#10」のセッションとちょっと別れます。LTでは標準のLivebookとExplorerの出力を利用しましたが、2次回で2つのことを勉強になりました:
-
Explorer.DataFrame
をKino.DataTable
で表示すること
@RyoWakabayashi さん - ありがとうございました -
Download.from()
でウェブからデータを取得すること
@piacerex さん - ありがとうございました
これから、以上の項目がわかったらプレゼンをしましょう。
セットアップ
Livebookの新しいセッションを開始して、「Reconnect and setup」のセクションに以下をインストールしましょう。
Mix.install([
{:explorer, "~> 0.3.1"},
{:kino, "~> 0.6.2"},
{:download, "~> 0.0.4"}
])
データをインポートしましょう
alias Explorer.DataFrame, as: DF
alias Explorer.Series, as: S
unless File.exists?("balls-in-play-2022.tsv"), do:
{:ok, _filename} = Download.from(
"https://raw.githubusercontent.com/westbaystars/first-pitch-homeruns/main/balls-in-play-2022.tsv"
)
df = DF.from_csv!("balls-in-play-2022.tsv", delimiter: "\t")
-
Explorer.DataFrame
とExplorer.Series
をalias
して、略語を使用します。 - 「balls-in-play-2022.tsv」ファイルが存在してない場合、ドウンロードします。
- それから、TSVファイルを「タブ区切り」でデータフレームへロードします。
結果は:
#Explorer.DataFrame<
Polars[13453 x 10]
Player string ["Walker, Adam", "Walker, Adam", "Nakajima, Hiroyuki", "Nakajima, Hiroyuki",
"Ohshiro, Takumi", ...]
選手 string ["ウォーカー アダム", "ウォーカー アダム", "中島 宏之",
"中島 宏之", "大城 卓三", ...]
...
うん、テーブルで表示してみましょう。
…
df = DF.from_csv!("balls-in-play-2022.tsv", delimiter: "\t")
df
|> DF.to_rows()
|> Kino.DataTable.new()
データがあります! でも、列の順番が変ですよ。データフレームの順番は Player
、選手
、Team
, Date
, Game
, Pitch
、などでした。
ドキュメンテーションを読むと、DF.to_rows(df)
はマップを作ります。そのマップの順番を保存してないので、列がぐちゃぐちゃになってしまう。だから、to_rows()
を呼ぶ前に列のキーを集まって、Kino.DataTable.new(data, keys: [“Player”, “選手”, “Team”, …])
で読んでみましょう。
...
df = DF.from_csv!("balls-in-play-2022.tsv", delimiter: "\t")
keys = DF.names(df)
df
|> DF.to_rows()
|> Kino.DataTable.new(keys: keys)
良くなりました。
では、これから列名が変わるので、show_table(df)
の関数を作ってみましょう。
show_table = fn df ->
keys = DF.names(df)
df
|> DF.to_rows()
|> Kino.DataTable.new(keys: keys)
end
...
df = DF.from_csv!("balls-in-play-2022.tsv", delimiter: "\t")
df
|> show_table.()
よっし。これが使えるものです。
ホームランを集計しましょう
本塁打の数を集計したいので、この風にできます。
df
|> DF.filter_with(&S.equal(&1["Result"], "home-run"))
|> DF.group_by(["選手", "Team"])
|> DF.summarise(Pitches: [:count])
|> DF.rename(Pitches_count: "本塁打")
|> DF.arrange(desc: "本塁打")
|> show_table.()
言葉でパイプラインを通じすると:
- ホームランが打席の結果にフィルターして、
- 「選手」と「Team」で集まって、
- レコードの数を集計して (投球のフィールドを利用してますが、何でもいいです)、
- 集計した列名を「本塁打」にリネームして、
- 本塁打の多いから少ないへソートして、
- 表を表示しましょう
2022年 9月 24日現在の本塁打のリーダーと比べて、みんなは同値を確認しています。村上はすごいですね。
チームの列を日本語化にしましょう
表を見るとチームが英語の略語を利用しています。これらの12名をMapに入れたら、簡単に入れ替えれます。
teams = %{
"CHU" => "中", "HAN" => "阪", "HIR" => "広",
"YAK" => "ヤ", "YOK" => "De", "YOM" => "巨",
"LOT" => "ロ", "NIP" => "日", "ORX" => "オ",
"RAK" => "楽", "SEI" => "西", "SFT" => "ソ"
}
df
|> DF.filter_with(&S.equal(&1["Result"], "home-run"))
|> DF.mutate([チーム: &S.transform(&1["Team"], fn team -> Map.get(teams, team) end)])
|> DF.group_by(["選手", "チーム"])
|> DF.summarise(Pitches: [:count])
|> DF.rename(Pitches_count: "本塁打")
|> DF.arrange(desc: "本塁打")
|> show_table.()
DF.mutate
で「チーム
」という列を作成して、「Team
」の列にある値をteams
のマップを取得して、日本語に変更します。
もちろん、DF.group_by
に「Team
」から「チーム
」の列を表示したいですね。
「ヤ」の代わりに「ヤクルト」などを出力したい場合、マップを編集することができます。
一球目で誰が一番のホームランを打ってますか?
でも、「一球目に誰が一番多くの本塁打を打ってますか?」を調べたかった。そうするために、もう一つフィルターを頭に追加しましょう。
df
|> DF.filter_with(&S.equal(&1["Pitches"], 1))
|> DF.filter_with(&S.equal(&1["Result"], "home-run"))
…
3ヶ月前、阪神の大山と巨人の岡本がトップで 6つずつでした。この間、巨人の中田翔と村上が大山を超えて、岡本選手は+2本を打ちました。
注意点:
Explorer 0.3.xから DF.*_with
の関数を追加して、0.2.xの filter
、mutate
、などのコールバック付き関数は廃止されました。以上に「DF.filter_with
」を利用していますが、DF.mutate
がまだ廃止された関数を利用してます。だから、以下の注意を表示しています:
warning: mutate/2 with a callback is deprecated, please use mutate_with/2 instead
(explorer 0.3.1) lib/explorer/data_frame.ex:1298: anonymous fn/3 in Explorer.DataFrame.df_for_mutations/2
(elixir 1.14.0-rc.0) lib/enum.ex:2468: Enum."-reduce/3-lists^foldl/2-0-"/3
(explorer 0.3.1) lib/explorer/data_frame.ex:1295: Explorer.DataFrame.df_for_mutations/2
(explorer 0.3.1) lib/explorer/data_frame.ex:1284: Explorer.DataFrame.mutate/2
(elixir 1.14.0-rc.0) src/elixir.erl:298: anonymous fn/4 in :elixir.eval_external_handler/1
(stdlib 4.0.1) erl_eval.erl:748: :erl_eval.do_apply/7
問題はS.transform
の第2パラメータがコールバック関数をDF.mutate_with
に渡せないことです。いろいろ調べてみましたが、いい方法がまだ見つかりません。このような変更が便利ですので、未来のリリースでできなくなって困ります。
まとめ
まぁ、私はプロ野球のデータを分析しますが、セミナーにいる方はアクセスログなどの使い方がすぐ見えました。Kino.DataTable
とグラフ機能を利用すれば、もう少しでExploratoryのようなツールになれると思います。データ分析するためにElixirとExplorerの未来が明るいと思います。
では、これからも、よろしくお願い申し上げます。