小さなエンドウ豆

まだまだいろいろ勉強中

WordPress の記事に D3 で作ったグラフを挿入する

WordPress に D3 で作ったグラフを挿入する

wp-d3 というプラグインを使って d3 で作ったグラフを載せてみました。 使い方などをここに示しておきます。

プラグインをインストール

WPのダッシュボード > プラグイン > 新規追加 > 「wp-d3」で検索 > インストール!

記事にグラフを載せる

適当に新規で記事を作ります。
ここで「WPD3」のボタンを押します。 f:id:h-piiice16:20171231160506p:plain

すると新たにウィンドウが開かれます。
ここにD3のコードを書いていきます。 f:id:h-piiice16:20171231160802p:plain

今回作ったコードは下記のようなものです。
ただの黒い円ですね。

var svg = d3.select('.wpd3-18850-0')
    .append('svg')
    .attr({
        width: 500,
        height:500
    });
 
svg.append('circle')
    .attr({
        cx:150,
        cy:150,
        r:80,
        fill: "black"
    });

コードを書き終わったら「insert」を押し、「save」を押し保存します。
「insert」を押すことで記事に下記のようなタグが埋め込まれます。
f:id:h-piiice16:20171231160745p:plain

これはさっき開かれたウィンドウのタブに振られていた番号です。(わかりにくいので下記の画像の赤い部分です) f:id:h-piiice16:20171231160802p:plain

これで記事を見てみると… f:id:h-piiice16:20171231161339p:plain

できてたー!簡単ですね!
ただ、d3 のバージョンがこのプラグインに内包されているものに依存してしまうためあまり凝ったものは載せれないかも… ちなみに d3 は3系で、wordpress は4系です。

Docker compose を使って WordPress ローカル環境を作った話

Docker を使ってWordPressのローカル環境を構築

現在 WordPress 公開されているページに対して新たに機能を追加する案件が降ってきました。仕事ではないですが…
まずローカルに本番同様の環境を作りたいが、今までだと仮想環境を作って、Webサーバどうしようなどなど環境構築はめんどくさい
そこで Docker を使ってWordPressを落として5秒で構築していきます。

WordPress エクスポート

元の環境からWordPress(以下WP)のコンテンツをダウンロードします。 「WordPress Export」とかで調べるとプラグインがいっぱい出てくるのでそれを使いましょう

Docker で環境構築

ここから速攻で出来上がります。
まず docker-compose.yml を作ります。内容は下記のようにしてください。

version: '3'

services:
   db:
     image: mysql:5.7
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

   wordpress:
     depends_on:
       - db
     image: wordpress:latest
     ports:
       - "8000:80"
     restart: always
     environment:
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress
volumes:
    db_data:

次に下記のコマンドで環境を

$ docker-compose up -d

これでWPサーバがたちあがったぽい。
sshしてみると...

$ docker exec -it e2d3wordpress_wordpress_1 bash
root@5b18004fe877:/var/www/html#

すごい...

WordPress インポート

さっきダウンロードしたWP全コンテンツを適当にインポートします。
こちらもプラグインを使えばらくちん
これで無事本番WPからローカル環境構築完了!

【Rails】Javascript で使う変数等を環境変数にまとめる

Rails プロジェクトの環境変数を JS で使用する

Rails アプリケーションで外出したくない環境変数.env に書き込んで dotenv-rails を使って呼び出すのが一般的らしいので設定してみました。

ただ、今回は mapbox という Javascript のライブラリの API token を .env に定義したいので、Rails環境変数を JS側でも使用できるように gon という Gem を使って参照する方法をまとめていきます。

Gem のインストール

まずRails プロジェクト配下の Gemfile に下記を追記する。

 gem 'gon'
 gem 'dotenv-rails'

※バージョンは今回指定していません...
それから $ bundle install でそれぞれインストールします。

.env の設定

次に .env ファイルを作ります。
例えば、下記のように環境変数を定義します。

MAPBOX_ACCESS_KEY={{自分のAPI token}}

Rails環境変数を扱う

Rails で定義した環境変数( MAPBOX_ACCESS_KEY )をコントローラで使用する際は下記のように書きます。

def show
  p ENV['MAPBOX_ACCESS_KEY']
end

※確認のためにprint してます
ENV[key] って感じで呼び出すことができます。便利!

Rails 環境変数を JS ファイルで使用

先程説明した gon という Gem を使用して環境変数を JS に渡します。
まずコントローラ側で下記のように書きます。

def show
  gon.mapbox_access_key = ENV['MAPBOX_ACCESS_KEY']
end

これでバックエンド側での設定は終わりです。
次に JS ファイル側に下記のように書いて使用します。

mapboxgl.accessToken=gon.mapbox_access_key;

gon.{{変数名}} で参照できるのすごく便利です。

最後に

.env は公開したくないので .gitignore などに追加しておきましょう。

参考 qiita.com

Kaggle に初挑戦

Kaggle とは

簡単に言うにまとめると

  1. データ持ってる人がデータ投稿する
  2. 投稿者がコンペを開く
  3. 世界中のデータサイエンティストたちが分析を行う
  4. 分析結果を Kaggle に提出してスコアが算出される
  5. 算出されたスコアをもとに競う

細かくいうとこんな感じです。
僕も存在は知っていたのですが、なかなか手が出せずにいました。
今日は Kaggle の中でも有名な タイタニックの生存予測」 のコンペに提出するまでの流れを説明したいと思います。

Kaggle 始め方

Kaggle

まずはアクセスしてアカウントを作ります。
FacebookGoogle のアカウントでサインインできるのでそれらを使うと便利かもです。

コンペへの参加の仕方

アカウントを作るとよくわからない偉い人たちのフィードが現れます。
とりあえず、これらは無視して、ページ上部の Competitions とクリックします。 f:id:h-piiice16:20170820173325p:plain

これですね。
これをクリックすると、いろいろなコンペが表示されます。
スクロールするとタイタニックのコンペもあるので、これをクリックします。

f:id:h-piiice16:20170820173600p:plain

すると、コンペの詳細ページが表示されます。
ここで予測に必要なデータをダウンロードします。
f:id:h-piiice16:20170820173828p:plain

ここで、以下の2つのファイルをダウンロードします。

(名前からして説明はいらないかもですけど、一応訓練データと予測用のデータです。)

生存予測

今回は提出するまでを目標としているので予測に関しては凝りませんでした。
使うモデルは Random Forest です。
予測を行うにあたり便利なのが Jupyter notebook でした。
これに関しては以下の記事を参考にして Anaconda をインストールしていただくと環境が整うと思います。

h-piiice16.hatenablog.com

train.csv の中身は数値データだけでなく文字列で与えられるデータもあります。
ランダムフォレストに学習させるデータは数値データでなくてはならないので train.csv の中からは以下のカラムを抜き出して使用しました。

  • Pclass … 乗客のクラス(1が高いらしい)
  • Sex … 性別
  • Age … 年齢
  • SibSp … 兄弟と配偶者の数
  • Parch … 親と子供の数
  • Fare … 料金

前処理として以下の2つを行いました。参考

# male ... 0, female ... 1
data = pd.read_csv('train.csv').replace('male', 0).replace('female', 1)

# データの抜き出し
train_data = data[['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare]]

# Age の欠損値を中央値で埋める
train_data["Age"].fillna(train_data.Age.median(), inplace=True)

# Fare の欠損値を中央値で埋める
train_data["Fare"].fillna(train_data.Fare.median(), inplace=True)

性別のデータは男性が male、女性が female で与えられているためそれを数値に変換します。
欠損値(NaN)に関しては中央値で埋めます。
これを test.csv にも行います。
そして scikit-learn の Random Forest を使ってモデルを作り、結果を出します。
ランダムフォレスト使ってタイタニックの生存予測をする例が以下になります。

# lable_data は正解データ
# label_data = data['Survived'] で抽出
# モデルの作成
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier()
model.fit(train_data, label_data)

# test_data は test.csv を加工したもの
# 予測
output = model.predict(test_data)

# 提出用のファイルへの書き出し
# test_origin は test.csv を読み込んだもの
test_origin['Survived'] = output
submit_data = test_origin[['PassengerId', 'Survived']]
submit_data.to_csv('output.csv', index=False)

これで output.csv に予測結果が出力される。
PassengerId は予測用のデータに付与されている乗客を表すIDのことです。
提出用のファイルの形式はこの PassengerId と 生存結果を表す Survived を並べたものです。

提出

先程のコンペの詳細ページへ戻り、Submit Prediction を押します。 このページで output.csv をアップロードし提出完了です!
このあとスコアが計算され、順位も出ます。(僕は6000番くらいでした…)

まとめ

以上が提出までの手順となります。
タイタニック以外にもコンペがたくさんあるのでなにかしらにチャレンジしたと思います。
今回提出に当たって便利だと思ったものを以下にまとめます。

  • Chrome ? の翻訳
    Kaggle のページで右クリックでできる
    ページが英語なので結構役に立つw
  • Jupyter notebook
    書いたコードを残せるので便利!
    グラフなどもかけるので分析が捗りそう
  • pandas
    python のライブラリで csv の読み込みや書き出し、欠損値の埋め合わせやデータの抽出などが簡単にできる

これで Kaggle では怖いものなしだと思われます(たぶん)

Railsでdb:seedを用いたデータの用意

db:seed とは

Rails アプリに予め必要なデータをデータベースに一括で投入することができる機能。今回はアプリ内でenumっぽく使いたいデータをどんどん投入していきます。

手順

まずはモデルとmigrateファイルを作り、db:migrateを実行します。

$ rails generate model area name:string
$ rails db:migrate # railsは5系です

これでAreaテーブルが作成されます。(ここらへん曖昧)

で、Rails プロジェクトの db/seeds.rb と以下のように編集します。

Area.create({name: '北海道'})
Area.create({name: '東北'})
Area.create({name: '関東'})
Area.create({name: '甲信越'})
Area.create({name: '東海・北陸'})
Area.create({name: '近畿'})
Area.create({name: '中国'})
Area.create({name: '四国'})
Area.create({name: '九州'})

保存して以下のコマンドを実行します。

$ rails db:seed

すると Area テーブルに9行のレコードが生成されます。 試しに rails console で見てみます。

$ rails console

irb> Area.all

   (2.9ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  Area Load (1.5ms)  SELECT  `areas`.* FROM `areas` LIMIT 11
=> #<ActiveRecord::Relation [#<Area id: 1, name: "北海道", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 2, name: "東北", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 3, name: "関東", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 4, name: "甲信越", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 5, name: "東海・北陸", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 6, name: "近畿", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 7, name: "中国", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 8, name: "四国", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">, #<Area id: 9, name: "九州", created_at: "2017-08-11 04:16:13", updated_at: "2017-08-11 04:16:13">]>

まあなんかいけてるっぽいw

db/seeds.rb に以下のように書くとCSVからデータを投入することもできる

require "csv"

CSV.foreach('db/company.csv') do |row|
  Company.create({area_id: row[0]}, {name: row[1]})
end

めっちゃ便利!

seed で IDを指定してレコードを作る

下記のようにブロックで書くとIDを指定することができます。

Area.create do |a|
  a.id = 1
  a.name = '北海道'
end

leaflet.jsの地図に駅をプロットする

leaflet.jsの地図に駅のレイヤーをオーバーレイする

前回作ったtopojsonをleaflet.jsを使って描画した日本地図に載せていきます。
方法としてはD3.jsでjsonを読み込んでレイヤーを作り、日本地図にオーバーレイする。
では始めます。 (鉄道データ引用元

複数のjsonファイルを読み込む

今回駅情報を記述したstation.jsonと線路情報を記述したrail.jsonを同時に読み込む必要があったため以下のように記述した。

d3.json("rail_road.json", function(railjson){
    d3.json("Station.json", function(stationjson) {
        railDraw(railjson, stationjson);
    });
});

この方法だとjsonファイルが何個もあるときどんどん深くなっていくのでおすすめできないかも… shpファイルをQGISなどを使って一つにまとめる方法や前のブログでやった変換時に複数のgeojsonファイルを指定して、一つのtopojsonに吐く方法などあるらしいのでそちらを使った方が良いかもしれない

leafletの地図にオーバーレイ

leaflet.jsで生成した地図にオーバーレイでレイヤー(svg)を追加する方法

var svg = d3.select(map.getPanes().overlayPane)
    .append("svg")
    .attr('class', 'leaflet-zoom-hide')
    .attr({width:960,height:500});

線路情報の描画

線路情報はjsonファイルの中を覗くとわかるが、緯度経度のリストがいくつも並んでいる形式になっている。以下例

"features": [
{ "type": "Feature", "properties": { "N02_001": "23", "N02_002": "5", "N02_003": "沖縄都市モノレール線", "N02_004": "沖縄都市モノレール" }, "geometry": { "type": "LineString", "coordinates": [ [ 127.67948, 26.21454 ], [ 127.6797, 26.21474 ], [ 127.67975, 26.2148 ], [ 127.68217, 26.21728 ], [ 127.68357, 26.21862 ], [ 127.68394, 26.21891 ], [ 127.68419, 26.21905 ] ] } },
{ "type": "Feature", "properties": { "N02_001": "12", "N02_002": "5", "N02_003": "いわて銀河鉄道線", "N02_004": "アイジーアールいわて銀河鉄道" }, "geometry": { "type": "LineString", "coordinates": [ [ 141.29139, 40.3374 ], [ 141.29176, 40.33723 ], [ 141.29243, 40.33692 ], [ 141.29323, 40.33654 ], [ 141.29379, 40.33624 ], [ 141.29411, 40.33608 ], [ 141.2949, 40.33563 ], [ 141.29624, 40.33477 ], [ 141.29813, 40.33354 ], [ 141.29862, 40.33317 ] ] } },
{ "type": "Feature", "properties": { "N02_001": "12", "N02_002": "5", "N02_003": "いわて銀河鉄道線", "N02_004": "アイジーアールいわて銀河鉄道" }, "geometry": { "type": "LineString", "coordinates": [ [ 141.27554, 40.23936 ], [ 141.27567, 40.23884 ], [ 141.27587, 40.23827 ], [ 141.27622, 40.23756 ], [ 141.27656, 40.23694 ], [ 141.27722, 40.23579 ], [ 141.27789, 40.23462 ], [ 141.27856, 40.23344 ], [ 141.27922, 40.23236 ], [ 141.27983, 40.23153 ], [ 141.28006, 40.23123 ], [ 141.28011, 40.23118 ], [ 141.28034, 40.23094 ], [ 141.28092, 40.23042 ], [ 141.28207, 40.22963 ], [ 141.28328, 40.22883 ], [ 141.28345, 40.22874 ] ] } },
{ "type": "Feature", "properties": { "N02_001": "12", "N02_002": "5", "N02_003": "いわて銀河鉄道線", "N02_004": "アイジーアールいわて銀河鉄道" }, "geometry": { "type": "LineString", "coordinates": [ [ 141.28659, 40.26092 ], [ 141.28538, 40.25874 ] ] } },
...

このgeometryの部分がそうである。これをsvgのpath要素で結んでいくイメージ

以下にD3でsvg要素にpathを追加していく例を記述します。

var g1 = svg.append("g");
    
//緯度経度->パスジェネレーター関数作成
var transform = d3.geo.transform({point: projectPoint});
var path = d3.geo.path().projection(transform);

 featureElement = g1.selectAll("path")
    .data(geo_rail_json.features)
    .enter()
    .append("path")
    .attr({
        "stroke": "red",
        "fill": "green",
        "fill-opacity": 0.4,
    });

function projectPoint(x, y) {
    var point = map.latLngToLayerPoint(new L.LatLng(y, x));
    this.stream.point(point.x, point.y);
}

上記のソースはネットでググったら同じようなものが色々出てきてそれを引用しましたが、いまいち何やっているかわからぬ。。 勉強しなければいけませんね

駅情報のプロット

駅情報を記述したjsonファイルであるstation.jsonはこんな感じ

"features": [
{ "type": "Feature", "properties": { "N02_001": "11", "N02_002": "2", "N02_003": "指宿枕崎線", "N02_004": "九州旅客鉄道", "N02_005": "二月田" }, "geometry": { "type": "LineString", "coordinates": [ [ 130.63035, 31.25405 ], [ 130.62985, 31.25459 ] ] } },
{ "type": "Feature", "properties": { "N02_001": "23", "N02_002": "5", "N02_003": "沖縄都市モノレール線", "N02_004": "沖縄都市モノレール", "N02_005": "古島" }, "geometry": { "type": "LineString", "coordinates": [ [ 127.70279, 26.23035 ], [ 127.70309, 26.23093 ] ] } },
{ "type": "Feature", "properties": { "N02_001": "24", "N02_002": "5", "N02_003": "東京臨海新交通臨海線", "N02_004": "ゆりかもめ", "N02_005": "お台場海浜公園" }, "geometry": { "type": "LineString", "coordinates": [ [ 139.77818, 35.62961 ], [ 139.77888, 35.63 ] ] } },
...

なぜか緯度経度の組みが2つずつあるような形式。おそらく南東と北西の点を取ってきているイメージ。四角形にすると右斜め下と左斜め上の点の緯度経度(わかりにくい)

これらを考慮しプロットするソースが以下の通りである

var g2 = svg.append("g");
 

geo_station_json.features.forEach(function(d){
    d.LatLng = new L.LatLng(d.geometry.coordinates[0][1], d.geometry.coordinates[0][0]);
});
 

stationElement = g2.selectAll("image")
    .data(geo_station_json.features)
    .enter()
    .append("image")
    .attr({"xlink:href":"./station.svg",
        "width":15,
        "height":10,
       });

今回はプロットする点を画像にしました。いつも通りだとsvgにcircle要素を追加していくのですが、image要素を追加していくことで画像がプロットされます。  

完成図

こちらが今回作った駅と線路をleaflet.jsに追加した地図です。 f:id:h-piiice16:20170611103900p:plain

気持ち悪い…拡大すると f:id:h-piiice16:20170611104103p:plain

なんかずれてる 直すのはまた今度にします。

今回作ったもの GitHub - hiracky16/RailRoad

topojsonへの変換と鉄道データの描画

geojson から topojson への変換

geojsonからtopojsonへの変換はググったらたくさん出てきてどれもnodejsのパッケージのtopojsonを使ったものが多かった。今回もそれを使います。

$ npm i -g topojson

$ geo2topo -q 1e6 railroad=N02-15_RailroadSection.json > N02-15_RailroadSection.topojson

-q 1e6というオプションが変換時に大切なものらしく ほぼ常時つけた方がよいらしい

railroadとはtopojsonでのオブジェクト名になるらしい。

D3.jsによるtopojsonの描画

変換したtopojsonファイルを描画していきます。
使うライブラリはみんな大好きD3です。

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  <meta charset="utf-8" />
  <title></title>
  <style>
  </style>
</head>
<body>
  <h1>Rail Road Test</h1>
  <script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
  <script src="//d3js.org/topojson.v0.min.js" charset="utf-8"></script>
  <script src="test.js"></script>
</body>

次にtest.jsについて

var width = 800;
var height = 800;

var svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

d3.json("rail_road.topojson", function(error, topo){

  var data = topojson.object(topo, topo.objects.railroad)
  var projection = d3.geo.mercator()
    .center(d3.geo.centroid(data))
    .scale(1200)
    .translate([width/2, height/2])

  var path = d3.geo.path().projection(projection);

  svg.selectAll("path")
    .data(data.geometries)
    .enter()
    .append("path")
    .attr("d", path);
});

dataの受け渡しの際にさっき設定したオブジェクト名"railroad"を指定して行っています。

上記のhtmlをブラウザで表示すると

f:id:h-piiice16:20170527225844p:plain

とまあわかりにくい

しかし描画できてるっぽいので今日はここまで!

次はこのsvg要素をオーバーレイとして、leaflet.jsなどを使い地図上に鉄道データをのせていきたいとおもいます。