2017年5月14日日曜日

スマートコントラクトプログラミングを始めてみるpart2

前回はこのサイトのQuick Startをなぞっていた。今回はTutorialを試してみる。

前回作成したプロジェクトを引き続き使うようなのですでに削除してしまった場合は練習がてら同様の手順を踏んでプロジェクトを作成する必要がある。

Greeter.solのコードを書き換える

プロジェクト内のcontracts内にあるGreeter.solを編集する。
ここでは新たにgreet(bytes name)という関数を定義せよ、とのことなので言われた通りにやる。
    pragma solidity ^0.4.0;
    
    contract Greeter {
        string public greeting;
    
        function Greeter() {
            greeting = 'Hello';
        }
    
        function setGreeting(string _greeting) public {
            greeting = _greeting;
        }
    
        function greet() constant returns (string) {
            return greeting;
        }
        function greet(bytes name) constant returns (bytes) {
            // create a byte array sufficiently large to store our greeting.
            // greetingを格納するのに十分な大きさの配列を作成する
            bytes memory namedGreeting = new bytes(
                name.length + 1 + bytes(greeting).length
            );
    
            //push the greeting onto our return value.
            //greeting.
            //greetingを戻り値にプッシュす
    
            for (uint i=0; i < bytes(greeting).length; i++) {
                namedGreeting[i] = bytes(greeting)[i];//先ほど作成したnamedGreetingという配列にgreetingというbyteデータを繰り返し代入していく
            }
    
            // add a space before pushing the name on.
            // 空白文字列を名前をプッシュする前に加える
            namedGreeting[bytes(greeting).length] = ' ';
    
            // loop over the name and push all of the characters onto the greeting.
            // 名前をループして全ての文字をgreetingにプッシュする
            for (i=0; i < name.length; i++) {
                namedGreeting[bytes(greeting).length + 1 + i] = name[i];
            }
            return namedGreeting;
    
        }
    }
    

test_greeter.pyを書き換える

プロジェクト内のtests内にあるtest_greeter.pyを編集する。
def test_named_greeting(chain)という関数を新たに追加する。
    def test_greeter(chain):
        greeter, _ = chain.provider.get_or_deploy_contract('Greeter')
    
        greeting = greeter.call().greet()
        assert greeting == 'Hello'
        print(greeting)
    
    def test_custom_greeting(chain):
        greeter, _ = chain.provider.get_or_deploy_contract('Greeter')
    
        set_txn_hash = greeter.transact().setGreeting('Guten Tag')
        chain.wait.for_receipt(set_txn_hash)
    
        greeting = greeter.call().greet()
        assert greeting == 'Guten Tag'
        print(greeting)
    
    def test_named_greeting(chain):
        greeter, _ = chain.provider.get_or_deploy_contract('Greeter')
    
        greeting = greeter.call().greet('Piper')
        assert greeting == 'Hello Piper'

テストしてみる

これまでの変化点が問題ないかテストを通じて確認する。
確認方法はQuick Startと同じだ。
前回と同様に結果として何ができたのかよくわからないままだが、成功したようだ。
    $ py.test tests/
    ============================= test session starts ==============================
    platform darwin -- Python 3.5.1, pytest-3.0.7, py-1.4.33, pluggy-0.4.0
    rootdir: /Users/xxx/Documents/testSC1, inifile:
    plugins: populus-1.6.9
    collected 3 items 
    
    tests/test_greeter.py ...
    
    =========================== 3 passed in 0.72 seconds ===========================
    

スマートコントラクトプログラミングを始めてみる

スマートコントラクトプログラミング用の環境を設定する
参考サイト①に沿って進めた。

使用言語

  • python3

環境

  • macOS Sierra ver10.12.4

依存関係のあるパッケージをインストールする

先ずはhomebrewで関連パッケージを取得
    brew install pkg-config libffi autoconf automake libtool openssl
次にpipでpopulusをインストールする
    $ pip install populus
geventの使用も勧められているが、ここではスルーする。
ネットワーク系のライブラリらしいが、中身もよくわからないので。

プロジェクトの初期化

適当に作ったディレクトリに移動して以下コマンドをターミナルに入力する。
結果は参考サイトと全く同じになるので問題ないだろう。
テスト用のコードやプロジェクト用のjsonファイルが自動生成されているのだろう。
    $ populus init
    Wrote default populus configuration to `./populus.json`.
    Created Directory: ./contracts
    Created Example Contract: ./contracts/Greeter.sol
    Created Directory: ./tests
    Created Example Tests: ./tests/test_greeter.py

コンパイルしてみる

早速コンパイルしてみる
    $ populus compile
するとsolcがない、というError発生。おそらくコンパイラがないのだろうと推測する。
参考サイト②の通りにsolodityをインストールする。
    $ brew update
    $ brew upgrade
    $ brew tap ethereum/ethereum
    $ brew install solidity #20min以上かかるのとCPUフル使用でPCが熱くなるので注意
    $ brew linkapps solidity  #以下の不安な警告が出る、しかしコンパイルは問題なくできる
    Warning:`brew linkapps` has been deprecated and will eventually be removed!
    Unfortunately `brew linkapps` cannot behave nicely with e.g. Spotlight using
    either aliases or symlinks and Homebrew formulae do not build "proper" `.app`
    bundles that can be relocated. Instead, please consider using `brew cask` and
    migrate formulae using `.app`s to casks.
改めてコンパイルしてみる
参考サイトと若干異なるがコンパイルが成功したようだ。
    $ populus compile
    > Found 1 contract source files
      - contracts/Greeter.sol
    > Compiled 1 contracts
      - Greeter
    > Wrote compiled assets to: build/contracts.json

コントラクトのテスト

greeterというコントラクトのテストをしてみる。いわゆるHello,Worldのようなものだろう。
    $ py.test tests/
    =========================================== test session starts ============================================
    platform darwin -- Python 3.5.1, pytest-3.0.7, py-1.4.33, pluggy-0.4.0
    rootdir: /Users/xxx/Documents/testSC1, inifile:
    plugins: populus-1.6.9
    collected 2 items 
    
    tests/test_greeter.py ..
    
    ========================================= 2 passed in 0.54 seconds =========================================
おそらく成功したようだ。しかし結果として何かの挨拶を出力するわけではないのか? ひとまずはこれでよしとしよう。

参考サイト

①:https://populus.readthedocs.io/en/latest/quickstart.html
②:http://solidity.readthedocs.io/en/develop/installing-solidity.html

2017年5月5日金曜日

NanoWalletの開発環境を設定する

macOS前提とする

node.jsのインストール

node.jsがなんたるかを知るにはこちら→Node.js を5分で大雑把に理解する
NanoWalletの開発はnode.jsとgulp.jsのある環境が前提となるので以下を参考に環境と設定する。
node.js:NodeBrewインストール編nodebrewを使ってMac OSX環境のNode.jsを管理する
※以下は補足
nodebrewをインストールしたのちにnode.jsをインストールすることになるが環境によっては以下のようなエラーがでる。
    Warning: Failed to create the file 
    Warning: /usr/local/var/nodebrew/src/v7.9.0/node-v7.9.0-darwin-x64.tar.gz: No 
    Warning: such file or directory
これは「インストーラーを所定のディレクトリに収めろ」という警告なので以下の手順で対応する。
①ディレクトリ作成:"nodebrew/src/v7.9.0"までそれぞれmkdirで作成する
②インストーラーをダウンロード:ターミナル上に表示されたダウンロードを試みたリンク先から直接インストーラーを取得し、先ほど作成したディレクトリに保存する。
    download failed: https://nodejs.org/dist/v7.9.0/node-v7.9.0-darwin-x64.tar.gz

NanoWalletソースコードの取得

How to build a module for Nano Wallet

追加したmyModuleがgulpのあとに認識されなず以下のようなエラーが出る。

gulp-notify: [Compile Error] Cannot find module './modules/myModule' from '/Users/xxx/Documents/NanoWallet/src/app'

これが解決できずに何もできずにいる。誰か助けてほしい。
上記のリンク先でも質問しているのだが、問題解決につながる回答が得られてない。

2017年5月4日木曜日

Webスクレイピングで自動車販売データを取得する

こちらのサイトから個人的な趣味としての分析用のデータを取得したいと思ったのでその内容を記す。

分析結果イメージ

実際に取得したデータをplotしたものが以下の画像となる。

分析結果イメージ:ブランド別の月度売上台数分布

関数作成編

URLからデータフレームを取得する

def generateDataFrame(url):#get DataFrame from url
        df = pd.io.html.read_html(url)
        return df

変数はurlのみ。このurlは後ほど言及するがhtmlファイルの保存ディレクトリを指定する。 Webページの仕様上、各年度のデータごとにurlが定義されていないため。 



データ整形①:不要な行を削除する

def dropunnecessaryRow(df,colList_tmp,dropColumns):#change columns name and drop unnecessary row
        df.columns = colList_tmp#一時的なカラム名を定義
        df = df.drop(0)#不要な行が1行目に含まれているため削除
        df = df.drop(1)#不要な行が2行目に含まれているため削除
        df = df.drop(dropColumns,axis=1)#削除したいカラム名を予め定義していた"dropColumns"というリストでカラムを削除する
        return df

 実はそのままデータフレームを生成すると以下のようにNaNを含んだり”3月”や”4〜3月”のように使いづらいカラム名が設定されている。一つずつ作り直すのも手間なので行丸ごと新たなものに入れ替える。加えてデータフレーム内に2つの時系列データが含まれている(1月と2月のように)のでそれらを別々のデータフレームに分離する。そのためにここからの処理ではdf1/2と分けてdropColumns1/2のようにそれぞれのデータフレームに対応したデータを後々用意することになる。

ちなみに以下はこちらのリンク先データを抜き出したもの。0-1行目に不要なカラム名が設定されているのがわかる。


0    月       3月    NaN      月     4~3月 NaN  NaN      NaN    NaN     NaN   
1   順位  ブランド通称名  ブランド名     台数      前年比 NaN   順位  ブランド通称名  ブランド名      台数   
2    1      ノート     日産  24383    178.2 NaN    1     プリウス    トヨタ  225066   
3    2     プリウス    トヨタ  22447     71.4 NaN    2      アクア    トヨタ  155566   
4    3      アクア    トヨタ  17798     76.3 NaN    3     シエンタ    トヨタ  127392   
5    4     C-HR    トヨタ  16816  (28-12) NaN    4      ノート     日産  123938   
6    5     フリード    ホンダ  14799    319.5 NaN    5     フィット    ホンダ   98923   
7    6      セレナ     日産  14577    192.7 NaN    6    ヴォクシー    トヨタ   92421   
8    7     シエンタ    トヨタ  13336    103.5 NaN    7      セレナ     日産   90369   
9    8     ヴィッツ    トヨタ  12654    128.9 NaN    8     カローラ    トヨタ   81391   
10   9     フィット    ホンダ  11629     74.8 NaN    9     ヴィッツ    トヨタ   78634   
11  10    ヴォクシー    トヨタ  10831    100.6 NaN   10     フリード    ホンダ   75384   
12  11   インプレッサ    スバル   9969    223.0 NaN   11      パッソ    トヨタ   74707   
13  12     CX-5    マツダ   9669    323.3 NaN   12     ヴェゼル    ホンダ   73583   
14  13     カローラ    トヨタ   9409     83.1 NaN   13       ノア    トヨタ   55061   
15  14     ヴェゼル    ホンダ   8976    100.1 NaN   14  ヴェルファイア    トヨタ   53799   
16  15     ルーミー    トヨタ   8253  (28-11) NaN   15      デミオ    マツダ   53321   
17  16      タンク    トヨタ   7614  (28-11) NaN   16  エクストレイル     日産   52948   
18  17     ハリアー    トヨタ   7360    152.6 NaN   17   インプレッサ    スバル   52756   
19  18  エクストレイル     日産   7289     86.3 NaN   18     ハリアー    トヨタ   48366   
20  19      デミオ    マツダ   7082     93.2 NaN   19  ステップワゴン    ホンダ   48110   
21  20      パッソ    トヨタ   7053    133.8 NaN   20      ソリオ    スズキ   47795   
22  21  ヴェルファイア    トヨタ   6538    130.6 NaN   21   エスクァイア    トヨタ   44136   
23  22       ノア    トヨタ   5934     88.4 NaN   22     C-HR    トヨタ   43600   
24  23  ステップワゴン    ホンダ   5884     82.0 NaN   23   アルファード    トヨタ   39243   
25  24     スイフト    スズキ   5465    195.0 NaN   24     シャトル    ホンダ   38367   
26  25   エスクァイア    トヨタ   5317     86.0 NaN   25     クラウン    トヨタ   36483   
27  26      ソリオ    スズキ   5244    103.4 NaN   26     ルーミー    トヨタ   30058   
28  27   アルファード    トヨタ   4978    123.7 NaN   27     アクセラ    マツダ   28770   
29  28     アクセラ    マツダ   4105    132.8 NaN   28    オデッセイ    ホンダ   28584   
30  29     シャトル    ホンダ   3935     75.1 NaN   29      タンク    トヨタ   27537   
31  30     クラウン    トヨタ   3692     69.5 NaN   30     CX-5    マツダ   27193  

データ整形②:新たなカラムを追加する

def addDatetoDataFrame(df,dateList,date):#ex)df以外のそれぞれの変数はdate:2017/1,dateList=[]のように定義される
        for i in range(0,len(df.index)):#空のdateListにdateで指定した日付をデータフレームに含まれるデータ数分追加する
                dateList.append(date)
        df['date'] = dateList#dateListを'date'カラムとして追加する
        return df

先ほどは不要なカラムを削除した。次は新たに必要なカラムを追加する。
時系列でデータを眺めたいのだがその日付データがない。
そのためdateで追加したい日付を定義する。

データ整形③:新たにカラム名を定義する

def convertColumns(df,colList):#colList1 or 2
        df.columns = colList
        return df

このままではデータフレームのカラム名が歯抜けの状態なので事前に定義しているcolListをカラム名として追加する。

データ整形④:データフレームを結合する

def connectDataFrame(df1,df2):
        df = pd.concat([df1,df2])
        return df
先ほども述べたように一つのデータフレームを二つに分けたのでそれを再び一つにする。
これで時系列でデータを見られるようになる。

データ整形⑤:インデックスを新たに定義する

def resetIx(df):
        df = df.reset_index(drop=True)
        return df
2つのデータフレームを一つにしただけではインデックスがごちゃごちゃしていて何かと都合が悪いのでインデックスを振り直す。

データ整形⑥:文字列を数値に変換する

def convertStringtoInt(df):#replace string to int value and Japanease to English
        for i in range(0,len(df.index)):
                df['volume'][i] = int(df['volume'][i])
        return df
売上台数などのデータが全てstring型なのでint型に変換する。

データ整形⑦:日本語を英語表記にする

def convertJaptoEng(df):#replace Japanease to English
        df['model'] = df['model'].replace(u'ノート',u'note')
        df['model'] = df['model'].replace(u'セレナ',u'serena')
        df['model'] = df['model'].replace(u'プリウス',u'prius')
        df['model'] = df['model'].replace(u'C-HR',u'C-HR')
        df['model'] = df['model'].replace(u'シエンタ',u'sienta')
        df['model'] = df['model'].replace(u'フリード',u'freed')
        df['model'] = df['model'].replace(u'アクア',u'aqua')
        df['model'] = df['model'].replace(u'ヴォクシー',u'voxy')
        df['model'] = df['model'].replace(u'ルーミー',u'roomy')
        df['model'] = df['model'].replace(u'フィット',u'fit')
        df['model'] = df['model'].replace(u'インプレッサ',u'impreza')
        df['model'] = df['model'].replace(u'ヴェゼル',u'vezel')
        df['model'] = df['model'].replace(u'タンク',u'tank')
        df['model'] = df['model'].replace(u'ヴィッツ',u'vitz')
        df['model'] = df['model'].replace(u'ハリアー',u'hurrier')
        df['model'] = df['model'].replace(u'エクストレイル',u'xtrail')
        df['model'] = df['model'].replace(u'ヴェルファイア',u'velfire')
        df['model'] = df['model'].replace(u'カローラ',u'carolla')
        df['model'] = df['model'].replace(u'デミオ',u'demio')
        df['model'] = df['model'].replace(u'ノア',u'noah')
        df['model'] = df['model'].replace(u'スイフト',u'swift')
        df['model'] = df['model'].replace(u'ソリオ',u'solio')
        df['model'] = df['model'].replace(u'パッソ',u'passo')
        df['model'] = df['model'].replace(u'ステップワゴン',u'stepwgn')
        df['model'] = df['model'].replace(u'アクセラ',u'axela')
        df['model'] = df['model'].replace(u'アルファード',u'alfard')
        df['model'] = df['model'].replace(u'エスクァイア',u'esquire')
        df['model'] = df['model'].replace(u'シャトル',u'shuttle')
        df['model'] = df['model'].replace(u'クラウン',u'crown')
        df['model'] = df['model'].replace(u'フォレスター',u'forester')
        df['model'] = df['model'].replace(u'CX-5',u'CX-5')
        df['model'] = df['model'].replace(u'ランドクルーザーW',u'landcruiser-w')
        df['model'] = df['model'].replace(u'オデッセイ',u'odyssey')
        df['model'] = df['model'].replace(u'リーフ',u'leaf')
        df['model'] = df['model'].replace(u'レヴォーグ',u'levorg')
        df['model'] = df['model'].replace(u'プレミオ',u'premio')
        df['model'] = df['model'].replace(u'スペイド',u'spaid')
        df['model'] = df['model'].replace(u'エスティマ',u'estima')
        df['model'] = df['model'].replace(u'イグニス',u'igunis')
        df['model'] = df['model'].replace(u'オーリス',u'auris')

        df['brand'] = df['brand'].replace(u'トヨタ',u'toyota')
        df['brand'] = df['brand'].replace(u'ホンダ',u'honda')
        df['brand'] = df['brand'].replace(u'日産',u'nissan')
        df['brand'] = df['brand'].replace(u'マツダ',u'mazda')
        df['brand'] = df['brand'].replace(u'スバル',u'subaru')
        df['brand'] = df['brand'].replace(u'スズキ',u'suzuki')

        return df

plot時に文字列が日本語だと文字化けするので全てローマ字表記に変換する。
ブランド名とモデル名を全て変換する。何を変換するかは手作業で洗い出すしかない。

処理実行編


モジュールのインポート

import pandas as pd
import matplotlib.pyplot as plt
import htmltable_mod as hl

先ほどの関数編で作成したスクリプトファイルをhtmltable_mod.pyで同じディレクトリに保存したのでモジュールとしてインポート


事前データの定義

colList1 = ['rank','model','brand','volume','compare','NaN','date']
colList2 = ['NaN','rank','model','brand','volume','compare','date']
colList_tmp = ['rank01','model01','brand01','volume01','compare01','NaN','rank02','model02','brand02','volume02','compare02']
dropColumns1 = ['rank02','model02','brand02','volume02','compare02']
dropColumns2 = ['rank01','model01','brand01','volume01','compare01']
date1 = '2016/01'
date2 = '2016/02'
date3 = '2016/03'
date4 = '2016/04'
date5 = '2016/05'
date6 = '2016/06'
date7 = '2016/07'
date8 = '2016/08'
date9 = '2016/09'
date10 = '2016/10'
date11 = '2016/11'
date12 = '2016/12'
url = 'file:///Users/xxx/Downloads/2016.htm'
$HOME/xxx/Documents/mymod

関数編でそれぞれの関数の変数としていたデータを処理前に事前定義しておく。
同じ名前の番号違いは元データの都合上データフレームを2つに分けざるを得ないので
それぞれに対するデータを定義する。
dateは一年間の各月分作る。また、urlはhtmlファイルの保存先を指定している。

処理パターン①:単月データ(ex:1月、2月)のみがデータフレームに含まれている場合

def main1(df,colList_tmp,dropColumns1,dropColumns2,date1,date2):
        dateList1 = []
        dateList2 = []
        colList1 = ['rank','model','brand','volume','compare','NaN','date']
        colList2 = ['NaN','rank','model','brand','volume','compare','date']
        df1 = hl.dropunnecessaryRow(df,colList_tmp,dropColumns1)#データ整形①:不要な行を削除する
        df2 = hl.dropunnecessaryRow(df,colList_tmp,dropColumns2)#データ整形①:不要な行を削除する
        df1 = hl.addDatetoDataFrame(df1,dateList1,date1)#データ整形②:新たなカラムを追加する
        df2 = hl.addDatetoDataFrame(df2,dateList2,date2)#データ整形②:新たなカラムを追加する
        df1 = hl.convertColumns(df1,colList1)#データ整形③:新たなカラム名を定義する
        df2 = hl.convertColumns(df2,colList2)#データ整形③:新たなカラム名を定義する
        df = hl.connectDataFrame(df1,df2)#データ整形④:データフレームを結合する
        df = hl.resetIx(df)#データ整形⑤:インデックスを新たに定義する
        df = hl.convertStringtoInt(df)#データ整形⑥:文字列を数値に変換する
        df = hl.convertJaptoEng(df)#データ整形⑦:日本語を英語表記にする

        return df

単月データのみが含まれているデータフレーム用に関数を組み合わせた処理を定義する。
二つのデータフレームに対して重複した処理を行っていく。

処理パターン②:複数月データ(ex:3月、3~4月)がデータフレームに含まれている場合

def main2(df,colList_tmp,dropColumns,date):
        dateList = []
        colList = ['rank','model','brand','volume','compare','NaN','date']
        df = hl.dropunnecessaryRow(df,colList_tmp,dropColumns)
        df = hl.addDatetoDataFrame(df,dateList,date)
        df = hl.convertColumns(df,colList)
        df = hl.resetIx(df)
        df = hl.convertStringtoInt(df)
        df = hl.convertJaptoEng(df)

        return df

先ほどと同じ処理、複数月データは除外するので全ての処理は一つずつ行う。
データフレームの結合はなしとする。

データフレームの生成

df = hl.generateDataFrame(url)
#select dataframe from list object
df1 = df[1]
df2 = df[2]
df3 = df[3]
df4 = df[4]
df5 = df[5]
df6 = df[6]
df7 = df[7]
df8 = df[8]

df1 = main1(df1,colList_tmp,dropColumns1,dropColumns2,date1,date2)
df2 = main2(df2,colList_tmp,dropColumns1,date3)
df3 = main1(df3,colList_tmp,dropColumns1,dropColumns2,date4,date5)
df4 = main2(df4,colList_tmp,dropColumns1,date6)
df5 = main1(df5,colList_tmp,dropColumns1,dropColumns2,date7,date8)
df6 = main2(df6,colList_tmp,dropColumns1,date9)
df7 = main1(df7,colList_tmp,dropColumns1,dropColumns2,date10,date11)
df8 = main2(df8,colList_tmp,dropColumns1,date12)

元データで対象とするのは8つのデータフレーム。それぞれに対して先ほど定義したmain関数で処理をしていく。奇数番号のデータフレームでは一つのデータフレームを2つに分けてまた一つに結合する、という処理を行う。


データフレームの結合

df = pd.concat([df1,df2,df3,df4,df5,df6,df7,df8])

先ほど生成した8つのデータフレームを一つに結合する。これでほとんど目的は達成した。

プロット

pt = pd.pivot_table(df, index=['brand','model'],columns='date',values='volume',aggfunc='sum')
pt.plot.bar(cmap='rainbow')
plt.legend(loc=4,bbox_to_anchor=(0,0),fontsize=5)
plt.tick_params(labelsize=6)
#legend location
##upper right:1,upper left:2,lower left:3,lower right:4,right:5
##center left:6,center right:7,lower center:8,upper center:9,center:10
plt.show()

おまけ。最終的にプロットしたい形にデータフレームを変えるためにピボットテーブルで整形する。この形が適しているのかどうかは別としてとりあえずプロットしてみたのが冒頭の画像である。