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 #[inline]
69 fn fit_transform(y: &[f64]) -> Result<(Self::State, Vec<f64>), TransformError> {
70 let state = Self::fit(y)?;
71 let transformed = Self::transform_slice(&state, y)?;
72 Ok((state, transformed))
73 }
74
75 #[inline]
83 fn transform_slice(state: &Self::State, y: &[f64]) -> Result<Vec<f64>, TransformError> {
84 let mut out = vec![0.0; y.len()];
85 Self::transform_into(state, y, &mut out)?;
86 Ok(out)
87 }
88
89 #[inline]
98 fn transform_into(
99 state: &Self::State,
100 y: &[f64],
101 out: &mut [f64],
102 ) -> Result<(), TransformError> {
103 map_slice_into(y, out, validate_finite, |value| {
104 Self::transform(state, value)
105 })
106 }
107
108 #[inline]
115 fn inverse_slice(state: &Self::State, values: &[f64]) -> Result<Vec<f64>, TransformError> {
116 let mut out = vec![0.0; values.len()];
117 Self::inverse_into(state, values, &mut out)?;
118 Ok(out)
119 }
120
121 #[inline]
129 fn inverse_into(
130 state: &Self::State,
131 values: &[f64],
132 out: &mut [f64],
133 ) -> Result<(), TransformError> {
134 map_slice_into(values, out, validate_finite, |value| {
135 Self::inverse(state, value)
136 })
137 }
138}
139
140pub(crate) fn map_slice_into(
141 values: &[f64],
142 out: &mut [f64],
143 validate: impl FnOnce(&[f64]) -> Result<(), TransformError>,
144 mut map: impl FnMut(f64) -> f64,
145) -> Result<(), TransformError> {
146 validate_output_len(values.len(), out.len())?;
147 validate(values)?;
148 for (out, value) in out.iter_mut().zip(values.iter().copied()) {
149 *out = map(value);
150 }
151 Ok(())
152}
153
154pub(crate) fn validate_output_len(expected: usize, actual: usize) -> Result<(), TransformError> {
155 if actual == expected {
156 Ok(())
157 } else {
158 Err(TransformError::LengthMismatch { expected, actual })
159 }
160}
161
162pub(crate) fn validate_non_empty_finite(values: &[f64]) -> Result<(), TransformError> {
163 if values.is_empty() {
164 return Err(TransformError::EmptyInput);
165 }
166 validate_finite(values)
167}
168
169pub(crate) fn validate_finite(values: &[f64]) -> Result<(), TransformError> {
170 if values.iter().all(|value| value.is_finite()) {
171 Ok(())
172 } else {
173 Err(TransformError::NonFiniteValue)
174 }
175}
176
177pub(crate) fn validate_positive(values: &[f64]) -> Result<(), TransformError> {
178 validate_non_empty_finite(values)?;
179 if values.iter().all(|value| *value > 0.0) {
180 Ok(())
181 } else {
182 Err(TransformError::NonPositiveValue)
183 }
184}
185
186pub(crate) fn validate_shifted_non_negative(
187 values: &[f64],
188 shift: f64,
189) -> Result<(), TransformError> {
190 validate_finite(values)?;
191 if values.iter().all(|value| *value + shift >= 0.0) {
192 Ok(())
193 } else {
194 Err(TransformError::BelowLowerBound)
195 }
196}
197
198pub(crate) fn median_sorted(values: &[f64]) -> Option<f64> {
199 match values.len() {
200 0 => None,
201 len if len % 2 == 1 => Some(values[len / 2]),
202 len => {
203 let upper = len / 2;
204 Some((values[upper - 1] + values[upper]) / 2.0)
205 }
206 }
207}
208
209pub mod prelude {
211 pub use crate::{
212 AsinhScale, AsinhScaleState, IdentityPositive, IdentityPositiveState, Log, Log1pShift,
213 Log1pShiftState, LogState, Standardize, StandardizeState, TargetTransform, TransformError,
214 };
215}