Untitled17.ipynb

분석 코드

# ===============================================================
# 0. 공통 라이브러리 및 Matplotlib 전역 설정
#    ─ 그래프 잘림 방지를 위해 DPI·여백·폰트 크기 일괄 조정
# ===============================================================
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
import os, sys

mpl.rcParams.update({
    "figure.dpi"       : 120,   # 화면 표시용 DPI
    "savefig.dpi"      : 300,   # 파일 저장용 DPI
    "figure.autolayout": True,  # 자동 레이아웃 조정
    "axes.titlesize"   : 16,    # 그래프 제목 글꼴 크기
    "axes.labelsize"   : 14,    # 축 라벨 글꼴 크기
    "xtick.labelsize"  : 11,    # x축 눈금 글꼴 크기
    "ytick.labelsize"  : 11,    # y축 눈금 글꼴 크기
    "axes.unicode_minus": False # 음수 부호 깨짐 방지
})
sns.set_theme(style="whitegrid", font_scale=0.9)

# ===============================================================
# 1. 데이터 로드 및 기초 정보
# ===============================================================
PATH       = "train.csv"            # 파일 경로
DATE_COL   = "영업일자"              # 날짜 칼럼
MENU_COL   = "영업장명_메뉴명"        # 메뉴 식별 칼럼
TARGET_COL = "매출수량"              # 목표(수요) 칼럼

df = (pd.read_csv(PATH, parse_dates=[DATE_COL])
        .assign(**{DATE_COL: lambda d: pd.to_datetime(   # strip→to_datetime
            d[DATE_COL].astype(str).str.strip(), errors="coerce")}))

bad_dates = df[DATE_COL].isna().sum()
print(f"[INFO] rows with invalid date parsing : {bad_dates}")  # ← 잘못 파싱된 행 수

# ─ 집계 값
n_rows   = len(df)                            # 전체 행 개수
n_menus  = df[MENU_COL].nunique()             # 고유 메뉴 수
d_min, d_max = df[DATE_COL].min(), df[DATE_COL].max()   # 날짜 범위
neg_vals = (df[TARGET_COL] < 0).sum()         # 음수 매출 수량

print("\\n🔎 Basic Overview")                   # 기본 개요
print(f" • rows             : {n_rows:,}")    # 전체 행
print(f" • unique menus     : {n_menus}")     # 고유 메뉴 수
print(f" • date range       : {d_min.date()} → {d_max.date()}")  # 날짜 범위
print(f" • negative targets : {neg_vals}")    # 음수 값 개수

print("\\n🔎 Target Description")                # 목표 변수 기술통계
print(df[TARGET_COL].describe())

# ===============================================================
# 2. Time-series continuity check  (시계열 연속성 확인)
# ===============================================================
missing_counts = []
for _, grp in df.groupby(MENU_COL):
    full_range = pd.date_range(grp[DATE_COL].min(), grp[DATE_COL].max(), freq="D")
    missing_counts.append(len(full_range) - grp[DATE_COL].nunique())

missing_series = pd.Series(missing_counts)
print("\\n🔎 Missing calendar days")            # 누락된 캘린더 일자
print(f" • mean missing days per series : {missing_series.mean():.2f}")  # 평균 누락
print(f" • max  missing days            : {missing_series.max()}")        # 최대 누락

# ===============================================================
# 3-A. Average sales by day-of-week   (요일별 평균 매출)
# ===============================================================
df["dow"] = df[DATE_COL].dt.dayofweek              # 0=Mon … 6=Sun
dow_mean  = df.groupby("dow")[TARGET_COL].mean()   # 요일별 평균

fig, ax = plt.subplots(figsize=(8, 5))
dow_mean.plot(kind="bar", ax=ax)
ax.set(title="Average Sales by Day of Week",       # 그래프 제목
       xlabel="Day of Week (0=Mon)",               # x축 라벨
       ylabel="Average Sales")                     # y축 라벨
fig.tight_layout(rect=[0, 0, 1, 0.95])
plt.show()

# ===============================================================
# 3-B. Target distribution (upper 99 % clipped)
#      → 목표 변수 분포 (상위 1 % 컷)
# ===============================================================
fig, ax = plt.subplots(figsize=(8, 5))
(df[TARGET_COL]
    .clip(upper=df[TARGET_COL].quantile(0.99))     # 상위 1 % 클리핑
    .hist(bins=50, ax=ax))
ax.set(title="Sales Quantity Distribution (99 % clipped)",  # 제목
       xlabel="Sales Quantity",                             # x축: 매출 수량
       ylabel="Frequency")                                  # y축: 빈도
fig.tight_layout(rect=[0, 0, 1, 0.95])
plt.show()

# ===============================================================
# 3-C. Monthly total sales trend   (월별 총 매출 추세)
# ===============================================================
df["month"] = df[DATE_COL].dt.to_period("M")       # 월 단위 Period
monthly_total = df.groupby("month")[TARGET_COL].sum()

fig, ax = plt.subplots(figsize=(10, 5))
monthly_total.plot(ax=ax)
ax.set(title="Monthly Total Sales Trend",           # 제목
       xlabel="Month",                              # x축: 월
       ylabel="Total Sales")                        # y축: 총 매출
fig.tight_layout(rect=[0, 0, 1, 0.95])
plt.show()

# ===============================================================
# 3-D. Correlation heatmap of Top-20 menus (상위 20개 메뉴 상관 히트맵)
#      ─ 긴 메뉴명을 코드(M01…M20)로 치환하여 축 잘림 방지
# ===============================================================
TOP_N = 20
top_menus = df[MENU_COL].value_counts().head(TOP_N).index

# 1) menu → code 매핑 (ex. M01, M02 …)
menu_codes   = {m: f"M{idx+1:02d}" for idx, m in enumerate(top_menus)}
code_to_menu = {v: k for k, v in menu_codes.items()}

# 2) 피벗 생성
pivot = (df[df[MENU_COL].isin(top_menus)]
         .assign(code=lambda d: d[MENU_COL].map(menu_codes))
         .pivot_table(index=DATE_COL,
                      columns="code",
                      values=TARGET_COL,
                      fill_value=0))

corr = pivot.corr()   # 상관계수 계산

fig, ax = plt.subplots(figsize=(10, 8))
sns.heatmap(corr, cmap="vlag", center=0, square=True, ax=ax,
            cbar_kws={"shrink": 0.8, "pad": 0.02})
ax.set_title("Correlation Heatmap of Top-20 Menus (coded)", pad=14)  # 제목

# 축 레이블 회전
plt.setp(ax.get_xticklabels(), rotation=45, ha="right")
plt.setp(ax.get_yticklabels(), rotation=0)
plt.tight_layout(rect=[0, 0, 1, 0.97])
plt.show()

# 3) 코드 ↔ 원본 메뉴명 표 출력
print("\\n🔎 Code → Original menu mapping")   # 코드 → 원본 메뉴 매핑
for code, name in code_to_menu.items():
    print(f" {code} : {name}")

# ===============================================================
# 4. (Optional) Save figure without clipping
#    ─ bbox_inches='tight' 옵션으로 여백 포함 저장
# ===============================================================
save_fig = "dow_mean.png"
fig, ax = plt.subplots(figsize=(8, 5))
dow_mean.plot(kind="bar", ax=ax)
ax.set(title="Average Sales by Day of Week",        # 그래프 제목
       xlabel="Day of Week (0=Mon)",                # x축 라벨
       ylabel="Average Sales")                      # y축 라벨
fig.tight_layout(rect=[0, 0, 1, 0.95])
fig.savefig(save_fig, bbox_inches="tight")          # 잘림 방지 저장
print(f"\\n📁 Figure saved as: {os.path.abspath(save_fig)}")   # 저장 경로

1. 코드 목적

섹션 기능 비고
0. 공통 설정 - matplotlib / seaborn 전역 RC 설정- DPI·폰트·여백 일괄 조정 👉 그래프가 Colab / 노션 썸네일에서 잘리지 않도록 미리 조치
1. 데이터 로드 - train.csv 읽기 & 날짜 파싱- 기본 통계(행 수, 메뉴 수, 범위 등) 출력
2. 시계열 연속성 - 메뉴별 캘린더 range 생성 후 누락일 계산 시계열 예측 전에 데이터 갭 유무 확인
3-A. 요일별 평균 - dayofweek → bar plot 요일 계절성(weekly seasonality) 탐색
3-B. 분포 - 목표변수 상위 1 % 클리핑 후 histogram 극단치(heavy tail) 시각화
3-C. 월별 총합 - Period('M') 집계 → line plot 연-중 시즌 트렌드 확인
3-D. 상위 20 메뉴 상관 - 긴 이름을 M01…M20 코드화- 피벗 → 상관행렬 → heat-map 대체관계 / 묶음판매 인사이트
4. (옵션) 저장 - 그래프를 bbox_inches='tight'로 저장 보고서용 이미지 export

image.png

2. 데이터 요약

항목 해석
행(row) 102 ,676 약 10만 건 판매 로그
메뉴 수 193 단일 매장 기준으론 다소 복잡한 카탈로그
기간 2023-01-01 ~ 2024-06-15 ~18 개월 데이터 확보
음수 수량 14 환불/취소로 추정 → 모델 학습 전 클리닝 필요
Missing day 평균 0, 최대 0 일자 단위 완전 — 추가 보간 불필요

목표변수(매출수량) 분포


3. 시각화 인사이트

image.png

3-A. 요일별 평균 매출

Mon(0) Tue(1) Wed(2) Thu(3) Fri(4) Sat(5) Sun(6)
7.8 8.4 8.0 10.0 12.2 15.2 12.8