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

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 |

`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 |

`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 |

`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) |

`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
#> <DataDictionary numberOfFields="2">
#> <DataField name="Petal.Width" optype="continuous" dataType="double"/>
#> <DataField name="Sepal.Length" optype="continuous" dataType="double"/>
#> </DataDictionary>
fit_pmml[[3]][[1]] #Mining Schema node
#> <MiningSchema>
#> <MiningField name="Petal.Width" usageType="predicted"/>
#> <MiningField name="Sepal.Length" usageType="active"/>
#> </MiningSchema>
```

The `LocalTransformations`

node contains `Sepal.Length.Sqrt`

as a derived field:```
fit_pmml[[3]][[3]]
#> <LocalTransformations>
#> <DerivedField name="Sepal.Length.Sqrt" dataType="double" optype="continuous">
#> <Apply function="sqrt">
#> <FieldRef field="Sepal.Length"/>
#> </Apply>
#> </DerivedField>
#> </LocalTransformations>
```

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 |

`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
#> <DataDictionary numberOfFields="3">
#> <DataField name="Petal.Width" optype="continuous" dataType="double"/>
#> <DataField name="Sepal.Length" optype="continuous" dataType="double"/>
#> <DataField name="Petal.Length" optype="continuous" dataType="double"/>
#> </DataDictionary>
fit_pmml[[3]][[1]] #Mining Schema node
#> <MiningSchema>
#> <MiningField name="Petal.Width" usageType="predicted"/>
#> <MiningField name="Sepal.Length" usageType="active"/>
#> <MiningField name="Petal.Length" usageType="active"/>
#> </MiningSchema>
```

The `Local.Transformations`

node contains `Squared.Length.Ratio`

as a derived field:```
fit_pmml[[3]][[3]]
#> <LocalTransformations>
#> <DerivedField name="Squared.Length.Ratio" dataType="double" optype="continuous">
#> <Apply function="pow">
#> <Apply function="/">
#> <FieldRef field="Sepal.Length"/>
#> <FieldRef field="Petal.Length"/>
#> </Apply>
#> <Constant dataType="double">2</Constant>
#> </Apply>
#> </DerivedField>
#> </LocalTransformations>
```

## 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")
#> <Apply function="+">
#> <Constant dataType="double">1</Constant>
#> <Constant dataType="double">2</Constant>
#> </Apply>
x <- 3
functionToPMML("foo(bar(x * y))")
#> <Apply function="foo">
#> <Apply function="bar">
#> <Apply function="*">
#> <FieldRef field="x"/>
#> <FieldRef field="y"/>
#> </Apply>
#> </Apply>
#> </Apply>
functionToPMML("if(a<2) {x+3} else if (a>3) {'four'} else {5}")
#> <Apply function="if">
#> <Apply function="lessThan">
#> <FieldRef field="a"/>
#> <Constant dataType="double">2</Constant>
#> </Apply>
#> <Apply function="+">
#> <FieldRef field="x"/>
#> <Constant dataType="double">3</Constant>
#> </Apply>
#> <Apply function="if">
#> <Apply function="greaterThan">
#> <FieldRef field="a"/>
#> <Constant dataType="double">3</Constant>
#> </Apply>
#> <Constant dataType="string">four</Constant>
#> <Constant dataType="double">5</Constant>
#> </Apply>
#> </Apply>
```

## 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.