LoginSignup
16
3

More than 1 year has passed since last update.

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.DataFrameKino.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.DataFrameExplorer.Seriesaliasして、略語を使用します。
  • 「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()

image.png

データがあります! でも、列の順番が変ですよ。データフレームの順番は 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)

image.png

良くなりました。

では、これから列名が変わるので、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.()

image.png

よっし。これが使えるものです。

ホームランを集計しましょう

本塁打の数を集計したいので、この風にできます。

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」で集まって、
  • レコードの数を集計して (投球のフィールドを利用してますが、何でもいいです)、
  • 集計した列名を「本塁打」にリネームして、
  • 本塁打の多いから少ないへソートして、
  • 表を表示しましょう

その結果は:
image.png

2022年 9月 24日現在の本塁打のリーダーと比べて、みんなは同値を確認しています。村上はすごいですね。
image.png

チームの列を日本語化にしましょう

表を見るとチームが英語の略語を利用しています。これらの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」から「チーム」の列を表示したいですね。
image.png

「ヤ」の代わりに「ヤクルト」などを出力したい場合、マップを編集することができます。

一球目で誰が一番のホームランを打ってますか?

でも、「一球目に誰が一番多くの本塁打を打ってますか?」を調べたかった。そうするために、もう一つフィルターを頭に追加しましょう。

df
|> DF.filter_with(&S.equal(&1["Pitches"], 1))
|> DF.filter_with(&S.equal(&1["Result"], "home-run"))

image.png

3ヶ月前、阪神の大山と巨人の岡本がトップで 6つずつでした。この間、巨人の中田翔と村上が大山を超えて、岡本選手は+2本を打ちました。

注意点:

Explorer 0.3.xから DF.*_withの関数を追加して、0.2.xの filtermutate、などのコールバック付き関数は廃止されました。以上に「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の未来が明るいと思います。

では、これからも、よろしくお願い申し上げます。

16
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
3