"""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)