2019-12-21
論理式みたいにCSVからデータを検索できるQuerypyを公開しました

目的

株取引をしている友人から移動平均線を使った取引手法が実際に通用しているのか検証したいという相談を受けました。彼はあまりプログラミング関連の知識はありませんが、問題をきちんと数式で記述できます。コードの勉強をしてからようやく彼の問題意識の検証、ではあまりに時間がもったいないです。

数式に近い形でクエリの条件をかけるような検索ツールがあれば、SQLやExcelを扱えなくても簡単なPythonが書ければすぐに分析に取り掛かれます。また、コードの可読性も大幅に向上します。

というわけで最低限の機能ですがデータ検索のためにQuerypyを公開しました。 https://github.com/yaufai/querypy

簡単な使い方

おそらく例をみれば使い方はすぐ分かるかと思いますので、READMEの簡単な日本語訳を載っけます。 例えば下のようなデータから成績が条件を満たす(合格点に達しているか、など)学生を知りたいとしましょう。

名前, 解析入門, 経済学入門
山田,      80,      50
 劉,      50,      80

検索用のオブジェクトとクエリを作成すればすぐに結果を得ることができます。

from querypy import Querypy

data    = Querypy.read_csv("scores.csv", "名前")
math101 = data.get_term("解析入門")
econ101 = data.get_term("経済学入門")

pass_math_exam  = math101 >=  60
better_in_econ  = math101 < econ101

print(data.find(pass_math_exam))
# -> ['劉']
print(data.find(better_in_econ))
# -> ['山田']

論理積や論理和を使った複雑な式にも対応できます。

print(data.find(
    (econ101 == 100) & (math101 == 100)
))
# -> [] (全教科満点はいなかった)
print(data.find(
    (econ101 >= 60) | pass_math_exam
))
# -> ['山田', '劉']  (どれか1つに合格した人は2人でした)

実装にあたって

本来==はオブジェクトが同じものかを比較しますが、math101 == 100bool値としては評価されていません。これを可能にしているのはPythonの演算子の振る舞いを上書きしているからです。

例えば==では

    def __eq__(self, term: Term) -> Formula.Formula:
        return self.__abstract_operator__(term, Formula.Relations.Equal)

というメソッドで上書きしています。型アノテーションをみると分かるように、返り値はboolではなくFormula.Formulaとなっています。このオブジェクトがCSVの項が条件を満たしているかの判断に責任を持っているわけです。

今後、やっていきたいこと

現在このツールはまだまだ最低限の機能(いや、それよりも少ない)しか持っていません。自分で使うにもまだまだ不自由なので追加していきたい機能として以下のものを考えています。

読者の方でもしこのソフトウェアに貢献されたい方が見えましたらぜひご協力ください。下にないものでも大歓迎です!

ユニットテストを書く

手元である程度動くことを確認した程度で本来公開するのに必要な水準さえ確認できていません。

数値以外にも対応

現在は主キーを除いて全ての要素は数値しか取ることができません。例えば「経済学を専攻していてEcon101で80点以上」のようなクエリは(わざわざ専攻に数値をわり当てるなどの工夫をしなければ)対応できません。

JSONなどに対応

基本的にはCSVファイルだけでも多くのことが表現できていますが、他にデータを記述する形式があるので、そちらの方も視野に入れていきたいです。

関数をつかった表現

現在は要素の比較までしか対応できてません。でもデータ分析には本来もっと複雑な条件が関わってくるはずです。

例えば上の例で「二教科の合計点が100点以上」というようなクエリで絞り込みたいときに合計を求める自作関数compute_total_scoreをみたいにかけるといいなと思っています:

def compute_total_score(terms: List[Term]) -> Union[float, int]:
    return reduce(lambda acc, cur: acc + cur, terms, 0)

query = compute_total_score([math101, econ101]) >= 100

二項間の関係

現在は、各行ごとに条件をチェックしています。学生の成績のように個別に評価する場合はそれで十分ですが、時系列データで「昨日は条件Aをみたし、今日は条件Bをみたす日付の組すべて」みたいな検索はできません。

2つのCSVを利用

関係モデルのデータベースのような構造をもった複数のCSVが扱えればようやく多くの実務で使える検索ツールとしてはじめの一歩を踏み出せたといえるでしょう。