Vega-Lite の View Composition (Elixir, Livebook)

**Livebook** 上で **Vega-Lite** の **View Composition** を試してみます。
**Vega-Lite** では単一のビューを生成するだけでなく、**facet** や **layer**、 **concatenate**、 **repeat** をサポートし、 レイヤー化やマルチ・ビュー表示を可能としています。

* **1. Faceting**
* **2. Layering**
* **3. Concatenation**
* **4. Repeating**
* **5. Resolution**

以下のドキュメントに従います。
[**Composing Layered & Multi-view Plots 公式ドキュメント**](https://vega.github.io/vega-lite/docs/composition.html)

**関連記事**
[VegaLite で埼玉県を切る( Elixir, Livebook) - Qiita](https://qiita.com/sand/items/a8b99ee8b68f49b5784a)
[Vega-Lite の View Composition (Elixir, Livebook) - Qiita](https://qiita.com/sand/items/f0e11ed907287be6436a)
[VegaLite の基礎 (Elixir, Livebook) -Qiita](https://qiita.com/sand/items/3433061b1966d6f8edb2)
[Livebook 事始め (Elixir) - Qiita](https://qiita.com/sand/items/6753c1782774b45daf6b)



まずは Livebook 上で必要なインストールを行っておきます。
```elixir:
Mix.install([
  {:vega_lite, "~> 0.1.6"},
  {:kino_vega_lite, "~> 0.1.4"},
  {:jason, "~> 1.2"}
])

alias VegaLite, as: Vl
```


# 1. Facet

**facet operator** を使った Vega-Lite の **Facet** の機能を見ていきます。**Data** をある **Field** でグループ分けをし、それぞれの **Data** の **subsets** のグラフを並べて表示してくれるような機能です。その **Field** 毎のグラフを比べることで、その **field** が与えている特性を認識できるようになります。
[Faceting a Plot into a Trellis Plot](https://vega.github.io/vega-lite/docs/facet.html)

:::note info
ファセットとは小平面の透明な宝石の表面に、多数作られたカット面の一つを指す。
ここではプロットをファセットし、Trellis（格子）プロットを作り出すことを考える。
Trellis（格子）プロット（または 小さな複数のプロット）は、同じデータの異なったサブセットを表示する、一連の似通ったプロットである。
:::

## 1-1. Row-Facet

**origin** (Europe, Japan, and USA) 毎の **Horsepower** の **histogram** を、各行 (**row**) に、表示します。

まずは **facet operator** を明記した書き方です。
```elixir:
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/data/cars.json")
|> Vl.facet(
  [row: [field: "Origin"]],
  Vl.new()
  |> Vl.mark(:bar)
  |> Vl.encode_field(:x, "Horsepower", type: :quantitative, bin: [maxbins: 15])
  |> Vl.encode(:y, aggregate: :count, type: :quantitative)
)
```

**facet operator** を明記せずに、**row channel** を指定すると **Vega-Lite** が自動的に上の **facet operator** の構文へコンバートしてくれます。ショートカットが可能です。
```elixir:
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/data/cars.json")
  |> Vl.mark(:bar)
  |> Vl.encode_field(:x, "Horsepower", type: :quantitative, bin: [maxbins: 15])
  |> Vl.encode(:y, aggregate: :count, type: :quantitative)
  |> Vl.encode_field(:row, "Origin")
```

どちらも下のグラフになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/bbf80124-d28b-b6e4-9ff1-1efc6fa63485.png)


**column channel** を指定すると横並びになります。
```elixir:
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/data/cars.json")
  |> Vl.mark(:bar)
  |> Vl.encode_field(:x, "Horsepower", type: :quantitative, bin: [maxbins: 15])
  |> Vl.encode(:y, aggregate: :count, type: :quantitative)
  |> Vl.encode_field(:column, "Origin")
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/8ec68320-3b06-2d2b-3e95-b887a4f74649.png)


## 1-2. Wrapped Facet

**facet channel** を指定するやり方です。 
```elixir:
Vl.new( name: "trellis_barley", height: [step: 12])
|> Vl.data_from_url("https://vega.github.io/vega-lite/data/barley.json")
|> Vl.mark(:point)
|> Vl.encode_field(:facet, "site", type: :ordinal, columns: 2,
                   sort: [op: "median", field: "yield"])
|> Vl.encode_field(:x, "yield", aggregate: :median, type: :quantitative,
                    scale: [zero: false])
|> Vl.encode_field(:y, "variety", type: :ordinal, sort: "-x")
|> Vl.encode_field(:color, "year", type: :nominal)
```

:::note info
**channel** はザックリ言えば、**座標** のようなものと考えられます。例えば Data を **2次元座標** で表せば異なる point でも同じ座標にプロットしてしまうところを、**3次元座標** で表せばキチンと異なる座標にプロットすることが可能なことがあります。当然 3次元の方がより多角的に Data を見ることができます。例えば **(x, y) 座標**の point に色を加え、 つまり**color軸** を設けて **(x, y, color) 座標** で表現することが可能です。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/c5181dc6-e718-e990-4246-0ca66cf1c027.png)


# 2. layer
**layer operator** は一つのグラフを、他のグラフに重ね合わせるものです。
[Layering views](https://vega.github.io/vega-lite/docs/layer.html)

まず2つのグラフを示し、それらを **layer operator** で重ね合わせてみましょう。

株価の推移グラフです。
```elixir:
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/data/stocks.csv")
|> Vl.mark(:line)
|> Vl.encode_field(:x, "date", type: :temporal)
|> Vl.encode_field(:y, "price", type: :quantitative)
|> Vl.encode_field(:color, "symbol", type: :nominal)
```


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/47c00d5b-80f1-806b-5a74-c6feeea018a5.png)


株価の平均のグラフです
```elixir:
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/data/stocks.csv")
|> Vl.mark(:rule)
|> Vl.encode_field(:y, "price", type: :quantitative, aggregate: :mean)
|> Vl.encode(:size, value: 2)
|> Vl.encode_field(:color, "symbol", type: :nominal)
```
**rule mark** は ひとつの data point を ひつつの line で表します。size で line の太さを指定します。 

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/b34134ae-2ddc-224d-7dc7-898123d33055.png)


２つのグラフを **layer operator** で重ね合わせます。

```elixir:
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/data/stocks.csv")
|> Vl.layers([
  Vl.new()
  |> Vl.mark(:line)
  |> Vl.encode_field(:x, "date", type: :temporal)
  |> Vl.encode_field(:y, "price", type: :quantitative)
  |> Vl.encode_field(:color, "symbol", type: :nominal),   # カンマ注意
  Vl.new()
  |> Vl.mark(:rule)
  |> Vl.encode_field(:y, "price", type: :quantitative, aggregate: :mean)
  |> Vl.encode(:size, value: 2)
  |> Vl.encode_field(:color, "symbol", type: :nominal)
])
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/0c4f2622-07fd-ad16-0a15-5cbd5e09e2c1.png)


# 3. Concat

**concat operator** でグラフを合成することができます。

* **hconcat** - 水平方向の合成
* **vconcat** - 垂直方向の合成
* **concat** - 一般的な合成 (wrappable)


```elixir:
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/data/weather.csv")
|> Vl.transform(filter: "datum.location === 'Seattle'")
|> Vl.concat(
  [
    Vl.new()
    |> Vl.mark(:bar)
    |> Vl.encode_field(:x, "date", time_unit: :month, type: :ordinal)
    |> Vl.encode_field(:y, "precipitation", aggregate: :mean),
    Vl.new()
    |> Vl.mark(:point)
    |> Vl.encode_field(:x, "temp_min", bin: true)
    |> Vl.encode_field(:y, "temp_max", bin: true)
    |> Vl.encode(:size, aggregate: :count)
  ],
  :horizontal
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/2b2800fb-38d3-8978-8eb1-4a1471149878.png)


# 4. Repeat 

**repeat operator** は **field 配列** のそれぞれの要素に対して、グラフを作り出すためのショートカットです。**Facet** のように複数のプロットを生成しますが、Facet とは違って **data set** の完全な複製を許します。data の **sub-set** ではなく **full-set** です。


```elixir:
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/data/weather.csv")
|> Vl.repeat(
  ["temp_max", "precipitation", "wind"],
  Vl.new()
  |> Vl.mark(:line)
  |> Vl.encode_field(:x, "date", time_unit: :month)
  |> Vl.encode_repeat(:y, :repeat, aggregate: :mean)   # y channel = repeated field.
  |> Vl.encode_field(:color, "location")
)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/8ef810eb-acff-d9d2-cd21-6ce3ce9bc4f7.png)



## 4-1. Multi-series Line Chart with Repeated Layers

**repeat** を **layer** と組み合わせて使い multi-series line chart を描くことができます。ここでは **repeater field** である data の値(datum)に **color** を map しています。

```elixir:
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/data/movies.json")
|> Vl.repeat(
  [layer: ["US Gross", "Worldwide Gross"]],
  Vl.new()
  |> Vl.mark(:line)
  |> Vl.encode_field(:x, "IMDB Rating", type: :quantitative, bin: true)
  |> Vl.encode_repeat(:y, :layer, aggregate: :mean, type: :quantitative,
                      title: "Mean of US and Worldwide Gross") 
  |> Vl.encode(:color, datum: [repeat: :layer], type: :nominal)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/b83a722b-129b-b763-32fa-3c5109d8ee21.png)


## 4-2. Repeated Histogram (Wrapped)



```elixir:
Vl.new(columns: 2)
|> Vl.repeat(
  ["Horsepower", "Miles_per_Gallon", "Acceleration", "Displacement"],
  Vl.new()
  |> Vl.data_from_url("https://vega.github.io/vega-lite/data/cars.json")
  |> Vl.mark(:bar)
  |> Vl.encode_repeat(:x, :repeat, bin: true)
  |> Vl.encode(:y, aggregate: :count)
  |> Vl.encode_field(:color, "Origin")
)
```



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/ec9ca68d-e48c-ca11-7d5b-dfed2481d243.png)




## 4-3. Scatterplot Matrix (SPLOM)


```elixir:
Vl.new()
|> Vl.data_from_url("https://vega.github.io/vega-lite/data/penguins.json")
|> Vl.repeat(
  [ row: ["Beak Length (mm)", "Beak Depth (mm)", "Flipper Length (mm)", "Body Mass (g)"],
  column: ["Body Mass (g)","Flipper Length (mm)","Beak Depth (mm)", "Beak Length (mm)"]],
  Vl.new(width: 110, height: 110)
  |> Vl.mark(:point)
  |> Vl.encode_repeat(:x, :column, type: :quantitative, scale: [zero: false])
  |> Vl.encode_repeat(:y, :row, type: :quantitative, scale: [zero: false])
  |> Vl.encode_field(:color, "Species", type: :nominal)
)
```

縦横のサイズを110にすることでギリギリ収まります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/83c9efcd-122b-245a-0b42-fa6088daf4ec.png)


# 5. Resolution

**Vega-Lite** は **scale domain** (color domain など)が、結合されるべきかどうか決定します。もしそうなら、その軸と凡例はマージされます。そうでなければ、独立していなければなりません。**Resolution** によってこの決定を行います。

２つの **repeated charts** に対して、それぞれ2つの独立した **color scales** を与えます。この時 **Vega-Lite** は自動的に2つのchartに対して異なる凡例(Legend)を生成します。
 
```elixir:
Vl.new()
|> Vl.repeat(
  [column: ["Origin", "Cylinders"]],
  Vl.new()
  |> Vl.data_from_url("https://vega.github.io/vega-lite/data/cars.json")
  |> Vl.mark(:point)
  |> Vl.encode_field(:x, "Horsepower", type: :quantitative)
  |> Vl.encode_field(:y, "Miles_per_Gallon", type: :quantitative)
  |> Vl.encode_repeat(:color, :column, type: :nominal)
)
|> Vl.resolve(:scale, color: :independent)
```

最後の **resolve** 行を independent で追加することで、2つのグラフの凡例が独立して表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/a481032f-5ba7-d1c0-7d26-a41d6f20e897.png)

最後の **resolve** 行を消すと、凡例(Legend) が一つになってしまいます。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/b8862a75-f2a9-d2d5-c97c-4b04a1406474.png)


今回は以上です。