The pmmlTransformations R package can be used to transform data and add new features to be used in predictive PMML models.
In this blog post, we will focus on FunctionXform
, a function introduced in version 1.3.0 of pmmlTransformations
, and present a few examples of using it to create new data features.
The pmmlTransformations R package can be used to transform data and add new features to be used in predictive PMML models.
In this blog post, we will focus on FunctionXform
, a function introduced in version 1.3.0 of pmmlTransformations
, and present a few examples of using it to create new data features.
How it works
Transformations in the pmmlTransformations
package work in the following manner: given a WrapData
object and a transformation name, the code calculates data for a new feature and creates a new WrapData
object. This new object is then passed in as the data
argument when training an R model with a compatible R package. When PMML is produced with pmml::pmml()
, the transformation is inserted into the LocalTransformations
node as a DerivedField
. Any original fields used by transformations are added to the appropriate nodes in the resulting PMML file.
While other transformations in the package transform only one field, FunctionXform
makes it possible to use multiple data fields and functions to produce a new feature.
Note that while FunctionXform
is part of the pmmlTransformations
package, the code to produce PMML from R is in the pmml
package. The following examples require both packages to be installed to work.
To make tables more readable in this blog post, we are using the kable
function (part of knitr
).
Single numeric field
Using the iris
dataset as an example, let’s construct a new numeric feature by transforming one variable.
First, load the required libraries:
library(pmml)
library(pmmlTransformations)
library(knitr)
Then load the data and display the first 3 lines:
data(iris)
kable(head(iris,3))
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species |
---|---|---|---|---|
5.1 | 3.5 | 1.4 | 0.2 | setosa |
4.9 | 3.0 | 1.4 | 0.2 | setosa |
4.7 | 3.2 | 1.3 | 0.2 | setosa |
Create the irisBox
wrapper object with WrapData
:
irisBox <- WrapData(iris)
irisBox
contains the data and transform information that will be used to produce PMML later. The original data is in irisBox$data
. Any new features created with a transformation are added as columns to this data frame.
kable(head(irisBox$data,3))
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species |
---|---|---|---|---|
5.1 | 3.5 | 1.4 | 0.2 | setosa |
4.9 | 3.0 | 1.4 | 0.2 | setosa |
4.7 | 3.2 | 1.3 | 0.2 | setosa |
Transform and field information is in irisBox$fieldData
. The fieldData data frame contains information on every field in the dataset, as well as every transform used. The functionXform
column contains expressions used in the FunctionXform
transform. Here we’ll show only a few of the columns:
kable(irisBox$fieldData[,1:5])
type | dataType | origFieldName | sampleMin | sampleMax | |
---|---|---|---|---|---|
Sepal.Length | original | numeric | NA | NA | NA |
Sepal.Width | original | numeric | NA | NA | NA |
Petal.Length | original | numeric | NA | NA | NA |
Petal.Width | original | numeric | NA | NA | NA |
Species | original | factor | NA | NA | NA |
Now add a new feature, Sepal.Length.Sqrt
, using FunctionXform
:
irisBox <- FunctionXform(irisBox,origFieldName="Sepal.Length",
newFieldName="Sepal.Length.Sqrt",
formulaText="sqrt(Sepal.Length)")
The new feature is calculated and added as a column to the irisBox$data
data frame:
kable(head(irisBox$data,3))
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species | Sepal.Length.Sqrt |
---|---|---|---|---|---|
5.1 | 3.5 | 1.4 | 0.2 | setosa | 2.258318 |
4.9 | 3.0 | 1.4 | 0.2 | setosa | 2.213594 |
4.7 | 3.2 | 1.3 | 0.2 | setosa | 2.167948 |
irisBox$fieldData
now contains a new row with the transformation expression in the functionXform
column:
kable(irisBox$fieldData[6,c(1:3,14)])
type | dataType | origFieldName | functionXform | |
---|---|---|---|---|
Sepal.Length.Sqrt | derived | numeric | Sepal.Length | sqrt(Sepal.Length) |
Construct a linear model to predict Petal.Width
using this new feature, and convert it to PMML:
fit <- lm(Petal.Width ~ Sepal.Length.Sqrt, data=irisBox$data)
fit_pmml <- pmml(fit, transform=irisBox)
Since the model predicts Petal.Width
using a variable based on Sepal.Length
, Sepal.Length
will be added to the DataDictionary
and MiningSchema
nodes in the resulting PMML. We can take a look at the relevant parts of the output like so:
fit_pmml[[2]] #Data Dictionary node
#>
#>
#>
#>
fit_pmml[[3]][[1]] #Mining Schema node
#>
#>
#>
#>
The LocalTransformations
node contains Sepal.Length.Sqrt
as a derived field:
fit_pmml[[3]][[3]]
#>
#>
#>
#>
#>
#>
#>
The PMML model can now be deployed and consumed. For any input data, the new Sepal.Length.Sqrt
feature will be created when the data is scored against the model.
Multiple input fields
It is also possible to create new features by combining several fields. Using the same iris
dataset, let’s create a new field using squares of Sepal.Length
and Petal.Length
:
irisBox <- WrapData(iris)
irisBox <- FunctionXform(irisBox,origFieldName="Sepal.Length,Petal.Length",
newFieldName="Squared.Length.Ratio",
formulaText="(Sepal.Length / Petal.Length)^2")
As before, the new field is added as a column to the irisBox$data
data frame:
kable(head(irisBox$data,3))
Sepal.Length | Sepal.Width | Petal.Length | Petal.Width | Species | Squared.Length.Ratio |
---|---|---|---|---|---|
5.1 | 3.5 | 1.4 | 0.2 | setosa | 13.27041 |
4.9 | 3.0 | 1.4 | 0.2 | setosa | 12.25000 |
4.7 | 3.2 | 1.3 | 0.2 | setosa | 13.07101 |
Fit a linear model for Petal.Length
using this new feature, and convert it to PMML:
fit <- lm(Petal.Width ~ Squared.Length.Ratio, data=irisBox$data)
fit_pmml <- pmml(fit, transform=irisBox)
The PMML will contain Sepal.Length
and Petal.Length
in the DataDictionary
and MiningSchema
, since these were used in FormulaXform
:
fit_pmml[[2]] #Data Dictionary node
#>
#>
#>
#>
#>
fit_pmml[[3]][[1]] #Mining Schema node
#>
#>
#>
#>
#>
The Local.Transformations
node contains Squared.Length.Ratio
as a derived field:
fit_pmml[[3]][[3]]
#>
#>
#>
#>
#>
#>
#>
#> 2
#>
#>
#>
PMML for arbitrary functions
The function functionToPMML
(part of the pmml
package) makes it possible to convert an R expression into PMML directly, without creating a model or calculating values. This can be useful for debugging.
As long as the expression passed to the function is a valid R expression (e.g., no unbalanced parentheses), it can contain arbitrary function names not defined in R. Constants in the expression passed to FunctionXform
are always assumed to be of type double
. Variables in the expression are always assumed to be field names, and not substituted. That is, even if x
has a value in the R environment, the resulting expression will still use x
.
functionToPMML("1 + 2")
#>
#> 1
#> 2
#>
x <- 3
functionToPMML("foo(bar(x * y))")
#>
#>
#>
#>
#>
#>
#>
#>
functionToPMML("if(a<2) else if (a>3) {'four'} else ")
#>
#>
#>
#> 2
#>
#>
#>
#> 3
#>
#>
#>
#>
#> 3
#>
#> four
#> 5
#>
#>
Conclusion
functionXform
makes it possible to easily create new features for PMML models with R.
The pmmlTransformations
functionXform
vignette contains additional examples, including transforming categorical data, using transformed features in another transform, unsupported functions, and notes on limitations of the function.