NH-Prediction / app.py
yokoha's picture
Update app.py
dc2be38 verified
raw
history blame
5.35 kB
import streamlit as st
import pandas as pd
import numpy as np
from prophet import Prophet
import plotly.express as px
import seaborn as sns
import matplotlib.pyplot as plt
from datetime import date
from pathlib import Path
# -------------------------------------------------
# CONFIG ------------------------------------------
# -------------------------------------------------
CSV_PATH = Path("price_data.csv")
PARQUET_PATH = Path("domae-202503.parquet") # 1996โ€‘1993-03 ๊ฐ€๊ฒฉ ๋ฐ์ดํ„ฐ
MACRO_START, MACRO_END = "1996-01-01", "2030-12-31"
MICRO_START, MICRO_END = "2020-01-01", "2026-12-31"
st.set_page_config(page_title="ํ’ˆ๋ชฉ๋ณ„ ๊ฐ€๊ฒฉ ์˜ˆ์ธก", page_icon="๐Ÿ“ˆ", layout="wide")
# -------------------------------------------------
# UTILITIES ---------------------------------------
# -------------------------------------------------
@st.cache_data(show_spinner=False)
def load_data() -> pd.DataFrame:
"""Load price data from Parquet if available, else CSV."""
if PARQUET_PATH.exists():
df = pd.read_parquet(PARQUET_PATH)
elif CSV_PATH.exists():
df = pd.read_csv(CSV_PATH)
else:
st.error("๋ฐ์ดํ„ฐ ํŒŒ์ผ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. price_data.csv ๋˜๋Š” domae-202503.parquet" )
st.stop()
# ํ‘œ์ค€ํ™”
df["date"] = pd.to_datetime(df["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())
@st.cache_data(show_spinner=False)
def fit_prophet(df: pd.DataFrame, horizon_end: str):
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
# -------------------------------------------------
# LOAD DATA ---------------------------------------
# -------------------------------------------------
raw_df = load_data()
st.sidebar.header("๐Ÿ” ํ’ˆ๋ชฉ ์„ ํƒ")
selected_item = st.sidebar.selectbox("ํ’ˆ๋ชฉ", get_items(raw_df))
current_date = date.today()
st.sidebar.caption(f"์˜ค๋Š˜: {current_date}")
item_df = raw_df.query("item == @selected_item").copy()
if item_df.empty:
st.error("์„ ํƒํ•œ ํ’ˆ๋ชฉ ๋ฐ์ดํ„ฐ ์—†์Œ")
st.stop()
# -------------------------------------------------
# PLOTS -------------------------------------------
# -------------------------------------------------
st.header(f"๐Ÿ“ˆ {selected_item} ๊ฐ€๊ฒฉ ์˜ˆ์ธก ๋Œ€์‹œ๋ณด๋“œ")
# Macro forecast 1996โ€“2030
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 1996โ€“2030")
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)
latest_price = macro_df.iloc[-1]["price"]
macro_pred = fc_macro.loc[fc_macro["ds"] == MACRO_END, "yhat"].iloc[0]
macro_pct = (macro_pred - latest_price) / latest_price * 100
st.metric("2030 ์˜ˆ์ธก๊ฐ€", f"{macro_pred:,.0f}", f"{macro_pct:+.1f}%")
# Micro forecast 2024โ€“2026
st.subheader("๐Ÿ”Ž 2024โ€“2026 ๋‹จ๊ธฐ ์˜ˆ์ธก")
micro_df = item_df[item_df["date"] >= MICRO_START]
m_micro, fc_micro = fit_prophet(micro_df, MICRO_END)
fig_micro = px.line(fc_micro, x="ds", y="yhat", title="Micro Forecast 2024โ€“2026")
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)
micro_pred = fc_micro.loc[fc_micro["ds"] == MICRO_END, "yhat"].iloc[0]
micro_pct = (micro_pred - latest_price) / latest_price * 100
st.metric("2026 ์˜ˆ์ธก๊ฐ€", f"{micro_pred:,.0f}", f"{micro_pct:+.1f}%")
# Seasonality components
with st.expander("๐Ÿ“† ์‹œ์ฆˆ๋„๋ฆฌํ‹ฐ & ํŒจํ„ด ์„ค๋ช…"):
comp_fig = m_micro.plot_components(fc_micro)
st.pyplot(comp_fig)
month_season = (fc_micro[["ds", "yearly"]]
.assign(month=lambda d: d.ds.dt.month)
.groupby("month")["yearly"].mean())
st.markdown(
f"**์—ฐ๊ฐ„ ํ”ผํฌ ์›”:** {int(month_season.idxmax())}์›”\n\n"
f"**์—ฐ๊ฐ„ ์ €์  ์›”:** {int(month_season.idxmin())}์›”\n\n"
f"**์—ฐ๊ฐ„ ๋ณ€๋™ํญ:** {month_season.max() - month_season.min():.1f}")
# Correlation heatmap
st.subheader("๐Ÿงฎ ํ’ˆ๋ชฉ ๊ฐ„ ์ƒ๊ด€๊ด€๊ณ„")
monthly_pivot = (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 = monthly_pivot.corr()
mask = np.triu(np.ones_like(corr, dtype=bool))
fig, ax = plt.subplots(figsize=(12, 10))
sns.heatmap(corr, mask=mask, cmap="RdBu_r", center=0, linewidths=.5, ax=ax)
st.pyplot(fig)
st.info("๋นจ๊ฐ„ ์˜์—ญ: ๊ฐ€๊ฒฉ ๋™์กฐํ™” / ํŒŒ๋ž€ ์˜์—ญ: ๋Œ€์ฒด์žฌ ๊ฐ€๋Šฅ์„ฑ.")
# Volatility Chart
st.subheader("๐Ÿ“Š 30์ผ ์ด๋™ ํ‘œ์ค€ํŽธ์ฐจ (๊ฐ€๊ฒฉ ๋ณ€๋™์„ฑ)")
vol = item_df.set_index("date")["price"].rolling(30).std().dropna().reset_index()
fig_vol = px.area(vol, x="date", y="price", title="Rolling 30D Std Dev")
st.plotly_chart(fig_vol, use_container_width=True)
st.caption("๋ฐ์ดํ„ฐ: domae-202503.parquet ยท Prophet ์˜ˆ์ธก ยท Streamlit ๋Œ€์‹œ๋ณด๋“œ")