Language basics
Hayashi uses a Stata-like command grammar with modern expression support. Statements are separated by newlines or semicolons. Results are printed unless suppressed with a trailing semicolon.
// Load data
load "wage1.csv" as df
// Variables and assignment
let x = 10
let y = x + 5
// Conditional
if y > 10 {
print("large")
}
// Loop
for i in 1..5 {
print(i)
}
Data I/O
Hayashi reads and writes several tabular formats. ODBC is available as an optional feature.
| Command | Description |
|---|---|
load "file.csv" as df | Load CSV/TSV |
load "file.json" as df | Load JSON array |
load "file.dta" as df | Load Stata .dta |
load "file.xlsx" as df | Load Excel sheet |
load "file.parquet" as df | Load Parquet |
load "sqlite://path.db" as df | SQL query to DataFrame |
save df, "csv", "out.csv" | Save DataFrame |
Block expressions & output control
A block can be used as an expression: it runs statements and returns the last expression. Variables declared inside are local to the block.
let df = {
let raw = load("data.csv")
generate raw y = log(x)
keep(raw, ["date", "y"])
raw
}
quietly on
let m = merge(df2, df3, key=id, type=inner)
quietly off
print("done")
quietly on suppresses automatic output from statements and estimators. print(...) and display ... still appear. The flag is scope-aware: toggling inside a block reverts when the block ends.
Data & I/O utilities
Common helpers for missing values, file-system checks, and simple caching.
| Command | Description |
|---|---|
dropna(df, col1, col2) | Remove rows with NaN |
ffill(df) | Forward-fill NaN in float columns |
file_exists("path") | Check if path exists |
ensure_dir("cache") | Create directory if missing |
write("text", "file.txt") | Write string to file |
export(df, "csv", "out.csv") | Save DataFrame |
Estimators
All estimators share a common formula syntax and return a model object with post-estimation methods.
| Command | Family |
|---|---|
ols(y ~ x1 + x2, df) | Ordinary least squares |
iv(y ~ x1 + x2, ~ z1 + z2, df) | Instrumental variables / 2SLS |
logit(y ~ x1 + x2, df) | Binary logistic regression |
probit(y ~ x1 + x2, df) | Binary probit regression |
poisson(y ~ x1 + x2, df) | Poisson regression |
nbreg(y ~ x1 + x2, df) | Negative binomial |
tobit(y ~ x1 + x2, df) | Censored regression |
qreg(y ~ x1 + x2, df, tau=0.5) | Quantile regression |
fe(y ~ x1 + x2 | id, df) | Panel fixed effects |
re(y ~ x1 + x2 | id, df) | Panel random effects |
ab(y ~ x1 + x2 | id + year, df) | Arellano-Bond dynamic panel |
heckman(y ~ x1 + x2, s ~ z1 + z2, df) | Heckman two-step |
garch(y ~ 1, df, p=1, q=1) | GARCH volatility |
var(df, lags=2) | Vector autoregression |
vecm(df, lags=2, rank=1) | Vector error correction |
did(y ~ treat + post, df) | Difference-in-differences |
gmm(y ~ x1, df, instruments=z1) | Generalized method of moments |
rolling(y ~ x1 + x2, df, window=30) | Rolling OLS with optional date=date_col |
Post-estimation
After fitting a model, use these verbs for inference, prediction, and diagnostics.
| Command | Description |
|---|---|
test(model, "x1 = 0") | Wald test |
predict(model, df) | Generate predictions |
margins(model, dydx(x1)) | Marginal effects |
bootstrap(model, reps=1000) | Bootstrap inference |
estat(model, "vif") | Diagnostic statistics |
nlcom(model, "x1/x2") | Nonlinear combination |
esttab(model) | Regression table output |
tidy(model) | Convert model to a tidy DataFrame of coefficients |
glance(model) | Convert model to a one-row summary DataFrame |
Modern syntax
Hayashi adds modern conveniences to the Stata-like grammar without breaking the REPL workflow.
// Pipes
let df2 = df |> filter(educ > 12) |> mutate(lwage = log(wage))
// F-strings
print("R² = {model.r2}")
// Match expression
let sign = match coef > 0 { true => "+", false => "-" }
// Functions
fn my_mean(x) {
return sum(x) / len(x)
}
// Import a plugin
import("sheep-farm/hayplot", as=gg)
let p = gg::hayplot(df, {"x": "educ", "y": "lwage"})
|> gg::geom_point("blue", 5.0)
|> gg::save_svg("scatter.svg")
Recipe: rolling CAPM
A complete daily CAPM pipeline with rolling betas and an SVG chart.
import("sheep-farm/hayahoo", as=yahoo)
import("sheep-farm/hayfred", as=fred)
import("sheep-farm/hayplot", as=gg)
fred::set_apikey("sua-chave")
let asset = yahoo::history("AAPL", {"range": "50y", "interval": "1d"})
generate asset asset_ret = log(close / L.close)
asset |> keep(["date", "asset_ret"])
let market = yahoo::history("SPY", {"range": "50y", "interval": "1d"})
generate market market_ret = log(close / L.close)
market |> keep(["date", "market_ret"])
let merged = merge(asset, market, key=date, type=inner)
let rf = fred::fred("TB3MS", {"start": merged["date"][0], "end": merged["date"][len(merged["date"])-1]})
generate rf rf_period = (value / 100.0) / 365
rf |> keep(["date", "rf_period"])
let with_rf = merge(merged, rf, key=date, type=left)
let filled = ffill(with_rf)
generate filled excess_asset = asset_ret - rf_period
generate filled excess_market = market_ret - rf_period
let df = dropna(filled, excess_asset, excess_market)
let model = df |> ols(excess_asset ~ excess_market, _)
print(model)
let roll = rolling(excess_asset ~ excess_market, df, window=252, date=date)
let betas = tidy(roll)
let coefs = betas["excess_market"]
print("mean =", mean(coefs))
print("sd =", sd(coefs))
print("min =", min(coefs))
print("max =", max(coefs))
generate betas idx = _n
let plot = gg::hayplot(betas, {"x": "idx", "y": "excess_market"})
|> gg::geom_line("#1f77b4", 1.5)
|> gg::geom_hline("red", 1.0, mean(coefs))
|> gg::labs("Rolling beta: AAPL vs SPY", "observation", "beta")
|> gg::set_dimensions(1200, 600)
|> gg::save_svg("rolling_beta.svg")
CLI commands
| Command | Description |
|---|---|
hay script.hay | Run a script |
hay | Start REPL |
hay install user/repo | Install plugin |
hay update [user/repo] | Update plugin(s) |
hay remove user/repo | Remove plugin |
hay list | List installed plugins |
hay validate | Run empirical validation suite |
hay dist-update | Update Hayashi binary to latest release |