依存を可能な限り少なくして、irisデータセットの散布図を描きたい

グラフを描きたいと思ったときには以下の2つ場合がある

  • 絵が描きたい
  • データを分析したい(EDA)

今回は概ね前者の場合の話。ついでにnumpy,scipy,pandasなどに依存したくない。依存はこれだけ。

$ pip freeze
pygal==2.4.0
vega-datasets==0.8.0

依存を可能な限り少なくして絵を描きたい

絵(グラフ)を描くためには以下2つが必要。

いちいち手元にファイルをダウンロードしてどこかに保存するということは面倒。 そして動かすのに依存が多いものもまた面倒。準備が可能な限り楽であってほしいし。不要な依存の読み込みなどで時間がかかってほしくもない。

irisデータセット

有名なやつ

This famous (Fisher's or Anderson's) iris data set gives the measurements in centimeters of the variables sepal length and width and petal length and width, respectively, for 50 flowers from each of 3 species of iris. The species are Iris setosa, versicolor, and virginica.

R

Rでirisという名で入っていたり。

# help(iris)
> summary(iris)
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width
 Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100
 1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300
 Median :5.800   Median :3.000   Median :4.350   Median :1.300
 Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199
 3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800
 Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500
       Species
 setosa    :50
 versicolor:50
 virginica :50

scikit-learn

scikit-learnにも入っていたり。

from sklearn import datasets
import pandas as pd

iris = datasets.load_iris()
df = pd.DataFrame(data=iris["data"], columns=iris["feature_names"])
print(df.describe())

ちなみに、pandasとRの互換表はこのあたりを見ると良い。

vega-datasets

ただ、どれも環境を整えるのがちょっと面倒。vega-datasetsの方が依存が少ないかもしれない。こちらのほうが依存が少ないので読み込みなどが早い。

from vega_datasets import data

df = data.iris()
print(df.describe())

(追記: それぞれの実行時間)

実行時間。おおよそデータのロード時間。不要な依存モジュールを読み込んでいるほど遅くなる。

# R
$ make 00
time r CMD BATCH --vanilla --slave 00iris.r 00iris.r.out
        0.88 real         0.17 user         0.12 sys

# sklearn
$ make 01
time python 01iris.py  | tee 01iris.py.out
...
real    0m4.661s
user    0m1.130s
sys     0m0.523s

# vega_datasets
$ make 02
time python 02iris.py  | tee 02iris.py.out
...
real    0m0.599s
user    0m0.500s
sys     0m0.141s

# vega_detasets with importlib.util.find_spec()
$ make 03
time python 03iris.py  | tee 03iris.py.out
...
real    0m0.078s
user    0m0.038s
sys     0m0.028s

gist https://gist.github.com/podhmo/afe65fc26f11c4e4e020654655e44175

pygal?

github.com

SVGを出力してくれるグラフライブラリ。最終更新日が2年前だったりライセンスがLGPLだったりとちょっと気になる点があるものの、依存がゼロなのでインストールも実行もはやい。こういうお絵描きのときには便利かもしれない。

ただ、複雑な機能を使わずお絵かきだけに徹するライブラリというのは、一昔前という感じはするかもしれない。最近のもの(?)はpandas前提のものが多い気がする。

最小のインストール

vega_datasetsは実はpandasに依存している。このpandasのインストールをスキップしたい。

$ pipdeptree -p vega-datasets
vega-datasets==0.8.0
  - pandas [required: Any, installed: 1.0.3]
    - numpy [required: >=1.13.3, installed: 1.18.2]
    - python-dateutil [required: >=2.6.1, installed: 2.8.1]
      - six [required: >=1.5, installed: 1.14.0]
    - pytz [required: >=2017.2, installed: 2019.3

pipに--no-depsオプションを付けると依存ライブラリを取得せずに自信だけをインストールしてくれる。そんなわけで以下の様な感じでインストールする。

# $ mkdir foo
# $ python -m venv foo
# $ cd foo
# $ . bin/activate

$ python -m pip install pygal
$ python -m pip install --no-deps vega_datasets

インストールされたパッケージは2つだけ

$ python -m pip freeze
pygal==2.4.0
vega-datasets==0.8.0

最小のデータ取得

直接vega_datasetsをimportしてしまうと、pandasをimportしようとしてエラーになる。まぁそれはそう。

>>> import vega_datasets
ModuleNotFoundError: No module named 'pandas'

実際にはモジュールのimportをせずに、あるモジュールがインストールされた位置のファイルを開きたい。具体的には vega_datasets/_data/alias.jsonにアクセスしたい。

importlib.util.find_specを使うとspecオブジェクトが手に入る。これはモジュールをimportする諸々の途中で使われるオブジェクト。そんなわけでこれを使ってModuleNotFoundErrorを迂回できる。こんな感じで。

# import vega_datasets # error on import panda
import pathlib
import json
import importlib.util

spec = importlib.util.find_spec("vega_datasets")
dirpath = spec.submodule_search_locations[0]
with (pathlib.Path(dirpath) / "_data/iris.json").open() as rf:
    data = json.load(rf)

テキトーに散布図を描く

どうやらpygalはpython2.xが主流だった頃の設計らしく、is_unicode=Trueをつけてあげないとbytesを返すようだ。そんなわけでこんな感じでコードを書けばirisデータセットの散布図が手に入る。

こんなかんじ。

scatter plot

import pygal
import pathlib
import json
from collections import defaultdict
import importlib.util


# dataset
spec = importlib.util.find_spec("vega_datasets")
dirpath = spec.submodule_search_locations[0]
with (pathlib.Path(dirpath) / "_data/iris.json").open() as rf:
    data = json.load(rf)

# aggregate
d = defaultdict(list)
for row in data:
    d[row["species"]].append(row)

# render graph
xy_chart = pygal.XY(stroke=False)
xy_chart.title = "Correlation"
for species, rows in d.items():
    xy_chart.add(species, [(row["sepalWidth"], row["sepalLength"]) for row in rows])

print(xy_chart.render(is_unicode=True))

参考にしたページはこのあたり

(ちなみに、render_to_png()png画像を生成できるが、これはlibcairoに依存しちゃっているので小さな依存を保てなくなる)

gist

より高機能なものを使いたい

ということで、この記事の趣旨自体は終わりなのだけれど。もう少し高機能なものを使いたくなる事がある。

その場合には個人的にはvegaとaltairを推そうと思っている。vegaはJSONを返すとグラフをよしなに描画してくれるというようなもの。そのvegaの形式に対応したJSONを生成してくれるのがaltair。

altair-viz.github.io

あとは、plotlyを使う人もいるかも知れない。

plotly.com

データの集計などには素直にpandasを使ったりするのが普通なのかもしれない。CSVを渡せば簡易DBになるし、dataframeは便利なときには便利。

(memo:) データを分析したい場合の例

お絵かきではなくデータを分析したい場合には例えばこういう感じのことをする。散布図一個じゃなくてpairplotするよね。。

(ここから下はテキトーに検索してその場で集めたリンクなのでメモ程度のもの。seabornとか使い方を忘れてしまったし、他のものを使いたい)

EDA (探索的データ解析)

もう少し雰囲気をつかみたい場合には

データセット

手軽なデータセットを集めたい場合は、kaggleなどから取ってきても良さそう。

どういうことしているのか知りたい場合は