본문 바로가기
데이터 분석 및 시각화

[ML] 의사 결정 나무(decision tree)_bike 데이터 활용

by 바다의 공간 2024. 7. 5.

트리구조로 도식화한 의사 결정 지원 도구의 일종입니다. (알고리즘이름임)

 

결정 트리는 3가지 종류의 노드로 구성됩니다.

이름 표시방법 이미지
결정 노드(decision node) 사각형으로 보통 표시함
기회 노드(chance node) 원으로 보통 표시함 -
종단 노드(end node) 삼각형으로 보통 표시함 -

 


의사 결정 나무(Decision Tree)

  • 데이터를 분석하고 패턴을 파악하여 결정 규칙을 나무 구조로 나타낸 기계학습 알고리즘
  • 간단하고 강력한 모델 중 하나로, 분류와 회귀 문제에 모두 사용
  • 엔트로피 : 데이터의 불확실성을 측정. 특정 속성으로 데이터를 나누었을 때 엔트로피가 얼마나 감소하는지를 계산하여 정보를 얻음. 정보 익득이 높은속성을 선택하여 데이터를 나누게 됨 (= 확실하게 나누는 기준으로 사용됨)
  • 지니계수 : 데이터의 불순도를 측정하는 또 다른 방법. 임의로 선택된 두 개의 요소가 서로 다른 클래스에 속할 확률을 나타냄 지니 불순도가 낮을수록 데이터가 잘 분리된 것
  • 의사결정 나무는 오버피팅이 매우 잘 일어납니다
    • 오버피팅(과적합): 학습데이터에서는 정확하나 테스트데이터에서는 성과가 나쁜 현상을 말함.
    • 오버피팅을 방지하는 방법
      • 사전 가지치기 : 나무가 다 자라기 전에 알고리즘을 멈추는 방법
      • 사후 가지치기 : 나무를 끝까지 다 돌린 후에 밑에서부터 가지를 쳐 나가는 방법

 


bike 데이터셋

아래 데이터는 바이크가 대여된 자료을 가지고 프로젝트를 진행해보려고 합니다.

독립변수들이 들어가게되면 그 날의 바이크가 얼마나 몇 대나 대여가 될지 예측해보려고 합니다.

독립변수는 나머지들이고 종속변수는 count인 대여개수가 될것입니다.

 

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
bike_df = pd.read_csv('/data/bike.csv')
bike_df

 

bike_df.info()

여기서 확인할 수 있는 것

1. 16개의 컬럼

2. 오브젝트의 유무

- 이 부분은 나중에 오브젝트에서 숫자로 변경해주어야합니다.


bike_df.describe()

sns.displot(bike_df['count'])

sns.boxplot(y=bike_df['count'])

sns.scatterplot(x='feels_like', y='count', data=bike_df, alpha=0.3)

alpha값을 준 이유는 뒤에있는 점들이 안 보일까봐 투명으로 주었습니다.

sns.scatterplot(x='pressure', y='count', data = bike_df, alpha=0.3)

 

 

 

 

sns.scatterplot(x='wind_speed', y='count', data=bike_df, alpha=0.3)

sns.scatterplot(x='wind_deg', y='count', data=bike_df, alpha=0.3)

 

풍향에 따른 차이도 분명히 있다는것을 확인할 수 있었습니다.


결측치 확인

bike_df.isna().sum()

이것들을 채우거나 drop시키면 되지 않습니다. 

그렇다고 다 영향력이 있는 컬럼들이기때문에 null값들을 0으로 채워주도록 하겠습니다.

bike_df = bike_df.fillna(0)
bike_df.isna().sum()

bike_df.info()

여기서 이제  object를 날짜 시간(타임)을 변경해야합니다.

bike_df['datetime'] = pd.to_datetime(bike_df['datetime'])
bike_df.info()

#2018-01-01 00:00:00▶날짜, 시간 뽑기 가능
bike_df.head()

#파생변수 만들기 [년도, 월, 시간]
bike_df['year']=bike_df['datetime'].dt.year
bike_df['month']=bike_df['datetime'].dt.month
bike_df['hour']=bike_df['datetime'].dt.hour
bike_df.head()

여기서 日은 왜 빼먹었냐면 계절, 저녁, 아침, 오후를 뽑을거기때문에 일자는 중요하게 생각하진 않았습니다.

 

그렇지만 연월일을 합쳐놓은 데이터를 파생변수로 만들어보겠습니다.

bike_df['date'] = bike_df['datetime'].dt.date
bike_df.head()

위의 그래프를 선 그래프로 추이를 확인해보기

plt.figure(figsize=(14,4))
sns.lineplot(x='date', y='count', data=bike_df)
plt.xticks(rotation=45)
plt.show()

 

데이터가 없는것 같으니 확인해보겠습니다.

bike_df[bike_df['year'] == 2019].groupby('month')['count'].mean()

2019년도에 대한 데이터를 그룹으로 바꾸고 월에 대한 카운트 세보도록 하겠습니다(평균치)

bike_df[bike_df['year'] == 2020].groupby('month')['count'].mean()

2020년도에 대한 데이터를 그룹으로 바꾸고 월에 대한 카운트 세보도록 하겠습니다(평균치)

4월데이터가 없다는걸 확인할 수 있고 

4월에는 영업을 하지 않는다는것을 확인할 수 있습니다.


 

이상치 데이터를 확인했으니 3등분으로 임의로 나누어서 카테고리컬하게 나누었습니다.

#covid
#2020-04-01 이전 : precovid
#2020-04-01 이후 ~ 2021-04-01 이전 : covid
#2021-04-01 이후 : postcovid

이 부분을 함수로 만들어보겠습니다.

def covid(date):
    if str(date) < '2020-04-01':
      return 'precovid'
    elif str(date) < '2021-04-01':
      return 'covid'
    else:
      return 'postcovid'

 

적용해보겠습니다.

bike_df['date'].apply(covid)

 

각각 어떤 covid의 형태인지 잘 나뉘어졌습니다.


함수로 만들었지만 람다를 배웠으니 람다로도 적용해보겠습니다.

이번에는 covid라는 파생변수도 만들어서 적용했습니다..

bike_df['covid'] = bike_df['date'].apply(
    lambda date: 'precovid' if str(date) < '2020-04-01'
                            else 'covid' if str(date) < '2021-04-01'
                            else 'postcovid'
)
bike_df


이제 원했던 시즌(계절)도 파생변수를 만들어보겠습니다.

# season
# 12월~2월: winter
# 3월~5월: spring
# 6월~8월: summer
# 9월~11월: fall
bike_df['season'] = bike_df['month'].apply(
    lambda x: 'winter' if x == 12
                            else 'fall' if x >= 9
                            else 'summer' if x >= 6
                            else 'spring' if x >= 3
                            else 'winter'
)
bike_df[['month', 'season']]
bike_df

맨 오른쪽 season파생변수로도 잘 들어간것을 확인할 수 있습니다.


#day_night
#21시 이후 ~ :night
#19시 이후~ : late evening
#17시 이후~ : early evening
#15시 이후~ : late afternoon
#13시 이후~ : early afternoon
#11시 이후~ : late morning
#06시 이후~ : early morning

 

bike_df['day_night'] = bike_df['hour'].apply(
    lambda x: 'night' if x >=21
                    else 'late evening' if x >= 19
                    else 'early evening' if x >= 17
                    else 'late afternoon' if x >= 15
                    else 'early afternoon' if x >= 13
                    else 'late morning' if x >= 11
                    else 'early morning' if x >= 6
                    else 'night'    
)

 

bike_df

파생변수로 잘 들어가있는것을 확인할 수 있습니다. 

그러면 이제 굳이 데이터라인에 들어가있지 않아도 되는 칼럼들을 drop해버리겠습니다.

bike_df.drop(['datetime', 'month', 'date', 'hour'], axis=1, inplace=True )
bike_df.head()

 

데이터 전처리는 어느정도 했고, 모델에 넣기 위해서 수치화되어있지않은 오브젝트가 무엇이 있는지 확인해보니

weather_main, covid, season, day_night 가 생겼습니다.

더 생긴이유는 방금 만들었기 때문입니다 이 부분도 숫자로 바꾸어줘야합니다.

 

일단 종류가 몇개인지 알아보도록 하겠습니다.

(유니크, 언유니크 활용)

for i in['weather_main', 'covid', 'season', 'day_night']:
    print(i, bike_df[i].nunique())

 

이정도는 원핫인코딩으로 만들 수 있기때문에 원핫인코딩으로 하겠습니다.

그런데 웨덜메인이 좀 많으니 도대체 뭐가 있는지 확인해보려고 합니다.

bike_df['weather_main'].unique()

#array(['Clouds', 'Clear', 'Snow', 'Mist', 'Rain', 'Fog', 'Drizzle',
       'Haze', 'Thunderstorm', 'Smoke', 'Squall'], dtype=object)

 

이렇게 11개가 있습니다.

흐린날, 맑은날, 눈오는날, 뭐 등등등 있습니다. 모두살려서 가져가겠습니다.

원핫인코딩 

bike_df = pd.get_dummies(bike_df, columns=['weather_main', 'covid', 'season', 'day_night'])

bike_df.head()

전처리가 모두 끝났습니다.


예측하기

from sklearn.model_selection import train_test_split
X_train, X_test,y_train, y_test = train_test_split(bike_df.drop('count', axis=1), bike_df['count'], test_size=0.2, random_state=2024)

테스트 트레이닝 값 나누기

X_train.shape , y_train.shape

#((26703, 39), (26703,))
X_test.shape , y_test.shape

#((6676, 39), (6676,))

 


from sklearn.tree import DecisionTreeRegressor

#DecisionTreeRegressor을 사용하는 이유는 예측하기 위해서 입니다.
dtr = DecisionTreeRegressor(random_state=2024)

DecisionTreeRegressor에 넣어줄 수 있는것은 많고 튜닝을 해주어야합니다.

random_state로 값을 2024로 주었습니다.

dtr.fit(X_train, y_train)

학습을 시켰습니다.

pred1 = dtr.predict(X_test)

예측했습니다.

sns.scatterplot(x=y_test, y=pred1)

from sklearn.metrics import mean_squared_error
mean_squared_error(y_test, pred1, squared=False)
#210.74186203651976

210정도 오차를 확인할 수 있습니다.

 


 

사용 설명
to_datedtime() datetime으로 변경
.apply 함수 처리되는 매소드