본문으로 건너뛰기

044 회귀 프로젝트 - 매출 예측

키워드: 매출 예측, 비즈니스, 회귀

개요

매출 예측은 비즈니스에서 매우 중요한 문제입니다. 과거 매출 데이터와 다양한 요인을 분석하여 미래 매출을 예측합니다. 이 글에서는 소매점 매출 예측 모델을 개발합니다.

실습 환경

  • Python 버전: 3.11 권장
  • 필요 패키지: flaml[automl], pandas, scikit-learn
pip install flaml[automl] pandas scikit-learn matplotlib

프로젝트 개요

목표

다양한 요인을 고려한 일별/주별 매출 예측

비즈니스 가치

  • 재고 관리 최적화
  • 인력 배치 계획
  • 마케팅 예산 책정
  • 현금 흐름 관리

Step 1: 데이터 준비

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime, timedelta

# 044 가상의 소매점 매출 데이터 생성
np.random.seed(42)
n_days = 730 # 2년

# 044 날짜 범위
start_date = datetime(2022, 1, 1)
dates = [start_date + timedelta(days=i) for i in range(n_days)]

# 044 기본 매출 패턴
base_sales = 50000
trend = np.linspace(0, 10000, n_days) # 성장 트렌드
seasonality = 10000 * np.sin(np.arange(n_days) * 2 * np.pi / 365) # 연간 계절성
weekly = 5000 * np.sin(np.arange(n_days) * 2 * np.pi / 7) # 주간 패턴

# 044 특별 이벤트
holiday_boost = np.zeros(n_days)
for i, date in enumerate(dates):
if date.month == 12: # 12월 크리스마스 시즌
holiday_boost[i] = 15000
if date.month == 11 and date.day >= 20: # 블랙프라이데이
holiday_boost[i] = 20000

# 044 날씨 영향 (랜덤)
weather_effect = np.random.randn(n_days) * 3000

# 044 프로모션
promotions = np.random.choice([0, 1], n_days, p=[0.9, 0.1])
promo_effect = promotions * np.random.uniform(5000, 15000, n_days)

# 044 최종 매출
sales = base_sales + trend + seasonality + weekly + holiday_boost + weather_effect + promo_effect
sales = np.maximum(sales, 10000) # 최소 매출

# 044 DataFrame 생성
df = pd.DataFrame({
'date': dates,
'sales': sales.astype(int),
'promotion': promotions,
'temperature': np.random.uniform(0, 35, n_days).round(1),
'is_weekend': [1 if d.weekday() >= 5 else 0 for d in dates],
'is_holiday': [1 if d.month == 12 and d.day in range(20, 32) else 0 for d in dates]
})

# 044 날짜 특성 추출
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day'] = df['date'].dt.day
df['dayofweek'] = df['date'].dt.dayofweek
df['weekofyear'] = df['date'].dt.isocalendar().week.astype(int)
df['quarter'] = df['date'].dt.quarter

print("데이터셋 정보:")
print(f" 기간: {df['date'].min().date()} ~ {df['date'].max().date()}")
print(f" 샘플 수: {len(df)}")
print(f"\n처음 5행:")
print(df.head())

Step 2: 탐색적 데이터 분석

매출 추이

fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 044 전체 추이
axes[0, 0].plot(df['date'], df['sales'], alpha=0.7)
axes[0, 0].set_xlabel('Date')
axes[0, 0].set_ylabel('Sales ($)')
axes[0, 0].set_title('Daily Sales Trend')

# 044 월별 평균
monthly = df.groupby('month')['sales'].mean()
axes[0, 1].bar(monthly.index, monthly.values)
axes[0, 1].set_xlabel('Month')
axes[0, 1].set_ylabel('Average Sales ($)')
axes[0, 1].set_title('Average Sales by Month')

# 044 요일별 평균
dow_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
daily = df.groupby('dayofweek')['sales'].mean()
axes[1, 0].bar(dow_names, daily.values)
axes[1, 0].set_xlabel('Day of Week')
axes[1, 0].set_ylabel('Average Sales ($)')
axes[1, 0].set_title('Average Sales by Day of Week')

# 044 프로모션 효과
promo_effect = df.groupby('promotion')['sales'].mean()
axes[1, 1].bar(['No Promo', 'Promo'], promo_effect.values)
axes[1, 1].set_xlabel('Promotion')
axes[1, 1].set_ylabel('Average Sales ($)')
axes[1, 1].set_title('Promotion Effect on Sales')

plt.tight_layout()
plt.show()

상관관계

# 044 상관관계 분석
numeric_cols = df.select_dtypes(include=[np.number]).columns
correlation = df[numeric_cols].corr()['sales'].sort_values(ascending=False)

print("매출과의 상관관계:")
print(correlation)

Step 3: 특성 엔지니어링

# 044 추가 특성 생성
df_features = df.copy()

# 044 이동 평균 (과거 데이터만 사용)
df_features['sales_ma7'] = df_features['sales'].shift(1).rolling(7).mean()
df_features['sales_ma30'] = df_features['sales'].shift(1).rolling(30).mean()

# 044 지연 특성 (lag features)
for lag in [1, 7, 14, 30]:
df_features[f'sales_lag{lag}'] = df_features['sales'].shift(lag)

# 044 결측치 제거 (첫 30일)
df_features = df_features.dropna()

print(f"특성 엔지니어링 후 샘플 수: {len(df_features)}")
print(f"특성 수: {df_features.shape[1] - 2}") # date, sales 제외

Step 4: 데이터 분할

from sklearn.model_selection import train_test_split

# 044 특성과 타겟 분리
feature_cols = [col for col in df_features.columns if col not in ['date', 'sales']]
X = df_features[feature_cols]
y = df_features['sales']

# 044 시간 기반 분할 (마지막 60일을 테스트로)
split_idx = len(df_features) - 60

X_train = X.iloc[:split_idx]
X_test = X.iloc[split_idx:]
y_train = y.iloc[:split_idx]
y_test = y.iloc[split_idx:]
dates_test = df_features['date'].iloc[split_idx:]

print(f"학습 데이터: {len(X_train)}개")
print(f"테스트 데이터: {len(X_test)}개")
print(f"테스트 기간: {dates_test.min().date()} ~ {dates_test.max().date()}")

Step 5: FLAML AutoML 학습

from flaml import AutoML

automl = AutoML()
automl.fit(
X_train, y_train,
task="regression",
time_budget=120,
metric="mape", # 비율 기반 오차
seed=42,
verbose=1
)

print(f"\n최적 모델: {automl.best_estimator}")
print(f"검증 MAPE: {automl.best_loss * 100:.2f}%")

Step 6: 모델 평가

from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score

# 044 예측
y_pred = automl.predict(X_test)

# 044 평가 지표
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)
mape = np.mean(np.abs((y_test - y_pred) / y_test)) * 100

print("테스트 성능:")
print(f" RMSE: ${rmse:,.0f}")
print(f" MAE: ${mae:,.0f}")
print(f" R²: {r2:.4f}")
print(f" MAPE: {mape:.2f}%")

예측 시각화

fig, axes = plt.subplots(2, 1, figsize=(14, 10))

# 044 시계열 비교
axes[0].plot(dates_test, y_test.values, label='Actual', linewidth=2)
axes[0].plot(dates_test, y_pred, label='Predicted', linewidth=2, alpha=0.8)
axes[0].fill_between(dates_test, y_pred * 0.9, y_pred * 1.1, alpha=0.2, label='±10% Band')
axes[0].set_xlabel('Date')
axes[0].set_ylabel('Sales ($)')
axes[0].set_title('Sales Prediction vs Actual')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# 044 오차 분포
errors = y_test.values - y_pred
axes[1].bar(dates_test, errors, alpha=0.7)
axes[1].axhline(y=0, color='r', linestyle='--')
axes[1].set_xlabel('Date')
axes[1].set_ylabel('Error ($)')
axes[1].set_title('Prediction Errors')
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

Step 7: 특성 중요도

if hasattr(automl.best_model, 'feature_importances_'):
importance = automl.best_model.feature_importances_
feature_names = X.columns

# 상위 10개
sorted_idx = np.argsort(importance)[::-1][:10]

plt.figure(figsize=(10, 6))
plt.barh(range(len(sorted_idx)), importance[sorted_idx][::-1])
plt.yticks(range(len(sorted_idx)), feature_names[sorted_idx][::-1])
plt.xlabel('Importance')
plt.title('Top 10 Feature Importance for Sales Prediction')
plt.tight_layout()
plt.show()

print("상위 5개 중요 특성:")
for i in sorted_idx[:5]:
print(f" {feature_names[i]}: {importance[i]:.4f}")

Step 8: 비즈니스 인사이트

# 044 요일별 예측 정확도
df_result = pd.DataFrame({
'date': dates_test.values,
'actual': y_test.values,
'predicted': y_pred,
'error': y_test.values - y_pred,
'abs_error': np.abs(y_test.values - y_pred),
'dayofweek': [d.dayofweek for d in dates_test]
})

dow_accuracy = df_result.groupby('dayofweek').agg({
'abs_error': 'mean',
'actual': 'mean'
})
dow_accuracy['mape'] = dow_accuracy['abs_error'] / dow_accuracy['actual'] * 100

print("요일별 예측 성능:")
print(dow_accuracy.round(2))

# 044 프로모션 여부에 따른 정확도
df_result['promotion'] = X_test['promotion'].values
promo_accuracy = df_result.groupby('promotion')['abs_error'].mean()
print(f"\n프로모션별 MAE:")
print(f" 프로모션 없음: ${promo_accuracy[0]:,.0f}")
print(f" 프로모션 있음: ${promo_accuracy[1]:,.0f}")

Step 9: 미래 예측 (다음 7일)

def forecast_next_days(model, last_data, n_days=7):
"""다음 n일 예측"""
predictions = []
current_data = last_data.copy()

for i in range(n_days):
# 다음 날 예측
next_day = model.predict(current_data[feature_cols].iloc[[-1]])[0]
predictions.append(next_day)

# 데이터 업데이트 (시뮬레이션)
# 실제로는 새로운 날짜의 특성이 필요

return predictions

# 044 간단한 예측 (실제로는 더 복잡한 처리 필요)
print("\n다음 7일 예측 시뮬레이션:")
print("(실제 구현 시 날짜별 특성 생성 필요)")

# 044 마지막 주 평균으로 추정
last_week_avg = y_test.tail(7).mean()
print(f"참고: 마지막 주 평균 매출: ${last_week_avg:,.0f}")

Step 10: 모델 저장

import pickle

model_package = {
'model': automl,
'features': feature_cols,
'metrics': {
'rmse': rmse,
'mae': mae,
'r2': r2,
'mape': mape
}
}

with open('sales_forecast_model.pkl', 'wb') as f:
pickle.dump(model_package, f)

print("모델 저장 완료: sales_forecast_model.pkl")

정리

  • 매출 예측에서 시간 특성(월, 요일, 계절)이 중요합니다.
  • Lag 특성이동 평균이 예측력을 높입니다.
  • MAPE로 비율 기반 오차를 평가합니다.
  • 프로모션, 휴일 등 외부 요인을 고려합니다.
  • 시계열 데이터는 시간 기반 분할을 사용합니다.
  • 비즈니스 인사이트를 위해 요일별, 이벤트별 분석을 수행합니다.

다음 글 예고

다음 글에서는 회귀에서의 앙상블 기법에 대해 알아보겠습니다. 여러 모델을 결합하여 예측 성능을 높이는 방법을 다룹니다.


FLAML AutoML 마스터 시리즈 #044