5. 複数ビューの構成#

複数のデータフィールドを可視化する際に、xycolorsizeshapeなど、利用可能な視覚的エンコーディングチャネルをできるだけ多く使用したくなるかもしれません。しかし、エンコーディングチャネルの数が増えると、チャートはすぐに煩雑で読みづらくなります。一つのチャートを「過剰に負荷」する代わりに、_複数のチャートを構成_して比較を迅速に行えるようにする方法があります。

このノートブックでは、_複数ビューの構成_に関するさまざまな操作を検討します:

  • layer:互換性のあるチャートを直接重ねて配置する、

  • facet:データを複数のチャートに分割し、行または列に整理する、

  • concatenate:任意のチャートを共通のレイアウト内に配置する、

  • repeat:基本的なチャート仕様を複数のデータフィールドに適用する。

その後、これらの操作が_ビュー構成代数_を形成する方法を見ていきます。この代数を組み合わせて、さまざまな複雑な複数ビュー表示を構築することができます。

このノートブックは、データ可視化カリキュラム の一部です。

import pandas as pd
import altair as alt

5.1. 天気データ#

アメリカの都市、シアトルとニューヨークの天気統計を可視化します。データセットを読み込み、最初の10行と最後の10行を確認してみましょう:

weather = 'https://cdn.jsdelivr.net/npm/vega-datasets@1/data/weather.csv'
df = pd.read_csv(weather)
df.head(10)
location date precipitation temp_max temp_min wind weather
0 Seattle 2012-01-01 0.0 12.8 5.0 4.7 drizzle
1 Seattle 2012-01-02 10.9 10.6 2.8 4.5 rain
2 Seattle 2012-01-03 0.8 11.7 7.2 2.3 rain
3 Seattle 2012-01-04 20.3 12.2 5.6 4.7 rain
4 Seattle 2012-01-05 1.3 8.9 2.8 6.1 rain
5 Seattle 2012-01-06 2.5 4.4 2.2 2.2 rain
6 Seattle 2012-01-07 0.0 7.2 2.8 2.3 rain
7 Seattle 2012-01-08 0.0 10.0 2.8 2.0 sun
8 Seattle 2012-01-09 4.3 9.4 5.0 3.4 rain
9 Seattle 2012-01-10 1.0 6.1 0.6 3.4 rain
df.tail(10)
location date precipitation temp_max temp_min wind weather
2912 New York 2015-12-22 4.8 15.6 11.1 3.8 fog
2913 New York 2015-12-23 29.5 17.2 8.9 4.5 fog
2914 New York 2015-12-24 0.5 20.6 13.9 4.9 fog
2915 New York 2015-12-25 2.5 17.8 11.1 0.9 fog
2916 New York 2015-12-26 0.3 15.6 9.4 4.8 drizzle
2917 New York 2015-12-27 2.0 17.2 8.9 5.5 fog
2918 New York 2015-12-28 1.3 8.9 1.7 6.3 snow
2919 New York 2015-12-29 16.8 9.4 1.1 5.3 fog
2920 New York 2015-12-30 9.4 10.6 5.0 3.0 fog
2921 New York 2015-12-31 1.5 11.1 6.1 5.5 fog

複数ビューの表示を作成し、各都市内および都市間の天気を調べてみましょう。

5.2. レイヤー#

複数のチャートを組み合わせる最も一般的な方法の1つは、マークをレイヤーとして重ねることです。基盤となるスケールドメインが互換性がある場合、それらを結合して_共有軸_を形成できます。一方で、xまたはyのエンコーディングが互換性がない場合は、代わりに_二重軸チャート_を作成し、別々のスケールと軸を使用してマークを重ねることができます。

5.2.1. 共有軸#

まず、各月ごとの平均最低気温と平均最高気温をプロットしてみましょう:

alt.Chart(weather).mark_area().encode(
  alt.X('month(date):T'),
  alt.Y('average(temp_max):Q'),
  alt.Y2('average(temp_min):Q')
)

このプロットでは、データ全体を通じた各月の気温範囲が示されています。しかし、これはシアトルとニューヨークの測定値を集計しているため、かなり誤解を招きやすいものです!

データを場所ごとに分割するために、色のエンコーディングを使用し、重なるエリアを考慮してマークの透明度を調整してみましょう:

alt.Chart(weather).mark_area(opacity=0.3).encode(
  alt.X('month(date):T'),
  alt.Y('average(temp_max):Q'),
  alt.Y2('average(temp_min):Q'),
  alt.Color('location:N')
)

シアトルはより温暖で、冬は暖かく、夏は涼しいことがわかります。

この場合、単にエリアマークを色で分割することで、特別な機能を持たないレイヤードチャートを作成しました。上記のチャートでは気温範囲が示されていますが、範囲の中央部分を強調したい場合もあります。

平均気温の中央値を示すラインチャートを作成してみましょう。calculate変換を使用して、日々の最低気温と最高気温の中央値を計算します:

alt.Chart(weather).mark_line().transform_calculate(
  temp_mid='(+datum.temp_min + +datum.temp_max) / 2'
).encode(
  alt.X('month(date):T'),
  alt.Y('average(temp_mid):Q'),
  alt.Color('location:N')
)

補足: calculate変換内で+datum.temp_minが使用されている点に注意してください。データを特別なパース指示なしでCSVファイルから直接読み込んでいるため、気温の値が内部的に文字列として表現されている可能性があります。値の前に+を付けることで、それを数値として扱うよう強制します。

次に、範囲エリアの上に中央値のラインを重ねることで、これらのチャートを組み合わせたいと思います。chart1 + chart2という構文を使用して、新しいレイヤードチャートを作成できます。この場合、chart1が最初のレイヤーとなり、chart2がその上に描画される2番目のレイヤーになります:

tempMinMax = alt.Chart(weather).mark_area(opacity=0.3).encode(
  alt.X('month(date):T'),
  alt.Y('average(temp_max):Q'),
  alt.Y2('average(temp_min):Q'),
  alt.Color('location:N')
)

tempMid = alt.Chart(weather).mark_line().transform_calculate(
  temp_mid='(+datum.temp_min + +datum.temp_max) / 2'
).encode(
  alt.X('month(date):T'),
  alt.Y('average(temp_mid):Q'),
  alt.Color('location:N')
)

tempMinMax + tempMid

これで複数のレイヤーを持つプロットが完成しました!しかし、y軸のタイトル(情報量が多いものの)が少し長く扱いにくくなっています…

プロットを整理するために軸をカスタマイズしてみましょう。レイヤーの1つにカスタム軸タイトルを設定すると、そのタイトルがすべてのレイヤーで共有軸タイトルとして自動的に使用されます:

tempMinMax = alt.Chart(weather).mark_area(opacity=0.3).encode(
  alt.X('month(date):T', title=None, axis=alt.Axis(format='%b')),
  alt.Y('average(temp_max):Q', title='Avg. Temperature °C'),
  alt.Y2('average(temp_min):Q'),
  alt.Color('location:N')
)

tempMid = alt.Chart(weather).mark_line().transform_calculate(
  temp_mid='(+datum.temp_min + +datum.temp_max) / 2'
).encode(
  alt.X('month(date):T'),
  alt.Y('average(temp_mid):Q'),
  alt.Color('location:N')
)

tempMinMax + tempMid

両方のレイヤーにカスタム軸タイトルを設定するとどうなるでしょうか?上記のコードを変更して確かめてみてください…

上記では+演算子を使用しましたが、これはAltairのlayerメソッドの便利な略記法です。layerメソッドを直接使用して、同じレイヤードチャートを生成することもできます:

alt.layer(tempMinMax, tempMid)

レイヤーへの入力順序が重要であることに注意してください。後のレイヤーは前のレイヤーの上に描画されます。上記のセルでチャートの順序を入れ替えてみてください。何が起こるでしょうか?(ヒント:lineマークの色に注目してください。)

5.2.2. 二重軸チャート#

シアトルは雨が多い街として有名です。それは本当なのでしょうか?

降水量と気温を並べて見てみましょう。まず、シアトルの月ごとの平均降水量を示す基本プロットを作成してみましょう:

alt.Chart(weather).transform_filter(
  'datum.location == "Seattle"'
).mark_line(
  interpolate='monotone',
  stroke='grey'
).encode(
  alt.X('month(date):T', title=None),
  alt.Y('average(precipitation):Q', title='Precipitation')
)

気温データとの比較を容易にするために、新しいレイヤードチャートを作成してみましょう。以下は、先ほどと同様にチャートをレイヤー化しようとした場合の結果です:

tempMinMax = alt.Chart(weather).transform_filter(
  'datum.location == "Seattle"'
).mark_area(opacity=0.3).encode(
  alt.X('month(date):T', title=None, axis=alt.Axis(format='%b')),
  alt.Y('average(temp_max):Q', title='Avg. Temperature °C'),
  alt.Y2('average(temp_min):Q')
)

precip = alt.Chart(weather).transform_filter(
  'datum.location == "Seattle"'
).mark_line(
  interpolate='monotone',
  stroke='grey'
).encode(
  alt.X('month(date):T'),
  alt.Y('average(precipitation):Q', title='Precipitation')
)

alt.layer(tempMinMax, precip)

降水量の値は、気温に比べてy軸の範囲をはるかに小さく使用しています!

デフォルトでは、レイヤードチャートは共有ドメインを使用します。つまり、x軸やy軸の値がすべてのレイヤーで結合され、共有範囲が決定されます。このデフォルト動作は、レイヤード値が同じ単位を持つ場合を前提としています。しかし、この例では、気温(摂氏)と降水量(インチ)を組み合わせているため、この前提は当てはまりません!

異なるy軸スケールを使用したい場合、Altairにレイヤー間でデータを*どのように解決するか(resolve)*を指定する必要があります。この場合、y軸のscaleドメインをsharedではなくindependent(独立)に設定したいと考えます。レイヤーオペレーターで作成されたChartオブジェクトには、resolve_scaleメソッドがあり、希望する解決方法を指定できます:

tempMinMax = alt.Chart(weather).transform_filter(
  'datum.location == "Seattle"'
).mark_area(opacity=0.3).encode(
  alt.X('month(date):T', title=None, axis=alt.Axis(format='%b')),
  alt.Y('average(temp_max):Q', title='Avg. Temperature °C'),
  alt.Y2('average(temp_min):Q')
)

precip = alt.Chart(weather).transform_filter(
  'datum.location == "Seattle"'
).mark_line(
  interpolate='monotone',
  stroke='grey'
).encode(
  alt.X('month(date):T'),
  alt.Y('average(precipitation):Q', title='Precipitation')
)

alt.layer(tempMinMax, precip).resolve_scale(y='independent')

これで、シアトルの最も雨の多い季節が秋(11月にピーク)であり、乾燥した夏と対照的であることがわかります。

上記のプロット仕様には、いくつかの冗長性があることに気づいたかもしれません。どちらのプロットも同じデータセットを使用し、シアトルのみを対象とする同じフィルターを適用しています。必要に応じて、コードを簡略化することができます。レイヤードチャートのトップレベルにデータとフィルター変換を提供すれば、個々のレイヤーは独自のデータ定義を持たない場合、そのデータを継承します:

tempMinMax = alt.Chart().mark_area(opacity=0.3).encode(
  alt.X('month(date):T', title=None, axis=alt.Axis(format='%b')),
  alt.Y('average(temp_max):Q', title='Avg. Temperature °C'),
  alt.Y2('average(temp_min):Q')
)

precip = alt.Chart().mark_line(
  interpolate='monotone',
  stroke='grey'
).encode(
  alt.X('month(date):T'),
  alt.Y('average(precipitation):Q', title='Precipitation')
)

alt.layer(tempMinMax, precip, data=weather).transform_filter(
  'datum.location == "Seattle"'
).resolve_scale(y='independent')

二重軸チャートは便利な場合もありますが、_誤解を招きやすい_傾向があります。これは、異なる単位や軸スケールが比較できない可能性があるためです。可能であれば、異なるデータフィールドを共通の単位にマッピングする変換を検討してください。例えば、分位数(quantiles)や相対的な割合変化を示す方法があります。

5.3. ファセット#

ファセット(Faceting) は、データセットをグループに分割し、各グループごとに個別のプロットを作成する手法です。以前のノートブックでは、rowおよびcolumnエンコーディングチャネルを使用してファセットチャートを作成する方法を学びました。まずはそれらのチャネルを復習し、それからそれらがより一般的なfacet演算子のインスタンスであることを示します。

まず、シアトルの最高気温値の基本的なヒストグラムを作成してみましょう:

alt.Chart(weather).mark_bar().transform_filter(
  'datum.location == "Seattle"'
).encode(
  alt.X('temp_max:Q', bin=True, title='Temperature (°C)'),
  alt.Y('count():Q')
)

この気温プロファイルは、その日の天候(霧雨、霧、雨、雪、晴れなど)によってどのように変化するのでしょうか?

columnエンコーディングチャネルを使用してデータを天候タイプでファセットしてみましょう。また、colorを冗長エンコーディングとして使用し、カスタマイズしたカラーレンジを適用します:

colors = alt.Scale(
  domain=['drizzle', 'fog', 'rain', 'snow', 'sun'],
  range=['#aec7e8', '#c7c7c7', '#1f77b4', '#9467bd', '#e7ba52']
)

alt.Chart(weather).mark_bar().transform_filter(
  'datum.location == "Seattle"'
).encode(
  alt.X('temp_max:Q', bin=True, title='Temperature (°C)'),
  alt.Y('count():Q'),
  alt.Color('weather:N', scale=colors),
  alt.Column('weather:N')
).properties(
  width=150,
  height=150
)

予想通り、珍しい雪の日は最も寒い気温に集中しており、その次に雨の日や霧の日が続きます。晴れの日はより暖かく、シアトルのイメージとは異なり、最も多い日数を占めています。しかし、シアトルの住民なら誰でも知っているように、霧雨は気温に関係なく時折やってきます!

チャート定義でのrowおよびcolumnエンコーディングチャネルに加えて、基本的なチャート定義を取り、明示的なfacet演算子を使用してファセットを適用することもできます。

上記のチャートを再作成してみましょう。ただし、今回はfacetを使用します。同じ基本的なヒストグラム定義を使用しますが、データソース、フィルター変換、列チャネルを削除します。その後、facetメソッドを呼び出し、データを渡して、weatherフィールドに基づいて列にファセットするよう指定します。facetメソッドはrowcolumnの両方の引数を受け入れ、これらを組み合わせてファセットされたプロットの2Dグリッドを作成することができます。

最後に、フィルター変換を追加し、トップレベルのファセットチャートに適用します。以前と同様にヒストグラム定義にフィルター変換を適用することもできますが、それはわずかに効率が劣ります。各ファセットセル内で「New York」の値をフィルターアウトするのではなく、ファセットチャートにフィルターを適用することで、Vega-Liteにファセット分割の前に値をあらかじめフィルターアウトできることを知らせることができます。

colors = alt.Scale(
  domain=['drizzle', 'fog', 'rain', 'snow', 'sun'],
  range=['#aec7e8', '#c7c7c7', '#1f77b4', '#9467bd', '#e7ba52']
)

alt.Chart().mark_bar().encode(
  alt.X('temp_max:Q', bin=True, title='Temperature (°C)'),
  alt.Y('count():Q'),
  alt.Color('weather:N', scale=colors)
).properties(
  width=150,
  height=150
).facet(
  data=weather,
  column='weather:N'
).transform_filter(
  'datum.location == "Seattle"'
)

上記の追加コードを考慮すると、なぜ明示的なfacet演算子を使用する必要があるのでしょうか?基本的なチャートの場合、可能であればcolumnrowエンコーディングチャネルを使用するべきです。しかし、facet演算子を明示的に使用することが便利になる場合があります。それは、レイヤードチャートのような構成されたビューをファセット化したい場合です。

以前のレイヤード温度プロットを再訪してみましょう。ニューヨークとシアトルのデータを同じプロットにプロットする代わりに、別々のファセットに分けてみます。個々のチャート定義は以前とほとんど同じで、1つのエリアチャートと1つのラインチャートです。唯一の違いは、今回はデータをチャートコンストラクタに直接渡さず、後でfacet演算子に渡すまで待つことです。チャートを以前と同様にレイヤー化し、その後レイヤードチャートオブジェクトに対してfacetを呼び出し、データを渡してlocationフィールドに基づくcolumnファセットを指定します:

tempMinMax = alt.Chart().mark_area(opacity=0.3).encode(
  alt.X('month(date):T', title=None, axis=alt.Axis(format='%b')),
  alt.Y('average(temp_max):Q', title='Avg. Temperature (°C)'),
  alt.Y2('average(temp_min):Q'),
  alt.Color('location:N')
)

tempMid = alt.Chart().mark_line().transform_calculate(
  temp_mid='(+datum.temp_min + +datum.temp_max) / 2'
).encode(
  alt.X('month(date):T'),
  alt.Y('average(temp_mid):Q'),
  alt.Color('location:N')
)

alt.layer(tempMinMax, tempMid).facet(
  data=weather,
  column='location:N'
)

これまで見てきたファセットチャートでは、ファセットセル全体で同じ軸スケールドメインを使用しています。この共有スケールと軸を使用するデフォルト設定は、値を正確に比較するのに役立ちます。しかし、場合によっては、各チャートを独立してスケーリングしたい場合があります。例えば、セル内の値の範囲が大きく異なる場合です。

レイヤードチャートと同様に、ファセットチャートもプロット間でスケールや軸を独立して解決(resolve)することをサポートしています。resolve_axisメソッドを呼び出して、y軸をindependent(独立)に設定した場合に何が起こるかを見てみましょう:

tempMinMax = alt.Chart().mark_area(opacity=0.3).encode(
  alt.X('month(date):T', title=None, axis=alt.Axis(format='%b')),
  alt.Y('average(temp_max):Q', title='Avg. Temperature (°C)'),
  alt.Y2('average(temp_min):Q'),
  alt.Color('location:N')
)

tempMid = alt.Chart().mark_line().transform_calculate(
  temp_mid='(+datum.temp_min + +datum.temp_max) / 2'
).encode(
  alt.X('month(date):T'),
  alt.Y('average(temp_mid):Q'),
  alt.Color('location:N')
)

alt.layer(tempMinMax, tempMid).facet(
  data=weather,
  column='location:N'
).resolve_axis(y='independent')

上記のチャートはほとんど変わりませんが、シアトルのプロットには独自の軸が追加されました。

では、代わりにresolve_scaleを呼び出して、基盤となるスケールドメインを解決した場合はどうなるでしょうか?

tempMinMax = alt.Chart().mark_area(opacity=0.3).encode(
  alt.X('month(date):T', title=None, axis=alt.Axis(format='%b')),
  alt.Y('average(temp_max):Q', title='Avg. Temperature (°C)'),
  alt.Y2('average(temp_min):Q'),
  alt.Color('location:N')
)

tempMid = alt.Chart().mark_line().transform_calculate(
  temp_mid='(+datum.temp_min + +datum.temp_max) / 2'
).encode(
  alt.X('month(date):T'),
  alt.Y('average(temp_mid):Q'),
  alt.Color('location:N')
)

alt.layer(tempMinMax, tempMid).facet(
  data=weather,
  column='location:N'
).resolve_scale(y='independent')

今度は、異なる軸スケールドメインを持つファセットセルが表示されました。この場合、独立したスケールを使用するのは悪いアイデアのように見えます!ドメインはそれほど大きく異ならず、ニューヨークとシアトルが同じくらいの夏の最高気温を持つと誤解される可能性があります。

よくある格言を借りるなら、「できるからといって、するべきとは限らない」…

5.4. 連結#

ファセットは、データの別々の分割を示すスモールマルチプルプロットを作成します。しかし、同じデータセット(分割ではない)や、異なるデータセットを含むビューで構成された表示を作成したい場合もあります。

Altairは、任意のチャートを組み合わせて構成されたチャートを作成するための連結演算子を提供します。hconcat演算子(略記法として|)は水平連結を行い、vconcat演算子(略記法として&)は垂直連結を行います。

まず、これまで見たことのあるような、ニューヨークとシアトルの月ごとの平均最高気温を示す基本的なラインチャートを作成してみましょう:

alt.Chart(weather).mark_line().encode(
  alt.X('month(date):T', title=None),
  alt.Y('average(temp_max):Q'),
  color='location:N'
)

時間に沿った気温だけでなく、降水量や風速も比較したい場合はどうすればよいでしょうか?

3つのプロットからなる連結チャートを作成してみましょう。まず、3つのプロットで共有すべきすべての要素を含む「基本」チャート定義を作成します。その後、この基本チャートを修正して、temp_maxprecipitationwindフィールドのy軸エンコーディングが異なるカスタマイズされたバリエーションを作成します。それらをパイプ(|)略記演算子を使用して連結します:

base = alt.Chart(weather).mark_line().encode(
  alt.X('month(date):T', title=None),
  color='location:N'
).properties(
  width=240,
  height=180
)

temp = base.encode(alt.Y('average(temp_max):Q'))
precip = base.encode(alt.Y('average(precipitation):Q'))
wind = base.encode(alt.Y('average(wind):Q'))

temp | precip | wind

代わりに、パイプ|演算子の代わりにより明示的なalt.hconcat()メソッドを使用することもできます。上記のコードをhconcatを使用するように書き換えてみてください。

垂直連結は水平連結と同様に機能します。&演算子(またはalt.vconcatメソッド)を使用して、コードを水平配置ではなく垂直配置に変更してみてください。

最後に、水平連結と垂直連結を組み合わせることができます。例えば、(temp | precip) & windのように記述すると何が起こるか試してみてください。

補足:括弧の重要性に注意してください…括弧を削除すると何が起こるでしょうか?これらのオーバーロードされた演算子は依然としてPythonの演算子優先順位ルールに従うため、垂直連結(&)は水平連結(|)よりも優先されます!

後ほど詳しく説明しますが、連結演算子を使用すると、任意のチャートを組み合わせてマルチビューダッシュボードを作成できます!

5.5. リピート#

上記の連結演算子は非常に汎用的で、任意のチャートを構成することができます。しかし、上記の例はやや冗長です。同じようなチャートが3つありますが、それぞれを個別に定義し、連結する必要がありました。

1つまたは2つの変数だけが変更される場合、repeat演算子を使用すると、複数のチャートを簡単に作成するための便利なショートカットを提供します。自由変数を含むテンプレート仕様を指定すると、repeat演算子は、その変数への指定された割り当てごとにチャートを作成します。

上記の連結の例をrepeat演算子を使用して再作成してみましょう。この例では、チャート間で変化する唯一の要素は、yエンコーディングチャネルに使用されるデータフィールドの選択です。テンプレート仕様を作成するには、alt.repeat('column')というリピータ変数をy軸フィールドとして使用します。このコードは、columnリピータに割り当てられた変数を使用して、チャートを水平方向に繰り返し配置したいことを示しています(リピータはフィールド名のみを提供するため、フィールドデータ型はtype='quantitative'として別途指定する必要があります)。

その後、repeatメソッドを呼び出し、各列のデータフィールド名を渡します:

alt.Chart(weather).mark_line().encode(
  alt.X('month(date):T',title=None),
  alt.Y(alt.repeat('column'), aggregate='average', type='quantitative'),
  color='location:N'
).properties(
  width=240,
  height=180
).repeat(
  column=['temp_max', 'precipitation', 'wind']
)

繰り返しは、列(column)だけでなく行(row)でもサポートされています。上記のコードをcolumnではなくrowを使用するように変更するとどうなるでしょうか?

また、rowcolumnの繰り返しを組み合わせることもできます!探査的データ分析における一般的な可視化方法の1つに、散布図行列(Scatter Plot Matrix, SPLOM)があります。調査する変数のコレクションが与えられると、SPLOMはそれらの変数のすべてのペアワイズプロットのグリッドを提供し、潜在的な関連性を評価するのに役立ちます。

repeat演算子を使用して、temp_maxprecipitationwindフィールドのSPLOMを作成してみましょう。まず、x軸とy軸のデータフィールドの両方にリピータ変数を使用するテンプレート仕様を作成します。その後、repeatを呼び出し、rowcolumnの両方に使用するフィールド名の配列を渡します。Altairは直積(またはデカルト積)を生成し、繰り返しチャートの完全な空間を作成します:

alt.Chart().mark_point(filled=True, size=15, opacity=0.5).encode(
  alt.X(alt.repeat('column'), type='quantitative'),
  alt.Y(alt.repeat('row'), type='quantitative')
).properties(
  width=150,
  height=150
).repeat(
  data=weather,
  row=['temp_max', 'precipitation', 'wind'],
  column=['wind', 'precipitation', 'temp_max']
).transform_filter(
  'datum.location == "Seattle"'
)

これらのプロットを見ると、降水量と風速の間に強い関連性は見られませんが、極端な風や降水イベントは似たような気温範囲(約5〜15°C)で発生していることがわかります。ただし、この観察結果は特に驚くべきものではありません。ファセットセクションの冒頭で示したヒストグラムを再確認すると、5〜15°Cの最高気温の日が最も一般的に発生していることが明らかです。

上記のコードを変更して、チャートの繰り返しについてより深く理解してみましょう。例えば、別の変数(temp_min)をSPLOMに追加してみてください。また、repeat演算子のrowまたはcolumnパラメータでフィールド名の順序を変更するとどうなるか試してみてください。

最後に、repeat演算子が提供する利便性を本当に理解するために、hconcatvconcatだけを使用して上記のSPLOMを再現する方法を想像してみてください!

5.6. ビュー構成代数#

これらの構成演算子、すなわちlayerfacetconcatrepeatを組み合わせることで、ビュー構成代数を形成します。これらの演算子を組み合わせることで、さまざまな複数ビューの可視化を構築できます。

例として、2つの基本的なチャートを作成してみましょう。1つはヒストグラム、もう1つはグローバル平均を示す単純な線(単一のruleマーク)です。

basic1 = alt.Chart(weather).transform_filter(
  'datum.location == "Seattle"'
).mark_bar().encode(
  alt.X('month(date):O'),
  alt.Y('average(temp_max):Q')
)

basic2 = alt.Chart(weather).transform_filter(
  'datum.location == "Seattle"'
).mark_rule(stroke='firebrick').encode(
  alt.Y('average(temp_max):Q')
)

basic1 | basic2

次に、これらの2つのチャートをlayer演算子を使用して組み合わせます。その後、そのレイヤードチャートをrepeat演算子を使用して繰り返し表示し、複数のフィールドのヒストグラムに平均値を重ねたものを示します:

alt.layer(
  alt.Chart().mark_bar().encode(
    alt.X('month(date):O', title='Month'),
    alt.Y(alt.repeat('column'), aggregate='average', type='quantitative')
  ),
  alt.Chart().mark_rule(stroke='firebrick').encode(
    alt.Y(alt.repeat('column'), aggregate='average', type='quantitative')
  )
).properties(
  width=200,
  height=150
).repeat(
  data=weather,
  column=['temp_max', 'precipitation', 'wind']
).transform_filter(
  'datum.location == "Seattle"'
)

上記の可視化のモデルは、多ビュー構成演算子に焦点を当てると以下のようになります:

repeat(column=[...])
|- layer
   |- basic1
   |- basic2

これから、すべての演算子を適用し、シアトルの天候に関する概要を示す最終的なダッシュボードを作成してみましょう。以前のセクションで作成したSPLOMとファセット化されたヒストグラム表示を、上記の繰り返しヒストグラムと組み合わせます:

splom = alt.Chart().mark_point(filled=True, size=15, opacity=0.5).encode(
  alt.X(alt.repeat('column'), type='quantitative'),
  alt.Y(alt.repeat('row'), type='quantitative')
).properties(
  width=125,
  height=125
).repeat(
  row=['temp_max', 'precipitation', 'wind'],
  column=['wind', 'precipitation', 'temp_max']
)

dateHist = alt.layer(
  alt.Chart().mark_bar().encode(
    alt.X('month(date):O', title='Month'),
    alt.Y(alt.repeat('row'), aggregate='average', type='quantitative')
  ),
  alt.Chart().mark_rule(stroke='firebrick').encode(
    alt.Y(alt.repeat('row'), aggregate='average', type='quantitative')
  )
).properties(
  width=175,
  height=125
).repeat(
  row=['temp_max', 'precipitation', 'wind']
)

tempHist = alt.Chart(weather).mark_bar().encode(
  alt.X('temp_max:Q', bin=True, title='Temperature (°C)'),
  alt.Y('count():Q'),
  alt.Color('weather:N', scale=alt.Scale(
    domain=['drizzle', 'fog', 'rain', 'snow', 'sun'],
    range=['#aec7e8', '#c7c7c7', '#1f77b4', '#9467bd', '#e7ba52']
  ))
).properties(
  width=115,
  height=100
).facet(
  column='weather:N'
)

alt.vconcat(
  alt.hconcat(splom, dateHist),
  tempHist,
  data=weather,
  title='Seattle Weather Dashboard'
).transform_filter(
  'datum.location == "Seattle"'
).resolve_legend(
  color='independent'
).configure_axis(
  labelAngle=0
)

このダッシュボードの完全な構成モデルは以下のようになります:

vconcat
|- hconcat
|  |- repeat(row=[...], column=[...])
|  |  |- splom base chart
|  |- repeat(row=[...])
|     |- layer
|        |- dateHist base chart 1
|        |- dateHist base chart 2
|- facet(column='weather')
   |- tempHist base chart

ふぅ! このダッシュボードには、レイアウトを改善するためのいくつかのカスタマイズも含まれています:

  • チャートのwidthおよびheightプロパティを調整して、レイアウトを整え、全体の可視化が画面に収まるようにしています。

  • resolve_legend(color='independent')を追加して、カラーレジェンドが温度ごとの色分けされたヒストグラムに直接関連付けられるようにしています。これを行わないと、レジェンドがダッシュボード全体に関連付けられてしまいます。

  • configure_axis(labelAngle=0)を使用して、軸ラベルが回転しないようにしています。これにより、SPLOM内の散布図や右側の月別ヒストグラムの間で適切な配置が確保されます。

これらの調整を削除または変更して、ダッシュボードのレイアウトがどのように反応するか試してみてください!

このダッシュボードは、他の場所のデータや別のデータセットを表示するために再利用できます。ダッシュボードを更新して、シアトルではなくニューヨークの天候パターンを表示してみてください。

5.7. まとめ#

複数ビューの構成に関する詳細情報(サブプロット間の間隔やヘッダーラベルの制御など)については、Altair Compound Charts ドキュメントを参照してください。

複数のビューを構成する方法を見た今、それらを実際に活用する準備が整いました。データを静的に表示するだけでなく、複数のビューを使用することで多次元のインタラクティブな探索が可能になります。たとえば、_リンクされた選択_を使用すると、一方のビューでポイントをハイライトして、他のビューで対応する値がハイライトされる様子を確認できます。

次のノートブックでは、インタラクティブ選択を個々のプロットや複数ビューの構成でどのように作成するかを詳しく調べます。