Explorer 0.4.0: 一球本塁打

## ExplorerのTidyverseシリーズをアップグレード

ElixirのNx Explorerが出たから半年が過ごして、新しいバージョン(`0.4.0`)が出ました。@RyoWakabayashiさんの「[Queryを使うと、データ分析が更に捗る](https://qiita.com/RyoWakabayashi/items/053296b36d8744aca9ca)」記事を読んだから、私の「[Explorer: LivebookでTidyverseの再訪](https://qiita.com/westbaystars/items/604c716bc643f5a7e15d)」の記事も更新したくなりました。

では、始めましょう。

## セットアップ

新しいLivebookのセッションを開いて、「Notebook dependencies and setup」に以下のモジュールをインストールします。

<!-- livebook:{"force_markdown":true} -->

```elixir
Mix.install([
  {:kino, "~> 0.7.0"},
  {:explorer, "~> 0.4.0"},
  {:download, "~> 0.0.4"}
])
```

両方`:kino`と`:explorer`のバージョンが上げました。実行して、準備が終わりました。

## データをインポートする

次は、データをインポートして、データフレームにロードしましょう。

```elixir
alias Explorer.DataFrame, as: DF
alias Explorer.Series, as: S
alias Kino.DataTable
# Explorer.Queryのマクロのためにこの行が必要です。
require Explorer.DataFrame

# データファイルがローカルに保存されてない場合、読みます。
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<
  Polars[13453 x 10]
  Player string ["Walker, Adam", "Walker, Adam", "Nakajima, Hiroyuki", "Nakajima, Hiroyuki",
   "Ohshiro, Takumi", ...]
  選手 string ["ウォーカー アダム", "ウォーカー アダム", "中島 宏之",
   "中島 宏之", "大城 卓三", ...]
  Team string ["YOM", "YOM", "YOM", "YOM", "YOM", ...]
  Date string ["2022-04-23", "2022-04-23", "2022-04-23", "2022-04-23", "2022-04-23", ...]
  Game integer [71798, 71798, 71798, 71798, 71798, ...]
  Pitch string ["curve", "split-finger", "cutter", "split-finger", "cutter", ...]
  Pitches integer [2, 4, 3, 3, 5, ...]
  Velocity integer [nil, nil, 130, 134, 125, ...]
  ResultType string ["hit", "hit", "hit", "hit", "hit", ...]
  Result string ["single", "double", "single", "double", "single", ...]
>
```

今まで、何も変わってません。利用するデータフレームを読みました。表で表示しましょう。

```elixir
df
|> DataTable.new()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710912/b2651591-3909-66f4-334f-18548f4d41d1.png)

よっしゃ～。データをきれいに表示しています。

## ホームランを集計する

以前のバージョンでは`DF.filter_with(...)`が関数のコールバックパラメータがちょっと複雑でした。`:explorer 0.4.0`の`Query`のおかげでより読みやすくなります。

最初は`0.3.0`でこうしました:

```elixir
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.()
```

今度は:

```elixir
df =
  df
  |> DF.rename_with(&String.downcase/1)
  |> DF.filter(result == "home-run")
  |> DF.group_by(["選手", "team"])
  |> DF.summarise(本塁打: count(pitches))
  |> DF.arrange(desc: 本塁打)

df |> DataTable.new()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710912/6f6e55e2-43a8-4027-569d-df59a3113833.png)

それぞれのデータフレームの関数が`Query`パラメータを取得して、マクロでシリーズ(`Explorer.Series`)の関数を呼んでいます。この読み方のほうがだいぶ書きやすくて、読みやすくなりました。

でも、注意点があります。

```elixir
df
|> DF.rename_with(&String.downcase/1)
|> DF.filter(result == "home-run")
...
```

`Result`という列名が変数に使えなさそうです。小文字の`result`に変更するとマクロがうまく動いています。しないと、「型が違います」エラーがありました。

`DF.group_by`が変わってないので、そのままで行います。

`DF.summarise(本塁打: count(pitches))`は`Query`を利用して、シリーズの関数を書いて、変数のような列名をパラメータを取ります。この書き方がいいですよ。

最後に、`DF.arrange(desc: 本塁打)`では「本塁打」にクォートが必要なくなりました。そうです、`Query`のパラメータです。先頭文字が大文字がなくて、漢字を使えそうですよ!

## チーム名を漢字に変更しましょう

以前、チームを日本語に`mutate`するためにElixirのマップを利用することができました。

```elixir
teams = %{
 "CHU" => "中", "HAN" => "阪", "HIR" => "広",
 "YAK" => "ヤ", "YOK" => "De", "YOM" => "巨",
 "LOT" => "ロ", "NIP" => "日", "ORX" => "オ",
 "RAK" => "楽", "SEI" => "西", "SFT" => "ソ"
}

...
|> DF.mutate([チーム: &S.transform(&1["Team"], fn team -> Map.get(teams, team) end)])
|> DF.group_by(["選手", "チーム"])
```

パイプラインに`mutate`を挿入して、「Team」から「チーム」の列で組み合わせました。

でも、もう`Explorer.Backend.LazySeries`の関数(例えば`mutate`, `filter`, `transform`)で`Enum`, `Map`, などが使えなくなりました。どうすればElixirのマップを利用して`mutate`ができると悩みました。

色々試してみて、やっとできました。必要なことはデータフレームから列を`pull`して、そのシリーズを使って、データフレームに`put`して、データフレームを戻す関数を作りました。何でも列をリマップすることができます。

```elixir
teams = %{
  "CHU" => "中",  "HAN" => "阪",  "HIR" => "広",
  "YAK" => "ヤ",  "YOK" => "De",  "YOM" => "巨",
  "LOT" => "ロ",  "NIP" => "日",  "ORX" => "オ",
  "RAK" => "楽",  "SEI" => "西",  "SFT" => "ソ"
}

mutate_with_map = fn df, column, map, new_label ->
  new_series =
    DF.pull(df, column)
    |> S.transform(fn key -> Map.get(map, key) end)

  DF.put(df, new_label, new_series)
end

df
|> mutate_with_map.("team", teams, :チーム)
|> DF.select(["選手", "チーム", "本塁打"])
|> DataTable.new()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710912/b2cc925d-b29a-1d02-c315-e140cfce870e.png)

前より作業が必要ですが、まだわかりやすいと思います。

## まとめ

大体3ヶ月づつでExplorerが更新しています。いつも新しいバージョンが良くなっています。`0.3.0`のときに「warning: mutate/2 with a callback is deprecated, please use mutate_with/2 instead」を心配しましたが、自分の関数でパイプラインで続ける方法を研究してよかった。

3月後でもっと強くなると信じてます。