長野エンジニアライフ

東京から長野に移住したエンジニアのブログです。🦒🗻⛰

Pythonでオープンデータを活用する(2)〜盗難推測編〜

この記事は、 JSL (日本システム技研) Advent Calendar 2019 - Qiita 12/17の記事です。

今回は↓で数値化した自転車盗難情報を使って任意に指定された自転車利用情報がどのくらい盗まれやすい特徴であるかを示す指標を作成する。 kawakeee.hatenablog.com

過去の自転車盗難情報の特徴に合わせ、以下項目を任意の自転車利用情報の項目として選別した。

  • occurTime(発生時)
  • age(年齢)
  • occupation(職業 )
  • isRock(施錠関係)
  • occurMonth(発生月)

目標とする指標

目標とする指標(以下、安全スコア)のざっくりとした定義
【安全スコア】
・スコアが大きい → 盗難被害の特徴に似ていない(安全)
・スコアが小さい → 盗難被害の特徴に似ている(危険)
安全スコアのイメージ

安全スコアの算出

安全スコアの算出には、マハラノビス距離を利用する。

【マハラノビス距離】
任意の点 が データ群 からどれだけ離れているかを示す数値

このマハラノビス距離を利用して任意の自転車利用情報過去の自転車盗難被害情報からどれだけ離れているか(盗まれやすい特徴であるか)を数値化する。
マハラノビス距離のイメージ

詳細については、ここがわかりやすい。

toukei-lab.com

自転車盗難データは5次元の行ベクトルの集合であるため、各行を {x_1, x_2, …, x_{1482}}(1482は推測に使う被害情報件数)とすると

{i}番目の行ベクトルは以下のように示せる。
x i = ( x i , occurTime x i , age x i , occupation x i , isRock x i , occurMonth )   (スペースの都合で列ベクトルで表現)

 
このとき平均ベクトルを{μ}、分散共分散行列を{\sum_{}}とすると、

{μ = \frac{1}{1482} \sum_{i=1}^{1482} x_i}

{\sum  = \frac{1}{1482}\sum_{i=1}^{1482} (x_i - μ)(x_i - μ)^{\mathrm{T}}}

となる。
 
上記で算出した数値、任意の行ベクトル(利用者情報)を{x_p}とすると
マハラノビス距離は以下の式から算出できる。

{mahal = \sqrt{(x_p - μ) ^{\mathrm{T}}\sum{}^{-1} (x_p - μ) }}  
 
以降↓の手順でマハラノビス距離(= 安全スコア)をPythonで算出する。
1. 使用データの準備
2. 平均ベクトルの生成
3. 分散共分散行列の生成
4. マハラノビス距離の算出

1. 使用データの準備

使用する以下のデータを準備する
・過去の自転車盗難情報
・任意の自転車利用情報

こちらで数値化したデータを過去の自転車盗難情報として使用する。Unnamed: 0は不要な列なので最初に削除しておく。

import pandas as pd
import numpy as np
import scipy as sc
from scipy import linalg
# 過去の自転車盗難情報
trans_data = pd.read_csv("./CSV/output/transform_bohan_data.csv")
# 不要な列が存在するので削除
trans_data = trans_data.drop(columns='Unnamed: 0')

次に以下の値で、任意の自転車利用情報userInfoを生成し、trans_dataの末尾に追加する。

userInfo = ( occurTime age occupation isRock occurMonth ) = ( 15(時) 30(歳代) 5(その他) 1(月) 1(施錠あり) )

# 任意の自転車利用情報を生成
userInfo = pd.Series([15, 30, 5, 1, 1], index=trans_data.columns)
# trans_dataの末尾に追加
trans_data = trans_data.append( userInfo, ignore_index=True )

2. 平均ベクトルの生成

次にtrans_dataの各列(特徴量ごと)の平均値を算出していく。trans_dataの行数、列数を定数ROWCOLUMNに指定する。

# 定数の準備(trans_dataの行/列の数)
ROW = 1482
COLUMN = 5
# column:列の初期化
column = []
# ave:平均ベクトルの初期化
ave = [0.0 for i in range(COLUMN)]

trans_dataの列ごとのリストを持つcolumnから平均値を算出しaveに格納する。

# columnにtrans_dataの列要素(5件分)を代入
for i in range(COLUMN):
    column.append(list(trans_data.iloc[:, i]))
# 平均ベクトルの計算(各列の平均値を算出) 
for i in range(COLUMN):
    ave[i] = np.average(column[i])
print(ave)
出力結果
[12.950742240215924, 19.87854251012146, 2.8157894736842106, 0.27800269905533065, 6.922402159244265]

3. 分散共分散行列の生成

diffuserInfoaveの差分を格納し、上記公式通りに分散共分散行列{\sum_{}}を算出する。

# diff: userInfoとaveの差分を格納する
diff = userInfo - ave
diff = np.array([diff])
# diffの転地行列
diff_T = np.swapaxes(diff, 0, 1)
# vcm:分散共分散行列の計算
vcm = (diff * diff_T) / ROW

4. マハラノビス距離の算出

diffvcmを用いて最後にマハラノビス距離を算出する。

# 算出のときはvcmの逆行列をもとめる
vcm_r = sc.linalg.pinv(vcm)
vcm_r = vcm_r.transpose()
vcm_r = np.identity(COLUMN)
# diffとvcm_rで行列の積(内積)をtempに代入
temp = np.dot(diff,vcm_r)
#  マハラノビス距離の算出
mahal = np.dot(temp[0],diff_T)
mahal = np.sqrt(mahal[0])
print(mahal)
出力結果
12.12477884941061

マハラノビス距離の考察

(1)年齢を60歳代にして同様にマハラノビス距離を算出する
userInfo' = ( occurTime age occupation isRock occurMonth ) = ( 15(時) 60(歳代) 5(その他) 1(月) 1(施錠あり) )

マハラノビス距離の出力結果
40.65309054438262

(2)年齢を20歳代にして同様にマハラノビス距離を算出する
userInfo'' = ( occurTime age occupation isRock occurMonth ) = ( 15(時) 20(歳代) 5(その他) 1(月) 1(施錠あり) )

マハラノビス距離の出力結果
6.677035044283844

今回、安全スコアの指標としてマハラノビス距離を算出した。
 
(1)、(2)より年齢が若いほど
マハラノビス距離の値は小さくなる
=> 安全スコアが小さくなる
という結果になった。

【安全スコアのイメージ(再掲)】
安全スコアのイメージ

目標は、安全スコアが小さくなる特徴は、盗難被害件数が多い特徴となっている事なので 実際に年齢が若いほど盗まれやすいのか、過去の盗難情報の年齢・職業別盗難発生件数から確認する。

年齢・職業別グラフ

年齢が若いほど発生件数が多くなる傾向がある事が確認できる。よって、安全スコアが小さくなる特徴は、盗難被害件数が多い特徴である事が確認できた。

他のグラフからも盗難件数が多くなる特徴が年齢以外にも見つかる。これらの特徴に、userInfoの値を設定すれば安全スコア(マハラノビス距離)が小さくなっていく事も確認できた。

以上より、オープンデータを活用した盗難推測(任意に指定した特徴量が盗難被害の特徴に似ているかを判定)を実現した。

ソースコード

github.com

(githubのnotebookが見れない場合はこちら)