π Multiclass wine quality classification with leakage-free scikit-learn pipelines - compare 9 classifier variants and a majority-voting ensemble under stratified cross-validation
An end-to-end machine learning study on the Wine Quality dataset. Nine classifier variants β Decision Tree, k-NN and Random Forest, three hyperparameter settings each β are evaluated with stratified 5-fold cross-validation, alongside a VotingClassifier ensemble that aggregates all nine by majority vote.
The project is built around methodological rigour rather than leaderboard chasing: every scaler lives inside a Pipeline so it can never see a test fold, missing data aborts the run, a single seed drives every random component, and the exact library versions are written to disk with the results. A Streamlit walkthrough presents the study, the glossary, the LaTeX tables and the interactive charts.
- π· Multiclass target β classifies the
qualitylabel (scores 3β8) ondata/WineQT.csv, an intentionally imbalanced real-world distribution. - π³ Nine classifier variants β
DecisionTreeClassifier,KNeighborsClassifierandRandomForestClassifier, three hyperparameter settings each, for a readable family-vs-family comparison. - π³οΈ Majority-voting ensemble β a
VotingClassifierwithvoting="hard"over the same nine pipelines, evaluated under identical folds. - π Leakage-free by construction β
MinMaxScalersits inside aColumnTransformerwithin eachPipeline, so it is fitted only on the training fold. - π Two metrics, deliberately β
accuracyplusbalanced_accuracy(mean per-class recall), because accuracy alone flatters a model on imbalanced wine scores. - π² Reproducible runs β one seed (
RANDOM_STATE = 42) shared by every estimator and byStratifiedKFold, with library versions recorded per run. - π Publication-ready export β results are written as CSV and LaTeX (
pandas.DataFrame.to_latex) for direct inclusion in a report. - π Interactive Streamlit app β four tabs covering the study description, requirements mapping, EDA and results, including a
.texpreview.
Captured from the container built by .tools/docker/docker-compose.yml.
All charts below are produced by python run_experiment.py and written to results/. Interactive Plotly versions of the same figures are saved as standalone HTML under results/wykresy/.
| Class distribution | Correlation matrix |
|---|---|
![]() |
![]() |
| Model comparison | Family aggregates |
|---|---|
![]() |
![]() |
Numeric results live in results/wyniki_szczegolowe.csv (per-model means and standard deviations) and results/wyniki_agregaty_rodzin.csv (per-family aggregates), with matching .tex tables beside them.
All scaling operations are performed inside a scikit-learn Pipeline object:
Pipeline([
("preprocess", ColumnTransformer([("num", MinMaxScaler(), feature_cols)])),
("clf", classifier),
])
Thanks to this, MinMaxScaler is fitted exclusively on the training fold β it never "sees" the test data before evaluation. Passing a ready Pipeline to cross_validate guarantees this automatically for each of the 5 splits. Details: scikit-learn β Common pitfalls.
The wczytaj_pelna_ramke function in src/experiment.py calls df.isna().any().any() and raises an exception if it finds missing values β the experiment will not run on an incomplete dataset.
In line with the requirement scikit-learn β Getting reproducible results:
- One fixed seed:
RANDOM_STATE = 42insrc/config.py; passed to every classifier (random_state) and toStratifiedKFold(shuffle=True, random_state=RANDOM_STATE). StratifiedKFoldwithshuffle=Truerequires a seed β without it the sample order after shuffling would differ on every run.- The same
cvobject is passed to eachcross_validatecall, which guarantees identical splits for all models. - The
results/wersje_bibliotek.txtfile records the versions of scikit-learn, numpy and pandas when the results are generated β making it possible to reproduce the environment.
| Family | Variants | Hyperparameters |
|---|---|---|
| DecisionTree | dt_gleb_5, dt_gleb_15, dt_gleb_25 |
max_depth 5 / 15 / 25 with min_samples_leaf 2 / 5 / 10 |
| kNN | knn_k5, knn_k11, knn_k21 |
n_neighbors 5 / 11 / 21, weights uniform / distance / uniform |
| RandomForest | rf_50, rf_100, rf_200 |
n_estimators 50 / 100 / 200 with max_depth 10 / 15 / 20 |
| Ensemble | majority_voting_9 |
VotingClassifier(voting="hard") over all nine pipelines above |
Validation is identical for every row: StratifiedKFold(n_splits=5, shuffle=True, random_state=42), scored on accuracy and balanced_accuracy via cross_validate.
- scikit-learn (
>=1.3,<2) βPipeline,ColumnTransformer,MinMaxScaler, the three classifier families,VotingClassifier,StratifiedKFold,cross_validate - NumPy (
>=1.24,<3) β numeric aggregation of fold scores
- pandas (
>=2.0,<3) β data loading,groupbyaggregation, CSV export andto_latextable generation
- Plotly (
>=5.18,<6) β all four figures, exported as interactive standalone HTML - Streamlit (
>=1.28,<2) β the four-tab study walkthrough
-
Docker β for the containerised path below (recommended)
-
Python 3.10+ (recommended)
-
pip and the ability to create a virtual environment
The container installs the scientific stack, runs the experiment and serves the dashboard in one step β no local Python setup and no virtual environment:
docker compose -f .tools/docker/docker-compose.yml up --buildThe dashboard is then available at http://localhost:8501.
Generated artefacts (results/, data/) are bind-mounted back to the host, so
charts and metrics written inside the container survive it being removed. Stop
the stack with:
docker compose -f .tools/docker/docker-compose.yml downgit clone https://github.com/dawidolko/WineQuality-Classifier-Python.git
cd WineQuality-Classifier-Pythonpip install -r requirements.txtRun the experiment on its own β it writes CSV, .tex, wersje_bibliotek.txt and the HTML charts in results/wykresy/:
python run_experiment.pyLaunch the Streamlit application:
streamlit run streamlit_app.pyThe start.sh (Linux/macOS) and start.bat (Windows) scripts run, in order:
- venv +
pip install -r requirements.txt python run_experiment.pystreamlit run streamlit_app.pyβ usuallyhttp://localhost:8501
chmod +x start.sh
./start.shstart.bat| Tab | Content |
|---|---|
| Project description | Study goal, experiment flow (what β why β effect), glossary of terms, requirements compliance, LaTeX file preview |
| Requirements & methodology | Table: requirement β implementation in code / files |
| Dataset (EDA) | quality class distribution, correlation matrix, data preview |
| Classification results | CV metric charts, tables, preview and download of results |
WineQuality-Classifier-Python/
βββ π data/
β βββ WineQT.csv # Source dataset (Kaggle / yasserh)
βββ π src/
β βββ config.py # Paths, seed (RANDOM_STATE = 42)
β βββ experiment.py # Pipeline, CV, ensemble, CSV/LaTeX export
β βββ wykresy.py # Plotly charts + HTML export
βββ π results/ # Generated artifacts
β βββ img_rozkald.png # Class distribution
β βββ img_korelacja.png # Correlation matrix
β βββ img_modele.png # Model comparison
β βββ img_agregaty.png # Family aggregates
β βββ wyniki_szczegolowe.csv/.tex # Per-model results
β βββ wyniki_agregaty_rodzin.csv/.tex # Per-family aggregates
β βββ wersje_bibliotek.txt # Library versions for reproducibility
β βββ wykresy/ # Interactive Plotly HTML charts
βββ π docs/
β βββ diagrams/pipeline.svg # Pipeline diagram
β βββ build_pptx.py # Presentation generator
β βββ Wine_Quality_Prezentacja.pptx # Generated presentation
βββ βΆοΈ run_experiment.py # Command-line entry point
βββ π₯οΈ streamlit_app.py # Web application
βββ π start.sh / start.bat # Experiment + Streamlit
βββ π¦ requirements.txt
βββ π README.md
| Path | Description |
|---|---|
data/WineQT.csv |
Source data |
src/config.py |
Paths, seed |
src/experiment.py |
Pipeline, CV, ensemble, CSV/LaTeX export, chart invocation |
src/wykresy.py |
Plotly charts + HTML export |
run_experiment.py |
Command-line entry point |
streamlit_app.py |
Web application |
start.sh / start.bat |
Experiment + Streamlit |
results/ |
Generated results (CSV, TeX, wykresy/*.html, wersje_bibliotek.txt) |
Ignored items include .venv/, Python cache, and IDE files. Generated files in results/ can optionally be added to the ignore list β .gitignore contains a ready, commented-out block with instructions.
This project is open source and available under the terms described in the LICENSE file.
Created by Dawid Olko
- Website β dawidolko.pl
- LinkedIn β @dawidolko




