yokoha commited on
Commit
dd6e62d
ยท
verified ยท
1 Parent(s): c2cf641

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +120 -64
app.py CHANGED
@@ -53,7 +53,7 @@ def _standardize_columns(df: pd.DataFrame) -> pd.DataFrame:
53
  df.rename(columns={df.columns[0]: "date"}, inplace=True)
54
 
55
  # โ”€โ”€ convert YYYYMM string to datetime โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
56
- if "date" in df.columns and pd.api.types.is_object_dtype(df["date" ]):
57
  sample = str(df["date"].iloc[0])
58
  if sample.isdigit() and len(sample) in (6, 8):
59
  df["date"] = pd.to_datetime(df["date"].astype(str).str[:6], format="%Y%m", errors="coerce")
@@ -101,10 +101,26 @@ def get_items(df: pd.DataFrame):
101
 
102
  @st.cache_data(show_spinner=False)
103
  def fit_prophet(df: pd.DataFrame, horizon_end: str):
 
 
 
 
 
 
 
 
 
 
 
 
104
  m = Prophet(yearly_seasonality=True, weekly_seasonality=False, daily_seasonality=False)
105
- m.fit(df.rename(columns={"date": "ds", "price": "y"}))
106
- periods = (pd.Timestamp(horizon_end) - df["date"].max()).days
 
 
107
  future = m.make_future_dataframe(periods=periods, freq="D")
 
 
108
  forecast = m.predict(future)
109
  return m, forecast
110
 
@@ -127,85 +143,125 @@ if item_df.empty:
127
  # MACRO FORECAST 1996โ€‘2030 ------------------------
128
  # -------------------------------------------------
129
  st.header(f"๐Ÿ“ˆ {selected_item} ๊ฐ€๊ฒฉ ์˜ˆ์ธก ๋Œ€์‹œ๋ณด๋“œ")
130
- macro_df = item_df[item_df["date"] >= MACRO_START]
131
-
132
- m_macro, fc_macro = fit_prophet(macro_df, MACRO_END)
133
- fig_macro = px.line(fc_macro, x="ds", y="yhat", title="Macro Forecast 1996โ€“2030")
134
- fig_macro.add_scatter(x=macro_df["date"], y=macro_df["price"], mode="lines", name="Actual")
135
- st.plotly_chart(fig_macro, use_container_width=True)
136
-
137
- latest_price = macro_df.iloc[-1]["price"]
138
- macro_pred = fc_macro.loc[fc_macro["ds"] == MACRO_END, "yhat"].iloc[0]
139
- macro_pct = (macro_pred - latest_price) / latest_price * 100
140
- st.metric("2030 ์˜ˆ์ธก๊ฐ€", f"{macro_pred:,.0f}", f"{macro_pct:+.1f}%")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
  # -------------------------------------------------
143
  # MICRO FORECAST 2024โ€‘2026 ------------------------
144
  # -------------------------------------------------
145
  st.subheader("๐Ÿ”Ž 2024โ€“2026 ๋‹จ๊ธฐ ์˜ˆ์ธก")
146
 
147
- micro_df = item_df[item_df["date"] >= MICRO_START]
148
- m_micro, fc_micro = fit_prophet(micro_df, MICRO_END)
149
- fig_micro = px.line(fc_micro, x="ds", y="yhat", title="Micro Forecast 2024โ€“2026")
150
- fig_micro.add_scatter(x=micro_df["date"], y=micro_df["price"], mode="lines", name="Actual")
151
- st.plotly_chart(fig_micro, use_container_width=True)
152
-
153
- micro_pred = fc_micro.loc[fc_micro["ds"] == MICRO_END, "yhat"].iloc[0]
154
- micro_pct = (micro_pred - latest_price) / latest_price * 100
155
- st.metric("2026 ์˜ˆ์ธก๊ฐ€", f"{micro_pred:,.0f}", f"{micro_pct:+.1f}%")
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
  # -------------------------------------------------
158
  # SEASONALITY & PATTERN ---------------------------
159
  # -------------------------------------------------
160
  with st.expander("๐Ÿ“† ์‹œ์ฆˆ๋„๋ฆฌํ‹ฐ & ํŒจํ„ด ์„ค๋ช…"):
161
- comp_fig = m_micro.plot_components(fc_micro)
162
- st.pyplot(comp_fig)
163
-
164
- month_season = (fc_micro[["ds", "yearly"]]
165
- .assign(month=lambda d: d.ds.dt.month)
166
- .groupby("month")["yearly"].mean())
167
- st.markdown(
168
- f"**์—ฐ๊ฐ„ ํ”ผํฌ ์›”:** {int(month_season.idxmax())}์›” \n"
169
- f"**์—ฐ๊ฐ„ ์ €์  ์›”:** {int(month_season.idxmin())}์›” \n"
170
- f"**์—ฐ๊ฐ„ ๋ณ€๋™ํญ:** {month_season.max() - month_season.min():.1f}")
 
 
 
171
 
172
- # -------------------------------------------------
173
- # CORRELATION HEATMAP -----------------------------
174
- # -------------------------------------------------
175
  # -------------------------------------------------
176
  # CORRELATION HEATMAP -----------------------------
177
  # -------------------------------------------------
178
  st.subheader("๐Ÿงฎ ํ’ˆ๋ชฉ ๊ฐ„ ์ƒ๊ด€๊ด€๊ณ„")
179
- monthly_pivot = (raw_df.assign(month=lambda d: d.date.dt.to_period("M"))
180
- .groupby(["month", "item"], as_index=False)["price"].mean()
181
- .pivot(index="month", columns="item", values="price"))
182
-
183
- corr = monthly_pivot.corr()
184
- fig, ax = plt.subplots(figsize=(12, 10))
185
- mask = np.triu(np.ones_like(corr, dtype=bool))
186
- sns.heatmap(corr, mask=mask, annot=False, cmap="coolwarm", center=0,
187
- square=True, linewidths=.5, cbar_kws={"shrink": .5})
188
-
189
- # Highlight correlations with selected item
190
- if selected_item in corr.columns:
191
- item_corr = corr[selected_item].sort_values(ascending=False)
192
- top_corr = item_corr.drop(selected_item).head(5)
193
- bottom_corr = item_corr.drop(selected_item).tail(5)
194
-
195
- col1, col2 = st.columns(2)
196
- with col1:
197
- st.markdown(f"**{selected_item}์™€ ์ƒ๊ด€๊ด€๊ณ„ ๋†’์€ ํ’ˆ๋ชฉ**")
198
- for item, val in top_corr.items():
199
- st.write(f"{item}: {val:.2f}")
200
- with col2:
201
- st.markdown(f"**{selected_item}์™€ ์ƒ๊ด€๊ด€๊ณ„ ๋‚ฎ์€ ํ’ˆ๋ชฉ**")
202
- for item, val in bottom_corr.items():
203
- st.write(f"{item}: {val:.2f}")
204
-
205
- st.pyplot(fig)
 
 
 
 
 
 
206
 
207
  # -------------------------------------------------
208
  # FOOTER ------------------------------------------
209
  # -------------------------------------------------
210
  st.markdown("---")
211
- st.caption("ยฉ 2025 ํ’ˆ๋ชฉ๋ณ„ ๊ฐ€๊ฒฉ ์˜ˆ์ธก ์‹œ์Šคํ…œ | ๋ฐ์ดํ„ฐ ๋ถ„์„ ์ž๋™ํ™”")
 
53
  df.rename(columns={df.columns[0]: "date"}, inplace=True)
54
 
55
  # โ”€โ”€ convert YYYYMM string to datetime โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
56
+ if "date" in df.columns and pd.api.types.is_object_dtype(df["date"]): # Fixed typo here
57
  sample = str(df["date"].iloc[0])
58
  if sample.isdigit() and len(sample) in (6, 8):
59
  df["date"] = pd.to_datetime(df["date"].astype(str).str[:6], format="%Y%m", errors="coerce")
 
101
 
102
  @st.cache_data(show_spinner=False)
103
  def fit_prophet(df: pd.DataFrame, horizon_end: str):
104
+ # Make a copy and ensure we have data
105
+ df = df.copy()
106
+ df = df.dropna(subset=["date", "price"])
107
+
108
+ if len(df) < 2:
109
+ st.warning("๋ฐ์ดํ„ฐ ํฌ์ธํŠธ๊ฐ€ ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ์ธก์„ ์œ„ํ•ด์„œ๋Š” ์ตœ์†Œ 2๊ฐœ ์ด์ƒ์˜ ์œ ํšจ ๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.")
110
+ return None, None
111
+
112
+ # Convert to Prophet format
113
+ prophet_df = df.rename(columns={"date": "ds", "price": "y"})
114
+
115
+ # Fit the model
116
  m = Prophet(yearly_seasonality=True, weekly_seasonality=False, daily_seasonality=False)
117
+ m.fit(prophet_df)
118
+
119
+ # Generate future dates
120
+ periods = max((pd.Timestamp(horizon_end) - df["date"].max()).days, 1)
121
  future = m.make_future_dataframe(periods=periods, freq="D")
122
+
123
+ # Make predictions
124
  forecast = m.predict(future)
125
  return m, forecast
126
 
 
143
  # MACRO FORECAST 1996โ€‘2030 ------------------------
144
  # -------------------------------------------------
145
  st.header(f"๐Ÿ“ˆ {selected_item} ๊ฐ€๊ฒฉ ์˜ˆ์ธก ๋Œ€์‹œ๋ณด๋“œ")
146
+ macro_df = item_df[item_df["date"] >= MACRO_START].copy()
147
+
148
+ # Add diagnostic info
149
+ with st.expander("๋ฐ์ดํ„ฐ ์ง„๋‹จ"):
150
+ st.write(f"- ์ „์ฒด ๋ฐ์ดํ„ฐ ์ˆ˜: {len(item_df)}")
151
+ st.write(f"- {MACRO_START} ์ดํ›„ ๋ฐ์ดํ„ฐ ์ˆ˜: {len(macro_df)}")
152
+ st.write(f"- ๊ธฐ๊ฐ„: {macro_df['date'].min()} ~ {macro_df['date'].max()}")
153
+ st.write(macro_df.head())
154
+
155
+ if len(macro_df) < 2:
156
+ st.warning(f"{MACRO_START} ์ดํ›„ ๋ฐ์ดํ„ฐ๊ฐ€ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ „์ฒด ๊ธฐ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.")
157
+ fig = px.line(item_df, x="date", y="price", title=f"{selected_item} ๊ณผ๊ฑฐ ๊ฐ€๊ฒฉ")
158
+ st.plotly_chart(fig, use_container_width=True)
159
+ else:
160
+ try:
161
+ m_macro, fc_macro = fit_prophet(macro_df, MACRO_END)
162
+ if m_macro is not None and fc_macro is not None:
163
+ fig_macro = px.line(fc_macro, x="ds", y="yhat", title="Macro Forecast 1996โ€“2030")
164
+ fig_macro.add_scatter(x=macro_df["date"], y=macro_df["price"], mode="lines", name="Actual")
165
+ st.plotly_chart(fig_macro, use_container_width=True)
166
+
167
+ latest_price = macro_df.iloc[-1]["price"]
168
+ macro_pred = fc_macro.loc[fc_macro["ds"] == MACRO_END, "yhat"].iloc[0]
169
+ macro_pct = (macro_pred - latest_price) / latest_price * 100
170
+ st.metric("2030 ์˜ˆ์ธก๊ฐ€", f"{macro_pred:,.0f}", f"{macro_pct:+.1f}%")
171
+ else:
172
+ st.warning("์˜ˆ์ธก ๋ชจ๋ธ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
173
+ fig = px.line(item_df, x="date", y="price", title=f"{selected_item} ๊ณผ๊ฑฐ ๊ฐ€๊ฒฉ")
174
+ st.plotly_chart(fig, use_container_width=True)
175
+ except Exception as e:
176
+ st.error(f"์˜ค๋ฅ˜ ๋ฐœ์ƒ: {str(e)}")
177
+ fig = px.line(item_df, x="date", y="price", title=f"{selected_item} ๊ณผ๊ฑฐ ๊ฐ€๊ฒฉ")
178
+ st.plotly_chart(fig, use_container_width=True)
179
 
180
  # -------------------------------------------------
181
  # MICRO FORECAST 2024โ€‘2026 ------------------------
182
  # -------------------------------------------------
183
  st.subheader("๐Ÿ”Ž 2024โ€“2026 ๋‹จ๊ธฐ ์˜ˆ์ธก")
184
 
185
+ micro_df = item_df[item_df["date"] >= MICRO_START].copy()
186
+ if len(micro_df) < 2:
187
+ st.warning(f"{MICRO_START} ์ดํ›„ ๋ฐ์ดํ„ฐ๊ฐ€ ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.")
188
+ fig = px.line(item_df, x="date", y="price", title=f"{selected_item} ์ตœ๊ทผ ๊ฐ€๊ฒฉ")
189
+ st.plotly_chart(fig, use_container_width=True)
190
+ else:
191
+ try:
192
+ m_micro, fc_micro = fit_prophet(micro_df, MICRO_END)
193
+ if m_micro is not None and fc_micro is not None:
194
+ fig_micro = px.line(fc_micro, x="ds", y="yhat", title="Micro Forecast 2024โ€“2026")
195
+ fig_micro.add_scatter(x=micro_df["date"], y=micro_df["price"], mode="lines", name="Actual")
196
+ st.plotly_chart(fig_micro, use_container_width=True)
197
+
198
+ latest_price = micro_df.iloc[-1]["price"]
199
+ micro_pred = fc_micro.loc[fc_micro["ds"] == MICRO_END, "yhat"].iloc[0]
200
+ micro_pct = (micro_pred - latest_price) / latest_price * 100
201
+ st.metric("2026 ์˜ˆ์ธก๊ฐ€", f"{micro_pred:,.0f}", f"{micro_pct:+.1f}%")
202
+ else:
203
+ st.warning("๋‹จ๊ธฐ ์˜ˆ์ธก ๋ชจ๋ธ์„ ์ƒ์„ฑํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
204
+ except Exception as e:
205
+ st.error(f"๋‹จ๊ธฐ ์˜ˆ์ธก ์˜ค๋ฅ˜: {str(e)}")
206
 
207
  # -------------------------------------------------
208
  # SEASONALITY & PATTERN ---------------------------
209
  # -------------------------------------------------
210
  with st.expander("๐Ÿ“† ์‹œ์ฆˆ๋„๋ฆฌํ‹ฐ & ํŒจํ„ด ์„ค๋ช…"):
211
+ if 'm_micro' in locals() and m_micro is not None and 'fc_micro' in locals() and fc_micro is not None:
212
+ comp_fig = m_micro.plot_components(fc_micro)
213
+ st.pyplot(comp_fig)
214
+
215
+ month_season = (fc_micro[["ds", "yearly"]]
216
+ .assign(month=lambda d: d.ds.dt.month)
217
+ .groupby("month")["yearly"].mean())
218
+ st.markdown(
219
+ f"**์—ฐ๊ฐ„ ํ”ผํฌ ์›”:** {int(month_season.idxmax())}์›” \n"
220
+ f"**์—ฐ๊ฐ„ ์ €์  ์›”:** {int(month_season.idxmin())}์›” \n"
221
+ f"**์—ฐ๊ฐ„ ๋ณ€๋™ํญ:** {month_season.max() - month_season.min():.1f}")
222
+ else:
223
+ st.info("ํŒจํ„ด ๋ถ„์„์„ ์œ„ํ•œ ์ถฉ๋ถ„ํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
224
 
 
 
 
225
  # -------------------------------------------------
226
  # CORRELATION HEATMAP -----------------------------
227
  # -------------------------------------------------
228
  st.subheader("๐Ÿงฎ ํ’ˆ๋ชฉ ๊ฐ„ ์ƒ๊ด€๊ด€๊ณ„")
229
+ try:
230
+ monthly_pivot = (raw_df.assign(month=lambda d: d.date.dt.to_period("M"))
231
+ .groupby(["month", "item"], as_index=False)["price"].mean()
232
+ .pivot(index="month", columns="item", values="price"))
233
+
234
+ if monthly_pivot.shape[1] > 1: # At least 2 items needed for correlation
235
+ corr = monthly_pivot.corr()
236
+ fig, ax = plt.subplots(figsize=(12, 10))
237
+ mask = np.triu(np.ones_like(corr, dtype=bool))
238
+ sns.heatmap(corr, mask=mask, annot=False, cmap="coolwarm", center=0,
239
+ square=True, linewidths=.5, cbar_kws={"shrink": .5})
240
+
241
+ # Highlight correlations with selected item
242
+ if selected_item in corr.columns:
243
+ item_corr = corr[selected_item].sort_values(ascending=False)
244
+ top_corr = item_corr.drop(selected_item).head(5)
245
+ bottom_corr = item_corr.drop(selected_item).tail(5)
246
+
247
+ col1, col2 = st.columns(2)
248
+ with col1:
249
+ st.markdown(f"**{selected_item}์™€ ์ƒ๊ด€๊ด€๊ณ„ ๋†’์€ ํ’ˆ๋ชฉ**")
250
+ for item, val in top_corr.items():
251
+ st.write(f"{item}: {val:.2f}")
252
+ with col2:
253
+ st.markdown(f"**{selected_item}์™€ ์ƒ๊ด€๊ด€๊ณ„ ๋‚ฎ์€ ํ’ˆ๋ชฉ**")
254
+ for item, val in bottom_corr.items():
255
+ st.write(f"{item}: {val:.2f}")
256
+
257
+ st.pyplot(fig)
258
+ else:
259
+ st.info("์ƒ๊ด€๊ด€๊ณ„ ๋ถ„์„์„ ์œ„ํ•œ ์ถฉ๋ถ„ํ•œ ํ’ˆ๋ชฉ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.")
260
+ except Exception as e:
261
+ st.error(f"์ƒ๊ด€๊ด€๊ณ„ ๋ถ„์„ ์˜ค๋ฅ˜: {str(e)}")
262
 
263
  # -------------------------------------------------
264
  # FOOTER ------------------------------------------
265
  # -------------------------------------------------
266
  st.markdown("---")
267
+ st.caption("ยฉ 2024 ํ’ˆ๋ชฉ๋ณ„ ๊ฐ€๊ฒฉ ์˜ˆ์ธก ์‹œ์Šคํ…œ | ๋ฐ์ดํ„ฐ ๋ถ„์„ ์ž๋™ํ™”")