File size: 2,292 Bytes
64c992d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Error statistics and least-squares fitting shared across the analysis modules.

numpy.linalg.lstsq, not statsmodels: fitting by formula string through statsmodels' patsy parser is
about 1000x slower in a loop that fits many (solvent, solute, formula) combinations, so every fitting
harness here builds a plain design matrix and calls ols_fit directly. Where a module still needs to
report a p-value or similar statsmodels-only diagnostic, keep a separate, explicitly-named
statsmodels code path as a test oracle, not the harness itself.
"""
import numpy as np


def rmse(predicted, actual):
    predicted = np.asarray(predicted, dtype=float)
    actual = np.asarray(actual, dtype=float)
    return float(np.sqrt(np.mean(np.square(predicted - actual))))


def mae(predicted, actual):
    predicted = np.asarray(predicted, dtype=float)
    actual = np.asarray(actual, dtype=float)
    return float(np.mean(np.abs(predicted - actual)))


def median_ae(predicted, actual):
    predicted = np.asarray(predicted, dtype=float)
    actual = np.asarray(actual, dtype=float)
    return float(np.median(np.abs(predicted - actual)))


def summarize_errors(predicted, actual):
    """{"n", "rmse", "mae", "median_ae"} for one (predicted, actual) pair."""
    predicted = np.asarray(predicted, dtype=float)
    actual = np.asarray(actual, dtype=float)
    return {
        "n": int(predicted.size),
        "rmse": rmse(predicted, actual),
        "mae": mae(predicted, actual),
        "median_ae": median_ae(predicted, actual),
    }


def ols_fit(design, response):
    """Least-squares coefficient vector for `design @ beta ~ response`. Callers build `design`
    (including any intercept column) and drop non-finite rows themselves -- this is a thin,
    single-purpose wrapper, not a formula parser."""
    design = np.asarray(design, dtype=float)
    response = np.asarray(response, dtype=float)
    beta, _residuals, _rank, _sv = np.linalg.lstsq(design, response, rcond=None)
    return beta


def linear_fit_1d(x, y):
    """(intercept, slope) of the least-squares line through (x, y)."""
    x = np.asarray(x, dtype=float)
    y = np.asarray(y, dtype=float)
    design = np.column_stack([np.ones(len(x)), x])
    intercept, slope = ols_fit(design, y)
    return float(intercept), float(slope)