use nalgebra::DVector; /// Apply pool adjacent violators algorithm (PAVA) to fit monotonic (isotonic) regression /// /// Ports https://github.com/scikit-learn/scikit-learn/blob/9cb11110280a555fd881455d65a48694e1f6860d/sklearn/_isotonic.pyx#L11 /// Author: Nelle Varoquaux, Andrew Tulloch, Antony Lee /// An excellent implementation, kudos to the sklearn team! /// /// BSD 3-Clause License /// /// Copyright (c) 2007-2023 The scikit-learn developers. /// All rights reserved. /// /// Redistribution and use in source and binary forms, with or without /// modification, are permitted provided that the following conditions are met: /// /// * Redistributions of source code must retain the above copyright notice, this /// list of conditions and the following disclaimer. /// /// * Redistributions in binary form must reproduce the above copyright notice, /// this list of conditions and the following disclaimer in the documentation /// and/or other materials provided with the distribution. /// /// * Neither the name of the copyright holder nor the names of its /// contributors may be used to endorse or promote products derived from /// this software without specific prior written permission. /// /// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" /// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE /// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE /// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE /// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL /// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR /// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER /// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, /// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE /// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pub fn monotonic_regression_pava(mut y: DVector, mut w: DVector) -> DVector { // target describes a list of blocks. At any time, if [i..j] (inclusive) is an active block, then target[i] := j and target[j] := i // For "active" indices (block starts): // w[i] := sum{w_orig[j], j=[i..target[i]]} // y[i] := sum{y_orig[j]*w_orig[j], j=[i..target[i]]} / w[i] let n = y.nrows(); let mut target = DVector::from_iterator(n, 0..n); let mut i = 0; while i < n { let mut k = target[i] + 1; if k == n { break; } if y[i] < y[k] { i = k; continue; } let mut sum_wy = w[i] * y[i]; let mut sum_w = w[i]; loop { // We are within a decreasing subsequence let prev_y = y[k]; sum_wy += w[k] * y[k]; sum_w += w[k]; k = target[k] + 1; if k == n || prev_y < y[k] { // Non-singleton decreasing subsequence is finished, update first entry y[i] = sum_wy / sum_w; w[i] = sum_w; target[i] = k - 1; target[k - 1] = i; if i > 0 { // Backtrack if we can. This makes the algorithm single-pass and ensures O(n) complexity i = target[i - 1]; } // Otherwise, restart from the same point break } } } // Reconstruct the solution let mut i = 0; while i < n { let k = target[i] + 1; let y_i = y[i]; y.view_mut((i + 1, 0), (k - i - 1, 1)).fill(y_i); // y[i+1:k] = y[i] i = k; } return y; }