LAC WATCH

セキュリティとITの最新情報

RSS

株式会社ラック

メールマガジン

サイバーセキュリティや
ラックに関する情報をお届けします。

ラックピープル | 

Pythonの可視化ライブラリを使いこなそう

こんにちは、イノベーション推進部イノベーション開発グループの青野です。
社内外のデータ活用やAIモデルの実装などを行う部署に所属しております。

さて、AIモデルの構築やデータ分析をするにあたり、プログラミング言語を必要としますが、よく使われる言語が「Python」や「R」です。特にPythonは広く使われており、AIモデルの構築やデータ分析のためのライブラリが豊富です。今回はその中でもPythonの可視化ライブラリについて、4種類のライブラリを比較しながら紹介したいと思います。

はじめに

私は2年間ほどデータ分析を使った、実業務の効率化や新たな取り組みへの意思決定のサポートを担当していました。データ分析の案件の中で、どの案件でも必ず行っていたことが「可視化」でした。

その中で可視化は

  • 業務有識者の方々が暗黙知として持っている知識ではあるが、明確にはわかっていないことをわかりやすく見せることができる
  • 現在、持っているデータのイメージをつけることができ、そのデータを使った業務改善に向けて取り組む促進剤になる
  • ということを学びました。

    Pythonのライブラリを使って可視化をすることはとても簡単で、「この図を書きたいならこのコード!」といったサンプルコードを紹介しているWebサイトが多くあります。特に、後述しますMatplotlibといったライブラリは少しややこしい部分もあるのですが、サンプルコードが世に多くあるため、深く学ばずとも、参照することで誰でも簡単に可視化することができるのです。

    しかし、高度な可視化をしようとするほど、Webサイトのサンプルコードだけでは、「もう少しこうしたいのだけど...」「ちょっと違うな...」といったことが増え、調べる作業に多くの時間をかけてしまう、ということがよくあります。私自身もそういった経験があり、Webサイトのサンプルコードをただ参照するだけでなく、可視化ライブラリの使い方をきちんと理解しようと思いました。その中で、Pythonの可視化ライブラリのややこしい書き方の区別や、高度な可視化が可能なJavaScriptベースの可視化ライブラリについて学びましたので、ご紹介していきたいと思います。

    Pythonライブラリの全体像

    Pythonの可視化ライブラリだけでも、数が多くどれを使えばいいのか迷うと思います。
    少し古い情報ですが、Pythonの可視化ライブラリの全体像です。

    引用先:Python's Visualization Landscape (PyCon 2017)
    引用先URL:Python's Visualization Landscape (PyCon 2017)

    今回はその中でも、基本的で必ずといっていいほど使うMatplotlibと、そのMatplotlibベースでより魅力的な可視化を提供するseaborn。そして、JavaScriptベースで静止画ではなく、インタラクティブに動かすことができる可視化を提供するBokehやplotlyについて紹介したいと思います。

    準備

    これから説明する内容は、Pythonの基本的な知識を持っていることと、Jupyter notebook上での作業を前提としますのでご了承ください。

    Project Jupyter | Home
    (Jupyter notebookについての説明は割愛します)

    自身でコーディングしながら色々触ってみて初めてイメージがつくと思うので、可能であれば実行環境を用意してから続きを読んでいただければと思います。正直なところ、読むだけだとなかなか頭に入りにくい内容です。

    まずは各ライブラリをインストールする必要があります。
    Jupyter notebookのセル内で以下を実行しましょう。

    !pip install matplotlib
    !pip install seaborn
    !pip install bokeh
    !pip install plotly
    

    Matplotlib

    最も使われている可視化ライブラリのひとつです。もともと数値解析ソフトウェア「MATLAB」利用者が簡単にPythonでも使えるようにと開発されたライブラリです。Pythonの可視化ライブラリはたくさんありますが、このMatplotlibがベースとなっていることも多くあるので、避けて通れないライブラリです。

    Matplotlibは一般に難しい(ややこしい)と思われがちなライブラリです。
    その原因のひとつが、「The pyplot API」と「The object-oriented API」の2つの書き方があるためです。

    The pyplot API:
    「MATLAB」利用者のための書き方で、「MATLAB」の操作感に近いです。
    単純な可視化をさくっと描画するために使われます。
    The object-oriented API:
    オブジェクト指向型の書き方です。高いカスタマイズ性があります。

    それぞれの書き方について説明します。

    The pyplot API

    早速The pyplot APIから見ていきます。

    import matplotlib.pyplot as plt # Matplotlibを使うときのおまじないです
     
    # データ準備
    x = [1, 2, 3] 
    y = [4, 5, 6]
     
    # The pyplot APIでの描画
    plt.scatter(x, y)
    plt.xlabel("x")
    plt.ylabel("y")
    plt.show()
    
    The pyplot APIを利用して描画したグラフ

    (何も面白くない図ですがご容赦ください...)
    描画部分のコードを見ていただいた通り、すべて「plt.xxx」で書かれています。
    この書き方こそが「The pyplot API」の書き方になります。

    複数グラフを描画したいときはこちらです。

    # データ準備
    names = ["A", "B", "C"]
    values = [1, 10, 100]
     
    # The pyplot APIでの描画
    plt.figure(figsize=(9, 3)) # 描画するサイズを決める
    plt.subplot(131) # 1行3列の左から1番目をこれから操作しますよ
    plt.bar(names, values) # 棒グラフ
    plt.subplot(132) # 1行3列の左から2番目をこれから操作しますよ
    plt.scatter(names, values) # 散布図
    plt.subplot(133) # 1行3列の左から3番目をこれから操作しますよ
    plt.plot(names, values) # 折れ線グラフ
     
    plt.suptitle("Categorical Plotting") # タイトルの設定
    plt.show() # 描画
    
    The pyplot APIを利用して描画したグラフ

    複数グラフを描画するときもすべて、「plt.xxx」となっていることが分かるかと思います。この書き方は後述しますが、描画全体であるFigureオブジェクトと、1グラフであるAxesオブジェクト(上でいうと、3つのAxesオブジェクトがあることになる)が明示せずとも自動で設定され、描画されていく書き方です。

    例えば、1つ前の例でいきなり「plt.scatter()」から始まっていますが、これはFigureオブジェクトが自動設定されていて、その中にAxesオブジェクトとなる散布図(scatter)が描画されているということになります。

    このように明示せずともパパっと描画できるので、サクッと使いたいときには非常に便利です。しかし、複雑な図になる程どこを操作しているのかが分からなくなったり、可読性が低くなったりする傾向にあります。そこで、明示的にオブジェクトを定義し、そのオブジェクトを操作するよう指示する方法が「The object-oriented API」です。

    The object-oriented API

    この書き方は上述したように、オブジェクトを明示的に定義していきます。
    その前にオブジェクトって何?となると思いますので、そちらを説明します。

    Matplotlibで特に重要なオブジェクトは2つです。上述していますが、FigureオブジェクトとAxesオブジェクトです。Figureオブジェクトは図全体を指します。Axesオブジェクトはデータを保持する部分で、グラフが複数あった場合は、その1グラフがAxesオブジェクトと考えてください。つまり、FigureオブジェクトはAxesオブジェクトを内包するような形になります。ではコードを見ていきます。

    x = [1, 2, 3]
    y = [4, 5, 6]
    fig, ax = plt.subplots() # Figureオブジェクトと1つのAxesオブジェクトを定義(「The pyplot API」にはこれがない)
    ax.plot(x, y); # Axesオブジェクト内のメソッドで描画
    
    The object-oriented APIを利用して描画したグラフ

    複数のグラフを描画する場合

    x = [1, 2, 3]
    y = [4, 5, 6]
    fig, (ax1, ax2) = plt.subplots(1, 2) # Figureオブジェクトと2つのAxesオブジェクトを定義
    ax1.plot(x, y) # 1つ目のAxesオブジェクト内のメソッドで描画
    ax2.scatter(x, y); # 2つ目のAxesオブジェクト内のメソッドで描画
    
    The object-oriented APIを利用して描画したグラフ

    ここでfigという変数を定義していますが、使っている場面がどこにもないと気づかれた方もいらっしゃると思います。
    figは、例えばJupyter notebookで操作していて別のセルで再度描画するときや、pngとして保存するときに出てきます。

    こんな感じです。

    fig.savefig("sample.png")  # 図全体をpngとして保存
    

    また、x軸のラベルを変更したり、タイトルを変更したり、目盛りの間隔を変更したい、というときは、Axesオブジェクトの方のメソッドで操作することになります。

    # ひとつ前の例の続きとします
    # 2つあるうち、左側の図の操作をしていきます
    import matplotlib.ticker as ticker
    ax1.set_xlabel("ラベルの変更")
    ax1.set_title("タイトルの変更")
    ax1.xaxis.set_major_locator(ticker.MultipleLocator(1)) # 目盛の間隔を1ごとにする
    fig # figの出番!
    
    The object-oriented APIを利用して描画したグラフ

    タイトルやラベルの日本語が豆腐になりました......。これはMatplotlibあるあるで、読み込んでいるフォントが日本語に対応していないからです。環境によって、どのフォントが入っているかが異なり、色々なやり方があるので、「Matplotlib 日本語」で検索してみてください。

    オンライン環境なら以下が便利かなと思います。

    # 日本語対応するためのライブラリをインストールする
    !pip install japanize-matplotlib
    # notebookの再起動が必要
    
    import matplotlib.pyplot as plt
    import japanize_matplotlib
    # これ以降の書き方は先ほどと一緒
    x = [1, 2, 3]
    y = [4, 5, 6]
    fig, (ax1, ax2) = plt.subplots(1, 2) # Figureオブジェクトと2つのAxesオブジェクトを定義
    ax1.plot(x, y) # 1つ目のAxesオブジェクト内のメソッドで描画
    ax2.scatter(x, y); # 2つ目のAxesオブジェクト内のメソッドで描画
     
    import matplotlib.ticker as ticker
    ax1.set_xlabel("ラベルの変更")
    ax1.set_title("タイトルの変更")
    ax1.xaxis.set_major_locator(ticker.MultipleLocator(1)) # 目盛の間隔を1ごとにする
    
    The object-oriented APIを利用して描画したグラフ

    これで、x軸のラベルやタイトル、x軸の表示の幅などを変更できたことが分かると思います。

    結局どっちで書けばいいの?

    ご紹介した2つの書き方を、ここで並べてみます。

    import numpy as np
    x = np.linspace(0, 2, 100)
     
    # The pyplot APIの書き方
    plt.figure(figsize=(5, 2.7))
    plt.subplot(121)
    plt.plot(x, x, label="linear")
    plt.plot(x, x**2, label="quadratic")
    plt.legend() # 汎用ラベルの表示設定
    plt.subplot(122)
    plt.plot(x, x**3, label="cubic")
    plt.legend();
     
    # The object-oriented APIの書き方
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(5, 2.7))
    ax1.plot(x, x, label="linear")
    ax1.plot(x, x**2, label="quadratic") 
    ax2.plot(x, x**3, label="cubic")
    ax1.legend()
    ax2.legend();
    
    The pyplot API、The object-oriented APIを利用して描画したグラフ

    どちらの書き方でも上図が描画されますが、この例では「The object-oriented API」の方が行数は少なくなりました。また、「The object-oriented API」の方が、「この行はどの図の何を操作しているか」が明示されているので、分かりやすいと個人的には思います。実はこの「The pyplot API」と「The object-oriented API」は、途中で書き方を切り替えられたりしますが、基本的には混乱をきたすので混合するような書き方はしません。

    ちなみに上の例では(そしてこれ以降の例も)つまらない描画ばかりになってしまいましたが、各ライブラリの公式サイトには、可視化例をまとめてくれているページがありますので、もしご興味があれば見てみてください。眺めるだけでも楽しいです。

    seaborn

    続いてはseabornというライブラリです。これはMatplotlibをベースとしたライブラリです。そのため、内部ではMatplotlibが動いていますが、seabornを使うとより魅力的な描画ができますよ、というライブラリです。MatplotlibベースのためMatplotlibの「The object-oriented API」の書き方を知らないとよく分からなくなります。

    さて、seabornには2つの書き方がある、というわけではないのですが、seabornの描画メソッドには「Figureレベル」と「Axesレベル」の2種類あります。ここで言っている「Figure」と「Axes」はMatplotlibのときと同じと考えてください。

    Figureレベル:
    Figureオブジェクト全体を管理しています。
    複数のグラフを一度に描画してくれるメソッドと考えてもらってよいと思います。
    戻り値はxxxGridというseaborn特有のオブジェクトです。
    Axesレベル:
    Axesオブジェクトのみを扱います。戻り値はAxesオブジェクトになります。

    以下でもう少し説明します。

    Figureレベル

    Figureオブジェクト全体を管理していると言いましたが、これはFigureレベルのメソッドを呼ぶと、MatplotlibのFigureオブジェクトとAxesオブジェクトを内部で一気に作ってくれるということです。

    import seaborn as sns # seabornを使うときのおまじないです
    df = sns.load_dataset("penguins") # サンプルデータの読み込み
    grid = sns.displot(df, x="flipper_length_mm", col="species", binwidth=3, height=3)
    grid.set_titles("{col_var} {col_name} aaa"); # xxxGrid特有のメソッドでタイトルを変更
    
    seabornのFigureレベルのメソッドを利用して描画したグラフ

    このように、sns.displot1行で3つのAxesオブジェクトを一気に作ってくれて、描画してくれます。ただし、戻り値がxxxGridというseaborn特有のオブジェクトなので、細かいカスタマイズはコード例の4行目のようにオブジェクト特有のメソッドで設定する必要があります。

    Axesレベル

    これはほとんどMatplotlibです。
    Matplotlibで使っていた

    ax.plot()
    ax.scatter()
    

    などの代わりにseabornのAxesレベルのメソッドを使うと、少し高度な図がパパっと描けます。

    fmri = sns.load_dataset("fmri") # サンプルデータの読み込み
    fig, ax = plt.subplots(figsize=(7, 5))
    sns.lineplot(x="timepoint", y="signal", hue="region", style="event", data=fmri, ax=ax) # ここだけseaborn
    ax.set_xlabel("fmri");
    
    seabornのAxesレベルのメソッドを利用して描画したグラフ

    このようにMatplotlibでは色々カスタマイズしないと描画できない図がseabornでは1行で書けます。特徴として、sns.lineplotの引数にax=axとあります。ここで描画したいMatplotlibのAxesオブジェクトを指定しているのです。後は、FigureオブジェクトやAxesオブジェクトを定義するところや、カスタマイズなどはMatplotlibです。Matplotlibをきちんと理解していないと使えないというのは、このような点でほとんどMatplotlibだからです。逆に言えば、Matplotlibを知っていれば容易に使えます。

    ちなみに、FigureレベルとAxesレベルの違いは、公式ドキュメントでメソッドを確認したときに、引数に「ax=」をとるかどうかの違いになります。あとは戻り値がxxxGridであるかAxesオブジェクトであるかでも違いが分かります。Figureレベルのメソッドに少し習得が必要ですが、覚える必要はなく、都度調べればいいと思います。

    Bokeh

    続いてはBokehです。これは上で紹介した2つのライブラリと大きく違う点があります。それはJavaScriptベースで、描画したグラフが動かせる点です。描画した時点で、グラフの拡大縮小や軸の移動などが可能です。

    流れをコードで説明します。

    # bokehを使うときのおまじないです(ちょっと多い)
    from bokeh.io import output_notebook, show
    from bokeh.plotting import figure
    from bokeh.resources import INLINE
    output_notebook(resources=INLINE)
     
    # 1. データを用意する
    x = [1, 2, 3, 4, 5]
    y = [6, 7, 2, 4, 5]
     
    # 2. figureを定義する
    p = figure(title="title", x_axis_label='x', y_axis_label='y')
     
    # 3. プロット方法を決める(ここでは折れ線グラフ)
    p.line(x, y, legend_label="label", line_width=2)
     
    # 4. 描画の詳細設定(ここでは汎用ラベルの設定)
    p.legend.location = "top_left" # 汎用ラベルの位置を左上にする
     
    # 5. 描画
    show(p) 
     
    
    Bokehを利用して描画したグラフ

    これだけでぐりぐり動かせるグラフを描けるのかと感動した記憶があります。
    その他、ネットワークグラフやGoogleMapなども描画することができます。

    複数グラフを描画したい場合はfigureを複数用意した後、

    column(p1, p2) # 縦並びの場合
    row(p1, p2) # 横並びの場合
    

    これらをshow関数に渡すだけです。

    書き方がMatplotlibと違います。Matplotlibは最初に枠を用意します。グラフが複数あるならその分の枠を用意して、そこにデータを埋め込んでいく。一方で、Bokehはグラフを1つずつ作って、最後に並べていく、というイメージです。BokehはFigureとAxesに分かれて定義したりせず、Figureを定義したら、そこからプロットのメソッドを呼び出します。そのため、初めて使ったときは、非常に直感的だなと思いました。

    しかし、3DやアニメーションはMatplotlibや後述するplotlyの方が豊富です。

    plotly

    最後のライブラリの紹介です。plotlyはBokehと同じように動かせるグラフの描画が可能なライブラリです。先にBokehを紹介しましたが、google trendによるとplotlyの方が人気です。

    plotlyを利用して描画したグラフ

    人気の真の理由はわかりませんが、Bokehよりメソッドが充実しているからだと思います。実はBokehは2022年11月時点で、箱ひげ図やヒストグラムなどの一般的な描画をするために1行でサクッと書けるメソッドはなく、複数行コードを書かないといけません。一方、plotlyはそれらのメソッドがすでに用意されています。

    さて、plotlyも描画の方法として、2つあります。
    ただし、Matplotlibのように書き方が思想によって2つあります、というわけではありません。

    graph_objects:
    複数の異なるグラフをカスタマイズして描画できる
    express:
    数行で簡単にグラフが書けます。ただし内部でgraph_objectを呼び出している

    この2つの使い分けは、描きたいグラフがメソッドで用意されているならexpress、描けないならgraph_objectsといった具合なのだと私は理解しました。

    同じ描画で2つの書き方を紹介します。

    ※ notebook内で描画されない場合はこちらをご確認ください。
    【Python】Plotly と Jupyter Labでグラフが表示されない場合の解決方法 | 月見ブログ

    import pandas as pd
     
    # サンプルデータの準備
    df = pd.DataFrame({
      "Fruit": ["Apples", "Oranges", "Bananas", "Apples", "Oranges", "Bananas"],
      "Contestant": ["Alex", "Alex", "Alex", "Jordan", "Jordan", "Jordan"],
      "Number Eaten": [2, 1, 3, 1, 3, 2],
    })
     
    # expressの書き方
    import plotly.express as px # expressを使うときのおまじないです
     
    fig = px.bar(df, x="Fruit", y="Number Eaten", color="Contestant", barmode="group")
    fig.show()
     
     
    # graph_objectsの書き方
    import plotly.graph_objects as go # graph_objectsを使うときのおまじないです
     
    fig = go.Figure()
    for contestant, group in df.groupby("Contestant"):
        fig.add_trace(go.Bar(x=group["Fruit"], y=group["Number Eaten"], name=contestant,
        hovertemplate="Contestant=%s<br>Fruit=%%{x}<br>Number Eaten=%%{y}<extra></extra>"% contestant))
        fig.update_layout(legend_title_text = "Contestant")
        fig.update_xaxes(title_text="Fruit")
        fig.update_yaxes(title_text="Number Eaten")
    fig.show()
    
    plotlyを利用して描画したグラフ

    圧倒的にexpressの方が短いです。
    とはいえ、expressで実現できない描画はgraph_objectsを使わないといけないので、大まかな流れを紹介します。

    graph_objects

    Figureオブジェクトをまず定義します。その後、Traceオブジェクト(MatplotlibでいうAxesオブジェクト)をFigureオブジェクトに追加していくようなイメージでコードしていきます。FigureオブジェクトにTraceオブジェクトを追加していくという流れはMatplotlibと少し異なるところです。

    from plotly.subplots import make_subplots
    fig = make_subplots(rows=1, cols=2) # 1行2列のグラフ
     
    fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6]), row=1, col=1) # 1行1列目のグラフをfigに追加
    fig.add_trace(go.Scatter(x=[20, 30, 40], y=[50, 60, 70]), row=1, col=2) # 1行2列目のグラフfigに追加
     
    # 詳細設定
    fig.update_layout(height=600, width=800, title_text="Side By Side Subplots") 
    fig.update_xaxes(title="b", row=1, col=2)
     
    fig.show() # 描画
    
    plotlyを利用して描画したグラフ

    アニメーション

    plotlyのすごい点の1つは、expressにある多くのメソッドはアニメーションにすることができるところです。Matplotlibでもできますが結構面倒な印象を持っていました。ですが、plotlyは以下のように2行でアニメーションの可視化ができるのです!

    ※ matplotlib アニメーションコード例
    Examples -- Matplotlib 3.6.2 documentation

    df = px.data.gapminder() # サンプルデータの読み込み
    px.scatter(df, x="gdpPercap", y="lifeExp", animation_frame="year", animation_group="country",
               size="pop", color="continent", hover_name="country",
               log_x=True, size_max=55, range_x=[100,100000], range_y=[25,90])
    
    plotlyを利用して描画したグラフ

    そのほかBokehと同じようにネットワークグラフやGoogleMapなども描画可能です。

    Bokehとplotly

    plotlyは3Dやアニメーションのメソッドが用意されており、かつ数行で書けるため、かなり優位です。Bokehにあって、plotlyにないものはなさそうでした。ただし、あるブログによると、データ処理速度やダッシュボードでの応答速度はBokehの方が速いそうです。確かにplotlyに対して少し重い印象は業務で使っていて感じました。しかし、そこまで大きなデータを扱うといったことがなければ、plotlyを使う方がよさそうです。

    Bokeh Vs Plotly: Which One Is Better In 2022? - Buggy Programmer

    さいごに

    Matplotlibはややこしいですが理解すると他のライブラリも理解しやすいです。また、基本的には高カスタマイズ性を手に入れるためにも「The object-oriented API」を使うことをお勧めします。JavaScriptベースのライブラリの2つについては、plotlyの方がメソッドは豊富ですし、Bokehは箱ひげ図やヒストグラムを書くために一苦労必要なことから、plotlyを習得する方がいいと思います。

    また、私が思う可視化ライブラリの選定の流れは、まずは数行で簡単に書けないかをseabornやplotlyのexpressで確認する。なければいずれかのライブラリ(自身が使いやすいと思うライブラリ)で頑張って書く、です。

    この記事では面白い可視化はしておりませんが、いざ業務等でデータの可視化をしてみると、それだけでも十分有用であることがあります。何かご自身が扱っているデータで「可視化してみたら面白そう!便利そう!」といったことがあれば、ぜひPythonの可視化ライブラリを使いこなして積極的に可視化をしていただければと思います。他にも色々なライブラリがありますので興味があればそちらもぜひ習得してみてください!

この記事は役に立ちましたか?

はい いいえ