본문으로 건너뛰기

055 회귀 실전 - 가격 최적화

키워드: 가격, 최적화

개요

가격 최적화는 수익을 극대화하면서 수요를 유지하는 최적의 가격점을 찾는 것입니다. 이 글에서는 PyCaret 회귀 모델을 활용하여 가격 최적화를 수행합니다.

실습 환경

  • Python 버전: 3.11 권장
  • 필요 패키지: pycaret[full]>=3.0

비즈니스 문제 정의

목표: 수익(Revenue = Price × Demand)을 최대화하는 최적 가격 결정 제약: 시장 점유율 유지, 경쟁사 가격 고려

데이터 준비

import pandas as pd
import numpy as np

# 055 가격-수요 데이터 시뮬레이션
np.random.seed(42)
n_samples = 3000

# 055 제품 카테고리
categories = ['Premium', 'Standard', 'Budget']

data = []
for _ in range(n_samples):
category = np.random.choice(categories)

# 카테고리별 기본 설정
base_config = {
'Premium': {'base_price': 500, 'base_demand': 100, 'elasticity': -1.5},
'Standard': {'base_price': 200, 'base_demand': 300, 'elasticity': -2.0},
'Budget': {'base_price': 50, 'base_demand': 800, 'elasticity': -2.5}
}[category]

# 가격 변동 (기본 가격의 50% ~ 150%)
price = base_config['base_price'] * np.random.uniform(0.5, 1.5)

# 경쟁사 가격
competitor_price = base_config['base_price'] * np.random.uniform(0.7, 1.3)

# 마케팅 지출
marketing_spend = np.random.uniform(0, 10000)

# 계절 (1-4분기)
quarter = np.random.randint(1, 5)

# 수요 계산 (가격 탄력성 모델)
price_ratio = price / base_config['base_price']
demand = base_config['base_demand'] * (price_ratio ** base_config['elasticity'])

# 경쟁 효과
if competitor_price < price:
demand *= 0.8 # 경쟁사가 더 싸면 수요 감소
elif competitor_price > price * 1.1:
demand *= 1.2 # 경쟁사가 더 비싸면 수요 증가

# 마케팅 효과
demand += np.log1p(marketing_spend) * 10

# 계절 효과
if quarter in [4, 1]: # 연말, 연초
demand *= 1.3

# 노이즈
demand += np.random.normal(0, demand * 0.1)
demand = max(1, demand)

# 원가
cost = price * np.random.uniform(0.3, 0.5)

data.append({
'category': category,
'price': price,
'competitor_price': competitor_price,
'marketing_spend': marketing_spend,
'quarter': quarter,
'cost': cost,
'demand': demand,
'revenue': price * demand,
'profit': (price - cost) * demand
})

data = pd.DataFrame(data)

print(f"데이터 크기: {len(data)}")
print(f"\n카테고리별 통계:")
print(data.groupby('category')[['price', 'demand', 'revenue', 'profit']].mean())

탐색적 데이터 분석

import matplotlib.pyplot as plt
import seaborn as sns

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

# 055 가격 vs 수요
axes[0, 0].scatter(data['price'], data['demand'], alpha=0.3, c=data['category'].astype('category').cat.codes)
axes[0, 0].set_xlabel('Price')
axes[0, 0].set_ylabel('Demand')
axes[0, 0].set_title('Price vs Demand')

# 055 가격 vs 수익
axes[0, 1].scatter(data['price'], data['revenue'], alpha=0.3, c=data['category'].astype('category').cat.codes)
axes[0, 1].set_xlabel('Price')
axes[0, 1].set_ylabel('Revenue')
axes[0, 1].set_title('Price vs Revenue')

# 055 카테고리별 수익 분포
data.boxplot(column='revenue', by='category', ax=axes[1, 0])
axes[1, 0].set_title('Revenue by Category')

# 055 가격 vs 이익
axes[1, 1].scatter(data['price'], data['profit'], alpha=0.3)
axes[1, 1].set_xlabel('Price')
axes[1, 1].set_ylabel('Profit')
axes[1, 1].set_title('Price vs Profit')

plt.tight_layout()
plt.savefig('price_optimization_eda.png', dpi=150)

수요 예측 모델

from pycaret.regression import *

# 055 특성 엔지니어링
data['price_ratio'] = data['price'] / data['competitor_price']
data['is_peak_season'] = data['quarter'].isin([1, 4]).astype(int)

# 055 수요 예측 모델 설정
reg = setup(
data=data,
target='demand',
categorical_features=['category'],
numeric_features=['price', 'competitor_price', 'marketing_spend',
'quarter', 'price_ratio'],
ignore_features=['revenue', 'profit', 'cost'],
session_id=42,
verbose=False
)

# 055 모델 비교
best = compare_models(n_select=3)

# 055 최적 모델 선택 및 튜닝
xgb = create_model('xgboost', verbose=False)
tuned_model = tune_model(xgb, optimize='RMSE')

print("수요 예측 모델 성능:")
print(pull())

가격 탄력성 분석

import numpy as np

def analyze_price_elasticity(model, category, base_data, price_range):
"""가격 탄력성 분석"""

sample = base_data[base_data['category'] == category].iloc[0:1].copy()

results = []
for price in price_range:
sample_copy = sample.copy()
sample_copy['price'] = price
sample_copy['price_ratio'] = price / sample_copy['competitor_price'].values[0]

pred = predict_model(model, data=sample_copy, verbose=False)
demand = pred['prediction_label'].values[0]

results.append({
'price': price,
'demand': demand,
'revenue': price * demand
})

return pd.DataFrame(results)

# 055 각 카테고리별 분석
categories = ['Premium', 'Standard', 'Budget']
base_prices = {'Premium': 500, 'Standard': 200, 'Budget': 50}

plt.figure(figsize=(15, 5))

for idx, cat in enumerate(categories):
prices = np.linspace(base_prices[cat] * 0.5, base_prices[cat] * 1.5, 50)
results = analyze_price_elasticity(tuned_model, cat, data, prices)

plt.subplot(1, 3, idx+1)
plt.plot(results['price'], results['revenue'], 'b-', linewidth=2)

# 최적 가격 표시
optimal_idx = results['revenue'].idxmax()
optimal_price = results.loc[optimal_idx, 'price']
optimal_revenue = results.loc[optimal_idx, 'revenue']

plt.axvline(x=optimal_price, color='r', linestyle='--', label=f'Optimal: ${optimal_price:.0f}')
plt.scatter([optimal_price], [optimal_revenue], color='red', s=100, zorder=5)

plt.xlabel('Price ($)')
plt.ylabel('Revenue ($)')
plt.title(f'{cat} Category')
plt.legend()
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('optimal_price_by_category.png', dpi=150)

최적 가격 찾기

from scipy.optimize import minimize_scalar

def find_optimal_price(model, base_sample, price_min, price_max, objective='revenue'):
"""최적 가격 찾기"""

def negative_revenue(price):
sample = base_sample.copy()
sample['price'] = price
sample['price_ratio'] = price / sample['competitor_price'].values[0]

pred = predict_model(model, data=sample, verbose=False)
demand = pred['prediction_label'].values[0]

if objective == 'revenue':
return -price * demand
else: # profit
cost = sample['cost'].values[0]
return -(price - cost) * demand

result = minimize_scalar(
negative_revenue,
bounds=(price_min, price_max),
method='bounded'
)

return result.x, -result.fun

# 055 각 카테고리별 최적 가격
print("=== 최적 가격 분석 ===\n")

for cat in categories:
sample = data[data['category'] == cat].iloc[0:1].copy()
base_price = base_prices[cat]

# 수익 최대화 가격
opt_price_rev, max_revenue = find_optimal_price(
tuned_model, sample,
base_price * 0.5, base_price * 1.5,
objective='revenue'
)

# 이익 최대화 가격
opt_price_profit, max_profit = find_optimal_price(
tuned_model, sample,
base_price * 0.5, base_price * 1.5,
objective='profit'
)

print(f"{cat} 카테고리:")
print(f" 기준 가격: ${base_price}")
print(f" 수익 최대화 가격: ${opt_price_rev:.0f} (수익: ${max_revenue:,.0f})")
print(f" 이익 최대화 가격: ${opt_price_profit:.0f} (이익: ${max_profit:,.0f})")
print()

가격 시나리오 분석

def scenario_analysis(model, base_data, scenarios):
"""가격 시나리오 분석"""

results = []

for scenario_name, price_changes in scenarios.items():
scenario_data = base_data.copy()

for cat, change in price_changes.items():
mask = scenario_data['category'] == cat
scenario_data.loc[mask, 'price'] *= (1 + change)
scenario_data.loc[mask, 'price_ratio'] = (
scenario_data.loc[mask, 'price'] /
scenario_data.loc[mask, 'competitor_price']
)

# 예측
pred = predict_model(model, data=scenario_data, verbose=False)
scenario_data['predicted_demand'] = pred['prediction_label']
scenario_data['predicted_revenue'] = scenario_data['price'] * scenario_data['predicted_demand']

results.append({
'Scenario': scenario_name,
'Total Revenue': scenario_data['predicted_revenue'].sum(),
'Avg Demand': scenario_data['predicted_demand'].mean(),
'Premium Revenue': scenario_data[scenario_data['category']=='Premium']['predicted_revenue'].sum(),
'Standard Revenue': scenario_data[scenario_data['category']=='Standard']['predicted_revenue'].sum(),
'Budget Revenue': scenario_data[scenario_data['category']=='Budget']['predicted_revenue'].sum()
})

return pd.DataFrame(results)

# 055 시나리오 정의
scenarios = {
'Current': {'Premium': 0, 'Standard': 0, 'Budget': 0},
'All +10%': {'Premium': 0.1, 'Standard': 0.1, 'Budget': 0.1},
'All -10%': {'Premium': -0.1, 'Standard': -0.1, 'Budget': -0.1},
'Premium +20%': {'Premium': 0.2, 'Standard': 0, 'Budget': 0},
'Budget -20%': {'Premium': 0, 'Standard': 0, 'Budget': -0.2},
'Mixed Strategy': {'Premium': 0.15, 'Standard': -0.05, 'Budget': -0.1}
}

# 055 분석 실행
test_data = data.sample(500, random_state=42)
scenario_results = scenario_analysis(tuned_model, test_data, scenarios)

print("\n=== 가격 시나리오 분석 ===\n")
print(scenario_results.to_string(index=False))

경쟁사 가격 대응 전략

def competitive_response(model, base_sample, our_price, competitor_prices):
"""경쟁사 가격 변화에 대한 최적 대응"""

results = []

for comp_price in competitor_prices:
sample = base_sample.copy()
sample['competitor_price'] = comp_price

# 최적 가격 찾기
opt_price, max_rev = find_optimal_price(
model, sample,
our_price * 0.5, our_price * 1.5
)

results.append({
'competitor_price': comp_price,
'optimal_price': opt_price,
'price_gap': opt_price - comp_price,
'expected_revenue': max_rev
})

return pd.DataFrame(results)

# 055 프리미엄 카테고리 분석
sample = data[data['category'] == 'Premium'].iloc[0:1].copy()
competitor_prices = np.linspace(300, 700, 9)

response = competitive_response(tuned_model, sample, 500, competitor_prices)

print("\n=== 경쟁사 가격 대응 전략 (Premium) ===\n")
print(response.to_string(index=False))

# 055 시각화
plt.figure(figsize=(10, 6))
plt.plot(response['competitor_price'], response['optimal_price'], 'bo-', linewidth=2, markersize=8)
plt.plot([300, 700], [300, 700], 'r--', label='Price Match')
plt.xlabel('Competitor Price ($)')
plt.ylabel('Our Optimal Price ($)')
plt.title('Competitive Pricing Response')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('competitive_response.png', dpi=150)

동적 가격 최적화

def dynamic_pricing(model, base_data, demand_threshold, max_adjustment=0.2):
"""재고/수요 기반 동적 가격 조정"""

results = []

for idx, row in base_data.iterrows():
sample = pd.DataFrame([row])

# 현재 수요 예측
pred = predict_model(model, data=sample, verbose=False)
current_demand = pred['prediction_label'].values[0]

# 수요 수준에 따른 가격 조정
if current_demand > demand_threshold * 1.2:
# 수요가 높으면 가격 인상
adjustment = min(0.1, max_adjustment)
elif current_demand < demand_threshold * 0.8:
# 수요가 낮으면 가격 인하
adjustment = max(-0.15, -max_adjustment)
else:
adjustment = 0

new_price = row['price'] * (1 + adjustment)

results.append({
'original_price': row['price'],
'current_demand': current_demand,
'adjustment': f"{adjustment:+.0%}",
'new_price': new_price,
'category': row['category']
})

return pd.DataFrame(results)

# 055 동적 가격 조정 시뮬레이션
sample_data = data.sample(10, random_state=42)
dynamic_results = dynamic_pricing(tuned_model, sample_data, demand_threshold=150)

print("\n=== 동적 가격 조정 예시 ===\n")
print(dynamic_results.to_string(index=False))

가격 최적화 대시보드 요약

def generate_pricing_summary(model, data, categories):
"""가격 최적화 요약 리포트"""

print("=" * 60)
print(" 가격 최적화 요약 리포트")
print("=" * 60)

for cat in categories:
cat_data = data[data['category'] == cat]
sample = cat_data.iloc[0:1].copy()

current_price = cat_data['price'].mean()
current_revenue = cat_data['revenue'].mean()

# 최적 가격 찾기
opt_price, expected_revenue = find_optimal_price(
model, sample,
current_price * 0.5, current_price * 1.5
)

improvement = (expected_revenue - current_revenue) / current_revenue * 100

print(f"\n{cat} 카테고리:")
print(f" 현재 평균 가격: ${current_price:.0f}")
print(f" 권장 가격: ${opt_price:.0f}")
print(f" 가격 변화: {(opt_price/current_price - 1)*100:+.1f}%")
print(f" 예상 수익 개선: {improvement:+.1f}%")

print("\n" + "=" * 60)

generate_pricing_summary(tuned_model, data, categories)

모델 저장

# 055 최종 모델
final_model = finalize_model(tuned_model)

# 055 저장
save_model(final_model, 'price_optimization_model')
print("\n모델 저장 완료: price_optimization_model.pkl")

실무 적용 팁

  1. A/B 테스트: 최적 가격을 실제 적용 전 테스트
  2. 세분화: 고객 세그먼트별 다른 가격 전략
  3. 경쟁 모니터링: 실시간 경쟁사 가격 추적
  4. 제약 조건: 최저가 정책, 브랜드 포지셔닝 고려
  5. 시간 요소: 시간대/요일별 동적 가격 책정

정리

  • 가격 최적화는 수요 예측 + 최적화의 조합
  • 가격 탄력성이 핵심 개념
  • 수익 vs 이익 최대화는 다른 가격점
  • 경쟁사 가격에 대한 대응 전략 필요
  • 시나리오 분석으로 리스크 파악

다음 글 예고

다음 글에서는 클러스터링의 이해를 다룹니다.


PyCaret 머신러닝 마스터 시리즈 #055