Wine Quality¶
This example uses the Wine Quality dataset to predict the quality of white wine. You can download the Jupyter Notebook of the study here.
fixed acidity
volatile acidity
citric acid
residual sugar
total sulfur dioxide
free sulfur dioxide
density
pH
sulphates
alcohol
quality (score between 0 and 10)
We will follow the data science cycle (Data Exploration - Data Preparation - Data Modeling - Model Evaluation - Model Deployment) to solve this problem.
Initialization¶
This example uses the following version of VerticaPy:
import verticapy as vp
vp.__version__
Out[2]: '1.1.0'
Connect to Vertica. This example uses an existing connection called VerticaDSN.
For details on how to create a connection, see the Connection tutorial.
You can skip the below cell if you already have an established connection.
vp.connect("VerticaDSN")
Let’s create a Virtual DataFrame of the dataset.
from verticapy.datasets import load_winequality
winequality = load_winequality()
winequality.head(5)
123 fixed_acidity100% | ... | 123 good100% | Abc color100% | |
| 1 | 3.9 | ... | 1 | white |
| 2 | 4.7 | ... | 0 | white |
| 3 | 4.7 | ... | 1 | white |
| 4 | 4.7 | ... | 0 | white |
| 5 | 4.9 | ... | 0 | white |
Data Exploration and Preparation¶
Let’s explore the data by displaying descriptive statistics of all the columns.
winequality.describe()
| ... | approx_75% | max | |
| "fixed_acidity" | ... | 7.7 | 15.9 |
| "volatile_acidity" | ... | 0.4 | 1.58 |
| "citric_acid" | ... | 0.39 | 1.66 |
| "residual_sugar" | ... | 8.1 | 65.8 |
| "chlorides" | ... | 0.065 | 0.611 |
| "free_sulfur_dioxide" | ... | 41.0 | 289.0 |
| "total_sulfur_dioxide" | ... | 156.0 | 440.0 |
| "density" | ... | 0.99699 | 1.03898 |
| "pH" | ... | 3.32 | 4.01 |
| "sulphates" | ... | 0.6 | 2.0 |
| "alcohol" | ... | 11.3 | 14.9 |
| "quality" | ... | 6.0 | 9.0 |
| "good" | ... | 0.0 | 1.0 |
- The quality of a wine is based on the equilibrium between certain components:
For red wines: tannin/smoothness/acidity
For white wines: smoothness/acidity
Based on this, we don’t have the data to create a good model for red wines (the tannins weren’t extracted). We do, however, have enough data to make a good model for white wines, so let’s filter out red wines from our study.
winequality.filter(winequality["color"] == "white").drop(["good", "color"])
123 fixed_acidity100% | ... | 123 volatile_acidity100% | 123 quality100% | |
| 1 | 4.4 | ... | 0.32 | 8 |
| 2 | 4.4 | ... | 0.46 | 6 |
| 3 | 4.4 | ... | 0.54 | 7 |
| 4 | 4.7 | ... | 0.67 | 5 |
| 5 | 4.8 | ... | 0.33 | 5 |
| 6 | 4.8 | ... | 0.34 | 6 |
| 7 | 4.9 | ... | 0.47 | 6 |
| 8 | 5.0 | ... | 0.27 | 7 |
| 9 | 5.0 | ... | 0.27 | 7 |
| 10 | 5.0 | ... | 0.29 | 8 |
| 11 | 5.0 | ... | 0.33 | 6 |
| 12 | 5.0 | ... | 0.55 | 8 |
| 13 | 5.0 | ... | 0.61 | 5 |
| 14 | 5.1 | ... | 0.21 | 5 |
| 15 | 5.1 | ... | 0.23 | 5 |
| 16 | 5.1 | ... | 0.25 | 7 |
| 17 | 5.1 | ... | 0.26 | 6 |
| 18 | 5.1 | ... | 0.35 | 6 |
| 19 | 5.1 | ... | 0.35 | 6 |
| 20 | 5.1 | ... | 0.35 | 6 |
Let’s draw the correlation matrix of the dataset.
winequality.corr(method = "spearman")
We can see a strong correlation between the density and the alcohol degree (the alcohol degree describes the density of pure ethanol in the wine).
We can drop the density column since it doesn’t influence the quality of the white wine (instead, its presence will just bias the data).
winequality.drop(["density"])
123 fixed_acidity100% | ... | 123 alcohol100% | 123 quality100% | |
| 1 | 4.2 | ... | 12.0 | 7 |
| 2 | 4.2 | ... | 8.0 | 3 |
| 3 | 4.5 | ... | 8.0 | 5 |
| 4 | 4.6 | ... | 10.2 | 5 |
| 5 | 4.8 | ... | 11.8 | 7 |
| 6 | 4.8 | ... | 11.3 | 7 |
| 7 | 4.8 | ... | 12.2 | 7 |
| 8 | 4.8 | ... | 10.3 | 6 |
| 9 | 4.8 | ... | 11.9 | 6 |
| 10 | 4.9 | ... | 9.4 | 6 |
| 11 | 4.9 | ... | 14.0 | 8 |
| 12 | 4.9 | ... | 10.4666666666667 | 5 |
| 13 | 4.9 | ... | 10.4666666666667 | 5 |
| 14 | 5.0 | ... | 11.3 | 6 |
| 15 | 5.0 | ... | 13.0 | 7 |
| 16 | 5.0 | ... | 11.8 | 6 |
| 17 | 5.0 | ... | 14.0 | 7 |
| 18 | 5.1 | ... | 12.3333333333333 | 5 |
| 19 | 5.1 | ... | 10.1 | 6 |
| 20 | 5.1 | ... | 11.0 | 7 |
We’re working with the scores given by wine tasters, so it’s likely that two closely competing wines will have a similar score. Knowing this, a k-nearest neighbors (KNN) model would be best.
KNN is sensitive to unnormalized data so we’ll have to normalize our data.
winequality.normalize(
[
"free_sulfur_dioxide",
"residual_sugar",
"pH",
"sulphates",
"volatile_acidity",
"fixed_acidity",
"citric_acid",
"chlorides",
"total_sulfur_dioxide",
"alcohol"
],
method = "robust_zscore",
)
123 fixed_acidity100% | ... | 123 alcohol100% | 123 quality100% | |
| 1 | -3.237555645487657 | ... | 1.61877782274383 | 8 |
| 2 | -3.237555645487657 | ... | 1.82112505058681 | 6 |
| 3 | -3.237555645487657 | ... | 1.21408336705787 | 7 |
| 4 | -2.8328611898017 | ... | 2.1583704303251 | 5 |
| 5 | -2.697963037906381 | ... | -0.337245379738298 | 5 |
| 6 | -2.697963037906381 | ... | -0.337245379738298 | 6 |
| 7 | -2.563064886011062 | ... | 0.741939835424254 | 6 |
| 8 | -2.428166734115743 | ... | 1.48387967084851 | 7 |
| 9 | -2.428166734115743 | ... | 1.48387967084851 | 7 |
| 10 | -2.428166734115743 | ... | 1.68622689869149 | 8 |
| 11 | -2.428166734115743 | ... | 0.404694455685957 | 6 |
| 12 | -2.428166734115743 | ... | 1.41643059490085 | 8 |
| 13 | -2.428166734115743 | ... | 2.09092135437744 | 5 |
| 14 | -2.293268582220424 | ... | 0.0 | 5 |
| 15 | -2.293268582220424 | ... | 0.741939835424254 | 5 |
| 16 | -2.293268582220424 | ... | 1.14663429111021 | 7 |
| 17 | -2.293268582220424 | ... | -0.809388911371915 | 6 |
| 18 | -2.293268582220424 | ... | 0.741939835424254 | 6 |
| 19 | -2.293268582220424 | ... | 0.741939835424254 | 6 |
| 20 | -2.293268582220424 | ... | 0.741939835424254 | 6 |
Machine Learning¶
Let’s create our KNN model.
from verticapy.machine_learning.vertica import KNeighborsRegressor
from verticapy.machine_learning.model_selection import cross_validate
predictors = winequality.get_columns(exclude_columns = ["quality"])
model = KNeighborsRegressor(name = "winequality_KNN", n_neighbors = 50)
cross_validate(model, winequality, predictors, "quality")
| ... | bic | time | |
| 1-fold | ... | -961.534591442771 | 0.6060566902160645 |
| 2-fold | ... | -849.682403020094 | 0.586998701095581 |
| 3-fold | ... | -919.764039948311 | 0.5348219871520996 |
| avg | ... | -910.327011470392 | 0.5759591261545817 |
| std | ... | 46.148464355854095 | 0.03011090490515871 |
Our model is pretty good. Our predicted scores have a median absolute error of less than 0.5. If we want to improve this model, we’ll probably need more relevant features.
Conclusion¶
We’ve solved our problem in a Pandas-like way, all without ever loading data into memory!