Build a Wine Classifier with scikit-learn
Train and compare three ML classifiers on a real wine dataset.
Introduction
30 Second Summary
When you pick up a bottle of wine, the label tells you what type it is. But hidden in the wine itself are chemical measurements that can identify its type just as reliably.
In this project, you will use scikit-learn to train a machine learning model that classifies wines into three categories based on their chemical properties. You will compare three different algorithms to find out which one makes the most accurate predictions.
What You'll Build
A Jupyter Notebook that loads a real wine dataset with 178 samples, explores it with pandas, and trains three classification models to predict wine types from chemical measurements.
By the end of this project, you'll have:
- A trained machine learning classifier that predicts wine types from 13 chemical measurements.
- A bar chart comparing the accuracy of three different algorithms side by side.
- A visual confusion matrix showing where your best model gets predictions right and wrong.
- Secret Mission: Tune your best model's hyperparameters with GridSearchCV to squeeze out even better accuracy.
Are there any prerequisites?
This project assumes basic Python knowledge and a basic understanding of object-oriented programming. No prior machine learning experience is needed.
Set Up Your ML Environment
Before you can train a machine learning model, you need the right tools installed. Python is the language behind most ML projects, and libraries like scikit-learn handle the heavy lifting of training and evaluating models.
In this step, you'll confirm your Python version, create an isolated environment, install three key libraries, and create the Jupyter Notebook where your entire ML pipeline will live.
In this step, get ready to:
- Confirm Python 3.11 or higher is installed.
- Set up a virtual environment with scikit-learn, pandas, and matplotlib.
- Create your Jupyter Notebook and verify all imports.
Verify your Python version and create a virtual environment
The libraries you'll install in this project require Python 3.11 or higher. Let's confirm your version before going any further.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
- Open the integrated terminal by pressing Ctrl+` (backtick key, same shortcut on both macOS and Windows).
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir wine-classifier
cd wine-classifier
- Confirm the folder exists by running:
ls
The folder should be empty for now. You'll add files to it shortly.
- Check your Python version by running the following command:
macOS
python3 --version
Windows
python --version
You should see a version number printed in the terminal. Match what you see to one of the tabs below.
✔️ I see version 3.11 or higher
You're all set. Your Python version is compatible with the libraries you'll install next.
ⓧ I see an older version
Your Python version is too old for the libraries this project uses. You need Python 3.11 or higher.
On macOS, the system-installed Python is often an older version. Even if you installed a newer Python, the python3 command may still point to the old one.
- Download and install Python 3.11 or higher from python.org/downloads.
- After installation, close and reopen your terminal, then run the version check command again.
- If macOS still shows an older version, try running python3.11 --version or python3.12 --version to find the newer install. Use that specific command for the rest of this project wherever you see python3.
ⓧ Command not found
Python is not installed on your machine yet.
- Download and install Python 3.11 or higher from python.org/downloads.
- On Windows, make sure to check the Add python.exe to PATH checkbox during installation.
- After installation, close and reopen your terminal, then run the version check command again.
A virtual environment is an isolated copy of Python for your project. Packages you install inside it stay separate from any other Python projects on your machine, which prevents version conflicts.
- Create and activate a virtual environment by running these commands:
macOS
python3 -m venv sklearn-env
source sklearn-env/bin/activate
Windows
python -m venv sklearn-env
sklearn-env\Scripts\activate
What do these commands do?
The first command creates a new folder called sklearn-env inside your project directory. This folder contains a clean copy of Python and pip just for this project.
The second command activates the environment. You should see (sklearn-env) appear at the start of your terminal prompt, confirming you're working inside the virtual environment.
Don't see (sklearn-env) in your prompt?
- On Windows, if you see a security error about running scripts, your PowerShell execution policy is blocking activation. Run Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser in PowerShell, then try the activate command again.
- Make sure you're in the wine-classifier folder when running the commands. Run ls to check that sklearn-env appears as a folder.
Still stuck? Help me activate my Python virtual environment.
- Install the three libraries your project needs by running:
pip install scikit-learn pandas matplotlib
What are these libraries?
- scikit-learn provides the machine learning algorithms you'll train and evaluate.
- pandas makes it easy to explore and manipulate data in table format.
- matplotlib creates the charts and visualizations you'll use to compare model results.
You should see pip download and install each package along with its dependencies. The install may take a minute or two.
Seeing installation errors?
- Make sure (sklearn-env) appears in your terminal prompt. If it doesn't, you're installing into your global Python instead of the virtual environment.
- If you see a network timeout, check your internet connection and try running the command again.
Need more help? Help me fix my pip install errors.
Now open your project folder in VS Code so you can start creating files.
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the wine-classifier folder, and click Open.
Why open the folder?
Opening the folder tells VS Code to treat wine-classifier as your workspace. This means VS Code can detect the virtual environment you created and offer it as a kernel for your notebook.
Create a new Jupyter Notebook
With your environment ready, you'll now create the notebook file where all your ML code will live. Jupyter Notebooks let you write and run code in small cells, which is perfect for experimenting with data and models one step at a time.
- Open the Command Palette by pressing Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows).
- Type Create: New Jupyter Notebook and select it from the dropdown.
- Save the new notebook by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Name the file wine_classifier.ipynb and save it inside the wine-classifier folder.
- Click Select Kernel in the top right corner of the notebook.
- Choose Python Environments.
- Select the sklearn-env environment from the list. It will appear labelled with venv next to it.
What is a kernel?
A kernel is the engine that runs the code in your notebook cells. By selecting sklearn-env as the kernel, you're telling the notebook to use the Python environment where you installed scikit-learn, pandas, and matplotlib.
Don't see sklearn-env in the kernel list?
- Make sure you opened the wine-classifier folder in VS Code (not a parent folder like Desktop). VS Code looks for virtual environments in the current workspace folder.
- Try installing ipykernel inside your virtual environment. Open the terminal, make sure (sklearn-env) is active, and run pip install ipykernel. Then click Select Kernel again.
Still stuck? Help me select my virtual environment kernel in VS Code.
Your notebook should already have an empty code cell. This is where you'll add your imports.
- Copy the following code into the first cell of your notebook:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, ConfusionMatrixDisplay
What does this code do?
- numpy and pandas handle numerical data and tabular data.
- matplotlib.pyplot creates charts for visualizing model performance.
- load_wine loads the built-in wine dataset you'll train on.
- train_test_split and StandardScaler prepare the data for training.
- DecisionTreeClassifier, KNeighborsClassifier, and RandomForestClassifier are the three ML algorithms you'll compare.
- accuracy_score, classification_report, and ConfusionMatrixDisplay measure and visualize how well your models perform.
- Run the cell by pressing Shift+Enter.
You should see no output below the cell. No output means every import loaded successfully.
Seeing a ModuleNotFoundError?
- Check that the correct kernel is selected. Click the kernel name in the top right of the notebook and confirm it shows sklearn-env.
- If the kernel is correct but imports still fail, open the terminal, confirm (sklearn-env) is active, and re-run pip install scikit-learn pandas matplotlib.
Need help? Help me fix my import errors in Jupyter.
✔️ Awesome, I've got everything!
Great. Double check you've saved your notebook by pressing Cmd+S (macOS) or Ctrl+S (Windows).
ⓧ I'd like to double check the full code
Here is the complete code for Cell 1. Compare it to what you have in your notebook.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, ConfusionMatrixDisplay
Your ML environment is locked and loaded. Next up, you'll load a real wine dataset and start exploring 178 wine samples to understand what your models will learn from.
Load and Explore the Wine Dataset
Your ML environment is set up and your imports are ready. Now it's time to get your hands on some real data.
scikit-learn comes with several built-in datasets, including the Wine dataset. It contains 178 wine samples, each described by 13 chemical measurements like alcohol content and color intensity.
In this step, get ready to:
- Load the Wine dataset into a pandas DataFrame.
- Explore the data's shape, features, and class distribution.
Load the wine dataset
Every machine learning project starts with data. Before you can train a model, you need to load a dataset and understand its structure.
What is the Wine dataset?
The Wine dataset is a classic classification dataset that contains chemical analysis results from 178 wines grown in the same region of Italy. Each sample has 13 chemical features (like Alcohol, Malic acid, and Ash) and belongs to one of three classes: class_0, class_1, or class_2.
Your goal is to train a model that predicts which class a wine belongs to based on these 13 measurements.
- In wine_classifier.ipynb, click the + Code button at the top of the notebook to create a new cell below your imports.
- Paste the following code into the new cell:
# Load the wine dataset directly as a pandas DataFrame
wine = load_wine(as_frame=True)
# Create a full DataFrame with features and target, then explore
df = wine.frame
print("Dataset shape:", df.shape)
print("\nFirst 5 rows:")
print(df.head())
print("\nSummary statistics:")
print(df.describe())
print("\nClass distribution:")
print(df["target"].value_counts())
print("\nFeature names:", wine.feature_names)
print("Target names:", list(wine.target_names))
What does this code do?
- load_wine(as_frame=True) loads the built-in Wine dataset and returns the data directly as a pandas DataFrame, instead of a raw NumPy array.
- wine.frame gives you the complete DataFrame with all 13 feature columns plus the target column (the wine class label).
- .head() shows the first 5 rows so you can see actual data values.
- .describe() prints summary statistics like mean, standard deviation, and min/max for each feature.
- .value_counts() counts how many samples belong to each wine class.
- Run the cell by clicking the play button to the left of the cell, or by pressing Shift+Enter.
You should see a large output with the dataset shape printed as (178, 14), followed by a table of the first 5 rows, summary statistics, and a class distribution.
Seeing a NameError?
- If you see NameError: name 'load_wine' is not defined, your Cell 1 (imports) hasn't been run yet. Click on Cell 1 and run it first, then run Cell 2 again.
- If you see a ModuleNotFoundError, make sure your Jupyter kernel is using the virtual environment where you installed your packages in Step 1.
Help me fix this NameError or ModuleNotFoundError in my Jupyter Notebook.
Explore the dataset output
Your cell output contains a lot of information. Let's break down the key parts.
What do the numbers tell you?
- Dataset shape (178, 14) means 178 wine samples and 14 columns. That's 13 chemical features plus 1 target column.
- Class distribution shows 59 samples in class_0, 71 in class_1, and 48 in class_2. The classes are not perfectly equal, but close enough for a first project.
- Feature names are real chemical properties like alcohol, malic_acid, and proline. These are the measurements your model will learn from.
- Summary statistics show that features have very different ranges. For example, proline values go up to the hundreds, while nonflavanoid_phenols stays below 1. This will matter when you scale the data later.
Why do feature ranges matter?
Some ML algorithms calculate distances between data points. If one feature has values in the hundreds and another is below 1, the large-range feature dominates the calculation. Scaling fixes this by putting all features on the same playing field.
✔️ Awesome, I've got everything!
Great. Double check you've saved your notebook.
ⓧ I'd like to double check the full code
Here's the full code for your notebook so far. Make sure your Cell 1 and Cell 2 match these exactly.
# Cell 1 - Imports
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, ConfusionMatrixDisplay
# Cell 2 - Load and explore the dataset
wine = load_wine(as_frame=True)
df = wine.frame
print("Dataset shape:", df.shape)
print("\nFirst 5 rows:")
print(df.head())
print("\nSummary statistics:")
print(df.describe())
print("\nClass distribution:")
print(df["target"].value_counts())
print("\nFeature names:", wine.feature_names)
print("Target names:", list(wine.target_names))
You've loaded and explored your dataset. Next up, you'll split this data into training and testing sets, then scale the features so your models can learn from them effectively.
Prepare the Data for Training
You've explored the wine dataset and confirmed it has 178 samples, 13 chemical features, and 3 target classes.
Before you can train a model on this data, you need to prepare it. In this step, you'll split the dataset into training and test sets, then scale the features so every measurement is on an equal footing.
In this step, get ready to:
- Split the dataset into separate training and test sets.
- Scale the features so algorithms can compare them fairly.
Split the data into training and test sets
A machine learning model needs two separate pools of data. The training set is what the model learns from. The test set is held back so you can check how well the model performs on data it has never seen.
- Create a new cell below Cell 2 in your notebook and paste the following code:
# Separate features (measurements) from the target (wine class)
X = wine.data
y = wine.target
# Split into 80% training and 20% testing sets
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# Verify the split sizes and class balance
print("Training set size:", X_train.shape[0])
print("Test set size:", X_test.shape[0])
print("\nTraining class distribution:")
print(y_train.value_counts())
print("\nTest class distribution:")
print(y_test.value_counts())
What does this code do?
- X = wine.data pulls out the 13 chemical measurements (the features). y = wine.target pulls out the wine class label (the thing you want to predict).
- train_test_split randomly divides both X and y into training (80%) and test (20%) portions.
- stratify=y ensures each class appears in both sets at roughly the same ratio as the original dataset.
- random_state=42 locks the random shuffle so you get the same split every time you run the cell.
- Run the cell by pressing Shift+Enter.
- You should see Training set size: 142 and Test set size: 36, followed by class distributions for both sets.
Seeing a NameError?
- If you see NameError: name 'wine' is not defined, go back and run Cell 1 (imports) and Cell 2 (load dataset) first. Cells must be run in order.
- If train_test_split is not recognized, make sure Cell 1 ran without errors. It imports train_test_split from sklearn.model_selection.
Why is my train_test_split cell throwing an error?
Scale the features with StandardScaler
The 13 features in the wine dataset are measured on wildly different scales. For example, Proline values go up to around 1680 while Nonflavanoid phenols sit below 1.
Some algorithms (like K-Nearest Neighbors) judge similarity by distance. Without scaling, Proline would dominate every distance calculation just because its numbers are bigger, not because it matters more.
- Create a new cell below Cell 3 and paste the following code:
# Create a scaler and fit it on training data only
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# Check that scaling worked
print("Scaled training data (first row):")
print(X_train_scaled[0].round(2))
What does this code do?
- StandardScaler() creates a scaler that centers each feature to a mean of 0 and a standard deviation of 1.
- fit_transform(X_train) learns the mean and standard deviation from the training data, then transforms it in one step.
- transform(X_test) applies the same scaling values (from training) to the test data. It does NOT re-learn new values from the test set.
Why fit the scaler on training data only?
If you fit the scaler on the entire dataset (training + test), information from the test set leaks into the training process. Your model's accuracy would look artificially high because the scaler already "saw" the test data.
By fitting on training data only, the test set stays truly unseen. This gives you an honest measure of how the model performs on new data.
- Run the cell by pressing Shift+Enter.
- You should see a row of decimal values, mostly between -2 and 2. These are the first training sample's features after scaling.
Seeing a NameError for X_train?
- If you see NameError: name 'X_train' is not defined, go back and run Cell 3 (the split cell) first. Each cell depends on the variables created in the cells above it.
- If the output shows all zeros or identical values, double-check that you typed fit_transform for the training data and transform (without fit) for the test data.
Why is my StandardScaler cell throwing an error?
✔️ Awesome, I've got everything!
Great. Double check that you've saved your notebook before moving on.
ⓧ I'd like to double check the full code
Here is the full notebook up to this point. Make sure your Cells 1 through 4 match.
# Cell 1 - Imports
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, ConfusionMatrixDisplay
# Cell 2 - Load and explore the dataset
wine = load_wine(as_frame=True)
df = wine.frame
print("Dataset shape:", df.shape)
print("\nFirst 5 rows:")
print(df.head())
print("\nSummary statistics:")
print(df.describe())
print("\nClass distribution:")
print(df["target"].value_counts())
print("\nFeature names:", wine.feature_names)
print("Target names:", list(wine.target_names))
# Cell 3 - Split the data
X = wine.data
y = wine.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print("Training set size:", X_train.shape[0])
print("Test set size:", X_test.shape[0])
print("\nTraining class distribution:")
print(y_train.value_counts())
print("\nTest class distribution:")
print(y_test.value_counts())
# Cell 4 - Scale the features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
print("Scaled training data (first row):")
print(X_train_scaled[0].round(2))
Your data is split and scaled. Next up, you'll train three different machine learning models on this data and find out which one is the best at classifying wines.
Train and Compare Three Models
Your data is split, scaled, and ready to go. Now comes the exciting part: training machine learning models to actually predict wine types from those chemical measurements.
Different algorithms learn patterns in different ways. In this step, you'll train three classifiers on the same data and compare which one predicts most accurately.
In this step, get ready to:
- Train three classification models on your scaled training data.
- Compare each model's accuracy on the test set.
- Visualize the results with a bar chart.
Train three classifiers and compare their accuracy
Each of the three classifiers you'll train takes a fundamentally different approach to predicting wine types from the same scaled features.
- Decision Tree learns a series of yes/no decision rules from your features ("Is alcohol above 13? Is color intensity above 4?").
- K-Nearest Neighbors (KNN) classifies a wine sample by finding the 5 closest training samples and going with their majority class.
- Random Forest builds 100 different decision trees and combines their votes for a more robust prediction.
Why three different algorithms?
No single algorithm is best for every dataset. A Decision Tree is easy to interpret but can overfit. KNN is simple and distance-based, which is why feature scaling mattered in the previous step. A Random Forest combines many trees to reduce overfitting.
By training all three on the same data, you can directly compare their strengths on this specific problem.
- Add a new cell below Cell 4 in wine_classifier.ipynb and paste the following code:
# Store three classifiers in a dictionary for easy comparison
models = {
"Decision Tree": DecisionTreeClassifier(random_state=42),
"KNN": KNeighborsClassifier(n_neighbors=5),
"Random Forest": RandomForestClassifier(n_estimators=100, random_state=42),
}
# Dictionary to store each model's accuracy
results = {}
# Train each model, make predictions, and calculate accuracy
for name, model in models.items():
model.fit(X_train_scaled, y_train)
predictions = model.predict(X_test_scaled)
accuracy = accuracy_score(y_test, predictions)
results[name] = accuracy
print(f"{name}: {accuracy:.4f}")
What does this code do?
- models is a dictionary that maps a human-readable name to each classifier object. This lets you loop through all three models with a single for loop.
- .fit() trains each model on the scaled training data. .predict() then generates predictions on the test set that the model has never seen.
- accuracy_score() compares predictions to the actual labels (y_test) and returns the fraction of correct predictions as a decimal between 0 and 1.
- results stores each model's accuracy so you can compare and visualize them in the next cell.
- Run the cell by pressing Shift+Enter.
- You should see three lines of output, each showing a model name followed by an accuracy score (a decimal like 0.9722). KNN and Random Forest typically score higher than the Decision Tree on this dataset.
Not seeing accuracy scores?
- Make sure you ran Cells 1 through 4 first. This cell depends on X_train_scaled, X_test_scaled, y_train, and y_test from earlier cells.
- Check for a NameError in the output. This means a variable from a previous cell is missing. Restart your kernel and run all cells from the top.
- help me debug my model training cell
Visualize the accuracy comparison
Numbers are useful, but a chart makes the comparison instantly clear. You'll use matplotlib to create a bar chart that shows each model's accuracy side by side.
- Add a new cell below Cell 5 and paste the following code:
# Create a bar chart comparing model accuracies
plt.figure(figsize=(8, 5))
plt.bar(results.keys(), results.values(), color=["#2196F3", "#4CAF50", "#FF9800"])
plt.ylabel("Accuracy")
plt.title("Model Accuracy Comparison")
plt.ylim(0.8, 1.0)
plt.tight_layout()
plt.show()
What does this code do?
- plt.figure(figsize=(8, 5)) creates a chart canvas that's 8 inches wide and 5 inches tall.
- plt.bar() draws one bar for each model using the names and accuracy scores from your results dictionary. Each bar gets a distinct color.
- plt.ylim(0.8, 1.0) zooms the y-axis into the 0.8 to 1.0 range. This makes small accuracy differences between models much easier to spot.
- Run the cell by pressing Shift+Enter.
- You should see a bar chart with three colored bars. The chart clearly shows which model achieves the highest accuracy on your wine test data.
Not seeing a chart?
- If you see a NameError mentioning results, make sure you ran Cell 5 first. The results dictionary is created there.
- If the chart appears but looks empty or has no bars, check that the color list has exactly three entries matching the three models.
- help me debug my matplotlib bar chart
✔️ Awesome, I've got everything!
Great. Double check you've saved your notebook before moving on.
ⓧ I'd like to double check the full code
Here is the complete notebook code through Cell 6. Compare each cell against yours:
# Cell 1 - Imports
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, ConfusionMatrixDisplay
# Cell 2 - Load and explore the dataset
wine = load_wine(as_frame=True)
df = wine.frame
print("Dataset shape:", df.shape)
print("\nFirst 5 rows:")
print(df.head())
print("\nSummary statistics:")
print(df.describe())
print("\nClass distribution:")
print(df["target"].value_counts())
print("\nFeature names:", wine.feature_names)
print("Target names:", list(wine.target_names))
# Cell 3 - Split the data
X = wine.data
y = wine.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print("Training set size:", X_train.shape[0])
print("Test set size:", X_test.shape[0])
print("\nTraining class distribution:")
print(y_train.value_counts())
print("\nTest class distribution:")
print(y_test.value_counts())
# Cell 4 - Scale the features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
print("Scaled training data (first row):")
print(X_train_scaled[0].round(2))
# Cell 5 - Train three models and compare accuracy
models = {
"Decision Tree": DecisionTreeClassifier(random_state=42),
"KNN": KNeighborsClassifier(n_neighbors=5),
"Random Forest": RandomForestClassifier(n_estimators=100, random_state=42),
}
results = {}
for name, model in models.items():
model.fit(X_train_scaled, y_train)
predictions = model.predict(X_test_scaled)
accuracy = accuracy_score(y_test, predictions)
results[name] = accuracy
print(f"{name}: {accuracy:.4f}")
# Cell 6 - Plot accuracy comparison
plt.figure(figsize=(8, 5))
plt.bar(results.keys(), results.values(), color=["#2196F3", "#4CAF50", "#FF9800"])
plt.ylabel("Accuracy")
plt.title("Model Accuracy Comparison")
plt.ylim(0.8, 1.0)
plt.tight_layout()
plt.show()
You've trained three models and can see which one performs best at a glance. Next up, you'll dive deeper into your top model's performance and see exactly which wine classes it gets right and wrong.
Evaluate Your Best Model
Your three models are trained, and the bar chart from the previous step shows which one came out on top. But a single accuracy number only tells you part of the story.
In this step, you'll dig deeper into your best model's performance. You'll generate a detailed report breaking down its accuracy per class, visualize its predictions with a confusion matrix, and use it to classify a brand new wine sample.
In this step, get ready to:
- Generate a classification report for the best model.
- Visualize the confusion matrix.
- Predict the class of a new wine sample.
Generate a classification report for the best model
Accuracy tells you the overall percentage of correct predictions. A classification report goes further by showing how well the model performs on each individual class.
- Create a new cell in wine_classifier.ipynb and paste the following code:
# Find the model with the highest accuracy
best_model_name = max(results, key=results.get)
best_model = models[best_model_name]
print(f"Best model: {best_model_name} ({results[best_model_name]:.4f})\n")
# Generate predictions on the test set
y_pred = best_model.predict(X_test_scaled)
# Print precision, recall, and F1-score for each class
print("Classification Report:")
print(classification_report(y_test, y_pred, target_names=wine.target_names))
What does this code do?
max(results, key=results.get) finds the model name with the highest accuracy score from the results dictionary you built in the previous step.
classification_report breaks down the model's performance into three metrics for each wine class. Precision measures how many of the model's predictions for a class were actually correct. Recall measures how many of that class's actual samples the model found. F1-score is the balance between precision and recall.
- Run the cell by pressing Shift+Enter.
- You should see the best model's name and accuracy printed first, followed by a table with precision, recall, and F1-score columns for each of the three wine classes (class_0, class_1, class_2).
Seeing a NameError?
- If you see NameError: name 'results' is not defined, go back and run Cell 5 (the cell that trains all three models) first.
- Make sure all previous cells (1 through 6) have been run in order. The cell numbers in the left margin should show sequential numbers, not empty brackets.
Help me debug my classification report cell.
Visualize the confusion matrix
The classification report gives you numbers. A confusion matrix gives you a picture of the same information, making it easy to spot exactly which classes the model confuses with each other.
- Create a new cell below and paste the following code:
# Create a confusion matrix visualization
fig, ax = plt.subplots(figsize=(6, 5))
ConfusionMatrixDisplay.from_predictions(
y_test, y_pred, display_labels=wine.target_names, ax=ax, cmap="Blues"
)
plt.title(f"Confusion Matrix - {best_model_name}")
plt.tight_layout()
plt.show()
What does this code do?
ConfusionMatrixDisplay.from_predictions builds a grid where each row represents the actual class and each column represents the predicted class. The numbers in each cell tell you how many samples landed in that combination.
The diagonal (top-left to bottom-right) shows correct predictions. Any numbers off the diagonal are mistakes. The cmap="Blues" parameter uses a blue color scale so higher values appear darker, making patterns easy to spot at a glance.
- Run the cell by pressing Shift+Enter.
- You should see a grid with class_0, class_1, and class_2 on both axes. The diagonal cells should have the highest numbers, meaning most predictions are correct.
Confusion matrix not showing?
- Make sure Cell 7 (the classification report cell) has been run first. The y_pred variable is created there and needed here.
- If you see a blank output, check that plt.show() is included at the end of the cell.
Help me debug my confusion matrix visualization.
Predict on a new wine sample
Your model has only been evaluated on data from the original dataset. The real test is whether it can classify a wine it has never seen before.
- Create a new cell below and paste the following code to classify a hypothetical wine sample:
# Create a hypothetical wine sample with 13 feature values
new_wine = np.array([[13.0, 2.0, 2.3, 19.0, 100.0, 2.5, 2.0, 0.35, 1.6, 5.0, 1.0, 2.6, 750.0]])
# Scale the sample using the same scaler from training
new_wine_scaled = scaler.transform(new_wine)
# Predict the wine class
prediction = best_model.predict(new_wine_scaled)
print(f"Predicted class: {wine.target_names[prediction[0]]}")
What does this code do?
The np.array creates a NumPy array with 13 values, one for each chemical feature in the wine dataset (alcohol, malic acid, ash, and so on).
Before predicting, you scale this new sample with the same scaler you fit on the training data. This is critical. The model learned from scaled features, so new data must be scaled the same way for the prediction to make sense.
- Run the cell by pressing Shift+Enter.
- You should see a predicted class name printed, such as Predicted class: class_0. The exact class depends on which model won and how it interprets these feature values.
Seeing a ValueError about feature count?
- Make sure the new_wine array has exactly 13 values inside the double brackets. The wine dataset has 13 features, so your sample must match.
- Check that the values are separated by commas and wrapped in double square brackets: [[...]]. The outer brackets create the array, and the inner brackets define one row.
Help me fix my wine prediction code.
Why scale new data with the training scaler?
When you called scaler.fit_transform(X_train) earlier, the scaler learned the mean and standard deviation of each feature from the training data. Calling scaler.transform(new_wine) applies those same learned values to the new sample.
If you created a new scaler and fit it on just the single sample, the scaling would be completely different and your prediction would be meaningless.
✔️ Awesome, I've got everything!
Great. Double check you've saved your notebook before moving on.
ⓧ I'd like to double check the full code
Here is the complete notebook up to this point. Compare each cell against yours:
# Cell 1 - Imports
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report, ConfusionMatrixDisplay
# Cell 2 - Load and explore the dataset
wine = load_wine(as_frame=True)
df = wine.frame
print("Dataset shape:", df.shape)
print("\nFirst 5 rows:")
print(df.head())
print("\nSummary statistics:")
print(df.describe())
print("\nClass distribution:")
print(df["target"].value_counts())
print("\nFeature names:", wine.feature_names)
print("Target names:", list(wine.target_names))
# Cell 3 - Split the data
X = wine.data
y = wine.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print("Training set size:", X_train.shape[0])
print("Test set size:", X_test.shape[0])
print("\nTraining class distribution:")
print(y_train.value_counts())
print("\nTest class distribution:")
print(y_test.value_counts())
# Cell 4 - Scale the features
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
print("Scaled training data (first row):")
print(X_train_scaled[0].round(2))
# Cell 5 - Train three models and compare accuracy
models = {
"Decision Tree": DecisionTreeClassifier(random_state=42),
"KNN": KNeighborsClassifier(n_neighbors=5),
"Random Forest": RandomForestClassifier(n_estimators=100, random_state=42),
}
results = {}
for name, model in models.items():
model.fit(X_train_scaled, y_train)
predictions = model.predict(X_test_scaled)
accuracy = accuracy_score(y_test, predictions)
results[name] = accuracy
print(f"{name}: {accuracy:.4f}")
# Cell 6 - Plot accuracy comparison
plt.figure(figsize=(8, 5))
plt.bar(results.keys(), results.values(), color=["#2196F3", "#4CAF50", "#FF9800"])
plt.ylabel("Accuracy")
plt.title("Model Accuracy Comparison")
plt.ylim(0.8, 1.0)
plt.tight_layout()
plt.show()
# Cell 7 - Evaluate the best model
best_model_name = max(results, key=results.get)
best_model = models[best_model_name]
print(f"Best model: {best_model_name} ({results[best_model_name]:.4f})\n")
y_pred = best_model.predict(X_test_scaled)
print("Classification Report:")
print(classification_report(y_test, y_pred, target_names=wine.target_names))
# Cell 8 - Confusion matrix
fig, ax = plt.subplots(figsize=(6, 5))
ConfusionMatrixDisplay.from_predictions(
y_test, y_pred, display_labels=wine.target_names, ax=ax, cmap="Blues"
)
plt.title(f"Confusion Matrix - {best_model_name}")
plt.tight_layout()
plt.show()
# Cell 9 - Predict on new data
new_wine = np.array([[13.0, 2.0, 2.3, 19.0, 100.0, 2.5, 2.0, 0.35, 1.6, 5.0, 1.0, 2.6, 750.0]])
new_wine_scaled = scaler.transform(new_wine)
prediction = best_model.predict(new_wine_scaled)
print(f"Predicted class: {wine.target_names[prediction[0]]}")
Secret mission
Hyperparameter Tuning with GridSearchCV
Your Random Forest model is already performing well. But how do you know if its default settings are truly the best? Use scikit-learn's GridSearchCV to automatically test different hyperparameter combinations and find the optimal configuration for your model.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- A Python virtual environment with scikit-learn, pandas, and matplotlib installed.
- The wine_classifier.ipynb notebook file on your Desktop.
Keep everything running
No action needed. Choose this if you're still actively experimenting with your wine classifier or want to revisit the notebook later.
- Your virtual environment and notebook stay on your Desktop, ready to use anytime.
- To pick up where you left off, activate your virtual environment and open wine_classifier.ipynb in VS Code.
Pause - I'll come back to this later
Shut down any running processes to free up memory, but keep your files for later.
- If your virtual environment is still active in the terminal, deactivate it by running:
deactivate
- Close the wine_classifier.ipynb tab in VS Code.
- Your project folder and virtual environment stay on your Desktop. To resume, activate the virtual environment again and reopen the notebook.
Delete - I don't want to use this again
Remove all project files and the virtual environment from your machine.
- If your virtual environment is still active, deactivate it first by running:
deactivate
- Delete the project folder from your Desktop. Open your Desktop in Finder (macOS) or File Explorer (Windows).
- Find the folder that contains your wine_classifier.ipynb file. Right-click it and select Move to Trash (macOS) or Delete (Windows).
- This removes your notebook, virtual environment, and all installed packages in one step.
Nice Work!
Nice Work!
You just built a complete machine learning classification pipeline from scratch. Your Jupyter Notebook can now load real data, train models, compare their performance, and make predictions on new samples.
You've learned how to:
- Load and explore a real-world dataset with pandas, then prepare it for training with train/test splitting and feature scaling.
- Train and compare three classification models (Decision Tree, K-Nearest Neighbors, and Random Forest) side by side using scikit-learn.
- Evaluate your best model with a confusion matrix and make predictions on new data using your trained classifier.
- Secret Mission: Tuned hyperparameters with GridSearchCV to find the optimal model configuration and squeeze out better accuracy.
Ready to quiz yourself?