The 4th Big Data Analysis Contest 予測部門 チュートリアル

create date : Nov. 12, 2018 at 22:00:00

本チュートリアルでは、モデルの構築ではなく、データの理解のための探索的分析、可視化を中心とした内容としています。
実装にはpython(versionは3.6.3)を使用。構成は以下のとおりです。

  • 必要なライブラリ
  • 必要なデータの用意
  • 実装
    1. データの読み込み
    2. データの理解
    3. 投稿ファイルの作成
  • まとめ

The 4th Big Data Analysis Contest 予測部門 チュートリアル

概要

「The 4th Big Data Analysis Contest」( https://signate.jp/competitions/136 )の実装例です。
本チュートリアルでは、モデルの構築ではなく、データの理解のための探索的分析、可視化を中心とした内容としています。
実装にはpython(versionは3.6.3)を使用。構成は以下のとおりです。

  • 必要なライブラリ
  • 必要なデータの用意
  • 実装
    1. データの読み込み
    2. データの理解
    3. 投稿ファイルの作成
  • まとめ

必要なライブラリ

日付の処理にdatetime、ファイルの処理にpandas、基本的な数値計算にnumpy、可視化にmatplotlib, seabornを使用します。

必要なデータの用意

https://signate.jp/competitions/136 へアクセスし, 「データ」タブを押し, 以下のファイルをダウンロードします。
データは、dataset フォルダへ纏めておきます。

  • 軌道検測_首都圏路線A (track_A.csv)
  • 軌道検測_首都圏路線B (track_B.csv)
  • 軌道検測_地方幹線C (track_C.csv)
  • 軌道検測_地方路線D (track_D.csv)
  • 設備台帳_首都圏路線A (equipment_A.csv)
  • 設備台帳_首都圏路線B (equipment_B.csv)
  • 設備台帳_地方幹線C (equipment_C.csv)
  • 設備台帳_地方路線D (equipment_D.csv)

実装

まず必要なライブラリをインポートします。

In [2]:
import datetime as dt
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

1. データの読み込み

4種類の軌道検測データを、路線名A,B,C,Dをキーとしたディクショナリに、項目"date"をタイムスタンプ型とした上で、データフレームとして読み込みます。
設備台帳データも同様に読み込みます。

In [3]:
# データの読み込み
tracks={}
for no in ['A','B','C','D']:
    tracks[no] = pd.read_csv("dataset/track_" + no + ".csv", parse_dates=["date"])
In [71]:
equipments={}
for no in ['A','B','C','D']:
    equipments[no] = pd.read_csv("dataset/equipment_" + no + ".csv")
In [4]:
tracks["A"].head()
Out[4]:
date キロ程 高低左 高低右 通り左 通り右 水準 軌間 速度
0 2017-04-01 10000 -1.16 -0.23 1.20 1.20 0.83 3.89 84.0
1 2017-04-01 10001 -1.17 -0.30 1.88 1.88 0.67 4.63 84.0
2 2017-04-01 10002 -1.09 -0.19 2.30 2.30 0.50 5.33 84.0
3 2017-04-01 10003 -0.64 0.20 2.28 2.28 0.46 5.90 84.0
4 2017-04-01 10004 0.47 0.89 1.74 1.74 0.62 6.38 84.5
In [72]:
equipments["A"].head()
Out[72]:
キロ程 バラスト ロングレール マクラギ種別 橋りょう 踏切 通トン 曲線半径 フラグ
0 10000 1 1 3 0 0 13.607 0 1
1 10001 1 1 3 0 0 13.607 0 1
2 10002 1 1 3 0 0 13.607 0 1
3 10003 1 1 3 0 0 13.607 0 1
4 10004 1 1 1 0 0 13.607 0 1

2. データの理解

まずは各路線の日付や計測地点の数、目的変数である"高低左"の欠損の割合を確認してみます。

In [5]:
for no, track in tracks.items():

    n_date = track["date"].unique().size
    n_kiro = track["キロ程"].unique().size
    n_data = len(track[track["高低左"].notnull()])

    print('{:*^16}'.format(no))
    print("期間  :{} - {}  ({}days)".format(track["date"].min().date(), track["date"].max().date(), n_date))
    print("計測地点数:{:,}".format(n_kiro))
    print("有効データ数 / 理論データ数:{:,} / {:,} ({:.2%})".format(n_data, len(track), n_data / len(track)))
*******A********
期間  :2017-04-01 - 2018-03-31  (365days)
計測地点数:27,906
有効データ数 / 理論データ数:7,470,943 / 10,185,690 (73.35%)
*******B********
期間  :2017-04-03 - 2018-03-31  (363days)
計測地点数:21,531
有効データ数 / 理論データ数:4,315,596 / 7,815,753 (55.22%)
*******C********
期間  :2017-04-01 - 2018-03-31  (365days)
計測地点数:55,684
有効データ数 / 理論データ数:11,362,106 / 20,324,660 (55.90%)
*******D********
期間  :2017-04-09 - 2018-03-31  (357days)
計測地点数:15,691
有効データ数 / 理論データ数:3,089,150 / 5,601,687 (55.15%)

路線によって開始日が若干異なっていること、理論データ数の半数近くが欠損していることが分かります。
次にどの日付、計測地点に欠損が生じているのかを可視化してみます。

In [14]:
fig, axes = plt.subplots(2,2,figsize=(10,10), sharey=True)

for k, (no, track) in enumerate(tracks.items()):
    i = int(k / 2)
    j = k % 2
    mat = track.groupby(["date","キロ程"]).max()["高低左"].unstack().notnull()
    mat.index = mat.index.format()
    sns.heatmap(mat, cbar=False, ax=axes[i, j])
    axes[i, j].set_title(no)
plt.subplots_adjust(left=None, bottom=None, top=None, hspace=0.4)

黒い箇所が欠損を表しますが、帯状に伸びている箇所も見られます。欠損の補完方法も鍵となりそうです。
次にtrack_Dのいくつかの計測地点の"高低左"の日別推移を可視化してみます。

In [183]:
track = tracks["D"][["date", "キロ程","高低左"]]

# 全計測地点から10箇所をランダムサンプリング
rnd = np.random.choice(track["キロ程"].unique(), size=10, replace=False, p=None)

# サンプリングした計測地点の高低左変位を描画(Y軸のレンジを平均値±3で統一)
fig, axes = plt.subplots(10,1,figsize=(15, 20) ,sharex=True)#, sharey=True)
for i, k in enumerate(rnd):
    data = track[track["キロ程"]==k]["高低左"]
    data.plot(ax=axes[i], title="キロ程:" + str(k), marker=".", linewidth=0)
    m = track[track["キロ程"]==k]["高低左"].mean()
    axes[i].set_ylim(m-3,m+3)

様々な形状が確認できますが、中には補修作業があったと推測できるものも見てとれます。補修作業後の動き方の特徴も詳しく分析する必要がありそうです。
次に、track_Dの計測地点を始点30地点、終点30地点に絞り、"高低左","高低右"の分布を箱ひげ図を用いて可視化してみます。

In [82]:
track = tracks["D"]

fig, axes = plt.subplots(2,1,figsize=(15,8))
start30_kiros = track["キロ程"].unique()[:30]
end30_kiros = track["キロ程"].unique()[-30:]

track1 = track[track["キロ程"].isin(start30_kiros)]
track2 = track[track["キロ程"].isin(end30_kiros)]

# 左は赤、右は青
sns.boxplot(x = "キロ程", y="高低左",data=track1, color="r", ax=axes[0])
sns.boxplot(x = "キロ程", y="高低右",data=track1, color="b", ax=axes[0])
axes[0].set_title("開始30地点")
axes[0].set_xticklabels(start30_kiros, rotation='vertical')
axes[0].set_ylabel("")

sns.boxplot(x = "キロ程", y="高低左",data=track2, color="r", ax=axes[1])
sns.boxplot(x = "キロ程", y="高低右",data=track2, color="b", ax=axes[1])
axes[1].set_title("終了30地点")
axes[1].set_xticklabels(end30_kiros, rotation='vertical')
axes[1].set_ylabel("")

plt.subplots_adjust(left=None, bottom=None, top=None, wspace=0, hspace=0.4) 

左右で若干の違いはありますが、滑らかに推移していることが分かります。予測対象の計測地点だけでなく、隣接地点の変位も参考になるかもしれません。
次に構造物の特徴に応じた"高低左"の分布の違いを見てみます。

In [159]:
# 軌道検測データと設備台帳データを統合
dfs={}
for no in ['A','B','C','D']:
    dfs[no] = pd.merge(tracks[no], equipments[no], on="キロ程")
In [193]:
# 踏切と踏切以外での"高低左"の分布の違いを確認
fig, axes = plt.subplots(1, 4, figsize=(18, 3), sharex=True, sharey=True)
for i, (no, df) in enumerate(dfs.items()):
    for f in [1, 0]: # 1=踏切, 0=踏切以外
        tmp = df[df["踏切"]==f]["高低左"].dropna() # 欠損があるとエラーとなるため削除
        tmp = tmp[(tmp < 10) & (tmp > -10)] # 範囲を限定
        sns.distplot(tmp, kde=True, rug=False, ax=axes[i])
        axes[i].legend(["踏切","踏切以外"])
    axes[i].set_title(no)

踏切以外はどの路線も-5.0 ~ 5.0 範囲にほぼ正規分布の形状で分布、踏切はやや歪な形をしていることが分かります。構造物の考慮も予測精度に影響すると考えれます。

3. 投稿ファイルの作成

ここでは、単純に計測地点(キロ程)毎の平均値を予測値として、投稿ファイルを作成してみます。
index_master を確認すると、"路線","date","キロ程"の優先順に昇順でソートしたものに、idが連番で付与されているため、この構成に合わせて予測値を作成します。

In [217]:
p = []
for no, track in tracks.items():
    mean_by_kiros = track.groupby("キロ程").mean()["高低左"] #キロ程毎の平均値
    mean_by_kiros91 = np.array(list(mean_by_kiros.values) * 91) # 評価期間の91日分に拡張
    p.extend(mean_by_kiros91) # 配列に追加
In [218]:
submit = pd.DataFrame(p)
submit = submit.fillna(0) #欠損値は0で補完
submit =submit.apply(lambda x:x.round(2)) #サイズが大きいため予測値を桁落とし
submit.to_csv("submit.csv", header=None)

投稿の結果、スコアは 0.91930 になりました。

まとめ

データの読み込み→データの理解→投稿ファイルの作成までの実装を一通り説明しました。
モデルの構築は行っていませんが、特徴量の選択・設計や、モデル選択、パラメータチューニング等、工夫の余地は多いと考えられます。
皆さんのご応募をお待ちしております.。

create date : Nov. 12, 2018 at 22:00:00