この広告は、90日以上更新していないブログに表示しています。
Python での作例はここにある
https://github.com/Gedevan-Aleksizde/pysocviz/tree/main/notebooks
2021/8/8: 注意書きが英語のままだったので日本語版も追加した:https://github.com/Gedevan-Aleksizde/pysocviz/tree/main/notebooks/jp
ちなみに既に似たようなことをしている人はいるが, 5章までやって1年くらい更新していない (ただし6章以降はRの他のパッケージに依存した作例が多いことに注意)
https://github.com/jkgiesler/healy-viz
(あからさまな宣伝) 勤務先でも布教している. 勤務先の採用情報はここにある採用情報|株式会社ファンコミュニケーションズ FANCOMI
ggthemes::theme_wsj
,theme_economis
,ggrepel::geom_text_repel
,scales::wrap_format
をそこそこ似たような感じで再現した. また,broom::tidy
,coefplot
,statebins
なども本文中で紹介されるグラフを作るのに必要なので, 限定的であるがPython で機能を再現した. notebookから抜粋するとこのような見た目になる.gray70
などと指定してもエラーが出るので,dcolors
という名前のディクショナリにggplot2 で標準的に使われている色名に対応するカラーコードを収録した. よってcolor geom_line(color=colors.get('gray70'))
などと書くことができる.R のggthemes,colorblindr パッケージで用意されているカラーユニバーサルデザインに基づいた配色のスケール関数も用意した.
網羅的に書くのは難しいので, 主に原著の作例を再現する際に気づいた点を挙げていく.
Python と R の構文の違いから,aes(x='x')
のように文字列として指定する必要がある. しかしこの文字列は式として評価される (Pandas の.eval()
と似ているが, 適用できる処理はaes()
のほうが多く便利である). 基本的に変数は numpy 配列として扱われるため,aes(x='x.astype(str)', y='np.log(y.mean()**2)')
のような書き方も可能になる.
逆に変数ではなく文字列として与えたい (補助線にaes()
を与えたいときなど) 場合はaes(color='"line1"')
のような書き方で対処できる.
加えて, v0.7 からはaes()
内でのみ使える関数factor()
,reorder()
が使用できる. これはgplot2 で使える同名の関数の機能を提供するもので, 前者はカテゴリ変数の再定義, 後者はカテゴリ変数の並び順を再定義する.
これも構文の違いが原因. R のように
ggplot()+ geom_point()+ scale_x_log10()
のような改行はPython ではできない1. しかし一方でPython なら+=
が使用できるため
g = ggplot()g += geom_point()g += scale_x_log10()
という書き方はできる (R ユーザ向けに説明すると,g <- g + geom_*
と同じ).
たとえばcolor="gray70"
などと書くとエラーが発生する. これは plotnine が matplotlib ベースであり, matplotlib にはそのような名前の色が登録されていないのが原因.pysocviz.properties.dcolors
というディクショナリを用意したので,
from pysocviz.propertiesimport dcolorsggplot(d, aes(x='x', y='y'), color=colors['gray70']) + geom_point()
みたいな書き方をする. なお, 単色の場合は ggplot2 より少ないが, 一方でカラーパレットは matplotlib のほうが豊富である (後述).
これも matplotlib が原因の制約.ggplot2 の破線 と異なり,matplotlib の破線は3種類しかなく, 名称も異なる. この修正はちょっとやそっとではできないので,pysocviz.properties.linetypes
に Plotnine で使用できる実線 + 破線3種類の名前エイリアスのみ用意した. 使い方はdcolors
と同じ.
20201/8/9: plotnine の話になってなかったので加筆修正
これは去年 R の文字化け問題をまとめた時にも少し触れたが, 放置していたのでまだ完全には解決していない.plotnine で, かつJupyter やインタラクティブウィンドウでの表示に限定する (たぶんPNGやJPGも大丈夫. PDF形式での保存とかは考えない) ならば,theme()
関数にフォントファミリを指定するだけでなんとかなるだろう (おそらく Win/Mac/Ubuntu どれでも同じ). これが例
from plotnineimport *import pandasas pdd = pd.DataFrame(dict(x=range(3), label=list('あいう')))g = ggplot(d, aes('x','x', label='label'))g += geom_point()g += geom_label(nudge_x=.1, nudge_y=.1) g += labs(x='あああ', y='いいい', title='ううう')g + theme(text=element_text(family='Noto Sans CJK JP'))
私の PC (Ubuntu 20.04) では以下のようになった.family='Noto Sans CJK JP'
の部分は当然それぞれの環境にインストールされているフォントによって変わる. 例えば最近のMac なら'Hiragino Sans'
(ヒラギノ角ゴ, 少し古いバージョンだとHiragino KakuGo ProN
みたいな別の名前になっていた気がする?),Windows 8以降なら'Yu Gothic'
(游ゴシック) とかで動作すると思う.
毎回theme(...)
と書くのが面倒な場合は以下のようにtheme_set()
でデフォルトのテーマを変更してしまう.
theme_set(theme_gray(base_family='Noto Sans CJK JP'))
なおデフォルトの灰色の背景色が気に入らないならtheme_gray
をtheme_bw
やtheme_classic
とかに変更する.matplotlib のデフォルトの雰囲気に近いものはそのまんまtheme_matplotlib
という名前になっている. ただしtheme_matplotlib
にはbase_family
引数がないのでこのように書かねばならない.
theme_set(theme_matplotlib() + theme(text=element_text(family='Noto Sans CJK JP')))
しかし私は R と比べてmatplotlib のフォントの認識処理をまだよく理解できておらず, 標準フォントでもたまに認識できないことがある (例えばMac では游ゴシックはなぜかデフォルトでは認識されてない) 原因を特定できていない. また, PDF 画像として保存する際の設定も少し複雑で, まだいい方法を発見できていない.
ちなみにmatplotlib を使用した全般での話としては,matplotlib.rcParams['font.family']
に, 自分の環境で使用可能なフォントファミリ名を正しく入力することで対処できる.matplotlib のフォント関連のネット上の解説はミスリードなものや間違ったものがかなり出回ってるので早めに訂正したいが, もう少し時間がかかる.
テキストの重なり回避はgeom_text
/geom_label
で手動調整するか,adjustText をインストールした上でadjust_text={'arrowprops': {'arrowstyle': '-'}}
を指定するとggrepel を使用したものに近いものが出力される. いちおう,ggrepel::geom_text_repel
と同等の関数も pysocviz に用意したが, どうもうまく動かないことがあるようだ.
Plotnine 開発者が作ったmizani パッケージを使用する. 例えばパーセント表記は以下のような書き方で可能.
from plotnineimport *from mizani.formattersimport percent_formatggplot(...) + geom_point() + scale_x_continuous(labels=percent_format())
詳細は公式ドキュメントや pysocviz の作例を参考に.
色パレットの名称は RColorbrewer と matplotlib とである程度互換性がある. むしろ matplotlib のほうが, 最初から viridis などを使えたりとプリセットが豊富
具体的には以下のページで一覧を見ることができる.
matplotlibhttps://matplotlib.org/stable/tutorials/colors/colormaps.html
Rcolorbrewerhttps://www.r-graph-gallery.com/38-rcolorbrewers-palettes.html
上記に記載されている色パレット名で指定する場合はscale_color_brewer
,scale_color_cmap
,scale_colro_cmap_d
を使用する. (後者は discrete に対応).*_brewer
は本家と同じ構文だが,*_cmap
,_cmap_d
は連続・離散で最初から分けられているのでよりシンプルになっている.
現時点(v0.8)ではlabs()
にこれらを指定してもサブタイトルやキャプションは表示されない. これらに書き込みたいテキストは, 代わりにタイトルや軸ラベルに押し込むか,annotate()
で描画領域の邪魔にならないところに書くしかない.
現時点では plotnine に種類の異なるグラフを連結する機能はない2. 原著の Ch. 8 でもcowplot パッケージを使う例があるが, これは同じ種類のグラフを連結しているのでfacet_grid
である程度再現することができる.
ただし, facet 系の処理も同様の制約が理由で, strip label の位置移動 (strip.position
) など本家ggplot2 にある機能の一部が実装されていない.
しかし, あくまで個人の経験の範囲だが, 種類の異なるグラフを連結することが可視化として必須の処理とは思えない (単に1つの画像ファイルとしてまとめたい, というのは可視化とは別の次元の問題とする) ので, そこまで大きな問題ではないと考えている (つまり他のグラフの描き方を模索してほしい)
Plotnine ではそれぞれha
/va
という名前に置き換えられている. また, 0-1 の数値で指定できたものがそれぞれ以下のような選択式になっている.
ha
:'left'
-'center'
-'right'
(ha='left'
がhjust = 0
に対応)va
:'top'
,'bottom'
,'center'
,'baseline'
,'center_baseline'
ggplot2 とサイズの単位が違うため, 絶対値の指定だとバランスが変わる. たぶん ggplot2 は図形とテキストの単位が違う一方で, plotnine は統一している. (一方でインチで指定する必要があるものもある). またunit()
に対応するものはない.
ggplot2 と違い,geom_point
,geom_line
, などの点や線のsize
とgeom_text()
のsize
はどちらもポイント単位である. 一方でテーマでの設定の多くはインチ単位である. 相対的なサイズが変わってしまうのはおそらく主にこのあたりが関係していると思われる.
現時点 (v0.8) ではGAM はサポートされていない. 自分でGAM のパラメータや信頼区間を計算してプロットする必要がある. (statsmodels にGAM のクラスがある)
なお, 使用可能なモデル一覧は公式ドキュメントのこのページに書いてある.
statsmodels の R 風 formula 構文に基づいているので R の formula の完全なエミュレーションはできない.
この formula の使い方の詳細は statsmodels と pasty の公式ドキュメントを参照.
残念ながらこれも matplotlib の仕様が関係している. どうしても凡例を下部に表示したい場合, 現状は issue#245 で提示されている解決策を試すしかない.
legend_position
にはキーワードだけでなくタプルでデフォルトからの相対座標を指定することができる.legend_direction='horizontal'
で凡例の水平配置を強制することで, 下へのはみ出しを防ぐsubplot_adjust
に matplotlib の subplot パラメータの設定をディクショナリで与える (設定できる項目はmatplotlib の公式ドキュメント を参考に)この方法は作例でも数箇所で使っている.
Python の仕様上1行80字に収めるにはこういう改行するしかないので我慢してください.
シングル・ダブルクオーテーションが混在しているのは原著からコードをコピペしたときに修正するのがめんどくさかったらから.
データフレームを間違って指定した (名前を間違えて存在しないものを指定するなど) した場合, データフレーム名についてのエラーよりも先に列名がないというエラーが出ることがある (ので原因に気づきにくい)
jupyter 上で<ggplot: (XXXXX)>
のような表示が邪魔な場合は,(ggplot() + geom_point).draw();
のように.draw();
を呼び出すことで表示させないことができる. 末尾の;
も必須. これも matplotlib の制約が関係している.
2021/8/8 追記:はてなブックマークのコメントを眺めたところ,ggplot2 に詳しくない人間を想定した,ggplot2/plotnine がどこまでできて, 何ができないのかの補足説明が足りないと気づいたので追記した.
同じようなグラフを書こうとすると, ほとんどの場合コードが長くなり過ぎるし, 不正確になる恐れがある. 例えばggplot2/plotnine は散布図や折れ線グラフを色分けした時, 対応する凡例を自動で表示してくれる. 一方でmatplotlib はそれも手動で書く必要がある. そしてそのように手動で行う必要のないものを手動で書くという運用は先日書いたようにミスを増やす恐れがあるので使いたくない.
というのは半分建前でmatplotlib の煩雑な構文をそもそも覚える気がしない
Plotnine を選択したのは (1) R からの転換が容易 (2) HTML 以外の媒体でも適切にグラフを描画できることにこだわった結果なので, そういうのが必要ないならplotly やbokeh でやっても問題ないと思われる. 特にplotly はなんとなくggplot2 の構文に影響を受けている感じなので, 比較的参考にしやすいかもしれない.
最後のseaborn については, これもmatplotlib がバックエンドで, かつ設計思想はggplot2/plotnine と似ている. しかし私がこれまで使ってきて, (1)ggplot2/plotnine のaes()
とscale_*
,theme_*
関数に見られるような, 「データの意味を定義する部分」と「デザインを決める部分」との分離が,seaborn では徹底していないと感じたこと, (2) 何度か厄介な不具合3に出くわしたことがあった, (3) デフォルトデザインがちょっと過剰装飾でクドい (個人の感想です) のでだんだんと使わなくなった.plotnine もバグが皆無なわけではないが, 比較的妥協できるレベルのもの (少し見栄えが悪くなる程度) が多い. ただし, 公平な評価のために書いておくと, 私はそれ以前に長いことggplot2 を使ってきているのでplotnine の扱いに慣れるのは早かった.
ライバル視しているmatplotlib (実際にはplotnine のバックエンドで mpl. が動いているので対立させるのはおかしいが) は既に古くからあって, ググれば描きたいグラフに近い作例がすぐ見つかる, という利点があるかもしれない. しかし,Python ユーザはそもそも今までggplot2 の使い方についてググったことはないのではないだろうか?
私の考える,matplotlib と比較した際のplotnine の利点は,
が挙げられる.
ただし, 特定の限定的な分野で使われるグラフは自分で構築する必要がある. 本文7章でも紹介されている州ごとの得票率をカルトグラム状のヒートマップで表示させるにはかなり工夫が必要だし,ローソク足チャートなんかもやや手間がかかる (R の場合は金融関係特有のグラフはquantmod などの目的特化の専用パッケージがあるし,matplotlib と似たような構文の基本パッケージも使われていたりする).
理想というか願望を言うなら,Python でも R におけるggplot2 のように,plotnine をベースとした派生パッケージのエコシステムがユーザーコミュニティから自然に生まれてくれればもっと便利になるだろう. (というか私の pysocviz が嚆矢になってくれるとありがたい)
一方で明確に欠点と言えるのは,
あたりだと思う. (1) はたぶんどうしようもないので反論できない.
しかし (2, 3) のような「〇〇ができない」というのは必ずしも問題ではなく, 「やらないほうがいいことはできない or やりづらいようになっている」と捉えることもできる.plotnine には円グラフを描く機能はないし, 3次元棒グラフなどは言うまでもない. これらは『データ可視化入門』の1章で不適切な可視化手段として批判されており,gplot2/plotnine 開発者もおそらくするべきでないと考えているので使えない. 棒グラフを3Dにしても新たな情報は提示できないし, 円グラフも適切に比率の情報を見せることができないため, 代わりにパレート図や帯グラフなどを使ったほうがいい4.
一方で3次元グラフの中では3次元の散布図やsurface plot は, 真面目な用途でもそれなりに使われているようである. しかしggplot2/plotnine は3次元のグラフ全般を想定していないので描くのは非常に困難である. ただし, ものによっては代替不可能かもしれないが, 2次元のヒートマップ (あるいは等高線) や, 複数系列の折れ線グラフで伝えたい情報を示せるかもしれない.
とはいえ, 『データ可視化入門』で導入されているグラフ作成のルールがあらゆる分野で合意されたわけではないことも事実である. まず "socviz" という名前の示すように, この本は社会科学分野での応用を考えた作例が多く, 3次元プロットは重視していない. 例えば「指導教授から『このデータはこういうグラフで示すのがルールだ (学会での慣例だ)』と言われた」「上司/顧客からこのグラフで描いてほしいと言われた」とか言われると私個人では解決するのは難しい.
そんなわけで要約するとこうなる: ある分野に特有なグラフばかり描く必要がある場合は専用パッケージを使う必要もあるし, 全くできないこともあるのでplotnine は最強無敵ではない. しかしいろいろなグラフを描く必要がある場合は汎用的に使えて慣れれば応用もきくggplot2/plotnine のほうが便利ではないか, ということになる.
socviz のデータセットを Pandas データフレームに移植しようとして気づいたのだが, socviz のデータセットには変な属性情報が混入したデータセットがいくつか含まれていた. おそらく haven パッケージあたりで stata からインポートしたのだろうが, 例えばクラス名がlabelled
になっている変数があるなど, 誤作動の原因になりそうな余計な属性情報が見られた. 原著の範囲であれば問題は発生しないが, 今回のようにデータセットをエクスポートする際はこういった不要な属性情報はなるべく消すようにしたほうがいいと思う.
7章の statebins (全米の州ごとの選挙結果をプロットしているもの) の自作がとても大変だった. しかもこれを見るのはほとんど日本人ユーザだろうから, この作例以外で役に立つ可能性がない.
バックスラッシュを使うという手があるが後から修正するのが大変なのでお勧めしない↩︎
それをやるのは matploblib の仕様上大変に面倒/実現不可能だと開発者が述べている, issue#46 参照↩︎
そのうち1つは前回紹介している:https://ill-identified.hatenablog.com/entry/2021/07/28/231922↩︎
ただしggplot2 には円グラフを描く機能があるhttps://www.r-graph-gallery.com/piechart-ggplot2.html↩︎
«Чай не водка ― много не выпьешь»
著者: 片桐智志/ KATAGIRI, Satoshi
注意: どのアカウントも定期的に確認していないため、連絡を見落とす可能性があります。そのうちなんとかします。
LinkedIn:https://www.linkedin.com/in/satoshi-katagiri/
FB:https://www.facebook.com/satoshi.katagiri2
En:https://en-ill-identified.blogspot.jp/
bluesky:https://bsky.app/profile/ill-identified.bsky.social
こっちの読者層は絶対に読まない趣味の話だけのブログ:https://under-identified.hatenablog.com/
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。