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%
2.2 KB · 70 lines swift
Raw Blame History
1//2//  ZScorePlugin.swift3//  Metrika4//5//  Author:  Simon-Pierre Boucher6//  Contact: contact@spboucher.ai7//  Copyright © 2026 Simon-Pierre Boucher. All rights reserved.8//910import ZQData11import ZQParser1213/// Reference native plugin (CLAUDE.md §4): `zscore varname` generates14/// `z_varname`, the standardized version of a numeric variable. Declares15/// `mutates: true` and returns the updated dataset via16/// `ZQResult.replacementFrame` — the pattern SPM-compiled-in user17/// plugins follow.18public struct ZScorePlugin: ZQCommandPlugin {19    public static let verb = "zscore"20    public static let syntax = ZQSyntaxSpec(21        acceptsVarlist: true,22        acceptsCondition: false,23        acceptsRange: false,24        acceptsWeight: false,25        options: [],26        mutates: true27    )2829    public init() {}3031    public func execute(_ ctx: ZQContext) async throws -> ZQResult {32        let names = ctx.command.varlist.flatMap(\.referencedNames)33        guard names.count == 1, let name = names.first else {34            throw ZQDataError("zscore: syntax is 'zscore varname'")35        }36        let (values, missing) = try ctx.frame.requireNumeric(name)3738        var count = 039        var sum = 0.040        for i in 0..<values.count where !missing[i] {41            sum += values[i]42            count += 143        }44        guard count > 1 else { throw ZQDataError("zscore: insufficient observations") }45        let mean = sum / Double(count)46        var squared = 0.047        for i in 0..<values.count where !missing[i] {48            squared += (values[i] - mean) * (values[i] - mean)49        }50        let sd = (squared / Double(count - 1)).squareRoot()51        guard sd > 0 else { throw ZQDataError("zscore: '\(name)' is constant") }5253        var scores = [Double](repeating: .nan, count: values.count)54        for i in 0..<values.count where !missing[i] {55            scores[i] = (values[i] - mean) / sd56        }5758        var frame = ctx.frame59        try frame.addColumn(ZQColumn(60            name: "z_\(name)",61            data: .float64(values: scores, missing: missing)62        ))63        return ZQResult(64            text: "(variable z_\(name) generated: mean \(mean), sd \(sd))",65            scalars: ["mean": mean, "sd": sd],66            replacementFrame: frame67        )68    }69}70