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 @st.cache_data(show_spinner=False) 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 @st.cache_data(show_spinner=False) 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("โœ… ์‹œ๊ฐํ™” ์™„๋ฃŒ! ํ’ˆ๋ชฉ์„ ๋ฐ”๊ฟ”๋ณด๋ฉฐ ์ธ์‚ฌ์ดํŠธ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.")