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%
9.5 KB · 193 lines markdown
Rendered Raw Blame History
1<div align="center">23<img src="docs/screenshots/icon.png" width="140" alt="Metrika icon">45# Metrika67**Stata-class statistics, GPU-accelerated by Apple Silicon.**8Native Swift. No Electron. No Python runtime. No compromises.910[![Release](https://img.shields.io/badge/release-v1.0.1-4f8cff)](../../releases/latest)11[![macOS](https://img.shields.io/badge/macOS-14%2B-black?logo=apple)](#requirements)12[![Swift](https://img.shields.io/badge/Swift-6-F05138?logo=swift&logoColor=white)](#building-from-source)13[![Apple Silicon](https://img.shields.io/badge/Apple%20Silicon-arm64-333)](#requirements)14[![GPU](https://img.shields.io/badge/GPU-Metal%20%2B%20MLX-9b6cff)](#the-gpu-invisibly)15[![Tests](https://img.shields.io/badge/tests-116%20passing-34c759)](#numerical-validation)16[![Validated](https://img.shields.io/badge/R--validated-1e--10-34c759)](#numerical-validation)17[![Notarized](https://img.shields.io/badge/DMG-signed%20%26%20notarized-0a84ff)](../../releases/latest)1819<img src="docs/screenshots/console-regression.png" width="820" alt="Cluster-robust regression with factor variables and a 10,000-replicate GPU bootstrap">2021*One session: a cluster-robust factor-variable regression, then a2210,000-replicate pairs bootstrap — **batched on the Apple GPU**, exactly23reproducible from `seed(42)` on any backend.*2425</div>2627---2829## Why Metrika3031- **One line, publication-ready output.** `reg log_rev price i.region, cluster(firm_id)` — the Stata mental model, with factor variables, `if`/`in` qualifiers, robust and cluster-robust inference.32- **The GPU is invisible.** A planner dispatches every command to CPU (LAPACK) or GPU (MLX) automatically. Large bootstrap runs execute as batched Metal solves; you never choose a backend.33- **Reproducibility is a feature, not an accident.** All randomness flows through a counter-based Philox4x32 generator: `set seed 42` produces *bit-identical* resamples on CPU and GPU, in any chunk order, across any parallelism.34- **Numbers you can defend.** Every CPU estimator is validated against R to **1e-10 relative tolerance** — coefficients, standard errors (classical, HC0–HC3, cluster), p-values, marginal effects. Penalized and boosted models cross-validate against glmnet and xgboost.35- **Big data on a laptop.** DuckDB columnar engine with bulk C-API extraction: **10 million rows load in 0.2 s**, summarize in ~0.3 s, regress in ~0.2 s. A Metal point-sprite renderer takes over scatter plots past 100k points and shrugs at 2,000,000.3637## Screenshots3839| | |40|:---:|:---:|41| **Swift Charts with `by()` groups**<br><img src="docs/screenshots/scatter-by.png" width="420"> | **2,000,000 points — Metal renderer**<br><img src="docs/screenshots/metal-2m.png" width="420"> |42| **Virtualized data browser with expression filter**<br><img src="docs/screenshots/data-browser.png" width="420"> | **Built-in manual for all 38 commands**<br><img src="docs/screenshots/manual.png" width="420"> |4344## Install4546Download **[Metrika.dmg](../../releases/latest)** — signed, notarized, and stapled. Drag into Applications. macOS 14+ on Apple Silicon.4748## A five-minute tour4950```stata51. sysuse sales                        // bundled 200-firm × 5-year panel52. gen log_rev = ln(revenue)53. summarize revenue price, detail5455. reg log_rev price i.region, robust  // HC1 SEs, factor expansion56. predict yhat57. margins, dydx(price)                // delta-method standard errors5859. xtset firm_id60. xtreg log_rev price, fe cluster(firm_id)61. ivregress 2sls log_rev (price = z1 z2), robust6263. logit purchase price64. margins, dydx(price)6566. bootstrap, reps(100000) seed(42): reg log_rev price   // GPU batched67. permute,  reps(10000)  seed(42): reg log_rev price    // exact p-values68. bayes, mcmcsize(20000) seed(42): reg log_rev price    // Gibbs sampler6970. lasso log_rev price z1 z2 orders, lambda(0.05)         // glmnet-exact71. boost log_rev z1 z2 orders, rounds(100) maxdepth(3)    // xgboost-exact7273. scatter log_rev price, by(region)74. histogram revenue, bins(20)75. save results.dta, replace           // native Stata .dta 11876```7778Everything above works identically in the console, in `.zyq` do-files79(⌘R in the editor), and headlessly through `metrika-cli`.8081## What's inside8283| Pillar | Contents |84|---|---|85| **Data** | parquet, csv, json, arrow, native Stata **.dta** (read 117–119, write 118) · DuckDB engine · explicit missing-value semantics with listwise-deletion reporting |86| **Estimation** | OLS (QR, never X'X) · logit / probit / poisson · 2SLS · panel fixed effects · summarize / tabulate / correlate |87| **Inference** | robust HC0–HC3 · cluster-robust with Stata small-sample factors · **GPU pairs bootstrap** · permutation tests · Bayesian regression (Gibbs) |88| **Machine learning** | lasso & elastic net (coordinate descent, glmnet-exact) · gradient-boosted trees (xgboost-exact) |89| **Post-estimation** | `predict` (xb, residuals, pr, n) · `margins, dydx()` with delta-method SEs |90| **Graphics** | Swift Charts scatter/line/histogram/kdensity · **Metal renderer** for millions of points |91| **Extensibility** | `.zyq` script commands with `args` macros · native Swift `ZQCommandPlugin`s with syntax validation and mutation gating |9293## Numerical validation9495Metrika's test suite doesn't check that code runs — it checks that the96*numbers are right*:9798- **116 tests** compare against golden values generated by R (`Tests/Fixtures/generate.R`): OLS coefficients, every SE variant, t/F/χ² p-values into the far tails (p = 4×10⁻²² matches R exactly), GLM likelihoods, marginal effects **and their delta-method SEs** — all at 1e-10 relative tolerance.99- Lasso/elastic-net coefficients match **glmnet** (including its subtle gaussian y-standardization convention); the selection pattern — which coefficients are exactly zero — matches exactly.100- Boosted-tree predictions match **xgboost** observation-by-observation.101- The GPU bootstrap's resample indices are asserted **bit-identical** to the CPU Philox reference; Philox itself is pinned to the Random123 known-answer vectors.102- With diffuse priors, the Bayesian posterior reproduces the frequentist answer within Monte-Carlo error — asserted, not assumed.103104## Architecture105106```107┌─────────────────────────────────────────────────────────┐108│                     Metrika (SwiftUI)                    │109│   Console · Data browser · Do-file editor · Manual       │110└──────────────────────────┬──────────────────────────────┘111112┌──────────────────────────▼──────────────────────────────┐113│                   MetrikaKit (Swift package)             │114│                                                          │115│   ZQParser     command grammar → typed AST               │116│   ZQPlanner    AST → CPU / GPU / hybrid dispatch         │117│   ZQEngine     sessions, execution, logging, help        │118│   ZQData       DataFrame façade over DuckDB + .dta       │119│   ZQStats      LAPACK estimators (Accelerate)            │120│   ZQGPU        MLX batched solves, Philox RNG            │121│   ZQGraphics   plot specs → Swift Charts / Metal         │122│   ZQPlugins    user commands & sandboxing                │123└──────────────────────────────────────────────────────────┘124```125126`MetrikaKit` has zero UI dependencies and is fully testable with127`swift test`. `ZQGPU` is the only module allowed to touch MLX/Metal;128`ZQStats` the only one touching Accelerate — backends stay swappable.129130## Building from source131132```bash133git clone https://github.com/spboucher-ai/metrika && cd metrika134./scripts/install_hooks.sh135136# Library + CLI + tests137cd MetrikaKit138swift build && swift test                # 116 tests (GPU suites auto-skip)139xcodebuild test -scheme MetrikaKit-Package \140  -destination 'platform=macOS' -skipPackagePluginValidation   # + GPU suites141142# App143cd .. && xcodegen generate144xcodebuild -project Metrika.xcodeproj -scheme Metrika \145  -skipPackagePluginValidation build146147# Signed, notarized DMG148./scripts/release.sh149```150151> SwiftPM's CLI cannot compile Metal shaders, so `swift test` skips the152> GPU suites and CLI builds fall back to CPU automatically; `xcodebuild`153> runs give you everything.154155## Documentation156157- **In the app**: the Manual tab, or `help <command>` in the console.158- **Static site**: [`docs/index.html`](docs/index.html) — regenerated from the same registry by `metrika-cli docs`, so it can never drift from the app.159160## Extending Metrika161162Drop a `.zyq` script into `~/Library/Application Support/Metrika/Commands/`:163164```stata165// logreg.zyq — its filename becomes the command166args response predictor167gen __log = ln(`response')168reg __log `predictor', robust169drop __log170```171172…or compile a Swift plugin into the app:173174```swift175public struct ZScorePlugin: ZQCommandPlugin {176    public static let verb = "zscore"177    public static let syntax = ZQSyntaxSpec(mutates: true)178    public func execute(_ ctx: ZQContext) async throws -> ZQResult {  }179}180```181182Plugins that shadow built-ins are rejected at startup; datasets can only183be mutated through a declared, gated channel.184185---186187<div align="center">188189**© 2026 Simon-Pierre Boucher. All rights reserved.**190[contact@spboucher.ai](mailto:contact@spboucher.ai)191192</div>193