概要
本稿ではグラフ可視化ライブラリ Altair の使い方を紹介させて頂きます。Altair はデータをインタラクティブに可視化するために開発された json のフォーマット Vega-Lite を Python から記述することができるライブラリです。
ぜひ【時系列データ編】も参照してみてください。
ハンズオン公開
Google Colabと GitHub Pages でハンズオンを公開しています。よろしければご活用ください。
可視化ツールとしての Altair の強み
Altair の強みは、高度なデータ分析が可能な Python の枠組みでインタラクティブなグラフを作成できるところにあります。同じ Python の可視化ライブラリとして最も普及している Matplotlib はインタラクティブなグラフの作成機能があまり充実していません。一方で Excel はリストを活用することでロップダウンと連動したグラフを作成することなどはできますが、Python のように高度な分析には対応していません。
可視化ツール | インタラクティブなグラフのカスタマイズ性 | 高度な分析 |
---|---|---|
Altair | ◎:Vega-Liteに則って自由に作成可能 | ◎:Pythonで可能 |
Matplotlib | △:ipywighets やanimation.ArtistAnimation() の活用で限定的に可能 |
◎:Pythonで可能 |
Excel | ◯:リストを活用したドロップダウンとの連動などが可能 | △:基礎的な集計・検定に限る |
Altair のようにインタラクティブなグラフを作成できる Python の可視化ライブラリとして、他には Bokeh や Plotly などがあります。それぞれ作成可能なグラフの種類やデザインが異なるため、好みに合わせて使い分けるとよいと思います。個人的な意見としては、三次元モデルの可視化が得意なのは Plotly で、二次元モデルの可視化が得意なのは Altair です。
データ作成
今回は架空の学校で行われた期末試験の得点をデモデータとして作成します。この学校には学生が 30 人在籍し、普通、特進、理数の 3 コースが存在します。期末試験の科目は国語、数学、理科、社会、英語で各教科 100 点満点とします。
import random
import pandas as pd
# パラメータ
N = 30 # 学生の人数
mu, sigma = 60, 18 # 学力の平均と標準偏差
seed = 1
# 得点データの生成
sex = ["男", "女"] # 性別
cl = ["普通", "理数", "特進"] # コース
sub = ["国語", "数学", "理科", "社会", "英語"] # 教科
id_lst = ["ID" + str(i + 1).zfill(len(str(N))) for i in range(N)]
random.seed(seed)
C = [random.randint(a=0, b=len(cl) - 1) for _ in range(N)] # コース分け
S = [random.gauss(mu=mu, sigma=sigma) * (1 + C[n] * 0.18) for n in range(N)] # 学力
G = [random.randint(a=0, b=1) for _ in range(N)] # 性別
T = { # テストの難易度
"国語": {"mu": 0.75, "sigma": 0.15, "cor": 0.02},
"数学": {"mu": 0.7, "sigma": 0.15, "cor": -0.05},
"社会": {"mu": 0.85, "sigma": 0.03, "cor": 0.03},
"理科": {"mu": 0.8, "sigma": 0.05, "cor": -0.02},
"英語": {"mu": 0.8, "sigma": 0.15, "cor": 0.01},
}
df = pd.DataFrame()
df["学生番号"] = id_lst
for s in sub:
df[s] = [
max(0,min(int(S[i] * random.gauss(mu=T[s]["mu"], sigma=T[s]["sigma"]) * (1 + G[i] * T[s]["cor"])),100))
for i in range(N)
]
df["コース"] = [cl[c] for c in C]
df["性別"] = [sex[g] for g in G]
df.head()
学生番号 国語 数学 理科 社会 英語 コース 性別
0 ID01 18 7 15 17 11 普通 女
1 ID02 47 34 49 57 80 特進 女
2 ID03 32 39 41 42 42 普通 男
3 ID04 64 34 68 71 54 理数 女
4 ID05 21 41 45 51 47 普通 男
インストール
本ハンズオンでは altair
と altair_saver
という2つのライブラリを使用します。両方とも pip でインストール可能です。
$ pip install altair altair_saver
conda でもインストールできます。詳細は公式ドキュメントを参照してください。
google colab の場合
デフォルトで altair
はインストールされています。以下ライブラリをインストールしてください。
! pip install -U requests
! pip install altair_saver
グラフの構成
altair で作られるグラフは chart object というクラスから生成されたオブジェクトです。chart object には以下の 6 種類があります。
alt.Chart() |
最も基本的なオブジェクト |
alt.LayerChart() |
複数のalt.Chart() を重ねられるオブジェクト |
alt.VConcatChart() |
複数の alt.Chart() を縦に並べられるオブジェクト |
alt.HConcatChart() |
複数の alt.Chart() を横に並べられるオブジェクト |
alt.RepeatChart() |
類似の alt.Chart() を縦や横に並べられるオブジェクト |
alt.FacetChart() |
alt.VConcatChart() や alt.HConcatChart() を重ねられるオブジェクト |
これらのオブジェクトはすべて Pandas DataFrame や json、csv などを引数とし、そのデータを可視化することができます。次は最も基本的なalt.Chart()
の構成を見ていきましょう。
Mark と Encoding
alt.Chart()
でデータを可視化するためには .mark_XXXX()と.encode()という2つのメソッドが不可欠です。それぞれの役割は以下の通りです。
メソッド | 役割 |
---|---|
.mark_XXXX() |
XXXX は可視化したいグラフの種類によって変わります。各グラフには channel といった要素があり、引数には全レコードで共通の処理をしたい channel をとります。例えば散布図でプロットの色や形を揃えたいときはここで指定しましょう。 |
.encode() |
引数には1レコードごとに異なる処理をしたい channel をとります。多くのグラフにおいて X 軸の alt.X("列名") や Y 軸のalt.Y("列名") は不可欠です。 |
以下の散布図は X 軸に国語の得点をとり、Y軸に数学の得点をとります。また性別ごとに色を分け、コースごとにグラフを分けています。一方でプロットの大きさや透過度、形は統一しています。
import altair as alt
altair_chart = (
alt.Chart(data=df)
.mark_point(size=30, opacity=0.9, shape="triangle")
.encode(
x=alt.X("国語"),
y=alt.Y("数学"),
row=alt.Row("コース"),
color=alt.Color("性別")
)
)
altair_chart
図の保存
altair_saver.save()
を使えば html または json で保存可能です。
from altair_saver import save
save(altair_chart, "./saved_chart.html", inline=False)
save(altair_chart, "./saved_chart.json")
オフラインで html 保存する際の注意点
inline=False
にすると vega、vega-lite、vega-embed を外部ファイル化できます。すなわち下記3ファイルを外部から読み込むことでファイルサイズを節約できます。
- src="https://cdn.jsdelivr.net/npm/vega@5"
- src="https://cdn.jsdelivr.net/npm/vega-lite@5"
- src="https://cdn.jsdelivr.net/npm/vega-embed@6"
ただしオフラインでは inline=True
としてhtmlファイルに直接埋め込む必要があります。
他形式の保存
png、svg、形式の保存はグラフ右上の三点印embed_options
から可能ですが解像度は調節できません。 altair_saver
を使用すれば png、svg、そして pdf でも高解像度で保存可能です。
ただ Jupyter 上でこれを実行するためには更なるセットアップが必要です。詳しくはこちらを参考にしてください。
Option による軸、凡例、ヘッダーの設定
エンコーディングされた各チャネルにはoptionを引数としてとることができます。option は channel ごとに仕様が異なっていたり、一部の channel でしか使えなかったりするものが多いです。よく使うものだけを覚えておくとよいでしょう。
以下の例では、散布図の軸ラベルや判例などをカスタマイズしています。
altair_chart_with_options = (
alt.Chart(data=df)
.mark_point(size=30, opacity=0.9, shape="triangle")
.encode(
x=alt.X(
"国語",
scale=alt.Scale(domain=[0, 100], bins=[0, 20, 40, 60, 80, 100]), # 軸の値の範囲
axis=alt.Axis(
ticks=False, # 軸に┬をいれるかどうか
grid=True, # グラフの中にマス目を描くかどうか
labelFont="Yu Gothic UI",
labelFontSize=15,
labelAngle=0,
titleFontSize=18,
titleFont="Yu Gothic UI",
titleAngle=0,
title="X軸のタイトルはここ",
),
),
y=alt.Y(
"数学",
scale=alt.Scale(domain=[0, 100], bins=[0, 20, 40, 60, 80, 100]),
axis=alt.Axis(
ticks=False,
grid=True,
labelFont="Yu Gothic UI",
labelFontSize=15,
labelAngle=0,
titleFontSize=18,
titleFont="Yu Gothic UI",
titleAngle=-90,
title="Y軸のタイトルはここ",
),
),
row=alt.Row(
"コース",
header=alt.Header(
labelFont="Yu Gothic UI",
labelFontSize=15,
labelAngle=-90,
titleFontSize=18,
titleFont="Yu Gothic UI",
titleAngle=-90,
title="ヘッダータイトルはここ",
),
),
color=alt.Color(
"性別",
scale=alt.Scale(domain=sex, range=["steelblue", "darkred"]), # プロットの色の指定
legend=alt.Legend(
titleFont="Yu Gothic UI",
title="凡例タイトルはここ",
),
),
)
)
altair_chart_with_options
alt.Scale()
は alt.X()
の引数では軸の値を指定するのに対し、alt.Color()
ではプロットの色を指定しています。上記のように channel ごとに仕様が異なる option については特に注意しましょう。なお色はalt.ColorName()またはカラーコードで指定できます。
グラフのサイズ指定
グラフのサイズは.properties()で指定します。ここではグラフのタイトルも指定できます。
altair_chart_with_options.properties(width=200, height=100, title="グラフのタイトルはここ")
テーマの設定
Altair で複数の Figure を作成する際、軸やヘッダー、凡例、グラフのサイズなどを毎回指定していてはコードが冗長になってしまいます。すべてのグラフに共通させたいパラメータは alt.themes.register()
で一括登録してしまうのがオススメです。ちなみにここで指定したパラメータは、登録後に個別の.encode()
で上書き可能です。
def font_config():
labelFont = "Yu Gothic UI"
labelFontSize = 15
labelAngle = 0
titleFont = "Yu Gothic UI"
titleFontSize = 18
titleAngle = 0
markFont = "Yu Gothic UI"
return {
"config": {
"axis": {
"ticks": True,
"grid": True,
"labelFont": labelFont,
"labelFontSize": labelFontSize,
"labelAngle": 0,
"titleFont": titleFont,
"titleFontSize": titleFontSize,
},
# 色分けした際の項目
"legend": {
"labelFont": labelFont,
"labelFontSize": labelFontSize,
"labelAngle": labelAngle,
"titleFont": titleFont,
"titleFontSize": titleFontSize,
"titleAngle": titleAngle,
},
# グラフ上部の文字
"header": {
"labelFont": labelFont,
"labelFontSize": 20,
"labelAngle": labelAngle,
"titleFont": titleFont,
"titleFontSize": 25,
"titleAngle": titleAngle,
},
"mark": {"font": markFont},
"title": {"font": titleFont, "subtitleFont": titleFont},
# 図の大きさ
"view": {"width": 300, "height": 300},
# 図の背景
"background": "white",
}
}
alt.themes.register(name="font_config", value=font_config)
alt.themes.enable(name="font_config")
変数型の指定
Encoding ではそのカラムの変数の型を指定する必要があります。上記のように指定せずとも自動で補完されることもありますが、指定しないとエラーが発生することもあります。指定可能な変数の型は以下のとおりです。
変数 | type=XXXX |
---|---|
連続変数 | quantitative |
順序変数 | ordinal |
名義変数 | nominal |
時系列変数 | temporal |
地理的変数 | geojson |
以下の例では"数学"の得点をあえて"順序変数"と指定することで得点の間隔が無視されています。またプロットの色を"社会"の得点とすることで得点の高低が色の濃淡によって表されています。
(
alt.Chart(data=df)
.mark_point(size=30, opacity=0.9, shape="triangle")
.encode(
x=alt.X("国語", type="quantitative"),
y=alt.Y("数学", type="ordinal",sort="-y"), # sort で降順になるように並び替えました
color=alt.Color("社会", type="quantitative", legend=None),
shape=alt.Shape("性別", type="nominal"),
)
.properties(width=300, height=300, title="グラフのタイトルはここ")
)
なお変数型は頭文字1文字で省略することも可能です。例えばx=alt.X("国語", type="quantitative")
はx=alt.X("国語:Q")
と同じです。
縦長テーブルへの変換
RDB には横長もしくは縦長というデータの構造があります。これはメタデータをどのように持つかの違いです。
横長 | 縦長 | |
---|---|---|
特徴 | メタデータを行ラベルまたは列ラベルに持つ | メタデータを RDB のフィールドに持つ |
長所 | テーブルのレコード数を節約できる | 列ラベルや行ラベルを参照せずともメタデータはわかる |
短所 | 行ラベルまたは列ラベルを参照しないとメタデータが分からない | テーブルのレコード数が長くなる |
別名 | 雑然(messy)データ | 整然(tidy)データ |
例えば上記のdf
では 国語
、数学
、 理科
、 社会
、 英語
という別々の列にそれぞれの科目の得点が記録されています。そのためそれぞれの値がどの科目の得点なのかは列ラベルを参照しないとわかりません。このようなデータ構造は横長といえます。一方で学生番号
やコース
、性別
などは列ラベルを参照しなくてもわかります。このようなデータ構造は縦長といえます。
Altair では .encode()
や後述の .add_selection()
、.transform_XXXXX()
などの関数によってレコード1行ごとの値を参照してデータのETL(Extract、Transform、Load)が行われます。この際、列ラベルや行ラベルを参照することはできません。したがって、縦長のデータ構造に変換することによって Altair による可視化のカスタマイズ性は飛躍的に向上します。
そこで df
で横長に持たれている科目ごとの得点を、.pandas.melt()
を用いることによって縦長のデータ構造に変換しましょう。
long_df = pd.melt(df, id_vars=["学生番号", "性別", "コース"], var_name="科目", value_name="得点")
long_df
学生番号 性別 コース 科目 得点
0 ID01 女 普通 国語 18
1 ID02 女 特進 国語 47
2 ID03 男 普通 国語 32
3 ID04 女 理数 国語 64
4 ID05 男 普通 国語 21
... ... ... ... ... ...
145 ID26 男 理数 英語 24
146 ID27 男 普通 英語 15
147 ID28 男 普通 英語 43
148 ID29 女 普通 英語 45
149 ID30 女 特進 英語 47
縦長データの可視化(箱ひげ図)
科目を縦長のデータ構造で持たせることによって科目ごとの得点の集計が可能になりました。例えば科目ごとの得点の中央値や四分位数を集計する際は以下のように箱ひげ図を .mark_boxplot()
で作成するとよいでしょう。すなわち X 軸に配置する科目
を key に groupby して Y 軸の得点
を集計します。ちなみに .mark_boxplot()
は複数のalt.Chart()
を重ね合わせたオブジェクトを返すため、一部の仕様が他の.mark_XXXX()
と異なります。
(
alt.Chart(long_df)
.mark_boxplot(
size=30, # 箱の幅
ticks=alt.MarkConfig(width=40), # ひげの ┸ や ┯ の幅
median=alt.MarkConfig(color="black", size=30), # 中央値に引かれる線の設定
)
.encode(
x=alt.X("科目:N"),
y=alt.Y(
"得点:Q",
scale=alt.Scale(domain=[0, 100],bins=[0, 20, 40, 60, 80, 100]), # domainは表示する点数の範囲
axis=alt.Axis(title="得点"),
),
column=alt.Column("性別", sort=alt.Sort(cl)),
)
.properties(width=300, height=300, title="各科目の得点分布")
.interactive()
)
上の箱ひげ図をカーソルでなぞると中央値などの統計量がポップアップされます。この機能はtooltipsと呼ばれています。他の.mark_XXXX()
では tooltip channel alt.Tooltip()
で指定されたカラムの値しか表示されませんが、.mark_boxplot()
で返される chart オブジェクトはデフォルトで tooltips を持っています。
Aggregate と Tooltips
Altair では aggregate 関数によってグループごとの要約統計量を算出することが可能です。算出可能な要約統計量は "mean"
、 "sum"
、"median"
、"min"
、"max"
、"count"
の6つです。
前節でふれた .alt.Tooltip()
でも aggregate 関数は使用可能です。
bar_chart = (
alt.Chart(long_df)
.mark_bar()
.encode(
x=alt.X(
"科目:N",
sort=alt.Sort(sub),
axis=alt.Axis(title="5教科"),
),
y=alt.Y(
"得点:Q",
aggregate="mean", # aggregate 関数の指定
scale=alt.Scale(domain=[0, 100]),
axis=alt.Axis(title="平均点"),
),
color=alt.Color(
"科目:N",
scale=alt.Scale(
domain=sub, range=["brown", "steelblue", "orange", "green", "purple"]
),
),
tooltip=[
alt.Tooltip(field="得点", aggregate="mean", format=".2f"), # 平均得点を集計して小数第二位まで表示
alt.Tooltip(field="科目"),
],
)
)
bar_chart
SQL の HAVING 句
や pandas や pyspark の .agg()
と似たような処理といえます。
エラーバーの挿入
ばらつきの大きさを表す要約統計量をエラーバーとして可視化させることもできます。.mark_errorbar(extent=XXX)
でエラーバーの計算方法を選択しましょう。引数はデフォルトでstderr
(標準誤差)です。他にはstderv
(標準偏差)、ci
(95%信頼区間)、iqr
(四分位範囲) といった統計量がとれます。
error_bar = (
alt.Chart(long_df)
.mark_errorbar(extent="ci",
clip=True,rule=True,size=100,
ticks=alt.MarkConfig(width=20,color="black"),
)
.encode(
x=alt.X(
"科目:N",
sort=alt.Sort(sub),
axis=alt.Axis(title="5教科"),
),
y=alt.Y(
"得点:Q",
scale=alt.Scale(domain=[0, 100]),
axis=alt.Axis(title="平均点"),
)
)
)
bar_chart + error_bar
棒グラフ(積み上げ)
alt.Y(stack=True)
で棒グラフを積み上げることができます。stack
はデフォルトでTrueになっているため省略しても構いません。
(
alt.Chart(long_df)
.mark_bar()
.encode(
x=alt.X(
"学生番号:N",
sort="-y",
axis=alt.Axis(labelAngle=-90),
),
y=alt.Y(
"得点:Q",
aggregate="sum",
stack=True,
scale=alt.Scale(domain=[0, 500]),
axis=alt.Axis(title="合計点"),
),
color=alt.Color(
"科目:N",
scale=alt.Scale(
domain=sub, range=["brown", "steelblue", "orange", "green", "purple"]
),
),
tooltip=[
alt.Tooltip(field="得点"),
alt.Tooltip(field="科目"),
alt.Tooltip(field="学生番号"),
],
)
.properties(width=500)
)
棒グラフ(並列)
棒グラフの並列表示は alt.Row()
または alt.Column()
を活用したグラフの分割で可能です。下記の例では学生1人につき1つのグラフオブジェクトを作成しています。
(
alt.Chart(long_df)
.transform_filter((alt.datum.学生番号 <= id_lst[4])) # 便宜上5名のみ表示
.mark_bar()
.encode(
x=alt.X("得点:Q", scale=alt.Scale(domain=[0, 100])),
y=alt.Y("科目:N", axis=None),
color=alt.Color(
"科目:N",
scale=alt.Scale(
domain=sub, range=["brown", "steelblue", "orange", "green", "purple"]
),
),
tooltip=[
alt.Tooltip(field="得点"),
alt.Tooltip(field="科目"),
alt.Tooltip(field="学生番号"),
],
row=alt.Row("学生番号:N", header=alt.Header(titleAngle=-90)),
)
.properties(height=70, width=500)
.configure_facet(spacing=5) # グラフごとの間隔
)
対数グラフ
対数グラフの作成はalt.Y(scale=alt.Scale(type="log"))
で行いましょう。ちなみに0以下の値があると表示されませんので前処理で削除しておきましょう。
(
alt.Chart(long_df)
.mark_bar()
.encode(
x=alt.X(
"学生番号:N",
sort="-y",
axis=alt.Axis(labelAngle=-90),
),
y=alt.Y(
"得点:Q",
aggregate="sum",
scale=alt.Scale(type="log"),
axis=alt.Axis(title="合計点"),
),
tooltip=[
alt.Tooltip(field="得点",aggregate="sum"),
alt.Tooltip(field="学生番号"),
],
)
.properties(width=500)
)
割合グラフ
stack="normalize"
で割合を算出することができます。
(
alt.Chart(long_df)
.mark_bar()
.encode(
x=alt.X(
"学生番号:N",
axis=alt.Axis(labelAngle=-90),
),
y=alt.Y(
"得点:Q",stack="normalize",
axis=alt.Axis(format="%"),
),
color=alt.Color(
"科目:N",
scale=alt.Scale(
domain=sub, range=["brown", "steelblue", "orange", "green", "purple"]
),
),
tooltip=[
alt.Tooltip(field="得点"),
alt.Tooltip(field="科目"),
alt.Tooltip(field="学生番号"),
],
)
.properties(width=500)
)
.transform_XXXXX()
を用いると様々な前処理が可能です。以下は .transform_bin()
、.transform_joinaggregate()
、.transform_calculate()
を組みあわせてほぼ同じグラフを作成する例です。
(
alt.Chart(long_df)
.transform_bin(as_="ビン", field="学生番号:N")
.transform_joinaggregate(合計得点="sum(得点):Q", groupby=["学生番号", "ビン"])
.transform_calculate(割合="datum.得点/datum.合計得点")
.mark_bar()
.encode(
x=alt.X(
"学生番号:N",
axis=alt.Axis(labelAngle=-90),
),
y=alt.Y(
"割合:Q",
axis=alt.Axis(format="%"),
),
color=alt.Color(
"科目:N",
scale=alt.Scale(
domain=sub, range=["brown", "steelblue", "orange", "green", "purple"]
),
),
tooltip=[
alt.Tooltip(field="割合",format="%"),
alt.Tooltip(field="科目"),
alt.Tooltip(field="学生番号"),
],
)
.properties(width=500)
)
積み上げヒストグラム
棒グラフと同様にヒストグラムを積みあげることも可能です。
(
alt.Chart(df)
.mark_bar()
.encode(
x=alt.X("国語",
bin=alt.Bin(step=10,extent=[0,100]),
axis=alt.Axis(title="得点")
),
y=alt.Y("国語",
aggregate="count",
axis=alt.Axis(title="人数",values=list(range(N))),
stack=True,
),
color=alt.Color("性別",
scale=alt.Scale(domain=sex, range=["steelblue","darkorange"])
),
)
)
重ね合わせヒストグラム
棒グラフやヒストグラムはstack=False
重ねて表示させることもできます。その場合opacity
を1未満に設定して透過させるようにするとよいでしょう。なお Altair ではX軸とY軸の配置は交換可能です。
(
alt.Chart(df)
.mark_bar(opacity=0.5)
.encode(
y=alt.Y("国語",
bin=alt.Bin(step=10,extent=[0,100]),
axis=alt.Axis(title="得点")
),
x=alt.X("国語",
aggregate="count",
axis=alt.Axis(title="人数",values=list(range(N))),
stack=False,
),
color=alt.Color("性別",
scale=alt.Scale(domain=sex, range=["steelblue","darkorange"])
),
)
)
条件式によるレコードの抽出
Altair では条件式によるレコードの抽出が .transform_filter()
で可能です。この操作は SQL の where 句とも類似しています。以下では国語の得点のみを抽出しています。
(
alt.Chart(long_df)
.transform_filter(alt.datum.科目 == "国語")
.mark_bar()
.encode(
x=alt.X(
"学生番号:N",
sort="-y",
axis=alt.Axis(labelAngle=-90),
),
y=alt.Y(
"得点:Q",
scale=alt.Scale(domain=[0, 100]),
axis=alt.Axis(title="国語の得点"),
),
tooltip=[
alt.Tooltip(field="得点"),
alt.Tooltip(field="学生番号"),
],
)
.properties(width=500)
)
条件式による分岐
.encode()
の channel には条件式(すなわち if 文)によってレコードごとに処理を変えられるものもあります。この if 文は alt.condition()
の引数 predicate
でとれます。alt.condition()
を渡すことができる channel として color
、size
、opacity
などがあります。
(
alt.Chart(long_df)
.transform_filter(alt.datum.科目 == "国語")
.mark_bar()
.encode(
x=alt.X(
"学生番号:N",
sort="-y",
axis=alt.Axis(labelAngle=-90),
),
y=alt.Y(
"得点:Q",
scale=alt.Scale(domain=[0, 100]),
axis=alt.Axis(title="国語の得点"),
),
color=alt.condition(
predicate=alt.datum.得点 >= 55, # if 文を挿入します。
if_true=alt.value("steelblue"), # 55 点以上のレコードは青色
if_false=alt.value("gray"), # それ以下は灰色
),
tooltip=[
alt.Tooltip(field="得点"),
alt.Tooltip(field="学生番号"),
],
)
.properties(width=500)
)
インタラクティブな条件式の変更
alt.condition()
や .transform_filter()
の引数である条件式は リアルタイムのカーソルの動きによって変更可能です。この条件式を変更させることができるオブジェクトがalt.selection()
です。このオブジェクトはalt.Chart()
のメソッドである.add_selection()
の引数です。
Binding による変更
alt.condition()
alt.selection_single()
は、図の効果によってユーザーの変更を受けとるものと、プルダウンやスライダーといった別のオブジェクトの変更を受けとるものがあります。後者の場合 binding すなわち alt.binding()
を引数にとります。
score_slider = alt.binding_range(min=0, max=100, step=1, name="score:")
score_selection = alt.selection_single(
fields=["合格点"], bind=score_slider, init={"合格点": 50}
)
(
alt.Chart(long_df)
.transform_filter(alt.datum.科目 == "国語")
.mark_bar()
.encode(
x=alt.X(
"学生番号:N",
sort="-y",
axis=alt.Axis(labelAngle=-90),
),
y=alt.Y(
"得点:Q",
scale=alt.Scale(domain=[0, 100]),
axis=alt.Axis(title="国語の得点"),
),
color=alt.condition(
predicate=alt.datum.得点 > score_selection.合格点,
if_true=alt.value("steelblue"),
if_false=alt.value("gray"),
),
tooltip=[
alt.Tooltip(field="得点"),
alt.Tooltip(field="学生番号"),
],
)
.add_selection(score_selection)
.properties(width=500)
)
.transform_filter()
.transform_filter()
も alt.selection_single()
によって変更可能です。下記例では各科目でX軸の学生順も変わる点に注意してください。
subject_dropdown = alt.binding_select(options=sub)
subject_selection = alt.selection_single(
fields=["科目"], bind=subject_dropdown, name="subject", init={"科目": "国語"}
)
(
alt.Chart(long_df)
.mark_bar()
.encode(
x=alt.X(
"学生番号:N",
sort="-y",
axis=alt.Axis(labels=False, title=None),
),
y=alt.Y(
"得点:Q",
scale=alt.Scale(domain=[0, 100]),
),
color=alt.condition(
predicate=alt.datum.得点 > score_selection.合格点,
if_true=alt.value("steelblue"),
if_false=alt.value("gray"),
),
tooltip=[
alt.Tooltip(field="得点"),
alt.Tooltip(field="学生番号"),
],
)
.add_selection(score_selection)
.add_selection(subject_selection)
.transform_filter(subject_selection)
.properties(width=500)
)
図の操作による変更
alt.selection_single()
で上記のように bind
を引数にとらない場合、自身の結合元の図の操作による変更を受けとります。下記例では fields=["学生番号"]
としているため、図でクリックされた学生番号のレコードが True として、それ以外の学生番号のレコードが False として処理されます。
student_selection = alt.selection_single(fields=["学生番号"], init={"学生番号": id_lst[0]})
(
alt.Chart(long_df)
.mark_bar()
.encode(
x=alt.X(
"学生番号:N",
sort="-y",
axis=alt.Axis(labelAngle=-90),
),
y=alt.Y(
"得点:Q",
scale=alt.Scale(domain=[0, 100]),
),
color=alt.condition(
predicate=student_selection,
if_true=alt.value("steelblue"),
if_false=alt.value("lightgray"),
),
tooltip=[
alt.Tooltip(field="得点"),
alt.Tooltip(field="学生番号"),
],
)
.add_selection(student_selection)
.add_selection(subject_selection)
.transform_filter(subject_selection)
.properties(width=500)
)
連動する図の作成
下記例では、上の棒グラフをドラッグした領域の学生数を下の棒グラフが集計します。
student_brush_selection = alt.selection(type="interval", encodings=["x"])
color_by_sex = alt.Color(
"性別:N",
scale=alt.Scale(domain=sex, range=["steelblue", "darkorange"]),
)
upper = (
alt.Chart(long_df)
.mark_bar()
.encode(
x=alt.X(
"学生番号:N",
sort="-y",
axis=alt.Axis(labelAngle=-90),
),
y=alt.Y(
"得点:Q",
scale=alt.Scale(domain=[0, 100]),
),
color=alt.condition(
predicate=student_brush_selection,
if_true=color_by_sex,
if_false=alt.value("lightgray"),
),
tooltip=[
alt.Tooltip(field="得点"),
alt.Tooltip(field="学生番号"),
],
)
.add_selection(student_brush_selection)
.add_selection(subject_selection)
.transform_filter(subject_selection)
.properties(width=500)
)
lower = (
alt.Chart(long_df)
.mark_bar()
.encode(
x=alt.X("count():Q", title="人数", scale=alt.Scale(domain=[0, N // 2])),
y=alt.Y("コース:N"),
color=color_by_sex,
)
.transform_filter(subject_selection)
.transform_filter(student_brush_selection)
.properties(width=500, height=150)
)
upper & lower
VConcatChart の活用
複数の図で共通の処理は alt.VConcatChart()
でまとめることもできる。上記例は data=long_df
と.transform_filter(subject_selection)
が2つのグラフで共通であったためalt.VConcatChart()
を活用して下記のようにまとめることも可能である。
student_brush_selection = alt.selection(type="interval", encodings=["x"])
color_by_sex = alt.Color(
"性別:N",
scale=alt.Scale(domain=sex, range=["steelblue", "darkorange"]),
)
upper = (
alt.Chart()
.mark_bar()
.encode(
x=alt.X(
"学生番号:N",
sort="-y",
axis=alt.Axis(labelAngle=-90),
),
y=alt.Y(
"得点:Q",
scale=alt.Scale(domain=[0, 100]),
),
color=alt.condition(
predicate=student_brush_selection,
if_true=color_by_sex,
if_false=alt.value("lightgray"),
),
tooltip=[
alt.Tooltip(field="得点"),
alt.Tooltip(field="学生番号"),
],
)
.add_selection(student_brush_selection)
.add_selection(subject_selection)
.properties(width=500)
)
lower = (
alt.Chart()
.mark_bar()
.encode(
x=alt.X("count():Q", title="人数", scale=alt.Scale(domain=[0, N // 2])),
y=alt.Y("コース:N"),
color=color_by_sex,
)
.transform_filter(student_brush_selection)
.properties(width=500, height=150)
)
(
alt.VConcatChart(
data=long_df,
vconcat=(upper,lower)
).transform_filter(subject_selection)
)
いろいろ調べてみよう
Altair には様々な機能があります!公式ページや【時系列データ編】などを参考にインタラクティブなグラフを作成してみましょう!
subject_mouse_selection = alt.selection(
type="single", fields=["科目"], on="mouseover", nearest=True, init={"科目": "国語"}
)
base = alt.Chart(long_df).encode(
x=alt.X(
"学生番号:N",
axis=alt.Axis(labelAngle=-90),
),
y=alt.Y(
"得点:Q",
scale=alt.Scale(domain=[0, 100]),
),
detail=alt.Detail("科目:N"),
tooltip=[
alt.Tooltip(field="得点"),
alt.Tooltip(field="科目"),
alt.Tooltip(field="学生番号"),
],
)
points = (
base.mark_circle()
.encode(
opacity=alt.condition(
predicate=subject_mouse_selection,
if_true=alt.value(1),
if_false=alt.value(0),
),
)
.add_selection(subject_mouse_selection)
)
lines = base.mark_line().encode(
color=alt.condition(
predicate=subject_mouse_selection,
if_true=alt.value("steelblue"),
if_false=alt.value("lightgray"),
),
opacity=alt.condition(
predicate=subject_mouse_selection,
if_true=alt.value(1),
if_false=alt.value(0.5),
),
)
text = (
alt.Chart()
.mark_text(align="center", dx=0, dy=-170, fontSize=18)
.encode(
text=alt.Text("科目:N"),
opacity=alt.condition(subject_mouse_selection, alt.value(1), alt.value(0)),
)
)
(points + lines + text).properties(width=500)
5000 行以上のテーブルを入力する場合
altair で下記 Error が表示される場合は入力レコードの上限を変更してください。
altair.utils.data.MaxRowsError:
The number of rows in your dataset is greater than the maximum allowed (5000).
For information on how to plot larger datasets in Altair, see the documentation
# 入力レコードの上限の変更
from altair import limit_rows, to_values
import toolz
t = lambda data: toolz.curried.pipe(data, limit_rows(max_rows=10000), to_values)
alt.data_transformers.register("custom", t)
alt.data_transformers.enable("custom")
Altair チート集
随時追加していきます。
ストリッププロット(ジッタープロット)
プロットは学生番号に対応しています。点の粗密で分布を確認できます。
(
alt.Chart(long_df)
.mark_circle(size=8)
.encode(
x=alt.X(
"jitter:Q",
title=None,
axis=alt.Axis(values=[0], ticks=True, grid=False, labels=False),
scale=alt.Scale(),
),
y=alt.Y("得点:Q"),
column=alt.Column("科目:N"),
)
.transform_calculate(
# Generate Gaussian jitter with a Box-Muller transform
jitter="sqrt(-2*log(random()))*cos(2*PI*random())"
)
.properties(width=50)
)
ヴァイオリンプロット
図形の幅で分布を表しています。人口ピラミットなどに用いられています。
x_range = [0, 100]
bin_range = 10
(
alt.Chart(long_df)
.transform_density("得点", as_=["得点", "density"], extent=x_range, groupby=["科目"])
.mark_area(orient="horizontal")
.encode(
x=alt.X(
"density:Q",
stack="center",
impute=None,
title=None,
axis=alt.Axis(labels=False, values=[0], grid=False, ticks=True),
),
y="得点:Q",
color=alt.Color("科目:N", legend=None),
column=alt.Column(
"科目:N",
header=alt.Header(
titleOrient="bottom",
labelOrient="bottom",
labelPadding=0,
labelFontSize=15,
titleFontSize=18,
),
),
)
.configure_facet(spacing=0)
.configure_view(stroke=None)
.properties(width=100, height=300)
)
リッジライン
図形の高さで分布を表しています。
step = 50
overlap = 0.5
x_range = [0, 100]
bin_range = 20
(
alt.Chart(long_df)
.transform_bin(as_="ビン", field="得点", bin=alt.Bin(step=bin_range, extent=x_range))
.transform_aggregate(y_axis="count()", groupby=["科目", "ビン"])
.transform_impute(
impute="y_axis", groupby=["科目"], key="ビン", value=0, keyvals=x_range
)
.mark_area(
interpolate="monotone", fillOpacity=0.6, stroke="lightgray", strokeWidth=0.5
)
.encode(
alt.X(
"ビン:Q",
title="得点",
axis=alt.Axis(grid=False),
scale=alt.Scale(domain=x_range),
),
alt.Y(
"y_axis:Q",
stack=None,
title=None,
axis=None,
scale=alt.Scale(range=[step, -step * overlap]),
),
alt.Fill("科目:N", legend=None),
alt.Row("科目:N", title=None, header=alt.Header(labelAlign="left")),
)
.properties(bounds="flush", width=400, height=int(step))
.configure_facet(spacing=0)
.configure_view(stroke=None)
.configure_title(anchor="end")
)