mlrCPO Core

Martin Binder


CPO Vignette Navigation

  1. First Steps (compact version)
  2. mlrCPO Core (compact version)
  3. CPOs Built Into mlrCPO (compact version)
  4. Building Custom CPOs (compact version)

Table of Contents


This vignette is supposed to be a short reference of the primitives and tools supplied by the mlrCPO package.

Lifecycle of a CPO

CPOs are first-class objects in R that represent data manipulation. They can be combined to form networks of operation, they can be attached to mlr Learners, and they have tunable Hyperparameters that influence their behaviour. CPOs go through a lifecycle from construction to CPO to a CPOTrained “retrafo” or “inverter” object. The different stages of a CPO related object can be distinguished using getCPOClass(), which takes one of five values:

#> [1] "CPOConstructor"
#> [1] "CPO"
getCPOClass(pid.task %>|% cpoPca())
#> [1] "CPORetrafo"
getCPOClass(inverter(bh.task %>>% cpoLogTrafoRegr()))
#> [1] "CPOInverter"
#> [1] "NULLCPO"


CPOs are created using CPOConstructors. These are R functions with a print function and many parameters in common.

The function parameters of a CPOConstructor


Functions that work on CPOs

The inner “state” of a CPO can be inspected and manipulated using various getters and setters.

Compare the predict type and operating type of a TOCPO or ROCPO:

The identicalCPO() function is used to check whether the underlying operation of two CPOs is identical. For this understanding, CPOs with different hyperparameters can still be “identical”.

CPO Composition

CPO composition can be done using %>>% or composeCPO. It results in a new CPO which mostly behaves like a primitive CPO. Exceptions are:

  • Compound CPOs have no id
  • Affect of compound CPOs cannot be retrieved

getCPOOperatingType() always considers the operating type of the whole CPO chain and may return multiple values:

Compound CPO Chaining and Decomposition

Composite CPO objects can be broken into their constituent primitive CPOs using as.list(). The inverse of this operation is pipeCPO(), which composes a list of CPOs in the given order.


CPO-Learner attachment works using %>>% or attachCPO.

The new object is a CPOLearner, which performs the operation given by the CPO before trainign the Learner.

The work performed by a CPOLearner can also be performed manually:

is equivalent to

CPOLearner Decomposition

It is possible to obtain both the underlying Learner and the attached CPO from a CPOLearner. Note that if a CPOLearner is wrapped by some method (e.g. a TuneWrapper), this does not work, since CPO can not probe below the first wrapping layer.


CPOs perform data-dependent operation. However, when this operation becomes part of a machine-learning process, the operation on predict-data must depend only on the training data. A CPORetrafo object represents the re-application of a trained CPO. A CPOInverter object represents the transformation of a prediction made on a transformed task back to the form of the original data.

The CPOTrained objects generated by application of a CPO (or application of another CPOTrained) can be retrieved using the retrafo() or the inverter() function.

Retrafos and inverters are stored as attributes:

It is possible to set the "retrafo" and "inverter" attributes of an object using retrafo() and inverter(). This can be useful for writing elegant scripts, especially since CPOTrained are automatically chained. To delete the CPOTrained attribute of an object, set it to NULL or NULLCPO, or use clearRI().

Functions that work on CPOTrained

General methods that work on CPOTrained object to inspect its object properties. Many methods that work on a CPO also work on a CPOTrained and give the same result.

A CPOTrained has information about whether it can be used as a CPORetrafo object (and be applied to new data using %>>%), or as a CPOInverter object (and used by invert()), or possibly both. This is given by getCPOTrainedCapability(), which returns a 1 if the object has an effect in the given role, 0 if the object has no effect (but can be used), or -1 if the object can not be used in the role.

The “CPO class” of a CPOTrained is determined by this as well. A pure inverter is CPOInverter, an object that can be used for retrafo is a CPORetrafo.

The CPO and the CPOConstructor used to create the `CPOTrained can be queried.

CPOTrained Inspection

CPOTrained objects can be inspected using getCPOTrainedState(). The state contains the hyperparameters, the control object (CPO dependent data representing the data information needed to re-apply the operation), and information about the Task / data.frame layout used for training (column names, column types) in data$shapeinfo.input and data$shapeinfo.output.

The state can be manipulated and used to create new CPOTraineds, using makeCPOTrainedFromState().

CPOTrained are Automatically Chained

When executing data %>>% CPO, the result has an associated CPORetrafo and CPOInverter object. When applying another CPO, the CPORetrafo and CPOInverter will be chained automatically. This is to make (data %>>% CPO1) %>>% CPO2 work the same as data %>>% (CPO1 %>>% CPO2).

retrafo(data2) is the same as retrafo(data %>>% pca %>>% scale):

To interrupt this chain, set retrafo to NULL either explicitly, or using clearRI().

this is equivalent to

Inversion using CPOTrained

To use CPOTrained objects for inversion, the invert() function is used. Besides the CPOTrained, it takes the data to invert, and optionally the predict.type. Typically CPOTrained objects that were retrieved using inverter() from a transformed dataset should be used for inversion. Retrafo CPOTrained objects retrieved from a transformed data set using retrafo() sometimes have both the "retrafo" as well as the "invert" capability (precisely when all TOCPOs used had the constant.invert flag set, see Building Custom CPOs) and can then also be used for inversion. In that case, however, the "truth" column of an inverted prediction is dropped.

Inversion can be done on both predictions given by mlr Learners, as well as plain vectors, data.frames, and matrix objects.

Note that the prediction being inverted must have the form of a prediction done with the predict.type that an inverter expects as input for the predict.type given to invert() as an argument. This can be queried using the getCPOPredictType() function. If invert() is called with predict.type = p, then the prediction must be one made with a Learner that has predict.type set to getCPOPredictType(cpo)[p].


NULLCPO is the neutral element of %>>% and the operations it represents (composeCPO(), applyCPO(), and attachCPO()), i.e. when it is used as an argument of these functions, the data, Learner or CPO is not changed. NULLCPO is also the result pipeCPO() called with the empty list, and of retrafo() and inverter() when they are called for objects with no CPOTrained objects attached.

as.list(NULLCPO)  # the inverse of pipeCPO
#> list()
inverter(bh.task %>>% cpoPca())  # cpoPca is a TOCPO, so no inverter is created

Many getters give characteristic results for NULLCPO.

#> [1] "NULLCPO"
#> [1] "NULLCPO"
#> [1] "NULLCPO"
#> named list()
#> [1] "Empty parameter set."
#> named list()
getCPOOperatingType(NULLCPO)  # operates neither on features nor on targets.
#> character(0)
#> $handling
#>  [1] "numerics"   "factors"    "ordered"    "missings"   "cluster"   
#>  [6] "classif"    "multilabel" "regr"       "surv"       "oneclass"  
#> [11] "twoclass"   "multiclass" "prob"       "se"        
#> $adding
#> character(0)
#> $needed
#> character(0)
# applying NULLCPO leads to a retrafo() of NULLCPO, so it is its own CPOTrainedCPO
# NULLCPO has no effect on applyCPO and invert, so NULLCPO's capabilities are 0.
#> retrafo  invert 
#>       0       0

Some helper functions convert NULLCPO to NULL and back, while leaving other values as they are.

nullToNullcpo(10) # not changed
#> [1] 10
nullcpoToNull(10) # ditto
#> [1] 10

CPO Name and ID

A CPO has a “name” which identifies the general operation done by this CPO. For example, it is "pca" for a CPO created using cpoPca(). Furthermore, a CPO has an “ID” which is associated with the particular CPO object at hand. For primitive CPOs, it can be queried and set using getCPOId() and setCPOId(), and it can be set during construction, but it defaults to the CPO’s name. The ID will also be prefixed to the CPO’s hyperparameters after construction, if they are exported. This can help prevent hyperparameter name clashes when composing CPOs with otherwise identical hyperparameter names. It is possible to set the ID to NULL to have no prefix for hyperparameter names.

cpo = cpoPca()
#> [1] "pca"
#>               Type len   Def Constr Req Tunable Trafo
#> logical   -  TRUE      -   -    TRUE     -
#> pca.scale  logical   - FALSE      -   -    TRUE     -
getParamSet(setCPOId(cpo, ""))
#>                 Type len   Def Constr Req Tunable Trafo
#> logical   -  TRUE      -   -    TRUE     -
#>  logical   - FALSE      -   -    TRUE     -
getParamSet(setCPOId(cpo, NULL))
#>           Type len   Def Constr Req Tunable Trafo
#> center logical   -  TRUE      -   -    TRUE     -
#> scale  logical   - FALSE      -   -    TRUE     -

In the following (silly) example an error is thrown because of hyperparameter name clash. This can be avoided by setting the ID of one of the constituents to a different value.

cpo %>>% cpo
#> Error in parameterClashAssert(cpo1, cpo2, cpo1$, cpo2$ Parameters "", "pca.scale" occur in both pca and pca
#> Use the id parameter when constructing, or setCPOId, to prevent name collisions.
cpo %>>% setCPOId(cpo, "two")
#> (pca >> two<pca>)( = TRUE, pca.scale = FALSE, = TRUE, two.scale = FALSE)

CPO Properties

CPOs contain information about the kind of data they can work with, and what kind of data they produce. getCPOProperties returns a list with the slots handling, adding, needed. properties$handling indicates the kind of data a CPO can handle, properties$needed indicates the kind of data it needs the data receiver (e.g. attached learner) to have, and properties$adding lists the properties it adds to a given learner. An example is cpoDummyEncode(), a CPO that converts factors to numerics: The receiving learner needs to handle numerics, so properties$needed == "numerics", but it adds the ability to handle factors (since they are converted), so properties$adding = c("factors", "ordered").

#> $handling
#>  [1] "numerics"   "factors"    "ordered"    "missings"   "cluster"   
#>  [6] "classif"    "multilabel" "regr"       "surv"       "oneclass"  
#> [11] "twoclass"   "multiclass" "prob"       "se"        
#> $adding
#> [1] "factors" "ordered"
#> $needed
#> [1] "numerics"

As a result, cpoDummyEncode endows a Learner with the ability to train on data with factor variables:

train("classif.geoDA", bc.task)  # gives an error
#> Error in checkLearnerBeforeTrain(task, learner, weights): Task 'BreastCancer-example' has factor inputs in 'Cl.thickness, Cell.size, Cell.shape, Marg.adhes...', but learner 'classif.geoDA' does not support that!
train(cpoDummyEncode( = TRUE) %>>% makeLearner("classif.geoDA"), bc.task)
#> Model for; learner.class=CPOLearner
#> Trained on: = BreastCancer-example; obs = 683; features = 9
#> Hyperparameters: validation=NULL
#> [1] "twoclass"   "multiclass" "numerics"
getLearnerProperties(cpoDummyEncode(TRUE) %>>% makeLearner("classif.geoDA"))
#> [1] "numerics"   "factors"    "ordered"    "twoclass"   "multiclass"


As described in more detail in the Building Custom CPOs vignette, CPOs can have properties that are considered only when composing CPOs, or only when checking data returned by CPOs. In short, consider a CPO that does imputation, but only for factorial features. This CPO would need to have "missings" in its $adding properties slot, since it enables Learner to handle (some) Tasks that have missing values. However, this CPO may under certain circumstances still return data that has missing values. This discrepancy is recorded internally by having two “hidden” sets of properties that can be retrieved with getCPOProperties() with get.internal set to TRUE. These properties are adding.min, the minimal set of properties added, and needed.max, the maximal set of properties needed by consecutive operators. These can be understood as a description of the “worst case” behaviour of the CPO, since behaviour that is out of bounds of these sets causes an error by the mlrCPO-framework.

An example is the cpoApplyFun CPO: When it is constructed, it is not known what kind of properties will be added or needed, so adding.min is empty while needed.max is the set of all data properties. When composing CPOs, this CPO is handled as if it magically does exactly the data conversion necessary to make the CPOs or Learner coming after it work with the data. If this ends up not being the case, an error is thrown during application or training by the following CPO or Learner.

CPO Affect

When constructing a CPO, it is possible to restrict the columns on which the CPO operates using the affect.* parameters of the CPOConstructor. These parameters are:

# onlhy PCA columns that have '.Length' in their name
cpo = cpoPca(affect.pattern = ".Length")
#> $pattern
#> [1] ".Length"
triris = iris %>>% cpo
#>   Sepal.Width Petal.Width Species       PC1         PC2
#> 1         3.5         0.2  setosa -2.460241 -0.24479165
#> 2         3.0         0.2  setosa -2.538962 -0.06093579
#> 3         3.2         0.2  setosa -2.709611  0.08355948
#> 4         3.1         0.2  setosa -2.565116  0.25420858
#> 5         3.6         0.2  setosa -2.499602 -0.15286372
#> 6         3.9         0.4  setosa -2.066375 -0.40249369

CPO Parameter Export

Sometimes when using many CPOs, their hyperparameters may get messy. mlrCPO enables the user to control which hyperparameter get exported. The parameter “export” can be one of "export.default", "export.set", "export.unset", "export.default.set", "export.default.unset", "export.all", "export.none". “all” and “none” do what one expects; “default” exports the “recommended” parameters; “set” and “unset” export the values that have not been set, or only the values that were set (and are not left as default). “default.set” and “default.unset” work as “set” and “unset”, but restricted to the default exported parameters.

#> Trafo chain of 1 cpos:
#> scale(center = TRUE, scale = TRUE)
#> Operating: feature
#> ParamSet:
#>                 Type len  Def Constr Req Tunable Trafo
#> logical   - TRUE      -   -    TRUE     -
#> scale.scale  logical   - TRUE      -   -    TRUE     -
!cpoScale(export = "export.none")
#> Trafo chain of 1 cpos:
#> scale()[not exp'd: center = TRUE, scale = TRUE]
#> Operating: feature
#> ParamSet:
#> [1] "Empty parameter set."
!cpoScale(scale = FALSE, export = "export.unset")
#> Trafo chain of 1 cpos:
#> scale(center = TRUE)[not exp'd: scale = FALSE]
#> Operating: feature
#> ParamSet:
#>                 Type len  Def Constr Req Tunable Trafo
#> logical   - TRUE      -   -    TRUE     -

Syntactic Sugar

There are some %>>%-related operators that perform similar operations but may be more concise in certain applications. In general these operators are left-assiciative, i.e. they are evaluated after the expressions to their left were evaluated. Therefore, for example, a %>>% b %<<% c is equivalent to (a %>>% b) %<<% c. Exceptions are the assignment operators, %<>>% and %<<<%, as well as the %>|% operator, see below.

The operators are: