Djangoで読書管理Webアプリを作る
ちょうど一年ほど前に、「FlaskとSQLAlchemyで読書記録Webアプリを作る」という記事を書きました。
その際に「DjangoはWebアプリケーションの作成までの決まりごとが多く、ハードルが高いため、とりあえずでいいからWebアプリケーションを簡単に作ってみたい方にはFlaskがオススメです。」と言っておりました。
確かにそうなのですが、その後がありました。Flaskは"Hello, World!"や簡単なホームページを作成するのはハードルが低いのですが、だんだんと機能を強化していくには、Flask-SQLALchemyやFlask-Migrateだったり、flask-WTFだったり、様々なモジュールを追加していかなくてはならないんですよね。これが結構、面倒だったりします。
一方で、Djangoは最初のハードルは高いのですが、一旦、当初の一連のセッティングや決まりごとを理解すると、なんといっても三大フレームワークの一つ、Webアプリケーションの機能強化を比較的簡単に図っていけることに気が付きました。
特に、デフォルトのAdmin(管理)画面を利用して、ユーザー登録機能が比較的簡単にできるし、デフォルトで十分なパスワードの暗号化が図られていたり、と至れり尽せり、という感じでしょうか。ただ、機能がありすぎて、公式ページで発見したDjango DocumentationというPDFをダウンロードしたところ、何と1996ページ!(笑)。まぁ、そのうち一部でも使えれば良いと考えることにしました。
ということで、昨年Flaskで作成した読書記録Webアプリの機能強化バージョンをDjangoで作成し、Herokuにデプロイしました。
なお、個人別に読書記録を管理する必要がありますので、ユーザー登録(メールアドレス・パスワード)は必須となっています。ただし、最終的にアプリを利用する必要がなくなった場合に、ユーザー登録は削除可能となっており、Heroku上のpostgreSQLからメールアドレスの情報も含めて削除されますので、ご安心ください。
【追記(2020/8/30)】
個人としてユーザー登録せずに機能の確認をしていただけるように、共有とはなりますが、以下のメールアドレス(テスト用)とパスワード(テスト用)を設定しました。是非ご利用ください。
メールアドレス(テスト用): test@user.com
パスワード(テスト用): testpassword
django-book-records.herokuapp.com
インデックスページ、ユーザー登録ページ、読書記録の入力ページのイメージを貼り付けておきます。
アプリケーションの概要
django-book-records
Django 2.2を利用した読書記録を管理するWebアプリケーションです。Herokuサービスに登録・アップロードしています。
主な機能
主な機能は以下の通りです。
- ユーザ登録機能。メール・パスワードを入力し仮登録。そして、入力したメールアドレスに自動メールを送信、本文に記載のリンクをクリックことで本登録完了。
- パスワード変更可能。また、パスワードを忘れた場合への対応機能も実装。
- ユーザー毎に読書記録の登録機能。入力フィールドは、以下の6つです。
- タイトル(必須)
- ジャンル
- 著者名
- おすすめ度(5段階)(デフォルト=「3:普通」)
- 読了日(デフォルト=当日)
- コメント
- 入力データの登録、更新、削除に対応しています。
- ジャンル登録機能。読書記録とは別画面で、読書記録登録前に先に登録しておく必要があります。
- 著者名登録機能。読書記録とは別画面で、読書記録登録前に先に登録しておく必要があります。
- その他の機能として、「インポート」「エクスポート」「一括削除機能」があります。
- インポート機能は、csv形式のファイルからデータを取り込む機能です。「その他」の「インポート」を選択すると、ファイル選択画面に推移します。留意点は以下の通り。
- 1行目及び1列目は取り込まない仕様になっています。csvファイルを作成する際には、1行目をタイトル行、1列目を通し番号に利用する等、適宜ご利用ください。
- csvの各行は「No」「タイトル」「ジャンル」「著者」「読了日」「おすすめ度」「コメント」の順で、作成してください。
- 「読了日」は YYYY-MM-DDのフォーマットで作成してください。
- おすすめ度は、1〜5の間で半角数字にて指定してください(5: とてもオススメ, 4:オススメ, 3: 普通, 2: どうだろう, 1: オススメせず)。 一旦、ひとつ読書記録を入力し、エクスポート機能を利用してcsvファイルを作成し、そのファイルを更新すると比較的楽にインポート用のcsvファイルを作成できるかと思います。
- エクスポートは、「その他」の「エクスポート」をクリックすることで自動的に読書記録が全件ダウンロードされます。
- その他「一括削除機能」があります。
- 検索機能については、スペースで区切った複数語検索に対応しています。
- Bootstrap4を利用しています。入力フォームでは
django-bootstrap4
を利用してます。 - Bootstrap Pagination を利用しています。1ページあたりの表示件数を10件に設定しています。
- 読了日は、当日から10年前から翌年まで登録可能です。
Requirement
Django==2.2.5
django-bootstrap4==2.0.1
参考にさせていただいたサイト
ユーザー登録機能は以下のサイトに記載のスクリプトを利用させていただいております。
その他、インポート・エクスポート機能も大いに参考にさせていただきました。ありがとうございました。
PlotlyとTA-libでテクニカル分析チャートを描く
前回はPythonを利用して株価のローソク足チャートを描くのに、PlotlyとCufflinksを使いました。Cufflinksを使うと、ローソク足チャートもテクニカル分析用のチャートもすごく簡単にきれいに描けました。
それはそれで良いのですが、今後、アルゴリズムトレードなどを研究していくにあたり、テクニカル分析のチャートだけでなく、データを利用できるといいですよね。
いろいろと調べていくと、テクニカル分析にはTA-Libというモジュールが良いようです。早速利用してみましょう。
TA-Libのインストール
これがうまくいかないですよね〜。私はMacを使っているのですが、conda install ta-lib
, pip install ta-lib
もうまくいかない。ネットで調べると、困っている方がそれなりにいて、それぞれ解決策が異なっているようです。私の環境ではうまくいかず、どこかのサイトにPythonのバージョンを下げたらインストールできたという記事が載っていたので、試してみました。
新たにPython 3.5の仮想環境を作ったら、なんと今回はインストールできました。環境は個々に異なりますので、うまくいかない方はPythonのダウングレードを試してみると良いかもしれません。
TA-Libを使ってみる
TA-Lib
こちらのサイトに、簡単な使い方が書かれています。
かなりのテクニカル分析が網羅されているようで、人気があるのもわかる気がします。
さて、必要なライブラリをインポートしておきます。
# TA-Libをインポート import talib as ta # 株価を取得するためにpandas_datareaderもインポート import pandas_datareader.data as web # numpyとpandasは必須 import numpy as np import pandas as pd # チャートを描くのにPlotlyを利用します import plotly.offline as pyo import chart_studio.plotly as py import plotly.graph_objs as go
Jupyter Notebookを利用してPlotlyのグラフを作成するには、以下のコマンドを実行しておきます。
pyo.init_notebook_mode()
さて、前回同様、pandas_datareaderを利用して、Yahooから日経225の株価データを入手します。
df = web.DataReader('^N225', 'yahoo', '2018-01-01')
これで2018/1/1以降、直近までの株価が入手できます。
さて、データの最後の5行を見ておきます。
問題ないですね。もし、最後の1行にデータがない(ゼロ)の場合には、以下の通り、最終行を除いておきます。
df = df.iloc[:-1]
さあ、それでは、テクニカル分析用にデータを作成していきます。TA-Libはnumpyのarray形式でしか入力を受け付けないようですので、高値・安値・終値をDataFrame, Series形式からnumpy.array形式に変換します。始値は使いません。
h = np.array(df['High']) l = np.array(df['Low']) c = np.array(df.loc[:, 'Close']) #このようにしても同じです。
単純移動平均 (Simple Moving Average)
sma20 = ta.SMA(c, timeperiod=20) sma50 = ta.SMA(c, timeperiod=50) sma200 = ta.SMA(c, timeperiod=200) # PlotlyではDataFrameを使うので、以下の通り、dfに追加しておきます。 df['SMA20'] = sma20 df['SMA50'] = sma50 df['SMA200'] = sma200
指数平滑化移動平均(Exponential Moving Average)
ema20 = ta.EMA(c, timeperiod=20) ema50 = ta.EMA(c, timeperiod=50) ema200 = ta.EMA(c, timeperiod=200) df['EMA20'] = ema20 df['EMA50'] = ema50 df['EMA200'] = ema200
MACD (Moving Average Convergence/Divergence)¶
macd, macd_sig, macd_hist = ta.MACD(c, fastperiod=12, slowperiod=26, signalperiod=9) df['MACD'] = macd df['MACD_SIG'] = macd_sig df['MACD_HIST'] = macd_hist
RSI (Relative Strength Index)¶
rsi_long = ta.RSI(c, timeperiod=14) rsi_short = ta.RSI(c, timeperiod=7) df['RSI_LONG'] = rsi_long df['RSI_SHORT'] = rsi_short
WILLR (Williams' %R)¶
willr = ta.WILLR(h, l, c, timeperiod=14) df['WILLR'] = willr
Stochastic (STOCH)
slowk, slowd = ta.STOCH(h, l, c, fastk_period=5, slowk_period=3, slowk_matype=0, slowd_period=3, slowd_matype=0) df['SLOWK'] = slowk df['SLOWD'] = slowd
Bolinger Bands (BBANDS)
u_band, m_band, l_band = ta.BBANDS(c, timeperiod=5, nbdevup=2, nbdevdn=2, matype=0) df['BBAND_U'] = u_band df['BBAND_M'] = m_band df['BBAND_L'] = l_band
ADX, DI+ and DI-
adx = ta.ADX(h,l,c,timeperiod=14) df['ADX'] = adx DI_plus = ta.PLUS_DI(h, l, c, timeperiod=14) DI_minus = ta.MINUS_DI(h, l, c, timeperiod=14) df['DI_plus'] = DI_plus df['DI_minus'] = DI_minus
Plotlyでチャート化してみる
さて、テクニカル分析データを作成しましたので、チャート化してみましょう。
Plotlyではdata
とlayout
をそれぞれ作成してから、グラフ化します。
まずはlayout
から。
layout = { 'height':1000, 'title' : {'text':'日経225推移', 'x':0.5}, 'titlefont': {'size':25}, 'xaxis': {'title': "", 'rangeslider':{'visible':False}}, 'yaxis' : {'domain': [.55, 1], 'title': "価格(円)" ,'side':"left", 'tickformat':',' }, 'yaxis2': {'domain': [.45, .55], 'title': "RSI", 'side':"right"}, 'yaxis3': {'domain': [.35, .45], 'title': "MACD", 'side':"right"}, 'yaxis4': {'domain': [.25, .35], 'title': "Will%R", 'side':"right"}, 'yaxis5': {'domain': [.15, .25], 'title': "STOCHASTIC", 'side':"right"}, 'yaxis6': {'domain': [.05, .15], 'title': "ADX&DI", 'side':"right"}, }
height
で全体の高さを指定しておいて、各yaxis
のdomain
にて、全体を1.0としたときの、各チャートのy軸に占める位置を[.05, .15]のように指定します。
まずは、ローソク足チャートを作成します。plotlyにはローソク足用のメソッドが用意されています。
trace = go.Candlestick( x = df.index, open = df['Open'], high = df['High'], low = df['Low'], close = df['Close'], yaxis = 'y1', name = '日経225' )
そして、以下の通り、data
とlayout
を指定します。
fig = {'data':[trace] ,'layout':layout}
ここで一旦チャートを描いてみましょう。
ここにテクニカル分析チャートを追加します。一度に追加してしまいましょう。
fig['data'].extend([ go.Scatter(yaxis="y1" ,x=df.index ,y=df["EMA20"], name= 'EMA20', line=dict(color='lightblue' ,width=1)), go.Scatter(yaxis="y1" ,x=df.index ,y=df["EMA50"] ,name= 'EMA50', line=dict(color='cyan' ,width=1)), go.Scatter(yaxis="y1" ,x=df.index ,y=df["EMA200"] ,name= 'EMA200', line=dict(color='darkblue' ,width=1)), go.Scatter(yaxis="y2" ,x=df.index ,y=df["RSI_LONG"] ,name= 'RSI Long', line=dict(color='yellowgreen' ,width=1)), go.Scatter(yaxis="y2" ,x=df.index ,y=df["RSI_SHORT"] ,name= 'RSI Short', line=dict(color='orange' ,width=1)), go.Scatter(yaxis="y3" ,x=df.index ,y=df["MACD"] ,name= 'MACD', line=dict(color='cornflowerblue' ,width=1)), go.Scatter(yaxis="y3" ,x=df.index ,y=df["MACD_SIG"] ,name= 'MACD(SIG)', line=dict(color='red' ,width=1)), go.Scatter(yaxis="y4" ,x=df.index ,y=df["WILLR"] ,name= 'WILL%R', line=dict(color='blue' ,width=1)), go.Scatter(yaxis="y5" ,x=df.index ,y=df["SLOWK"] ,name= 'STOCHASTIC %K', line=dict(color='blue' ,width=1)), go.Scatter(yaxis="y5" ,x=df.index ,y=df["SLOWD"] ,name= 'STOCHASTIC %D', line=dict(color='red' ,width=1)), go.Scatter(yaxis="y6" ,x=df.index ,y=df["ADX"] ,name= 'ADX', line=dict(color='darkgreen' ,width=2)), go.Scatter(yaxis="y6" ,x=df.index ,y=df["DI_plus"] ,name= 'D+', line=dict(color='orange' ,width=2)), go.Scatter(yaxis="y6" ,x=df.index ,y=df["DI_minus"] ,name= 'D-', line=dict(color='lightgreen' ,width=2)), ]) pyo.iplot(fig)
こんな感じで、うまく描けました!Plotlyはカーソルをグラフの上に持っていくと、数値が表示されるし、一部を拡大・縮小したりできるので、便利ですよね。そして、Plotlyに利用者として登録しておくと、上記のように動くグラフをブログに貼り付けたりできますので、おすすめです。
[2020/7/8 追記]
前回のブログにも記載した通り、今回のグラフも、よくみるとx軸には土日祝日もプロットされています。すなわち、プロットがなくギャップがあいているグラフとなっています。これを解消する方法を偶然知りましたので、追加情報として共有いたします。実は、layoutのxaxisに'xaxis': {'type': 'category'}を追加すると、土日祝日がx軸から除外されるようになります。ご確認いただけると幸いです。
PlotlyとCufflinksでローソク足チャートを描く
Plotlyとはデータ可視化のためのプラットフォームです。
Pythonだけでなく、RやJavaScriptなどにも対応しています。オンラインモードだと無料アカウントを作る必要がありますが、外部公開しない設定にする場合には課金されます。ただし、オフラインで使う場合、特にアカウント作る必要はありません。
Plotlyを使うと、このような(↓)いろいろなチャートを作成できます。
基本的にJupyter Notebookで利用できますし、グラフにカーソルを合わせると、グラフを構成している数値が表示されます。また、拡大・縮小などもできるので便利です。
さらに、Cufflinksというパッケージを合わせて利用すると、Pandasのデータフレームを使ってPlotlyでチャートを描けるようになります。
先日、以下の本を見ていたら、ローソク足チャートが紹介されていましたので、試してみました。
Python for Finance: Mastering Data-Driven Finance
- 作者: Yves Hilpisch
- 出版社/メーカー: O'Reilly Media
- 発売日: 2018/12/31
- メディア: ペーパーバック
- この商品を含むブログを見る
インストール
以下のサイトを見ると、conda
を利用してのインストールは利用できないようですので、pip
を利用してインストールします。
https://anaconda.org/gwinnen/plotly-and-cufflinks/notebook
pip install plotly pip install cufflinks
株価を取得する
株価のヒストリカルデータを無料で取得するのは、その時々でそれまで利用できていた方法ができなくなったりすることもあり、結構大変なんですよね。
日本の株価については、現時点では、以下のサイトで個別企業毎にCSVファイルをダウンロードして利用できます。
米国の株価については、現時点では、Yahoo Financeからのデータがまた取得できているようですので、pandas-datareader
というモジュールを利用します。
pip install pandas-datareader
でインストールします。
import pandas_datareader.data as web df = web.DataReader('^N225', 'yahoo', '2000/1/1','2019/10/16')
とすれば、Yahoo Financeから日経225のデータを取得して、データフレーム形式に格納できます。
日経225のローソク足チャートを描く
import plotly.offline as pyo import cufflinks as cf import numpy as np
まず、上記をインポートしておきます。 また、Jupyter notebookにて利用できるように、以下を実行します。
pyo.init_notebook_mode()
さて、先ほどの日経225のデータのうち直近60日分切り出し、quotesとします(日々のデータを見やすくし、確認するためです)。
quotes = df.iloc[-60:]
quotes.head()
quotesの最初の5行を表示させると以下の通り。
さて、このquotesをローソク足チャートにしましょう。 非常に簡単です。以下の通り、実行するだけです。
qf = cf.QuantFig(quotes, name='日経 225') pyo.iplot( qf.iplot(asFigure=True) )
以上です。と言いたいところですが、よく見てみると、横軸に土日も表示されていて、その場合もちろん株価はないので、チャートにギャップができてしまっています。
これを解消するためにいろいろ調べてみました。 英語のサイトでも議論になっていて、根本的な解決策はまだないようです。 したがって、横軸の表示を工夫するしかないのですが、以下のサイトを参考にさせていただきました。
これをQuantFigureオブジェクトにも適用できるよう、修正する必要があります。
まずは、quotesのインデックスをリセットします。
quotes.reset_index(inplace=True)
この段階でチャートを描きます。
qf = cf.QuantFig(quotes, name='日経225') pyo.iplot( qf.iplot(asFigure=True) )
土日が表示されなくなり、ギャップはなくなりました。しかし、横軸が単に数字の羅列となってしまいました。
そこで以下の通り、工夫します。直接QuantFigureオブジェクトの修正がうまくいきませんでしたので、一旦、Figureオブジェクトにして、そのFigureオブジェクトを修正しています。
figure = qf.iplot(asFigure=True) figure['layout'].update({ 'xaxis': {'tickmode':'array', 'tickvals': np.arange(0, quotes.index[-1],5), 'ticktext': [x.strftime('%y/%m/%d') for x in quotes['Date']][0::5], 'tickfont':{'size':10}} })
なお、参考にしたサイトと同様に、5日置きにデータを表示するようにしています。
pyo.iplot( figure )
うまく行きました。
ただし、グラフの左側と下側の目盛の表記が切れてしまっています。これを修正するには、marginを調整します。デフォルトでは左右上下ともmarginが30となっていますので、以下の通り、左と下側を少し大きめにしてみると良いかと思います。
figure['layout'].update({ 'xaxis': {'tickmode':'array', 'tickvals': np.arange(0, quotes.index[-1],5), 'ticktext': [x.strftime('%y/%m/%d') for x in quotes['Date']][0::5], 'tickfont':{'size':10}}, 'margin': {'b': 70, 'l':40, 'r':30, 't':30} # ここを追加 })
なお、QuantFigureを利用すると何が良いかというと、テクニカル分析用のチャートを簡単に追加するメソッドが準備されていることです。
ボリンジャーバンドを追加する
# ボリンジャーバンドを追加します qf.add_bollinger_bands() # あとはこれまでと同様にFigureオブジェクトを修正して、表示するのみ figure = qf.iplot(asFigure=True) figure['layout'].update({ 'xaxis': {'tickmode':'array', 'tickvals': np.arange(0, quotes.index[-1],5), 'ticktext': [x.strftime('%y/%m/%d') for x in quotes['Date']][0::5], 'tickfont':{'size':10}} }) pyo.iplot( figure )
簡単ですね〜。うれしくなってきました。
MACDと出来高も表示させる
qf.add_macd() qf.add_volume()
さて、表示させます。
figure = qf.iplot(asFigure=True) figure['layout'].update({ 'xaxis': {'tickmode':'array', 'tickvals': np.arange(0, quotes.index[-1],5), 'ticktext': [x.strftime('%y/%m/%d') for x in quotes['Date']][0::5], 'tickfont':{'size':10}} }) pyo.iplot( figure )
他にも、単純移動平均・RSI・サポートライン・レジスタンスライン・トレンドライン等様々なチャートを追加できます。以下のページを参考にしてみてください。
https://jpoles1.github.io/cufflinks/html/cufflinks.quant_figure.html
その他
上記で利用したJupyter notebookをGistにアップロードしておきました。ただし、Plotlyで描画したグラフはうまく表示されませんでしたが、コード部分のみでも参考になるかもしれませんので、そのままにしておきます。
gist4238ca3c5ba76ecbefb3e5bcc7bdd218
なお、Cufflinksの基本的な使い方は、こちらに簡単に紹介されていますので、参考にしてみてください。
Pythonよる線形回帰分析(2)〜線形回帰分析の留意点
線形回帰分析における留意点をまとめると、以下の通り。
(「Introduction to Statistical Learning」
と「RとPythonで学ぶ[実践的]データサイエンス&機械学習」を参考にしてまとめています。)
1. 残差の分布
(1) 正規性
- statsmodelsでは、回帰モデルを作成した際に、モデルを格納したオブジェクトに残差の値が記録されているため、これらの値を取り出してヒストグラムや密度プロット等を作成して残差の分布を確認する。
- 残差について正規分布が仮定できないような場合、そして、正規分布ではなく別のモデルが仮定できるような場合については、線形回帰ではなく一般線形回帰モデルを使う。
(2) 非線形性
(3) 相関
- 線形回帰分析における重要な前提は、誤差項が互いに独立しているということ。
- もし、誤差項が互いに相関がある場合には、本来の標準誤差を低く見積もってしまう傾向がある。結果として、信頼区間や予測値の間隔が本来より狭くなってしまう。
- 時系列データの場合にこうした相関が発生しやすく、時間の関数として残差をプロットすると良い。
- 一般論として、線形回帰モデルには、無相関の誤差という前提が極めて重要であるため、こうした相関リスクを軽減するために、実験のデザインを工夫することが大事。
(4) 分散の一定性
- 線形回帰分析における重要な前提のもう一つが、残差の分散が一定である、というもの。線形回帰モデルにおける標準誤差、信頼区間、仮説検定は全てこの前提に依拠している。
- もしこの問題に当たったら、対応の一つが、 や により を変換してみること。
2. 多重共線性
- (多重)共線性とは、2つもしくはそれ以上の説明変数の相関が高い状況をいう。
- 共線性があると、回帰係数の予測値がより不正確になり、回帰係数の標準誤差が大きくなる。
- 共線性を見つける簡単な方法は、説明変数の「相関係数マトリクス」を見ること。しかし、この方法で全ての共線性が見つかる訳ではない。それぞれの相関はそれほど高くなくても3つか4つの説明変数により共線性が発生する場合があり、これは多重共線性と呼ばれる。
- 相関係数マトリクスを見るよりも良いのは、分散拡大係数(VIF: Variance inflation factor)を計算すること。VIFは個々の説明変数ごとに算出される。VIFの最小値は1で、この場合、共線性が全くないことを示す。VIFが10以上(5以上と厳しく設定する場合もあり)であれば、共線性の問題があり、該当する説明変数を削るのが望ましいというのが経験則。
- ここでは、説明変数を他のすべての説明変数で回帰分析をした場合の決定係数を表す。
- 共線性が認められた場合の対応として2つ。1つは問題のある説明変数のうち一つを除くこと。もう一つは、共線性が認められる説明変数2つから1つの新たな変数を作成すること。
- 交互作用項があるモデルは多重共線性が発生しやすい。この場合に、多重共線性を避けるために使われる方法が「中心化(centering)」。中心化とは、元の値を平均値との差で置き換える方法。
- 交互作用は常に加えるべきというものではない。解釈の複雑さや多重共線性の問題に加えて、オーバーフィッティングの危険性も増大するので、注意が必要。
- 多重共線性を確認するときの問題は、ダミー変数については1つの説明変数から展開されていること。そこで、ダミー変数もうまく扱えるようなGVIF(GeneralizedVarianceInflationfactor)という指標が提案されている。
3. 外れ値の取扱い
(1) 観測値の外れ値 (Outlier)
- 外れ値を特定するには、残差プロットを利用すると良い。しかしながら、どの程度大きい場合に外れ値としたら良いか判断が難しい場合あり。
- その場合には、単なる残差をプロットするのではなく、スチューデント化残差(studentized residuals)という各残差を標準誤差で割った数値をプロットすると良い。スチューデント化残差が3を超えてくれば、外れ値の可能性が高い。
- また、外れ値がデータ収集や記録の段階でのミスによるものということが明らかなのであれば、観測値から除外することも一つの解決策。しかし、外れ値は、説明変数の欠如を示している可能性もあるので、取り扱いには注意が必要。
(2) 説明変数の外れ値(High Leverage Point)
- High Leverage Point(高レバレッジ点)とは通常と異なるx値のことをいう。
- 高レバレッジ点は、回帰直線への影響が大きい傾向があり、それらを特定することは非常に重要。
この高レバレッジ点を見つけるには、単回帰モデルの場合は、以下のleverage statisticを計算する。
4. その他
なお、前回に続き、「RとPythonで学ぶ[実践的]データサイエンス&機械学習」の線形回帰分析をPythonで行なっています。具体的には、交互作用項と多重共線性の関係、および中心化による解決策の箇所です。
Pythonによる線形回帰分析(1)〜 Statsmodelsを利用する
Pythonを利用した線形回帰分析について、基本的事項のみ記載した書籍はいろいろあるようですが、実務における課題やその課題に対する対応まで書いた書籍はないんですよね。その場合、R言語を使っているケースが多い気がします。
例えば、「RとPythonで学ぶ[実践的]データサイエンス&機械学習」は実務上の課題や対応方法を具体的に記述していて、非常に良い本なのですが、統計分析(線形回帰分析、クラスタリング、因子分析、主成分分析等)ではR言語が利用されていて、Pythonが利用されているのは機械学習・ディープラーニングのみになっています。
- 作者: 有賀友紀,大橋俊介
- 出版社/メーカー: 技術評論社
- 発売日: 2019/03/26
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
そこで、この本で利用されている線形回帰分析の事例の一部をPythonで書き直してみました。自分自身への備忘の意味もあり、以下に整理してみます。なお、残差分析やAIC等の確認を行うため、Statsmodelを利用しています。Scikit-learnのLinearRegressionにはそこまでの機能はないようです(予測することに重きを置いている?)
統計検定2級に合格しました
先日受験した統計検定2級の結果が、届いてました。無事、統計検定2級に合格していました。ホームページに正解が掲載されていたので、自己採点していましたが、なんとか合格できて良かった。
実は、試験、案外難しかったんですよね。
受験前には2015年以降の過去問をすべてやって臨んだのですが、過去問にない問題を出題するようにしているのか、年々難しくなっている印象を受けました。
過去問は2015〜2017年の問題を先に行い、最後に2018年の問題を解きました。当初7割前後だった正答率は、試験直前に解いた2018年の問題では正答率は8割台となり、大丈夫かなと高を括っていましたが、なんのなんの、初めて見る問題に時間を取られてしまい、思った以上にてこづった次第です。
こちらが合格証です。
それはそうと、ご参考になるか分かりませんが、僕の勉強法は以下の通り(実際には試行錯誤の側面も多々ありましたが、現在、振り返って整理すると以下の通り)。
(1)統計Web/統計学の時間
結局、こちらのホームページに最もお世話になったと思います。
エクセル統計を作っている会社が、社員の方向けに展開していた統計検定2級向けの勉強会資料を公開していただいているようで、統計検定2級の範囲を過不足なく網羅しています。 初心者にも分かりやすく書かれているのに加えて、各単元の最後には基礎を確認するための練習問題があり、これらを繰り返し勉強すると基礎力がつくと思います。
ただし、統計検定2級の過去問をされた方は分かると思いますが、難しい問題も一定程度出ており、それらの対策をどのように行っていくのかが余裕を持って合格できるかどうかのポイントになるのではないでしょうか。
(2)統計検定2級模擬問題集1〜3
これも統計Webを運営している会社が発行しているもので、残念ながらKindle版しかないようですが、過去問以外に何か統計検定2級対策の問題をするとしたらこの位しか見あたりませんでした。内容としては、統計検定2級の傾向を捉えていると思います。問題数は多くありませんが、過去問以外に問題を解きたいという方の選択肢かなと思います。
- 作者: BellCurve
- 発売日: 2018/06/11
- メディア: Kindle版
- この商品を含むブログを見る
(3)統計検定2級公式問題集(過去問)
統計検定のホームページにも過去問は掲載されていますが、問題と正解しか掲載されておらず、解説はありません。したがって、分からない問題の解説を読み、理解を促進するためには、この公式問題集が必要になります。
僕は、冒頭に書きましたように、2015〜2018年までの問題を全て解きました。
日本統計学会公式認定 統計検定 2級 公式問題集[2016〜2018年]
- 作者: 日本統計学会
- 出版社/メーカー: 実務教育出版
- 発売日: 2019/03/20
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
その他、以前の記事にも書きましたが、以下の参考書を使って勉強を行いました。ただし、統計検定2級のためのテキストではないため、検定の範囲を超える内容があったり、選択問題のみの統計検定2級対策としては、そこまで掘り下げなくても良い内容もあったりします。
(4)入門統計学〜検定から多変量解析・実験計画法まで〜
入門の名に相応しく、網羅的に概念的な理解をするのに適した書籍です。数式は最小限に抑えられており、数式を使って理解したい方には向きません。統計の初学者にとっては、記述統計(平均・分散・標準偏差)はもちろん、確率分布(正規分布、標準正規分布、t分布、χ2分布、F分布)から検定・多変量解析・実験計画法まで、更にはノンパラメトリック手法・因子分析・主成分分析も一応網羅しており、一通りの概念を理解するのは良い本かと思います。
- 作者: 栗原伸一
- 出版社/メーカー: オーム社
- 発売日: 2011/07/26
- メディア: 単行本(ソフトカバー)
- 購入: 20人 クリック: 51回
- この商品を含むブログ (2件) を見る
(5)基本統計学第4版
数理的な側面からしっかり説明しているほか、具体例も豊富です。また、標本が少ない場合の検定等でなぜ自由度n-1を使うのかを厳密に説明しています。統計検定2級の範囲を勉強する中で、理論的側面を掘り下げたい場合に、選択的に利用すると良いテキストです。
- 作者: 宮川公男
- 出版社/メーカー: 有斐閣
- 発売日: 2015/03/30
- メディア: 単行本
- この商品を含むブログを見る
統計検定2級の受験をされる方に参考にしていただけたら幸いです。
FlaskとSQLAlchemyで読書記録Webアプリを作る(7)
これまで機能のアップデートを図ってきた読書記録Webアプリですが、最終化を図りたいと思います。
今回の主要テーマは検索機能の実装ですが、まずは、それ以外のところを改良していきます。
入力項目の追加(オススメ度・コメント)
折角データベースを作成するので、入力項目を充実させましょう。読書をして、その本が良かったのかどうか、「オススメ度」を5段階で入力できるようにします。また、本の感想などを入力できるように「コメント」欄も作成しましょう。
まずは、models.pyとforms.pyの修正です。models.pyにおいては、Bookクラスにrecommendedとcommentを追加します。
# web/models.py class Book(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(64), index=True) genre = db.Column(db.String(64), index=True) date = db.Column(db.Date) recommended = db.Column(db.Integer) #追加 comment = db.Column(db.String(256)) #追加 author_id = db.Column(db.Integer, db.ForeignKey('author.id')) def __repr__(self): return '<Book {}>'.format(self.title)
同様に、forms.pyにおいては、recommendedはSelectField
にて(データベースに保存する値,'表示')の組み合わせをchoicesで指定します。また、commentはTextAreaField
にて指定しました。
# web/forms.py class BookForm(FlaskForm): title = StringField('書籍', validators=[DataRequired()]) author = SelectField('著者', coerce=int, validators=[DataRequired()]) genre = SelectField('ジャンル',choices=[('小説','小説'),('経営','経営'),('歴史','歴史'),('ビジネス','ビジネス'),('宗教哲学','宗教哲学'),('自然科学','自然科学'),('社会科学','社会科学'),('工学','工学'),('芸術','芸術'),('言語','言語'),('趣味','趣味'),('その他','その他')]) date = DateField('読了日', format="%Y-%m-%d") recommended = SelectField('オススメ度', choices=[('5','5: とてもオススメ'),('4','4: ややオススメ'),('3','3: 普通'),('2','2: 余りオススメしない'),('1','1: 全くオススメしない')]) #追加 comment = TextAreaField('コメント') #追加 submit = SubmitField('登録')
これらの変更と併せて、register.html、index.html、each.htmlを修正します。 register.htmlとeach.htmlには、以下の入力を追加します。
<div class="form-group"> {{ form.recommended.label(class="form-control-label") }} {{ form.recommended(class="form-control form-control-lg") }} </div> <div class="form-group"> {{ form.comment.label(class="form-control-label") }} {{ form.comment(class="form-control form-control-lg") }} </div>
また、index.htmlは、以下の通り、修正します。コメントは長くなる場合がありますので、index.htmlの一覧には表示しないことにしました。
<table class="table"> <thead class="thead-light"> <tr> <th scope="col">書籍名</th> <th scope="col">著者</th> <th scope="col">ジャンル</th> <th scope="col">読了日</th> <th scope="col">オススメ度</th> 追加 </tr> </thead> {% for book in books.items%} <tbody> <tr> <td> <a href="{{ url_for('books.update', id=book.id) }}"> {{ book.title }} </a></td> {% for author in authors %} {% if author.id == book.author_id %} <td> <a href="{{ url_for('authors.update', id=book.author_id)}}"> {{ author.name }}</a></td> {% endif %} {% endfor %} <td> {{ book.genre }} </td> <td> {{ book.date }} </td> <td> {{ book.recommended }} </td> 追加 </tr> </tbody> {% endfor %} </table>
読了日の入力の簡素化
そうそう「読了日」も改良しましょう。フォーマットが”yyyy-mm-dd”等と予め指定する仕様になっているので、カレンダーから選択できるようにすれば、入力の際にフォーマットも気にせず楽ですよね。wtformsにはそのようなフォーマットが準備されています。具体的には、wtforms.fields.html5からDateFieldをインポートするように変更します。BookFormクラスは変更の必要がありません。
# 修正前 from wtforms import StringField, DateField, SubmitField, SelectField, TextAreaField # 修正後 from wtforms import StringField, SubmitField, SelectField, TextAreaField from wtforms.fields.html5 import DateField
検索機能の実装
それでは、今回の主要テーマである検索機能の実装をしていきます。 検索は、ひとつの大きな機能ですので、フォルダ構成を以下の通り変更します。また、Blueprint機能を利用します。
books.py config.py web/ __init__.py <--- 修正 models.py forms.py <--- 修正 books/ views.py authors/ views.py searches/ <--- フォルダを新規作成 views.py <--- 新規作成 templates/ base.html <--- 修正 books/ index.html register.html each.html authors/ index.html register.html each.html searches/ search.html search_result.html
まずは、forms.pyにSearchFormクラスを追加します。
# web/forms.py class SearchForm(FlaskForm): title = StringField('書籍') author = SelectField('著者', coerce=int) start_date = DateField('検索開始日', format="%Y-%m-%d") end_date = DateField('検索終了日', format="%Y-%m-%d") submit = SubmitField('検索')
取り敢えず、views.pyを以下の通り作成します(箱を準備します)。
# web/searches/views.py from web import app, db from web.forms import SearchForm from web.models import Book, Author from flask import render_template, redirect, url_for, request, Blueprint searches = Blueprint('searches', __name__) @searches.route('/searches/', methods=['GET','POST']) def index_search(): return render_template('searches/search.html', form=form)
そうそう、__init__.py
も修正するのを忘れないように。
# web/__init__.py from flask import Flask from config import Config from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate app = Flask(__name__) app.config.from_object(Config) db = SQLAlchemy(app) migrate = Migrate(app, db) from web.books.views import books from web.authors.views import authors from web.searches.views import searches # これと app.register_blueprint(books) app.register_blueprint(authors) app.register_blueprint(searches) # これを追加
ここまでは枠組みです。それでは検索機能を作っていきます。 まずは、検索ページですね。以下の通り作成します。register.htmlをコピペして修正すれば簡単です。
{% extends "base.html" %} {% block content %} <div class="container"> <form class="form-group" method="POST"> {{ form.hidden_tag() }} <br> <div class="form-group"> {{ form.title.label(class="form-control-label") }} {{ form.title(class="form-control form-control-lg") }} </div> <div class="form-group"> {{ form.author.label(class="form-control-label") }} {{ form.author(class="form-control form-control-lg") }} </div> <div class="form-group"> {{ form.start_date.label(class="form-control-label") }} {{ form.start_date(class="form-control form-control-lg") }} </div> <div class="form-group"> {{ form.end_date.label(class="form-control-label") }} {{ form.end_date(class="form-control form-control-lg") }} </div> {% if errors %} <p>{{ errors }}</p> {% endif %} <div class="form-group"> {{form.submit(class="btn btn-primary")}} </div> </form> </div> {% endblock %}
メニュー画面に「検索」を追加しておきましょう。
# web/templates/base.html <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav mr-auto"> <li class="nav-item active"> <a class="nav-link" href="{{url_for('books.register')}}">読書の記録</a> </li> <li class="nav-item active"> <a class="nav-link" href="{{url_for('authors.index')}}">著者一覧</a> </li> <li class="nav-item active"> <a class="nav-link" href="{{url_for('authors.register')}}">著者の登録</a> </li> <li class="nav-item active"> <a class="nav-link" href="{{url_for('searches.index_search')}}">検索</a> </li> </ul> </div>
検索結果表示画面(search_results.html)は、基本的にはindex.htmlと一緒です。 ここまでできたところで、views.pyを作り込んでいきましょう。まず、全体像です。
# web/searches/views.py @searches.route('/searches/', methods=['GET','POST']) def index_search(): registered_authors = db.session.query(Author).order_by('name') authors_list = [(0,"")] for i in registered_authors: authors_list.append([i.id, i.name]) form = SearchForm() form.author.choices = authors_list if form.start_date.data is None: form.start_date.data = datetime.date(datetime.datetime.today().year,1,1) if form.end_date.data is None: form.end_date.data = datetime.datetime.today() if form.validate_on_submit(): if form.author.data != 0: books = Book.query.filter(Book.title.like('%' + form.title.data + '%')).filter(Book.author_id==form.author.data).filter(Book.date>=form.start_date.data).filter(Book.date<=form.end_date.data) else: books = Book.query.filter(Book.title.like('%' + form.title.data + '%')).filter(Book.date>=form.start_date.data).filter(Book.date<=form.end_date.data) books = books.order_by(Book.date.desc()).paginate(page=1, per_page=app.config['ITEMS_PER_PAGE'], error_out=False) authors = db.session.query(Author).join(Book, Book.author_id == Author.id).all() session['title'] = form.title.data session['author'] = form.author.data session['start_date'] = form.start_date.data.strftime('%Y-%m-%d') session['end_date'] = form.end_date.data.strftime('%Y-%m-%d') return render_template('searches/search_results.html', books=books, authors=authors) return render_template('searches/search.html', form=form)
上から具体的に説明していきます。
registered_authors = db.session.query(Author).order_by('name') authors_list = [(0,"")] for i in registered_authors: authors_list.append([i.id, i.name])
検索において、著者を選ぶリストを作成する部分です。読書記録を登録する画面では authors_list = [(i.id, i.name) for i in registered_authors]
の一行でしたが、検索においては author_list =
以下3行になっています。検索においては、リストから誰も選ばないという選択肢を作るためです。デフォルトでの表示は(0,"")
となります。
if form.start_date.data is None: form.start_date.data = datetime.date(datetime.datetime.today().year,1,1) if form.end_date.data is None: form.end_date.data = datetime.datetime.today()
これは検索画面を開いた際に、検索開始日と検索終了日のデフォルト値を作成するものです。これがないと、検索時に必ず、検索開始日と検索終了日を入力しないといけなくなるため手間です。それを避けるためのものです。デフォルト値は、検索開始日が当年の1月1日、検索終了日が当日としています。
if form.author.data != 0: books = Book.query.filter(Book.title.like('%' + form.title.data + '%')).filter(Book.author_id==form.author.data).filter(Book.date>=form.start_date.data).filter(Book.date<=form.end_date.data) else: books = Book.query.filter(Book.title.like('%' + form.title.data + '%')).filter(Book.date>=form.start_date.data).filter(Book.date<=form.end_date.data)
これは、検索画面において、著者が選択されていない、すなわち、form.author.dataが0の場合と、著者が選択されている場合の処理を分けるようにしています。book.author_id = 0は存在しないため、同じ処理を行うと検索結果が現れなくなってしまうためです。
session['title'] = form.title.data session['author'] = form.author.data session['start_date'] = form.start_date.data.strftime('%Y-%m-%d') session['end_date'] = form.end_date.data.strftime('%Y-%m-%d')
検索結果もページネーション機能を利用します。異なるページに移る際にも、検索条件を保持できるようにFlaskのSessionを利用しています。これにより検索条件はSessionを通じて、サーバー上に暗号化されて保存されます(config.pyにてSECRET_KEYの設定が必要)。なお、wtformsのDateFieldはdatetime形式で日付が保存されるため、Sessionに保存する際には、一旦、テキスト形式に変換しています。
検索結果の2ページ以降を表示する箇所を作成します。 先ほど、Sessionを通じて保存した検索条件を取り出して、formに格納します。その際に日付についてはテキスト形式からdatetime形式に変換しておきます。
それ以降の検索については、index_search()と同様です。
@searches.route('/searches/<int:page_num>', methods=['GET','POST']) def search_results(page_num): form = SearchForm() form.title.data = session.get('title') form.author.data = session.get('author') form.start_date.data = datetime.datetime.strptime(session.get('start_date'),'%Y-%m-%d') form.end_date.data = datetime.datetime.strptime(session.get('end_date'),'%Y-%m-%d') if form.author.data != 0: books = Book.query.filter(Book.title.like('%' + form.title.data + '%')).filter(Book.author_id==form.author.data).filter(Book.date>=form.start_date.data).filter(Book.date<=form.end_date.data) else: books = Book.query.filter(Book.title.like('%' + form.title.data + '%')).filter(Book.date>=form.start_date.data).filter(Book.date<=form.end_date.data) books = books.order_by(Book.date.desc()).paginate(page=page_num, per_page=app.config['ITEMS_PER_PAGE'], error_out=False) authors = db.session.query(Author).join(Book, Book.author_id == Author.id).all() return render_template('searches/search_results.html', books=books, authors=authors)
以上です。画面を見てみましょう。 まずは、検索画面。
著者を選択せずに、検索。
どうやらワークしていますね。テストのため1ページ当たりの件数を1件に設定していますので、ちょっと変ですが。 これで一旦、読書記録Webアプリは完成しました。
読書記録Webアプリで個人で利用することを想定していますので、ログイン機能は実装していません。 これについては、今回のシリーズとは別の機会にご紹介できればと思います。
最近はDjangoに注力しており、今のところご紹介できる見込みはありません。すみません。【2020/9/1追記】
今回もスクリプトを以下にアップロードしております。ご参考まで。