Elixir の dbg を Livebook で遊び倒す

## はじめに

先日開催された LiveView JP#10　で @piacerex さんが披露してくださった

dbg を Livebook 上で動かすデモが面白かったので、

自分でも色々動かしてみました

https://qiita.com/torifukukaiou/items/65cb4fa0b7875d5e4426

## 実行環境

- macOS Monterey 12.5.1
- Elixir 1.14.0
- erlang 25.0.4

## 準備

最新版を動かすため、 Livebook のリポジトリをクローンしてきてローカルで実行します

https://github.com/livebook-dev/livebook

```bash
git clone https://github.com/livebook-dev/livebook.git
cd livebook
```

私のように asdf を使っている人は以下のようにして Elixir 1.14 以上を使うようにします
それ以外の人は何らかの手段で Elixir 1.14 以上をインストールしてください

```bash
asdf install elixir 1.14.0-otp-25
asdf local elixir 1.14.0-otp-25
asdf install erlang 25.0.4
asdf local erlang 25.0.4
```

Livebook を起動します

```bash
mix deps.get --only prod
MIX_ENV=prod mix phx.server
```

`[Livebook] Application running at http://localhost:8080/?token=...` のように URL が表示されたら、
ブラウザで開いてください

こんな感じの画面が開きます

![スクリーンショット 2022-09-30 17.40.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/8b51ac5a-f90a-2b99-f8e8-f8bac55f06ff.png)

右上 New notebook をクリックし、新しいノートブックを開けば準備完了です

![スクリーンショット 2022-09-30 17.43.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/85751035-145b-3e14-87af-7f7408069d14.png)

## パッケージのインストール

一番上（Section の上）のセルに以下の内容を貼り付けで実行してください

kino を GitHub からインストールしているのがミソです

```elixir
Mix.install([
  {:kino, "~> 0.6", github: "livebook-dev/kino", branch: "main", override: true},
  {:kino_vega_lite, "~> 0.1.1"},
  {:nx, "~> 0.3"},
  {:evision, "~> 0.1", github: "cocoa-xu/evision", branch: "main"},
  {:explorer, "~> 0.3"},
  {:download, "~> 0.0"}
])
```

evision のインストールに10分くらいかかると思いますが、しばらく待ってください

最終的に `:ok` が出れば成功です

![スクリーンショット 2022-09-30 17.45.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/d87898fc-389b-b6f5-1fc2-4069b3da9da1.png)

## Elixir バージョン確認

`dbg` は Elixir 1.14 以上でないと動かないため、一応確認します

```elixir
System.version()
```

![スクリーンショット 2022-09-30 17.46.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/994fce56-767a-461b-d25b-433034ef4867.png)

## エイリアス

長い名前のものにエイリアスをつけておきます

```elixir
alias Explorer.DataFrame, as: DF
alias Explorer.Series, as: S
alias Evision, as: OpenCV
alias VegaLite, as: Vl
```

## ヘルパー

画像ダウンロード、画像表示用のヘルパーを作っておきます

```elixir
defmodule Helper do
  def download!(url, save_as) do
    unless File.exists?(save_as) do
      Download.from(url, path: save_as)
    end

    save_as
  end

  def show_image(mat) do
    OpenCV.imencode!(".png", mat)
    |> IO.iodata_to_binary()
    |> Kino.Image.new(:png)
  end

  def show_image_from_path(image_path) do
    image_path
    |> File.read!()
    |> Kino.Image.new(:jpeg)
  end
end
```

## 文字列操作

ここから `dbg` 祭りです

```elixir
"Elixir is cool!"
|> String.trim_trailing("!")
|> String.split()
|> Enum.reverse()
|> List.first()
|> dbg()
```

うまくいくと、このように表示されます

パイプ毎に ▷ から始まる行として表示されています

![スクリーンショット 2022-09-30 17.49.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/15421fca-a75c-a47e-90f4-0a0b943cc5d0.png)

各パイプの行をクリックすると、その時点での出力が表示されます

![string.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/8fa67ec5-cc80-1578-1d80-e8d8117b47e9.gif)

また、各パイプの右端トグルスイッチをオフにすると、そのパイプが実行されなかった場合の処理結果が表示されます

![string_toggle.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/978ccd40-08c6-d769-5315-3d5e435aeadf.gif)

## 数値計算

数値計算ならこんな感じになります

```elixir
[1, 2, 3]
|> Enum.map(&(&1 * 3))
|> Enum.map(&(&1 + 1))
|> Enum.map(&(&1 * 2))
|> dbg()
```

![enum.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/4173fb79-2a33-c0ea-0147-d0b303029da2.gif)

ドラッグ&ドロップで順番を入れ替えることも可能です

![drag.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/ac91c006-3877-14e4-68ab-3310b8475e50.gif)

## 行列演算

Nx を使った行列演算も

```elixir
[1, 2, 3]
|> Nx.tensor()
|> Nx.multiply(3)
|> Nx.tile([2, 2])
|> Nx.mean()
|> dbg()
```

![nx.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/f9e68e34-629e-d0e4-2a11-fe39b2a93468.gif)

## 画像処理

まず画像をダウンロードしてきます

```elixir
image_path = "dog.jpg"

"https://raw.githubusercontent.com/pjreddie/darknet/master/data/dog.jpg"
|> Helper.download!(image_path)
|> Helper.show_image_from_path()
```

![スクリーンショット 2022-09-30 18.03.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/79506437-6636-8b01-d3cf-ac61bd21d0c1.png)

Evision でこれをアレコレ編集します

```elixir
{:ok, move} =
  [
    [1, 0, 100],
    [0, 1, 50]
  ]
  |> Nx.tensor(type: {:f, 32})
  |> OpenCV.Nx.to_mat()

{:ok, rotation} = OpenCV.getRotationMatrix2D([512 / 2, 512 / 2], 90, 1)

image_path
|> OpenCV.imread!()
|> OpenCV.blur!([9, 9])
|> OpenCV.warpAffine!(move, [512, 512])
|> OpenCV.warpAffine!(rotation, [512, 512])
|> OpenCV.rectangle!([50, 10], [125, 60], [255, 0, 0])
|> OpenCV.ellipse!([300, 300], [100, 200], 30, 0, 360, [255, 255, 0], thickness: 3)
|> Helper.show_image()
|> dbg()
```

画像がコロコロ変わって面白いですね

![evision.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/0541d7d6-7bab-87d8-4bad-1208bb10fdd1.gif)

![drag_image.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/a37d336e-8e21-7fe8-5104-b9aa86cddf2b.gif)

## データ分析

次は Explorer で試してみましょう

アヤメのデータセットを取得してきます

```elixir
iris = Explorer.Datasets.iris()
```

![スクリーンショット 2022-09-30 18.09.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/de488542-0465-369c-c246-dca5142cf48c.png)

これを編集してみます

```elixir
iris
|> DF.filter_with(&S.equal(&1["species"], "Iris-virginica"))
|> DF.select(["sepal_length", "sepal_width", "petal_length", "petal_width"])
|> DF.arrange(desc: "sepal_width")
|> DF.rename(["ガクの長さ", "ガクの幅", "花弁の長さ", "花弁の幅"])
|> DF.to_rows()
|> Kino.DataTable.new()
|> dbg()
```

![exp.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/3bb7915d-f278-6c9e-2079-c8d3fa9615a5.gif)

## グラフ

最後にグラフです

散布図用関数を用意します

```elixir
get_values = fn df, col ->
  df
  |> DF.pull(col)
  |> S.to_list()
end

scatter = fn df, x_col, y_col ->
  x = get_values.(df, x_col)
  y = get_values.(df, y_col)
  class = get_values.(df, "species")

  Vl.new(width: 300, height: 300)
  |> Vl.data_from_values(x: x, y: y, class: class)
  |> Vl.mark(:point)
  |> Vl.encode_field(:x, "x",
    type: :quantitative,
    scale: [domain: [Enum.min(x), Enum.max(x)]],
    title: x_col
  )
  |> Vl.encode_field(:y, "y",
    type: :quantitative,
    scale: [domain: [Enum.min(y), Enum.max(y)]],
    title: y_col
  )
  |> Vl.encode_field(:color, "class", type: :nominal)
end
```

アヤメのデータを編集しながら散布図に出します

```elixir
iris
|> DF.filter_with(&S.greater(&1["sepal_length"], 3))
|> DF.filter_with(&S.greater(&1["petal_length"], 3))
|> DF.filter_with(&S.equal(&1["species"], "Iris-virginica"))
|> scatter.("sepal_length", "petal_length")
|> dbg()
```

![scatter.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/5859b2b4-9102-e212-8bcd-9002b4f5fb59.gif)

## まとめ

画像も表もグラフもコロコロ変えられるので、
画像処理やデータ分析が捗りそうですね！