|
|
|
|
|
|
|
|
|
|
|
|
|
|
import streamlit as st |
|
|
import pandas as pd |
|
|
import numpy as np |
|
|
from sklearn.model_selection import train_test_split |
|
|
from sklearn.preprocessing import StandardScaler |
|
|
from sklearn.ensemble import RandomForestClassifier |
|
|
from sklearn.metrics import accuracy_score |
|
|
|
|
|
|
|
|
st.set_page_config(page_title="Wine Judge", page_icon="Wine Glass", layout="centered") |
|
|
|
|
|
|
|
|
st.markdown(""" |
|
|
<style> |
|
|
.main {background:#0a001a; color:#f0e6ff;} |
|
|
.stApp {background:linear-gradient(160deg,#1a0033,#000);} |
|
|
h1 {font-size:4rem; text-align:center; |
|
|
background:linear-gradient(90deg,#ff6b6b,#ffd93d,#6bcf7f); |
|
|
-webkit-background-clip:text; -webkit-text-fill-color:transparent;} |
|
|
.card {background:rgba(40,10,80,0.7); padding:2rem; border-radius:20px; |
|
|
border:1px solid #8a2be2; margin:2rem 0;} |
|
|
.good {color:#00ff9d; font-size:4rem; text-align:center; font-weight:bold;} |
|
|
.bad {color:#ff4757; font-size:3.5rem; text-align:center;} |
|
|
</style> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
@st.cache_data |
|
|
def make_data(n=600): |
|
|
np.random.seed(42) |
|
|
data = pd.DataFrame({ |
|
|
'fixed_acidity' : np.random.uniform(4, 16, n), |
|
|
'volatile_acidity' : np.random.uniform(0.08, 1.6, n), |
|
|
'citric_acid' : np.random.uniform(0, 1, n), |
|
|
'residual_sugar' : np.random.uniform(0.5, 20, n), |
|
|
'chlorides' : np.random.uniform(0.005, 0.4, n), |
|
|
'free_sulfur_dioxide' : np.random.uniform(1, 80, n), |
|
|
'total_sulfur_dioxide': np.random.uniform(6, 300, n), |
|
|
'density' : np.random.uniform(0.987, 1.01, n), |
|
|
'pH' : np.random.uniform(2.7, 4.0, n), |
|
|
'sulphates' : np.random.uniform(0.3, 2.0, n), |
|
|
'alcohol' : np.random.uniform(8, 15, n), |
|
|
}) |
|
|
data['wine_type'] = np.random.choice(['Red', 'White'], n, p=[0.4, 0.6]) |
|
|
|
|
|
quality = (data['alcohol']*0.8 - data['volatile_acidity']*3 + data['sulphates']*2 + |
|
|
+ np.random.normal(0,1,n)).clip(3,9).astype(int) |
|
|
data['quality'] = quality |
|
|
data['good_wine'] = (quality >= 6).astype(int) |
|
|
return data |
|
|
|
|
|
df = make_data() |
|
|
|
|
|
st.markdown("<h1>Wine Judge</h1>", unsafe_allow_html=True) |
|
|
st.markdown("<p style='text-align:center;font-size:1.6rem;color:#d8bfd8;'>Legendary or forgettable?</p>", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
c1,c2,c3 = st.columns(3) |
|
|
c1.metric("Total Bottles", len(df)) |
|
|
c2.metric("Red", len(df[df.wine_type=='Red'])) |
|
|
c3.metric("White", len(df[df.wine_type=='White'])) |
|
|
|
|
|
|
|
|
X = df.drop(columns=['quality','good_wine']) |
|
|
y = df['good_wine'] |
|
|
|
|
|
X = pd.get_dummies(X, columns=['wine_type'], drop_first=False) |
|
|
|
|
|
|
|
|
TRAIN_COLUMNS = X.columns.tolist() |
|
|
|
|
|
scaler = StandardScaler() |
|
|
X[TRAIN_COLUMNS] = scaler.fit_transform(X) |
|
|
|
|
|
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) |
|
|
|
|
|
@st.cache_resource |
|
|
def get_model(): |
|
|
clf = RandomForestClassifier(n_estimators=300, max_depth=12, random_state=42, class_weight='balanced', n_jobs=-1) |
|
|
clf.fit(X_train, y_train) |
|
|
return clf |
|
|
|
|
|
model = get_model() |
|
|
acc = accuracy_score(y_test, model.predict(X_test)) |
|
|
st.success(f"Model Accuracy: {acc:.1%}") |
|
|
|
|
|
|
|
|
st.markdown("<div class='card'>", unsafe_allow_html=True) |
|
|
st.subheader("Judge Your Wine") |
|
|
|
|
|
wine = st.radio("Wine Type", ["Red", "White"], horizontal=True) |
|
|
|
|
|
|
|
|
input_data = {} |
|
|
input_data['wine_type_Red'] = 1 if wine == "Red" else 0 |
|
|
input_data['wine_type_White'] = 1 if wine == "White" else 0 |
|
|
|
|
|
num_features = [c for c in TRAIN_COLUMNS if 'wine_type' not in c] |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
for i, col_name in enumerate(num_features): |
|
|
with col1 if i%2==0 else col2: |
|
|
mn, mx, avg = df[col_name].min(), df[col_name].max(), df[col_name].mean() |
|
|
val = st.slider(col_name.replace("_"," ").title(), float(mn), float(mx), float(avg), 0.1) |
|
|
input_data[col_name] = val |
|
|
|
|
|
if st.button("Judge This Wine", use_container_width=True): |
|
|
|
|
|
sample = pd.DataFrame([input_data]) |
|
|
sample = sample.reindex(columns=TRAIN_COLUMNS, fill_value=0) |
|
|
|
|
|
|
|
|
sample[num_features] = scaler.transform(sample[num_features]) |
|
|
|
|
|
pred = model.predict(sample)[0] |
|
|
prob = model.predict_proba(sample)[0] |
|
|
|
|
|
st.markdown("<br>", unsafe_allow_html=True) |
|
|
if pred == 1: |
|
|
st.balloons() |
|
|
st.markdown("<div class='good'>EXCELLENT WINE!</div>", unsafe_allow_html=True) |
|
|
st.success(f"Confidence: {prob[1]:.1%} – Open it tonight!") |
|
|
else: |
|
|
st.markdown("<div class='bad'>Not Great...</div>", unsafe_allow_html=True) |
|
|
st.warning(f"Confidence: {prob[0]:.1%} – Maybe for cooking?") |
|
|
|
|
|
st.markdown("</div>", unsafe_allow_html=True) |
|
|
st.caption("100% original • Synthetic data • Zero copyright • Runs instantly") |