SPB Git

spb/metrika Public

Stata-class statistics, GPU-accelerated by Apple Silicon. Native Swift — no Electron, no Python runtime, no compromises.

Swift 92.4% HTML 3.3% R 3% Shell 1.3%
4.7 KB · 122 lines swift
Raw Blame History
1//2//  PanelIVTests.swift3//  Metrika4//5//  Author:  Simon-Pierre Boucher6//  Contact: contact@spboucher.ai7//  Copyright © 2026 Simon-Pierre Boucher. All rights reserved.8//910import Foundation11import Testing12import ZQEngine13import ZQParser1415/// xtreg FE and ivregress 2SLS against R fixtures, end-to-end through the16/// session (which exercises the parser's IV-group grammar too).17@Suite("Panel FE and IV", .serialized)18struct PanelIVTests {19    let fixtures: Fixtures20    let session: ZQSession2122    init() async throws {23        self.fixtures = try Fixtures()24        self.session = try ZQSession(discoverUserCommands: false)25        _ = try await session.execute("use \(fixtures.datasetURL.path)")26        _ = try await session.execute("gen log_rev = ln(revenue)")27        _ = try await session.execute("xtset firm_id")28    }2930    @Test("ivregress parser grammar")31    func parserIVGroup() throws {32        let parser = ZQCommandParser()33        let command = try #require(34            try parser.parse("ivregress 2sls y x1 (p q = z1 z2 z3), robust")35        )36        #expect(command.verb == "ivregress")37        #expect(command.subverb == "2sls")38        #expect(command.varlist == [.simple("y"), .simple("x1")])39        #expect(command.ivSpec == ZQIVSpec(40            endogenous: ["p", "q"], instruments: ["z1", "z2", "z3"]41        ))42        #expect(command.hasOption("robust"))43    }4445    @Test("xtreg fe matches the R within estimator")46    func fixedEffects() async throws {47        let result = try await session.execute("xtreg log_rev price, fe")48        #expect(result.scalars["N"] == fixtures["fe_N"])49        #expect(result.scalars["N_g"] == fixtures["fe_G"])50        expectClose(try #require(result.scalars["b_price"]), fixtures["fe_b_price"], "b[price]")51        expectClose(try #require(result.scalars["b__cons"]), fixtures["fe_b_cons"], "b[_cons]")52        expectClose(53            try #require(result.scalars["se_price"]),54            fixtures["fe_se_price"], "se[price]"55        )56        expectClose(try #require(result.scalars["r2_w"]), fixtures["fe_r2_within"], "within R²")57        expectClose(try #require(result.scalars["rmse"]), fixtures["fe_rmse"], "root MSE")58        #expect(result.text.contains("Fixed-effects (within) regression"))59    }6061    @Test("xtreg fe with panel-clustered SE matches R")62    func fixedEffectsClustered() async throws {63        let result = try await session.execute("xtreg log_rev price, fe cluster(firm_id)")64        expectClose(65            try #require(result.scalars["se_price"]),66            fixtures["fe_se_cluster_price"], "cluster se[price]"67        )68        #expect(result.scalars["df_r"] == fixtures["fe_G"] - 1)69    }7071    @Test("xtreg without xtset or fe errors clearly")72    func xtregValidation() async throws {73        let fresh = try ZQSession(discoverUserCommands: false)74        _ = try await fresh.execute("use \(fixtures.datasetURL.path)")75        await #expect(throws: ZQEngineError.self) {76            _ = try await fresh.execute("xtreg revenue price, fe")   // no xtset77        }78        await #expect(throws: ZQEngineError.self) {79            _ = try await session.execute("xtreg log_rev price")     // no fe80        }81    }8283    @Test("ivregress 2sls matches the R manual 2SLS")84    func twoStageLeastSquares() async throws {85        let result = try await session.execute(86            "ivregress 2sls log_rev (price = z1 z2)"87        )88        #expect(result.scalars["N"] == fixtures["iv_N"])89        expectClose(try #require(result.scalars["b_price"]), fixtures["iv_b_price"], "b[price]")90        expectClose(try #require(result.scalars["b__cons"]), fixtures["iv_b_cons"], "b[_cons]")91        expectClose(92            try #require(result.scalars["se_price"]), fixtures["iv_se_price"], "se[price]"93        )94        expectClose(95            try #require(result.scalars["se__cons"]), fixtures["iv_se_cons"], "se[_cons]"96        )97        expectClose(try #require(result.scalars["r2"]), fixtures["iv_r2"], "R²")98        expectClose(try #require(result.scalars["rmse"]), fixtures["iv_rmse"], "root MSE")99        #expect(result.text.contains("Instrumented: price"))100    }101102    @Test("ivregress robust SE matches R HC1-small")103    func ivRobust() async throws {104        let result = try await session.execute(105            "ivregress 2sls log_rev (price = z1 z2), robust"106        )107        expectClose(108            try #require(result.scalars["se_price"]),109            fixtures["iv_se_hc1_price"], "robust se[price]"110        )111    }112113    @Test("ivregress order condition is enforced")114    func orderCondition() async throws {115        await #expect(throws: (any Error).self) {116            _ = try await session.execute(117                "ivregress 2sls log_rev (price orders = z1)"118            )119        }120    }121}122