First stable release. Since 0.5.0 the package has grown from a curve-plotting tool into a full specification-curve workflow -- estimate the curve, compare standard errors, test it, decompose it, and report it -- and the public API is now considered stable. The deprecated camelCase function and argument names introduced this cycle continue to work (with a warning).
sca_test(): a permutation-based joint-inference test for the whole
specification curve, following Simonsohn, Simmons, and Nelson (2020). It
tests the sharp null that the focal variable has no effect in any
specification by repeatedly permuting that variable (blocked within fixed
effects when present) and re-estimating the entire curve, then comparing the
observed curve to the resulting null distribution via three statistics: the
median estimate, the share of specifications significant in the predicted
direction, and a Stouffer combination of the per-specification p-values.
Supports the same model interface as sca() (formula or arguments, glm
families, fixed effects, weights), one- and two-sided tests, parallelisation,
and reproducible seeding. plot_sca_test() visualises each statistic's null
distribution against the observed value, and plot_sca_test_specs() (with
sca_test(keep_curves = TRUE)) draws the specification curve against each
specification's own permutation null band.sca_test() gains a null_type argument with two design-preserving nulls
alongside the default "shuffle_x". "freedman_lane" (Freedman & Lane 1983)
permutes the residuals of a reduced model y ~ controls (the focal variable
omitted) and refits the curve, preserving the focal variable's correlation
with the controls -- so it stays calibrated under collinearity, where
shuffling the focal variable is anti-conservative. "residual_bootstrap"
imposes the null on the response (y - betahat * x) and resamples rows, per
Simonsohn, Simmons, and Nelson's (2020) observational scheme. Both are for
linear models (including fixed effects) and fit every specification on a
common sample; a new common_sample argument is also exposed.sca_test()
is run with keep_curves = TRUE and a confound-preserving null
(null_type = "freedman_lane" or "residual_bootstrap"), it now attaches a
multiple-comparison-corrected p-value for every specification, answering
which specifications are more extreme than chance once you account for having
searched all of them. It uses the min-P / max-statistic permutation method of
Westfall and Young (1993), reusing the permutation null already computed (no
extra permutations), and compares each specification to its own null -- which
keeps it calibrated under the confound-preserving nulls, where an
under-controlled specification's null is not centred on zero. The new exported
sca_minp() recomputes the adjustment from an existing result with a different
method ("single_step", the default, controls weak FWER; "step_down" is the
more powerful Westfall-Young free step-down) or significance threshold.
plot_sca_test_specs() highlights the specifications that survive correction,
and print(), sca_report(), sca_table(), glance(), and
as.data.frame(what = "specs") surface the results.tidy() and glance() methods (the generics
that broom and modelsummary dispatch on, so no broom dependency is
added) are provided for sca() curves and sca_test() results, along with
as.data.frame() for sca_test. sca_table() renders a publication results
block -- a dependency-free data frame by default, or Markdown / LaTeX / gt /
kableExtra / flextable -- and sca_report() writes a one-paragraph,
manuscript-ready summary. sca() output now carries an "sca" class (it
remains a data frame in every other respect) so these methods can dispatch.sca_variance() and plot_variance(): decompose the variance of the
focal coefficient across the specification curve into the share attributable
to each control choice (plus a residual). Uses an LMG / Shapley
decomposition of R-squared by default (order-invariant shares that sum to the
model R-squared), with a Type II partial sums-of-squares option; adds no new
dependency. Analogous to specr::icc_specs() but suited to speccurvieR's
binary control-inclusion choices.plot_influence() shows how including vs
excluding each control shifts the independent variable's coefficient;
plot_coef_fit() plots the coefficient against model fit (are the
best-fitting specifications outliers?); and plot_multi_se() draws the
specification curve faceted by standard error type, so you can see which
specifications stay significant under each choice of standard error.plot_se(): visualises the output of se_compare(), plotting each
coefficient's estimate with a confidence interval for every standard error
type so the sensitivity of inference to the choice of standard error is easy
to see.theme_sca() are now applied across all plots.
Specification-curve and standard-error points are filled by significance with
a thin white outline so they read as distinct markers against their own error
bars; the zero reference line is a softened red and gridlines are lighter.
plot_curve() and the model-fit plots combine their panels with patchwork
(so the combined plot is now customisable and panels align precisely);
plot_curve() gains median_line and point_size arguments; and
plot_control_distributions() gains a zero_line argument and a cohesive
fill. theme_sca() gains a base_family argument (defaulting to the
speccurvieR.base_family option, else the device font) for setting the plot
font without giving up portability.se_compare() now supports two-way and multiway clustered standard errors.
In addition to a character vector (each element a separate one-way
clustering, unchanged), cluster accepts a list whose elements are each a
character vector of dimensions to cluster on jointly, so
cluster = list("a", "b", c("a", "b")) returns one-way SEs by a, by b,
and two-way clustered by both. Multiway estimates use the native
Cameron-Gelbach-Miller computation in sandwich::vcovCL (non-fixed-effects,
including glm) and fixest (fixed effects); multiway columns are labelled
with the dimensions joined by _BY_ (e.g. "HC1_a_BY_b").se_compare() gains family and link arguments mirroring sca(), so the
full range of glm() model families (e.g. logistic, Poisson) can be compared
across standard error types. The iid, heteroskedasticity-consistent,
clustered, and bootstrapped standard error machinery all carry over to glm
fits. Fixed effects remain OLS-only and are dropped with a warning when a
non-linear family is supplied, matching sca()'s behaviour.sca() gains a formula interface:
sca(y ~ x + control1 + control2 | fixed_effect, data). The first
right-hand-side term is the focal independent variable, the remaining terms
are controls, and anything after | is treated as fixed effects. The original
y / x / controls / fixed_effects argument interface is unchanged.sca() now reports n_obs, the number of observations each specification was
fit on, and gains a common_sample argument. With default listwise deletion,
specifications with different control sets can be fit on different samples
(which conflates control effects with sample changes); n_obs makes this
visible and common_sample = TRUE fits every specification on the rows that
are complete across all model variables. New plot_samplesizes() plots the
per-specification sample sizes.plotCurve() -> plot_curve(), fixedEffects ->
fixed_effects). The previous camelCase names are kept as deprecated aliases
that warn and forward to the new names, so existing code keeps working; they
are scheduled for removal in version 2.0.0.sca() through the
joint-inference test.plot_sca_test_specs() no longer shows blank legend keys for tiers
with no specifications (e.g. when every specification survives correction);
unused tiers are dropped from the legend.sca() no longer drops the focal variable (and crash with
"subscript out of bounds") when a control variable's name contains the focal
variable's name as a substring (e.g. focal "Temp" with a control "TempX")
or the focal name contains regular-expression metacharacters. The focal term
is now matched by exact equality rather than a substring/regex test.se_compare() accepts a formula object (y ~ x | fe), not only a
formula string ("y ~ x | fe"). Previously a formula object crashed with
"the condition has length > 1", because as.character() on a formula returns
a length-3 vector that broke the internal fixed-effects check. se_compare()
also now skips an individual clustered standard-error type that fails to
compute (e.g. a numerically singular clustered HC3) with a clear warning,
instead of letting the whole call error on a raw LAPACK message.sqrt(sample_size / nrow(data))
(an m-out-of-n bootstrap). Previously resamples were drawn without
replacement and were not rescaled, which overstated the standard error by
roughly sqrt(nrow(data) / sample_size) and collapsed it to ~0 when
boot_sample_size equalled the number of rows."CL_FE" column of se_compare() is now clustered by the first
fixed effect, as its name and documentation describe. Modern fixest
(>= 0.10) defaults feols() to IID standard errors, so the previous code
(which read off the feols() default) was returning IID rather than
cluster-robust standard errors under that label.sca() now raises an informative error when the focal variable x
does not correspond to a single model coefficient (for example a factor or
interaction term), instead of failing with a cryptic "subscript out of
bounds" error or silently returning NA for every specification.sca()'s control-indicator columns (used by plot_vars() and the
plot_curve() control panel) are now built by exact term membership instead
of substring matching on the model terms. Previously a control whose name was
a substring of another term (for example "O2" inside "O2Sat") was marked
present in specifications that did not contain it.sca() and se_compare() now validate that the supplied column names exist
in data, failing with an informative message instead of a cryptic
deep-stack error.sca() accepts family = "gaussian" as an alias for OLS and defaults to a
glm family's canonical link when link is not supplied (e.g.
family = "binomial" no longer requires explicitly specifying the logit
link). An unrecognised family now raises a clear error.se_compare() no longer errors on a fixed-effects model when clustering with
specific (non-"all") standard error types, and no longer warns that the
fixed-effects-only "CL_FE" type is invalid when the formula has fixed
effects.se_compare() standard-error type validation and
bootstrap sample-size expansion, a stray debug print, and a crash when
combining fixed effects with a non-linear family in sca().testthat test suite and refactored the internals of sca() and
se_compare(); no change to their output for valid input.Added support for weights in se_compare().
Added support for weights in sca().