AsaHP
Top > 前ページ | 1 2 3 4 5 | 次ページ

Python PandasとJavascriptで高速にWeb開発 サンプル付き (3/5)

データ分析で用いられるPython Pandasと、Javascriptフレームワークを組み合わせて、シンプルなSPA(単一ページ)型のWeb開発を行う。この方法はWebアプリの行数を減らし、高速に開発できる。このソースは2019年に作成したものであり、現在の環境ではうまく動かない可能性がある。

グラフ表示ボタンと軸選択

ここからグラフ表示をするための準備作業を行う。単にグラフ表示するだけでなく、軸を選択してそれに合わせてグラフ表示を行う。グラフ表示ボタンと軸選択のHTMLは以下のようになる。

<div class="col-sm-3 border border-info rounded py-3">
  <form id="graph-form">
    <div style="margin:10px;">
      <label for="graph-x">X軸:</label>
      <select name="graph-x" id="graph-x" class="custom-select custom-select-sm">
      {% for col in cols: %}
        <option value="{{col}}">{{col}}</option>
      {% endfor %}
      </select>
    </div>
    <div style="margin:10px;">
      <label for="graph-y">Y軸:</label>
      <select name="graph-y" id="graph-y" class="custom-select custom-select-sm">
      {% for col in cols_graph_y: %}
        <option value="{{col[0]}}" {{col[1]}}>{{col[0]}}</option>
      {% endfor %}
      </select>
    </div>
    <button type="button" class="btn btn-primary" onclick="graph()" style="margin:10px;">グラフ表示</button>
  </form>
</div>

これはBootstrapの列幅3の部分である。selectのclass部分でBootstrapの形式を指定して、select表示を整形している。このような指定方法だと、select部分は以下のように列幅全体を使う表示になる。このような表示にしたくない場合は、Bootstrapのform-inline機能などを使う必要がある。form-inline機能を使うとselect表示などが横並びになる。

optionとして選択する軸を列挙するが、長くなるのでFlaskの機能を使ってforループにしている。これはFlaskのJinja2という、HTMLを整形するテンプレートエンジンである。Pythonでindex.htmlを指定する際に、以下のような記述でcolsとcols_graph_yを指定する。

@app.route('/')
def index():
    cols = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
    cols_graph_y = [['sepal_length', ''], ['sepal_width', ''], ['petal_length', 'selected'], ['petal_width', '']]
    return render_template('index.html', cols=cols, cols_graph_y=cols_graph_y)

なおJinja2を使えばPythonからHTMLをそのまま書く事もできるが、全体の構造が崩れるのでやらない方がいい。

Javascriptでグラフデータの要求をサーバに送る。最初にXY軸が同じならエラーにしている。serialize関数によりHTMLのformに入力した内容をPOSTの形式に変更できる。

function graph() {
  let graphX = $('#graph-x').val();
  let graphY = $('#graph-y').val();
  if (graphX == graphY) {
    alert('XY軸が同じです');
    return;
  }

  $.post('/get-graph',$('#graph-form').serialize())
    .fail(function(ret) {
      alert('グラフ表示に失敗しました');
    })
    .done(function(ret) {
      …
    });
}

グラフ表示のPython前処理

C3.jsを使って散布図を表示する。書き方は以下のWebページに記載されている。

C3.js 散布図例

data指定部分だけを抜き出すと以下のようになる。種別名ごとにデータを分け、さらにそれをX軸とY軸に分け、データの先頭に種別名を入れる必要がある。

…
data: {
    xs: {
        setosa: 'setosa_x',
        versicolor: 'versicolor_x',
    },
    columns: [
        ["setosa_x", 3.5, 3.0, 3.2, …],
        ["versicolor_x", 3.2, 3.2, 3.1, …],
        ["setosa", 0.2, 0.2, 0.2, …],
        ["versicolor", 1.4, 1.5, 1.5, …],
    ],
    type: 'scatter'
},
…

普通にプログラムを組むと、そこそこ大変な処理である。これを簡単に処理するためPandasの機能を使う。まずJavascriptからのデータを受け取り、元データファイルからコメントとデータ部分を読み込む。

@app.route('/get-graph', methods=['POST'])
def get_graph():
    graph_x = request.form['graph-x']
    graph_y = request.form['graph-y']

    df_comment = pd.read_csv('src/iris.csv', nrows=1, header=None, usecols=[0], encoding='CP932')
    df_comment.columns = ['src']
    df_data = pd.read_csv('src/iris.csv', skiprows=1, encoding='CP932')

次に種別名を抽出する。種別名の抽出は、Pandasのunique関数を使うだけでできる。X軸側には"_x"という文字列を追加する必要があるので、その処理も行う。これらをまとめてデータフレームに入れておく。

    names = df_data["name"].unique()
    names_x = names + "_x"
    df_names = pd.DataFrame({ 'x' : names_x })
    df_names.index = names

df_namesデータフレームの内容は以下の通りである。xが列名、Iris-setosa等がインデックス、Iris-versicolor_x等がデータである。

Javascriptでデータを読ませるため、JSON形式に変換する。JSONはJavascriptのデータを文字列に変換し、ファイルなどで読み書きできるようにしたものである。Javascriptオブジェクトを主体にしたデータ形式だが、実際にはJavascriptのデータなら何でも扱える。記述方法はほとんどJavascriptのデータ記述方法と同じである。日本語などはエンコードする必要があるが、JSON変換時に自動処理されるのであまり気にする必要はない。

Pandasにはto_json関数というJSON形式を出力する機能がある。様々な出力オプションがあり、覚えるのに手間がかかるが使うのは簡単である。このあたりは完全に覚えるよりも、必要な時にWebで情報を検索すればよい。以下のWebページに完全な説明がある。

pandas.DataFrame.to_json

df_namesデータフレームにto_json関数を使うと、以下のような文字列になる。このx部分は、グラフ表示のxs部分そのものである。これをJavascriptに渡した後でそのまま使えばよい。

{"x":{"Iris-setosa":"Iris-setosa_x","Iris-versicolor":"Iris-versicolor_x","Iris-virginica":"Iris-virginica_x"}}

次に数値データを抽出する。数値データは種別名ごとに分割し、さらにX軸とY軸で抽出する必要がある。

これらもPandasの機能で簡単にできる。種別名ごとの分割は行選択の機能を、XY軸の抽出はloc関数を使えばよい。X軸がsepal_length、Y軸がpetal_lengthの場合は以下のようになる。最後に行列をT関数で転置しておく。

df_sel = df_data[df_data.name == 'Iris-setosa']
df_sel_xy = df_sel.loc[:, ['sepal_length', 'petal_length']].T

df_sel_xyデータフレームは以下のようになる。これはグラフ表示のcolumns部分の数値側である。sepal_length行がIris-setosa_xに、petal_length行がIris-setosaに相当する。

これをJSONに変化する。to_json関数のorientオプションを使い、データだけを抜き出す。

df_sel_xy.to_json(orient='values')

変換したJSONデータは以下のようになる。2次元の配列形式に変換される。

[[5.1,4.9,4.7,…],[1.4,1.4,1.3,…]]

最後に全部のデータを1つのJSONにまとめ、postの結果として返す。JSONは文字列データなので、細かい所は自作できる。種別名ごとにループで処理している。数値データのキーとして種別名(Iris-setosa等)を付けている。

Pythonにあるjson.dumps関数により、データフレームでない普通のPythonデータをJSONに変換できる。ここでは文字列だけを変換しているが、リストや辞書データでも変換可能である。

import json
…
@app.route('/get-graph', methods=['POST'])
def get_graph():
    …
    graph_json = '{"comment":' + df_comment.T.to_json() + ',"name":' + df_names.to_json() + ',"data":'
    head = '{'
    for name in df_names.index:
        df_sel = df_data[df_data.name == name]
        graph_json += head + json.dumps(name) + ':' + df_sel.loc[:, [graph_x, graph_y]].T.to_json(orient='values')
        head = ','
    graph_json += '}}'
    return graph_json

グラフ表示のJavascript処理

上記のデータをJavascriptで受け取り、C3.jsによる散布図を作成する。まずHTMLによりグラフを書く場所を指定する。グラフの上にコメントも書くようにしておく。

<div id="graph-comment"></div>
<div id="graph-plot"></div>

Pythonが作成したJSONデータを受け取る。jQueryの$.parseJSON関数により、JSONからJavascriptのデータ形式に変換できる。

グラフの数値データはまだ完全にできていなので、ここで整形する。種別名のループを作り、X軸用のデータとY軸用のデータを作成する。これをcolDataにまとめて入れる。unshift関数は配列先頭への挿入で、push関数は配列末尾への挿入である。

function graph() {
  let graphX = $('#graph-x').val();
  let graphY = $('#graph-y').val();
  …
  $.post('/get-graph',$('#graph-form').serialize())
    …
    .done(function(ret) {
      let retParse = $.parseJSON(ret);
      let colData = [];
      Object.keys(retParse.name.x).forEach(function (key) {
        let colDataX = Array.from(retParse.data[key][0]);
        colDataX.unshift(key + '_x');
        colData.push(colDataX);
        let colDataY = Array.from(retParse.data[key][1]);
        colDataY.unshift(key);
        colData.push(colDataY);
      });
      …
    });

コメントを表示する。jQueryのhtml関数により、HTMLの内容を変更できる。

      let commentStr = '元データ:' + retParse.comment[0].src;
      $('#graph-comment').html('<p>' + commentStr + '</p>');

最後にC3.jsの散布図グラフを表示する。bindtoでグラフの出力先を指定する。colorsで色の指定をしている。

      chart = c3.generate({
        bindto: '#graph-plot',
        data: {
          xs: retParse.name.x,
          columns: colData,
          type: 'scatter',
          colors: {'Iris-setosa': '#ff0000ff', 'Iris-versicolor': '#00ff00ff', 'Iris-virginica': '#0000ffff'}},
        axis: {
          x: {
            label: graphX,
            tick: {
              fit: false}},
          y: {
            label: graphY}
        },
        tooltip: {
          grouped: false}
      });

表示されるグラフは以下のようになる。

これで第1段階のWebアプリは完成である。ソースについては1ページ目を参照して欲しい。

Top > 前ページ | 1 2 3 4 5 | 次ページ