グラフ描く簡単な環境作っときたいかも

グラフ描くの簡単な環境作っときたいかも。要求は以下の2つ。

  • 手軽にグラフが描ける
  • 手軽にグラフを共有できる

前者は例えばjupyter上でだけ表示みたいなのが嫌な感じ。 後者は例えばGUIで表示とかだけなのは嫌な感じ。

まだ結局一番良いと思える方法は見つかっていない(かなしい)。

今のところの方法

今のところとりあえずグラフの描画にはpyplotを使うという気持ちでいる。

とりあえず表示する場合と共有する場合とで分けていて。全部jupyterで済ませれるならそれ。 そうじゃない場合は以下の方法でやっている。

画像を表示するだけ

import matplotlib.pyplot as plt
plt.style.use('ggplot')

plt.plot([1, 2, 3, 4])
plt.ylabel('y')
plt.show()

line.png

画像を共有する場合

画像を出力してその画像を共有というのが無難な感じそう。

import matplotlib
matplotlib.use("AGG")  # NOQA
import matplotlib.pyplot as plt
plt.style.use('ggplot')

plt.plot([1, 2, 3, 4])
plt.ylabel('y')

dpi = float(plt.gcf().get_dpi())
plt.gcf().set_size_inches(400 / dpi, 300 / dpi)

plt.savefig("images/line-400x300.png", dpi=dpi)

dpiの指定に現在のfigureが持っているdpiを使うのと、rcParamsの中のsavefigのdpiを使うのとどちらが良いのかあんまり分かっていない。

$identify images/line-400x300.png
images/line-400x300.png PNG 400x300 400x300+0+0 8-bit sRGB 10.7KB 0.000u 0:00.000

ただ、gistにuploadするときにはちょっと大変な作業を踏まないといけないのであんまり画像で共有というのが機能しないような気もする。

問題点

問題点は以下の様な感じ。

  • グラフの表示と画像として出力とでコードが変わってしまう
  • 画像として出力してもgist上では楽に共有できない

画像として出力した結果を共有するなら画像サーバー的なものを用意してそこにuploadした後にmarkdown上で参照するという感じが良いのかも。 答えは見つかっていない。

jupyterでやれば良い場合

なんだかんだでgistで共有する場合にはjupyterでやるのが一番ラクな気もする。

import matplotlib.pyplot as plt

plt.style.use('ggplot')

plt.plot([1, 2, 3, 4])
plt.ylabel('some numbers')

以下をわすれずに

%matplotlib inline

こういう感じ

問題点

問題点はipynbのレンダリングが遅いこと数秒程度は普通に待たされる。 その後

Sorry, something went wrong. Reload?

とか悲しい感じ(githubやgist上での話)。

sqlalchemyのORMじゃない方の機能(expression language api)

使い方忘れることが多いのでメモ。

準備

テキトウにデータを作って準備する。

テキトウにテーブル作成。

CREATE TABLE groups (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT
);
CREATE TABLE users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  group_id INTEGER,
  name TEXT,
  FOREIGN KEY(group_id) REFERENCES groups(id)
);
CREATE TABLE skills (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  user_id INTEGER,
  name TEXT,
  FOREIGN KEY(user_id) REFERENCES users(id)
);

テキトウにデータを追加。

INSERT INTO groups (id, name) VALUES(NULL, 'X');
INSERT INTO groups (id, name) VALUES(NULL, 'Y');
INSERT INTO groups (id, name) VALUES(NULL, 'Z');
INSERT INTO users (id, group_id, name) VALUES(NULL, 1, 'foo');
INSERT INTO users (id, group_id, name) VALUES(NULL, 1, 'bar');
INSERT INTO users (id, group_id, name) VALUES(NULL, 2, 'boo');
INSERT INTO skills (id, user_id, name) VALUES(NULL, 1, 'a');
INSERT INTO skills (id, user_id, name) VALUES(NULL, 1, 'b');
INSERT INTO skills (id, user_id, name) VALUES(NULL, 1, 'c');
INSERT INTO skills (id, user_id, name) VALUES(NULL, 2, 'a');
INSERT INTO skills (id, user_id, name) VALUES(NULL, 2, 'b');
INSERT INTO skills (id, user_id, name) VALUES(NULL, 2, 'c');
INSERT INTO skills (id, user_id, name) VALUES(NULL, 3, 'a');
INSERT INTO skills (id, user_id, name) VALUES(NULL, 3, 'b');
INSERT INTO skills (id, user_id, name) VALUES(NULL, 3, 'c');
groups -* users -* skills

みたいな形の構造

使う

transaction気にしなければ以下の様な感じ。まじめに自分で定義したくなければmetadataのreflectを使う。

import sqlalchemy as sa


def run(url, *, echo):
    config = {"url": url}
    engine = sa.engine_from_config(config, prefix="")
    metadata = sa.MetaData(bind=engine)
    metadata.reflect(engine)
    engine.echo = echo

    with engine.connect() as conn:
        usedb(conn, metadata.tables)

def usedb(conn, tables):
    for row in conn.execute(tables["users"].select()):
        print(row)

url = "sqlite:///../src/groups.db"
run(url, echo=True)
2017-07-05 22:19:36,252 INFO sqlalchemy.engine.base.Engine SELECT users.id, users.group_id, users.name 
FROM users
2017-07-05 22:19:36,252 INFO sqlalchemy.engine.base.Engine ()
(1, 1, 'foo')
(2, 1, 'bar')
(3, 2, 'boo')

join

普通にjoinもできる。

qs = tables["users"].join(tables["skills"], tables["users"].c.id == tables["skills"].c.user_id)
for row in conn.execute(qs.select().where(tables["users"].c.id == 1)):
print(row)
(1, 1, 'foo', 1, 1, 'a')
(1, 1, 'foo', 2, 1, 'b')
(1, 1, 'foo', 3, 1, 'c')

select(field,…)

もちろん、select句を直接指定できる。

qs = tables["users"].join(tables["skills"], tables["users"].c.id == tables["skills"].c.user_id)
for row in conn.execute(
    sa.sql.select([tables["users"].c.name, tables["skills"].c.name]).select_from(qs).where(tables["users"].c.id == 1)
):
    print(row)
('foo', 'a')
('foo', 'b')
('foo', 'c')

fetch many

limitとは別に内部的なapiがbuffering的なものをサポートしていた場合にはfetchmanyが使える。

def chunked(cursor, *, n):
    while True:
        rows = cursor.fetchmany(n)
        if not rows:
            break
        yield rows


qs = tables["users"].join(tables["skills"], tables["users"].c.id == tables["skills"].c.user_id)
for rows in chunked(conn.execute(qs.select().where(tables["users"].c.id == 1)), n=2):
    print(rows)
[(1, 1, 'foo', 1, 1, 'a'), (1, 1, 'foo', 2, 1, 'b')]
[(1, 1, 'foo', 3, 1, 'c')]