はじめに
データ間のネットワークやフローのような相互関係を表現する際には円形でのデータ可視化がよく行われます。下記のような図はChord Diagramと呼ばれており、各データの相互関係を分かりやすく美しく可視化することができます。
↑ Chord Diagramプロット例 (https://jokergoo.github.io/circlize_book/book/the-chorddiagram-function.html)
またバイオインフォマティクス(情報生物学)分野ではゲノム情報や遺伝子ネットワークの可視化表現にCircosという円形データ可視化ツール(Perlモジュール)が広く使われており、様々なデータの数値・統計量・相互関係の円形可視化がされているのをよく目にします。
↑ Circosプロット例 (http://circos.ca/)
本記事では、Chord DiagramやCircosのような円形データ可視化をPythonのAPIから簡単かつ柔軟に実行できるように個人開発したパッケージ「pyCirclize」(GitHub / Document)について紹介します。例として、以下のような円形データ可視化を実行することができます。
開発目的
Rではcirclizeが円形データ可視化パッケージとして有名で多くのユーザーに利用されています。circlizeは円形データ可視化のための高水準・低水準APIが数多く用意されており、ユーザーは柔軟に円形データ可視化のためのコードを記述することができます。
その一方、Pythonではcirclizeほど機能が豊富かつユーザーから広く利用されている円形データ可視化パッケージはまだ見当たらないのが現状です。そこで、Pythonでもcirclizeのような可視化ができるようにすることを目的として「pyCirclize」の開発に取り組みました。
実装機能
pyCirclizeは名前からも分かる通り、circlizeを参考にして開発をしており、円形描画レイアウトの考え方や各種描画API仕様等で似通っている部分があります(参考:circlize - Circular layout)。Pythonの標準的な描画パッケージであるmatplotlibをベースにcirclizeっぽいデータ可視化機能を実装しています。
本項では、pyCirclizeで可視化をする上での基本的なレイアウト設計とデータ描画機能について簡単に説明します。
-
円形描画レイアウト設計
円形描画レイアウトはユーザーの入力する 1. データ数・サイズ、2. 度数範囲指定(-360°〜360°)、3. スペース度数指定、によって決定されます。入力データ一つに対して一つのセクター(Sector)を定義し、データサイズ・度数範囲・スペース度数に応じて円形領域における適切なセクターサイズが決定されます。各セクター内には各種データを描画するためのトラック(Track)を自由な位置に複数配置することができ、トラックに定義された各描画APIを呼び出すことでトラック内にデータを描画できます。
例えば、下図は5つのデータ(A=10、B=15、C=12、D=20、E=15
)で度数範囲=0〜360°
、スペース度数=5°
に対するセクター及びトラックの描画レイアウト設定例です。
-
データ描画機能
セクターに設定したデータに紐づく何かしらの数値・統計量をLine・Bar・Point等のグラフあるいはArrow・Rectangle・Tree等の図形としてトラックに描画することができます。ユーザーが入力したXY直交座標系データをトラック範囲内の適切な極座標系データへ変換を行った上で描画が実行されます。またデータ内あるいはデータ間でのネットワークやフロー等の関連性を示すためのデータリンク(無方向・順方向・逆方向・双方向の指定可)を描画する機能も実装されています。
文章のみで説明されても少しイメージしづらいと思います。下図のデータ可視化例も参照すると、どのようにデータ描画ができるのか直感的に分かりやすいかなと思います。
インストール方法
pyCirclizeはPyPIとconda-forgeにそれぞれパッケージを登録しているので、pip
またはconda
コマンドでインストール可能です。
PyPIパッケージ
pip install pycirclize
Condaパッケージ
conda install -c conda-forge pycirclize
API実行例
pyCirclizeの基礎・実践のAPI実行例について、いくつか参考に記載します。実践例にはバイオインフォマティクス(情報生物学)分野のゲノム可視化例が多いです。
基礎
pyCirclizeのGetting Startedドキュメントより実行例を一部抜粋したものを記載しています。基礎的な実行例に興味がある方は、Plot API Exampleドキュメントと併せて参照するとpyCirclizeに実装されているプロット機能の理解が深まると思います。
セクター・トラックのレイアウト可視化
from pycirclize import Circos
# Initialize circos sectors
sectors = {"A": 10, "B": 15, "C": 12, "D": 20, "E": 15}
circos = Circos(sectors, space=5)
for sector in circos.sectors:
# Plot sector axis & name text
sector.axis(fc="none", ls="dashdot", lw=2, ec="black", alpha=0.5)
sector.text(f"Sector: {sector.name}={sector.size}", size=15)
# Set Track01 (Radius: 75 - 100)
track1 = sector.add_track((75, 100))
track1.axis(fc="tomato", alpha=0.5)
track1.text(track1.name)
# Set Track02 (Radius: 45 - 70)
track2 = sector.add_track((45, 70))
track2.axis(fc="cyan", alpha=0.5)
track2.text(track2.name)
# Set Track03 (Radius: 15 - 40)
track3 = sector.add_track((15, 40))
track3.axis(fc="lime", alpha=0.5)
track3.text(track3.name)
circos.savefig("basic_example01.png")
トラックへのデータプロット
from pycirclize import Circos
import numpy as np
np.random.seed(0)
sectors = {"A": 10, "B": 15, "C": 12, "D": 20, "E": 15}
circos = Circos(sectors, space=5)
for sector in circos.sectors:
# Plot sector axis & name text
sector.text(f"Sector: {sector.name}", r=110, size=15)
# Create x positions & randomized y values for data plotting
x = np.arange(sector.start, sector.end) + 0.5
y = np.random.randint(0, 100, len(x))
# Plot line
line_track = sector.add_track((75, 100), r_pad_ratio=0.1)
line_track.axis()
line_track.xticks_by_interval(1)
line_track.line(x, y)
# Plot points
points_track = sector.add_track((45, 70), r_pad_ratio=0.1)
points_track.axis()
points_track.scatter(x, y)
# Plot bar
bar_track = sector.add_track((15, 40), r_pad_ratio=0.1)
bar_track.axis()
bar_track.bar(x, y)
circos.savefig("basic_example02.png")
データ内・データ間リンクのプロット
from pycirclize import Circos
sectors = {"A": 10, "B": 20, "C": 15}
name2color = {"A": "red", "B": "blue", "C": "green"}
circos = Circos(sectors, space=5)
for sector in circos.sectors:
track = sector.add_track((95, 100))
track.axis(fc=name2color[sector.name])
track.text(sector.name, color="white", size=12)
track.xticks_by_interval(1)
# Plot links in various styles
circos.link(("A", 0, 1), ("A", 7, 8))
circos.link(("A", 1, 2), ("A", 7, 6), color="skyblue")
circos.link(("A", 9, 10), ("B", 4, 3), direction=1, color="tomato")
circos.link(("B", 5, 7), ("C", 6, 8), direction=1, ec="black", lw=1, hatch="//")
circos.link(("B", 18, 16), ("B", 11, 13), r1=90, r2=90, color="violet", ec="red", lw=2, ls="dashed")
circos.link(("C", 1, 3), ("B", 2, 0), direction=1, color="limegreen")
circos.link(("C", 11.5, 14), ("A", 4, 3), direction=2, color="chocolate", ec="black", lw=1, ls="dotted")
circos.savefig("basic_example03.png")
実践
実践例のソースコードは長くなるので、ここには記載しません。代わりにドキュメントへのリンクをそれぞれ貼ってあるので、興味がある方はそちらからソースコードを参照してください。
Chord Diagram例 ①
下記の行列データを入力として、Chord Diagramをプロット (ドキュメント)
K L
A 83 79
B 90 118
C 165 81
D 121 77
E 187 197
F 177 8
G 141 127
H 29 27
I 95 82
J 107 39
Chord Diagram例 ②
下記の行列データを入力として、Chord Diagramをプロット (ドキュメント)
A B C D E F G H I J
A 51 115 60 17 120 126 115 179 127 114
B 108 138 165 170 85 221 75 107 203 79
C 108 54 72 123 84 117 106 114 50 27
D 62 134 28 185 199 179 74 94 116 108
E 211 114 49 55 202 97 10 52 99 111
F 87 6 101 117 124 171 110 14 175 164
G 167 99 109 143 98 42 95 163 134 78
H 88 83 136 71 122 20 38 264 225 115
I 145 82 87 123 121 55 80 32 50 12
J 122 109 84 94 133 75 71 115 60 210
バイオインフォマティクス分野のデータ可視化に馴染みがない方は、以降の図の意味を理解するのは難しいと思います。ただpyCirclizeによる可視化の柔軟性を示す例として参考にはなるかと思います。
バクテリオファージゲノム
GFFファイルを入力として、腸内細菌を宿主とするバクテリオファージ(Enterobacteria phage)のゲノム特徴を可視化 (ドキュメント)
大腸菌ゲノム
Genbankファイルを入力として、大腸菌(Escherichia coli)の各種ゲノム特徴を可視化 (ドキュメント)
ヒトゲノム
UCSCの各種ヒトゲノムデータ(hg38)を入力として、ゲノム特徴及び重複セグメントの一部をリンクとして可視化 (ドキュメント)
系統樹
Ensemblデータベースの脊椎動物(Vertebrates)における種の系統樹を色付きで可視化 (ドキュメント)
開発で苦労した点
振り返り・備忘録も兼ねて開発で苦労した点を記載します。
極座標(Polar)プロット
今回のpyCirclizeの開発で初めて一からまともにmatplotlibの極座標(Polar)プロット機能を触ってみました。XY直交座標系とはデータの取り扱いが当然異なるのでかなり戸惑いました。度数(Degree)
を弧度(Radian)
へ変換したり、セクターやトラック内データの(X座標
, Y座標
)を(弧度(Radian)座標
, 半径(Radius)座標
)へ変換したりと直交座標 -> 極座標
で都度座標の変換が必要だったのが、数学が苦手な自分にはなかなかに面倒な作業でした。コーディング中に「今どの位置の何の座標系のデータを取り扱っているんだっけ??」と混乱しながら何とか実装していました。
描画速度の効率化
matplotlibで大量データのプロットを適当に実装すると地味に描画時間がかかることがあります。pyCirclizeでは以下の実装方針を取ることで描画速度を可能な限り効率化することを試みました。
-
Collectionを利用した一括描画
開発当初は描画API呼び出し毎に、逐次プロットしていく方法をとっていましたが、それでは求める描画速度に到達しませんでした。matplotlibではPatch(図形の基底クラス)に属するオブジェクトをCollectionとして一括描画することで、逐次プロットするより描画速度を効率化できる仕組みがあります(参考)。そこで、描画API呼び出し毎にPatchオブジェクトを生成・収集し、最後にPatchCollectionとしてまとめて描画する仕様に変更した。
この効率化を実現するために本来は簡単な描画処理も少し煩雑に実装する必要が出てきて、開発に苦労する一因になりました。ただこの効率化のおかげで、ある程度大きなデータでもそこそこ速く描画はできるようになったと思います。
ドキュメント作成
開発で苦労「した」点、というよりも現在も苦労「している」点です。
pyCirclizeには様々なプロット機能を実装していますが、全ての機能詳細をドキュメントとして文章化するのはかなり面倒で難しい作業です。2022年12月現在では、とりあえず実行例を羅列しているだけの感じになっているドキュメントサイトをGitHub Pages上でmkdocsを利用して作成・公開しています。今後、もうちょっと良い感じのドキュメントに仕上げられるといいなと思います。
最後に
pyCirclizeをとりあえず公開してみても良いかなと思えるレベルまでは開発してみましたが、正直circlizeの機能・ドキュメントの豊富さにはまだまだ遠く及びません。circlizeは2014年くらいから継続的に開発されている洗練されたRパッケージなので、開発期間2ヶ月程度のpyCirclizeが及ばないのは当然ではあるのですが。。。
ただpyCirclizeも基本的な円形データ可視化を実行する上で必要な機能は一応実装できているかなと思います。またPythonで円形データ可視化が簡単かつ柔軟にできることに意味があるとも考えているので、興味がある方はぜひ実際に触ってみてください。
pyCirclizeが「いいね!」と思う方がいたら、GitHubでStarを付けてもらえると嬉しいです。