1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//! Blom (1958) normal scores using average ranks.
use crateStatError;
use crate;
use crateerfc_inv;
use crate;
use SQRT_2;
/// Φ⁻¹(p) — the standard normal quantile function.
///
/// Derivation: `erfc_inv(p) = −norm_ppf(p/2)/√2` (from `special/inverse.rs`)
/// ⟹ `norm_ppf(p) = −√2·erfc_inv(2p)`. We route through the **public**
/// `special::erfc_inv` because `norm_ppf` is private in that module.
pub
/// Blom (1958) normal scores: Φ⁻¹((rᵢ − 3/8) / (n + 1/4)).
///
/// **Convention:** `rᵢ` is the average rank of the i-th finite observation;
/// `a = 3/8` (Blom only — van der Waerden `a=0` and Rankit `a=1/2` are not
/// provided). Denominator `n + 1/4 = n − 2·(3/8) + 1`. Φ⁻¹ via public
/// `special::erfc_inv`: `Φ⁻¹(p) = −√2·erfc_inv(2p)`.
/// NaN values are omitted (Omit policy). Output length = finite count.
///
/// **Matches** `scipy.stats.norm.ppf((rankdata(v,'average') − 3/8) / (n + 1/4))`.
///
/// # Parameters
/// - `v`: input slice; may contain NaN.
///
/// # Returns
/// Normal scores in the order of the finite values of `v`.
///
/// # Errors
/// - [`StatError::EmptyInput`] if `v` is empty.
/// - [`StatError::AllNaN`] if all values are NaN.
///
/// # Examples
/// ```
/// use commonstats::transform::normal_scores;
/// let zs = normal_scores(&[1.0, 2.0, 3.0]).unwrap();
/// // Symmetric: first score is negative, last is positive
/// assert!(zs[0] < 0.0 && zs[2] > 0.0);
/// ```