Elixir Explorer データフレーム（DataFrame）の列名がクエリで使えない場合の回避方法

## はじめに

Explorer でデータフレームを操作する場合、クエリ（Query）が非常に便利です

https://qiita.com/RyoWakabayashi/items/053296b36d8744aca9ca

しかし、列名が「2022年」のような数字で始まる場合や、列名に「()」が含まれる場合など、クエリの中で使えないケースがあります

この記事ではその場合の回避方法について解説します

前回の記事でサラッとやっていましたが、自分のために備忘録

https://qiita.com/RyoWakabayashi/items/e8a5253e9f5f8305579b

今回の解説のために Livebook を使います

https://livebook.dev/

実装したノートブックはこちら



## セットアップ

Explorer と Kino をインストールします

```elixir
Mix.install([
  {:explorer, "~> 0.5"},
  {:kino, "~> 0.8"}
])
```

## 準備

記述を簡素にするため `alias` を実行し、クエリを使うために `import` を実行しておきます

```elixir
alias Explorer.DataFrame
alias Explorer.Series
import Explorer.DataFrame
```

## 通常の場合

まず、特に問題ない場合のクエリ実行例を示します

`x1`、`x2`、`x3` にそれぞれ1の倍数、2の倍数、3の倍数を入れてデータフレームにします

```elixir
df =
  DataFrame.new(%{
    "x1" => 1..5,
    "x2" => 2..10//2,
    "x3" => 3..15//3
  })

Kino.DataTable.new(df)
```

結果は以下のようになります

![スクリーンショット 2023-02-27 23.31.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/81e93e55-9e93-8883-9c3c-2c0c09e333ee.png)

このデータフレームから `x1` 列が 3 より大きい行を抽出する場合、以下のようなコードになります

```elixir
df
|> DataFrame.filter(x1 > 3)
|> Kino.DataTable.new()
```

![スクリーンショット 2023-02-27 23.58.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/329c2c56-5ff6-46a3-fffa-9e84861a3a2e.png)

`filter` 関数の中がクエリになっていて、 `x1` という列名をそのまま抽出条件に使っています

また、 `x2` の各値を2倍した `x4` 列を追加する場合は以下のようなコードになります

```elixir
df
|> DataFrame.mutate(x4: x2 * 2)
|> Kino.DataTable.new()
```

![スクリーンショット 2023-02-27 23.59.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/076be3ce-0464-5d7e-0113-8981247645d7.png)

`mutate` 関数の中がクエリです

新しい列名の `x4` もそのまま指定できるので、非常に直観的に記述できます

## 列名をそのまま使えない場合

問題は数字が先頭にある場合です

```elixir
df =
  DataFrame.new(%{
    "1x" => 1..5,
    "2x" => 2..10//2,
    "3x" => 3..15//3
  })

Kino.DataTable.new(df)
```

![スクリーンショット 2023-02-27 23.37.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/b44b343c-6daf-d8cd-0295-a8266e7262c9.png)

この場合、列名が `1x` なので、通常と同じようにクエリを実行しようとすると、、、

```elixir
df
|> DataFrame.filter(1x > 3)
|> Kino.DataTable.new()
```

以下のようなエラーが発生します

```elixir
** (SyntaxError) /tmp/col.livemd#cell:qbozgjkaf3lytbslxctb5qzbyyfhfhqp:2:21: invalid character "x" after number 1. If you intended to write a number, make sure to separate the number from the character (using comma, space, etc). If you meant to write a function name or a variable, note that identifiers in Elixir cannot start with numbers. Unexpected token: x
    |
  2 | |> DataFrame.filter(1x > 3)
    |  
```

> If you meant to write a function name or a variable, note that identifiers in Elixir cannot start with numbers

Elixir では関数名や変数を数字で始めることができません

というわけで、 `col` 関数を使ってエラーを回避します

`col` 関数に列名を文字列として渡すことで、列を指定できます

```elixir
df
|> DataFrame.filter(col("1x") > 3)
|> Kino.DataTable.new()
```

![スクリーンショット 2023-02-27 23.59.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/aa0ebf6e-1330-782b-5109-2f1afb7de006.png)

では、 `mutate` ではどうでしょう

```elixir
df
|> DataFrame.mutate(4x: col("2x") * 2)
|> Kino.DataTable.new()
```

このような書き方をした場合、やはり以下のようなエラーになります

```elixir
** (SyntaxError) /tmp/col.livemd#cell:sngf5fjbljfrobve2ccnoywbf52ncwfv:2:21: invalid character "x" after number 4. If you intended to write a number, make sure to separate the number from the character (using comma, space, etc). If you meant to write a function name or a variable, note that identifiers in Elixir cannot start with numbers. Unexpected token: x
    |
  2 | |> DataFrame.mutate(4x: col("2x") * 2)
    |                     ^
```

かと言って、以下のような書き方もできません

```elixir
df
|> DataFrame.mutate(col("4x"): col("2x") * 2)
|> Kino.DataTable.new()
```

この場合は以下のようなエラーになります

```elixir
** (SyntaxError) /tmp/col.livemd#cell:sngf5fjbljfrobve2ccnoywbf52ncwfv:2:30: unexpected token: ":" (column 30, code point U+003A)
    |
  2 | |> DataFrame.mutate(col("4x"): col("2x") * 2)
    |                              ^
```

`mutate` の引数はキーワードリストです

なので、以下のような書き換えが可能です

`x: 1, y: 2` -> `[{:x, 1}, {:y, 2}]`

これを利用して、 `mutate` の場合は以下のように書くことができます

```elixir
df
|> DataFrame.mutate([{"4x", col("2x") * 2}])
|> Kino.DataTable.new()
```

![スクリーンショット 2023-02-28 0.00.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/82037750-d954-a88a-a927-fe128d8e7148.png)

## 列名に変数を使う場合

最後に応用で列名に変数を使う場合です

クエリ内で変数を使う場合、 変数の先頭に `^` を付ける必要があります

文字列の中で変数展開して列名を指定する場合、文字列全体の前に `^` を付けます

```elixir
1..3
|> Enum.reduce(df, fn index, acc_df ->
  DataFrame.mutate(acc_df, [{^"-#{index}x", col(^"#{index}x") * -1}])
end)
|> Kino.DataTable.new()
```

![スクリーンショット 2023-02-27 23.57.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/d4bc0d7d-b2f3-bc5b-9457-14407d78b0d5.png)

## まとめ

少しややこしくはありますが、これで色々なケースに対応できますね