Kwon's 데이터분석기

Daicon 경진대회 - 대구 교통사고 피해 예측 EDA 코드 본문

데이터 분석

Daicon 경진대회 - 대구 교통사고 피해 예측 EDA 코드

DataKwon 2023. 12. 12. 14:13

대구 교통사고 피해예측 AI 경진대회가 있어서 참여를 해 보았다.

(데이터 출처: https://dacon.io/competitions/official/236193/data)

 

코딩을 입력한 곳은 Jupyter Notebook을 사용하였다.

 

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import os
import warnings
warnings.filterwarnings('ignore')
from matplotlib import rc
rc('font', family = 'Malgun Gothic')
plt.rcParams['axes.unicode_minus']= False

먼저 데이터 분석하기 전 필요한 기본 코드를 실행시켰다.

 

train_data = pd.read_csv('./dataset/accident/train.csv')
test_data = pd.read_csv('./dataset/accident/test.csv')
result_data = pd.read_csv('./dataset/accident/sample_submission.csv')

print(train_data.info(), '\n')
print(test_data.info())

그 후 주피터 노트북에 사용할 데이터를 불러온 후 train데이터셋과 test데이터셋의 변수에 대한 정보를 불러와 무슨 변수들이 존재하는지에 대해서 info()함수를 통해 확인해보았다.

 

변수에 대한 종류는 이렇게 나타났다. 이러한 변수를 가지고 먼저 데이터에 대해서 탐색을 해볼 것이다.

 

# train, test 데이터셋중 ID는 데이터EDA를 할 때 사용하기 어려운 고유변수이기 때문에 제거해준다.
train_data.drop(['ID'], axis = 1, inplace = True)
test_data.drop(['ID'], axis = 1, inplace = True)

 

# 데이터에 대한 shape, 결측값 유무, 중복값, describe 통계값, 유니크 값의 개수를 알아보는 함수 생성
def wrangling(train_set, test_set):
    print('---'*10)
    display(train_set.shape)
    display(test_set.shape)
    print('---'*10)
    display(train_set.isna().sum())
    display(test_set.isna().sum())
    print('---'*10)
    display(train_set[train_set.duplicated()])
    display(test_set[test_set.duplicated()])
    print('---'*10)
    display(train_set.describe())
    display(test_set.describe())
    print('---'*10)
    display(train_set.nunique())
    display(test_set.nunique())
    
# train, test데이터를 넣어서 생성한 함수에 대한 값 출력
wrangling(train_data, test_data)

 

# 데이터에 대해서 int형식으로 변경하지만 만약 값이 결측값이면 nan값을 넣어준다.
import re
def extract_convert_int(data):
    numbers = re.findall(r'\d+', str(data))
    return int(numbers[0]) if numbers else np.nan
  
'''
가해운전자 연령과 피해운전자 연령은 연령이라는 수치형으로 나와야하는데 info()결과에서 object로
나왔기 때문에 int값으로 변환시켜준다.
값 중 결측값이면 nan값을 넣어주는 이유는 피해운전자 연령에서 결측값이 존재하기 때문이다.
'''
train_data['가해운전자 연령'] = train_data['가해운전자 연령'].apply(extract_convert_int)
train_data['피해운전자 연령'] = train_data['피해운전자 연령'].apply(extract_convert_int)

 

'''
이렇게 처리한 데이터의 타입을 범주형인 object타입과 수치형인 int, object타입의 열의 종류를
구분하여 데이터의 타입에 따라 그래프를 그려볼 것이다.
'''
num_cols = train_data.select_dtypes(exclude = 'object').columns.tolist() #수치형 변수 열 추출
cat_cols = train_data.select_dtypes(include = 'object').columns.tolist() #범주형 변수 열 추출


# 수치형 변수에 대한 히스토그램을 그린다.
i = 0
plt.figure(figsize=(12, 8))
for col in num_cols:
    i += 1
    plt.subplot(3, 3, i)
    sns.distplot(train_data[col])
plt.tight_layout()

수치형 변수에 대한 히스토그램

이러한 히스토그램만으로는 이 데이터가 정규분포를 따르는지에 대해서는 잘 알수가 없었다.

더욱 확실하게 정규분포를 따르는지 확인해보기 위해 Q-Q Plot을 한번 찍어본다.

 

from scipy import stats

i = 0
plt.figure(figsize=(12, 8))
for col in num_cols:
    i += 1
    plt.subplot(3, 3, i)
    stats.probplot(train_data[col], dist="norm", plot=plt)
    #sns.distplot(train_data[col])
plt.tight_layout()

수치형 데이터에 대한 Q-Q Plot

Q-Q Plot을 그려본 결과 가해운전자 연령, 피해운전자 연령은 확실하게 정규분포를 따른다고 할 수 있고

사망자수, 경상자수, 부상자수, ECLO의 Q-Q Plot은 잘 이어지다 선에 벗어나는 것을 보니 비정규적인 꼬리 부분이나 이상치의 존재가 있다는 것을 나타낸다.

그리고 중상자수는 확실하게 정규분포를 따르지 않는 것 같다는 것을 알 수 있었다.

 

이제 범주형 변수에 대해서 막대그래프를 그려 범주별로 비교를 해볼 것이다.

i = 0
plt.figure(figsize = (24, 24))
for col in ['요일',
           '기상상태',
           '시군구',
           '도로형태',
           '노면상태',
           '사고유형',
           '사고유형 - 세부분류',
            '법규위반',
            '가해운전자 차종',
            '가해운전자 성별',
            '가해운전자 상해정도',
            '피해운전자 차종',
            '피해운전자 성별',
            '피해운전자 상해정도'
           ]:
    
    i += 1
    plt.subplot(5, 3, i)
    sns.barplot(x=train_data[col], y= train_data['ECLO'], ci = None)
    plt.xticks(rotation=45)
plt.tight_layout()

먼저 사용할 범주를 지정을 해 준 다음 이러한 범주 별 ECLO를 비교를 할 것이다.

범주형 변수 별 막대그래프

범주형 변수 별 ECLO에 대한 막대그래프로 알 수 있는 정보

- 토요일, 일요일의 경우에 ECLO가 다른 날짜보다 약간 높음

- 안개가 낀 상황에 ECLO가 높음

- 단일로 - 터널의 경우 ECLO가 높음

- 노면상태가 침수인 경우 ECLO가 높았으며 반대로, 적설인 경우 ECLO가 낮음

- 차vs사람 보다 차vs차일 때 ECLO가 높음

- 도로외이탈 - 추락일때가 ECLO가 가장 높고 그다음은 전도전복 - 전복, 추돌과 정면충돌이 비슷하게 높음

- 법규위반이 과속인 경우 ECLO가 높음

- 가해운전자 차종이 승용, 승합, 화물, 건설기계인 경우 ECLO가 높음

- 가해운전자 성별에 따른 차이는 보이지 않음

- 피해운전자 차종이 농기계인 경우 ECLO가 높음

 

그래프중 가해운전자 상해정도와 피해운전자의 상해정도를 보지않은 이유는 ECLO는 인명피해 심각도로

  • ECLO = 사망자수 * 10 + 중상자수 * 5 + 경상자수 * 3 + 부상자수 * 1 이렇게 구해지기 때문에 넣지 않았다.

 

데이터 중 시군구 별 ECLO의 막대그래프가 보기 힘들기 때문에 따로 확인해본다.

plt.figure(figsize=(50,12))
sns.barplot(x=train_data['시군구'], y= train_data['ECLO'], ci = None)
plt.xticks(rotation=90)
plt.show()

시군구 별 ECLO의 막대그래프

이렇게 확인해봐도 보기 힘들지만 여기서 알 수 있는 것은 대구의 시군구 별 ECLO가 큰 차이는 없지만 특정 몇 구역에는 차이가 존재하는 것을 확인할 수 있다. 아마도 ECLO가 다른 시군구보다 큰 지역은 사고와 관련된 문제가 존재하는 것 같다

 

시군구별 ECLO평균이 높은곳과 낮은곳을 찾아볼 것이다.

num_by_region = train_data.groupby(['시군구'])['ECLO'].mean().sort_values(ascending=False)
print('높은 ECLO')
display(num_by_region[:10])
print('-'*50)
print('낮은 ECLO')
display(num_by_region[-10:])

ECLO 높은 지역과 낮은 지역의 각 평균

높은 ECLO와 낮은 ECLO의 시군구 지역을 확인할 수 있었고, ECLO가 높은 지역에 대해서 한번 직접적으로 무엇이 문제인 것인지 확인이 필요해 보인다.

 

위의 지역별 ECLO는 동을 기준으로 얻어진 ECLO의 평균값이기 때문에 군, 구에 대해서도 ECLO의 평균값을 구하여 군, 구로 ECLO의 평균을 한번 파악해본다.

# 데이터의 형태가 (시) (군/구) (동) 으로 이루어져 있기 때문에 이를 기준으로 분리해줄 수 있도록함
location_pattern = r'(\S+) (\S+) (\S+)'

# 도시 구 동이라는 컬럼을 만들어, 시를 train데이터의 도시에
# 군/구를 train데이터의 구에, 동을 train데이터의 동에 넣어준다.
train_data[['도시', '구', '동']] = train_data['시군구'].str.extract(location_pattern)

#train데이터를 구를 기준으로 그룹핑을 하여 ECLO의 평균 중 가장 큰 값 10개를 출력
display(train_data.groupby(['구'])['ECLO'].mean().nlargest(10))
#train데이터를 동을 기준으로 그룹핑을 하여 ECLO의 평균 중 가장 큰 값 10개를 출력
display(train_data.groupby(['동'])['ECLO'].mean().nlargest(10))

구, 동을 기준으로 ECLO평균이 높은    10개의 값 출력

이를 통하여 구를 기준으로 달성군이 ECLO평균이 가장 높고, 동을 기준으로 노곡동이 ECLO 평균이 가장 높은 것을

알 수 있다.

 

이번에는 법규위반 별 가해운전자 연령과 피해운전자의 연령의 boxplot을 그려보았다.

i = 0
plt.figure(figsize = (8, 10))
for col in ['가해운전자 연령',
            '피해운전자 연령'
           ]:
    
    i += 1
    plt.subplot(2, 1, i)
    sns.boxplot(data=train_data, y='법규위반', x = col)
plt.tight_layout()

법규위반별 가해, 피해운전자 연령의 boxplot

boxplot으로 알수 있었던 것은 법규위반별 가해, 피해운전자의 연령의 분포가 30~ 50대가 큰 것을 알 수 있다.

아마도 차를 가질 수 있는 사람들은 경제적으로 여유가 존재하는 성인부터 차를 가지고 있을 확률이 높기 때문에 이러한 분포를 가진것일거다.

 

이번에는 가해운전자 연령과 피해운전자 연령과 ECLO에 대해서 산점도를 그려 무슨 관계를 가지고 있는지 확인을 한다.

plt.scatter(x=train_data['가해운전자 연령'], y = train_data['ECLO'])
plt.ylabel('ECLO')

plt.scatter(x=train_data['피해운전자 연령'], y = train_data['ECLO'])
plt.ylabel('ECLO')
plt.legend(['가해운전자', '피해운전자'])

가해, 피해운전자 연령 - ECLO의 산점도

산점도 결과 가해운전자와 피해운전자의 연령은 ECLO와 선형관계가 존재하지 않는다는 것을 알 수 있다.

 

그 다음으로는 년, 월, 시간에 대해서 ECLO가 어떻게 되는지에 대해서 확인해본다.

먼저 데이터 중 년, 월, 일, 시의 정보가 모두 들어있는 사고일시를 사용을 하여 데이터의 열을 제작하여 값을 넣어준다.

import datetime as dt
train_data['사고일시'] = pd.to_datetime(train_data['사고일시'])

# 년도라는 열을 만들어 사고일시에서 년도의 값을 넣어준다.
train_data['년도'] = pd.to_datetime(train_data['사고일시']).dt.year

# 월이라는 열을 만들어 사고일시에서 월의 값을 넣어준다.
train_data['월'] = pd.to_datetime(train_data['사고일시']).dt.month

# 일이라는 열을 만들어 사고일시에서 일의 값을 넣어준다.
train_data['일'] = pd.to_datetime(train_data['사고일시']).dt.day

# 시간이라는 열을 만들어 사고일시에서 시간의 값을 넣어준다.
train_data['시간'] = pd.to_datetime(train_data['사고일시']).dt.hour

 

그 후 년도에 따른 ECLO의 그래프, 월에 대한 ECLO의 그래프, 시간에 대한 ECLO의 그래프를 그려준다.

plt.figure(figsize=(18,12))
plt.subplot(3, 1, 1)
sns.lineplot(data=train_data, x= '년도', y = 'ECLO')

plt.subplot(3,1,2)
sns.barplot(data=train_data, x= '월', y = 'ECLO')

plt.subplot(3,1,3)
sns.lineplot(data=train_data, x = '시간', y='ECLO')

년, 월, 시에 따른 ECLO값 그래프

위 그래프를 통해 ECLO의 값이 연도가 증가할수록 점진적으로 낮아지는 것을 알 수 있고, 월별에 따른 ECLO의 값에는 차이가 존재하지 않아 월은 상관이 없어보인다. 시간대에 따른 ECLO의 값에서는 20시부터 ECLO가 증가하기 시작하여 3시에 최고를 찍으며, 5시까지 높은 ECLO를 얻게 된다. 그 후의 시간대는 ECLO가 낮은 것을 알 수 있다.

 

마지막으로 데이터 중 수치형 변수에 대해서 상관관계를 구하여 히트맵을 그려보겠다.

# 데이터 중 수치형 데이터를 구해준다.
numeric_data = train_data.select_dtypes(include=[np.number])
# 이렇게 구한 수치형 데이터에 대해서 상관계수를 구해준다.
correlation_matrix = numeric_data.corr()

# 구한 상관계수를 통해 히트맵을 그려준다.
plt.figure(figsize=(16,12))
sns.heatmap(correlation_matrix, annot = True)
plt.show()

ㅇ수치형 데이터에 대한 상관계수 히트맵

이러한 히트맵에서 ECLO가 사망자수, 중상자수, 경상자수와 관계가 있다고 나온다.

먼저 ECLO는 사망자수, 중상자수, 경상자수와 관계가 존재하는 이유는 ECLO자체가 사망자수, 중상자수, 경상자수, 부상자수를 기준으로 점수를 산정해서 만들어진 변수이기 때문이다.

그 외의 변수끼리의 상관관계는 존재하지 않는다고 볼 수 있다.

 

이렇게 데이터 시각화를 통한 데이터 분석 및 EDA를 마치겠다.

아직 부족한 실력이라서 다른 코드를 함께 참고하면서 만들었으며, 교통사고에 대한 도메인 지식이 따로 존재하지 않았기 때문에 무엇을 분석을 해야하는지 감이 잡히지 않아 다른사람이 EDA를 한 것을 다양하게 보며 시도를 해보았다.

부족한 부분이 있을 수 있지만 이는 차차 다른사람들의 코드를 하나씩 흡수해가면서 역량을 올리도록 할 것이다.

다음에 다른 프로젝트에 대해서 EDA 및 데이터분석을 조금이나마 더 숙달시켜 내것으로 만들기 위해 노력할 것이다.