Loading...

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_acidity
Decimal(6,3)
100%
123
volatile_acidity
Decimal(7,4)
100%
123
citric_acid
Decimal(6,3)
100%
123
residual_sugar
Decimal(7,3)
100%
123
chlorides
Double
100%
123
free_sulfur_dioxide
Decimal(7,2)
100%
123
total_sulfur_dioxide
Decimal(7,2)
100%
123
density
Double
100%
123
ph
Decimal(6,3)
100%
123
sulphates
Decimal(6,3)
100%
123
alcohol
Double
100%
123
quality
Integer
100%
123
good
Integer
100%
Abc
color
Varchar(20)
100%
16.30.670.4812.60.05257.0222.00.99793.170.529.360white
27.40.40.295.40.04431.0122.00.9943.30.511.181white
37.10.260.312.20.04429.0128.00.99373.340.6410.981white
49.00.310.486.60.04311.073.00.99382.90.3811.650white
56.30.390.246.90.0699.0117.00.99423.150.3510.240white

Data Exploration and Preparation

Let’s explore the data by displaying descriptive statistics of all the columns.

winequality.describe()
countmeanstdminapprox_25%approx_50%approx_75%max
"fixed_acidity"6497.07.2151.2964337577998143.86.4227576.9680657.665601315.9
"volatile_acidity"6497.00.33970.164636474084678570.080.228485270.291782920.405398551.58
"citric_acid"6497.00.3190.145317864897591520.00.246559630.310712580.39077221.66
"residual_sugar"6497.05.4434.7578037431474170.61.82312873.12622028.14364965.8
"chlorides"6497.00.056033861782360980.035033601372459090.0090.038237848600616640.047064319080129360.06486110006682260.611
"free_sulfur_dioxide"6497.030.5317.74939977200251.017.03415928.74825341.335823289.0
"total_sulfur_dioxide"6497.0115.7456.521854522630286.077.33673118.71745155.37856440.0
"density"6497.00.99469663383099860.0029986730037191820.987110.99233676727153110.99483420403345580.99693736206979081.03898
"ph"6497.03.2190.160787202103987982.723.10842593.20650773.321044.01
"sulphates"6497.00.5310.14880587361449040.220.43166510.50650130.60005792.0
"alcohol"6497.010.4918008311528281.1927117488709938.09.48533699596562710.30945806025622411.30836270067684714.9
"quality"6497.05.8183777127905190.87325527153112513.05.06.06.09.0
"good"6497.00.196552254886870860.397421408895367030.00.00.00.01.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_acidity
Decimal(6,3)
100%
123
volatile_acidity
Decimal(7,4)
100%
123
citric_acid
Decimal(6,3)
100%
123
residual_sugar
Decimal(7,3)
100%
123
chlorides
Double
100%
123
free_sulfur_dioxide
Decimal(7,2)
100%
123
total_sulfur_dioxide
Decimal(7,2)
100%
123
density
Double
100%
123
ph
Decimal(6,3)
100%
123
sulphates
Decimal(6,3)
100%
123
alcohol
Double
100%
123
quality
Integer
100%
18.80.190.35.00.02834.0120.00.992422.940.4711.25
27.70.460.183.30.05418.0143.00.993923.120.5110.86
38.80.270.255.00.02452.099.00.99252.870.4911.45
45.80.180.281.30.0349.094.00.990923.210.5211.26
55.80.150.321.20.03714.0119.00.991373.190.510.26
65.60.320.337.40.03725.095.00.992683.250.4911.16
76.10.330.327.80.05252.0183.00.996573.390.659.55
87.10.320.39.90.04163.0192.00.996423.120.4910.26
96.20.230.350.70.05124.0111.00.99163.370.4311.03
108.90.30.354.60.03232.0148.00.994583.150.4511.57
116.00.140.175.60.03637.0127.00.993733.050.579.86
126.80.240.299.50.04256.0157.00.995863.110.5110.16
136.70.210.4814.80.0531.0195.00.999422.950.758.86
148.90.30.354.60.03232.0148.00.994583.150.4511.57
156.10.30.32.10.03150.0163.00.98953.390.4312.77
167.20.370.411.60.03234.0214.00.99633.10.519.86
176.70.640.31.20.0318.076.00.98923.160.612.94
187.20.370.411.60.03234.0214.00.99633.10.519.86
196.10.30.32.10.03150.0163.00.98953.390.4312.77
207.60.280.4920.150.0630.0145.01.001963.010.448.55

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_acidity
Decimal(6,3)
100%
123
volatile_acidity
Decimal(7,4)
100%
123
citric_acid
Decimal(6,3)
100%
123
residual_sugar
Decimal(7,3)
100%
123
chlorides
Double
100%
123
free_sulfur_dioxide
Decimal(7,2)
100%
123
total_sulfur_dioxide
Decimal(7,2)
100%
123
ph
Decimal(6,3)
100%
123
sulphates
Decimal(6,3)
100%
123
alcohol
Double
100%
123
quality
Integer
100%
16.30.670.4812.60.05257.0222.03.170.529.36
27.40.40.295.40.04431.0122.03.30.511.18
37.10.260.312.20.04429.0128.03.340.6410.98
49.00.310.486.60.04311.073.02.90.3811.65
56.30.390.246.90.0699.0117.03.150.3510.24
68.20.220.366.80.03412.090.03.010.3810.58
77.10.190.283.60.03316.078.02.910.7811.46
87.30.250.3613.10.0535.0200.03.040.468.97
97.90.20.341.20.0429.0118.03.140.4110.66
107.10.260.325.90.03739.097.03.310.411.66
117.00.20.345.70.03532.083.03.190.4611.56
126.90.30.334.10.03526.0155.03.250.7912.38
138.10.290.497.10.04222.0124.03.140.4110.86
145.80.170.31.40.03755.0130.03.290.3811.36
155.90.4150.020.80.03822.063.03.360.369.35
166.60.230.261.30.04516.0128.03.360.610.06
178.60.550.3515.550.05735.5366.53.040.6311.03
186.90.350.741.00.04418.0132.03.130.5510.25
197.60.140.741.60.0427.0103.03.070.410.87
209.20.280.4911.80.04229.0137.03.10.3410.14

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_acidity
Real
100%
123
volatile_acidity
Real
100%
123
citric_acid
Real
100%
123
residual_sugar
Real
100%
123
chlorides
Real
100%
123
free_sulfur_dioxide
Real
100%
123
total_sulfur_dioxide
Real
100%
123
ph
Real
100%
123
sulphates
Real
100%
123
alcohol
Real
100%
123
quality
Integer
100%
1-0.62633932256071454.9111557830319881.96056334665388051.3367247609547580.84253734429747161.44563053271913282.016276711787808-0.0465182254505077660.4414422660014937-0.75607873642646956
20.77911043334619981.6587446267190056-0.29991064767105520.0219452585864261530.10323904098655225-0.15602509850974183-0.28380212786737360.86037858456808940.24928288649345830.53843544794619528
30.3958059544624959-0.02769078766550382-0.06196601668948299-0.56240118691061010.10323904098655225-0.27922937783503987-0.14579739748806271.13942375688150381.5943985430497060.39460053857145518
42.82340098739262050.5746075746146781.96056334665388050.241075175647814780.01082675307268724-1.3880678917627223-1.4108407592984127-1.9300731385660557-0.90367339055475390.89802272138304675
5-0.62633932256071451.538284954262969-0.89477222512498570.29585765491316192.413546238833176-1.5112721710880204-0.3988060698501327-0.186040811607215-1.1919124598168072-0.108821644240137774
61.8012557103694102-0.50952947748964940.53289556076444740.27759682849137957-0.8208838381520965-1.3264657521000733-1.0198273565570317-1.1626989147041658-0.90367339055475390.106930719821973658
70.3958059544624959-0.8709084948577585-0.41888296316184126-0.3067496170056568-0.9132961260659616-1.0800571934494771-1.2958368173156536-1.8603118454877022.93951419960595350.75418781200830666
80.6513422737182984-0.14815046012154020.53289556076444741.42802889306366980.65771276846974220.090383460140854261.5102593670636681-0.9534150354691049-0.13503587252261245-1.04374855517595097
91.4179512314857063-0.75044882240172210.2949509297828753-0.7450094511284341-0.26641011066890713-0.27922937783503987-0.3758052814535808-0.25580210468556863-0.61543432129270090.17884817450934376
100.3958059544624959-0.027690787665503820.05700629880130310.11324939069533808-0.54364697441050210.3367920187914504-0.8588218377811690.930139877646443-0.71151401104671860.89802272138304676
110.2680377948345946-0.75044882240172210.29495092978287530.07672773785177331-0.7284715502382315-0.09442295884709281-1.18083287533289450.09300436070619948-0.135035872522612450.82610526669567666
120.14026963520669330.45414790215864170.1759786142920892-0.21544548489674487-0.7284715502382315-0.464035796822986960.47522388921883640.51157211917632133.03559388935997141.40144490419463958
131.67348755074150880.33368822970260532.07953566214466660.3323793077567267-0.08158553484117712-0.7104443554735831-0.2378005510742699-0.25580210468556863-0.61543432129270090.322683083884085066
14-1.265180120700221-1.1118278397698313-0.18093833218026908-0.7084877982848693-0.54364697441050211.3224262533938347-0.0997958206949590.7906172914897357-0.90367339055475390.68227035732093656
15-1.13741196107231971.83943413540306-3.5121631659222796-0.8180527568155636-0.4512346864966371-0.7104443554735831-1.64084864326393091.278946343038211-1.0958327700627895-0.75607873642646955
16-0.2430348436770106-0.38906980503361294-0.6568275941434134-0.72674862470665170.19565132890041725-1.0800571934494771-0.14579739748806271.2789463430382111.2100797840336353-0.25265655361487796
172.31232834888101563.46563971355955140.413923245273661371.87541914039733841.30459878386679670.121184529972178785.339890635089546-0.95341503546910491.49831885329568840.466517993258825153
180.14026963520669331.05644626443882355.053843549414319-0.78153110397199880.10323904098655225-0.9568529141241792-0.0537942439018554-0.325563397763922240.7296813352635468-0.108821644240137775
191.0346467526020025-1.47320685713794045.053843549414319-0.6719661454413045-0.26641011066890713-0.4024336571603379-0.7208171074018581-0.744131156234044-0.71151401104671860.322683083884085067
203.07893730664842340.213228557246568942.07953566214466661.1906381495804987-0.08158553484117712-0.279229377835039870.0612096980809037-0.5348472769989832-1.2879921495708246-0.18073909892750784

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_variancemax_errormedian_absolute_errormean_absolute_errormean_squared_errorroot_mean_squared_errorr2r2_adjaicbictime
1-fold7.771561172376096e-162.620.62000000000000010.76889440993789290.83887204968944080.9339703534470446-0.08851703309372083-0.09532451922939145-260.5556650591127-201.64842682860820.0002968311309814453
2-fold-2.220446049250313e-163.340.65725321888412020.85025045815515390.83130922419059291.0303272991195274-0.2889521080685411-0.29687924280450995-280.1300005990673-221.03455886336940.0028929710388183594
3-fold1.1102230246251565e-153.00.77628248502618850.62673088500903070.7915590608067430.8951673046482754-0.015724310680232723-0.021880215593446106-365.9531989405429-306.6931253727590.00356292724609375
avg5.551115123125783e-162.9866666666666670.68451190130343630.74862525103402580.8205801115622590.9531549857382825-0.13106448394749823-0.13802799254244916-302.212954866241-243.12537035491220.0022509098052978516
std5.661048867003676e-160.294089933334836960.066649984280658530.09237019359388950.0207519496690018660.0568219066527439340.11553086599702140.1162577774215762145.7741024958172245.640626181243690.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!