JavaScriptでnullなどが入った配列をソートする

JavaScriptで配列をソートするとき、nullなどが入っていると、うまくソートされないことがあります。

そこで、ソートするための関数を作りました。

var compare = (a, b, desc = true) => {
    if (a !== a && b !== b) return 0;
    if (a !== a) return 1;
    if (b !== b) return -1; 
    
    if (a == null && b == null) return 0;
    if (a == null) return 1;
    if (b == null) return -1;
    
    if (a === '' && b === '') return 0;
    if (a === '') return 1;
    if (b === '') return -1;
    
    var sig = desc ? 1 : -1;
    return a < b ? sig : (a > b ? -sig : 0);
}

用途としては、テーブルをカラムごとにソートするような場合を想定しているので、降順であろうと、昇順であろうと、NaN, null, undefined, 空文字は後に持ってくるようにしています。

これを使って、

arr.sort((a, b) => compare(a, b)); // 降順
arr.sort((a, b) => compare(a, b, false)); // 昇順

のようにします。

JupyterでGo言語とJavaScript(Node.js)を使う

以前、

仮想マシンでJupyterを使うときの初期設定

を書いたのですが、さらに設定を追加しました。

Jupyterの拡張機能が結構充実しているので、どうせならPython以外の言語も使えるようにしておきたいところです。

今回はGo言語とJavaScript(Node.js)を使えるようにしました。R言語やRubyも使えるようにしたいのですが、目下の課題で使う予定がないので、また必要になった時に設定しようと思います。

JupyterでGo言語

Go言語を使うためには、gophernotesをインストールします。

まずはOSのバージョンを確認します。

$ cat /etc/os-release 

バージョンは14.04でした。他のバージョンで検証していないので、以下このバージョンを前提にします。

また、Go言語のバージョンは1.9.2でやりました。

gophernotesをインストールするためにZeroMQ 4.X.Xが必要なので、インストールします。

$ sudo apt-get update
$ sudo apt-get install -y libtool pkg-config build-essential autoconf automake uuid-dev

$ wget http://download.zeromq.org/zeromq-4.0.5.tar.gz
$ tar xvzf zeromq-4.0.5.tar.gz
$ cd zeromq-4.0.5
$ ./configure
$ sudo make install
$ sudo ldconfig

あとは、

$ go get -u github.com/gopherdata/gophernotes
$ mkdir -p $(jupyter --data-dir)/kernels/gophernotes
$ cp $GOPATH/src/github.com/gopherdata/gophernotes/kernel/* $(jupyter --data-dir)/kernels/gophernotes

としてkernelを追加すれば、ノート新規作成時にGoを選択できるようになります。

JupyterでJavaScript(Node.js)

ijavascriptをインストールします。

npmは入っている前提(自分の場合、5.6.0)で、

$ npm install -g ijavascript
$ ijsinstall

とすればいいだけです。

ES2015 (ES6)が普通に使えて感動します。

仮想マシンでGo言語を使うときの初期設定

以下のようなシェルスクリプトを用意しました。

引数でバージョンを指定できます。デフォルトは1.9.2です。

レポジトリ管理のためのghqと、パッケージ管理のためのglideもインストールします。

#!/bin/bash

echo '# goenv' >> ~/.bash_profile
git clone https://github.com/syndbg/goenv.git ~/.goenv
echo 'export GOENV_ROOT="$HOME/.goenv"' >> ~/.bash_profile
echo 'export PATH="$GOENV_ROOT/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(goenv init -)"' >> ~/.bash_profile

echo '# go' >> ~/.bash_profile
echo 'export GOPATH="$HOME/dev"' >> ~/.bash_profile
echo 'export PATH="$GOPATH/bin:$PATH"' >> ~/.bash_profile

source ~/.bash_profile

go_version=${1:-1.9.2}
goenv install $go_version
goenv global $go_version

go get github.com/motemen/gore
go get github.com/nsf/gocode
go get github.com/k0kubun/pp
go get golang.org/x/tools/cmd/godoc

go get github.com/motemen/ghq
git config --global ghq.root '~/dev/src'
mkdir -p ~/dev/src/github.com
cd; ln -fs ~/dev/src/github.com github.com

curl https://glide.sh/get | sh

仮想マシンでNode.jsを使うときの初期設定

以下のようなシェルスクリプトを用意しました。

引数でnvm, node, npmのバージョンを指定できます。指定しなければデフォルトの値が入ります。nodeのバージョンは現時点のLTS版をデフォルトとしています。

#!/bin/bash

nvm_version=${1:-0.33.2}
node_version=${2:-v8.9.4}
npm_version=${3:-5.6.0}

echo '# node' >> ~/.bash_profile
curl -o- https://raw.githubusercontent.com/creationix/nvm/v$nvm_version/install.sh | bash
echo 'export NVM_DIR="$HOME/.nvm"' >> ~/.bash_profile
echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> ~/.bash_profile

source ~/.bash_profile

nvm install $node_version
nvm alias default $node_version
nvm use default
npm install -g npm@$npm_version

仮想マシンでJupyterを使うときの初期設定

さっとPythonで何かを試すときに、仮想マシンを用意して、以下のシェルスクリプトでJupyterを初期化することにしました。

引数でPythonのバージョンも指定できるようにしました。デフォルトは3.5.4です。

numpy, scipy, pandasはたいてい必要になるので、ついでにインストールしています。あと、グラフ描画のためにmatplotlibとseabornも入れています。

bash専用のnotebookもあると便利かと思い、bash_kernelもインストールしました。

ホームディレクトリで

$ jupyter notebook

で起動し、ファイルは'~/notebooks'に置くことを想定しています。

#!/bin/bash

echo '# pyenv' >> ~/.bash_profile
git clone https://github.com/pyenv/pyenv.git ~/.pyenv
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
echo -e 'if command -v pyenv 1>/dev/null 2>&1; then\n  eval "$(pyenv init -)"\nfi' >> ~/.bash_profile

source ~/.bash_profile

python3_version=${1:-3.5.4}
pyenv global $python3_version
pip install --upgrade pip
pip install --upgrade setuptools
pip install \
  numpy \
  scipy \
  pandas \
  matplotlib \
  seaborn \
  jupyter \
  bash_kernel \
  jupyter_contrib_nbextensions \
  jupyterthemes

cd; pyenv local $python3_version; mkdir -p $HOME/notebooks
jupyter notebook --generate-config
cat <<EOF >> $HOME/.jupyter/jupyter_notebook_config.py
c.NotebookApp.ip = '0.0.0.0'
c.NotebookApp.port = 8080
c.NotebookApp.open_browser = False
c.NotebookApp.notebook_dir = u'notebooks/'
EOF

python -m bash_kernel.install

jupyter contrib nbextension install --user
mkdir -p $(jupyter --data-dir)/nbextensions
cd $(jupyter --data-dir)/nbextensions; \
  [-e vim_binding] && rm -rf vim_binding; \
  git clone https://github.com/lambdalisue/jupyter-vim-binding.git vim_binding
jupyter nbextension enable autosavetime/main
jupyter nbextension enable codefolding/main
jupyter nbextension enable collapsible_headings/main
jupyter nbextension enable equation-numbering/main
jupyter nbextension enable execute_time/ExecuteTime
jupyter nbextension enable gist_it/main
jupyter nbextension enable livemdpreview/livemdpreview
jupyter nbextension enable scratchpad/main
jupyter nbextension enable select_keymap/main
jupyter nbextension enable toc2/main
jupyter nbextension enable vim_binding/vim_binding
jupyter contrib nbextensions migrate

jt -t monokai -vim -f inconsolata -N -T

それにしても、拡張機能が充実していますね。もう、ちょっとしたことなら全てJupyterで事足りそうです。

Pythonによる仮想通貨自動売買への道(1)

全何回になるのか未定ですが、Pythonで仮想通貨自動売買のプログラムを書いていきます。

なぜPythonでやるかというと、いずれ機械学習を試したくなった時のことを考慮に入れているからです。他の言語でも良いのですが、やはり機械学習まわりのライブラリが充実しているPythonでやる方が実装が楽になります。もちろん、機械学習のAPIだけPythonで作って、それを叩くという構成でも良いのですが、特にそうするメリットが出てくるまでは、Pythonでささっと済ませようと思います。

なお、技術の調査と遊びが目的で行っているので、これを参考に本気で利益を上げようと考えている方は、あくまでも自己責任で取り組んでください。

さて、自動売買とはいえ、チャートを表示する機能は欲しいところです。まずは株価のデータを利用して、ロウソク足チャートを表示させる方法を確認します。

例として、Quandlで提供されているソフトバンクの株価のデータを使わせて頂きました。

非営業日のところに空白があり、本当は詰めて表示する方が良いのかもしれませんが、ひとまずこれで良しとします。

Pythonでアプリオリ・アルゴリズムを実装してPyPI登録

アソシエーション分析(バスケット分析)

Pythonでアプリオリ・アルゴリズムを実装したライブラリはいくつかありますが、リフト(Lift)値を考慮に入れたものは、Orangeしか見当たりませんでした。

しかし、Orangeはpip installできないので不便だと思い、自前で実装してPyPIにパッケージ登録しました。

https://github.com/aknd/akapriori

使用方法

$ pip install akapriori

インストールしておき、

from akapriori import apriori

transactions = [
    ("apricot", "apple", "cherry", "plum", "banana"),
    ("strawberry", "plum", "cherry"),
    ("persimmon", "peach", "banana", "apple"),
    ("kiwi fruit", "apple", "pear"),
    ("cherry", "pear", "banana"),
    ("watermelon", "apple"),
    ("plum", "banana"),
    ("pear", "peach", "cherry", "banana", "apricot"),
    ("pineapple", "apple", "plum"),
    ("banana", "plum", "peach"),
    ("grape", "cherry"),
    ("mandarin", "plum"),
    ("melon", "apple", "persimmon", "plum"),
    ("peach", "cherry", "apple"),
    ("apple", "mandarin", "plum", "persimmon"),
]

“support >= 0.05 and confidence >= 0.3 and lift > 2"を満たすルールを抽出したいなら、以下のように実装します。

rules = apriori(transactions, support=0.05, confidence=0.3, lift=2)
rules_sorted = sorted(rules, key=lambda x: (x[4], x[3], x[2]), reverse=True) # ORDER BY lift DESC, confidence DESC, support DESC

for r in rules_sorted:
    print(r)

出力結果

(frozenset(['kiwi fruit']), frozenset(['pear']), 0.06666666666666667, 1.0, 5.0)
(frozenset(['melon']), frozenset(['persimmon']), 0.06666666666666667, 1.0, 5.0)
(frozenset(['pear']), frozenset(['kiwi fruit']), 0.06666666666666667, 0.3333333333333333, 5.0)
(frozenset(['persimmon']), frozenset(['melon']), 0.06666666666666667, 0.3333333333333333, 5.0)
(frozenset(['apricot']), frozenset(['banana']), 0.13333333333333333, 1.0, 2.5)
(frozenset(['apricot']), frozenset(['cherry']), 0.13333333333333333, 1.0, 2.5)
(frozenset(['grape']), frozenset(['cherry']), 0.06666666666666667, 1.0, 2.5)
(frozenset(['strawberry']), frozenset(['cherry']), 0.06666666666666667, 1.0, 2.5)
(frozenset(['apricot']), frozenset(['pear']), 0.06666666666666667, 0.5, 2.5)
(frozenset(['mandarin']), frozenset(['persimmon']), 0.06666666666666667, 0.5, 2.5)
(frozenset(['banana']), frozenset(['apricot']), 0.13333333333333333, 0.3333333333333333, 2.5)
(frozenset(['cherry']), frozenset(['apricot']), 0.13333333333333333, 0.3333333333333333, 2.5)
(frozenset(['pear']), frozenset(['apricot']), 0.06666666666666667, 0.3333333333333333, 2.5)
(frozenset(['persimmon']), frozenset(['mandarin']), 0.06666666666666667, 0.3333333333333333, 2.5)

基本的な定義

トランザクション: 個々の客の1回の買い物(バスケット)

Ω: 全トランザクション

A: 商品Aの購入を含むトランザクション

n(Ω): 全トランザクション数(全バスケット数)

n(A): 商品Aの購入を含むトランザクションの総数(商品Aを含むバスケット数)

n(A ∩ B): 商品Aと商品Bの両方の購入を含むトランザクションの総数(商品Aと商品Bの両方を含むバスケット数)

P(A): 商品Aが購入される確率

P(A ∩ B): 商品Aと商品Bが同時購入される確率

P(B | A): 商品Aを購入しているという条件での、商品Bを購入している条件付き確率

A => B: 「商品Aを購入していると商品Bも購入している」というルール(左側を条件部、右側を結論部と呼ぶ)

Support

Supp(A): 商品AのSupport(支持度)

Supp(A) := n(A) / n(Ω) = P(A)

Supp(A => B): ルール A => B のSupport(支持度)

Supp(A => B) := n(A ∩ B) / n(Ω) = P(A ∩ B) = P(B ∩ A) = n(B ∩ A) / n(Ω) =: Supp(B => A)

矢印が付いていますが、Supp(A => B)に方向性はないことに注意しましょう。

Supportが大きいルールを抽出することが必要な理由は、そもそもほとんど売れていな商品Aと商品Bがたまたま同時に買われた様な場合に、そのルールを重要視してしまうことを防ぐためです。

また、Supportが小さいものは、そもそもほとんど売れていないので、たとえルールとして意味があったとしても、それを発見したところでビジネスとしての旨味はないとも考えられます。

Confidence

Conf(A => B): ルール A => B のConfidence(確信度)

Conf(A => B) := Supp(A => B) / Supp(A) = P(A ∩ B) / P(A) = P(B | A)

Confidenceが大きいルールほど、併売の結びつきが強いルールと言えます。

Lift

Lift(A => B): ルール A => B のLift(リフト)

Lift(A => B) := Conf(A => B) / Supp(B) = P(B | A) / P(B)

単純に商品Bを購入する割合よりも商品Aを購入した中で商品Bも購入した割合の方が高いとき、リフトは1を超えます。

したがって、リフトが1を超えたルールが意味のあるルールです。

リフトが大きいルールを抽出することで、ルールに関係なく売れている商品を除外することができます。