Add Satterthwaite and Kenward-Roger fixed-effect tests
This commit is contained in:
13
include/spectra_reml/distributions.hpp
Normal file
13
include/spectra_reml/distributions.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace spectra::reml {
|
||||
|
||||
// Two-sided Student-t tail probability and upper F tail probability.
|
||||
// Both accept non-integer positive degrees of freedom.
|
||||
[[nodiscard]] double student_t_two_sided_p(double statistic,
|
||||
double degrees_of_freedom);
|
||||
[[nodiscard]] double f_upper_tail(double statistic,
|
||||
double numerator_degrees_of_freedom,
|
||||
double denominator_degrees_of_freedom);
|
||||
|
||||
} // namespace spectra::reml
|
||||
27
include/spectra_reml/fixed_effects.hpp
Normal file
27
include/spectra_reml/fixed_effects.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "spectra_reml/types.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace spectra::reml {
|
||||
|
||||
struct FixedEffectHypothesis {
|
||||
// Rows are restrictions and columns correspond to beta in design order.
|
||||
ColMajorMatrix contrast;
|
||||
// Empty means a zero right-hand side.
|
||||
std::vector<double> rhs;
|
||||
};
|
||||
|
||||
// Computes coefficient-wise tests and any supplied general linear hypotheses
|
||||
// at an already fitted REML solution. Inputs must use the same (possibly GRM-
|
||||
// rotated) coordinate system and phenotype scale as the supplied fit.
|
||||
[[nodiscard]] FixedEffectInferenceResult infer_fixed_effects_spectral(
|
||||
const std::vector<double>& y_star, const ColMajorMatrix& x_star,
|
||||
const std::vector<double>& eigenvalues, const RemlResult& fit,
|
||||
FixedEffectTestMethod method,
|
||||
const std::vector<FixedEffectHypothesis>& hypotheses = {},
|
||||
double rank_tolerance_relative = 1e-10,
|
||||
double covariance_floor_relative = 1e-12);
|
||||
|
||||
} // namespace spectra::reml
|
||||
@@ -64,6 +64,49 @@ enum class FitStatus {
|
||||
|
||||
[[nodiscard]] const char* to_string(FitStatus status) noexcept;
|
||||
|
||||
enum class FixedEffectTestMethod {
|
||||
none,
|
||||
satterthwaite,
|
||||
kenward_roger
|
||||
};
|
||||
|
||||
[[nodiscard]] const char* to_string(FixedEffectTestMethod method) noexcept;
|
||||
|
||||
enum class FixedEffectInferenceStatus {
|
||||
not_requested,
|
||||
ok,
|
||||
boundary_conditional,
|
||||
fit_not_converged,
|
||||
invalid_contrast,
|
||||
information_singular,
|
||||
numerical_error
|
||||
};
|
||||
|
||||
[[nodiscard]] const char* to_string(FixedEffectInferenceStatus status) noexcept;
|
||||
|
||||
struct FixedEffectTestResult {
|
||||
bool valid = false;
|
||||
std::size_t numerator_df = 0;
|
||||
double denominator_df = std::numeric_limits<double>::quiet_NaN();
|
||||
double statistic = std::numeric_limits<double>::quiet_NaN();
|
||||
double p_value = std::numeric_limits<double>::quiet_NaN();
|
||||
double estimate = std::numeric_limits<double>::quiet_NaN();
|
||||
double standard_error = std::numeric_limits<double>::quiet_NaN();
|
||||
std::string error;
|
||||
};
|
||||
|
||||
struct FixedEffectInferenceResult {
|
||||
FixedEffectTestMethod method = FixedEffectTestMethod::none;
|
||||
FixedEffectInferenceStatus status =
|
||||
FixedEffectInferenceStatus::not_requested;
|
||||
// One test per beta, in design-matrix column order. Satterthwaite reports
|
||||
// a signed t statistic; Kenward-Roger reports an F statistic with 1 NumDF.
|
||||
std::vector<FixedEffectTestResult> coefficient_tests;
|
||||
// Optional general linear hypotheses requested by the caller.
|
||||
std::vector<FixedEffectTestResult> hypothesis_tests;
|
||||
std::string error;
|
||||
};
|
||||
|
||||
struct RemlOptions {
|
||||
std::size_t max_iterations = 100;
|
||||
std::size_t line_search_max_evaluations = 48;
|
||||
@@ -95,6 +138,12 @@ struct RemlOptions {
|
||||
// sqrt(OLS residual mean square / 2).
|
||||
double initial_sigma_e = std::numeric_limits<double>::quiet_NaN();
|
||||
double initial_sigma_g = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
// Batch fixed-effect inference. The numerical fit API itself remains
|
||||
// usable without inference; run_task_batch invokes the inference engine at
|
||||
// the final REML estimate and defaults to Satterthwaite t/F tests.
|
||||
FixedEffectTestMethod fixed_effect_test =
|
||||
FixedEffectTestMethod::satterthwaite;
|
||||
};
|
||||
|
||||
struct RemlResult {
|
||||
@@ -113,6 +162,8 @@ struct RemlResult {
|
||||
// Row-wise packed lower triangle:
|
||||
// (0,0), (1,0), (1,1), (2,0), (2,1), (2,2), ...
|
||||
std::vector<double> beta_covariance_packed_lower;
|
||||
FixedEffectInferenceResult fixed_effect_inference;
|
||||
FixedEffectTestResult extra_fixed_effect_joint_test;
|
||||
std::string error;
|
||||
|
||||
[[nodiscard]] bool has_estimates() const noexcept {
|
||||
|
||||
Reference in New Issue
Block a user