Spaces:
Running
Running
Update app.py
Browse files
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(
|
106 |
-
|
|
|
|
|
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 |
-
|
133 |
-
|
134 |
-
|
135 |
-
st.
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
149 |
-
|
150 |
-
|
151 |
-
st.plotly_chart(
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
|
157 |
# -------------------------------------------------
|
158 |
# SEASONALITY & PATTERN ---------------------------
|
159 |
# -------------------------------------------------
|
160 |
with st.expander("๐ ์์ฆ๋๋ฆฌํฐ & ํจํด ์ค๋ช
"):
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
|
|
|
|
|
|
171 |
|
172 |
-
# -------------------------------------------------
|
173 |
-
# CORRELATION HEATMAP -----------------------------
|
174 |
-
# -------------------------------------------------
|
175 |
# -------------------------------------------------
|
176 |
# CORRELATION HEATMAP -----------------------------
|
177 |
# -------------------------------------------------
|
178 |
st.subheader("๐งฎ ํ๋ชฉ ๊ฐ ์๊ด๊ด๊ณ")
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
206 |
|
207 |
# -------------------------------------------------
|
208 |
# FOOTER ------------------------------------------
|
209 |
# -------------------------------------------------
|
210 |
st.markdown("---")
|
211 |
-
st.caption("ยฉ
|
|
|
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 ํ๋ชฉ๋ณ ๊ฐ๊ฒฉ ์์ธก ์์คํ
| ๋ฐ์ดํฐ ๋ถ์ ์๋ํ")
|