수달이네 기술 블로그

14. 서울 따릉이 대여 수요 예측 본문

AI공부/머신러닝

14. 서울 따릉이 대여 수요 예측

슬픈 수달이 2026. 1. 6. 23:02

https://www.kaggle.com/datasets/joebeachcapital/seoul-bike-sharing/data

이번엔 한국의 서울시 따릉이 자전거 대여 수요 예측을 해보려 한다.

데이터셋 가져오기

bike_df = pd.read_csv('house\\data\\SeoulBikeData.csv', encoding='CP949')
bike_df.head()

 

데이터셋은 아래와 같다.

Date Rented Bike Count Hour Temperature(캜) Humidity(%) Wind speed (m/s) Visibility (10m) Dew point temperature(캜) Solar Radiation (MJ/m2) Rainfall(mm) Snowfall (cm) Seasons Holiday Functioning Day
01/12/2017 254 0 -5.2 37 2.2 2000 -17.6 0.0 0.0 0.0 Winter No Holiday Yes
01/12/2017 204 1 -5.5 38 0.8 2000 -17.6 0.0 0.0 0.0 Winter No Holiday Yes
01/12/2017 173 2 -6.0 39 1.0 2000 -17.7 0.0 0.0 0.0 Winter No Holiday Yes
01/12/2017 107 3 -6.2 40 0.9 2000 -17.6 0.0 0.0 0.0 Winter No Holiday Yes
01/12/2017 78 4 -6.0 36 2.3 2000 -18.6 0.0 0.0 0.0 Winter No Holiday Yes
  • Date : 연월일
  • Rented Bike count - 매 시간마다 대여한 자전거 수
  • Hour - 하루 중 시간
  • Temperature - 온도
  • Humidity - 습도 %
  • Windspeed - 풍속 m/s
  • Visibility - 가시거리 m
  • Dew point temperature - 이슬점 온도
  • Solar radiation - 태양 복사 MJ/m2
  • Rainfall - 강우량 mm
  • Snowfall - 적설량 cm
  • Seasons - 겨울, 봄, 여름, 가을
  • Holiday - 휴일/휴일 없음
  • Functional Day - 운영되지 않았던 , 정상적으로 운영된 
bike_df.info()

# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 8760 entries, 0 to 8759
# Data columns (total 14 columns):
#  #   Column                    Non-Null Count  Dtype  
# ---  ------                    --------------  -----  
#  0   Date                      8760 non-null   object 
#  1   Rented Bike Count         8760 non-null   int64  
#  2   Hour                      8760 non-null   int64  
#  3   Temperature(캜)            8760 non-null   float64
#  4   Humidity(%)               8760 non-null   int64  
#  5   Wind speed (m/s)          8760 non-null   float64
#  6   Visibility (10m)          8760 non-null   int64  
#  7   Dew point temperature(캜)  8760 non-null   float64
#  8   Solar Radiation (MJ/m2)   8760 non-null   float64
#  9   Rainfall(mm)              8760 non-null   float64
#  10  Snowfall (cm)             8760 non-null   float64
#  11  Seasons                   8760 non-null   object 
#  12  Holiday                   8760 non-null   object 
#  13  Functioning Day           8760 non-null   object 
# dtypes: float64(6), int64(4), object(4)
# memory usage: 958.2+ KB

또한 위와 같은 info로 구성되어있다. NULL값은 당장 보이진 않는다.

Date컬럼

위에서 Date컬럼의 경우 2017-12-01 이런 형식으로 되어있다.

Date의 경우 시간이 지날수록 자전거 빌리는 수를 확인하기엔 좋겠지만, 주기성을 확인하기엔 힘들다고 판단, 새로운의미있는 값으로 해당 값을 분리한다.

bike_df['year'] = bike_df['Date'].dt.year
bike_df['month'] = bike_df['Date'].dt.month
bike_df['day'] = bike_df['Date'].dt.day
bike_df.head()

Date컬럼을 세가지 컬럼으로 나누어 주었다.

컬럼 확인(EDA)

산점도 확인(scatterplot)

sns.scatterplot(x='Temperature', y='Rented Bike Count', data=bike_df, alpha=0.3)

날씨와 빌린 자전거 대수간의 산점도를 확인할 수 있다.

온도

  • 위 결과를 보면 날씨가 추울 때는 확연히 빌린 대수가 적고, 날씨가 20~30도 사이에 가장 많은 양을 빌리는 것을 알 수 있다.

풍속

  • 풍속도 확인해보면 너무 강하지 않으면 고루 퍼져있는 것을 알 수 있다.

가시거리(m)

  • 다양한 요인으로 결정되는 가시거리를 확인하면 마찬가지로 시야가 좁아질 경우엔 많이 타지 않는다(안개 등)

시간대 별로 확인해보면

  • 8시에 순간 높아진것을 보면 출퇴근시간에 많이 빌리며,
  • 늦은 밤이되고 이른 아침이 될수록 떨어진다.

해당 시간을 간단하게 쪼개는 컬럼으로 만들어볼 수도 있다.

bike_df['TimeOfDay'] = pd.cut(bike_df['Hour'], 
                            bins=[0, 5, 11, 17, 23], 
                            labels=['Dawn', 'Morning', 'Afternoon', 'Evening'],
                            include_lowest=True)
  • 위처럼, TimeOfDay라는 컬럼을 추가하여, 새벽, 아침, 오후, 저녁의 4가지 라벨로 나눠줄 수 있다.

날짜별로 확인하면.

plt.figure(figsize=(14, 4))
sns.lineplot(x='Date', y='Rented Bike Count', data=bike_df)
plt.xticks(rotation=45)
plt.show()

  • 달이 지나 높아지긴 했으나, 이게 계절에 의한 영향인지 확인할 수 없다. 또한 연도별로도 한눈에 판단하기 힘들다.
  • 따라서 해당 컬럼은 drop해준다.
bike_df = bike_df.drop('Date', axis=1)
bike_df.head()

functioning day라는 컬럼은 신뢰성을 확인할 수 있는 컬럼으로 자전거가 운영되지 않은 날을 표기해준다.

sns.barplot(x='Functioning Day', y='Rented Bike Count', data=bike_df)

seaborn의 바차트를 확인하면 평균이 신뢰구간과 함께 표시된다.(groupby)

  • 여기서 y축은 RentedBikeCount의 평균을 표시한다.
  • 여기서 No의 경우 어차피 운영하지 않으니 평균이 0으로 표시된거고, Yes를 보면 평균이 700대 이고, 신뢰구간도 확인할 수 있다.

숫자 컬럼이 아닌 값은 원핫 인코딩 해주어야 한다.

숫자 컬럼이 아닌 값을 확인

bike_df.select_dtypes(exclude=['number']).columns.tolist()
# ['Seasons', 'Holiday', 'Functioning Day', 'TimeOfDay']

위 네개의 컬럼인데.

for i in bike_df.select_dtypes(exclude=['number']).columns.tolist():
    print(i, bike_df[i].nunique())

# Seasons 4
# Holiday 2
# Functioning Day 2
# TimeOfDay 4

위 네개의 컬럼의 unique즉, 값 종류의 개수를 출력하면 위와 같이 되어, 원핫 인코딩으로 해도 상관없음을 확인했다.(값이 적으므로 컬럼 개수가 크게 증가하지 않음)

bike_df = pd.get_dummies(bike_df, columns=bike_df.select_dtypes(exclude=['number']).columns.tolist(), drop_first=True)
  • 위는 원핫인코딩을 진행해준 값이다(pasdas의 get_dummies함수)

이후엔 컬럼간의 상관관계를 분석해줄것이다.