1. Altairの紹介#

Altair は、Python向けの宣言型統計可視化ライブラリです。Altairは、幅広い統計グラフを迅速に作成するための強力かつ簡潔な可視化文法を提供します。

宣言型」とは、ループ処理や低レベルの描画コマンドなど、可視化を実装するための手順を指定するのではなく、データ、グラフィックマーク、エンコーディングチャネルといった、可視化に含めたい要素を高レベルで指定することを意味します。重要なポイントは、データフィールドと、x軸やy軸、色などの視覚的なエンコーディングチャネルとのリンクを宣言することです。プロットの詳細は自動的に処理されます。この宣言型プロットのアイデアを基に、簡潔な文法を用いて、単純なものから洗練されたものまで、幅広い可視化を作成できます。

Altairは、インタラクティブなグラフィックスの高レベル文法である Vega-Lite を基にしています。Altairは、Vega-Liteの仕様を JSON (JavaScript Object Notation) 形式で生成するための親しみやすいPython API (アプリケーションプログラミングインターフェース) を提供します。Jupyter Notebook、JupyterLab、Colabなどの環境では、この仕様を直接ウェブブラウザにレンダリングできます。AltairやVega-Liteの背景や基本概念についてさらに知りたい場合は、OpenVisConf 2017のVega-Liteプレゼンテーション動画 をご覧ください。

このノートブックでは、Altairでの可視化作成の基本的なプロセスを説明します。まず、Altairパッケージとその依存関係がインストールされていることを確認する必要があります(詳細はAltairインストールドキュメント を参照してください)。または、依存関係があらかじめインストールされているノートブック環境を使用する必要があります。

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

1.1. インポート#

まず、必要なライブラリをインポートする必要があります。データフレーム用のPandasと、可視化用のAltairです。

import pandas as pd
import altair as alt

1.2. レンダラー#

環境によっては、Altair用のレンダラーを指定する必要がある場合があります。JupyterLabJupyter Notebook、または Google Colab をインターネット接続が有効な状態で使用している場合、特に何も設定する必要はありません。それ以外の場合は、Altairチャートの表示に関するドキュメントを参照してください。

1.3. データ#

Altairでは、データはPandasのデータフレームを基に構築されます。データフレームは名前付きデータのの集合で構成されており、これをデータのフィールドと呼ぶこともあります。

Altairを使用する場合、データセットは通常データフレームとして提供されます。また、ネットワークでアクセス可能なデータセットを読み込むためにURLを指定することもできます。後述するように、データフレームの名前付き列は、Altairでのプロット作成において重要な要素となります。

私たちはしばしば、vega-datasets リポジトリのデータセットを使用します。これらのデータセットの一部は、直接Pandasのデータフレームとして利用可能です。

from vega_datasets import data  # vega_datasetsをインポート
cars = data.cars()              # carsデータをPandasデータフレームとして読み込む
cars.head()                     # 最初の5行を表示
Name Miles_per_Gallon Cylinders Displacement Horsepower Weight_in_lbs Acceleration Year Origin
0 chevrolet chevelle malibu 18.0 8 307.0 130.0 3504 12.0 1970-01-01 USA
1 buick skylark 320 15.0 8 350.0 165.0 3693 11.5 1970-01-01 USA
2 plymouth satellite 18.0 8 318.0 150.0 3436 11.0 1970-01-01 USA
3 amc rebel sst 16.0 8 304.0 150.0 3433 12.0 1970-01-01 USA
4 ford torino 17.0 8 302.0 140.0 3449 10.5 1970-01-01 USA

vega-datasetsコレクション内のデータセットは、URLを介してアクセスすることもできます:

data.cars.url
'https://cdn.jsdelivr.net/npm/vega-datasets@v1.29.0/data/cars.json'

データセットのURLは、Altairに直接渡すことができます(JSONやCSVなどのサポートされている形式の場合)。また、以下のようにPandasのデータフレームとして読み込むこともできます:

pd.read_json(data.cars.url).head() # JSONデータをデータフレームにロードする
Name Miles_per_Gallon Cylinders Displacement Horsepower Weight_in_lbs Acceleration Year Origin
0 chevrolet chevelle malibu 18.0 8 307.0 130.0 3504 12.0 1970-01-01 USA
1 buick skylark 320 15.0 8 350.0 165.0 3693 11.5 1970-01-01 USA
2 plymouth satellite 18.0 8 318.0 150.0 3436 11.0 1970-01-01 USA
3 amc rebel sst 16.0 8 304.0 150.0 3433 12.0 1970-01-01 USA
4 ford torino 17.0 8 302.0 140.0 3449 10.5 1970-01-01 USA

データ フレームの詳細、および Altair でプロットするために Pandas データ フレームを準備するための便利な変換については、Altair を使用したデータの指定 ドキュメントを参照してください。

1.3.1. 気象データ#

Altair での統計視覚化は、”tidy” データ フレームから始まります。ここでは、まず、特定の citymonth の平均降水量 (precip) を含む単純なデータ フレーム (df) を作成します :

df = pd.DataFrame({
    'city': ['Seattle', 'Seattle', 'Seattle', 'New York', 'New York', 'New York', 'Chicago', 'Chicago', 'Chicago'],
    'month': ['Apr', 'Aug', 'Dec', 'Apr', 'Aug', 'Dec', 'Apr', 'Aug', 'Dec'],
    'precip': [2.68, 0.87, 5.31, 3.94, 4.13, 3.58, 3.62, 3.98, 2.56]
})

df
city month precip
0 Seattle Apr 2.68
1 Seattle Aug 0.87
2 Seattle Dec 5.31
3 New York Apr 3.94
4 New York Aug 4.13
5 New York Dec 3.58
6 Chicago Apr 3.62
7 Chicago Aug 3.98
8 Chicago Dec 2.56

1.4. チャート オブジェクト#

Altair の基本的なオブジェクトは Chart で、データ フレームを 1 つの引数として受け取ります。

chart = alt.Chart(df)

これまで、Chart オブジェクトを定義し、上で生成した単純なデータ フレームを渡しました。チャートにデータを使って何かを実行するようにはまだ指示していません。

1.5. マークとエンコーディング#

チャートオブジェクトが用意できたら、次にデータをどのように可視化するかを指定します。まず、データを表現するために使用するグラフィカルなマーク(幾何学的な形状)を指定します。チャートオブジェクトのmark属性を、Chart.mark_*メソッドを使用して設定します。

例えば、Chart.mark_point()を使用してデータをポイントとして表示することができます:

alt.Chart(df).mark_point()

ここでは、データセット内の各行に対して1つのポイントが描画されますが、これらのポイントは位置がまだ指定されていないため、すべて重なっています。

ポイントを視覚的に分離するには、データセット内のフィールドをさまざまなエンコーディングチャネル(または単にチャネル)にマッピングする必要があります。例えば、データのcityフィールドをポイントのy軸位置を表すyチャネルにエンコードすることができます。これを指定するには、encodeメソッドを使用します:

alt.Chart(df).mark_point().encode(
  y='city',
)

encode()メソッドは、エンコーディングチャネル(例えば、xycolorshapesizeなど)と、データセット内のフィールド(フィールド名でアクセス可能)との間にキーと値のマッピングを構築します。Pandasデータフレームの場合、Altairは自動的に適切なデータ型を判断します。この場合、名義型(nominal type)が適用され、これは順序付けされていないカテゴリカルな値を表します。

これでデータを1つの属性で分離しましたが、各カテゴリ内で複数のポイントが重なっています。これらをさらに分離するために、'precip'フィールドをxチャネルにマッピングしてみましょう:

alt.Chart(df).mark_point().encode(
    x='precip',
    y='city'
)

シアトルは、最も雨が少ない月と最も雨が多い月の両方を持っています!

'precip'フィールドのデータ型も、Altairによって自動的に推測され、今回は量的(quantitative)型として扱われます(つまり、実数値としての数値)。グリッド線や適切な軸タイトルも自動的に追加されていることがわかります。

上記では、キーワード引数(例: x='precip')を使用してキーと値のペアを指定しましたが、Altairではエンコーディング定義の構築メソッドも提供されています。このメソッドは、alt.X('precip')という構文を使用します。この方法は、後述するように、エンコーディングに対してより多くのパラメータを指定する際に便利です。

alt.Chart(df).mark_point().encode(
    alt.X('precip'),
    alt.Y('city')
)

エンコーディングを指定する2つのスタイルは混在させることができます。例えば、x='precip', alt.Y('city')encode関数に有効な入力です。

上記の例では、各フィールドのデータ型はPandasデータフレーム内の型に基づいて自動的に推測されました。しかし、フィールド名に注釈を付けることで、Altairにデータ型を明示的に指定することもできます。

  • 'b:N'名義型(順序付けされていないカテゴリカルデータ)を示します。

  • 'b:O'順序型(ランク付けされたデータ)を示します。

  • 'b:Q'量的型(意味のある大きさを持つ数値データ)を示します。

  • 'b:T'時間型(日付/時間データ)を示します。

例えば、alt.X('precip:N')のように指定できます。

データ型を明示的に指定する必要があるのは、外部URLからデータを直接Vega-Liteに読み込む場合(Pandasを完全にスキップする場合)や、自動推測された型と異なる型を使用したい場合です。

precipを量的変数ではなく、名義型や順序型として扱った場合、上記のチャートはどうなると思いますか?コードを変更して試してみましょう!

データ型とエンコーディングチャネルについては、データ可視化カリキュラム の次のノートブックで詳しく説明します。

1.6. データ変換: 集計#

データの可視化方法に柔軟性を持たせるために、Altairにはデータの集計を行うための組み込みの構文があります。例えば、フィールド名とともに集計関数を指定することで、すべての値の平均を計算することができます:

alt.Chart(df).mark_point().encode(
    x='average(precip)',
    y='city'
)

各x軸のカテゴリ内で、対応する値の平均を反映した1つのポイントが表示されていることがわかります。

シアトルは本当にこれらの都市の中で平均降水量が最も少ないのでしょうか?(実際にそうです!)しかし、このプロットが誤解を招く可能性があるのはどのような点でしょうか?どの月が含まれているのか、降水量として何がカウントされているのかを考えてみましょう。

Altairは、さまざまな集計関数をサポートしています。例えば、count(件数)、min(最小値)、max(最大値)、average(平均値)、median(中央値)、およびstdev(標準偏差)などがあります。後のノートブックでは、集計、ソート、フィルタリング、新しい派生フィールドの作成(計算式を使用)を含むデータ変換について詳しく見ていきます。

1.7. マークタイプの変更#

集計された値を円形のポイントではなく、長方形のバーで表現したい場合があります。その場合、Chart.mark_pointChart.mark_barに置き換えることで実現できます:

alt.Chart(df).mark_bar().encode(
    x='average(precip)',
    y='city'
)

名義型フィールドay軸にマッピングされているため、結果は横向きの棒グラフになります。縦向きの棒グラフを作成するには、単純にxyのキーワードを入れ替えるだけです:

alt.Chart(df).mark_bar().encode(
    x='city',
    y='average(precip)'
)

1.8. 可視化のカスタマイズ#

Altair / Vega-Liteはデフォルトで可視化のプロパティを自動的に決定しますが、これらはメソッドを使用してカスタマイズ可能です。例えば、チャネルクラスのaxis属性を使用して軸タイトルを指定したり、scale属性を使用してスケールのプロパティを変更したりできます。また、Chart.mark_*メソッドのcolorキーワードに有効なCSSカラー文字列を指定することで、マークの色を設定することも可能です:

alt.Chart(df).mark_point(color='firebrick').encode(
  alt.X('precip', scale=alt.Scale(type='log'), axis=alt.Axis(title='Log-Scaled Values')),
  alt.Y('city', axis=alt.Axis(title='Category')),
)

後のモジュールでは、スケール、軸、凡例に関するさまざまなオプションを詳しく見ていき、カスタマイズされたチャートの作成方法を探ります。

1.9. 複数のビュー#

これまでに見たように、AltairのChartオブジェクトは単一のマークタイプを持つプロットを表します。しかし、複数のチャートやレイヤーを含むより複雑な図表はどうでしょうか?Altairは、一連のビュー構成(view composition)オペレーターを使用して、複数のチャート定義を組み合わせ、より複雑なビューを作成することができます。

まず最初に、carsデータセットをプロットし、製造年ごとの平均燃費を示す折れ線グラフを作成してみましょう:

alt.Chart(cars).mark_line().encode(
    alt.X('Year'),
    alt.Y('average(Miles_per_Gallon)')
)

このプロットを拡張するために、平均化された各データポイントにcircleマークを追加したい場合があります。(circleマークは、塗りつぶされた円を使用するpointマークの便利な省略形です。)

まず、それぞれのチャートを別々に定義します。最初に折れ線グラフ、次に散布図を作成します。その後、layerオペレーターを使用してこれらを組み合わせ、レイヤードチャートにします。ここでは、省略形として+(プラス)オペレーターを使用してレイヤリングを実行します:

line = alt.Chart(cars).mark_line().encode(
    alt.X('Year'),
    alt.Y('average(Miles_per_Gallon)')
)

point = alt.Chart(cars).mark_circle().encode(
    alt.X('Year'),
    alt.Y('average(Miles_per_Gallon)')
)

line + point

このチャートは、以前に作成したチャート定義を再利用し、修正することで作成することも可能です!チャートを完全に書き直すのではなく、まず折れ線グラフを作成し、その後mark_pointメソッドを呼び出して、異なるマークタイプを持つ新しいチャート定義を生成することができます:

mpg = alt.Chart(cars).mark_line().encode(
    alt.X('Year'),
    alt.Y('average(Miles_per_Gallon)')
)

mpg + mpg.mark_circle()

(線上にポイントを配置する必要は非常に一般的であるため、lineマークには新しいレイヤーを生成するための省略形も含まれています。mark_lineメソッドに引数point=Trueを追加して試してみてください!)

では、このチャートを、例えば時間経過による平均馬力を示す他のプロットと並べて表示したい場合はどうすればよいでしょうか?

*結合(concatenation)*オペレーターを使用することで、複数のチャートを縦または横に並べることができます。ここでは、|(パイプ)オペレーターを使用して、2つのチャートを横に並べて結合してみます:

hp = alt.Chart(cars).mark_line().encode(
    alt.X('Year'),
    alt.Y('average(Horsepower)')
)

(mpg + mpg.mark_circle()) | (hp + hp.mark_circle())

このデータセットでは、1970年代から1980年代初頭にかけて、平均燃費が向上する一方で、平均馬力が減少していることがわかります。

後のノートブックでは、ビュー構成に焦点を当てます。ここでは、レイヤリングや結合だけでなく、データをサブプロットに分割するfacetオペレーターや、テンプレートから結合チャートを簡潔に生成するためのrepeatオペレーターについても詳しく学びます。

1.10. インタラクティビティ#

基本的なプロットやビュー構成に加えて、AltairおよびVega-Liteの最も魅力的な機能の1つは、インタラクションをサポートしている点です。

パン(移動)やズーム(拡大縮小)をサポートするシンプルなインタラクティブプロットを作成するには、Chartオブジェクトのinteractive()メソッドを呼び出します。以下のチャートでは、クリックしてドラッグすることでパンしたり、スクロールホイールを使用してズームしたりすることができます:

alt.Chart(cars).mark_point().encode(
    x='Horsepower',
    y='Miles_per_Gallon',
    color='Origin',
).interactive()

マウスホバー時に詳細情報を表示するには、tooltipエンコーディングチャネルを使用します:

alt.Chart(cars).mark_point().encode(
    x='Horsepower',
    y='Miles_per_Gallon',
    color='Origin',
    tooltip=['Name', 'Origin'] # show Name and Origin in a tooltip
).interactive()

より複雑なインタラクション(例えば、リンクされたチャートやクロスフィルタリング)には、Altairが提供する*選択(selection)*の抽象化を使用します。これにより、インタラクティブな選択を定義し、それをチャートのコンポーネントにバインドすることが可能です。この内容については、後のノートブックで詳しく解説します。

以下は、より複雑な例です。上部のヒストグラムは年ごとの車の台数を示しており、インタラクティブな選択を使用して、下部の散布図(馬力対燃費を表示)におけるポイントの不透明度を変更します。

上部のチャートで範囲をドラッグして選択すると、下部のチャートのポイントにどのような影響があるかを確認してください。コードを確認する際に、まだ完全に理解できなくても心配しないでください! これは意欲的な例であり、必要な詳細は他のノートブックを通じて順次説明していきます。

# create an interval selection over an x-axis encoding
brush = alt.selection_interval(encodings=['x'])

# determine opacity based on brush
opacity = alt.condition(brush, alt.value(0.9), alt.value(0.1))

# an overview histogram of cars per year
# add the interval brush to select cars over time
overview = alt.Chart(cars).mark_bar().encode(
    alt.X('Year:O', timeUnit='year', # extract year unit, treat as ordinal
      axis=alt.Axis(title=None, labelAngle=0) # no title, no label angle
    ),
    alt.Y('count()', title=None), # counts, no axis title
    opacity=opacity
).add_selection(
    brush      # add interval brush selection to the chart
).properties(
    width=400, # set the chart width to 400 pixels
    height=50  # set the chart height to 50 pixels
)

# a detail scatterplot of horsepower vs. mileage
# modulate point opacity based on the brush selection
detail = alt.Chart(cars).mark_point().encode(
    alt.X('Horsepower'),
    alt.Y('Miles_per_Gallon'),
    # set opacity based on brush selection
    opacity=opacity
).properties(width=400) # set chart width to match the first chart

# vertically concatenate (vconcat) charts using the '&' operator
overview & detail
/Users/yuichiyazaki/.pyenv/versions/miniforge3-4.10.3-10/lib/python3.9/site-packages/altair/utils/deprecation.py:65: AltairDeprecationWarning: 'add_selection' is deprecated. Use 'add_params' instead.
  warnings.warn(message, AltairDeprecationWarning, stacklevel=1)

1.11. 補足: JSON出力の検証#

AltairはVega-LiteのPython APIとして、プロットの仕様をVega-Liteスキーマに準拠したJSON文字列に変換することを主な目的としています。Chart.to_jsonメソッドを使用すると、Altairがエクスポートし、Vega-Liteに送信するJSON仕様を確認できます:

chart = alt.Chart(df).mark_bar().encode(
    x='average(precip)',
    y='city',
)
print(chart.to_json())
{
  "$schema": "https://vega.github.io/schema/vega-lite/v5.16.3.json",
  "config": {
    "view": {
      "continuousHeight": 300,
      "continuousWidth": 300
    }
  },
  "data": {
    "name": "data-fdfbb22e8e0e89f6556d8a3b434b0c97"
  },
  "datasets": {
    "data-fdfbb22e8e0e89f6556d8a3b434b0c97": [
      {
        "city": "Seattle",
        "month": "Apr",
        "precip": 2.68
      },
      {
        "city": "Seattle",
        "month": "Aug",
        "precip": 0.87
      },
      {
        "city": "Seattle",
        "month": "Dec",
        "precip": 5.31
      },
      {
        "city": "New York",
        "month": "Apr",
        "precip": 3.94
      },
      {
        "city": "New York",
        "month": "Aug",
        "precip": 4.13
      },
      {
        "city": "New York",
        "month": "Dec",
        "precip": 3.58
      },
      {
        "city": "Chicago",
        "month": "Apr",
        "precip": 3.62
      },
      {
        "city": "Chicago",
        "month": "Aug",
        "precip": 3.98
      },
      {
        "city": "Chicago",
        "month": "Dec",
        "precip": 2.56
      }
    ]
  },
  "encoding": {
    "x": {
      "aggregate": "average",
      "field": "precip",
      "type": "quantitative"
    },
    "y": {
      "field": "city",
      "type": "nominal"
    }
  },
  "mark": {
    "type": "bar"
  }
}

ここで、encode(x='average(precip)')が、field名、データのtype、およびaggregateフィールドを含むJSON構造に展開されていることに注目してください。encode(y='city')ステートメントも同様に展開されています。

先ほど確認したように、Altairの省略構文にはフィールドの型を指定する方法も含まれています:

x = alt.X('average(precip):Q')
print(x.to_json())
{
  "aggregate": "average",
  "field": "precip",
  "type": "quantitative"
}

この省略構文は、属性を名前で明示的に指定する方法と同等です:

x = alt.X(aggregate='average', field='precip', type='quantitative')
print(x.to_json())
{
  "aggregate": "average",
  "field": "precip",
  "type": "quantitative"
}

1.12. 可視化の公開#

データを可視化した後、それをウェブ上に公開したい場合があります。これを簡単に実現するには、vega-embed JavaScriptパッケージを使用できます。Chart.saveメソッドを使用することで、任意のチャートからスタンドアロンのHTMLドキュメントを生成する簡単な例を作成できます:

chart = alt.Chart(df).mark_bar().encode(
    x='average(precip)',
    y='city',
)
chart.save('chart.html')

基本的なHTMLテンプレートでは、以下のような出力が生成されます。この場合、Chart.to_jsonで生成されたプロットのJSON仕様は、JavaScript変数specに格納する必要があります:

<!DOCTYPE html>
<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
    <script src="https://cdn.jsdelivr.net/npm/vega-lite@4"></script>
    <script src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
  </head>
  <body>
  <div id="vis"></div>
  <script>
    (function(vegaEmbed) {
      var spec = {}; /* JSON output for your chart's specification */
      var embedOpt = {"mode": "vega-lite"}; /* Options for the embedding */

      function showError(el, error){
          el.innerHTML = ('<div style="color:red;">'
                          + '<p>JavaScript Error: ' + error.message + '</p>'
                          + "<p>This usually means there's a typo in your chart specification. "
                          + "See the javascript console for the full traceback.</p>"
                          + '</div>');
          throw error;
      }
      const el = document.getElementById('vis');
      vegaEmbed("#vis", spec, embedOpt)
        .catch(error => showError(el, error));
    })(vegaEmbed);
  </script>
</body>
</html>

Chart.saveメソッドを使用することで、このようなHTML出力をファイルに保存する便利な方法が提供されます。Altair/Vega-Liteの埋め込みに関する詳細は、vega-embedプロジェクトのドキュメントを参照してください。

1.13. 次のステップ#

🎉 おめでとうございます!Altairのイントロダクションを完了しました!次のノートブックでは、Altairのデータ型、グラフィカルマーク、および視覚エンコーディングチャネルのモデルを活用して、さらに高度な可視化の作成に取り組みます。