vtreat package

John Mount, Nina Zumel

2024-06-12

vtreat is a data.frame processor/conditioner (available for R, and for Python) that prepares real-world data for supervised machine learning or predictive modeling in a statistically sound manner.

A nice video lecture on what sorts of problems vtreat solves can be found here.

vtreat takes an input data.frame that has a specified column called “the outcome variable” (or “y”) that is the quantity to be predicted (and must not have missing values). Other input columns are possible explanatory variables (typically numeric or categorical/string-valued, these columns may have missing values) that the user later wants to use to predict “y”. In practice such an input data.frame may not be immediately suitable for machine learning procedures that often expect only numeric explanatory variables, and may not tolerate missing values.

To solve this, vtreat builds a transformed data.frame where all explanatory variable columns have been transformed into a number of numeric explanatory variable columns, without missing values. The vtreat implementation produces derived numeric columns that capture most of the information relating the explanatory columns to the specified “y” or dependent/outcome column through a number of numeric transforms (indicator variables, impact codes, prevalence codes, and more). This transformed data.frame is suitable for a wide range of supervised learning methods from linear regression, through gradient boosted machines.

The idea is: you can take a data.frame of messy real world data and easily, faithfully, reliably, and repeatably prepare it for machine learning using documented methods using vtreat. Incorporating vtreat into your machine learning workflow lets you quickly work with very diverse structured data.

In all cases (classification, regression, unsupervised, and multinomial classification) the intent is that vtreat transforms are essentially one liners.

The preparation commands are organized as follows:

In all cases: variable preparation is intended to be a “one liner.”

These current revisions of the examples are designed to be small, yet complete. So as a set they have some overlap, but the user can rely mostly on a single example for a single task type.

For more detail please see here: arXiv:1611.09477 stat.AP (the documentation describes the R version, however all of the examples can be found worked in Python here).

vtreat is available as an R package, and also as a Python/Pandas package.

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:

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.

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 2nd edition of Practical Data Science with R covers using vtreat in R in chapter 8 “Advanced Data Preparation.”

The idea is: data.frames 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.

Some small examples:

We attach our packages.

library("vtreat")
packageVersion("vtreat")
## [1] '1.6.5'
citation('vtreat')
## To cite package 'vtreat' in publications use:
## 
##   Mount J, Zumel N (2024). _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 = {2024},
##     note = {https://github.com/WinVector/vtreat/, https://winvector.github.io/vtreat/},
##   }

A small categorical example.

# categorical example
set.seed(23525)

# we set up our raw training and application data
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))

# we perform a vtreat cross frame experiment
# and unpack the results into treatmentsC
# and dTrainCTreated
unpack[
  treatmentsC = treatments,
  dTrainCTreated = crossFrame
  ] <- mkCrossFrameCExperiment(
  dframe = dTrainC,
  varlist = setdiff(colnames(dTrainC), 'y'),
  outcomename = 'y',
  outcometarget = TRUE,
  verbose = FALSE)

# the treatments include a score frame relating new
# derived variables to original columns
treatmentsC$scoreFrame[, c('origName', 'varName', 'code', 'rsq', 'sig', 'extraModelDegrees', 'recommended')] %.>%
  knitr::kable(.)
origName varName code rsq sig extraModelDegrees recommended
x x_catP catP 0.1669568 0.2064389 2 FALSE
x x_catB catB 0.2547883 0.1185814 2 TRUE
z z clean 0.2376018 0.1317602 0 TRUE
z z_isBAD isBAD 0.2960654 0.0924840 0 TRUE
x x_lev_NA lev 0.2960654 0.0924840 0 FALSE
x x_lev_x_a lev 0.1300057 0.2649038 0 FALSE
x x_lev_x_b lev 0.0060673 0.8096724 0 FALSE
# the treated frame is a "cross frame" which
# is a transform of the training data built 
# as if the treatment were learned on a different
# disjoint training set to avoid nested model
# bias and over-fit.
dTrainCTreated %.>%
  head(.) %.>%
  knitr::kable(.)
x_catP x_catB z z_isBAD x_lev_NA x_lev_x_a x_lev_x_b y
0.50 0.0000000 1 0 0 1 0 FALSE
0.40 -0.4054484 2 0 0 1 0 FALSE
0.40 -10.3089860 3 0 0 1 0 TRUE
0.20 8.8049919 4 0 0 0 1 FALSE
0.25 -9.2104404 3 1 0 0 1 TRUE
0.25 9.2104404 6 0 1 0 0 TRUE
# Any future application data is prepared with
# the prepare method.
dTestCTreated <- prepare(treatmentsC, dTestC, pruneSig=NULL)

dTestCTreated %.>%
  head(.) %.>%
  knitr::kable(.)
x_catP x_catB z z_isBAD x_lev_NA x_lev_x_a x_lev_x_b
0.4285714 -0.9807709 10.0 0 0 1 0
0.2857143 -0.2876737 20.0 0 0 0 1
0.0714286 0.0000000 30.0 0 0 0 0
0.2857143 9.6158638 3.2 1 1 0 0

A small numeric example.

# numeric example
set.seed(23525)

# we set up our raw training and application data
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))

# we perform a vtreat cross frame experiment
# and unpack the results into treatmentsN
# and dTrainNTreated
unpack[
  treatmentsN = treatments,
  dTrainNTreated = crossFrame
  ] <- mkCrossFrameNExperiment(
  dframe = dTrainN,
  varlist = setdiff(colnames(dTrainN), 'y'),
  outcomename = 'y',
  verbose = FALSE)

# the treatments include a score frame relating new
# derived variables to original columns
treatmentsN$scoreFrame[, c('origName', 'varName', 'code', 'rsq', 'sig', 'extraModelDegrees')] %.>%
  knitr::kable(.)
origName varName code rsq sig extraModelDegrees
x x_catP catP 0.4047085 0.0899406 2
x x_catN catN 0.2822908 0.1753958 2
x x_catD catD 0.0209693 0.7322571 2
z z clean 0.2880952 0.1701892 0
z z_isBAD isBAD 0.3333333 0.1339746 0
x x_lev_NA lev 0.3333333 0.1339746 0
x x_lev_x_a lev 0.2500000 0.2070312 0
# the treated frame is a "cross frame" which
# is a transform of the training data built 
# as if the treatment were learned on a different
# disjoint training set to avoid nested model
# bias and over-fit.
dTrainNTreated %.>%
  head(.) %.>%
  knitr::kable(.)
x_catN x_catD z z_isBAD x_lev_NA x_lev_x_a x_catP y
-0.2666667 0.5000000 1 0 0 1 0.6 0
-0.5000000 0.0000000 2 0 0 1 0.5 0
-0.0666667 0.5000000 3 0 0 1 0.6 0
-0.5000000 0.0000000 4 0 0 1 0.5 1
0.4000000 0.7071068 5 0 0 0 0.2 0
-0.4000000 0.7071068 3 1 0 0 0.2 1
# Any future application data is prepared with
# the prepare method.
dTestNTreated <- prepare(treatmentsN, dTestN, pruneSig=NULL)

dTestNTreated %.>%
  head(.) %.>%
  knitr::kable(.)
x_catP x_catN x_catD z z_isBAD x_lev_NA x_lev_x_a
0.5000 -0.25 0.5000000 10.000000 0 0 1
0.2500 0.00 0.7071068 20.000000 0 0 0
0.0625 0.00 0.7071068 30.000000 0 0 0
0.2500 0.50 0.0000000 3.666667 1 1 0

Related work:

Note

Notes on controlling vtreat’s cross-validation plans can be found here.

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