Author: zhao_xun@sjtu.edu.cn · License: MIT
Dedicated to Haoping Yu, the one I love most.
A standalone specification curve analysis tool for econometric robustness checks. It enumerates all valid combinations of control variables, estimates OLS with N-way absorbed fixed effects, computes heteroskedasticity-robust, one-way clustered, or CGM two-way clustered standard errors, and exports publication-ready figures and a significance summary table.
The workflow and output design draw on Stata's spec_curve command and extend it with batch configuration, parallel execution, and two export formats.
PNG mode — static publication-quality figure exported as a raster image.
HTML mode — a self-contained interactive webpage with two view modes. It opens in compact mode by default only when there are more than 1024 specifications; smaller charts disable compact and open in detail. Switch Y, X, and fixed-effect spec from a top selector bar when multiple combinations are present, and sort specifications by coefficient, observation count, or signed significance.
detail is the full interactive view. Hover highlights a specification across all panels, click pins the current specification, and the right details panel lists included controls in input order with per-control statistics. COEF confidence bands are drawn as per-specification slices for a finer PNG-like edge texture; STARS uses COEF-style round points stacked from bottom to top.
When switching Y, X, or fixed-effect spec in the top selector bar, the other selectors keep their current value when that value is still available; fixed-effect specs are preserved by matching the displayed spec label across Y/X combinations. In-chart SORT, MODE, and Controls color selections also persist across top-selector switches when the target chart supports the selected mode, without flashing the default toolbar state during iframe reload.
compact is the dense overview view for large specification sets. It hides the right details panel, disables central-chart hover/click selection, caps the column width at the 8192-specification baseline, and allows horizontal scrolling when the compact plot is wider than the viewport. After rendering, the compact chart is cached as one bitmap and scrolling redraws only the visible slice; sorting, filter chips, guide chips, CI chips, and viewport resizing rebuild that cache. STARS uses segmented CONTROL-like color bars whose heights encode significance level, CONTROL and OBS use thin square bars, and compact COEF points scale to the compact baseline column width.
Both PNG and HTML title metadata show the analysis engine (stata or r) used for the exported figure.
For external engines, elapsed-time metadata includes the engine handoff/setup step that belongs to the run, the model-estimation time, and the export rendering time reported in progress output.
Interactive HTML uses the persisted coefficient-axis scale when re-sorting specifications, so CI bands keep the same visual length implied by the saved ci90/ci95/ci99 values.
The HTML significance legend uses the same colors as the coefficient points; n.s. is black.
HTML TEST GUIDES include all, no, and best. In DETAIL, guide lines cannot be hidden; clicking a guide chip jumps to the next matching specification and cycles when multiple matches exist. In COMPACT, guide chips keep the old show/hide behavior. best is a yellow helper line recomputed for the current filtered view: when the currently visible 3-star significant specifications contain no full-test specification, it marks the visible 3-star specification with the largest number of included controls_test variables, breaking ties by smaller p-value and then original order. The HTML caches these dynamic best lookups in page memory by filter state; the cache is not persisted and is cleared when the page closes.
In DETAIL, the right-side control-coefficient badge text is white for significant controls except +1 and -1, which use black text. Significant badge backgrounds use the same 1/2/3 red-blue color levels as STARS. 0+ and 0- use a light gray background with deep red/deep blue text.
The right-side detail panel always shows content in DETAIL mode, even before any specification is hovered or pinned. Its width is computed from the configured control-variable names and updates with the viewport so variable names display fully without hover tooltips. It always shows all controls_test variables in the original input order. Variables not included in the current specification keep their name and a blank badge box, while coefficient and p-value cells stay empty.
The right-side detail panel header includes a COPY button between the specification index and significance badge. It copies the current specification's included control variable names, space-separated, in the same order as the sidebar list.
In the right-side detail panel, clicking a control-variable row cycles its filter through significant included controls, non-significant included controls, not-controlled specifications, and off. Multiple selected rows are combined with AND semantics, so the main chart removes non-matching specifications and re-packs the remaining columns so the kept specifications are contiguous. Filter state is shown on the control rows themselves, with a small legend above the control coefficients for sig, no-sig, and no-control; CLEAR uses the same button style as COPY in the panel header.
The HTML Controls color switch defaults to gray, continuous gray run-length shading for the control matrix. gray draws only the regular varying matrix controls and does not reserve blank rows for controls_must. Switching to sig colors each included control cell by that control's own coefficient significance, using the same red/blue signed levels as STARS and the right sidebar, with a darker neutral gray for insignificant or missing stats. In sig, all controls_must variables are also drawn in the control matrix, ordered before the regular varying matrix controls, and their row labels show a small leading dot to distinguish them from test controls.
Dependencies are declared in pyproject.toml and managed by uv.
uv syncCore dependencies: numpy, pandas, matplotlib, pyreadstat.
The recommended workflow is config-file driven. Copy config/config.example.toml, fill in your variable names and file path, then run:
uv run regression-monkeyOr point to the config explicitly:
uv run regression-monkey config/config.tomlCLI flags override any TOML value:
uv run regression-monkey config/config.toml --dpi 600 --n-jobs 0All regression commands accept the optional --keepsingletons modifier after
their ordinary arguments. Without it, fixed-effect singletons are recursively
removed as in Stata reghdfe by default. With it, singleton observations are
retained, matching Stata reghdfe, keepsingletons.
Interactive specification searches require the same standardized field input
style as --baseline and --bdiff. --drop [MAX] tests removals from the
variables listed in controls. Multiple variables listed in x are jointly
included in every regression; the first x is the tested coefficient used for
ranking and reporting, while the remaining x variables stay in every model.
--replace runs the baseline and one regression for each candidate replacing
the current control. Repeated replacement candidates are reported and
automatically deduplicated.
regmonkey --spec --drop
regmonkey --spec --replace
regmonkey --spec --drop --templatepath : "panel.dta"
y : outcome
x : treatment1 treatment2
controls: size leverage current
absorb : firm year
cluster : firm
replace : current alternative1 alternative2
The final replace: field is required only for --replace. Stata use and
global input is rejected; use -t or --template to print the standardized
input form before the paste prompt.
Standardized input may include extra fields unused by the current command; the
parser ignores those fields and their continuation lines so one input can be
reused across --baseline, --bdiff, --spec, and --sum.
Use regmonkey --baseline --kjyj to estimate and directly export a KJYJ
baseline-regression table. Paste the six standardized fields and finish with a
blank line. By default, only the model containing all controls is estimated
and exported as column (1), without an intermediate 有控制 header. Add
--noncontrols to use the two-column layout: column (1) without controls and
column (2) with controls. Every variable under x is jointly included in each requested model.
The default table has exactly two equal-width cells per row: the variable cell
and the model cell. The first column is centered in every baseline layout,
matching the existing centered first column in bdiff tables.
Both models use the same absorbed fixed effects and clustering settings. Add
--short to keep all controls in estimation while hiding their individual RTF
rows and adding a Controls summary row. The command defaults to 8 workers and writes
~/Downloads/<timestamp>_<data-stem>_baseline.rtf.
regmonkey --baseline --kjyj
regmonkey --baseline --kjyj --noncontrols
regmonkey --baseline --kjyj --short
regmonkey --baseline --kjyj --templatepath : "/path/to/panel.dta"
y : AnalystError
x : Charts Diagrams Pictures
controls: Lev Size ROA SOE Big4 Growth Loss Top1 Mhold Dual Focus BussComplexity TextReadability TextTone TextWord AnalystHorizon AnalystPortfolio AnalystFirmExp BrokerSize AnalystIndExp AnalystIndSpec
absorb : code analyst#year
cluster : code analyst
The default RTF shows only (1) and omits the intermediate 有控制 header;
--noncontrols restores the 无控制 / 有控制 headings and (1)/(2) numbering.
Its default title is the left-aligned,
regular-weight 表3 基准回归结果; the digit 3 uses Times New Roman while the
Chinese title text uses SimHei, with exactly 26 ASCII spaces between 表3 and
基准回归结果. It reports coefficients and t statistics in full-width Chinese
parentheses for every x; unless --short is used, column (2) also lists
every control coefficient and t statistic while the matching column (1)
cells remain blank. Each requested model reports Constant after the controls. Fixed
effects use bdiff-style English labels such as Firm FE and
Analyst×Year FE. Constant and fixed-effect names are upright; N and the
R in R² are italic, while the Adj. prefix and superscript ² remain
upright. The note renders
its t symbol in italic. The table reports adjusted R-squared but
uses title-case Yes fixed-effect indicators. The Controls row appears only
with --short; no RTF mode reports an F statistic.
These KJYJ conventions apply only to the RTF. Terminal output stays in a fixed
bdiff-style form: by default it prints every x under Controls; with
--noncontrols, it first prints every x under No controls, inserts a blank
line, and then prints the controlled results. N, adjusted
R-squared, and F appear once in each group heading; each x row
starts with a [level+direction] code, followed by the variable name,
coefficient, p-value, and # marks. Level 3/2/1 badges use red/green/blue;
level 0 badges and variable names keep the default terminal color. The b
field uses red for positive coefficients and blue for negative coefficients;
p-values keep the default terminal color.
All KJYJ A4 exports use exact 2.54 cm top/bottom margins and 3.175 cm
left/right margins. KJYJ --baseline, --bdiff, --sum, and --def tables
all use a total table width of 14.9 cm; each exporter distributes its columns
within that fixed width.
The KJYJ --sum title has its own fixed placement rule: the paragraph remains
left-aligned with the existing 420-twip first-line indent, and exactly 23 ASCII
spaces separate 表2 from 主要变量描述性统计. It must not be replaced by RTF
automatic centering.
Use --ai [DEF_RTF] with any of those four commands, or with
regression-monkey-tables, to place one schema-validated Chinese explanation
before the table title. Bare --ai preserves the original behavior. If the
optional path points to an RTF generated by regmonkey --def, regmonkey
reverse-reads its variable categories, names, symbols, and definitions and adds
that structured context to the system prompt for matching variables. For
example:
regmonkey --baseline --ai ~/Downloads/20260810_variable_definitions.rtf
regmonkey --bdiff 1000 --ai ~/Downloads/20260810_variable_definitions.rtfThe exporter sends the exact structured table result as
JSON to the local /Users/zhaoxun/scripts/test/2batch checkout. 2batch uses
deepseek-v4-pro with temperature 1 through the configured stream API,
validates the JSON response, and retries an
invalid response up to twice with structured validation errors. Temporary
Parquet, prompt, schema, TOML, response, and cost files are removed after the
call, including on failure; only the final RTF remains. The paragraph uses
10.5-pt SimSun, exact 12-pt line spacing, and a two-character first-line indent;
Latin letters and numbers use Times New Roman, and literal variable names are
italic. --bdiff --export false --ai makes no LLM call because no table is
written.
To inspect whole-sample missing rates, run regmonkey --clean, paste Stata
use, global y, global x, and global controls lines, then press one blank
line. The report preserves every observation and shows each variable's overall
and by-year missing percentage. A separate complete-sample section reports the
annual share for which all pasted Y, X, and control variables are non-missing.
When global option_reghdfe is present, variables from absorb(...) and
vce(cluster ...) get their own missing-rate sections and are also required by
the complete-sample definition. year is the default time variable; override
it with --time fiscal_year or a pasted global time fiscal_year line.
regmonkey --cleanFor an interactive bdiff run, the repetition count defaults to 1,000 and can be
overridden positionally. Paste the standardized field settings, then press one
blank line after the final field. Stata commands
such as use, global, and local are not accepted. Use -t or --template
to print the input template before the prompt:
regmonkey --bdiff
regmonkey --bdiff --template
# Match Stata bdiff, bsample: bootstrap the pool, then randomly split it
regmonkey --bdiff 1000 --bootstrap
# Retry seed-42 results with p in [0.05, 0.10] using seeds 0 through 9
regmonkey --bdiff 1000 --seeds
# Retry every finite seed-42 result with p >= 0.05
regmonkey --bdiff 1000 --seeds force
# Merge all otherwise selected group tables horizontally into one RTF
regmonkey --bdiff 1000 --merge --export all
# Put all selected tables in one RTF, starting each subsequent table on a new page
regmonkey --bdiff 1000 --join --export all
# First merge each pipe array horizontally, then join those tables by page
regmonkey --bdiff 1000 --merge --join --export all
# Hide individual controls in RTF tables while retaining Constant
regmonkey --bdiff 1000 --shortpath : ""
y :
x :
controls:
absorb : code analyst#year
cluster : code
groups :
byvars : ind#year year all
ties : low high average(code) average(seed)
All nine fields must be present, but controls is the only field that may be
empty. y accepts exactly one variable, while x accepts one or more
space-separated variables. Every x enters each group regression, but the
coefficient-difference test and permutation test always target the first x.
The other non-empty fields accept space-separated values. In absorb, join interacted fixed effects with
#, such as analyst#year; cluster lists clustering variables. The program
constructs the internal estimation clause from these two fields.
groups accepts one or more space-separated grouping variables. With
--merge, | separates output arrays: for example,
groups: size attention | portfolio experience produces two merged tables,
one for each side of the pipe. A variable that is already binary 0/1 is used
directly and ignores the median settings;
otherwise, its group is formed by the median. Each space-separated
byvars item is one grouping method. # joins keys, so
ind#year means joint ind + year grouping, while all means the full sample.
Terminal fixed effects, derived-group headings, and seed notices preserve the
input # syntax instead of rewriting it as _;
ties accepts one or more methods: low assigns median ties to Low; high
assigns median ties to High;
average(code) stably assigns them in ascending code order; and
average(seed) randomly assigns them using --seed. Both average methods keep
Low and High within each median scope equal in size or different by at most one.
Legacy average remains an alias for average(code). With --seeds, an
average(seed) grouping is rebuilt for every retry seed 0–9 before that seed's
selected resampling test. The run executes the
Cartesian product of the requested byvars and median methods. For example,
byvars: ind#year year ind all with ties: average(code) low produces
4 × 2 = 8 results with distinct derived variables and filenames. Missing
grouping values or grouping keys are retained in the loaded data but excluded
only from the corresponding bdiff. The terminal reports actual regression
observations as a percentage of all loaded rows.
Each grouping variable selected by the current --export mode normally
produces one file named ~/Downloads/<timestamp>_<group-variable>.rtf. Each
file is a complete two-column table whose four header rows show y, the italic
grouping variable, Low/High, and model numbers in that order. The first row
merges every model column and shows y once; its bottom rule spans the complete
result area but omits the variable-label column. VARIABLES sits in the first
cell of the model-number row. Only header rows two and three use borderless
minimum-width spacer columns: row two separates adjacent grouping pairs, while
row three separates Low from High and also separates adjacent pairs. The top
rule, model-number bottom rule,
N top rule, and adjusted-R-squared bottom rule span the entire table; the two
middle header rules omit the variable-label and spacer columns. One merged cell after the tested-X t statistic reports
Diff. = plus the coefficient difference and its significance stars on the
first line, then italic p followed by -value = and the empirical value on a
second line without a semicolon. With
--merge, the tables within each | array are joined horizontally in input
order. One array writes <timestamp>_merged.rtf; multiple arrays write
<timestamp>_merged_1.rtf, _merged_2.rtf, and so on. Two grouping variables
inside one array therefore produce four consecutively numbered model columns
(1)–(4), with a separate difference cell spanning each ordinary Low/High
model pair. Every bdiff table uses the shared 14.9 cm total width, gives its
centered variable-name column the minimum dynamically calculated width that
keeps the longest displayed variable or row label on one line, then divides the remaining
width equally across all model columns. With --join, all otherwise selected tables are written
to one <timestamp>_joined.rtf; the first table starts on page 1 and every
subsequent table starts after a page break. Without --merge, each grouping
variable is one page. With both options, each | array is first merged into one
horizontal table and those tables are then joined by page.
The default --export significant merges only groups whose reported p-value is
below 0.10; use --export all to include every successful group in the merged
table. RTF export is enabled only when byvars contains exactly one method and
ties contains exactly one method; otherwise all export options are ignored
and the regressions plus terminal report still run. Use --output DIR to
override the output root.
All bdiff RTF tables report Constant using the same effective-sample and
clustered-standard-error definition as the KJYJ baseline table. With --short,
the exported RTF omits only the individual variables listed under controls:,
keeps every variable listed under x, the first-X difference row, and
Constant, and adds a Controls row
immediately above the fixed-effect rows. Its model cells show Yes when the
regression includes controls (No when the input controls: field is empty).
The first-column labels Constant, Controls, and fixed-effect names are
upright; their Yes / No model cells are also upright. N and the R in
R² are italic, while the Adj. prefix and superscript ² remain upright.
By default, bdiff uses Fisher's permutation test: it keeps the pooled sample
fixed and randomly reassigns groups without replacement. --bootstrap matches
Stata bdiff, bsample: each repetition first resamples the pooled observations
with replacement and then randomly splits that bootstrap sample. The default
RTF note starts with 组间系数差异采用Fisher置换检验,随机置换1000次。; in
bootstrap mode it instead states the Bootstrap repetition count. Counts are
shown without thousands separators, then the note reports italic t statistics
and clustering, significance levels, and the grouping rule last. Joint Chinese
grouping scopes use an ASCII
hyphen (for example, 行业-年度). Grouping-variable names in the note are
italic. Visible spaces between Chinese text and adjacent English names or
numbers are removed; spaces inside English phrases are retained.
A merged table states a shared byvars × ties rule once instead of repeating
it for every Low/High model pair.
With --seeds, the permutation test starts with seed 42. If a group's
empirical p-value is in the inclusive 0.05–0.10 interval, only that group is
rerun with seeds 0 through 9. --seeds force removes the upper boundary and
reruns every group with a finite seed-42 p-value greater than or equal to 0.05.
When at least one group will be retried, the terminal first prints the complete
seed-42 result for every group, including groups that do not trigger retries;
only then does the seed 0–9 stage begin.
If any retry reaches a higher significance tier than the seed-42 result, the
complete result with the smallest qualifying retry p-value is used. Thus a
one-star retry replaces a nonsignificant seed-42 result, while a one-star
seed-42 result still requires a two- or three-star retry. If no retry improves
the significance tier, the original seed-42 result is retained. Without --seeds,
only the single seed selected by --seed is run. Selecting a non-42 initial
seed also disables both retry modes. Before retrying, the terminal lists
each triggering group and its original seed-42 p-value. Afterward it reports
the count and share of seed 0–9 retries with p < 0.05, the mean p-value, sample
standard deviation, minimum, maximum, and selected seed for each group. Retry timing is averaged
over the 10 seeds and displayed as average : ...s/seed.
Export format is controlled by --export-format:
# PNG only (default)
uv run regression-monkey config/config.toml --export-format png
# Interactive HTML only
uv run regression-monkey config/config.toml --export-format html
# Both
uv run regression-monkey config/config.toml --export-format bothdata = "path/to/data.dta"
y = ["MPATT"]
x = ["ln_info", "ln_quant", "ln_qual"]
controls_test = ["SOE", "Big4", ["ListAge1", "FirmAge1"]]
controls_must = ["Lev", "Size", ["ROA", "ROE"]]
moderators = [] # R only; each z must already be a control variable
mediators = [] # R only; each m must be an existing numeric column and must not be a control variable
output = "outputs"
export_format = "png" # png | html | both
dpi = 300
fig_width = 14
n_jobs = 0 # 0 = auto-parallel (up to 9 cores)
engine = "r" # r | stata
Firm_FE = "code"
Ind_FE = "ind"
Time_FE = "year"
Region_FE = "pref"
no_absorb_vce_robust = false
no_absorb_vce_cluster_firm = false
absorb_firm_time_vce_cluster_firm = true
absorb_firm_indtime_vce_cluster_firm = true
absorb_ind_time_vce_cluster_firm = trueA complete template is at config/config.example.toml.
The refactored package exposes active modules at package root, such as from regression_monkey import shared, stata, html.
Supported formats: .dta, .csv, .parquet, .pq.
Multiple y and x values are accepted; all y × x combinations are processed in one run.
Both controls_test and controls_must support a mixed structure in TOML or the Python API:
| Syntax | Meaning |
|---|---|
"var" |
single variable |
"var1 var2" |
two flat variables, same as listing them separately |
["A", "B"] |
mutually exclusive alternative group |
Semantics differ:
controls_test— each plain entry is optional (include or not). An alternative group means at most one of its members is included. Adds a factor of(group_size + 1)to the total spec count.controls_must— each plain entry is always included. An alternative group means exactly one of its members is included. Adds a factor ofgroup_size.
A variable that appears in both lists causes an immediate error.
The auto mode selects from a catalog of predefined no-FE / FE + VCE combinations. Terminal output shows the Stata-style noabsorb vce(...) or absorb(...) vce(...) form followed by an explicit standard-error label such as Std.Err.=Robust or Std.Err.=cluster(firm); internal keys use underscores.
| TOML key | Stata equivalent |
|---|---|
no_absorb_vce_cluster_firm |
noabsorb vce(cluster firm) |
no_absorb_vce_robust |
noabsorb vce(robust) |
absorb_firm_time_vce_cluster_firm |
absorb(firm year) vce(cluster firm) |
absorb_firm_indtime_vce_cluster_firm |
absorb(firm i.ind#i.year) vce(cluster firm) |
absorb_firm_regiontime_vce_cluster_firm |
absorb(firm i.region#i.year) vce(cluster firm) |
absorb_firm_indtime_regiontime_vce_cluster_firm |
absorb(firm i.ind#i.year i.region#i.year) vce(cluster firm) |
absorb_firm_time_vce_cluster_region |
absorb(firm year) vce(cluster region) |
absorb_firm_time_vce_cluster_ind |
absorb(firm year) vce(cluster ind) |
absorb_firm_time_vce_cluster_firm_time |
absorb(firm year) vce(cluster firm year) |
absorb_ind_region_time_vce_cluster_ind |
absorb(ind region year) vce(cluster ind) |
absorb_ind_time_vce_cluster_firm |
absorb(ind year) vce(cluster firm) |
absorb_firm_time_vce_robust |
absorb(firm year) vce(robust) |
| (and robust variants of the above) |
The user-facing estimation engines are R/fixest and Stata/reghdfe. The old Python estimation engine and manual --fe mode are no longer supported by the main CLI.
Each run creates a timestamped subdirectory:
outputs/20260414_174122/
config_snapshot.toml
sig.parquet
ab_firm_time_cl_firm/
MPATT_ln_info_ab_firm_time_cl_firm.png
ab_firm_indtime_cl_firm/
MPATT_ln_info_ab_firm_indtime_cl_firm.png
interactive.html # only in html / both modesig.parquet lists all significant specifications across the entire run, sorted by p_value ascending. Fields: Star, coef, p_value, t_value, obs, Y, X, Controls, FE, cluster, Specs, and grouping columns when applicable.
Star values: +3/+2/+1 for positive coefficients significant at 99%/95%/90%; -3/-2/-1 for negative.
Switch to reghdfe by setting engine = "stata" in TOML or passing --engine stata:
uv run regression-monkey config/config.toml --engine stataThe Stata engine also supports subgroup heterogeneity analysis via grouping_variable_by_ind_time, grouping_variable_by_time, or grouping_variable_by_none. For each grouping variable, one extended figure is produced showing the main coefficient curve, b_z=0/1 subgroup curves, and the c.x#c.z interaction coefficient — all in the same PNG. The legacy key grouping_variable remains as an alias for grouping_variable_by_ind_time.
Switch to the R/fixest implementation by setting engine = "r" in TOML or passing --engine r:
uv run regression-monkey config/config.toml --engine rThe R engine lives in src/regression_monkey/engine/r.py. It writes a narrow handoff dataset containing only the variables needed by the enabled catalog specs, generates one temporary .R script per Y × X × spec, runs it with Rscript, then reads the standard *_results.parquet / *_plot_meta.json contract used by the PNG and HTML exporters.
R mode accepts moderators = ["z1", "z2"] and mediators = ["m1", "m2"] in TOML, or --moderators z1 z2 --mediators m1 m2 on the CLI. Each moderator z must already appear in controls_must or controls_test; otherwise the run fails before estimation. Each mediator m must be an existing numeric column in the data and must not appear in controls_must or controls_test; any overlap raises an error before estimation. The main COEF curve remains the original regression without moderator or mediator terms. For specifications that include moderator z, R additionally estimates an augmented regression with z and x*z, stores the three statistics x*z, z, and x in the standard *_results.parquet, and HTML renders them below COEF in a MODERATORS panel. Significant moderator rows are exported per variable as sig_moderator_<z>.parquet. For every specification, R also estimates one reference regression per mediator m that replaces y with m. The x coefficient statistics are stored in *_results.parquet, HTML renders them in a MEDIATORS panel below MODERATORS, and significant rows are exported as sig_mediator_<m>.parquet. These checks do not change the number of enumerated control specifications.
R mode is accuracy-first. Each specification keeps the same effective sample it would have under per-spec fixest::feols; specifications with identical effective samples share a cached fixest::demean() call, and the absorbed matrix is then reused with lm.fit(). Robust and one-way clustered SE are computed on that absorbed matrix with fixest-matched absorbed-FE degree-of-freedom accounting. Multi-way clustered specs fall back to exact per-spec feols because fixest may apply non-positive-definite VCOV repairs that should not be approximated. Large-sample cache keys are stored as ordinary string values, not R environment variable names, so runs with tens of thousands of rows do not hit R's variable-name length limit.
The effective-sample cache optimization is documented in assets/R引擎样本缓存优化.md.
R mode requires a working Rscript and the R package fixest.
# macOS with Homebrew, if R is not installed yet
brew install r
# Install the required R package and optional speed-up packages
Rscript -e 'install.packages(c("fixest", "data.table", "arrow"), repos = "https://cloud.r-project.org")'
# Verify that Regression Monkey can find Rscript and fixest
Rscript -e 'cat(R.version.string, "\n"); stopifnot(requireNamespace("fixest", quietly = TRUE))'arrow is optional but recommended: when available, the Python side writes the R handoff data as Feather; otherwise it falls back to CSV. data.table is also optional and speeds up CSV reads when Feather is unavailable.
If Rscript is not on PATH, point Regression Monkey at the exact executable:
uv run regression-monkey config/config.toml --engine r --rscript-path /path/to/Rscriptor set it in TOML:
engine = "r"
rscript_path = "/path/to/Rscript"R mode currently supports catalog auto specs only, so enable at least one spec flag such as no_absorb_vce_robust = true or absorb_firm_time_vce_cluster_firm = true. Subgroup grouping_variable_* plots remain Stata-only. moderators and mediators are R-only. n_jobs = 0 uses 8 R worker processes by default; --n-jobs N or n_jobs = N sets the worker count explicitly.
Plot from an existing results file without re-running regressions:
# PNG
uv run regression-monkey-plot \
--results outputs/<timestamp>/foo_results.parquet \
--meta outputs/<timestamp>/foo_plot_meta.json \
--output outputs/<timestamp>/foo_replot.png
# HTML
uv run regression-monkey-html \
--results outputs/<timestamp>/foo_results.parquet \
--meta outputs/<timestamp>/foo_plot_meta.json \
--output outputs/<timestamp>/foo_interactive.htmlPass --keep-temp to the main entry point to retain *_results.parquet and *_plot_meta.json after a run.
Total spec count = product of per-slot factors:
| Slot type | Factor |
|---|---|
Plain controls_test variable |
×2 |
controls_test alternative group of size g |
×(g+1) |
Plain controls_must variable |
×1 |
controls_must alternative group of size g |
×g |
12 plain controls_test variables → 4 096 regressions per spec.
R mode reuses cached demeaned samples for robust and one-way clustered specs, and falls back to exact per-spec fixest::feols for multi-way clustering. Stata mode delegates estimation to reghdfe; plotting remains handled by the shared PNG/HTML exporters.

