LoginSignup
87
82

More than 1 year has passed since last update.

Seabornがめっちゃ進化していた!

Last updated at Posted at 2022-06-20

Next-generation seaborn interface

なんか、知らない間にseabornのinterfaceがめちゃくちゃ進化していたので共有 (https://seaborn.pydata.org/nextgen/demo.html) 。みんなが何かカッコいい図を使いたいときにとりあえず使うライブラリ。それがSeaborn。しかし、結局細かい調整をしようとすると、くそ長いパラメータを与えるか、MatplotlibのAPIを直接叩く羽目になってしまう。そもそも、現状のSeabornは描画Methodsが細分化されていて、Seaborn単体でも覚えなきゃいけないこと結構多い。ということで、慣れてくるとMatplotlibで1から書いた方が早くなってきて、筆者はあまり使わなくってしまっていた。

しかし、最新のseabornのプレリリースでinterfaceが大きく変更されていて、さまざまな形式のplotを統一的な記法で表現できるように進化していた。下記の例を見てもらえば分かると思うが、この新記法はRのggplot2にそっくりなので、ggplot2を使ったことがある人には馴染みやすいかも。

import seaborn as sns
sns.set_theme()
tips = sns.load_dataset("tips")

import seaborn.objects as so
(
    so.Plot(
        tips, "total_bill", "tip",
        color="smoker", marker="smoker", pointsize="size",
    )
    .facet("time")
    .add(so.Scatter())
    .configure(figsize=(7, 4))
)

開発者もggplotに似ていることは認めていて、https://seaborn.pydata.org/nextgen/ で、そのことについて触れている。ただ、ggplotを移植することを目的としているわけではなくて、pythonらしい書き方ができる新しいインタフェースを目指している模様。

使い方

以下のコマンドでalpha版をインストールすることができる。

pip install https://github.com/mwaskom/seaborn/archive/refs/tags/v0.12.0a0.tar.gz

正直、開発者の説明ページ(https://seaborn.pydata.org/nextgen/demo.html) を見てもらったほうが早いのだが、少し特徴的だなと思った点について触れていく。

Plot object

ggplotのggplotに対応するもの。データを読み込んでPlot class objectを作成することが新インタフェースで最初にしなければならないことになる。Plot objectを作成した時点では、何もデータは描画されず、空のaxes objectが作られるのみである。

import seaborn as sns
sns.set_theme()
import seaborn.objects as so
tips = sns.load_dataset("tips")
so.Plot(tips, x="total_bill", y="tip")

ggplotと違う点としては、ggplotでいうaes 関数を噛ませないで、x, yなどを直接指定するところだろうか。

add method

ggplotの+に対応するもの。以下のような感じでplotすることができる。

so.Plot(tips, x="total_bill", y="tip", color="time").add(so.Scatter())

まさしくggplot。このadd methodは、Scatter()以外にもLine()Bar()のような描画クラス(Mark class objects)が用意されているので、簡単に別のタイプのplotをつくることができる。ただしalpha versionには heatmap を作る機能がなかったりなど、まだ作れないplotもある。
add methodには、グラフの種類を指定する Mark class objects 以外にも、データを統計処理などによって変換する Stat class objects。描画の際のデータの重なりを解消する Move class objects を与えることができる。この3つ種類のobjectを組み合わせることで、複雑なデータも綺麗に視覚化することが可能になっている。以下では、Stat class objects の1つAgg classとMove class objectsの1つDodge classについて簡単に紹介する。

Stats (Agg class)

既に記したようにadd methodには、単にScatter()Line()Bar()のような Mark class だけではなく、dataをどのように変換するかを指定するAgg classを指定することができる。その結果Mark classは元データをそのまま描画するのではなく、Agg classで処理されたデータを描画することになる。
これはめちゃくちゃ便利。

例えば、以下のようなplotを考える。

fmri = sns.load_dataset("fmri").query("region == 'parietal'")
so.Plot(fmri, x="timepoint", y="signal", color="event").add(so.Scatter())

どうやら、x軸の値は離散的であるため、x軸の値が同じ群を平均化したりするとデータの全体像を掴めそうである。そういうときに使えるのが Agg class

fmri = sns.load_dataset("fmri").query("region == 'parietal'")
so.Plot(fmri, x="timepoint", y="signal", color="event").add(so.Scatter(), so.Agg())

なるほどなぁ。これは便利だ。Agg classを与えることで、データをxの値が同じ群ごとで平均化し、それを Scatter() で plotしてる。デフォルトだとAgg()は"mean"で統計処理してる見たいだけどAgg(func="median")みたいな指定もできるみたい。

add methodはggplotでの+と同じで重ねてつかうことができるので、以下のように、平均を示すline plotと分布を示すscatterの組み合わせたplotもすぐに作ることができる。(こういうのは、これまでのSeabornでは結構面倒臭かった。)

(
    so.Plot(fmri, x="timepoint", y="signal", color="event")
    .add(so.Scatter())
    .add(so.Line(),so.Agg())
)

すごい。しかもlegendの表示も調整されていて、alpha版といいつつ結構作り込まれているようである。

Moves (Dodge class)

実をいうと、まだ挙動がよく理解していない。。。グループ化されたbar plotがjitter plotなどが互いに重ならないように調整してくれるclassであるようだ。以下のように使う。
まず、Dodge classを用いなかった場合の単純なbar plotの例.

so.Plot(tips, "day", "total_bill", color="time").add(so.Bar(), so.Agg())

こんな感じで、colorの異なる2つのbarが重なってしまう。そういうときに使うのが Dodge class。これを使うと、

so.Plot(tips, "day", "total_bill", color="time").add(so.Bar(), so.Agg(), move=so.Dodge())

みたいになって、重なりを解消してくれる。
複雑なgroup化をした場合にも、重なりを自動で解消してくれるっぽい。以下がその例。

(
    so.Plot(tips, "day", "total_bill", color="time", alpha="sex")
    .add(so.Bar(), so.Agg(), move=so.Dodge())
)

Scale function (scale method)

名前のとおり、scalingするためのfunction。ただ、x, yだけじゃなくて、colorとかについて簡単にscalingできるようになっていて使いやすい。以下、colorのlog scaleの変換例。

planets = sns.load_dataset("planets").query("distance < 1000")
(
    so.Plot(planets, x="distance", y="orbital_period", color="mass")
    .scale(x="log", y="log", color=so.Continuous("Reds", transform="log"))
    .add(so.Scatter(fillalpha=1.0))
)

facet method

ggplotのfacet_wrapに対応するもの。普段からggplotを利用している人には分かると思うが、facetに指定したカテゴリー名の値ごとにsubplotを作ってくれる。

On method

個人的に、一番感動したのがこれ。on methodをつかうことで、plotするFigure/subFigure or axes objectを指定することができる。つまり、新しいSeabornではもうFigure level functionとaxes level functionの区別がないということである。これを使うと以下のような使い方ができる。

f = mpl.figure.Figure(constrained_layout=True, figsize=(8, 4))
sf1, sf2 = f.subfigures(1, 2)
(
    so.Plot(tips, x="total_bill", y="tip", color="day")
    .add(so.Scatter())
    .on(sf1)
    .plot()
)
(
    so.Plot(tips, x="total_bill", y="tip", color="day")
    .facet("day", wrap=2)
    .add(so.Scatter())
    .on(sf2)
    .plot()
)

Patchworklibの対応

せっかくなので、自作libraryのpatchworklibを対応させて、ggplot+patchwork再現してみることにした。
on methodが実装されたこともあり、対応は以前のSeabornに比べると非常に楽だった。

import seaborn as sns
import seaborn.objects as so
import patchworklib as pw
sns.set_theme()
tips = sns.load_dataset("tips")
g1 = pw.load_ngs(
        so.Plot(
            tips, "total_bill", "tip",
            color="smoker", marker="smoker", pointsize="size",
        )
        .facet("time")
        .add(so.Scatter())
        .on(pw.basefigure)
        .plot()
     )
g2 = pw.load_ngs(
        so.Plot(fmri, x="timepoint", y="signal", color="event")
        .add(so.Scatter())
        .on(pw.basefigure)
        .plot()
     )

(g1.outline|g2.outline).savefig()

download.png

今後への期待

version 0.11 までのSeabornのplotを完全再現できるようにはまだなっていないので、今後のupdateに期待したい。一方で、公式のドキュメントを読むと分かるが、描画関数(Mark function)やStat functionは自作することもできるみたいなので、現状足りないものは自分で作ればいいのかもしれない笑。
いずれにしても、このinterfaceが今後のpythonでのグラフ描画の中心的存在になっていくことはほぼ確実だと思うので、早めに慣れておくといい気がする。

87
82
2

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
87
82