Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +49 -64
src/streamlit_app.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
# =============================================================
|
| 2 |
-
# โฝ
|
| 3 |
# =============================================================
|
| 4 |
import streamlit as st
|
| 5 |
import pandas as pd
|
|
@@ -9,23 +9,20 @@ import joblib
|
|
| 9 |
# ===============================
|
| 10 |
# ์ฑ ๊ธฐ๋ณธ ์ค์ + ๋ชจ๋ฐ์ผ ํ์ ๋ฒ๊ทธ ๋์ CSS/JS
|
| 11 |
# ===============================
|
| 12 |
-
st.set_page_config(page_title="โฝ
|
| 13 |
|
| 14 |
# ๐ง ๋ชจ๋ฐ์ผ์์ ํ
์คํธ/๋ฉํธ๋ฆญ์ด ์๋ฆฌ์ง ์๋๋ก overflow/zoom/๊ธ๊ผด ๊ณ ์ + ์ด๊ธฐ ๋ฆฌ์ฌ์ด์ฆ ํธ๋ฆฌ๊ฑฐ
|
| 15 |
st.markdown(
|
| 16 |
"""
|
| 17 |
<style>
|
| 18 |
-
/* ์ ์ญ์ ์ผ๋ก overflow ์จ๊น์ ํด์ */
|
| 19 |
.stApp, .block-container, [data-testid="stVerticalBlock"], [data-testid="column"] {
|
| 20 |
overflow: visible !important;
|
| 21 |
}
|
| 22 |
-
/* ๋ชจ๋ฐ์ผ์์ ์ฌ๋ฐฑ/์ค์ผ์ผ ๋ณด์ */
|
| 23 |
@media (max-width: 768px) {
|
| 24 |
.block-container {
|
| 25 |
padding-left: 1rem !important;
|
| 26 |
padding-right: 1rem !important;
|
| 27 |
}
|
| 28 |
-
/* ํ๋ฅ ํ์์ ์ฐ๋ ํ
์คํธ ๋ฐ์ค ๊ณตํต ์คํ์ผ */
|
| 29 |
.prob-pill {
|
| 30 |
font-size: 1.1rem;
|
| 31 |
color: #ffffff;
|
|
@@ -43,7 +40,6 @@ st.markdown(
|
|
| 43 |
}
|
| 44 |
</style>
|
| 45 |
<script>
|
| 46 |
-
// ์ฒซ ๋ ๋ ์งํ ๊ฐ์ ๋ฆฌ์ฌ์ด์ฆ๋ก ๋ ์ด์์ ์ฌ๊ณ์ฐ ์ ๋ (๋ชจ๋ฐ์ผ ํ์ ๋๋ฝ ๋ฐฉ์ง)
|
| 47 |
window.addEventListener('load', () => {
|
| 48 |
setTimeout(() => { window.dispatchEvent(new Event('resize')); }, 150);
|
| 49 |
});
|
|
@@ -52,16 +48,16 @@ st.markdown(
|
|
| 52 |
unsafe_allow_html=True,
|
| 53 |
)
|
| 54 |
|
| 55 |
-
st.title("โฝ
|
| 56 |
|
| 57 |
EQ_DECIMALS = 2
|
| 58 |
def eq(a, b, decimals=EQ_DECIMALS):
|
| 59 |
return np.round(a, decimals) == np.round(b, decimals)
|
| 60 |
|
| 61 |
# ===============================
|
| 62 |
-
# Feature ๋ชฉ๋ก
|
| 63 |
# ===============================
|
| 64 |
-
|
| 65 |
'norm_win','norm_draw','norm_lose','mean_odds','std_odds','cv_odds',
|
| 66 |
'p_win','p_draw','p_lose','overround','entropy','spread','spread_draw',
|
| 67 |
'odds_ratio_wd','odds_ratio_wl','odds_ratio_dl','draw_prob_ratio','draw_ratio',
|
|
@@ -71,9 +67,7 @@ expected_cols_base59 = [
|
|
| 71 |
'draw_margin','fav_ratio','draw_skew','log_spread','draw_entropy_component','dominance_score',
|
| 72 |
'hmean_odds','hstd_odds','hcv_odds','hentropy','hspread','hspread_draw',
|
| 73 |
'hp_win','hp_draw','hp_lose','hp_win_norm','hp_draw_norm','hp_lose_norm','hoverround',
|
| 74 |
-
'diff_win_prob','diff_draw_prob','diff_lose_prob','diff_draw_odds'
|
| 75 |
-
]
|
| 76 |
-
expected_cols_handicap65 = expected_cols_base59 + [
|
| 77 |
'base_win_odds','base_draw_odds','base_lose_odds',
|
| 78 |
'base_overround_ex','base_entropy_ex','base_spread_ex'
|
| 79 |
]
|
|
@@ -84,9 +78,7 @@ expected_cols_handicap65 = expected_cols_base59 + [
|
|
| 84 |
def build_feature_dict(win, draw, lose, hwin, hdraw, hlose):
|
| 85 |
d = {}
|
| 86 |
denom = (win+draw+lose)
|
| 87 |
-
d['norm_win'] = win/denom
|
| 88 |
-
d['norm_draw'] = draw/denom
|
| 89 |
-
d['norm_lose'] = lose/denom
|
| 90 |
d['mean_odds'] = np.mean([win,draw,lose])
|
| 91 |
d['std_odds'] = np.std([win,draw,lose])
|
| 92 |
d['cv_odds'] = d['std_odds']/d['mean_odds'] if d['mean_odds']>0 else 0
|
|
@@ -98,37 +90,35 @@ def build_feature_dict(win, draw, lose, hwin, hdraw, hlose):
|
|
| 98 |
d['spread'] = max(win,draw,lose)-min(win,draw,lose)
|
| 99 |
d['spread_draw'] = abs(draw-(win+lose)/2)
|
| 100 |
d['odds_ratio_wd'],d['odds_ratio_wl'],d['odds_ratio_dl']=win/draw,win/lose,draw/lose
|
| 101 |
-
d['draw_prob_ratio']
|
| 102 |
-
d['draw_ratio']
|
| 103 |
-
d['draw_prob_gap']
|
| 104 |
-
d['fav_gap']
|
| 105 |
-
d['fav_draw_gap']
|
| 106 |
-
d['fav_diff']
|
| 107 |
-
d['draw_gap_mean']
|
| 108 |
-
d['rank_win'],d['rank_draw'],d['rank_lose']
|
| 109 |
-
d['ev_win'],d['ev_draw'],d['ev_lose']
|
| 110 |
-
d['draw_vs_avg']
|
| 111 |
-
d['draw_vs_max']
|
| 112 |
-
d['cv_spread']
|
| 113 |
-
d['cv_draw_gap']
|
| 114 |
-
d['draw_margin']
|
| 115 |
-
d['fav_ratio']
|
| 116 |
-
d['draw_skew']
|
| 117 |
-
d['log_spread']
|
| 118 |
-
d['draw_entropy_component']
|
| 119 |
-
d['dominance_score']
|
| 120 |
-
|
| 121 |
-
d['
|
| 122 |
-
d['hstd_odds']
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
p_hn
|
| 126 |
-
d['
|
| 127 |
-
d['
|
| 128 |
-
d['
|
| 129 |
-
d['
|
| 130 |
-
d['hspread'] = max(hwin,hdraw,hlose)-min(hwin,hdraw,hlose)
|
| 131 |
-
d['hspread_draw'] = abs(hdraw-(hwin+hlose)/2)
|
| 132 |
d['diff_win_prob']=d['p_win_norm']-d['hp_win_norm']
|
| 133 |
d['diff_draw_prob']=d['p_draw_norm']-d['hp_draw_norm']
|
| 134 |
d['diff_lose_prob']=d['p_lose_norm']-d['hp_lose_norm']
|
|
@@ -137,29 +127,30 @@ def build_feature_dict(win, draw, lose, hwin, hdraw, hlose):
|
|
| 137 |
d['base_overround_ex'],d['base_entropy_ex'],d['base_spread_ex']=p_tot,d['entropy'],d['spread']
|
| 138 |
return d
|
| 139 |
|
| 140 |
-
def
|
| 141 |
-
d = build_feature_dict(win,draw,lose,hwin,hdraw,hlose)
|
| 142 |
-
|
| 143 |
-
return
|
| 144 |
|
| 145 |
# ===============================
|
| 146 |
-
# ๋ชจ๋ธ ๋ก๋
|
| 147 |
# ===============================
|
| 148 |
@st.cache_resource
|
| 149 |
def load_models():
|
| 150 |
-
base = joblib.load("
|
| 151 |
-
hand = joblib.load("
|
| 152 |
enc = joblib.load("label_encoder_handicap.pkl")
|
| 153 |
return base, hand, enc
|
|
|
|
| 154 |
model_base, model_hand, encoder_hand = load_models()
|
| 155 |
|
| 156 |
# ===============================
|
| 157 |
# ์์ธก
|
| 158 |
# ===============================
|
| 159 |
def predict_all(win, draw, lose, hwin, hdraw, hlose):
|
| 160 |
-
|
| 161 |
-
probs_base = model_base.predict_proba(
|
| 162 |
-
probs_hand = model_hand.predict_proba(
|
| 163 |
return dict(zip(["์น","๋ฌด","ํจ"], probs_base)), dict(zip(["ํธ๋ ์น","ํธ๋ ๋ฌด","ํธ๋ ํจ"], probs_hand))
|
| 164 |
|
| 165 |
# ===============================
|
|
@@ -257,23 +248,17 @@ st.divider()
|
|
| 257 |
|
| 258 |
# ===============================
|
| 259 |
# ๐ข ํ๋ฅ ํ์ (๋ชจ๋ฐ์ผ ์์ ํธํ)
|
| 260 |
-
# - columns ์ฌ์ฉ ์ ํจ
|
| 261 |
-
# - markdown/HTML๋ง ์ฌ์ฉ
|
| 262 |
-
# - ํ์ด์ง ํ๋จ์์ ๋ ๋ (๋ชจ๋ฐ์ผ ๋ ๋ ํ์ด๋ฐ ์์ )
|
| 263 |
# ===============================
|
| 264 |
base_probs, hand_probs = predict_all(win, draw, lose, hwin, hdraw, hlose)
|
| 265 |
|
| 266 |
-
|
| 267 |
-
mobile_text_mode = st.toggle("๐ฑ ๋ชจ๋ฐ์ผ ๊ฐ์ ํธํ ๋ชจ๋(ํ
์คํธ๋ง)", value=True, help="๋ชจ๋ฐ์ผ์์ ๋๋ฝ๋ ๊ฒฝ์ฐ ์ผ๋์ธ์")
|
| 268 |
|
| 269 |
st.markdown('<div class="prob-section">', unsafe_allow_html=True)
|
| 270 |
st.markdown("### โฝ ๊ธฐ๋ณธ ์น/๋ฌด/ํจ ํ๋ฅ ")
|
| 271 |
if mobile_text_mode:
|
| 272 |
-
# ํ
์คํธ๋ง (๊ฐ์ฅ ์์ )
|
| 273 |
for k, emoji in zip(["์น","๋ฌด","ํจ"], ["๐ข","๐ก","๐ด"]):
|
| 274 |
st.markdown(f"{emoji} **{k}** : {base_probs[k]*100:.2f}%")
|
| 275 |
else:
|
| 276 |
-
# pill ์คํ์ผ (๋ชจ๋ฐ์ผ์์๋ ์ ๋ณด์ด๋๋ก ์ปค์คํ
)
|
| 277 |
html = "".join([f'<span class="prob-pill"><b>{k}</b>: {base_probs[k]*100:.2f}%</span>'
|
| 278 |
for k in ["์น","๋ฌด","ํจ"]])
|
| 279 |
st.markdown(html, unsafe_allow_html=True)
|
|
@@ -288,4 +273,4 @@ else:
|
|
| 288 |
st.markdown(html, unsafe_allow_html=True)
|
| 289 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 290 |
|
| 291 |
-
st.caption("โ
|
|
|
|
| 1 |
# =============================================================
|
| 2 |
+
# โฝ XGBoost 3-Class ์์ธก + ์ ์ฌ ๊ฒฝ๊ธฐ ๋ถํฌ (Full Integration, Mobile-Fix)
|
| 3 |
# =============================================================
|
| 4 |
import streamlit as st
|
| 5 |
import pandas as pd
|
|
|
|
| 9 |
# ===============================
|
| 10 |
# ์ฑ ๊ธฐ๋ณธ ์ค์ + ๋ชจ๋ฐ์ผ ํ์ ๋ฒ๊ทธ ๋์ CSS/JS
|
| 11 |
# ===============================
|
| 12 |
+
st.set_page_config(page_title="โฝ XGBoost ์์ธก + ์ ์ฌ ๊ฒฝ๊ธฐ ํ์๊ธฐ", layout="wide")
|
| 13 |
|
| 14 |
# ๐ง ๋ชจ๋ฐ์ผ์์ ํ
์คํธ/๋ฉํธ๋ฆญ์ด ์๋ฆฌ์ง ์๋๋ก overflow/zoom/๊ธ๊ผด ๊ณ ์ + ์ด๊ธฐ ๋ฆฌ์ฌ์ด์ฆ ํธ๋ฆฌ๊ฑฐ
|
| 15 |
st.markdown(
|
| 16 |
"""
|
| 17 |
<style>
|
|
|
|
| 18 |
.stApp, .block-container, [data-testid="stVerticalBlock"], [data-testid="column"] {
|
| 19 |
overflow: visible !important;
|
| 20 |
}
|
|
|
|
| 21 |
@media (max-width: 768px) {
|
| 22 |
.block-container {
|
| 23 |
padding-left: 1rem !important;
|
| 24 |
padding-right: 1rem !important;
|
| 25 |
}
|
|
|
|
| 26 |
.prob-pill {
|
| 27 |
font-size: 1.1rem;
|
| 28 |
color: #ffffff;
|
|
|
|
| 40 |
}
|
| 41 |
</style>
|
| 42 |
<script>
|
|
|
|
| 43 |
window.addEventListener('load', () => {
|
| 44 |
setTimeout(() => { window.dispatchEvent(new Event('resize')); }, 150);
|
| 45 |
});
|
|
|
|
| 48 |
unsafe_allow_html=True,
|
| 49 |
)
|
| 50 |
|
| 51 |
+
st.title("โฝ XGBoost ์์ธก + ์ ์ฌ ๊ฒฝ๊ธฐ ๋ถํฌ + ์ ์ฌ ๊ฒฝ๊ธฐ ํ์๊ธฐ")
|
| 52 |
|
| 53 |
EQ_DECIMALS = 2
|
| 54 |
def eq(a, b, decimals=EQ_DECIMALS):
|
| 55 |
return np.round(a, decimals) == np.round(b, decimals)
|
| 56 |
|
| 57 |
# ===============================
|
| 58 |
+
# Feature ๋ชฉ๋ก (65ํผ์ฒ ํตํฉ)
|
| 59 |
# ===============================
|
| 60 |
+
expected_cols_65 = [
|
| 61 |
'norm_win','norm_draw','norm_lose','mean_odds','std_odds','cv_odds',
|
| 62 |
'p_win','p_draw','p_lose','overround','entropy','spread','spread_draw',
|
| 63 |
'odds_ratio_wd','odds_ratio_wl','odds_ratio_dl','draw_prob_ratio','draw_ratio',
|
|
|
|
| 67 |
'draw_margin','fav_ratio','draw_skew','log_spread','draw_entropy_component','dominance_score',
|
| 68 |
'hmean_odds','hstd_odds','hcv_odds','hentropy','hspread','hspread_draw',
|
| 69 |
'hp_win','hp_draw','hp_lose','hp_win_norm','hp_draw_norm','hp_lose_norm','hoverround',
|
| 70 |
+
'diff_win_prob','diff_draw_prob','diff_lose_prob','diff_draw_odds',
|
|
|
|
|
|
|
| 71 |
'base_win_odds','base_draw_odds','base_lose_odds',
|
| 72 |
'base_overround_ex','base_entropy_ex','base_spread_ex'
|
| 73 |
]
|
|
|
|
| 78 |
def build_feature_dict(win, draw, lose, hwin, hdraw, hlose):
|
| 79 |
d = {}
|
| 80 |
denom = (win+draw+lose)
|
| 81 |
+
d['norm_win'], d['norm_draw'], d['norm_lose'] = win/denom, draw/denom, lose/denom
|
|
|
|
|
|
|
| 82 |
d['mean_odds'] = np.mean([win,draw,lose])
|
| 83 |
d['std_odds'] = np.std([win,draw,lose])
|
| 84 |
d['cv_odds'] = d['std_odds']/d['mean_odds'] if d['mean_odds']>0 else 0
|
|
|
|
| 90 |
d['spread'] = max(win,draw,lose)-min(win,draw,lose)
|
| 91 |
d['spread_draw'] = abs(draw-(win+lose)/2)
|
| 92 |
d['odds_ratio_wd'],d['odds_ratio_wl'],d['odds_ratio_dl']=win/draw,win/lose,draw/lose
|
| 93 |
+
d['draw_prob_ratio']=d['p_draw']/max(d['p_win'],d['p_lose'])
|
| 94 |
+
d['draw_ratio']=draw/min(win,lose)
|
| 95 |
+
d['draw_prob_gap']=abs(d['p_draw']-(d['p_win']+d['p_lose'])/2)
|
| 96 |
+
d['fav_gap']=abs(win-lose)
|
| 97 |
+
d['fav_draw_gap']=abs(draw-min(win,lose))
|
| 98 |
+
d['fav_diff']=abs(win-lose)
|
| 99 |
+
d['draw_gap_mean']=abs(draw-d['mean_odds'])
|
| 100 |
+
d['rank_win'],d['rank_draw'],d['rank_lose']=pd.Series([win,draw,lose]).rank().tolist()
|
| 101 |
+
d['ev_win'],d['ev_draw'],d['ev_lose']=win*d['p_win_norm'],draw*d['p_draw_norm'],lose*d['p_lose_norm']
|
| 102 |
+
d['draw_vs_avg']=draw/d['mean_odds']
|
| 103 |
+
d['draw_vs_max']=draw/max(win,draw,lose)
|
| 104 |
+
d['cv_spread']=d['spread']/d['mean_odds']
|
| 105 |
+
d['cv_draw_gap']=d['fav_draw_gap']/d['mean_odds']
|
| 106 |
+
d['draw_margin']=abs(draw-(win+lose)/2)
|
| 107 |
+
d['fav_ratio']=min(win,lose)/max(win,lose)
|
| 108 |
+
d['draw_skew']=(draw-win)-(lose-draw)
|
| 109 |
+
d['log_spread']=np.log(max(win,draw,lose))-np.log(min(win,draw,lose))
|
| 110 |
+
d['draw_entropy_component']=-d['p_draw_norm']*np.log(d['p_draw_norm'])
|
| 111 |
+
d['dominance_score']=max(d['p_win_norm'],d['p_lose_norm'])-d['p_draw_norm']
|
| 112 |
+
d['hmean_odds']=np.mean([hwin,hdraw,hlose])
|
| 113 |
+
d['hstd_odds']=np.std([hwin,hdraw,hlose])
|
| 114 |
+
d['hcv_odds']=d['hstd_odds']/d['hmean_odds'] if d['hmean_odds']>0 else 0
|
| 115 |
+
p_h=1/np.array([hwin,hdraw,hlose]); p_hn=p_h/p_h.sum()
|
| 116 |
+
d['hp_win'],d['hp_draw'],d['hp_lose']=p_h
|
| 117 |
+
d['hp_win_norm'],d['hp_draw_norm'],d['hp_lose_norm']=p_hn
|
| 118 |
+
d['hoverround']=p_h.sum()
|
| 119 |
+
d['hentropy']=-np.sum(p_hn*np.log(p_hn))
|
| 120 |
+
d['hspread']=max(hwin,hdraw,hlose)-min(hwin,hdraw,hlose)
|
| 121 |
+
d['hspread_draw']=abs(hdraw-(hwin+hlose)/2)
|
|
|
|
|
|
|
| 122 |
d['diff_win_prob']=d['p_win_norm']-d['hp_win_norm']
|
| 123 |
d['diff_draw_prob']=d['p_draw_norm']-d['hp_draw_norm']
|
| 124 |
d['diff_lose_prob']=d['p_lose_norm']-d['hp_lose_norm']
|
|
|
|
| 127 |
d['base_overround_ex'],d['base_entropy_ex'],d['base_spread_ex']=p_tot,d['entropy'],d['spread']
|
| 128 |
return d
|
| 129 |
|
| 130 |
+
def build_feature_frame(win, draw, lose, hwin, hdraw, hlose):
|
| 131 |
+
d = build_feature_dict(win, draw, lose, hwin, hdraw, hlose)
|
| 132 |
+
df = pd.DataFrame([d])
|
| 133 |
+
return df[expected_cols_65]
|
| 134 |
|
| 135 |
# ===============================
|
| 136 |
+
# ๋ชจ๋ธ ๋ก๋ (XGBoost)
|
| 137 |
# ===============================
|
| 138 |
@st.cache_resource
|
| 139 |
def load_models():
|
| 140 |
+
base = joblib.load("xgb_model_wdl_softmax.pkl")
|
| 141 |
+
hand = joblib.load("xgb_model_handicap_65f.pkl")
|
| 142 |
enc = joblib.load("label_encoder_handicap.pkl")
|
| 143 |
return base, hand, enc
|
| 144 |
+
|
| 145 |
model_base, model_hand, encoder_hand = load_models()
|
| 146 |
|
| 147 |
# ===============================
|
| 148 |
# ์์ธก
|
| 149 |
# ===============================
|
| 150 |
def predict_all(win, draw, lose, hwin, hdraw, hlose):
|
| 151 |
+
df_feat = build_feature_frame(win, draw, lose, hwin, hdraw, hlose)
|
| 152 |
+
probs_base = model_base.predict_proba(df_feat.values)[0]
|
| 153 |
+
probs_hand = model_hand.predict_proba(df_feat.values)[0]
|
| 154 |
return dict(zip(["์น","๋ฌด","ํจ"], probs_base)), dict(zip(["ํธ๋ ์น","ํธ๋ ๋ฌด","ํธ๋ ํจ"], probs_hand))
|
| 155 |
|
| 156 |
# ===============================
|
|
|
|
| 248 |
|
| 249 |
# ===============================
|
| 250 |
# ๐ข ํ๋ฅ ํ์ (๋ชจ๋ฐ์ผ ์์ ํธํ)
|
|
|
|
|
|
|
|
|
|
| 251 |
# ===============================
|
| 252 |
base_probs, hand_probs = predict_all(win, draw, lose, hwin, hdraw, hlose)
|
| 253 |
|
| 254 |
+
mobile_text_mode = st.toggle("๐ฑ ๋ชจ๋ฐ์ผ ๊ฐ์ ํธํ ๋ชจ๋(ํ
์คํธ๋ง)", value=True)
|
|
|
|
| 255 |
|
| 256 |
st.markdown('<div class="prob-section">', unsafe_allow_html=True)
|
| 257 |
st.markdown("### โฝ ๊ธฐ๋ณธ ์น/๋ฌด/ํจ ํ๋ฅ ")
|
| 258 |
if mobile_text_mode:
|
|
|
|
| 259 |
for k, emoji in zip(["์น","๋ฌด","ํจ"], ["๐ข","๐ก","๐ด"]):
|
| 260 |
st.markdown(f"{emoji} **{k}** : {base_probs[k]*100:.2f}%")
|
| 261 |
else:
|
|
|
|
| 262 |
html = "".join([f'<span class="prob-pill"><b>{k}</b>: {base_probs[k]*100:.2f}%</span>'
|
| 263 |
for k in ["์น","๋ฌด","ํจ"]])
|
| 264 |
st.markdown(html, unsafe_allow_html=True)
|
|
|
|
| 273 |
st.markdown(html, unsafe_allow_html=True)
|
| 274 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 275 |
|
| 276 |
+
st.caption("โ XGBoost 3-Class Softmax Models | ๊ธฐ๋ณธยทํธ๋ 65ํผ์ฒ ํตํฉํ")
|