見出し画像

交通事故統計情報のオープンデータを使って、交通事故の予測をしてみました


1.はじめに

・このブログはAidemy Premiumのカリキュラムの一環で、受講修了条件を満たすために公開しています。6か月のデータ分析講座で学んだことを使ってオリジナルテーマのブログを作成しようと思います。

・私は保険業界で働いていますので、事故の外部環境(天候、時間帯、車両種別、年齢)などから車両の損壊程度の予測をするというテーマで取り組みたいと考えました。


2.データ分析環境

上記のサイトからデータを取得し、ローカルマシン上で分析を行いました。
・Windows 11 Pro
・jupyterlab バージョン 4.0.3
・Python 3.11.4


3.データ分析の流れ

  1. データ前処理
    ・2021年の本票データを訓練用データ(305196行, 58列)、2022年の本票データ(300839行, 68列)をテスト用データとし、車両の損壊程度(1:大破、2:中破、3:小破、4:損壊なし、0:対象外当事者)の予測を試みようと思います。
    ・対象年によってデータの項目数が異なるため、「表3-1-1.交通事故分析対象データ」のようなデータに揃えるよう、前処理を行いました。

  2. データの可視化
    ・つぎに訓練用データとテストデータをマージした全体のデータの分布を確認しました。「表3-2-1. 対象データの可視化」のとおり可視化すると、分析対象のデータは以下の特徴がありそうです。
    (グラフの左から順に)
    1)事故内容(1:死亡、2:負傷)は、圧倒的に「2:負傷」が多い
    2) 昼夜(11:昼-明、12:昼-昼、13:昼-暮、21:夜-暮、22:夜-夜、23:夜-明)は、圧倒的に「12:昼-昼」が多い
    3)車両の損壊程度(1:大破、2:中破、3:小破、4:損壊なし、0:対象外当事者)は、圧倒的に「3:小破」が多い
    4) 年齢(01:0~24歳、25:25~34歳、35:35~44歳、45:45~54歳、55:55~64歳、65:65~74歳、75:75歳以上、00:不明)は、「45:45~54歳」がやや多い。

    ・また、「表3-2-2. 対象データのヒートマップ」のとおり、車両の損壊程度と各カラムの相関をヒートマップで可視化すると、つぎの相関関係があることが分かりました。
    5)当事者種別と用途別には強い負の相関(-0.51)がある
    6)当事者種別と速度規制には弱い負の相関(-0.23)がある
    7)用途別と車両形状には弱い負の相関(-0.25)がある
    8)当事者種別と車両形状には弱い正の相関(0.21)がある

  3. 特徴量作成
    各カラムはカテゴリカル変数のため、One-Hot Encodingを施し、特徴量作成をします。なお、One-Hot Encodingを行うと特徴量が膨大になるため、当事者B側のカラムは全て削除し、ターゲット変数を「車両の損壊程度」にします。この結果、
    ・2021年の事故データ(訓練データ)は、 (299187行, 84列)
    ・2022年の事故データ(検証データ)は、 (293627行, 84列)
    として特徴量を作成しました。

  4. 予測モデルを構築
    車両の損壊程度の予測を行うため、モデルを構築します。
    タスクは分類問題(1:大破、2:中破、3:小破、4:損壊なし、0:対象外当事者)なので、初めにロジスティック回帰を用いて予測モデルを構築したところ、予測性能は以下のとおりでした。
    ・2021年の訓練データの2割を検証データとした場合の正解率
     0.8303419231926201
    ・2022年の事故データを検証データとした場合の正解率
     0.8317593409325437

  5. 予測モデルの性能向上
    予測モデルの性能向上のために、ロジスティック回帰以外のモデルで2022年の事故データを検証データとした場合の正解率を比較しました。
    ・非線形SVM:結果が返ってこず(計算量が多すぎるものと思います)
    ・決定木:0.7923249564924206
    ・ランダムフォレスト:0.8155789488023921


4.考察

・ロジスティック回帰、決定木、ランダムフォレストを比較した結果、ロジスティック回帰が一番良い結果になりましたが、車両の損壊程度は圧倒的に「3:小破」が多いという分布になっているため、今回使用したデータで正解率を評価するのは妥当でないと考えました。

・現実の予測問題(例えばこれが保険料率を決定するような予測モデル)を考えると、実際は「3:小破」であるのに「1:大破」や「2:中破」と予測してしまうと保険契約者に不利な予測となり、予測モデルとしては不十分かと思います。
したがって、今回の問題では二値分類の問題における「再現率」が評価精度としては優位になると考えます。

・今回の問題は多クラス分類であるため、マクロ平均(クラスごとのF1-scoreの平均)やマイクロ平均(レコード×クラスのペアごとにF1-scoreを算出)といった評価基準があるようです。
この評価基準を算出すると以下のような値になりましたが、この値に対する解釈は今後勉強していこうと思います。
・macro-f1_score: 0.4073966024343171
・micro-f1_score: 0.8317593409325436


5.おわりに

・6か月のデータ分析講座を終え、pythonや機械学習に関して初心者でしたが、自身でテーマを決め分析を行えるようになりました。

・実際に分析を行ってみると、公開されているCSVデータを元にしても、データの前処理と可視化を行い、データの特徴を概観することにかなりの時間を費やすことも実感できました。

・今後も勉強を続け、E資格の取得やKaggleの問題にチャレンジしていきたいと思います。この記事を読んでいただき、ありがとうございます。


6.Appendix -表-

表3-1-1.交通事故分析対象データ *赤字に該当する行を含むデータは分析対象から除外
表3-2-1. 対象データの可視化
表3-2-2. 対象データのヒートマップ

7.Appendix -コード-

データ分析の流れが分かるよう、分析に用いたPythonコードをポイントで抜粋して掲載します。

3-1. データ前処理で用いたコード

#-----------------------
# 3-1. データ前処理
#-----------------------

import numpy as np
import pandas as pd

# オープンデータ読み込み
train_df = pd.read_csv('honhyo_2021.csv', encoding='shift_jis')
test_df = pd.read_csv('honhyo_2022.csv', encoding='shift_jis')

# 不要なカラムの削除
train_df = train_df.drop(drop_col_2021, axis=1)
test_df = test_df.drop(drop_col_2022, axis=1)

# 列名の変更
train = train_df.rename(columns=rename_col_2021)
test = test_df.rename(columns=rename_col_2022)

#条件にマッチしたIndexを訓練データから削除
train = train.drop(drop_index)
test = test.drop(drop_index)

# データ全体にまとめて処理を行うために、訓練データとテストデータを結合
all_df = pd.concat([train,test],axis=0).reset_index(drop=True)
all_df['Test_Flag'] = 0
all_df.loc[train.shape[0]: , 'Test_Flag'] = 1

3-2. データ可視化で用いたコード

#-----------------------
# 3-2. データ可視化
#-----------------------

import seaborn as sns

# 事故内容について可視化
sns.countplot(x='jiko', hue='Test_Flag', data=all_df) 
plt.show()

# 昼夜について可視化
sns.countplot(x='daynight', hue='Test_Flag', data=all_df) 
plt.show()

# 車両の損壊程度(当事者A)について可視化
sns.countplot(x='vehicledamage_a',hue='Test_Flag', data=all_df)
plt.show()

# 年齢(当事者A)について可視化
sns.countplot(x='age_a',hue='Test_Flag', data=all_df)
plt.show()

# 車両損壊の程度と各カラムの相関をヒートマップで可視化
sns.heatmap(
    train[['vehicledamage_a',
           'daynight',
           'weather',
           'terrain',
           'jikoruikei',
           'age_a',
           'party_a',
           'purpose_a',
           'vehicle_a',
           'speed_a']].corr(),
    vmax=1,vmin=-1,annot=True
    )

3-3. 特徴量作成で用いたコード

#-----------------------
# 3-3. 特徴量作成
#-----------------------

# One-Hot_Encodingで変換
all_df = pd.get_dummies(all_df, columns=OneHot_col)

# 前処理を施したall_dfを訓練データとテストデータに分割
train = all_df[all_df['Test_Flag']==0]
test = all_df[all_df['Test_Flag']==1].reset_index(drop=True)

# 訓練データのvehicledamage_aをtargetにする
target = train['vehicledamage_a']

# テストデータのvehicledamage_aをtest_valにする
test_val = test['vehicledamage_a']

# 'Test_Flag','vehicledamage_a'カラムを削除
train = train.drop(['Test_Flag','vehicledamage_a'], axis=1)
test = test.drop(['Test_Flag','vehicledamage_a'], axis=1)

3-4. 予測モデル構築で用いたコード

#-----------------------
# 3-4. 予測モデル構築
#-----------------------

from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.svm import LinearSVC, SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn.metrics import f1_score

# 訓練データの一部を分割し検証データを作成
X_train ,X_val ,y_train ,y_val = train_test_split(
    train, target, 
    test_size=0.2, shuffle=True,random_state=0
    )

# モデルを定義し学習(ロジスティック回帰)
model = LogisticRegression() 

# 他のモデルを定義し学習する時は、下記を置換
# model = LinearSVC()
# model = SVC()
# model = DecisionTreeClassifier()
# model = RandomForestClassifier()

model.fit(X_train, y_train)

# テストデータを予測
test_pred = model.predict(test)

# テストデータに対しての予測を行い、正解率を算出
print('test_accuracy_score:', accuracy_score(test_val, test_pred))

# マクロ平均とマイクロ平均を算出
print('test_macro-f1_score:', f1_score(test_val, test_pred, average='macro'))
print('test_micro-f1_score:', f1_score(test_val, test_pred, average='micro'))

この記事が気に入ったらサポートをしてみませんか?