|
import pandas as pd |
|
import numpy as np |
|
import datetime as dt |
|
import warnings |
|
|
|
from statsmodels.tsa.holtwinters import ExponentialSmoothing |
|
import plotly.graph_objects as go |
|
import gradio as gr |
|
|
|
warnings.filterwarnings("ignore") |
|
|
|
|
|
|
|
|
|
DATA_FILE = "202503-domae.parquet" |
|
FORECAST_END_YEAR = 2030 |
|
SEASONAL_PERIODS = 12 |
|
|
|
|
|
|
|
|
|
|
|
def load_data(path: str) -> pd.DataFrame: |
|
"""Parquet โ ์๋ณ ํผ๋ฒ ํ
์ด๋ธ(DateIndex, ์ด: ํ๋ชฉ, ๊ฐ: ๊ฐ๊ฒฉ).""" |
|
df = pd.read_parquet(path) |
|
|
|
|
|
if "date" in df.columns: |
|
df["date"] = pd.to_datetime(df["date"]) |
|
elif "PRCE_REG_MM" in df.columns: |
|
df["date"] = pd.to_datetime(df["PRCE_REG_MM"].astype(str), format="%Y%m") |
|
else: |
|
raise ValueError("์ง์๋์ง ์๋ ๋ ์ง ์ปฌ๋ผ ํ์์
๋๋ค.") |
|
|
|
|
|
item_col = "PDLT_NM" if "PDLT_NM" in df.columns else "item" |
|
price_col = "AVRG_PRCE" if "AVRG_PRCE" in df.columns else "price" |
|
|
|
monthly = ( |
|
df.groupby(["date", item_col])[price_col] |
|
.mean() |
|
.reset_index() |
|
) |
|
pivot = ( |
|
monthly |
|
.pivot(index="date", columns=item_col, values=price_col) |
|
.sort_index() |
|
) |
|
|
|
pivot.index = pd.to_datetime(pivot.index).to_period("M").to_timestamp() |
|
return pivot |
|
|
|
pivot = load_data(DATA_FILE) |
|
products = pivot.columns.tolist() |
|
|
|
|
|
|
|
|
|
|
|
def _fit_forecast(series: pd.Series) -> pd.Series: |
|
"""์๋ณ ์๊ณ์ด โ 2025โ04 ์ดํ FORECAST_END_YEARโ12๊น์ง ์์ธก.""" |
|
|
|
series = series.asfreq("MS") |
|
|
|
|
|
last_date = series.index[-1] |
|
end_date = dt.datetime(FORECAST_END_YEAR, 12, 1) |
|
horizon = (end_date.year - last_date.year) * 12 + (end_date.month - last_date.month) |
|
if horizon <= 0: |
|
return pd.Series(dtype=float) |
|
|
|
try: |
|
model = ExponentialSmoothing( |
|
series, |
|
trend="add", |
|
seasonal="mul", |
|
seasonal_periods=SEASONAL_PERIODS, |
|
initialization_method="estimated", |
|
) |
|
res = model.fit(optimized=True) |
|
fc = res.forecast(horizon) |
|
except Exception: |
|
|
|
growth = series.pct_change().fillna(0).mean() |
|
fc = pd.Series( |
|
[series.iloc[-1] * (1 + growth) ** i for i in range(1, horizon + 1)], |
|
index=pd.date_range( |
|
series.index[-1] + pd.DateOffset(months=1), |
|
periods=horizon, |
|
freq="MS", |
|
), |
|
) |
|
return fc |
|
|
|
|
|
FULL_SERIES = {} |
|
FORECASTS = {} |
|
for item in products: |
|
hist = pivot[item].dropna() |
|
fc = _fit_forecast(hist) |
|
FULL_SERIES[item] = pd.concat([hist, fc]) |
|
FORECASTS[item] = fc |
|
|
|
|
|
|
|
|
|
|
|
today = dt.date.today() |
|
tomorrow = today + dt.timedelta(days=1) |
|
|
|
def build_tomorrow_df() -> pd.DataFrame: |
|
"""๋ด์ผ(์ผ ๋จ์) ์์ ๊ฐ๊ฒฉ DataFrame ๋ฐํ.""" |
|
preds = {} |
|
for item, series in FULL_SERIES.items(): |
|
|
|
daily = series.resample("D").interpolate("linear") |
|
preds[item] = round(daily.loc[tomorrow], 2) if tomorrow in daily.index else np.nan |
|
return ( |
|
pd.DataFrame.from_dict(preds, orient="index", columns=[f"๋ด์ผ({tomorrow}) ์์๊ฐ(KRW)"]) |
|
.sort_index() |
|
) |
|
|
|
tomorrow_df = build_tomorrow_df() |
|
|
|
|
|
|
|
|
|
|
|
def plot_item(item: str): |
|
hist = pivot[item].dropna().asfreq("MS") |
|
fc = FORECASTS[item] |
|
|
|
fig = go.Figure() |
|
fig.add_trace(go.Scatter(x=hist.index, y=hist.values, mode="lines", name="Historical")) |
|
fig.add_trace(go.Scatter(x=fc.index, y=fc.values, mode="lines", name="Forecast")) |
|
fig.update_layout( |
|
title=f"{item} โ Monthly Avg Price (1996โ2025) & Forecast(2025โ04โ2030โ12)", |
|
xaxis_title="Date", |
|
yaxis_title="Price (KRW)", |
|
legend=dict(orientation="h", y=1.02, x=0.01), |
|
margin=dict(l=40, r=20, t=60, b=40), |
|
) |
|
return fig |
|
|
|
|
|
|
|
|
|
with gr.Blocks(title="๋๋งค ๊ฐ๊ฒฉ ์์ธกย App") as demo: |
|
gr.Markdown("## ๐ ๋๋งค ๊ฐ๊ฒฉ ์์ธก ๋์๋ณด๋ (1996โ2030)") |
|
|
|
|
|
item_dd = gr.Dropdown(products, value=products[0], label="ํ๋ชฉ ์ ํ") |
|
chart_out = gr.Plot(label="๊ฐ๊ฒฉ ์ถ์ธ") |
|
|
|
|
|
gr.Markdown(f"### ๋ด์ผ({tomorrow}) ๊ฐ ํ๋ชฉ ์์๊ฐ (KRW)") |
|
tomorrow_table = gr.Dataframe(tomorrow_df, interactive=False, height=400) |
|
|
|
def update_chart(product): |
|
return plot_item(product) |
|
|
|
item_dd.change(update_chart, inputs=item_dd, outputs=chart_out, queue=False) |
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
demo.launch() |
|
|