yokoha commited on
Commit
4fb476c
ยท
verified ยท
1 Parent(s): 4ff6bc2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +75 -72
app.py CHANGED
@@ -8,15 +8,20 @@ import seaborn as sns
8
  import matplotlib.pyplot as plt
9
  from datetime import date
10
 
11
- # --------------------------------------------------
12
- # 0. CONFIG & UTILS
13
- # --------------------------------------------------
14
- DATA_PATH = "price_data.csv" # โ–ถ๏ธŽ CSV: date(YYYY-MM-DD), item, price
15
-
 
 
 
 
 
 
 
16
  @st.cache_data(show_spinner=False)
17
  def load_data(path: str) -> pd.DataFrame:
18
- """Load & preprocess price data.
19
- Expects columns: date, item, price."""
20
  df = pd.read_csv(path, parse_dates=["date"])
21
  df.sort_values("date", inplace=True)
22
  return df
@@ -25,86 +30,83 @@ def load_data(path: str) -> pd.DataFrame:
25
  def get_items(df: pd.DataFrame):
26
  return sorted(df["item"].unique())
27
 
28
- # Prophet helper ------------------------------------------------------------
29
-
30
  def fit_prophet(df: pd.DataFrame, horizon_end: str):
31
- """Fit Prophet on df(date, price) and forecast till horizon_end (YYYY-MM-DD)."""
32
  m = Prophet(yearly_seasonality=True, weekly_seasonality=False, daily_seasonality=False)
33
  m.fit(df.rename(columns={"date": "ds", "price": "y"}))
34
  future = m.make_future_dataframe(periods=(pd.Timestamp(horizon_end) - df["date"].max()).days, freq="D")
35
  forecast = m.predict(future)
36
  return m, forecast
37
 
38
- # --------------------------------------------------
39
- # 1. DATA LOAD
40
- # --------------------------------------------------
41
- st.title("๐Ÿ“ˆ ํ’ˆ๋ชฉ๋ณ„ ๊ฐ€๊ฒฉ ์˜ˆ์ธก ๋Œ€์‹œ๋ณด๋“œ")
42
-
43
  raw_df = load_data(DATA_PATH)
44
-
45
- st.sidebar.header("๐Ÿ” ํ’ˆ๋ชฉ ์„ ํƒ")
46
- selected_item = st.sidebar.selectbox("ํ’ˆ๋ชฉ", get_items(raw_df))
47
-
48
  current_date = date.today()
49
- st.sidebar.markdown(f"**์˜ค๋Š˜ ๋‚ ์งœ:** {current_date}")
50
 
51
  item_df = raw_df[raw_df["item"] == selected_item].copy()
52
-
53
  if item_df.empty:
54
- st.warning("์„ ํƒํ•œ ํ’ˆ๋ชฉ์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
55
  st.stop()
56
 
57
- # --------------------------------------------------
58
- # 2. MACRO FORECAST 1996โ€“2030
59
- # --------------------------------------------------
60
- st.subheader(f"๐ŸŒ ๊ฑฐ์‹œ ๊ฐ€๊ฒฉ ์ถ”์ด ์˜ˆ์ธก: 1996โ€“2030 ({selected_item})")
61
-
62
- macro_start = "1996-01-01"
63
- macro_end = "2030-12-31"
64
- macro_df = item_df[item_df["date"] >= macro_start]
65
-
66
- m_macro, fc_macro = fit_prophet(macro_df, macro_end)
67
 
68
- fig_macro = px.line(fc_macro, x="ds", y="yhat", title="Macro Forecast (daily)")
 
69
  fig_macro.add_scatter(x=macro_df["date"], y=macro_df["price"], mode="lines", name="Actual")
70
  st.plotly_chart(fig_macro, use_container_width=True)
71
 
72
- # --------------------------------------------------
73
- # 3. MICRO FORECAST 2024โ€“2026 (์•„๋ž˜ ๋ฐฐ์น˜)
74
- # --------------------------------------------------
75
- st.subheader("๐Ÿ”Ž ๋ฏธ์‹œ ๊ฐ€๊ฒฉ ์ถ”์ด ์˜ˆ์ธก: 2024โ€“2026")
 
 
76
 
77
- micro_start = "2020-01-01" # ๋” ์ตœ๊ทผ ๋ฐ์ดํ„ฐ๋งŒ ํ•™์Šต
78
- micro_horizon_end = "2026-12-31"
79
- micro_df = item_df[item_df["date"] >= micro_start]
 
80
 
81
- m_micro, fc_micro = fit_prophet(micro_df, micro_horizon_end)
82
 
83
- fig_micro = px.line(fc_micro, x="ds", y="yhat", title="Micro Forecast (daily)")
 
84
  fig_micro.add_scatter(x=micro_df["date"], y=micro_df["price"], mode="lines", name="Actual")
85
  st.plotly_chart(fig_micro, use_container_width=True)
86
 
87
- # --------------------------------------------------
88
- # 4. SEASONALITY COMPONENTS
89
- # --------------------------------------------------
90
- st.subheader("๐Ÿ“† ์‹œ์ฆˆ๋„๋ฆฌํ‹ฐ ๋ถ„์„")
91
 
92
- with st.expander("์‹œ์ฆˆ๋„๋ฆฌํ‹ฐ ๊ทธ๋ž˜ํ”„ ์—ด๊ธฐ/๋‹ซ๊ธฐ"):
 
 
 
93
  comp_fig = m_micro.plot_components(fc_micro)
94
  st.pyplot(comp_fig)
95
- st.markdown("""
96
- **์„ค๋ช…**
97
- * **Yearly seasonality**: ๊ณ„์ ˆ์  ํŒจํ„ด(์˜ˆ: ์ˆ˜ํ™•๊ธฐยท๋ช…์ ˆ ์ˆ˜์š”)
98
- * **Trend**: ์žฅ๊ธฐ ์ถ”์„ธ.
99
- * ์ฃผ๊ฐ„ ์„ฑ๋ถ„์€ ์ƒ๋žตํ–ˆ์Šต๋‹ˆ๋‹ค(๊ฐ€๊ฒฉ ๋ฐ์ดํ„ฐ๊ฐ€ ์ฃผ๊ฐ„ granularity๊ฐ€ ์•„๋‹ˆ๋ฏ€๋กœ).
100
- """)
101
-
102
- # --------------------------------------------------
103
- # 5. CORRELATION HEATMAP (ํ’ˆ๋ชฉ ๊ฐ„)
104
- # --------------------------------------------------
 
 
 
105
  st.subheader("๐Ÿงฎ ํ’ˆ๋ชฉ ๊ฐ„ ์ƒ๊ด€๊ด€๊ณ„ ํžˆํŠธ๋งต")
106
 
107
- # ํ”ผ๋ฒ—: ์›”๊ฐ„ ํ‰๊ท  ๊ฐ€๊ฒฉ์œผ๋กœ ๋‹จ์œ„ ๋งž์ถ”๊ธฐ
108
  corr_df = (raw_df.assign(month=lambda d: d["date"].dt.to_period("M"))
109
  .groupby(["month", "item"], as_index=False)["price"].mean()
110
  .pivot(index="month", columns="item", values="price"))
@@ -118,25 +120,26 @@ st.pyplot(fig)
118
 
119
  st.markdown("""
120
  **ํ•ด์„ ๊ฐ€์ด๋“œ**
121
- - ๋นจ๊ฐ„์ƒ‰์€ ์–‘์˜ ์ƒ๊ด€ โ†’ ๋‘ ํ’ˆ๋ชฉ ๊ฐ€๊ฒฉ์ด ํ•จ๊ป˜ ์˜ค๋ฅด๋‚ด๋ฆผ.
122
- - ํŒŒ๋ž€์ƒ‰์€ ์Œ์˜ ์ƒ๊ด€ โ†’ ๋Œ€์ฒด์žฌ/์ˆ˜์š” ์ด๋™ ๊ฐ€๋Šฅ์„ฑ.
123
- - ์ ˆ๋Œ“๊ฐ’ โ‰ฅ 0.7 ์ธ ๊ด€๊ณ„๋Š” price elasticityยท์ˆ˜๊ธ‰ ์—ฐ๋™์„ฑ ๋ถ„์„์— ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
124
  """)
125
 
126
- # --------------------------------------------------
127
- # 6. EXTRA CHART: ๊ฐ€๊ฒฉ ๋ณ€๋™์„ฑ(rolling std)
128
- # --------------------------------------------------
129
- st.subheader("๐Ÿ“Š 30์ผ ์ด๋™ ํ‘œ์ค€ํŽธ์ฐจ โ€“ ๊ฐ€๊ฒฉ ๋ณ€๋™์„ฑ")
130
 
131
- vol_df = (item_df.set_index("date")["price"]
132
- .rolling(window=30)
133
- .std().reset_index(name="rolling_std"))
134
- fig_vol = px.area(vol_df, x="date", y="rolling_std", title="30D Rolling Std Dev")
135
  st.plotly_chart(fig_vol, use_container_width=True)
136
 
137
  st.markdown("""
138
- - **๋†’์€ ๋ณ€๋™์„ฑ ๊ตฌ๊ฐ„**์€ ์žฌ๊ณ ยท๊ณ„์•ฝ ์ „๋žต ์กฐ์ • ํ•„์š”.
139
- - ํŠนํžˆ ๋‚ ์”จยท์ˆ˜์š” ์ด๋ฒคํŠธ(๋ช…์ ˆ, ํญ์—ผ ๋“ฑ)์™€ ๊ฒน์น˜๋Š”์ง€ ๊ต์ฐจ ๋ถ„์„ํ•ด ๋ณด์„ธ์š”.
140
  """)
141
 
142
- st.success("โœ… ์‹œ๊ฐํ™” ์™„๋ฃŒ! ํ’ˆ๋ชฉ์„ ๋ฐ”๊ฟ”๋ณด๋ฉฐ ์ธ์‚ฌ์ดํŠธ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.")
 
 
 
 
8
  import matplotlib.pyplot as plt
9
  from datetime import date
10
 
11
+ # ---------------------------------------------
12
+ # CONFIG --------------------------------------
13
+ # ---------------------------------------------
14
+ DATA_PATH = "price_data.csv" # CSV: date, item, price
15
+ MACRO_START, MACRO_END = "1996-01-01", "2030-12-31"
16
+ MICRO_START, MICRO_END = "2020-01-01", "2026-12-31"
17
+
18
+ st.set_page_config(page_title="ํ’ˆ๋ชฉ๋ณ„ ๊ฐ€๊ฒฉ ์˜ˆ์ธก", page_icon="๐Ÿ“ˆ", layout="wide")
19
+
20
+ # ---------------------------------------------
21
+ # UTILITIES -----------------------------------
22
+ # ---------------------------------------------
23
  @st.cache_data(show_spinner=False)
24
  def load_data(path: str) -> pd.DataFrame:
 
 
25
  df = pd.read_csv(path, parse_dates=["date"])
26
  df.sort_values("date", inplace=True)
27
  return df
 
30
  def get_items(df: pd.DataFrame):
31
  return sorted(df["item"].unique())
32
 
33
+ @st.cache_data(show_spinner=False)
 
34
  def fit_prophet(df: pd.DataFrame, horizon_end: str):
 
35
  m = Prophet(yearly_seasonality=True, weekly_seasonality=False, daily_seasonality=False)
36
  m.fit(df.rename(columns={"date": "ds", "price": "y"}))
37
  future = m.make_future_dataframe(periods=(pd.Timestamp(horizon_end) - df["date"].max()).days, freq="D")
38
  forecast = m.predict(future)
39
  return m, forecast
40
 
41
+ # ---------------------------------------------
42
+ # LOAD ----------------------------------------
43
+ # ---------------------------------------------
 
 
44
  raw_df = load_data(DATA_PATH)
45
+ selected_item = st.sidebar.selectbox("๐Ÿ” ํ’ˆ๋ชฉ ์„ ํƒ", get_items(raw_df))
 
 
 
46
  current_date = date.today()
47
+ st.sidebar.write(f"**์˜ค๋Š˜:** {current_date}")
48
 
49
  item_df = raw_df[raw_df["item"] == selected_item].copy()
 
50
  if item_df.empty:
51
+ st.error("๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
52
  st.stop()
53
 
54
+ # ---------------------------------------------
55
+ # MACRO FORECAST ------------------------------
56
+ # ---------------------------------------------
57
+ st.header(f"๐Ÿ“ˆ {selected_item} ๊ฐ€๊ฒฉ ์ „๋ง")
58
+ macro_df = item_df[item_df["date"] >= MACRO_START]
 
 
 
 
 
59
 
60
+ m_macro, fc_macro = fit_prophet(macro_df, MACRO_END)
61
+ fig_macro = px.line(fc_macro, x="ds", y="yhat", title="Macro Forecast 1996โ€“2030")
62
  fig_macro.add_scatter(x=macro_df["date"], y=macro_df["price"], mode="lines", name="Actual")
63
  st.plotly_chart(fig_macro, use_container_width=True)
64
 
65
+ # --- Metrics โ†“
66
+ latest_price = macro_df.iloc[-1]["price"]
67
+ macro_last = fc_macro[fc_macro["ds"] == MACRO_END]["yhat"].iloc[0]
68
+ macro_diff = macro_last - latest_price
69
+ macro_pct = macro_diff / latest_price * 100
70
+ st.metric(label="2030 ์˜ˆ์ธก ๊ฐ€๊ฒฉ", value=f"{macro_last:,.0f}", delta=f"{macro_pct:+.1f}% vs ์ตœ๊ทผ")
71
 
72
+ # ---------------------------------------------
73
+ # MICRO FORECAST ------------------------------
74
+ # ---------------------------------------------
75
+ st.subheader("๐Ÿ”Ž ๋ฏธ์‹œ ์˜ˆ์ธก 2024โ€“2026")
76
 
77
+ micro_df = item_df[item_df["date"] >= MICRO_START]
78
 
79
+ m_micro, fc_micro = fit_prophet(micro_df, MICRO_END)
80
+ fig_micro = px.line(fc_micro, x="ds", y="yhat", title="Micro Forecast 2024โ€“2026")
81
  fig_micro.add_scatter(x=micro_df["date"], y=micro_df["price"], mode="lines", name="Actual")
82
  st.plotly_chart(fig_micro, use_container_width=True)
83
 
84
+ micro_last = fc_micro[fc_micro["ds"] == MICRO_END]["yhat"].iloc[0]
85
+ micro_diff = micro_last - latest_price
86
+ micro_pct = micro_diff / latest_price * 100
87
+ st.metric(label="2026 ์˜ˆ์ธก ๊ฐ€๊ฒฉ", value=f"{micro_last:,.0f}", delta=f"{micro_pct:+.1f}% vs ์ตœ๊ทผ")
88
 
89
+ # ---------------------------------------------
90
+ # PATTERN & SEASONALITY -----------------------
91
+ # ---------------------------------------------
92
+ with st.expander("๐Ÿ“† ์‹œ์ฆˆ๋„๋ฆฌํ‹ฐ ๋ถ„์„ ๋ฐ ํŒจํ„ด ํ•ด์„ค"):
93
  comp_fig = m_micro.plot_components(fc_micro)
94
  st.pyplot(comp_fig)
95
+
96
+ # ์›”๋ณ„ seasonality summary
97
+ month_season = (fc_micro[["ds", "yearly"]]
98
+ .assign(month=lambda d: d["ds"].dt.month)
99
+ .groupby("month")["yearly"].mean())
100
+ peak_month = int(month_season.idxmax())
101
+ trough_month = int(month_season.idxmin())
102
+
103
+ st.markdown(f"**ํŒจํ„ด ์š”์•ฝ** \n- __์—ฐ๊ฐ„ ํ”ผํฌ__: {peak_month}์›” \n- __์—ฐ๊ฐ„ ์ €์ __: {trough_month}์›” \n- ํ‰๊ท  ๋ณ€๋™ํญ(์—ฐ): {month_season.max() - month_season.min():.1f} ๋‹จ๊ฐ€")
104
+
105
+ # ---------------------------------------------
106
+ # CORRELATION HEATMAP -------------------------
107
+ # ---------------------------------------------
108
  st.subheader("๐Ÿงฎ ํ’ˆ๋ชฉ ๊ฐ„ ์ƒ๊ด€๊ด€๊ณ„ ํžˆํŠธ๋งต")
109
 
 
110
  corr_df = (raw_df.assign(month=lambda d: d["date"].dt.to_period("M"))
111
  .groupby(["month", "item"], as_index=False)["price"].mean()
112
  .pivot(index="month", columns="item", values="price"))
 
120
 
121
  st.markdown("""
122
  **ํ•ด์„ ๊ฐ€์ด๋“œ**
123
+ - **๋นจ๊ฐ„์ƒ‰(+)**: ๋‘ ํ’ˆ๋ชฉ ๊ฐ€๊ฒฉ์ด ๋™์กฐํ™” โ€“ ๊ณต๊ธ‰๋ง/์ˆ˜์š” ์—ฐ๋™ ๊ฐ€๋Šฅ์„ฑ.
124
+ - **ํŒŒ๋ž€์ƒ‰(-)**: ๋Œ€์ฒด์žฌ ๊ด€๊ณ„.
125
+ - ์ ˆ๋Œ“๊ฐ’ โ‰ฅ 0.7 ์€ ์ •์ฑ…ยท์žฌ๊ณ  ์ „๋žต ์„ค๊ณ„ ์‹œ ์ฃผ์˜ ๊นŠ๊ฒŒ ๋ณผ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
126
  """)
127
 
128
+ # ---------------------------------------------
129
+ # EXTRA CHARTS -------------------------------
130
+ # ---------------------------------------------
131
+ st.subheader("๐Ÿ“Š ์ถ”๊ฐ€ ์ธ์‚ฌ์ดํŠธ: 30์ผ ์ด๋™ ๋ณ€๋™์„ฑ")
132
 
133
+ vol_df = (item_df.set_index("date")["price"].rolling(30).std().reset_index())
134
+ fig_vol = px.area(vol_df, x="date", y="price", title="30D Rolling Std Dev")
 
 
135
  st.plotly_chart(fig_vol, use_container_width=True)
136
 
137
  st.markdown("""
138
+ - ๋ณ€๋™์„ฑ ๊ธ‰๋“ฑ ๊ตฌ๊ฐ„์€ **๊ณต๊ธ‰ ์ถฉ๊ฒฉยท์ˆ˜์š” ์ด๋ฒคํŠธ** ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.
139
+ - ์ตœ๊ทผ ๋ณ€๋™์„ฑ์ด ๋‚ฎ์•„์ง€๋ฉด **๊ฐ€๊ฒฉ ์•ˆ์ฐฉ**์œผ๋กœ ํ•ด์„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
140
  """)
141
 
142
+ # ---------------------------------------------
143
+ # FOOTER --------------------------------------
144
+ # ---------------------------------------------
145
+ st.caption("๋ฐ์ดํ„ฐ ์ถœ์ฒ˜: ๋‚ด๋ถ€ ๋†์ˆ˜์‚ฐ๋ฌผ ๊ฐ€๊ฒฉ DB ยท Forecast by Prophet ยท Dashboard built with Streamlit")