vtreat is an R data.frame processor/conditioner that prepares real-world data for predictive modeling in a statistically sound manner. For more detail please see here: arXiv:1611.09477 stat.AP DOI. There is also a series of articles recording the evolution of vtreat including some tutorials here.

(logo: Julie Mount, source: “The Harvest” by Boris Kustodiev 1914)

Even with modern machine learning techniques (random forests, support vector machines, neural nets, gradient boosted trees, and so on) or standard statistical methods (regression, generalized regression, generalized additive models) there are common data issues that can cause modeling to fail. vtreat deals with a number of these in a principled and automated fashion.

In particular vtreat emphasizes a concept called “y-aware pre-processing” and implements:

The idea is: even with a sophisticated machine learning algorithm there are many ways messy real world data can defeat the modeling process, and vtreat helps with at least ten of them. We emphasize: these problems are already in your data, you simply build better and more reliable models if you attempt to mitigate them. Automated processing is no substitute for actually looking at the data, but vtreat supplies efficient, reliable, documented, and tested implementations of many of the commonly needed transforms.

To help explain the methods we have prepared some documentation:

Install either from CRAN:

install.packages('vtreat')

Or from GitHub:

# install.packages('devtools')
devtools::install_github('WinVector/vtreat', build_vignettes=TRUE)

And then:

library('vtreat')
help('vtreat')

Data treatments are “y-aware” (use distribution relations between independent variables and the dependent variable). For binary classification use ‘designTreatmentsC()’ and for numeric regression use ‘designTreatmentsN()’.

After the design step, ‘prepare()’ should be used as you would use model.matrix. ‘prepare()’ treated variables are all numeric and never take the value NA or +-Inf (so are very safe to use in modeling).

In application we suggest splitting your data into three sets: one for building vtreat encodings, one for training models using these encodings, and one for test and model evaluation.

‘vtreat’ is supplied by Win-Vector LLC under a GPL-3 license, without warranty.

The purpose of ‘vtreat’ library is to reliably prepare data for supervised machine learning. We try to leave as much as possible to the machine learning algorithms themselves, but cover most of the truly necessary typically ignored precautions. The library is designed to produce a ‘data.frame’ that is entirely numeric and takes common precautions to guard against the following real world data issues:

The above are all awful things that often lurk in real world data. Automating these steps ensures they are easy enough that you actually perform them and leaves the analyst time to look for additional data issues. For example this allowed us to essentially automate a number of the steps taught in chapters 4 and 6 of Practical Data Science with R (Zumel, Mount; Manning 2014) into a very short worksheet (though we think for understanding it is essential to work all the steps by hand as we did in the book). The idea is: ‘data.frame’s prepared with the ’vtreat’ library are somewhat safe to train on as some precaution has been taken against all of the above issues. Also of interest are the ‘vtreat’ variable significances (help in initial variable pruning, a necessity when there are a large number of columns) and ‘vtreat::prepare(scale=TRUE)’ which re-encodes all variables into effect units making them suitable for y-aware dimension reduction (variable clustering, or principal component analysis) and for geometry sensitive machine learning techniques (k-means, knn, linear SVM, and more). You may want to do more than the ‘vtreat’ library does (such as Bayesian imputation, variable clustering, and more) but you certainly do not want to do less.

There have been a number of recent substantial improvements to the library, including:

Some of our related articles (which should make clear some of our motivations, and design decisions):

Examples of current best practice using ‘vtreat’ (variable coding, train, test split) can be found here and here.

Trivial example:

library("vtreat")
packageVersion("vtreat")
 #  [1] '1.0.3'
citation('vtreat')
 #  
 #  To cite package 'vtreat' in publications use:
 #  
 #    John Mount and Nina Zumel (2018). vtreat: A Statistically Sound
 #    'data.frame' Processor/Conditioner.
 #    https://github.com/WinVector/vtreat/,
 #    https://winvector.github.io/vtreat/.
 #  
 #  A BibTeX entry for LaTeX users is
 #  
 #    @Manual{,
 #      title = {vtreat: A Statistically Sound 'data.frame' Processor/Conditioner},
 #      author = {John Mount and Nina Zumel},
 #      year = {2018},
 #      note = {https://github.com/WinVector/vtreat/, https://winvector.github.io/vtreat/},
 #    }

# categorical example
dTrainC <- data.frame(x=c('a','a','a','b','b',NA,NA),
   z=c(1,2,3,4,NA,6,NA),
   y=c(FALSE,FALSE,TRUE,FALSE,TRUE,TRUE,TRUE))
dTestC <- data.frame(x=c('a','b','c',NA),z=c(10,20,30,NA))

# help("designTreatmentsC")

treatmentsC <- designTreatmentsC(dTrainC,colnames(dTrainC),'y',TRUE,
                                 verbose=FALSE)
print(treatmentsC$scoreFrame[,c('origName', 'varName', 'code', 'rsq', 'sig', 'extraModelDegrees')])
 #    origName   varName  code         rsq        sig extraModelDegrees
 #  1        x    x_catP  catP 0.285095342 0.09874390                 2
 #  2        x    x_catB  catB 0.117443640 0.28930668                 2
 #  3        z   z_clean clean 0.237601767 0.13176020                 0
 #  4        z   z_isBAD isBAD 0.296065432 0.09248399                 0
 #  5        x  x_lev_NA   lev 0.296065432 0.09248399                 0
 #  6        x x_lev_x.a   lev 0.130005705 0.26490379                 0
 #  7        x x_lev_x.b   lev 0.006067337 0.80967242                 0

# help("prepare")

dTrainCTreated <- prepare(treatmentsC,dTrainC,pruneSig=1.0,scale=TRUE)
varsC <- setdiff(colnames(dTrainCTreated),'y')
# all input variables should be mean 0
sapply(dTrainCTreated[,varsC,drop=FALSE],mean)
 #         x_catP        x_catB       z_clean       z_isBAD      x_lev_NA 
 #   1.585994e-16  0.000000e+00  7.927952e-18 -7.926292e-18  3.965082e-18 
 #      x_lev_x.a     x_lev_x.b 
 #  -1.982154e-17  9.917546e-19
# all non NA slopes should be 1
sapply(varsC,function(c) { lm(paste('y',c,sep='~'),
   data=dTrainCTreated)$coefficients[[2]]})
 #     x_catP    x_catB   z_clean   z_isBAD  x_lev_NA x_lev_x.a x_lev_x.b 
 #          1         1         1         1         1         1         1
dTestCTreated <- prepare(treatmentsC,dTestC,pruneSig=c(),scale=TRUE)
print(dTestCTreated)
 #        x_catP     x_catB  z_clean    z_isBAD   x_lev_NA  x_lev_x.a
 #  1 -0.2380952 -0.1897682 1.194595 -0.1714286 -0.1714286 -0.2380952
 #  2  0.1785714 -0.1489924 2.951351 -0.1714286 -0.1714286  0.1785714
 #  3  1.0119048 -0.1320682 4.708108 -0.1714286 -0.1714286  0.1785714
 #  4  0.1785714  0.4336447 0.000000  0.4285714  0.4285714  0.1785714
 #      x_lev_x.b
 #  1  0.02857143
 #  2 -0.07142857
 #  3  0.02857143
 #  4  0.02857143
# numeric example
dTrainN <- data.frame(x=c('a','a','a','a','b','b',NA,NA),
   z=c(1,2,3,4,5,NA,7,NA),y=c(0,0,0,1,0,1,1,1))
dTestN <- data.frame(x=c('a','b','c',NA),z=c(10,20,30,NA))
# help("designTreatmentsN")
treatmentsN = designTreatmentsN(dTrainN,colnames(dTrainN),'y',
                                verbose=FALSE)
print(treatmentsN$scoreFrame[,c('origName', 'varName', 'code', 'rsq', 'sig', 'extraModelDegrees')])
 #    origName   varName  code          rsq       sig extraModelDegrees
 #  1        x    x_catP  catP 1.304740e-01 0.3793327                 2
 #  2        x    x_catN  catN 2.616674e-01 0.1950536                 2
 #  3        x    x_catD  catD 9.705853e-02 0.4525551                 2
 #  4        z   z_clean clean 2.880952e-01 0.1701892                 0
 #  5        z   z_isBAD isBAD 3.333333e-01 0.1339746                 0
 #  6        x  x_lev_NA   lev 3.333333e-01 0.1339746                 0
 #  7        x x_lev_x.a   lev 2.500000e-01 0.2070312                 0
 #  8        x x_lev_x.b   lev 1.110223e-16 1.0000000                 0
dTrainNTreated <- prepare(treatmentsN,dTrainN,pruneSig=1.0,scale=TRUE)
varsN <- setdiff(colnames(dTrainNTreated),'y')
# all input variables should be mean 0
sapply(dTrainNTreated[,varsN,drop=FALSE],mean) 
 #         x_catP        x_catN        x_catD       z_clean       z_isBAD 
 #   2.775558e-17  0.000000e+00 -2.775558e-17  4.857226e-17  6.938894e-18 
 #       x_lev_NA     x_lev_x.a     x_lev_x.b 
 #   6.938894e-18  0.000000e+00  7.703720e-34
# all non NA slopes should be 1
sapply(varsN,function(c) { lm(paste('y',c,sep='~'),
   data=dTrainNTreated)$coefficients[[2]]}) 
 #     x_catP    x_catN    x_catD   z_clean   z_isBAD  x_lev_NA x_lev_x.a 
 #          1         1         1         1         1         1         1 
 #  x_lev_x.b 
 #          1
dTestNTreated <- prepare(treatmentsN,dTestN,pruneSig=c(),scale=TRUE)
print(dTestNTreated)
 #    x_catP x_catN      x_catD   z_clean    z_isBAD   x_lev_NA x_lev_x.a
 #  1  -0.25  -0.25 -0.06743804 0.9952381 -0.1666667 -0.1666667     -0.25
 #  2   0.25   0.00 -0.25818161 2.5666667 -0.1666667 -0.1666667      0.25
 #  3   0.75   0.00 -0.25818161 4.1380952 -0.1666667 -0.1666667      0.25
 #  4   0.25   0.50  0.39305768 0.0000000  0.5000000  0.5000000      0.25
 #        x_lev_x.b
 #  1 -2.266233e-17
 #  2  6.798700e-17
 #  3 -2.266233e-17
 #  4 -2.266233e-17

# for large data sets you can consider designing the treatments on 
# a subset like: d[sample(1:dim(d)[[1]],1000),]

Related work:

Note

Note: vtreat is meant only for “tame names”, that is: variables and column names that are also valid simple (without quotes) R variables names.