탐색적 데이터 분석 - 기상정보를 활용한 공공자전거 수요 분석

LSH·2023년 6월 1일
1

교육 정보

  • 교육 명: 경기미래기술학교 AI 교육
  • 교육 기간: 2023.05.08 ~ 2023.10.31
  • 오늘의 커리큘럼: 빅데이터 기초 활용 역량 강화 (5/10~6/9) - 데이터 분석
  • 강사: 조미정 강사님 (빅데이터, 머신러닝, 인공지능)
  • 강의 계획:
    1. 파이썬 언어 기초 프로그래밍
    2. 크롤링 - 데이터 분석을 위한 데이터 수집(파이썬으로 진행)
    3. 탐색적 데이터 분석, 분석 실습
    - 분석은 파이썬만으로는 할 수 없으므로 분석 라이브러리/시각화 라이브러리를 통해 분석
    4. 통계기반 데이터 분석
    5. 미니프로젝트

탐색적 데이터 분석 - 기상정보를 활용한 공공자전거 수요 분석

프로젝트 개요

1. 문제 정의 및 데이터

  • 문제 현상
    자전거대여소는 코로나 여파로 문제를 겪고 있다. 대여 건수도 많이 줄었지만 계절이나 날씨에 따라 대여건수의 변동성이 너무 커서 운영 비용 문제에서도 큰 영향을 끼치고 있다. 따라서 날씨예보정보를 토대로 대여건수를 사전에 예측하고, 운영 비용을 조정하려고 한다.
    -> 우리가 뭘 할때는 결국 비용을 조정하는것임
  • 데이터
    서울특별시 공공자전거 이용정보(시간대별)
    http://data.seoul.go.kr/dataList/OA-15245/F/1/datasetView.do
    대여일시, 대여시간, 대여소번호, 대여소명, 정기권유무, 성별, 연령대, 탄소량, 이동거리, 이동시간
    사용시간, 이용건수는 대여일시, 대여소번호, 연령대별 건수
    2020년6월-12월, 2021년6~12월 데이터 사용
    기상청 자료(기상자료개방포털)
    https://data.kma.go.kr/cmmn/main.do
    일자별시간별 기상자료(2020년,2021년)(기온, 강수, 바람, 기압, 습도, 일사, 일조, 눈, 구름, 시정, 지면상태, 지면 · 초상온도, 일기현상, 증발량, 현상번호)

2. 프로젝트 목표 정의

문제정의: 자전거 수요 및 혼잡도 예측이 어려워 적절한 응대가 되지않고 효과적인 운영이 어려움
가설검정: 자전거 수요 예측으로 운영인력 및 인건비 절감 효과, 효과적인 대여소 운영
해결방안: 자전거 대여 수요 분석 - 기준별 자전거 수요량 확인 (EDA)
효과검증: 모델 활용 전/후 운영비용 및 고객만족도 점수

3. 프로젝트 절차

데이터 로드 - 시간대별 공공자전거 데이터, 기상정보데이터를 불러오고 구조 확인
데이터 전처리 - 결측치, 이상치, 컬럼명 변경
데이터 시각화
분석정리

참고 - 데이터 전처리

  • 데이터 정제 -이상값삭제, 잡음 완화, 결측(빈값) 추가 등 -> 정확성 높임
    • 결측값
      : 삭제, 대체 등
      작성자의 주관이 가장 많이 들어가는 편
      고려 사항이 많음 - 가장 있을법한 데이터로 대체해야 함
      혹은 머신러닝으로 예측값을 넣을 수 있음
    • 이상값
      : 동떨어진 값 (입력오류도 있음)
      • 이상값 탐지
        • 시각적으로 확인
        • 통계적 기법으로 확인
        • IQR 기준 (박스플롯)
      • 이상값 처리
        • 이상값 제외
        • 가장 가까운 최대/최소값으로 대체
        • 데이터 자체가 편향적일 경우 log 변환등을 통해 편향치를 조정하는 경우도 있음
        • binning (구간으로 묶기-이상치가 제거되는 효과)
    • 잡음
  • 통합 - 여러 자료 통합
  • 축소 - 필요한 정보만 가져와서 데이터 축소
  • 변환 - 필요시 데이터 타입 등을 변환

구현

0. 한글 사용 설정

#글꼴 설치
!sudo apt-get install -y fonts-nanum # 나눔폰트 설치
!sudo fc-cache -fv # 폰트가 캐시에 저장되므로 위에서 설치한 폰트가 적용되도록 폰트 캐시 재 조성 
!rm ~/.cache/matplotlib -rf # matplotlib의 폰트 캐시를 삭제 
#이거 한 다음에 런타임을 재시작 해야함 (런타임-다시시작)
# 라이브러러 불러오기
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings(action='ignore')
# font 설정
plt.rc('font', family='NanumBarunGothic') 
# 한글 깨짐 테스트
plt.scatter([0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5])
plt.title('산점도')
plt.xlabel('키')
plt.ylabel('몸무게')
plt.show()
from google.colab import drive
drive.mount('/content/drive')

1. 데이터 로드

데이터

from google.colab import drive
drive.mount('/content/drive')
#데이터를 구글 드라이브에서 넣어서 사용함 - 따릉이데이퍼 폴더
1.1. 공공자전거 데이터 로드
from glob import glob
#제시한 조건에 맞는 파일명을 리싀트로 반환
file_names = glob("/content/drive/MyDrive/Colab Notebooks/경기미래기술학교AI과정/빅데이터 분석을 위한 기획과 탐색/04_탐색적데이터분석/따릉이데이터/*.csv")
#따릉이 폴더의 모든 csv 파일 이름을 리스트에 담기
#for 문으로 concat하기
bike_df = pd.DataFrame()
for file_name in file_names:
  temp = pd.read_csv(file_name, encoding = 'cp949')
  bike_df = pd.concat([bike_df, temp], ignore_index=True)
  print (file_name, temp.shape[0])

info를 확인하고 내가 처리할 방향에 맞는지, 수정이 필요한 데이터가 있는지 확인

bike_df.info()
#
#결과
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 29827682 entries, 0 to 29827681
Data columns (total 12 columns):
 #   Column  Dtype  
---  ------  -----  
 0   대여일자    object 
 1   대여시간    int64  
 2   대여소번호   int64  
 3   대여소명    object 
 4   대여구분코드  object 
 5   성별      object 
 6   연령대코드   object 
 7   이용건수    int64  
 8   운동량     object 
 9   탄소량     object 
 10  이동거리    float64
 11  사용시간    int64  
dtypes: float64(1), int64(4), object(7)
memory usage: 2.7+ GB

하고싶은것

  • 대여 시간 및 대여 일자 별 이용 건수 분석

사용할 데이터 정리
1. key (날씨와 key_on 할데이터): bike_df.대여일자, bike_df.대여시간 = weather_info.일시
2. 이용건수

1.2. 기상 데이터 로드
file_name1 = '/content/drive/MyDrive/Colab Notebooks/경기미래기술학교AI과정/빅데이터 분석을 위한 기획과 탐색/04_탐색적데이터분석/따릉이데이터/기상데이터/weather_2020.csv'
file_name2 = '/content/drive/MyDrive/Colab Notebooks/경기미래기술학교AI과정/빅데이터 분석을 위한 기획과 탐색/04_탐색적데이터분석/따릉이데이터/기상데이터/weather_2021.csv'
weather1 = pd.read_csv(file_name1, encoding = 'cp949')
weather2 = pd.read_csv(file_name2, encoding = 'cp949')
# 데이터 두개 합치기
weather_info = pd.concat([weather1, weather2],ignore_index = True)
# weather_info.sample(10)
weather_info.info()

2. 데이터 전처리

데이터에서 수정할 부분이 있는지 확인하고 처리함

2.1. 공공자전거 데이터 전처리
2.1.1. 결측치 확인
bike_df.isna().sum()
# 사용할데이터에는 결측치 없음
#
#결과
대여일자             0
대여시간             0
대여소번호            0
대여소명             0
대여구분코드           0
성별        13251460
연령대코드            0
이용건수             0
운동량              0
탄소량              0
이동거리             0
사용시간             0
dtype: int64

사용할데이터에는 결측치 없음

2.1.2. 집계 데이터 생성

날짜 정보와 merge에 사용할 대여일자, 대여시간으로 필요한 데이터(여기서는 이용건수)를 집계

bike_df2 = bike_df.groupby(['대여일자', '대여시간'])['이용건수'].sum()
bike_df2 = bike_df2.reset_index() #인덱스 재 정렬 , 기존 인덱스를 열로
bike_df2
#
#결과
대여일자	대여시간	이용건수
0	2020-06-01	0	790
1	2020-06-01	1	915
2	2020-06-01	2	688
3	2020-06-01	3	506
4	2020-06-01	4	352
...	...	...	...
10248	2021-12-31	19	1756
10249	2021-12-31	20	1365
10250	2021-12-31	21	1455
10251	2021-12-31	22	876
10252	2021-12-31	23	599
10253 rows × 3 columns
2.1.3. 파생변수 생성

대여일자에서 년도, 월, 일, 요일, 공휴일 변수 생성

bike_df2['대여일자'] = pd.to_datetime(bike_df2['대여일자'])
#2. datatime을 가지고 세부적인 정보를 빼냄
bike_df2['년도'] = bike_df2['대여일자'].dt.year
bike_df2['월'] = bike_df2['대여일자'].dt.month
bike_df2['일'] = bike_df2['대여일자'].dt.day
bike_df2['요일(num)'] = bike_df2['대여일자'].dt.dayofweek
bike_df2['공휴일'] = 0 #0: 평일 1: 공휴일
# 이번에는 토요일 일요일만 공휴일로 변경하고자 함
bike_df2.loc[bike_df2['요일(num)'].isin([5,6]),['공휴일']] = 1
bike_df2.sample(10)

2.2. 기상 데이터 전처리
2.2.1. 날짜,시간 컬럼 생성
#날짜 및 시간 컬럼 생성
weather_info['날짜'] = weather_info['일시'].str[:10]
weather_info['시간'] = weather_info['일시'].str[11:13].astype(int)
2.2.2. 컬럼선택
# 컬럼 선택 사용할 컬럼을 순서대로 가져와서 새 데이터 프레임 생성(기존 데이터프레임에서 drop할수도 있음)
weather_df = weather_info[['날짜', '시간', '기온(°C)', '강수량(mm)', '풍속(m/s)', '풍향(16방위)', '습도(%)','일조(hr)',
       '일사(MJ/m2)', '적설(cm)','전운량(10분위)',  '지면온도(°C)']]
#칼럼명 변경이 필요하다면
weather_df.columns = ['날짜', '시간', '기온', '강수량(mm)', '풍속(m/s)', '풍향(16방위)', '습도(%)','일조',
       '일사', '적설(cm)','전운량',  '지면온도']
# []안을 순서대로 다 바꿀수 있음
# 혹은 dict 형태로 넣어서 바꾸고싶은것만 바꿀수 있음 
2.2.3. 결측치 확인
weather_df.isnull().sum()
#
#결과
날짜              0
시간              0
기온              1
강수량(mm)     15534
풍속(m/s)         2
풍향(16방위)        2
습도(%)           0
일조           7951
일사           7951
적설(cm)      16957
전운량            20
지면온도           13
dtype: int64

강수량, 적설, 일조, 일사같이 NaN값이 0인 경우는 0으로 fill
전운량, 기온, 지면온도, 풍향, 풍속 는 같은 일자의 이전시간대의 데이터로 대체
📕 이런 기본적인 방법들은 이미 만들어져 있으므로 메소드 찾아서 적용!

  • NaN 값을 0으로 fill (fillna)
# 1. 강수량과 적설이 0인 경우는 0으로 fill
weather_df['강수량(mm)'].fillna(0, inplace = True)
weather_df['적설(cm)'].fillna(0, inplace = True)
weather_df['일조'].fillna(0, inplace = True)
weather_df['일사'].fillna(0, inplace = True)
  • NaN 값을 직전 데이터의 값으로 fill (ffill)
# 날짜 시간으로 정렬 (되어있더라도 확실하게 하기위해 한번 더 정렬)
weather_df = weather_df.sort_values(['날짜','시간'])
# 전 값으로 채우기 같은 메소드는 이미 만들어져 있음!! 찾아보기 
weather_df['기온'].fillna(method='ffill',inplace = True)
weather_df['풍속(m/s)'].fillna(method='ffill',inplace = True)
weather_df['풍향(16방위)'].fillna(method='ffill',inplace = True)
weather_df['전운량'].fillna(method='ffill',inplace = True)
weather_df['지면온도'].fillna(method='ffill',inplace = True)
2.2.4. 데이터 결합
weather_df['날짜'] = pd.to_datetime(weather_df['날짜'])
#데이터 타입 맞추기 
bike_mg = pd.merge (bike_df2, 
                       weather_df, 
                       left_on =['대여일자', '대여시간'], 
                       right_on = ['날짜', '시간']) #default = inner 
bike_mg.head()


중복 데이터 drop

bike_mg = bike_mg.drop(['대여일자', '날짜', '시간'], axis = 1)


📕 데이터는 처리가 끝난경우 가벼워지므로 추후 필요할 경우를 대비하여 파일로 저장해놓으면 좋음

#저장
bike_mg.to_csv('bike_mg.csv', index = False, encoding='utf-8-sig')
#불러오기
bike_mg = pd.read_csv('bike_mg.csv')
2.2.5. 데이터 시각화

원본 데이터프레임을 보존하고 복사본을 생성해서 시각화 할 예정

data = bike_mg.copy()

데이터의 기술 통계를 보고 정상적인 값인지 확인 (강수량이 마이너스 값이 없는지 등)

desc_df = data.describe().T
desc_df

  • 이용건수의 분포
sns.histplot(data['이용건수'])

sns.boxplot(data['이용건수'])

이렇게 아웃라이너가 많으면 머신러닝등에서는 일반추세 예측에 방해가 될 수 있어 제거할 수도 있지만 본 포스트에서는 분석 목적이라서 따로 제거하지 않음

sns.lineplot(x=data['년도'].map(str) + data['월'].map(str), y=data['이용건수'])

참고 - 정규분포형태로 만들기 위해서(이상치 처리) log, sqrt(루트) 취할 수 있음

import numpy as np
sns.histplot(np.log1p(data['이용건수']))
# 이 그래프에서는 크게 편향이 좋아지지 않으므로 사용 하지 않을것 
#편향치 확인
data['이용건수'].skew()

  • 위 그래프와 같이 분포 형태가 달라짐
  • 피처의 분포
    • 필요한 컬럼 선택
con_cols = []
#원하는 컬럼을 지정할 수 있는 조건이 있다면 컬럼을 고르기위해 조건문을 사용할 수 있음 
for col in data.columns:
  if (data[col].dtype == 'float64') or (col == '습도(%)'):
    con_cols.append(col)
print(con_cols)
#
# 결과
['기온', '강수량(mm)', '풍속(m/s)', '풍향(16방위)', '습도(%)', '일조', '일사', '적설(cm)', '전운량', '지면온도']
  • 시각화
fig, axes = plt.subplots(2,5, figsize = (20,8))
ax = axes.flatten()
# axes = (n,n)형태 / ax = m형태
for i, col in enumerate(con_cols):
  sns.histplot(data = data, x = col, ax = ax[i])

  • 이용건수와 피처와의 관계
# 다양한 그래프 subplot으로 나타내기
# 연속성 데이터는 line, 범주형 데이터는 bar그래프로 그려보기 
# figure 크기 설정
fig, axes = plt.subplots(2,3, figsize = (20,8))
# 그래프 그리기
sns.barplot(data = data, x = '년도', y= '이용건수', ax = axes[0,0])
sns.barplot(data = data, x = '월', y= '이용건수', ax = axes[0,1])
sns.barplot(data = data, x = '공휴일', y= '이용건수', ax = axes[0,2])
sns.lineplot(data = data, x = '기온', y= '이용건수', ax = axes[1,0])
sns.barplot(data = data, x = '대여시간', y= '이용건수', ax = axes[1,1])
sns.lineplot(data = data, x = '강수량(mm)', y= '이용건수', ax = axes[1,2])
#제목 넣기 
axes[0,0].set_title('년도별 이용건수')
axes[0,1].set_title('월별 이용건수')
axes[0,2].set_title('공휴일여부에 따른 이용건수')
axes[1,0].set_title('기온별 이용건수')
axes[1,1].set_title('대여시간별 이용건수')
axes[1,2].set_title('강수량(mm)별 이용건수')
# 간격두기
fig.subplots_adjust(hspace = 0.4)

분석
2021 이용건수가 2020에 비해 증가
9월이용건수가 최대, 12월이 최소
공휴일 이용건수보다 평일 이용건수가 더 많음 (출퇴근?)
기온에 따라서는 15도정도, 30도 정도에서 한번씩 피크가 옴 , 왜 20도에서는 이용건수가 대여시간은 8시, 18시에 많은것을 봐서는 출퇴근때 가장 많이 이용함을 알 수 있음 -> 공휴일과 연관지어서 생각 가능
비는 안올때가 많은데 30, 60 일때는 뭔가 확인하고싶음
공휴일에 이용시간도 궁금

  • 평일과 공휴일 이용건수 차이
plt.figure(figsize = (15,3))
sns.pointplot(x='대여시간', y='이용건수',data = data, hue = '공휴일')
# 평일과 공휴일에는 완전히 다른 모습을 보임! 오후 4시를 피크로 완만한 그래프 

  • 요일에 따른 이용건수 차이
plt.figure(figsize = (15,3))
sns.pointplot(x='대여시간', y='이용건수',data = data, hue = '요일(num)')
# 요일로 보면 토요일에 이용건수가 더 많고 토요일이 좀더 오후에 전반적으로 이용이 높음  

  • 요일별 이용건수 (box, 요일을 한글로)
sns.boxplot(x='요일(num)', y='이용건수',data = data)
dofw = list('월화수목금토일')
plt.xticks([0,1,2,3,4,5,6],dofw)
plt.show()
#휴일은 상대적으로 변동성이 적음. 평일은 변동성이 큰편->아마 업무시간 변화가 아닐까

  • 위에서 고른 feature와 이용건수의 상관관계를 regplot을 통해 확인하기
fig, axes = plt.subplots(4,3, figsize = (20,20))
ax = axes.flatten()
for i, col in enumerate(con_cols):
  #print(i, col)
  sns.regplot(data = data, x = col, y='이용건수', ax = ax[i], line_kws={'color': 'red'})
  ax[i].set_ylim(0, 30000)

  • 상관관계
plt.figure(figsize=(10,8))
sns.heatmap(data[con_cols].corr(), annot=True, cmap = 'coolwarm')

  • 분석 정리
    • 2021 이용건수가 2020에 비해 증가
    • 9월 이용건수가 최대, 12월이 최소
    • 공휴일 이용건수보다 평일 이용건수가 더 많음. 대여시간은 8시, 18시에 많은것을 봐서는 출퇴근때 가장 많이 이용함을 알 수 있음
    • 출근을 하지 않는 공휴일에는 평일과 완전히 다른 양상을 가져 오후 4시를 피크로 완만한 그래프
    • 요일로 보면 토요일에 이용건수가 더 많고 토요일이 좀더 오후에 전반적으로 이용이 높음 #휴일은 상대적으로 변동성이 적음. 평일은 변동성이 큰 편(아마 업무시간 변화로 추정중)
    • 기온이 15, 30도씨 일때 이용이 가장 많으나 그 중간치에서는 또 많지 않음 → 선형적인 혹은 하나의 값에 의존하는 상관관계를 가지지 않음
    • 우천시에는 사용량이 확실히 적으나 종종 이상치가 존재함
    • 강한 상관관계를 보이는 경우는 없음
profile
:D

0개의 댓글