Predicting wine quality using Random Forests

Hello everyone! In this article I will show you how to run the random forest algorithm in R. We will use the wine quality data set (white) from the UCI Machine Learning Repository.

What is the Random Forest Algorithm?

In a previous post, I outlined how to build decision trees in R. While decision trees are easy to interpret, they tend to be rather simplistic and are often outperformed by other algorithms. Random Forests are one way to improve the performance of decision trees. The algorithm starts by building out trees similar to the way a normal decision tree algorithm works. However, every time a split has to made, it uses only a small random subset of features to make the split instead of the full set of features (usually \(\sqrt[]{p}\), where p is the number of predictors). It builds multiple trees using the same process, and then takes the average of all the trees to arrive at the final model. This works by reducing the amount of correlation between trees, and thus helping reduce the variance of the final tree. The simplest way to understand this is (as explained in Introduction to Statistical Learning): if you have some numbers \(Z_1, Z_2,…,Z_n\) with a variance of \(\sigma^2\), then their mean \(\overline{Z}\) will have variance \(\sigma^2/n\).

Exploring Data Analysis

Let us read in the data and explore it. We can read in the data directly from the page using the read.table function.

url <- 'https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv'
wine <- read.table(url, sep = ';', header = TRUE)
head(wine)
fixed.acidity volatile.acidity citric.acid residual.sugar chlorides free.sulfur.dioxide total.sulfur.dioxide density   pH sulphates alcohol quality
1           7.0             0.27        0.36           20.7     0.045                  45                  170  1.0010 3.00      0.45     8.8       6
2           6.3             0.30        0.34            1.6     0.049                  14                  132  0.9940 3.30      0.49     9.5       6
3           8.1             0.28        0.40            6.9     0.050                  30                   97  0.9951 3.26      0.44    10.1       6
4           7.2             0.23        0.32            8.5     0.058                  47                  186  0.9956 3.19      0.40     9.9       6
5           7.2             0.23        0.32            8.5     0.058                  47                  186  0.9956 3.19      0.40     9.9       6
6           8.1             0.28        0.40            6.9     0.050                  30                   97  0.9951 3.26      0.44    10.1       6

Let us look at the distribution of the wine quality. We can use barplot for this.

barplot(table(wine$quality))

The barplot:
graph1

As we can see, there are a lot of wines with a quality of 6 as compared to the others. The dataset description states – there are a lot more normal wines than excellent or poor ones. For the purpose of this discussion, let’s classify the wines into good, bad, and normal based on their quality.

wine$taste <- ifelse(wine$quality < 6, 'bad', 'good')
wine$taste[wine$quality == 6] <- 'normal'
wine$taste <- as.factor(wine$taste)

This will classify all wines into bad, normal, or good, depending on whether their quality is less than, equal to, or greater than 6 respectively. Let’s look at the distribution again.

table(wine$taste)
bad   good normal 
  1640   1060   2198 

Before we build our model, let’s separate our data into testing and training sets.

set.seed(123)
samp <- sample(nrow(wine), 0.6 * nrow(wine))
train <- wine[samp, ]
test <- wine[-samp, ]

This will place 60% of the observations in the original dataset into train and the remaining 40% of the observations into test.

Building the model

Now, we are ready to build our model. We will need the randomForest library for this.

library(randomForest)
model <- randomForest(taste ~ . - quality, data = train)

We can use ntree and mtry to specify the total number of trees to build (default = 500), and the number of predictors to randomly sample at each split respectively. Let’s take a look at the model.

model
Call:
 randomForest(formula = taste ~ . - quality, data = train) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 3

        OOB estimate of  error rate: 29.54%
Confusion matrix:
       bad good normal class.error
bad    683   16    274   0.2980473
good    16  404    229   0.3775039
normal 222  111    983   0.2530395

We can see that 500 trees were built, and the model randomly sampled 3 predictors at each split. It also shows a matrix containing prediction vs actual, as well as classification error for each class. Let’s test the model on the test data set.

pred <- predict(model, newdata = test)
table(pred, test$taste)
pred     bad good normal
  bad    482   10    130
  good    14  252     85
  normal 171  149    667

We can test the accuracy as follows:

(482 + 252 + 667) / nrow(test)
0.7147959

There we have it! We achieved ~71.5% accuracy with a very simple model. It could be further improved by feature selection, and possibly by trying different values of mtry.

That brings us to the end of the article. I hope you enjoyed it! As always, if you have questions or feedback, feel free to reach out to me on Twitter or leave a comment below!

12 Comments

  1. CJ
    chetna jain February 20, 2017

    Hi Teja! Is there a way I can score a different data-set using the algorithm used to develop the model?
    2. Can Sampling Weight used to weight my final model?

    Reply
  2. JD
    Juan Carlos Palacios Derqui February 10, 2017

    I am getting this error for this line:

    > model str(wine)
    ‘data.frame’: 4898 obs. of 12 variables:
    $ fixed.acidity : num 7 6.3 8.1 7.2 7.2 8.1 6.2 7 6.3 8.1 …
    $ volatile.acidity : num 0.27 0.3 0.28 0.23 0.23 0.28 0.32 0.27 0.3 0.22 …
    $ citric.acid : num 0.36 0.34 0.4 0.32 0.32 0.4 0.16 0.36 0.34 0.43 …
    $ residual.sugar : num 20.7 1.6 6.9 8.5 8.5 6.9 7 20.7 1.6 1.5 …
    $ chlorides : num 0.045 0.049 0.05 0.058 0.058 0.05 0.045 0.045 0.049 0.044 …
    $ free.sulfur.dioxide : num 45 14 30 47 47 30 30 45 14 28 …
    $ total.sulfur.dioxide: num 170 132 97 186 186 97 136 170 132 129 …
    $ density : num 1.001 0.994 0.995 0.996 0.996 …
    $ pH : num 3 3.3 3.26 3.19 3.19 3.26 3.18 3 3.3 3.22 …
    $ sulphates : num 0.45 0.49 0.44 0.4 0.4 0.44 0.47 0.45 0.49 0.45 …
    $ alcohol : Factor w/ 104 levels “10”,”10.1″,”10.15″,..: 88 95 2 101 101 2 97 88 95 22 …
    $ quality : int 6 6 6 6 6 6 6 6 6 6 …

    Thanks

    Reply
    1. JD
      Juan Carlos Palacios Derqui February 10, 2017

      Don´t worry, just found the issue, variable alcohol should be num

      Reply
  3. JG
    JS Gourdet February 5, 2016

    Have a look to this shiny apps I built in the same purpose: https://geojsg.shinyapps.io/wine/

    Reply
    1. K
      Klodian February 5, 2016

      Nice, thanks for sharing.

      Reply
    2. TK
      Teja K February 5, 2016

      I think I’ve seen this before (maybe someone shared it on Twitter or something)? Anyway, great job man!

      Reply
  4. N
    N1t1nA February 5, 2016

    url url

    [1] “https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-white.csv”

    > wine <- read.table(url)

    Error in scan(file, what, nmax, sep, dec, quote, skip, nlines, na.strings, :

    line 2 did not have 9 elements

    Reply
    1. RP
      Roberto Palloni February 5, 2016

      This is the correct code:

      > url wine head(wine)

      Cheers!

      Reply
      1. N
        N1t1nA February 5, 2016

        Thanks
        Another Error I am newbie trying the code as it is

        barplot(table(wine$quality))

        Error in plot.window(xlim, ylim, log = log, …) :

        need finite ‘xlim’ values

        In addition: Warning messages:

        1: In min(w.l) : no non-missing arguments to min; returning Inf

        2: In max(w.r) : no non-missing arguments to max; returning -Inf

        3: In min(x) : no non-missing arguments to min; returning Inf

        4: In max(x) : no non-missing arguments to max; returning -Inf

        Reply
        1. TK
          Teja K February 5, 2016

          I got this error too, not sure why it’s popping up, because it usually comes up when there are NA values. Restarting R fixed it for me.

          Reply
    2. ŠG
      Šarūnas Grigaliūnas February 5, 2016

      read.table(url, sep = ‘;’, header = TRUE)

      Reply
      1. TK
        Teja K February 5, 2016

        Fixed!

        Reply

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.