Power Transforms
A power transform can compress large differences, expand them, reverse order, or remove information. The effect depends on the exponent and the input domain. “Apply a power transform” is therefore incomplete without naming both.
This chapter covers fixed maps such as square root, square, and reciprocal. The related Box–Cox and Yeo–Johnson chapters cover families that can learn a shape parameter from training data.
Start with positive inputs and one exponent
Let $x$ be a strictly positive input and $p$ a chosen real exponent. Write $z$ for its transformed value:
For $p=1/2$, this is the square root. For $p=2$, it is the square. For $p=-1$, it is the reciprocal, $1/x$. Begin with [1, 4, 9, 16] and compare these maps:
| Input x | Transformed value |
|---|---|
| 1 | 1.0000 |
| 4 | 2.0000 |
| 9 | 3.0000 |
| 16 | 4.0000 |
Think about the gaps the model will see
On positive inputs, an exponent between 0 and 1 produces an increasing, concave map: the same absolute increment makes a smaller output change at a larger starting value. An exponent greater than 1 produces an increasing, convex map, so that increment has a larger output effect at larger inputs.
A negative exponent is decreasing. A reciprocal turns a long positive duration into a small inverse duration, which can be meaningful for a rate-like representation. It also becomes unbounded as the duration approaches zero. Compression at one end can create sensitivity at the other.
These are changes in geometry, not universal improvements. A distance-based model now compares gaps between transformed values. A linear model in the transformed feature describes a different relationship with the original input.
A linear predictor after a square root
If $z=\sqrt{x}$, a linear predictor with learned intercept $a$ and coefficient $b$ is $\hat y=a+bz=a+b\sqrt{x}$. It is linear in the transformed coordinate, but curved as a function of the original $x$. That can be useful if the response grows more slowly at larger inputs.
The domain determines whether information survives
A square is one-to-one on nonnegative inputs, but not on all real inputs. Both −3 and +3 become 9. If the sign carries information, replacing the feature by its square removes that information.
Arbitrary fractional powers of negative values are not generally real. Even when a real odd root exists, a floating-point exponent may not express that intended operation. Use np.cbrt or std::cbrt for real cube roots rather than assuming a generic fractional-power call handles negative inputs the same way.
Zero is valid for a square or square root, but not for a reciprocal. The general implementation below deliberately restricts inputs to strictly positive values so one policy works across its exponent choices.
Raw powers and PowerTransformer are different things
np.power(x, p) applies the exponent you provide. It does not learn a parameter or standardize the output. Scikit-learn’s PowerTransformer instead fits a Box–Cox or Yeo–Johnson parameter for each feature, and standardizes the transformed columns by default.
Those families use more than the raw expression $x^p$. For example, Box–Cox uses a logarithm at its parameter value zero; raw $x^0$ would be constant. Box–Cox with parameter −1 gives $1-1/x$, which is increasing on positive inputs, while the raw reciprocal $1/x$ is decreasing.
The family’s parameterization is what provides those properties. See Box–Cox for the formula and its log limit, and Yeo–Johnson for a version that accepts negative and zero values.
Choose an operation for a reason, then evaluate it
A physical definition may specify a reciprocal or square root directly. If the exponent is a modeling choice, compare it within the model-selection procedure. Choosing it after inspecting final-test performance makes that test part of selection.
Replacing a column with its square also differs from adding a squared column while retaining the original. The latter is a feature expansion: the model can use both coordinates. Polynomial feature generation is therefore related to, but not identical with, changing one feature’s scale.
Check input validity, transformed ranges, missing-value handling, and the downstream model’s behavior. Reuse any learned exponent, offset, or subsequent scaler on future inputs. The pipeline notes show how to keep that sequence together.
Compare fixed powers and their inverses
For positive input and a nonzero exponent, the inverse raises the positive transformed value to power $1/p$. The example excludes the constant map from the inverse check.
Run the fixed-power comparison
import numpy as np
x = np.array([1., 4., 9., 16.])
for p in [0.5, 1., 2., -1., 0.]:
z = np.power(x, p)
print(p, z.round(4).tolist())
if p != 0:
np.testing.assert_allclose(np.power(z, 1 / p), x)
print(np.cbrt([-8., 0., 8.]).tolist())
0.5 [1.0, 2.0, 3.0, 4.0]
1.0 [1.0, 4.0, 9.0, 16.0]
2.0 [1.0, 16.0, 81.0, 256.0]
-1.0 [1.0, 0.25, 0.1111, 0.0625]
0.0 [1.0, 1.0, 1.0, 1.0]
[-2.0, 0.0, 2.0]
If the transformation is applied to a target, inversion of a predicted transformed mean needs extra care. The log-target counterexample explains why a nonlinear inverse does not generally commute with averaging.
C++: separate the forward domain from inverse existence
The forward helper permits exponent zero, while the inverse rejects it. It also detects when floating-point overflow or underflow takes an otherwise positive result outside the usable range. These are fixed transforms; no parameters are fitted.
Compile the positive-domain power helpers
#include <cmath>
#include <iomanip>
#include <iostream>
#include <stdexcept>
double positive_power(double x, double p) {
if (!(x > 0) || !std::isfinite(x) || !std::isfinite(p))
throw std::invalid_argument("positive finite x and finite exponent required");
const double z = std::pow(x, p);
if (!(z > 0) || !std::isfinite(z))
throw std::overflow_error("power overflow or underflow");
return z;
}
double inverse_positive_power(double z, double p) {
if (p == 0 || !std::isfinite(p))
throw std::invalid_argument("nonzero finite exponent required");
return positive_power(z, 1 / p);
}
int main() {
std::cout << std::fixed << std::setprecision(4);
for (double p : {0.5, 2., -1.}) {
std::cout << "p=" << p << ":";
for (double x : {1.,4.,9.,16.})
std::cout << " " << positive_power(x, p);
std::cout << "\n";
}
}
p=0.5000: 1.0000 2.0000 3.0000 4.0000
p=2.0000: 1.0000 16.0000 81.0000 256.0000
p=-1.0000: 1.0000 0.2500 0.1111 0.0625References and further reading
- NumPy: power — fixed exponents and behavior for negative inputs and non-integer powers.
- NumPy: cbrt — the real cube-root operation.
- Scikit-learn: PowerTransformer — fitted Box–Cox/Yeo–Johnson parameters and the separate standardization step.