blob: 4d8e279706835407ccacd3a81c4a89245ef65263 [file]
#-------------------------------------------------------------
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
#-------------------------------------------------------------
# Applies a fitted power transformation and optional standardization.
# Transforms each feature using its previously estimated lambda and scaling parameters.
#
# INPUT:
# -------------------------------------------------------------------------------------
# X Input feature matrix of shape n-by-m
# lambdas Precomputed lambda parameters of shape 1-by-m, one per column
# means Transformed column means of shape 1-by-m; empty to skip standardization
# scales Transformed column scales of shape 1-by-m; empty to skip standardization
# method Power transformation method: "yeo-johnson" (default) or "box-cox"
# -------------------------------------------------------------------------------------
#
# OUTPUT:
# -------------------------------------------------------------------------------------
# Y Power-transformed matrix of shape n-by-m
# -------------------------------------------------------------------------------------
m_powerTransformApply = function(
Matrix[Double] X,
Matrix[Double] lambdas,
Matrix[Double] means,
Matrix[Double] scales,
String method="yeo-johnson")
return (Matrix[Double] Y)
{
n = nrow(X)
m = ncol(X)
if (method != "yeo-johnson" & method != "box-cox") {
stop("powerTransformApply: unsupported method '" + method +
"'; expected 'yeo-johnson' or 'box-cox'")
}
validatedX = replace(target=X, pattern=NaN, replacement=1.0)
if (method == "box-cox" & min(validatedX) <= 0.0) {
stop("powerTransformApply: Box-Cox requires strictly positive input")
}
if (nrow(lambdas) != 1 | ncol(lambdas) != m) {
stop("powerTransformApply: lambdas must have shape 1-by-ncol(X)")
}
hasMeans = nrow(means) > 0 | ncol(means) > 0
hasScales = nrow(scales) > 0 | ncol(scales) > 0
if (hasMeans != hasScales) {
stop("powerTransformApply: means and scales must either both be provided or both be empty")
}
if (hasMeans & (nrow(means) != 1 | ncol(means) != m |
nrow(scales) != 1 | ncol(scales) != m)) {
stop("powerTransformApply: means and scales must have shape 1-by-ncol(X)")
}
Y = matrix(0.0, rows=n, cols=m)
# Handle boundary points (0 and 2)
eps = 1e-12
# Loop over columns for transformation
for (j in 1:m){
x = X[,j]
nanMask = is.na(x)
lambda_j = as.scalar(lambdas[1,j])
if (method == "box-cox") {
x = replace(target=x, pattern=NaN, replacement=1.0)
if (abs(lambda_j) < eps) {
y = log(x)
}
else {
y = (x^lambda_j - 1.0) / lambda_j
}
}
else {
x = replace(target=x, pattern=NaN, replacement=0.0)
nonnegative = x >= 0
# Use intermediate inputs to avoid invalid domains in scoring or fractional powers
x_pos = ifelse(nonnegative, x, 0.0)
x_neg = ifelse(nonnegative, 0.0, x)
# Transform nonnegative values (x>=0)
if (abs(lambda_j) < eps) {
y_pos = log(x_pos + 1)
}
else {
y_pos = ((x_pos + 1)^lambda_j - 1) / lambda_j
}
# Transform negative values (x<0)
if (abs(lambda_j - 2) < eps) {
y_neg = -log(1 - x_neg)
}
else {
y_neg = -((1 - x_neg)^(2 - lambda_j) - 1) / (2 - lambda_j)
}
# Combine the two branches
y = ifelse(nonnegative, y_pos, y_neg)
}
Y[,j] = ifelse(nanMask, NaN, y)
}
if (hasMeans) {
Y = (Y - means) / scales
}
}