// // GLMTests.swift // Metrika // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // Copyright © 2026 Simon-Pierre Boucher. All rights reserved. // import Foundation import Testing import ZQData import ZQStats /// GLM estimators vs R glm() fixtures (epsilon 1e-12) at 1e-10 relative /// tolerance. @Suite("ZQGLM vs R fixtures") struct GLMTests { let fixtures: Fixtures let purchase: [Double] let orders: [Double] let price: [Double] let firmID: [Int] init() async throws { self.fixtures = try Fixtures() let store = try ZQDataStore() let frame = try await store.load(contentsOf: fixtures.datasetURL) let (purchaseAll, _) = try frame.requireNumeric("purchase") let (ordersAll, _) = try frame.requireNumeric("orders") let (priceAll, priceMissing) = try frame.requireNumeric("price") let (firmAll, _) = try frame.requireNumeric("firm_id") var purchase: [Double] = [], orders: [Double] = [] var price: [Double] = [], firm: [Int] = [] for i in 0.. chi2" ) } @Test("logit robust and cluster standard errors") func logitRobust() throws { let robust = try ZQGLM.fit( y: purchase, predictors: [("price", price)], family: .logit, variance: .hc0 ) expectClose( robust.coefficients[0].standardError, fixtures["logit_se_hc0_price"], "robust se[price]" ) let clustered = try ZQGLM.fit( y: purchase, predictors: [("price", price)], family: .logit, variance: .cluster(firmID) ) expectClose( clustered.coefficients[0].standardError, fixtures["logit_se_cluster_price"], "cluster se[price]" ) } @Test("probit matches R glm(binomial(probit))") func probit() throws { let result = try ZQGLM.fit( y: purchase, predictors: [("price", price)], family: .probit ) expectClose(result.coefficients[0].estimate, fixtures["probit_b_price"], "b[price]") expectClose(result.coefficients[1].estimate, fixtures["probit_b_cons"], "b[_cons]") expectClose( result.coefficients[0].standardError, fixtures["probit_se_price"], "se[price]" ) expectClose(result.logLikelihood, fixtures["probit_ll"], "log likelihood") } @Test("poisson matches R glm(poisson)") func poisson() throws { let result = try ZQGLM.fit( y: orders, predictors: [("price", price)], family: .poisson ) expectClose(result.coefficients[0].estimate, fixtures["pois_b_price"], "b[price]") expectClose(result.coefficients[1].estimate, fixtures["pois_b_cons"], "b[_cons]") expectClose( result.coefficients[0].standardError, fixtures["pois_se_price"], "se[price]" ) expectClose(result.logLikelihood, fixtures["pois_ll"], "log likelihood") let robust = try ZQGLM.fit( y: orders, predictors: [("price", price)], family: .poisson, variance: .hc0 ) expectClose( robust.coefficients[0].standardError, fixtures["pois_se_hc0_price"], "robust se[price]" ) } @Test("logit rejects non-binary outcomes") func binaryValidation() { #expect(throws: ZQStatsError.self) { _ = try ZQGLM.fit( y: orders, predictors: [("price", price)], family: .logit ) } } @Test("correlate matches R cor()") func correlate() async throws { let store = try ZQDataStore() let frame = try await store.load(contentsOf: fixtures.datasetURL) let (revenueAll, _) = try frame.requireNumeric("revenue") let (priceAll, priceMissing) = try frame.requireNumeric("price") var revenue: [Double] = [], priceComplete: [Double] = [] for i in 0..