이전 포스팅에 바로 이어서, 이번에는 고객의 과거 데이터를 바탕으로 행동을 예측해봅시다.
여기서 해볼 에측은 회원의 과거 헬스장 이용 이력 데이터를 바탕으로, 다음 달의 이용 횟수가 몇 회가 될지를 예측해볼 것입니다. 이러한 예측을 위해 머신 러닝(Machine Learning, 기계학습)을 수행해볼 것인데, 여기서 사용하는 것은 지도학습 모델링입니다.
지도학습 모델링은 '정답 데이터'가 존재하는 모델링을 의미합니다. 여기서 이야기하는 정답 데이터는 종속변수(y, target)을 의미하며 이러한 정답 데이터가 없으면 비지도학습입니다.
과거 데이터를 학습 데이터(train set)로 하여 모델을 만들고, 검증 데이터(validation set)로 모델의 성능을 평가합니다. 마지막으로 정답을 모르는 테스트 데이터(test data)에 만든 모델을 적용해 '정답 데이터'를 예측해내는 것이 지도학습입니다.
여기서는 과거 6개월 간의 데이터를 바탕으로 아직 오지 않은 다음 달의 이용횟수를 예측해볼 것입니다. 그러기 위해선 과거 6개월의 데이터(train data)로 학습을 시키고, 다음 달의 데이터가 test data가 되겠죠? 다음 달의 '이용횟수'를 예측하는 것이므로 회귀(Regression)의 문제로 풀어나갈 것입니다.
(사실 이 교재에는 다음 달의 데이터셋에 대해 예측하지 않고 간단하게만 예측하고 끝냅니다. 그래서 이 포스팅은 제가 직접 다음 달의 데이터셋을 만들어보았고, 이에 대해 예측하는 과정을 설명합니다.)
✔Table of Contents
Tech 36. 다음 달의 이용횟수 예측을 위한, 데이터 전처리
데이터는 저번 포스팅과 마찬가지로 같은 데이터셋을 사용합니다. 하지만 모델링에 적용하기 위해 데이터를 새로운 데이터로 가공해볼 건데요, 이용이력(ul) 데이터를 불러와 연월 및 customer_id별 이용횟수를 카운트해봅니다. groupby 메서드를 사용하면 되겠죠?
#datetime 형태로 변경
ul['usedate'] = pd.to_datetime(ul['usedate'])
#usedate -> 연월로 변경
ul['연월'] = ul['usedate'].dt.strftime('%Y-%m')
#이제 연월, 고객id 별로 이용횟수를 집계해보자
ul_m = ul.groupby(['연월', 'customer_id'], as_index=False).count()
#새로운 데이터 ul_m
ul_m.head()
#log_id -> cnt 컬럼명 변경
ul_m.rename(columns = {'log_id': 'cnt'}, inplace=True)
#usedate는 필요없으므로 버림
del ul_m['usedate']
ul_m
먼저 데이터 가공을 위해 이용이력(ul) 데이터를 'ul_m' 데이터셋으로 만듭니다. 연월 및 고객 id별 이용횟수를 카운트한 데이터가 만들어졌습니다. 참고로 이 데이터는 다음 '파이썬 데이터 분석 #5' 장에서 또 한번 더 사용할 것이므로 csv 파일로 저장해둡니다.
#ul_m 데이터는 다음 5장에서도 또 이용할 데이터이므로 미리 저장해둔다.
ul_m.to_csv('use_log_months.csv', index=False)
이제부터가 중요한데요, 'ul_m' 데이터를 모델링에 사용할 데이터로 가공합니다.
이를 위해서는 특정 월 한 달에 대응하는 과거 데이터 6개월분의 데이터를 만듭니다. 예를 들어, 2018년 10월 한 달에 대응하는 과거 6개월분의 데이터는 2018년 4월부터 2018년 9월이 되겠죠?
헬스장 데이터셋에서 제공하는 데이터는 2018년 4월부터 2019년 3월까지의 데이터입니다. 그럼 과거 6개월분을 얻을 수 있는 월은 2018년 10월, 11월, 12월, 2019년 1월, 2월, 3월 이렇게 6개 달에 대해 만들 수 있겠죠! 그리고 우리는 데이터가 존재하지 않는, 미래의 2019년 4월의 이용횟수를 예측해볼 것입니다. 우선은 train data를 만드는 데 집중해봅시다.
year_months = list(ul_m['연월'].unique())
pred_data = pd.DataFrame()
year_months
먼저 새롭게 만들어질 train data는 'pred_data'라는 빈 데이터프레임 안에 넣을 것입니다.
ul_m 데이터프레임의 유니크한 '연월' 값을 뽑아보면 위와 같습니다. 2018년 10월부터 2019년 3월까지의 연월에 대해서 과거 6개월분의 데이터를 만들어봅니다.
#year_month의 2018-10부터 뽑아야 하므로 6부터 끝까지 뽑는다.
for i in range(6, len(year_months)):
#해당월인 거 하나씩 뽑아 임시 데이터프레임 tmp에 저장
tmp = ul_m.loc[ul_m['연월'] == year_months[i]]
#tmp의 cnt를 cnt_pred로 변경 (얘가 예측하려고 하는 정답 데이터)
tmp.rename(columns = {'cnt': 'cnt_pred'}, inplace=True)
#그리고 여기서는 tmp 하나의 달에 대응하는 그 이전 6개월달 분의 데이터(tmp_before)를 만든다.
for j in range(1,7):
tmp_before = ul_m.loc[ul_m['연월'] == year_months[i-j]]
del tmp_before['연월']
#tmp_before에는 cnt 열을 과거의 열로 하나씩 채워나감.
#예를 들어 2018년 10월에 대응하는 tmp_before는 2018.04 ~ 2018.09이다. 2018년 9월부터 역순으로 cnt 0 , cnt 1, .. cnt 6 이라는 열이 생성됨
tmp_before.rename(columns = {'cnt': 'cnt_{}'.format(j-1)}, inplace=True)
#그리고 tmp와 tmp_before를 결합
tmp = pd.merge(tmp, tmp_before, on ='customer_id', how='left')
#이렇게 tmp_before를 전부 뽑아 tmp로 결합하고 나서는, 비워뒀던 pred_data 에 tmp를 하나씩 결합시킨다. axis=0이므로 row-bind임
pred_data = pd.concat([pred_data, tmp], ignore_index = True)
pred_data
코드가 살짝 복잡하여 주석을 자세히 달아두었습니다.
먼저 for문 안에 임시 데이터셋인 'tmp'를 만듭니다. 이는 2018-10월~ 2019-4월에 해당하는 'ul_m' 데이터만 인덱싱한 데이터프레임입니다. 원래 'cnt' 였던 컬럼명을 'cnt_pred'로 바꿨습니다.
그리고 다음으로는 for문 안에 또다른 for문을 만들어 임시 데이터셋인 'tmp_before'를 만듭니다. 이렇게 한 이유는 앞서 tmp에 해당하는 달의 이전 6개월 분의 과거 데이터를 넣기 위해서입니다. 예를 들어 2018년 10월의 tmp 데이터가 만들어졌다 하면, 2018년 9월부터 (과거로 거슬러올라가) 8월, 7월, 6월, 5월, 4월 이렇게 6개월어치의 데이터를 넣고 각각 컬럼명을 cnt_0, cnt_1, cnt_2, cnt_3, cnt_4, cnt_5로 합니다. (컬럼명 수정은 format 함수를 사용)
그리고 만들어진 tmp와 tmp_before를 merge해줍니다. tmp_before 데이터셋에는 조인을 위한 'customer_id'와, cnt_0~cnt_5 만 필요하므로 '연월' 컬럼은 삭제했습니다.
최종 merge된 데이터셋인 'tmp'를 아까 만들어둔 'pred_data' 라는 빈 데이터프레임에 차곡차곡 쌓습니다. concat 함수를 사용하며, 이 함수의 디폴트는 axis=0 로 row-bind로 쌓이게 됩니다. 위의 사진은 최종 'pred_data' 데이터프레임을 출력한 모습입니다.
재가공한 'pred_data' 라는 데이터프레임을 자세히 뜯어보면 다음과 같습니다. 이 데이터 전체를 우리는 학습을 위한 train data로 사용할 것이며, cnt_0 ~ cnt_5 까지는 독립변수, cnt_pred 는 종속변수입니다.
즉, 이 데이터를 학습시켜 cnt_pred를 예측해보고, 실제 cnt_pred(정답 데이터)와 맞는지로 성능을 평가합니다.
다만 여기서 전처리를좀 해줘야 할 부분이 있네요! NaN은 해당 회원의 가입기간이 짧아 해당 기간에 헬스장을 이용하지 않은 경우입니다. NaN이 하나라도 포함되면 그 행은 전체 삭제해줍니다. 또한 이렇게 삭제 후엔 인덱스가 자동으로 바뀌어있지 않으므로 reset index로 초기화합니다.
pred_data = pred_data.dropna() #디폴트는 how='any', axis= 0 (NaN이 1개라도 포함된 경우 행 전체 삭제)
#인덱스가 바뀌어있지 않으므로 초기화
pred_data = pred_data.reset_index(drop=True) #이전의 인덱스 버림
pred_data.head()
Tech 37. 새로운 변수 가공하여 추가하기
앞서 우리는 'pred_data'를 잘 만들었는데요. 그런데 cnt_0 ~ cnt_5로만 cnt_pred 컬럼을 예측하기엔 변수의 수가 좀 적다는 느낌이 듭니다. 그래서 '회원 기간'이라는 새로운 시계열 변수를 추가해보겠습니다. (cnt_0 ~ cnt_5 변수도 시계열 변수이므로 잘 맞을 거 같네요!)
예측 성능을 높이기 위해선 데이터를 잘 설명해주는 좋은 변수가 추가될수록 성능이 높아집니다.
먼저, 'c' 데이터의 'start_ date'를 'pred_data' 데이터프레임에 조인시킵니다.
pred_data = pd.merge(pred_data, c[['customer_id', 'start_date']], on = 'customer_id', how='left')
pred_data.head()
dtypes를 확인해보니, start_date는 datetime 형태가 아니므로 바꿔줘야겠군요.
회원의 회원 기간을 계산하는 것은 '연월' 컬럼과 'start_date' 간의 날짜 차이를 계산하면 되겠죠?
우선 연산을 위해, 두 컬럼 모두 datetime으로 바꿔줍니다.
#먼저 datetime 형태로 변경
pred_data['now_date'] = pd.to_datetime(pred_data['연월'], format="%Y-%m-%d")
#start_date도 datetime으로 바꿔주기
pred_data['start_date'] = pd.to_datetime(pred_data['start_date'])
pred_data.head()
'now_date' 컬럼은 '연월' 컬럼을 날짜 형식으로 바꾼 것입니다.
dtypes를 다시 확인하면 제대로 datetime으로 들어간걸 확인할 수 있네요. 이제 relativedelta 메서드를 활용해 두 변수 간 날짜 차이(단위: month)를 계산해봅시다.
#이제 start_date, now_date 둘 다 datetime 형태이므로 날짜 연산이 가능하다. 두 날짜의 차이를 계산한 새로운 'period'(단위: month) 열을 만들자.
from dateutil.relativedelta import relativedelta
pred_data['period'] = None #초기화
for i in range(len(pred_data)):
delta = relativedelta(pred_data['now_date'][i], pred_data['start_date'][i])
pred_data['period'][i] = delta.years*12 + delta.months
pred_data.head()
참고로 다음 코드도 만들어봤는데 이렇게 해도 결과는 같습니다. 그런데 처음 쓴 코드가 더 빠른 거 같네요!
from dateutil.relativedelta import relativedelta
pred_data['period'] = None #초기화
for i in range(len(pred_data)):
delta = relativedelta(pred_data.loc[i, 'now_date'], pred_data.loc[i, 'start_date'])
pred_data.loc[i, 'period'] = delta.years*12 + delta.months
pred_data.head()
이렇게 모델링을 위한 데이터 가공을 마쳤습니다.
글이 너무 길어지는 관계로 모델링 부분은 다음 포스팅에서 이어집니다.
'Data Science > Analysis Study' 카테고리의 다른 글
머신러닝을 활용한 고객 이탈 예측 - 전처리 / 파이썬 데이터 분석 실무 테크닉 100 (6) | 2022.04.12 |
---|---|
머신러닝을 활용한 고객 이용 횟수 예측 - 모델링 / 파이썬 데이터 분석 실무 테크닉 100 (6) | 2022.04.06 |
고객 유형을 나누기(파이썬 클러스터링, 군집 분석) / 파이썬 데이터 분석 실무 테크닉 100 (6) | 2022.04.06 |
파이썬 EDA : 고객의 전체 모습 파악하기 -2편 / 파이썬 데이터 분석 실무 테크닉 100 (8) | 2022.01.17 |
파이썬 EDA : 고객의 전체 모습 파악하기 -1편 / 파이썬 데이터 분석 실무 테크닉 100 (3) | 2022.01.17 |