[Kaggle] 자전거 수요 예측 분석 (bike-sharing demand prediction)

2020. 4. 2. 22:36노트/Python : 프로그래밍

 

 

데이터 다운로드  

 

sampleSubmission.csv
0.14MB
test.csv
0.31MB
train.csv
0.62MB

 

https://www.kaggle.com/c/bike-sharing-demand/data

 

Bike Sharing Demand

Forecast use of a city bikeshare system

www.kaggle.com

 

bike data 불러오기 

이번 프로젝트는 Kaggle에 있는 공용 자전거 수요 데이터를 학습하여, 각 날짜마다 자전거 수요를 예측해보는 프로젝트에 대해 포스팅하려고 한다. 

 

먼저 트레이닝 데이터와 테스트 데이터를 불러온다. 

train=pd.read_csv("train.csv", parse_dates=['datetime'])
#pare_dates: 날짜 시간으로된 컬럼을 datetime으로 파싱
train.head()
test=pd.read_csv("test.csv",parse_dates=['datetime'])
test.head()

train.info()
test.info()
train.shape #(10886,12)
test.shape #(6493,9)

 

 

중복값 결측값 확인 

  • 훈련 데이터, 테스트 데이터 중복 값 있는지 확인
  • 훈련 데이터, 테스트 데이터 null 값 (결측값) 확인
train[train.duplicated()] # 중복값 없음 
test[test.duplicated()] # 중복값 없음

 

 

train.isnull().sum() #결측값 없음
test.isnull().sum() #결측값 없음

 

 

  • 결측값 확인 다른방법 : missingno 라이브러리
#Anaconda prompt >> pip install missingno 설치
import missingno as msno
msno.matrix(train,figsize=(12,5)) 
#흰색선이 보이지 않으므로 결측값 없음 

 

 

 

년,월,일,시 에 따른 평균 대여량 구하기 

 

  • datetime에서 년,월,일,시,분,초를 추출하여 각각의 column으로 구성
train['year']=train['datetime'].dt.year # 년도 추출 
train['month']=train['datetime'].dt.month # 월도 추출 
train['day']=train['datetime'].dt.day # 일 추출 
train['hour']=train['datetime'].dt.hour # 시 추출 
train['minute']=train['datetime'].dt.minute # 분 추출 
train['second']=train['datetime'].dt.second # 초 추출 
train['dayofweek']=train['datetime'].dt.dayofweek #요일 추출 , 월요일:0, 일요일:6
test['year']=test['datetime'].dt.year # 년도 추출 
test['month']=test['datetime'].dt.month # 월도 추출 
test['day']=test['datetime'].dt.day # 일 추출 
test['hour']=test['datetime'].dt.hour # 시 추출 
test['minute']=test['datetime'].dt.minute # 분 추출 
test['second']=test['datetime'].dt.second # 초 추출 
test['dayofweek']=test['datetime'].dt.dayofweek #요일 추출 , 월요일:0, 일요일:6

 

탐색적 데이터 분석 (EDA)

 

  • 기술통계 분석 
train.describe() #기술통계

 

 

 

  • 근무일 유무/요일/시즌/날씨에 따른 시간대별 자전거 대여량 구하기 
import matplotlib.pyplot as plt
fig,((ax1,ax2,ax3),(ax4,ax5,ax6)) =plt.subplots(nrows=2, ncols=3)
fig.set_size_inches(20,8)

import seaborn as sns 
sns.barplot(data=train, x="year", y="count", ax=ax1)
sns.barplot(data=train, x="month", y="count", ax=ax2)
sns.barplot(data=train, x="day", y="count", ax=ax3)
sns.barplot(data=train, x="hour", y="count", ax=ax4)
sns.barplot(data=train, x="minute", y="count", ax=ax5)
sns.barplot(data=train, x="second", y="count", ax=ax6)

 

 

 

2011년도 보다 2012년도가 자전거 수요량이 상승했다. 

겨울보다 여름에 자전거 수요량이 상승한다. 

연초보다 연말에 자전거 수요량이 소폭 상승한다.

새벽시간보다 출퇴근 시간에 자전거 수요량이 상승한다. 

 

fig, axes = plt.subplots(nrows=2, ncols=2)
fig.set_size_inches(20,8)

sns.boxplot(data=train, x="season", y="count", ax=axes[0][0])
sns.boxplot(data=train, x="workingday", y="count", ax=axes[0][1])
sns.boxplot(data=train, orient="v", y="count", ax=axes[1][0]) 
#상자 그림 작성 (세로방향) orient“v” | “h”, optional
sns.boxplot(data=train, x="hour", y="count", ax=axes[1][1])

 

 

겨울 보다 여름 가을에 수요량이 더 많은 것을 확인할 수 있다. 

겨울에는 극단치 데이터가 많은 편이다. 

근무시간이 아닐 때 보다 근무시간에 극단치 데이터가 많은 편이다. 

출퇴근 시간에 자전거 수요량이 높음을 확인할 수 있다. 

train['dayofweek'].unique()

 

 

train['dayofweek'].value_counts()

 

 

fig, (ax1, ax2, ax3, ax4, ax5)=plt.subplots(nrows=5)
fig.set_size_inches(18,25)

sns.pointplot(data=train, x="hour", y="count", ax=ax1)
# x, y, hue names of variables in data or vector data, optional
sns.pointplot(data=train, x="hour", y="count",hue='workingday' ,ax=ax2)
sns.pointplot(data=train, x="hour", y="count",hue='dayofweek' ,ax=ax3)
sns.pointplot(data=train, x="hour", y="count",hue='weather' ,ax=ax4)
sns.pointplot(data=train, x="hour", y="count",hue='season' ,ax=ax5)

 

 

fig,(ax1,ax2,ax3)=plt.subplots(ncols=3)
fig.set_size_inches(12,5)

sns.regplot(x="temp",y="count",data=train, ax=ax1)
sns.regplot(x="windspeed",y="count",data=train, ax=ax2)
sns.regplot(x="humidity",y="count",data=train, ax=ax3)

 

 

자전거 수요량과 기후는 양의 상관관계

풍속은 적은 양의 상관관계 

습도와는 음의 상관관계를 보인다.  

 

  • 연도별 월별 자전거 대여량 구하기

#2011년과 2012년 따로 구분하여 재출력 하고자함 
def ym(mydt):
    return"{0}-{1}".format(mydt.year,mydt.month)

train['year_month']=train['datetime'].apply(ym)
train[['datetime','year_month']]

 

 

fig,axes=plt.subplots(nrows=1, ncols=1)
fig.set_size_inches(18,4)

sns.barplot(data=train, x="year_month", y="count",ax=axes)
# 2012년에 자전거 대여량증가, 겨울보다 여름에 증가 

 

 

 

 

 

아웃라이어 제거

 

정상범위 데이터 : count열값-count열값평균 < 3*(count.std)

#이상치(outliers) 제거 z<3 
import numpy as np

trainWithoutOutliers=train[np.abs(train["count"]-train["count"].mean()) <= (train['count'].std()*3)]

print(train.shape)
print(trainWithoutOutliers.shape) # Outliers 제거 확인

 

 

데이터 보정

#풍속이 0인 것과 0이 아닌 것을 구분하여 저장 
trainWind0=train.loc[train['windspeed']==0]
trainWindNot0=train.loc[train['windspeed']!=0]

print(trainWind0.shape)
print(trainWindNot0.shape)

 

 

 

-풍속 등 각 필드에 0으로 저장되어 있는 값을 근사값으로 보정

 

# 머신러닝 랜덤포레스트로 풍속 예측 
from sklearn.ensemble import RandomForestClassifier

#data의 windspeed값이 0인 데이터를 랜덤 포레스트를 이용하여 예측한 값으로 대체 
def predict_windspeed(data): #(출력확인용)
    #풍속 예측에 사용되는 변수 
    wCol=['season','weather','humidity','temp','atemp']
    
    #풍속을 0인 것과 0이 아닌 것으로 구분
    dataWind0=data.loc[data['windspeed']==0]
    dataWindNot0=data.loc[data['windspeed']!=0]
    
    #랜덤포레스트 분류기 생성
    rfModel=RandomForestClassifier()
    dataWindNot0['windspeed']=dataWindNot0['windspeed'].astype("str")
    
    # wCol > 풍속학습 > 모델완성 
    rfModel.fit(dataWindNot0[wCol], dataWindNot0['windspeed']) #(학습대상, 학습자료)
    
    #학습한 모델로 풍속 0에 대한 데이터 예측 
    preValue=rfModel.predict(X=dataWind0[wCol])
    print(preValue)
    
    #풍속이 0인 것과 0이 아닌 것으로 분류 
    predictWind0=dataWind0
    predictWindNot0=dataWindNot0
    
    #예측값을 풍속이 0인 데이터에 대입
    predictWind0['windspeed']=preValue
    #풍속이 0이 아닌 데이터와 풍속이 0인 데이터 병합하여 data에 대입
    data=predictWindNot0.append(predictWind0)
    return data
print(predict_windspeed(train))

 

 

 

데이터를 보정할 출력값들을 확인해보았으니, 실제로 0인 풍속값들을 예측치들로 대체해보자.

함수 코드는 위에서 소개한 predict_windspeed와 동일하지만 맨 아래의 행이름 reset 부분만 재구성하여 추가해주었다. 

 

def predict_windspeed2(data): #(실제데이터 보정용)
    #풍속 예측에 사용되는 변수 
    wCol=['season','weather','humidity','temp','atemp']
    
    #풍속을 0인 것과 0이 아닌 것으로 구분
    dataWind0=data.loc[data['windspeed']==0]
    dataWindNot0=data.loc[data['windspeed']!=0]
    
    #랜덤포레스트 분류기 생성
    rfModel=RandomForestClassifier()
    dataWindNot0['windspeed']=dataWindNot0['windspeed'].astype("str")
    
    # wCol > 풍속학습 > 모델완성 
    rfModel.fit(dataWindNot0[wCol], dataWindNot0['windspeed']) #(학습대상, 학습자료)
    
    #학습한 모델로 풍속 0에 대한 데이터 예측 
    preValue=rfModel.predict(X=dataWind0[wCol])
    print(preValue)
    
    #풍속이 0인 것과 0이 아닌 것으로 분류 
    predictWind0=dataWind0
    predictWindNot0=dataWindNot0
    
    #예측값을 풍속이 0인 데이터에 대입
    predictWind0['windspeed']=preValue
    #풍속이 0이 아닌 데이터와 풍속이 0인 데이터 병합하여 data에 대입
    data=predictWindNot0.append(predictWind0)

    # 행이름 reset(data만 따로 가져오기 위함)
    data.reset_index(inplace=True)
    data.drop('index',inplace=True, axis=1)
    return data

 

train=predict_windspeed2(train)
test=predict_windspeed2(test)

 

 

대체후 풍속값들의 분포를 시각화하여 확인해보면 train데이터 와 test데이터 모두 0 이 사라진 것을 확인할 수 있다. 

fig, ax1 = plt. subplots()
plt.sca(ax1)
plt.xticks(rotation=30)

sns.countplot(data=train, x="windspeed", ax=ax1)

 

 

fig, ax1 = plt. subplots()
plt.sca(ax1)
plt.xticks(rotation=30)

sns.countplot(data=test, x="windspeed", ax=ax1)

 

 

 

 

자전거 대여수 예상 값 출력

 

 

먼저 종속변수들을 분석해야한다. 

연속형변수는 dtype이 float64(실수형) 인 변수들을 의미한다.

 

#feature selection 
# 연속형(temp, humi, wind, atemp), 범주형 변수는 타입을 category로 변경 

train.columns

 

 

train.info()

 

 

범주형변수는 int64로 나타나있는 경우가 많은데, 이를 category로 변경해주어야 한다. 

 

feature_names=['season','holiday','workingday','weather','temp','atemp',
              'humidity','windspeed','year','hour','dayofweek']

#수리형을 범주형으로 바꿔주는 for 문 
c_f_n =['season','holiday','workingday','weather',
        'year','hour','dayofweek','month']

for v in c_f_n:
    #dtype이 int64라서 category로 변경
    train[v]=train[v].astype("category")
    test[v]=train[v].astype("category")
train.info() # dtype 변경 확인 

 

 

# 4개의 변수로 되어있음 확인 
train['season'].dtypes 
test['season'].dtypes

 

 

 

 

트레이닝 및 테스트 데이터 대입 

 

xtrain=train[feature_names]
xtrain.shape #(10886, 11)
xtest=test[feature_names]
xtest.shape #(6493, 11)

 

 

ytrain=train['count'] #레이블(정답)
ytrain.shape

 

 

RMSLE 실제치와 예측치의 차이 함수 구현

 

모델의 성능을 평가하는 함수이다. 

 

 

def rmsle(predicted_value, actual_value):
    predicted_value=np.array(predicted_value)
    actual_value=np.array(actual_value)
    log_predict=np.log(predicted_value+1)
    log_actual=np.log(actual_value+1)
    diff=log_predict-log_actual
    diff=np.square(diff)
    mean_diff=diff.mean()
    score=np.sqrt(mean_diff)
    return score

모델 성능을 평가해본다. 

from sklearn.metrics import make_scorer
rmsle_scorer=make_scorer(rmsle)
rmsle_scorer

 

 

# k-fold corss_validation # 핸즈온 머신러닝 p.127 
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
from pandas import DataFrame

kfold=KFold(n_splits=10, shuffle=True, random_state=42)

from sklearn.ensemble import RandomForestRegressor

model =RandomForestRegressor(n_estimators=100, n_jobs=-1,random_state=42)

%time score=cross_val_score(model, xtrain, ytrain,cv=kfold, scoring=rmsle_scorer )
print(score.mean())

 

 

 

0.33정도 나왔다. 성능이 안좋다 ;_; 

 

model.fit(xtrain,ytrain)

 

 

 

x 트레이닝변수에 정답값인 y변수를 model에 학습시킨다. 

prediction=model.predict(xtest)
prediction

 

 

test데이터에 예측 값을 출력하였다.

 

kaggle 제출 

 

submission=pd.read_csv("sampleSubmission.csv")
submission['count']=prediction
submission.to_csv("result.csv",index=False)

 

상단의 Bike Sharing Demand Competition에 검색하여 들어간다. 

Competition이 종료되어 Late Submission을 클릭하고, 준비된 csv 파일을 업로드하면

점수를 바로 확인할 수 있고 순위를 비교해볼 수 있다. 

 

 

또이이잉 1.8139점이다. 점수가 낮으면 낮을 수록 좋다. 3169.5등 정도 하였다. ㅎ0ㅎ 

아마 다른 데이터 전처리 작업도 해야되는 것 같다. &_&  3천등에서 계속 업데이트 해나가야지!