Spaces:
Running
Running
import streamlit as st | |
import pandas as pd | |
import numpy as np | |
from prophet import Prophet | |
import plotly.express as px | |
import plotly.graph_objects as go | |
import seaborn as sns | |
import matplotlib.pyplot as plt | |
from datetime import date | |
# -------------------------------------------------- | |
# 0. CONFIG & UTILS | |
# -------------------------------------------------- | |
DATA_PATH = "price_data.csv" # โถ๏ธ CSV: date(YYYY-MM-DD), item, price | |
def load_data(path: str) -> pd.DataFrame: | |
"""Load & preprocess price data. | |
Expects columns: date, item, price.""" | |
df = pd.read_csv(path, parse_dates=["date"]) | |
df.sort_values("date", inplace=True) | |
return df | |
def get_items(df: pd.DataFrame): | |
return sorted(df["item"].unique()) | |
# Prophet helper ------------------------------------------------------------ | |
def fit_prophet(df: pd.DataFrame, horizon_end: str): | |
"""Fit Prophet on df(date, price) and forecast till horizon_end (YYYY-MM-DD).""" | |
m = Prophet(yearly_seasonality=True, weekly_seasonality=False, daily_seasonality=False) | |
m.fit(df.rename(columns={"date": "ds", "price": "y"})) | |
future = m.make_future_dataframe(periods=(pd.Timestamp(horizon_end) - df["date"].max()).days, freq="D") | |
forecast = m.predict(future) | |
return m, forecast | |
# -------------------------------------------------- | |
# 1. DATA LOAD | |
# -------------------------------------------------- | |
st.title("๐ ํ๋ชฉ๋ณ ๊ฐ๊ฒฉ ์์ธก ๋์๋ณด๋") | |
raw_df = load_data(DATA_PATH) | |
st.sidebar.header("๐ ํ๋ชฉ ์ ํ") | |
selected_item = st.sidebar.selectbox("ํ๋ชฉ", get_items(raw_df)) | |
current_date = date.today() | |
st.sidebar.markdown(f"**์ค๋ ๋ ์ง:** {current_date}") | |
item_df = raw_df[raw_df["item"] == selected_item].copy() | |
if item_df.empty: | |
st.warning("์ ํํ ํ๋ชฉ์ ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.") | |
st.stop() | |
# -------------------------------------------------- | |
# 2. MACRO FORECAST 1996โ2030 | |
# -------------------------------------------------- | |
st.subheader(f"๐ ๊ฑฐ์ ๊ฐ๊ฒฉ ์ถ์ด ์์ธก: 1996โ2030 ({selected_item})") | |
macro_start = "1996-01-01" | |
macro_end = "2030-12-31" | |
macro_df = item_df[item_df["date"] >= macro_start] | |
m_macro, fc_macro = fit_prophet(macro_df, macro_end) | |
fig_macro = px.line(fc_macro, x="ds", y="yhat", title="Macro Forecast (daily)") | |
fig_macro.add_scatter(x=macro_df["date"], y=macro_df["price"], mode="lines", name="Actual") | |
st.plotly_chart(fig_macro, use_container_width=True) | |
# -------------------------------------------------- | |
# 3. MICRO FORECAST 2024โ2026 (์๋ ๋ฐฐ์น) | |
# -------------------------------------------------- | |
st.subheader("๐ ๋ฏธ์ ๊ฐ๊ฒฉ ์ถ์ด ์์ธก: 2024โ2026") | |
micro_start = "2020-01-01" # ๋ ์ต๊ทผ ๋ฐ์ดํฐ๋ง ํ์ต | |
micro_horizon_end = "2026-12-31" | |
micro_df = item_df[item_df["date"] >= micro_start] | |
m_micro, fc_micro = fit_prophet(micro_df, micro_horizon_end) | |
fig_micro = px.line(fc_micro, x="ds", y="yhat", title="Micro Forecast (daily)") | |
fig_micro.add_scatter(x=micro_df["date"], y=micro_df["price"], mode="lines", name="Actual") | |
st.plotly_chart(fig_micro, use_container_width=True) | |
# -------------------------------------------------- | |
# 4. SEASONALITY COMPONENTS | |
# -------------------------------------------------- | |
st.subheader("๐ ์์ฆ๋๋ฆฌํฐ ๋ถ์") | |
with st.expander("์์ฆ๋๋ฆฌํฐ ๊ทธ๋ํ ์ด๊ธฐ/๋ซ๊ธฐ"): | |
comp_fig = m_micro.plot_components(fc_micro) | |
st.pyplot(comp_fig) | |
st.markdown(""" | |
**์ค๋ช ** | |
* **Yearly seasonality**: ๊ณ์ ์ ํจํด(์: ์ํ๊ธฐยท๋ช ์ ์์) | |
* **Trend**: ์ฅ๊ธฐ ์ถ์ธ. | |
* ์ฃผ๊ฐ ์ฑ๋ถ์ ์๋ตํ์ต๋๋ค(๊ฐ๊ฒฉ ๋ฐ์ดํฐ๊ฐ ์ฃผ๊ฐ granularity๊ฐ ์๋๋ฏ๋ก). | |
""") | |
# -------------------------------------------------- | |
# 5. CORRELATION HEATMAP (ํ๋ชฉ ๊ฐ) | |
# -------------------------------------------------- | |
st.subheader("๐งฎ ํ๋ชฉ ๊ฐ ์๊ด๊ด๊ณ ํํธ๋งต") | |
# ํผ๋ฒ: ์๊ฐ ํ๊ท ๊ฐ๊ฒฉ์ผ๋ก ๋จ์ ๋ง์ถ๊ธฐ | |
corr_df = (raw_df.assign(month=lambda d: d["date"].dt.to_period("M")) | |
.groupby(["month", "item"], as_index=False)["price"].mean() | |
.pivot(index="month", columns="item", values="price")) | |
corr = corr_df.corr() | |
fig, ax = plt.subplots(figsize=(12, 10)) | |
mask = np.triu(np.ones_like(corr, dtype=bool)) | |
sns.heatmap(corr, mask=mask, cmap="RdBu_r", center=0, linewidths=.5, ax=ax) | |
st.pyplot(fig) | |
st.markdown(""" | |
**ํด์ ๊ฐ์ด๋** | |
- ๋นจ๊ฐ์์ ์์ ์๊ด โ ๋ ํ๋ชฉ ๊ฐ๊ฒฉ์ด ํจ๊ป ์ค๋ฅด๋ด๋ฆผ. | |
- ํ๋์์ ์์ ์๊ด โ ๋์ฒด์ฌ/์์ ์ด๋ ๊ฐ๋ฅ์ฑ. | |
- ์ ๋๊ฐ โฅ 0.7 ์ธ ๊ด๊ณ๋ price elasticityยท์๊ธ ์ฐ๋์ฑ ๋ถ์์ ํ์ฉํ ์ ์์ต๋๋ค. | |
""") | |
# -------------------------------------------------- | |
# 6. EXTRA CHART: ๊ฐ๊ฒฉ ๋ณ๋์ฑ(rolling std) | |
# -------------------------------------------------- | |
st.subheader("๐ 30์ผ ์ด๋ ํ์คํธ์ฐจ โ ๊ฐ๊ฒฉ ๋ณ๋์ฑ") | |
vol_df = (item_df.set_index("date")["price"] | |
.rolling(window=30) | |
.std().reset_index(name="rolling_std")) | |
fig_vol = px.area(vol_df, x="date", y="rolling_std", title="30D Rolling Std Dev") | |
st.plotly_chart(fig_vol, use_container_width=True) | |
st.markdown(""" | |
- **๋์ ๋ณ๋์ฑ ๊ตฌ๊ฐ**์ ์ฌ๊ณ ยท๊ณ์ฝ ์ ๋ต ์กฐ์ ํ์. | |
- ํนํ ๋ ์จยท์์ ์ด๋ฒคํธ(๋ช ์ , ํญ์ผ ๋ฑ)์ ๊ฒน์น๋์ง ๊ต์ฐจ ๋ถ์ํด ๋ณด์ธ์. | |
""") | |
st.success("โ ์๊ฐํ ์๋ฃ! ํ๋ชฉ์ ๋ฐ๊ฟ๋ณด๋ฉฐ ์ธ์ฌ์ดํธ๋ฅผ ํ์ธํ์ธ์.") |