2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Python】データ可視化ライブラリ Altair ハンズオン【時系列データ編】

Last updated at Posted at 2022-12-05

概要

【基礎編】からご覧になることをおすすめします。

本稿ではグラフ可視化ライブラリ Altair を用いた時系列データの可視化を紹介させていただきます。。

ハンズオン公開

GitHub Pages.ipynbファイルGoogle colabでハンズオンを公開しています。よろしければご活用ください。

各種インポート

import urllib
import json
import pandas as pd
import altair as alt

可視化用デモデータ読込

2000〜2010年の米国業種別失業者数(千人)count および失業率rateをデモデータとして使用します。

可視化用デモデータ読込
data_path = "https://cdn.jsdelivr.net/npm/vega-datasets@1.29.0/data/unemployment-across-industries.json"

with urllib.request.urlopen(data_path) as f:
    unemployment_across_industries = f.read().decode("cp932").split("\n")[0]
dict_data = json.loads(unemployment_across_industries)
keys = list(dict_data[0].keys())
df = pd.DataFrame([[row[key]for key in keys] for row in dict_data], columns=keys)
df["date"] = pd.to_datetime(df["date"])
df
df
	series	year	month	count	rate	date
0	Government	2000	1	430	2.1	2000-01-01 08:00:00+00:00
1	Government	2000	2	409	2.0	2000-02-01 08:00:00+00:00
2	Government	2000	3	311	1.5	2000-03-01 08:00:00+00:00
3	Government	2000	4	269	1.3	2000-04-01 08:00:00+00:00
4	Government	2000	5	370	1.9	2000-05-01 07:00:00+00:00
...	...	...	...	...	...	...
1703	Self-employed	2009	10	610	5.9	2009-10-01 07:00:00+00:00
1704	Self-employed	2009	11	592	5.7	2009-11-01 07:00:00+00:00
1705	Self-employed	2009	12	609	5.9	2009-12-01 08:00:00+00:00
1706	Self-employed	2010	1	730	7.2	2010-01-01 08:00:00+00:00
1707	Self-employed	2010	2	680	6.5	2010-02-01 08:00:00+00:00

date列のデータ型はdatetime64[ns, UTC]です。

df.types
series                 object
year                    int64
month                   int64
count                   int64
rate                  float64
date      datetime64[ns, UTC]
dtype: object

テーマの設定

【基礎編】と同様に全グラフ共通のテーマを設定します。
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,
                # "titleAngle": 0, # Axis のtitleAngleは encode がきかなくなる
            },
            # 色分けした際の項目
            "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": 500, "height": 300},
            # 図の背景
            "background": "white",
        }
    }


alt.themes.register(name="font_config", value=font_config)
alt.themes.enable(name="font_config")

折れ線グラフ

時系列データはtype=temporalで指定可能です。

折れ線グラフ
(
    alt.Chart(df)
    .mark_line()
    .encode(
        x=alt.X(field="date",type="temporal"),
        y=alt.Y(field="count",type="quantitative"),
        color=alt.Color(
            field="series",type="nominal",
            scale=alt.Scale(scheme="category20b"), # デフォルトより色のバリエーション数が多いです。
            legend=alt.Legend(labelFontSize=10)
        ),
        tooltip=[
            alt.Tooltip(field="count",type="temporal"),
            alt.Tooltip(field="series",type="nominal"),
            alt.Tooltip(field="date",type="temporal", format="%Y年%m月%d日 %H時%M分"),
        ],
    )
)

visualization (22).png

マウスオーバーによるハイライト

Altair ではマウスに最も近いポイントをハイライトさせることができます。.alt_selection() でマウスの位置に近いレコードの series を表示させるために basedetail="series" としているところがポイントです。detail がないと特定のレコードのみを抽出してハイライトさせることができません。

マウスオーバーによるハイライト
series_mouse_selection = alt.selection(
    type="single", fields=["series"], on="mouseover", nearest=True, init={"series": "Agriculture"}
)

base = alt.Chart(df).encode(
    x=alt.X(field="date",type="temporal"),
    y=alt.Y(field="count",type="quantitative"),
    detail=alt.Detail(field="series",type="nominal"),
    tooltip=[
        alt.Tooltip(field="count",type="temporal"),
        alt.Tooltip(field="series",type="nominal"),
        alt.Tooltip(field="date",type="temporal", format="%Y年%m月%d日 %H時%M分"),
    ],
)

points = (
    base.mark_circle()
    .encode(
        opacity=alt.condition(
            predicate=series_mouse_selection,
            if_true=alt.value(1),
            if_false=alt.value(0),
        ),
    )
    .add_selection(series_mouse_selection)
)

lines = base.mark_line().encode(
    color=alt.condition(
        predicate=series_mouse_selection,
        if_true=alt.value("steelblue"),
        if_false=alt.value("lightgray"),
    ),
    opacity=alt.condition(
        predicate=series_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("series:N"))
    .transform_filter(series_mouse_selection)
)

(points + lines + text).properties(width=500)

visualization (23).png

ドロップダウンによるハイライト

Altair ではドロップダウンで選択した項目のみをハイライトさせることができます。

ドロップダウンによるハイライト
series_dropdown = alt.binding_select(options=sorted(set(df["series"])))
series_selection = alt.selection_single(
    fields=["series"], bind=series_dropdown, name="_", init={"series": "Construction"}
)

line_chart = (
    alt.Chart(df)
    .mark_line()
    .encode(
        x=alt.X(field="date",type="temporal"),
        y=alt.Y(field="count",type="quantitative"),
        detail=alt.Detail(field="series",type="nominal"),
        color=alt.condition(
            predicate=series_selection,
            if_true=alt.value("steelblue"),
            if_false=alt.value("lightgray"),
        ),
        opacity=alt.condition(
            predicate=series_selection,
            if_true=alt.value(1),
            if_false=alt.value(0.5),
        ),
    )
)
line_chart.add_selection(series_selection)

スクリーンショット 2022-11-27 22.44.30.png

全期間の平均を表示

複数のグラフを重ねて表示させることも可能です。

全期間の平均を表示
avg_line = (
    alt.Chart(df)
    .mark_rule(color="darkorange",size=2.5)
    .encode(
        y=alt.Y("mean(count):Q",axis=alt.Axis(title=None)),
        strokeDash=alt.value([7, 7]),  # dashed line: 7 pixels dash + 7 pixels space
    )
)

text = (
    alt.Chart(df)
    .mark_text(align="left",dx=5,dy=-15,text="Average",color="darkorange")
    .encode(y="mean(count):Q")
)

(
    line_chart.add_selection(series_selection)
    + avg_line.transform_filter(series_selection)
    + text.transform_filter(series_selection)
)

ドラックした区間の平均を表示

2009年付近をドラックすると、その頃に失業者数が増加していたことがわかります。

ドラックした区間の平均を表示
date_brush = alt.selection(type="interval", encodings=["x"])

(
    line_chart.add_selection(series_selection).add_selection(date_brush)
    + avg_line.transform_filter(series_selection).transform_filter(date_brush)
    + text.transform_filter(series_selection).transform_filter(date_brush)
)

スクリーンショット 2022-11-27 22.59.29.png

回帰分析

trans_regression()で回帰分析が可能です。method回帰方法を選べます。線形回帰はmethod=linearです。

回帰分析
pr_line = (
    line_chart
    .transform_filter(series_selection)
    .transform_regression(on="date", regression="count",method="poly")
    .mark_line(size=3,opacity=1)
    .encode(
        strokeDash=alt.value([7, 7]),  # dashed line: 7 pixels dash + 7 pixels space
        color=alt.value("darkorange"),
    )
)

line_chart.add_selection(series_selection) + pr_line

visualization (24).png

エラーバンド

ばらつきの大きさは.mark_errorband()で表示可能です。.mark_errorbar()と同様にでエラーバーで表示する統計量の種類は extentで指定しましょう。引数はデフォルトでstderr(標準誤差)です。他にはstderv(標準偏差)、ci(95%信頼区間)、iqr(四分位範囲)がとれます。

以下のエラーバーは産業別失業者数の95%信頼区間を表示しています。

エラーバンド
line = alt.Chart(df).mark_line().encode(x="date:T", y="mean(count):Q")

band = alt.Chart(df).mark_errorband(extent="ci").encode(x="date:T", y="count:Q")

band + line

visualization (25).png

スライダーで選択した年の可視化

特定の1年間を可視化する手法です。

year_slider = alt.binding_range(min=2000, max=2009, step=1)
year_selection = alt.selection_single(name="year", fields=["year"],
                                      bind=year_slider, init={"year": 2000})

(
    alt.Chart(df)
    .mark_line()
    .encode(
        x=alt.X("yearmonth(date)",type="temporal",
                axis=alt.Axis(title="Year Month",labelAngle=-45)
               ),
        y=alt.Y(field="count",type="quantitative"),
        color=alt.Color(
            field="series",type="nominal",
            scale=alt.Scale(scheme="category20b"),
            legend=alt.Legend(labelFontSize=10)
        ),
        tooltip=[
            alt.Tooltip(field="count",type="temporal"),
            alt.Tooltip(field="series",type="nominal"),
            alt.Tooltip(field="date",type="temporal", format="%Y年%m月%d日 %H時%M分"),
        ],
    )
    .add_selection(year_selection)
    .transform_filter(year_selection)
)

2008年を選択した例です。

スクリーンショット 2022-11-27 23.04.27.png

正規化積みあげ面グラフ

棒グラフと同様に、面グラフも stack="normalize" で正規化できます。

正規化積みあげ面グラフ
(
    alt.Chart(df)
    .mark_area()
    .encode(
        x="date:T",
        y=alt.Y("count:Q", stack="normalize"),
        color=alt.Color("series:N",
            scale=alt.Scale(scheme="category20b"),
            legend=alt.Legend(labelFontSize=10)
        ),
    )
)

visualization (1).png

凡例で選択した項目のハイライト

凡例でクリックした項目がハイライトされます。2個目以降はshiftキーを押しながらクリックしてください。

凡例で選択した項目のハイライト

series_multi_selection = alt.selection_multi(fields=["series"], bind="legend")

(
    alt.Chart(df)
    .mark_area()
    .encode(
        x="date:T",
        y=alt.Y("count:Q", stack="normalize"),
        color=alt.Color("series:N",
            scale=alt.Scale(scheme="category20b"),
            legend=alt.Legend(labelFontSize=10)
        ),
        opacity=alt.condition(series_multi_selection, alt.value(1), alt.value(0.2))
    )
    .add_selection(series_multi_selection)
)

スクリーンショット 2022-11-27 23.08.40.png

選択した項目だけで正規化

凡例でクリックした項目のみで正規化されます。2個目以降はshiftキーを押しながらクリックしてください。

選択した項目だけで正規化
base = (
    alt.Chart(df)
    .mark_area()
    .encode(
        x="date:T",
        y=alt.Y("count:Q", stack="normalize"),
        color=alt.Color("series:N",
            scale=alt.Scale(scheme="category20b"),
            legend=alt.Legend(labelFontSize=10)
        ),
    )
)

background = base.mark_area(opacity=0)
foreground = base.add_selection(series_multi_selection).transform_filter(series_multi_selection)

background + foreground

スクリーンショット 2022-11-27 23.10.33.png

選択期間の拡大

下の横長グラフでドラックした区間を上のグラフで表示させることが可能です。

選択期間の拡大
base = (
    alt.Chart(df)
    .encode(
        x=alt.X(field="date",type="temporal"),
        y=alt.Y(field="count",type="quantitative"),
        color=alt.Color(
            field="series",type="nominal",
            scale=alt.Scale(scheme="category20b"),
            legend=alt.Legend(labelFontSize=10)
        ),
        tooltip=[
            alt.Tooltip(field="count",type="temporal"),
            alt.Tooltip(field="series",type="nominal"),
            alt.Tooltip(field="date",type="temporal", format="%Y年%m月%d日 %H時%M分"),
        ],
    )
)

upper = (
    base.mark_line()
    .encode(alt.X("date:T",scale=alt.Scale(domain=date_brush)))
    .properties(height=300)
)

lower = (
    base
    .mark_area()
    .properties(height=100)
    .add_selection(date_brush)
)

upper & lower

2007年以降を拡大させた場合、以下のようになります。

スクリーンショット 2022-11-27 23.13.17.png

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?