데이터 분석/캐글(Kaggle)

[Kaggle] Bike Sharing Demand (1)

Jerry Jun 2020. 8. 31. 19:03
728x90

pyoji

 

Bike Sharing Demand

Forecast use of a city bikeshare system

www.kaggle.com

Kaggle 를 통해 처음 공부해볼 것은 "Bike Sharing Demand" 이다.

train.csv 데이터를 이용해 학습하고 test.csv 를 활용하여 count 를 예측하는데에 목적을 둔다..

 


Data Fields

  • datetime : 날짜
  • season : 1 = spring | 2 = summer | 3 = fall | 4 = winter
  • holiday : 휴일인지 여부
  • workingday : 근무일인지 여부
  • weather : 1, 2, 3, 4 종류별로 다름
  • temp : 온도
  • atemp : 체감온도
  • humidity : 습기
  • windspeed : 풍속
  • casual : 등록되지 않은 사용자 기록
  • registered : 등록된 사용자 기록
  • count : 대여 횟수

test.csv 파일에는 세 가지 항목(casual, registered, count)이 없다.


import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats

# 노트북 안에 그래프를 그리기 위해
%matplotlib inline

# 그래프에서 마이너스 폰트 깨지는 문제에 대한 대처
plt.style.use('ggplot')

# 그래프에서 마이너스 폰트 깨지는 문제에 대한 대처
mpl.rcParams['axes.unicode_minus'] = False

# seaborn 그래프 한글 깨짐 방지
plt.rcParams['font.family'] = 'NanumGothic'​

코드 진행에 필요한 라이브러리를 모두 선언한다.

 


train = pd.read_csv("/SBA/bike/train.csv", parse_dates = ["datetime"])
train.shape
------------------------------------------
(10886, 12)

pandas 를 통해 kaggle에서 주어진 train.csv 파일을 불러들인다.

 

* parse_dates : 파일(데이터) 안에 날짜/시간이 있을 때 데이터를 파싱하는 것이다. 

 

 


Null 개수를 파악하는 3가지 방법(물론 더 있을 것이다).

1. info()

train.info()
------------------------------------
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10886 entries, 0 to 10885
Data columns (total 12 columns):
 #   Column      Non-Null Count  Dtype         
---  ------      --------------  -----         
 0   datetime    10886 non-null  datetime64[ns]
 1   season      10886 non-null  int64         
 2   holiday     10886 non-null  int64         
 3   workingday  10886 non-null  int64         
 4   weather     10886 non-null  int64         
 5   temp        10886 non-null  float64       
 6   atemp       10886 non-null  float64       
 7   humidity    10886 non-null  int64         
 8   windspeed   10886 non-null  float64       
 9   casual      10886 non-null  int64         
 10  registered  10886 non-null  int64         
 11  count       10886 non-null  int64         
dtypes: datetime64[ns](1), float64(3), int64(8)
memory usage: 1020.7 KB​

 

 

 

2. isnull().sum()

train.isnull().sum()
------------------------------------
datetime      0
season        0
holiday       0
workingday    0
weather       0
temp          0
atemp         0
humidity      0
windspeed     0
casual        0
registered    0
count         0
dtype: int64

sum() 을 붙이는 이유는 보기 쉬운 숫자로 나오기 때문이다.

 

 

 

3.  import missingno

import missingno as msno
msno.matrix(train, figsize = (12,5))
--------------------------------------------
<matplotlib.axes._subplots.AxesSubplot at 0x25cb280aa00>

missingno

모두 null 값이 없는 모습이다.


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.shape
---------------------------------------------------
(10886, 18)​

날짜/시간을 세분화하여 조사하기 위해 분리하는 것이다.

column 이 12에서 18로 증가한 모습을 볼 수 있다.

 

 

 

 

# barplot

figure, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(nrows = 2, ncols = 3)
figure.set_size_inches(18, 8)

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)

ax1.set(ylabel = 'Count', title = '연도별 대여량')
ax2.set(ylabel = 'Month', title = '월별 대여량')
ax3.set(ylabel = 'Day', title = '일별 대여량')
ax4.set(ylabel = 'Hour', title = '시간별 대여량')
ax5.set(ylabel = 'Minute', title = '분별 대여량')
ax6.set(ylabel = 'Second', title = '초별 대여량')
--------------------------------------------------------------------------

barplot

 

- 연도별 대여량 : 2011년 < 2012년

- 월별 대여량 : 6월 > 7~9월 > 10월 > 11월 > 12월 > ...  // 겨울에 대여량이 적어진다.

- 일별 대여량 : 1~19일 데이터만 있으므로 사용불가. (test.csv에 나머지 날짜가 있다.)

- 시간별 대여량 : 출퇴근 시간에 급격히 상승하는 모습을 볼 수 있다. 

- 분, 초별 대여량 : 의미없음.

 

 

 

# boxplot

fig, axes = plt.subplots(nrows = 2, ncols = 2)
fig.set_size_inches(12, 10)

sns.boxplot(data = train, y = "count", orient = 'v', ax = axes[0][0])
sns.boxplot(data = train, y = "count", x = "season", orient = 'v', ax = axes[0][1])
sns.boxplot(data = train, y = "count", x = "hour", orient = 'v', ax = axes[1][0])
sns.boxplot(data = train, y = "count", x= "workingday", orient = 'v', ax = axes[1][1])

axes[0][0].set(ylabel = "Count", title = "대여량")
axes[0][1].set(ylabel = "Season", title = "계절별 대여량")
axes[1][0].set(ylabel = "Hour Of the day", title = "시간별 대여량")
axes[1][1].set(ylabel = "Working Day", title = "근무일 여부에 따른 대여량")

boxplot

- 계절별 대여량 : 가을에 가장 많은 것을 볼 수 있고 겨울에 가장 봄에 가장 적은 것을 볼 수 있다.

- 시간별 대여량 : barplot과 마찬가지로 출퇴근 시간에 높은 것을 볼 수 있다.

- 주중 / 주말별 대여량 : 근무날일 때는 출퇴근시간의 큰 격차가 보여지고 있다.

 

 

 

# pointplot

train["dayofweek"] = train["datetime"].dt.dayofweek
train.shape
--------------------------------------------------
(10886, 19)​
# 시간대별 대여량
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)
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)

pointplot

 

2번째 그래프 : 근무일에는 출퇴근 시간에 대여량이 많지만 주말에는 완만한 곡선이 그려지는 것을 알 수 있다.

3번째 그래프 : 요일별로 봤을 때도 토,일요일을 제외하고는 평일은 완만하다.

4번째 그래프 : 날씨로 봤을 때 날씨가 안좋을수록 대여량이 낮아지는 것을 볼 수 있다.

5번째 그래프 : 계절별 그래프로 봤을 때 가을 > 여름 > 겨울 > 봄 순으로 대여량이 적어지는 것을 볼 수 있다.

 

 

# heatmap

corrMatt = train[["temp", "atemp", "casual", "registered", "humidity", "windspeed", "count"]]
corrMatt = corrMatt.corr()
print(corrMatt)

mask = np.array(corrMatt)
mask[np.tril_indices_from(mask)] = False
--------------------------------------------------------------------------
                temp     atemp    casual  registered  humidity  windspeed  \
temp        1.000000  0.984948  0.467097    0.318571 -0.064949  -0.017852   
atemp       0.984948  1.000000  0.462067    0.314635 -0.043536  -0.057473   
casual      0.467097  0.462067  1.000000    0.497250 -0.348187   0.092276   
registered  0.318571  0.314635  0.497250    1.000000 -0.265458   0.091052   
humidity   -0.064949 -0.043536 -0.348187   -0.265458  1.000000  -0.318607   
windspeed  -0.017852 -0.057473  0.092276    0.091052 -0.318607   1.000000   
count       0.394454  0.389784  0.690414    0.970948 -0.317371   0.101369   

               count  
temp        0.394454  
atemp       0.389784  
casual      0.690414  
registered  0.970948  
humidity   -0.317371  
windspeed   0.101369  
count       1.000000  
fig, ax = plt.subplots()
fig.set_size_inches(20,10)
sns.heatmap(corrMatt, mask = mask, vmax = .8, square = True, annot = True)​

heatmap

  • 온도, 습도, 풍속은 거의 연관관계가 없다.
  • 대여량과 가장 연관이 높은 건 registered 이지만 test 데이터에는 이 값이 없다.

 

 

# regplot

fig, (ax1, ax2, ax3) = plt.subplots(ncols = 3)
fig.set_size_inches(12, 5)
sns.regplot(x = "temp", y = "count", color = 'red', data = train, ax = ax1)
sns.regplot(x = "windspeed", y = "count", color = 'blue', data = train, ax = ax2)
sns.regplot(x = "humidity", y = "count", color = 'green', data = train, ax = ax3)

regplot 은 산점도 그래프이다.

산점도 그래프를 사용하면 어느 부분에 데이터들이 뭉쳐있는지 쉽게 알아볼 수 있다.

 

풍속이 없을 때에는 0으로 몰려있는 모습을 볼 수 있다. 0 인 데이터들은 없애는 게 좋은 것 같다.

 


# 년도와 월을 붙여서 보자.
def concatenate_year_month(datetime):
    return "{0}-{1}".format(datetime.year, datetime.month)

train["year_month"] = train["datetime"].apply(concatenate_year_month)

print(train.shape)
train[["datetime", "year_month"]].head()
------------------------------------------------------
(10886, 20)
                datetime	year_month
0	2011-01-01 00:00:00	2011-1
1	2011-01-01 01:00:00	2011-1
2	2011-01-01 02:00:00	2011-1
3	2011-01-01 03:00:00	2011-1
4	2011-01-01 04:00:00	2011-1

* Pandas - apply(func, axis)

잘 알지 못하는 기능은 짚고 넘어가자.

apply 에는 함수가 들어간다. 그 함수에서 반환되는 값이 데이터프레임을 업데이트 시킨다. 

 

이 경우에서는

datetime.year : "2011"

datetime.month : "1"

이 두 데이터를 "2011-1" 로 이어서 반환한 것이다. 그것을 year_month 라는 새로운 컬럼을 만들어 넣었다.

 

fig, (ax1, ax2) = plt.subplots(nrows = 1, ncols = 2)
fig.set_size_inches(18, 4)

sns.barplot(data = train, x = "year", y = "count", ax = ax1)
sns.barplot(data = train, x = "month", y = "count", ax = ax2)

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

sns.barplot(data = train, x = "year_month", y = "count", ax = ax3)

subplot

 

  •  시간이 지날수록 대여량이 증가하는 추세인 것을 알 수 있다.

 


# trainWithoutOutliers : 0에 몰려있는 데이터

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

print(train.shape)
print(trainWithoutOutliers.shape)
-------------------------------------------------------
(10886, 20)
(10739, 20)

 

이제 코드 해석을 해봐야겠다. 이번 코드는 한 눈에 안들어온다.

 

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

 

각각의 count 값에서 count의 평균을 빼고 그 값을 절대값을 씌운다.

그래서 그 값이 count의 표준편차 곱하기 3한 값보다 작거나 같은 값을 trainWithoutOutliers 에 넣는다.

 

이상치를 제거하는 부분이다. 

표준편차에 3을 곱하는 것은 3시그마 법칙을 사용하여 모든 데이터의 99.7% 를 사용하고 아닌 것은 걸러내는 것 같다.

 

 

# distplot, probplot

# count값의 데이터 분포도 파악

figure, axes = plt.subplots(ncols = 2, nrows = 2)
figure.set_size_inches(12, 10)

sns.distplot(train["count"], ax = axes[0][0])
stats.probplot(train["count"], dist = "norm", fit = True, plot = axes[0][1])
sns.distplot(np.log(trainWithoutOutliers["count"]), ax = axes[1][0])
stats.probplot(np.log1p(trainWithoutOutliers["count"]), dist = 'norm', fit = True, plot = axes[1][1])
-------------------------------------------------------------------------------------------
((array([-3.82819677, -3.60401975, -3.48099008, ...,  3.48099008,
          3.60401975,  3.82819677]),
  array([0.69314718, 0.69314718, 0.69314718, ..., 6.5971457 , 6.59850903,
         6.5998705 ])),
 (1.3486990121229776, 4.562423868087808, 0.9581176780909612))

distplot

 

이상치를 제거한 후 봤더니 count변수가 오른쪽에 치우쳐져 있다.

대부분의 기계학습은 종속변수가 normal 이어야 하기에 정규분포를 갖는 것이 바람직하다.

대안으로 outlier data를 제거하고 "count"변수에 로그를 씌워 변경해 봐도 정규분포를 따르지는 않지만

이전 그래프보다는 좀 더 자세히 표현하고 있다.

 

 

정규분포를 따르기 위해서는 결측치, 이상치 등등을 잘 해결해야 하는 것 같다. 

이 부분에 대해서 좀 더 공부해봐야겠다.


 

* 공부자료 출처 : Kaggle & Youtube

 

EDA & Ensemble Model (Top 10 Percentile)

Explore and run machine learning code with Kaggle Notebooks | Using data from Bike Sharing Demand

www.kaggle.com

 

Youtube 참고영상

 

300x250