slope 6.6.0
Loading...
Searching...
No Matches
normalize.h
Go to the documentation of this file.
1
5
6#pragma once
7
8#include "jit_normalization.h"
9#include "math.h"
10#include "observation_weights.h"
11#include <Eigen/SparseCore>
12#include <algorithm>
13#include <cmath>
14#include <limits>
15
16namespace slope {
17
18namespace detail {
19
20class WeightRangeSums
21{
22public:
23 explicit WeightRangeSums(const Eigen::VectorXd& weights)
24 : weights(weights)
25 {
26 }
27
28 double sum(Eigen::Index begin, Eigen::Index end);
29
30private:
31 const Eigen::VectorXd& weights;
32 Eigen::VectorXd sums;
33};
34
35template<typename T, typename F>
36void
37forEachWeightedColumnValue(const Eigen::MatrixBase<T>& x,
38 const Eigen::VectorXd& weights,
39 const int column,
40 const Eigen::Index,
41 WeightRangeSums*,
42 F visit)
43{
44 for (Eigen::Index i = 0; i < x.rows(); ++i) {
45 if (weights(i) > 0.0) {
46 visit(x(i, column), weights(i));
47 }
48 }
49}
50
51template<typename T, typename F>
52void
53forEachWeightedColumnValue(const Eigen::SparseMatrixBase<T>& x,
54 const Eigen::VectorXd& weights,
55 const int column,
56 const Eigen::Index support,
57 WeightRangeSums* range_sums,
58 F visit)
59{
60 Eigen::Index stored_rows = 0;
61 for (typename T::InnerIterator it(x.derived(), column); it; ++it) {
62 if (weights(it.row()) > 0.0) {
63 visit(it.value(), weights(it.row()));
64 ++stored_rows;
65 }
66 }
67
68 // Implicit zeros matter only when they occur on positive-weight rows.
69 if (stored_rows < support) {
70 double implicit_weight = 0.0;
71 if (range_sums) {
72 Eigen::Index next_row = 0;
73 for (typename T::InnerIterator it(x.derived(), column); it; ++it) {
74 implicit_weight += range_sums->sum(next_row, it.row());
75 next_row = it.row() + 1;
76 }
77 implicit_weight += range_sums->sum(next_row, weights.size());
78 }
79 visit(0.0, implicit_weight);
80 }
81}
82
83template<typename T>
84Eigen::VectorXd
85weightedFeatureCenters(const T& x,
86 const std::string& type,
87 const Eigen::VectorXd& weights)
88{
89 Eigen::VectorXd centers(x.cols());
90 const double total = weights.sum();
91 const Eigen::Index support = positiveWeightCount(weights);
92 for (Eigen::Index j = 0; j < x.cols(); ++j) {
93 double value =
94 type == "min" ? std::numeric_limits<double>::infinity() : 0.0;
95 forEachWeightedColumnValue(
96 x, weights, j, support, nullptr, [&](double entry, double weight) {
97 if (type == "min") {
98 value = std::min(value, entry);
99 } else {
100 value += (weight / total) * entry;
101 }
102 });
103 centers(j) = value;
104 }
105 return centers;
106}
107
108template<typename T>
109Eigen::VectorXd
110weightedFeatureScales(const T& x,
111 const std::string& type,
112 const Eigen::VectorXd& weights)
113{
114 Eigen::VectorXd scales(x.cols());
115 Eigen::VectorXd means;
116 if (type == "sd") {
117 means = weightedFeatureCenters(x, "mean", weights);
118 }
119 const double total = weights.sum();
120 const Eigen::Index support = positiveWeightCount(weights);
121 WeightRangeSums range_sums(weights);
122 for (Eigen::Index j = 0; j < x.cols(); ++j) {
123 double value = 0.0;
124 double minimum = std::numeric_limits<double>::infinity();
125 double maximum = -std::numeric_limits<double>::infinity();
126 forEachWeightedColumnValue(
127 x,
128 weights,
129 j,
130 support,
131 type == "sd" ? &range_sums : nullptr,
132 [&](double entry, double weight) {
133 if (type == "range") {
134 minimum = std::min(minimum, entry);
135 maximum = std::max(maximum, entry);
136 } else if (type == "max_abs") {
137 value = std::max(value, std::abs(entry));
138 } else if (type == "l1") {
139 value += (weight / total) * std::abs(entry);
140 } else {
141 double centered = entry;
142 if (type == "sd") {
143 minimum = std::min(minimum, entry);
144 maximum = std::max(maximum, entry);
145 centered -= means(j);
146 }
147 value = std::hypot(value, std::sqrt(weight / total) * centered);
148 }
149 });
150 if (type == "range") {
151 value = maximum - minimum;
152 } else if (type == "sd" && minimum == maximum) {
153 // Rounding in the mean must not give constant columns a nonzero scale.
154 value = 0.0;
155 } else if (type == "l1") {
156 value *= support;
157 } else if (type == "l2") {
158 value *= std::sqrt(support);
159 }
160 scales(j) = value;
161 }
162 return scales;
163}
164
165} // namespace detail
166
184template<typename T>
185void
186computeCenters(Eigen::VectorXd& x_centers,
187 const T& x,
188 const std::string& type,
189 const Eigen::VectorXd& observation_weights = Eigen::VectorXd())
190{
191 int p = x.cols();
192 Eigen::VectorXd weights;
193 if (observation_weights.size() > 0) {
194 weights =
195 detail::normalizeObservationWeights(observation_weights, x.rows());
196 if (detail::uniformObservationWeights(weights)) {
197 weights.resize(0);
198 }
199 }
200
201 if (type == "manual") {
202 if (x_centers.size() != p) {
203 throw std::invalid_argument("Invalid dimensions in centers");
204 }
205
206 if (!x_centers.allFinite()) {
207 throw std::invalid_argument("Centers must be finite");
208 }
209
210 } else if (weights.size() > 0 && (type == "mean" || type == "min")) {
211 x_centers = detail::weightedFeatureCenters(x, type, weights);
212 } else if (type == "mean") {
213 x_centers = means(x);
214 } else if (type == "min") {
215 x_centers = mins(x);
216 } else if (type != "none") {
217 throw std::invalid_argument("Invalid centering type");
218 }
219}
220
240template<typename T>
241void
242computeScales(Eigen::VectorXd& x_scales,
243 const T& x,
244 const std::string& type,
245 const Eigen::VectorXd& observation_weights = Eigen::VectorXd())
246{
247 int p = x.cols();
248 Eigen::VectorXd weights;
249 if (observation_weights.size() > 0) {
250 weights =
251 detail::normalizeObservationWeights(observation_weights, x.rows());
252 if (detail::uniformObservationWeights(weights)) {
253 weights.resize(0);
254 }
255 }
256
257 if (type == "manual") {
258 if (x_scales.size() != p) {
259 throw std::invalid_argument("Invalid dimensions in scales");
260 }
261 if (!x_scales.allFinite()) {
262 throw std::invalid_argument("Scales must be finite");
263 }
264 } else if (weights.size() > 0 &&
265 (type == "sd" || type == "l1" || type == "l2" ||
266 type == "max_abs" || type == "range")) {
267 x_scales = detail::weightedFeatureScales(x, type, weights);
268 } else if (type == "sd") {
269 x_scales = stdDevs(x);
270 } else if (type == "l1") {
271 x_scales = l1Norms(x);
272 } else if (type == "l2") {
273 x_scales = l2Norms(x);
274 } else if (type == "max_abs") {
275 x_scales = maxAbs(x);
276 } else if (type == "range") {
277 x_scales = ranges(x);
278 } else if (type != "none") {
279 throw std::invalid_argument("Invalid scaling type");
280 }
281}
282
305template<typename T>
307normalize(Eigen::MatrixBase<T>& x,
308 Eigen::VectorXd& x_centers,
309 Eigen::VectorXd& x_scales,
310 const std::string& centering_type,
311 const std::string& scaling_type,
312 const bool modify_x,
313 const Eigen::VectorXd& observation_weights = Eigen::VectorXd())
314{
315 const int p = x.cols();
316
317 computeCenters(x_centers, x, centering_type, observation_weights);
318 computeScales(x_scales, x, scaling_type, observation_weights);
319
320 // Handle zero-variance columns by setting their scale to 1.0
321 // to avoid division by zero during normalization
322 if (scaling_type != "none" && scaling_type != "manual") {
323 for (int j = 0; j < p; ++j) {
324 if (std::abs(x_scales(j)) == 0.0) {
325 x_scales(j) = 1.0;
326 }
327 }
328 }
329
330 bool center = centering_type != "none";
331 bool scale = scaling_type != "none";
332 bool center_jit = center && !modify_x;
333 bool scale_jit = scale && !modify_x;
334
335 JitNormalization jit_normalization;
336
337 if (center_jit && scale_jit) {
338 jit_normalization = JitNormalization::Both;
339 } else if (center_jit) {
340 jit_normalization = JitNormalization::Center;
341 } else if (scale_jit) {
342 jit_normalization = JitNormalization::Scale;
343 } else {
344 jit_normalization = JitNormalization::None;
345 }
346
347 if (modify_x && (center || scale)) {
348 const bool ignored_rows = observation_weights.size() > 0 &&
349 (observation_weights.array() == 0.0).any();
350 if (ignored_rows) {
351 for (int j = 0; j < p; ++j) {
352 for (Eigen::Index i = 0; i < x.rows(); ++i) {
353 // Ignored extremes must not overflow and contaminate later products.
354 if (observation_weights(i) == 0.0) {
355 x(i, j) = 0.0;
356 continue;
357 }
358 if (center) {
359 x(i, j) -= x_centers(j);
360 }
361 if (scale) {
362 x(i, j) /= x_scales(j);
363 }
364 }
365 }
366 } else {
367 for (int j = 0; j < p; ++j) {
368 if (center) {
369 x.col(j).array() -= x_centers(j);
370 }
371 if (scale) {
372 x.col(j).array() /= x_scales(j);
373 }
374 }
375 }
376 }
377
378 return jit_normalization;
379}
380
403template<typename T>
405normalize(Eigen::SparseMatrixBase<T>& x,
406 Eigen::VectorXd& x_centers,
407 Eigen::VectorXd& x_scales,
408 const std::string& centering_type,
409 const std::string& scaling_type,
410 const bool,
411 const Eigen::VectorXd& observation_weights = Eigen::VectorXd())
412{
413 computeCenters(x_centers, x, centering_type, observation_weights);
414 computeScales(x_scales, x, scaling_type, observation_weights);
415
416 // Handle zero-variance columns by setting their scale to 1.0
417 // to avoid division by zero during normalization
418 const int p = x.cols();
419 if (scaling_type != "none" && scaling_type != "manual") {
420 for (int j = 0; j < p; ++j) {
421 if (std::abs(x_scales(j)) == 0.0) {
422 x_scales(j) = 1.0;
423 }
424 }
425 }
426
427 bool center = centering_type != "none";
428 bool scale = scaling_type != "none";
429 bool center_jit = center;
430 bool scale_jit = scale;
431
432 JitNormalization jit_normalization;
433
434 if (center_jit && scale_jit) {
435 jit_normalization = JitNormalization::Both;
436 } else if (center_jit) {
437 jit_normalization = JitNormalization::Center;
438 } else if (scale_jit) {
439 jit_normalization = JitNormalization::Scale;
440 } else {
441 jit_normalization = JitNormalization::None;
442 }
443
444 // TODO: Implement in-place scaling for sparse matrices.
445 // if (modify_x && scaling_type != "none") {
446 // for (int j = 0; j < x.cols(); ++j) {
447 // for (Eigen::SparseMatrix<double>::InnerIterator it(x, j); it; ++it) {
448 // it.valueRef() = it.value() / x_scales(j);
449 // }
450 // }
451 // }
452
453 return jit_normalization;
454}
455
476std::tuple<Eigen::VectorXd, Eigen::MatrixXd>
477rescaleCoefficients(const Eigen::VectorXd& beta0,
478 const Eigen::SparseMatrix<double>& beta,
479 const Eigen::VectorXd& x_centers,
480 const Eigen::VectorXd& x_scales,
481 const bool intercept);
482
483} // namespace slope
Enums to control predictor standardization behavior.
Mathematical support functions for the slope package.
Namespace containing SLOPE regression implementation.
Definition clusters.h:11
Eigen::VectorXd means(const Eigen::SparseMatrixBase< T > &x)
Computes the arithmetic mean for each column of a sparse matrix.
Definition math.h:708
Eigen::VectorXd l1Norms(const T &x)
Computes the L1 (Manhattan) norms for each column of a matrix.
Definition math.h:589
JitNormalization
Enums to control predictor standardization behavior.
@ None
No JIT normalization.
void computeCenters(Eigen::VectorXd &x_centers, const T &x, const std::string &type, const Eigen::VectorXd &observation_weights=Eigen::VectorXd())
Definition normalize.h:186
Eigen::VectorXd ranges(const Eigen::SparseMatrixBase< T > &x)
Computes the range (max - min) for each column of a matrix.
Definition math.h:845
std::tuple< Eigen::VectorXd, Eigen::MatrixXd > rescaleCoefficients(const Eigen::VectorXd &beta0, const Eigen::SparseMatrix< double > &beta, const Eigen::VectorXd &x_centers, const Eigen::VectorXd &x_scales, const bool intercept)
Rescales the coefficients using the given parameters.
void computeScales(Eigen::VectorXd &x_scales, const T &x, const std::string &type, const Eigen::VectorXd &observation_weights=Eigen::VectorXd())
Definition normalize.h:242
Eigen::VectorXd mins(const Eigen::SparseMatrixBase< T > &x)
Computes the minimum value for each column of a sparse matrix.
Definition math.h:898
JitNormalization normalize(Eigen::MatrixBase< T > &x, Eigen::VectorXd &x_centers, Eigen::VectorXd &x_scales, const std::string &centering_type, const std::string &scaling_type, const bool modify_x, const Eigen::VectorXd &observation_weights=Eigen::VectorXd())
Definition normalize.h:307
Eigen::VectorXd stdDevs(const Eigen::SparseMatrixBase< T > &x)
Computes the standard deviation for each column of a matrix.
Definition math.h:754
Eigen::VectorXd l2Norms(const Eigen::SparseMatrixBase< T > &x)
Computes the L2 (Euclidean) norms for each column of a sparse matrix.
Definition math.h:614
Eigen::VectorXd maxAbs(const Eigen::SparseMatrixBase< T > &x)
Computes the maximum absolute value for each column of a matrix.
Definition math.h:657