1#![forbid(unsafe_code)]
2use thiserror::Error;
5
6pub mod transforms;
7
8pub use transforms::{
9 AsinhScale, AsinhScaleState, IdentityPositive, IdentityPositiveState, Log, Log1pShift,
10 Log1pShiftState, LogState, Standardize, StandardizeState,
11};
12
13#[derive(Debug, Clone, PartialEq, Eq, Error)]
15pub enum TransformError {
16 #[error("target vector must contain at least one value")]
18 EmptyInput,
19
20 #[error("target contains a non-finite value")]
22 NonFiniteValue,
23
24 #[error("target value must be finite and > 0")]
26 NonPositiveValue,
27
28 #[error("target value is below the fitted lower bound")]
30 BelowLowerBound,
31
32 #[error("target scale must be positive")]
34 ZeroScale,
35
36 #[error("output length is {actual}, expected {expected}")]
38 LengthMismatch {
39 expected: usize,
41 actual: usize,
43 },
44}
45
46pub trait TargetTransform {
48 type State;
50
51 fn fit(y: &[f64]) -> Result<Self::State, TransformError>;
58 fn transform(state: &Self::State, y: f64) -> f64;
60 fn inverse(state: &Self::State, value: f64) -> f64;
62
63 fn fit_transform(y: &[f64]) -> Result<(Self::State, Vec<f64>), TransformError> {
69 let state = Self::fit(y)?;
70 let transformed = Self::transform_slice(&state, y)?;
71 Ok((state, transformed))
72 }
73
74 fn transform_slice(state: &Self::State, y: &[f64]) -> Result<Vec<f64>, TransformError> {
82 let mut out = vec![0.0; y.len()];
83 Self::transform_into(state, y, &mut out)?;
84 Ok(out)
85 }
86
87 fn transform_into(
96 state: &Self::State,
97 y: &[f64],
98 out: &mut [f64],
99 ) -> Result<(), TransformError> {
100 map_slice_into(y, out, validate_finite, |value| {
101 Self::transform(state, value)
102 })
103 }
104
105 fn inverse_slice(state: &Self::State, values: &[f64]) -> Result<Vec<f64>, TransformError> {
112 let mut out = vec![0.0; values.len()];
113 Self::inverse_into(state, values, &mut out)?;
114 Ok(out)
115 }
116
117 fn inverse_into(
125 state: &Self::State,
126 values: &[f64],
127 out: &mut [f64],
128 ) -> Result<(), TransformError> {
129 map_slice_into(values, out, validate_finite, |value| {
130 Self::inverse(state, value)
131 })
132 }
133}
134
135pub(crate) fn map_slice_into(
136 values: &[f64],
137 out: &mut [f64],
138 validate: impl FnOnce(&[f64]) -> Result<(), TransformError>,
139 mut map: impl FnMut(f64) -> f64,
140) -> Result<(), TransformError> {
141 validate_output_len(values.len(), out.len())?;
142 validate(values)?;
143 for (out, value) in out.iter_mut().zip(values.iter().copied()) {
144 *out = map(value);
145 }
146 Ok(())
147}
148
149pub(crate) fn validate_output_len(expected: usize, actual: usize) -> Result<(), TransformError> {
150 if actual == expected {
151 Ok(())
152 } else {
153 Err(TransformError::LengthMismatch { expected, actual })
154 }
155}
156
157pub(crate) fn validate_non_empty_finite(values: &[f64]) -> Result<(), TransformError> {
158 if values.is_empty() {
159 return Err(TransformError::EmptyInput);
160 }
161 validate_finite(values)
162}
163
164pub(crate) fn validate_finite(values: &[f64]) -> Result<(), TransformError> {
165 if values.iter().all(|value| value.is_finite()) {
166 Ok(())
167 } else {
168 Err(TransformError::NonFiniteValue)
169 }
170}
171
172pub(crate) fn validate_positive(values: &[f64]) -> Result<(), TransformError> {
173 validate_non_empty_finite(values)?;
174 if values.iter().all(|value| *value > 0.0) {
175 Ok(())
176 } else {
177 Err(TransformError::NonPositiveValue)
178 }
179}
180
181pub(crate) fn validate_shifted_non_negative(
182 values: &[f64],
183 shift: f64,
184) -> Result<(), TransformError> {
185 validate_finite(values)?;
186 if values.iter().all(|value| *value + shift >= 0.0) {
187 Ok(())
188 } else {
189 Err(TransformError::BelowLowerBound)
190 }
191}
192
193pub(crate) fn median_sorted(values: &[f64]) -> Option<f64> {
194 match values.len() {
195 0 => None,
196 len if len % 2 == 1 => Some(values[len / 2]),
197 len => {
198 let upper = len / 2;
199 Some((values[upper - 1] + values[upper]) / 2.0)
200 }
201 }
202}
203
204pub mod prelude {
206 pub use crate::{
207 AsinhScale, AsinhScaleState, IdentityPositive, IdentityPositiveState, Log, Log1pShift,
208 Log1pShiftState, LogState, Standardize, StandardizeState, TargetTransform, TransformError,
209 };
210}