Wine Quality¶
This example uses the Wine Quality dataset to predict the quality of white wine.
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 vastorbit:
import vastorbit as vo
vo.__version__
Connect to VAST. This example uses an existing connection called VASTDSN.
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.
vo.connect("VASTDSN")
Let’s create a VastFrame of the dataset.
from vastorbit.datasets import load_winequality
winequality = load_winequality()
winequality.head(5)
123 fixed_acidityDecimal(6,3) | 123 volatile_acidityDecimal(7,4) | 123 citric_acidDecimal(6,3) | 123 residual_sugarDecimal(7,3) | 123 chloridesDouble | 123 free_sulfur_dioxideDecimal(7,2) | 123 total_sulfur_dioxideDecimal(7,2) | 123 densityDouble | 123 phDecimal(6,3) | 123 sulphatesDecimal(6,3) | 123 alcoholDouble | 123 qualityInteger | 123 goodInteger | Abc colorVarchar(20) | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 6.3 | 0.67 | 0.48 | 12.6 | 0.052 | 57.0 | 222.0 | 0.9979 | 3.17 | 0.52 | 9.3 | 6 | 0 | white |
| 2 | 7.4 | 0.4 | 0.29 | 5.4 | 0.044 | 31.0 | 122.0 | 0.994 | 3.3 | 0.5 | 11.1 | 8 | 1 | white |
| 3 | 7.1 | 0.26 | 0.31 | 2.2 | 0.044 | 29.0 | 128.0 | 0.9937 | 3.34 | 0.64 | 10.9 | 8 | 1 | white |
| 4 | 9.0 | 0.31 | 0.48 | 6.6 | 0.043 | 11.0 | 73.0 | 0.9938 | 2.9 | 0.38 | 11.6 | 5 | 0 | white |
| 5 | 6.3 | 0.39 | 0.24 | 6.9 | 0.069 | 9.0 | 117.0 | 0.9942 | 3.15 | 0.35 | 10.2 | 4 | 0 | white |
Data Exploration and Preparation¶
Let’s explore the data by displaying descriptive statistics of all the columns.
winequality.describe()
| count | mean | std | min | approx_25% | approx_50% | approx_75% | max | |
|---|---|---|---|---|---|---|---|---|
| "fixed_acidity" | 6497.0 | 7.215 | 1.296433757799814 | 3.8 | 6.422757 | 6.968065 | 7.6656013 | 15.9 |
| "volatile_acidity" | 6497.0 | 0.3397 | 0.16463647408467857 | 0.08 | 0.22848527 | 0.29178292 | 0.40539855 | 1.58 |
| "citric_acid" | 6497.0 | 0.319 | 0.14531786489759152 | 0.0 | 0.24655963 | 0.31071258 | 0.3907722 | 1.66 |
| "residual_sugar" | 6497.0 | 5.443 | 4.757803743147417 | 0.6 | 1.8231287 | 3.1262202 | 8.143649 | 65.8 |
| "chlorides" | 6497.0 | 0.05603386178236098 | 0.03503360137245909 | 0.009 | 0.03823784860061664 | 0.04706431908012936 | 0.0648611000668226 | 0.611 |
| "free_sulfur_dioxide" | 6497.0 | 30.53 | 17.7493997720025 | 1.0 | 17.034159 | 28.748253 | 41.335823 | 289.0 |
| "total_sulfur_dioxide" | 6497.0 | 115.74 | 56.52185452263028 | 6.0 | 77.33673 | 118.71745 | 155.37856 | 440.0 |
| "density" | 6497.0 | 0.9946966338309986 | 0.002998673003719182 | 0.98711 | 0.9923367672715311 | 0.9948342040334558 | 0.9969373620697908 | 1.03898 |
| "ph" | 6497.0 | 3.219 | 0.16078720210398798 | 2.72 | 3.1084259 | 3.2065077 | 3.32104 | 4.01 |
| "sulphates" | 6497.0 | 0.531 | 0.1488058736144904 | 0.22 | 0.4316651 | 0.5065013 | 0.6000579 | 2.0 |
| "alcohol" | 6497.0 | 10.491800831152828 | 1.192711748870993 | 8.0 | 9.485336995965627 | 10.309458060256224 | 11.308362700676847 | 14.9 |
| "quality" | 6497.0 | 5.818377712790519 | 0.8732552715311251 | 3.0 | 5.0 | 6.0 | 6.0 | 9.0 |
| "good" | 6497.0 | 0.19655225488687086 | 0.39742140889536703 | 0.0 | 0.0 | 0.0 | 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_acidityDecimal(6,3) | 123 volatile_acidityDecimal(7,4) | 123 citric_acidDecimal(6,3) | 123 residual_sugarDecimal(7,3) | 123 chloridesDouble | 123 free_sulfur_dioxideDecimal(7,2) | 123 total_sulfur_dioxideDecimal(7,2) | 123 densityDouble | 123 phDecimal(6,3) | 123 sulphatesDecimal(6,3) | 123 alcoholDouble | 123 qualityInteger | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 8.8 | 0.19 | 0.3 | 5.0 | 0.028 | 34.0 | 120.0 | 0.99242 | 2.94 | 0.47 | 11.2 | 5 |
| 2 | 7.7 | 0.46 | 0.18 | 3.3 | 0.054 | 18.0 | 143.0 | 0.99392 | 3.12 | 0.51 | 10.8 | 6 |
| 3 | 8.8 | 0.27 | 0.25 | 5.0 | 0.024 | 52.0 | 99.0 | 0.9925 | 2.87 | 0.49 | 11.4 | 5 |
| 4 | 5.8 | 0.18 | 0.28 | 1.3 | 0.034 | 9.0 | 94.0 | 0.99092 | 3.21 | 0.52 | 11.2 | 6 |
| 5 | 5.8 | 0.15 | 0.32 | 1.2 | 0.037 | 14.0 | 119.0 | 0.99137 | 3.19 | 0.5 | 10.2 | 6 |
| 6 | 5.6 | 0.32 | 0.33 | 7.4 | 0.037 | 25.0 | 95.0 | 0.99268 | 3.25 | 0.49 | 11.1 | 6 |
| 7 | 6.1 | 0.33 | 0.32 | 7.8 | 0.052 | 52.0 | 183.0 | 0.99657 | 3.39 | 0.65 | 9.5 | 5 |
| 8 | 7.1 | 0.32 | 0.3 | 9.9 | 0.041 | 63.0 | 192.0 | 0.99642 | 3.12 | 0.49 | 10.2 | 6 |
| 9 | 6.2 | 0.23 | 0.35 | 0.7 | 0.051 | 24.0 | 111.0 | 0.9916 | 3.37 | 0.43 | 11.0 | 3 |
| 10 | 8.9 | 0.3 | 0.35 | 4.6 | 0.032 | 32.0 | 148.0 | 0.99458 | 3.15 | 0.45 | 11.5 | 7 |
| 11 | 6.0 | 0.14 | 0.17 | 5.6 | 0.036 | 37.0 | 127.0 | 0.99373 | 3.05 | 0.57 | 9.8 | 6 |
| 12 | 6.8 | 0.24 | 0.29 | 9.5 | 0.042 | 56.0 | 157.0 | 0.99586 | 3.11 | 0.51 | 10.1 | 6 |
| 13 | 6.7 | 0.21 | 0.48 | 14.8 | 0.05 | 31.0 | 195.0 | 0.99942 | 2.95 | 0.75 | 8.8 | 6 |
| 14 | 8.9 | 0.3 | 0.35 | 4.6 | 0.032 | 32.0 | 148.0 | 0.99458 | 3.15 | 0.45 | 11.5 | 7 |
| 15 | 6.1 | 0.3 | 0.3 | 2.1 | 0.031 | 50.0 | 163.0 | 0.9895 | 3.39 | 0.43 | 12.7 | 7 |
| 16 | 7.2 | 0.37 | 0.4 | 11.6 | 0.032 | 34.0 | 214.0 | 0.9963 | 3.1 | 0.51 | 9.8 | 6 |
| 17 | 6.7 | 0.64 | 0.3 | 1.2 | 0.03 | 18.0 | 76.0 | 0.9892 | 3.16 | 0.6 | 12.9 | 4 |
| 18 | 7.2 | 0.37 | 0.4 | 11.6 | 0.032 | 34.0 | 214.0 | 0.9963 | 3.1 | 0.51 | 9.8 | 6 |
| 19 | 6.1 | 0.3 | 0.3 | 2.1 | 0.031 | 50.0 | 163.0 | 0.9895 | 3.39 | 0.43 | 12.7 | 7 |
| 20 | 7.6 | 0.28 | 0.49 | 20.15 | 0.06 | 30.0 | 145.0 | 1.00196 | 3.01 | 0.44 | 8.5 | 5 |
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_acidityDecimal(6,3) | 123 volatile_acidityDecimal(7,4) | 123 citric_acidDecimal(6,3) | 123 residual_sugarDecimal(7,3) | 123 chloridesDouble | 123 free_sulfur_dioxideDecimal(7,2) | 123 total_sulfur_dioxideDecimal(7,2) | 123 phDecimal(6,3) | 123 sulphatesDecimal(6,3) | 123 alcoholDouble | 123 qualityInteger | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 6.3 | 0.67 | 0.48 | 12.6 | 0.052 | 57.0 | 222.0 | 3.17 | 0.52 | 9.3 | 6 |
| 2 | 7.4 | 0.4 | 0.29 | 5.4 | 0.044 | 31.0 | 122.0 | 3.3 | 0.5 | 11.1 | 8 |
| 3 | 7.1 | 0.26 | 0.31 | 2.2 | 0.044 | 29.0 | 128.0 | 3.34 | 0.64 | 10.9 | 8 |
| 4 | 9.0 | 0.31 | 0.48 | 6.6 | 0.043 | 11.0 | 73.0 | 2.9 | 0.38 | 11.6 | 5 |
| 5 | 6.3 | 0.39 | 0.24 | 6.9 | 0.069 | 9.0 | 117.0 | 3.15 | 0.35 | 10.2 | 4 |
| 6 | 8.2 | 0.22 | 0.36 | 6.8 | 0.034 | 12.0 | 90.0 | 3.01 | 0.38 | 10.5 | 8 |
| 7 | 7.1 | 0.19 | 0.28 | 3.6 | 0.033 | 16.0 | 78.0 | 2.91 | 0.78 | 11.4 | 6 |
| 8 | 7.3 | 0.25 | 0.36 | 13.1 | 0.05 | 35.0 | 200.0 | 3.04 | 0.46 | 8.9 | 7 |
| 9 | 7.9 | 0.2 | 0.34 | 1.2 | 0.04 | 29.0 | 118.0 | 3.14 | 0.41 | 10.6 | 6 |
| 10 | 7.1 | 0.26 | 0.32 | 5.9 | 0.037 | 39.0 | 97.0 | 3.31 | 0.4 | 11.6 | 6 |
| 11 | 7.0 | 0.2 | 0.34 | 5.7 | 0.035 | 32.0 | 83.0 | 3.19 | 0.46 | 11.5 | 6 |
| 12 | 6.9 | 0.3 | 0.33 | 4.1 | 0.035 | 26.0 | 155.0 | 3.25 | 0.79 | 12.3 | 8 |
| 13 | 8.1 | 0.29 | 0.49 | 7.1 | 0.042 | 22.0 | 124.0 | 3.14 | 0.41 | 10.8 | 6 |
| 14 | 5.8 | 0.17 | 0.3 | 1.4 | 0.037 | 55.0 | 130.0 | 3.29 | 0.38 | 11.3 | 6 |
| 15 | 5.9 | 0.415 | 0.02 | 0.8 | 0.038 | 22.0 | 63.0 | 3.36 | 0.36 | 9.3 | 5 |
| 16 | 6.6 | 0.23 | 0.26 | 1.3 | 0.045 | 16.0 | 128.0 | 3.36 | 0.6 | 10.0 | 6 |
| 17 | 8.6 | 0.55 | 0.35 | 15.55 | 0.057 | 35.5 | 366.5 | 3.04 | 0.63 | 11.0 | 3 |
| 18 | 6.9 | 0.35 | 0.74 | 1.0 | 0.044 | 18.0 | 132.0 | 3.13 | 0.55 | 10.2 | 5 |
| 19 | 7.6 | 0.14 | 0.74 | 1.6 | 0.04 | 27.0 | 103.0 | 3.07 | 0.4 | 10.8 | 7 |
| 20 | 9.2 | 0.28 | 0.49 | 11.8 | 0.042 | 29.0 | 137.0 | 3.1 | 0.34 | 10.1 | 4 |
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_acidityReal | 123 volatile_acidityReal | 123 citric_acidReal | 123 residual_sugarReal | 123 chloridesReal | 123 free_sulfur_dioxideReal | 123 total_sulfur_dioxideReal | 123 phReal | 123 sulphatesReal | 123 alcoholReal | 123 qualityInteger | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | -0.6263393225607145 | 4.911155783031988 | 1.9605633466538805 | 1.336724760954758 | 0.8425373442974716 | 1.4456305327191328 | 2.016276711787808 | -0.046518225450507766 | 0.4414422660014937 | -0.7560787364264695 | 6 |
| 2 | 0.7791104333461998 | 1.6587446267190056 | -0.2999106476710552 | 0.021945258586426153 | 0.10323904098655225 | -0.15602509850974183 | -0.2838021278673736 | 0.8603785845680894 | 0.2492828864934583 | 0.5384354479461952 | 8 |
| 3 | 0.3958059544624959 | -0.02769078766550382 | -0.06196601668948299 | -0.5624011869106101 | 0.10323904098655225 | -0.27922937783503987 | -0.1457973974880627 | 1.1394237568815038 | 1.594398543049706 | 0.3946005385714551 | 8 |
| 4 | 2.8234009873926205 | 0.574607574614678 | 1.9605633466538805 | 0.24107517564781478 | 0.01082675307268724 | -1.3880678917627223 | -1.4108407592984127 | -1.9300731385660557 | -0.9036733905547539 | 0.8980227213830467 | 5 |
| 5 | -0.6263393225607145 | 1.538284954262969 | -0.8947722251249857 | 0.2958576549131619 | 2.413546238833176 | -1.5112721710880204 | -0.3988060698501327 | -0.186040811607215 | -1.1919124598168072 | -0.10882164424013777 | 4 |
| 6 | 1.8012557103694102 | -0.5095294774896494 | 0.5328955607644474 | 0.27759682849137957 | -0.8208838381520965 | -1.3264657521000733 | -1.0198273565570317 | -1.1626989147041658 | -0.9036733905547539 | 0.10693071982197365 | 8 |
| 7 | 0.3958059544624959 | -0.8709084948577585 | -0.41888296316184126 | -0.3067496170056568 | -0.9132961260659616 | -1.0800571934494771 | -1.2958368173156536 | -1.860311845487702 | 2.9395141996059535 | 0.7541878120083066 | 6 |
| 8 | 0.6513422737182984 | -0.1481504601215402 | 0.5328955607644474 | 1.4280288930636698 | 0.6577127684697422 | 0.09038346014085426 | 1.5102593670636681 | -0.9534150354691049 | -0.13503587252261245 | -1.0437485551759509 | 7 |
| 9 | 1.4179512314857063 | -0.7504488224017221 | 0.2949509297828753 | -0.7450094511284341 | -0.26641011066890713 | -0.27922937783503987 | -0.3758052814535808 | -0.25580210468556863 | -0.6154343212927009 | 0.1788481745093437 | 6 |
| 10 | 0.3958059544624959 | -0.02769078766550382 | 0.0570062988013031 | 0.11324939069533808 | -0.5436469744105021 | 0.3367920187914504 | -0.858821837781169 | 0.930139877646443 | -0.7115140110467186 | 0.8980227213830467 | 6 |
| 11 | 0.2680377948345946 | -0.7504488224017221 | 0.2949509297828753 | 0.07672773785177331 | -0.7284715502382315 | -0.09442295884709281 | -1.1808328753328945 | 0.09300436070619948 | -0.13503587252261245 | 0.8261052666956766 | 6 |
| 12 | 0.1402696352066933 | 0.4541479021586417 | 0.1759786142920892 | -0.21544548489674487 | -0.7284715502382315 | -0.46403579682298696 | 0.4752238892188364 | 0.5115721191763213 | 3.0355938893599714 | 1.4014449041946395 | 8 |
| 13 | 1.6734875507415088 | 0.3336882297026053 | 2.0795356621446666 | 0.3323793077567267 | -0.08158553484117712 | -0.7104443554735831 | -0.2378005510742699 | -0.25580210468556863 | -0.6154343212927009 | 0.32268308388408506 | 6 |
| 14 | -1.265180120700221 | -1.1118278397698313 | -0.18093833218026908 | -0.7084877982848693 | -0.5436469744105021 | 1.3224262533938347 | -0.099795820694959 | 0.7906172914897357 | -0.9036733905547539 | 0.6822703573209365 | 6 |
| 15 | -1.1374119610723197 | 1.83943413540306 | -3.5121631659222796 | -0.8180527568155636 | -0.4512346864966371 | -0.7104443554735831 | -1.6408486432639309 | 1.278946343038211 | -1.0958327700627895 | -0.7560787364264695 | 5 |
| 16 | -0.2430348436770106 | -0.38906980503361294 | -0.6568275941434134 | -0.7267486247066517 | 0.19565132890041725 | -1.0800571934494771 | -0.1457973974880627 | 1.278946343038211 | 1.2100797840336353 | -0.2526565536148779 | 6 |
| 17 | 2.3123283488810156 | 3.4656397135595514 | 0.41392324527366137 | 1.8754191403973384 | 1.3045987838667967 | 0.12118452997217878 | 5.339890635089546 | -0.9534150354691049 | 1.4983188532956884 | 0.46651799325882515 | 3 |
| 18 | 0.1402696352066933 | 1.0564462644388235 | 5.053843549414319 | -0.7815311039719988 | 0.10323904098655225 | -0.9568529141241792 | -0.0537942439018554 | -0.32556339776392224 | 0.7296813352635468 | -0.10882164424013777 | 5 |
| 19 | 1.0346467526020025 | -1.4732068571379404 | 5.053843549414319 | -0.6719661454413045 | -0.26641011066890713 | -0.4024336571603379 | -0.7208171074018581 | -0.744131156234044 | -0.7115140110467186 | 0.32268308388408506 | 7 |
| 20 | 3.0789373066484234 | 0.21322855724656894 | 2.0795356621446666 | 1.1906381495804987 | -0.08158553484117712 | -0.27922937783503987 | 0.0612096980809037 | -0.5348472769989832 | -1.2879921495708246 | -0.1807390989275078 | 4 |
Machine Learning¶
Let’s create our KNN model.
from vastorbit.machine_learning.vast import KNeighborsRegressor
from vastorbit.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")
| explained_variance | max_error | median_absolute_error | mean_absolute_error | mean_squared_error | root_mean_squared_error | r2 | r2_adj | aic | bic | time | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1-fold | 7.771561172376096e-16 | 2.62 | 0.6200000000000001 | 0.7688944099378929 | 0.8388720496894408 | 0.9339703534470446 | -0.08851703309372083 | -0.09532451922939145 | -260.5556650591127 | -201.6484268286082 | 0.0002968311309814453 |
| 2-fold | -2.220446049250313e-16 | 3.34 | 0.6572532188841202 | 0.8502504581551539 | 0.8313092241905929 | 1.0303272991195274 | -0.2889521080685411 | -0.29687924280450995 | -280.1300005990673 | -221.0345588633694 | 0.0028929710388183594 |
| 3-fold | 1.1102230246251565e-15 | 3.0 | 0.7762824850261885 | 0.6267308850090307 | 0.791559060806743 | 0.8951673046482754 | -0.015724310680232723 | -0.021880215593446106 | -365.9531989405429 | -306.693125372759 | 0.00356292724609375 |
| avg | 5.551115123125783e-16 | 2.986666666666667 | 0.6845119013034363 | 0.7486252510340258 | 0.820580111562259 | 0.9531549857382825 | -0.13106448394749823 | -0.13802799254244916 | -302.212954866241 | -243.1253703549122 | 0.0022509098052978516 |
| std | 5.661048867003676e-16 | 0.29408993333483696 | 0.06664998428065853 | 0.0923701935938895 | 0.020751949669001866 | 0.056821906652743934 | 0.1155308659970214 | 0.11625777742157621 | 45.77410249581722 | 45.64062618124369 | 0.001408551958331754 |
Our model is good enough for the use case. Our predicted scores have a median absolute error of less than 1. 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!