diff --git a/DESCRIPTION b/DESCRIPTION index 8188bcd..c727bc2 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: ggfixest Title: Dedicated ggplot2 methods for fixest objects -Version: 0.0.3.9000 +Version: 0.0.3.9001 Authors@R: c(person(given = "Grant", family = "McDermott", diff --git a/NAMESPACE b/NAMESPACE index 4d73143..d52fb34 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -6,3 +6,5 @@ export(ggiplot) export(iplot_data) import(fixest) import(ggplot2) +importFrom(fixest,coefplot) +importFrom(fixest,iplot) diff --git a/NEWS.md b/NEWS.md index 1adbcbd..23f211e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,12 +1,19 @@ -# ggfixest 0.0.3.9000 (development version) +# ggfixest 0.0.3.9001 (development version) ## New features -- The `aggr_es` function now supports numeric sequences for aggregating -specific period subsets, in addition to the existing keyword strings like "pre" -or "post". This functionality passes through to the higher order functions that -call `aggr_es` under the hood. (#33) - +- The `aggr_es` function now supports numeric sequences for aggregating a +specific subset of periods, in addition to the existing keyword strings like +"pre" or "post". This functionality also passes through to the higher order +plotting functions that call `aggr_es` under the hood. For example, +`ggiplot(est, aggr_eff = 6:8)`. (#33) +- Users can now adjust standard errors for model objects on-the-fly at plot +time, by passing an appropriate argument, e.g. `ggcoefplot(est, vcov = "hc1")`. +These on-the-fly adjustments are done via `summary.fixest`, and so the effect is +just the same as passing an adjusted object directly, e.g. +`ggcoefplot(summary(est, vcov = "hc1"))`. However, it may prove more convenient +for simultaneously adjusting a list of multiple models, e.g. +`ggcoefplot(list(est1, est2, est3), vcov = "hc1")`. (#35) # ggfixest 0.0.3 diff --git a/R/ggcoefplot.R b/R/ggcoefplot.R index aab9a7a..b256a7d 100644 --- a/R/ggcoefplot.R +++ b/R/ggcoefplot.R @@ -55,6 +55,10 @@ #' channel. For example, we can make the CI band lighter with #' `ci.fill.par = list(alpha = 0.2)` (the default alpha is 0.3). #' * `dict` a dictionary for overriding coefficient names. +#' * `vcov`, `cluster` or `se` as alternative options for adjusting the +#' standard errors of the model object(s) on the fly. See `summary.fixest` for +#' details. Written here in superseding order; `cluster` will only be +#' considered if `vcov` is not null, etc. #' @details These functions generally try to mimic the functionality and (where #' appropriate) arguments of `fixest::coefplot` and `fixest::iplot` as #' closely as possible. However, by leveraging the ggplot2 API and diff --git a/R/ggiplot.R b/R/ggiplot.R index 1e9dc38..73f2216 100644 --- a/R/ggiplot.R +++ b/R/ggiplot.R @@ -49,6 +49,10 @@ ggiplot = function( ref.line.par = list(col = "black", lty = 2, lwd = 0.3) if (!is.null(dots[["ref.line.par"]])) ref.line.par = utils::modifyList(ref.line.par, dots[["ref.line.par"]]) + # VCOV adjustments (if any) + vcov = if (!is.null(dots[['vcov']])) dots[['vcov']] else NULL + cluster = if (!is.null(dots[['cluster']])) dots[['cluster']] else NULL + se = if (!is.null(dots[['se']])) dots[['se']] else NULL # The next few blocks grab the underlying iplot/coefplot data, contingent on the # object that was passed into the function (i.e. fixest, fixest_multi, or @@ -58,11 +62,21 @@ ggiplot = function( if (inherits(object, c("fixest", "fixest_multi"))) { if (length(ci_level) == 1) { - data = iplot_data_func(object, .ci_level = ci_level, .dict = dict, .aggr_es = aggr_eff, .keep = keep, .drop = drop, .group = group, .i.select = i.select) + data = iplot_data_func( + object, + .ci_level = ci_level, .dict = dict, .aggr_es = aggr_eff, + .keep = keep, .drop = drop, .group = group, .i.select = i.select, + .vcov = vcov, .cluster = cluster, .se = se + ) } else { data = lapply( ci_level, - function(ci_l) iplot_data_func(object, .ci_level = ci_l, .dict = dict, .aggr_es = aggr_eff, .keep = keep, .drop = drop, .group = group, .i.select = i.select) + function(ci_l) iplot_data_func( + object, + .ci_level = ci_l, .dict = dict, .aggr_es = aggr_eff, + .keep = keep, .drop = drop, .group = group, .i.select = i.select, + .vcov = vcov, .cluster = cluster, .se = se + ) ) data = do.call("rbind", data) } @@ -82,13 +96,18 @@ ggiplot = function( if (length(ci_level) == 1) { data = lapply( object, iplot_data_func, - .ci_level = ci_level, .dict = dict, .aggr_es = aggr_eff, .group = group, .i.select = i.select + .ci_level = ci_level, .dict = dict, .aggr_es = aggr_eff, + .group = group, .i.select = i.select, + .vcov = vcov, .cluster = cluster, .se = se ) } else { data = lapply(ci_level, function(ci_l) { - lapply(object, iplot_data_func, - .ci_level = ci_l, - .dict = dict, .aggr_es = aggr_eff, .group = group, .i.select = i.select + lapply( + object, iplot_data_func, + .ci_level = ci_l, + .dict = dict, .aggr_es = aggr_eff, + .group = group, .i.select = i.select, + .vcov = vcov, .cluster = cluster, .se = se ) }) data = do.call(function(...) Map("rbind", ...), data) diff --git a/R/iplot_data.R b/R/iplot_data.R index d7117a0..a04d28a 100644 --- a/R/iplot_data.R +++ b/R/iplot_data.R @@ -42,6 +42,11 @@ #' aggregated mean treatment effects for some subset of the model should be #' added as a column to the returned data frame. Passed to #' `aggr_es(..., aggregation = "mean")`. +#' @param .vcov,.cluster,.se Alternative options for adjusting the standard +#' errors of the model object on the fly. See `summary.fixest` for details +#' (although note that the "." period prefix should be ignored in the latter's +#' argument documentation). Written here in superseding order; `.cluster` will +#' only be considered if `.vcov` is not null, etc. #' @details This function is a wrapper around #' `fixest::iplot(..., only.params = TRUE)`, but with various checks and tweaks #' to better facilitate plotting with `ggplot2` and handling of complex object @@ -51,6 +56,7 @@ #' relative x-axis positions, and other aesthetic information needed to draw #' a ggplot2 object. #' @import ggplot2 +#' @importFrom fixest coefplot iplot #' @export #' @examples #' library(fixest) @@ -77,7 +83,10 @@ iplot_data = function( .i.select = 1, # .aggr_es = c("none", "post", "pre", "both"), .aggr_es = NULL, - .group = "auto" + .group = "auto", + .vcov = NULL, + .cluster = NULL, + .se = NULL ) { # .aggr_es = match.arg(.aggr_es) @@ -94,7 +103,16 @@ iplot_data = function( .group = NULL } - p = fixest::coefplot(object, only.params = TRUE, ci_level = .ci_level, dict = .dict, keep = .keep, drop = .drop, internal.only.i = .internal.only.i, i.select = .i.select) + # Catch VCOV adjustments (if any) + if (!is.null(.vcov)) { + object = summary(object, vcov = .vcov) + } else if (!is.null(.cluster)) { + object = summary(object, cluster = .cluster) + } else if (!is.null(.se)) { + object = summary(object, se = .se) + } + + p = coefplot(object, only.params = TRUE, ci_level = .ci_level, dict = .dict, keep = .keep, drop = .drop, internal.only.i = .internal.only.i, i.select = .i.select) d = p$prms if (inherits(object, "fixest_multi")) { @@ -423,9 +441,18 @@ coefplot_data = function( .dict = fixest::getFixest_dict(), .internal.only.i = FALSE, .i.select = 1, - .aggr_es = "none" + .aggr_es = "none", + .vcov = NULL, + .cluster = NULL, + .se = NULL ) { - iplot_data(object, .ci_level = .ci_level, .dict = .dict, .keep = .keep, .drop = .drop, .internal.only.i = .internal.only.i, .group = .group) + iplot_data( + object, + .ci_level = .ci_level, .dict = .dict, + .keep = .keep, .drop = .drop, + .internal.only.i = .internal.only.i, .group = .group, + .vcov = .vcov, .cluster = .cluster, .se = .se + ) } diff --git a/README.Rmd b/README.Rmd index d05114c..3bbfd8e 100644 --- a/README.Rmd +++ b/README.Rmd @@ -43,7 +43,7 @@ install.packages("ggfixest", repos = "https://grantmcdermott.r-universe.dev") ## Quickstart -The [package website](http://grantmcdermott.com/ggfixest) +The [package website](https://grantmcdermott.com/ggfixest/) provides a number of examples in the help documentation. (Also available by typing `?ggcoefplot` or `?ggiplot` in your R console.) But here are a few quickstart examples to whet your appetite. diff --git a/README.md b/README.md index d1fea20..e060a65 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ install.packages("ggfixest", repos = "https://grantmcdermott.r-universe.dev") ## Quickstart -The [package website](http://grantmcdermott.com/ggfixest) provides a +The [package website](https://grantmcdermott.com/ggfixest/) provides a number of examples in the help documentation. (Also available by typing `?ggcoefplot` or `?ggiplot` in your R console.) But here are a few quickstart examples to whet your appetite. diff --git a/inst/tinytest/_tinysnapshot/ggcoefplot_did_iid.svg b/inst/tinytest/_tinysnapshot/ggcoefplot_did_iid.svg new file mode 100644 index 0000000..6caf367 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/ggcoefplot_did_iid.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +0 +5 + + + + + + + + + + + + +x1 +1 +2 +3 +4 +6 +7 +8 +9 +10 + +treat × (period = ...) +Estimate and 95% Conf. Int. +Effect on y + + diff --git a/inst/tinytest/_tinysnapshot/ggiplot_multi_complex_kitchen_iid.svg b/inst/tinytest/_tinysnapshot/ggiplot_multi_complex_kitchen_iid.svg new file mode 100644 index 0000000..b1a685e --- /dev/null +++ b/inst/tinytest/_tinysnapshot/ggiplot_multi_complex_kitchen_iid.svg @@ -0,0 +1,349 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +sample: Odds + +Sun & Abraham (2020) + + + + + + + + + + +sample: Odds + +TWFE + + + + + + + + + + +sample: Evens + +Sun & Abraham (2020) + + + + + + + + + + +sample: Evens + +TWFE + + + + + +-5 +0 +5 + + + +-5 +0 +5 +-4 +0 +4 +8 + + + + +-4 +0 +4 +8 + + + + +Time to treatment +Estimate and [80% & 95%] Conf. Int. + +group + + + + + + + + +Sun & Abraham (2020) +TWFE +Staggered treatment: Split mutli-sample + + diff --git a/inst/tinytest/test_aggr_es.R b/inst/tinytest/test_aggr_es.R index 0c7ab98..ffdeac0 100644 --- a/inst/tinytest/test_aggr_es.R +++ b/inst/tinytest/test_aggr_es.R @@ -80,7 +80,7 @@ aggr_rhs1_known = data.frame( # # tests ---- -tol = 1e-6 +tol = 1e-4 for (col in c("term", "estimate", "std.error", "statistic", "p.value", "s.value", "conf.low", "conf.high")) { diff --git a/inst/tinytest/test_ggcoefplot.R b/inst/tinytest/test_ggcoefplot.R index 5a44068..b7a5583 100644 --- a/inst/tinytest/test_ggcoefplot.R +++ b/inst/tinytest/test_ggcoefplot.R @@ -56,3 +56,13 @@ data("base_did", package = "fixest") est_did = feols(y ~ x1 + i(period, treat, 5) | id + period, base_did) p_did = ggcoefplot(est_did) expect_snapshot_plot(p_did, label = "ggcoefplot_did") + + +# +## vcov adjustment (passed through ...) ---- + +p_did_iid_summ = ggcoefplot(summary(est_did, vcov = "iid")) # manual approach +p_did_iid = ggcoefplot(est_did, vcov = "iid") # passed through "..." +expect_snapshot_plot(p_did_iid_summ, label = "ggcoefplot_did_iid") +expect_snapshot_plot(p_did_iid, label = "ggcoefplot_did_iid") # should be identical + diff --git a/inst/tinytest/test_ggiplot.R b/inst/tinytest/test_ggiplot.R index 7d8cbfe..c0cd13f 100644 --- a/inst/tinytest/test_ggiplot.R +++ b/inst/tinytest/test_ggiplot.R @@ -153,3 +153,41 @@ p18 = ggiplot( expect_snapshot_plot(p16, label = "ggiplot_multi_complex") expect_snapshot_plot(p17, label = "ggiplot_multi_complex_mci") expect_snapshot_plot(p18, label = "ggiplot_multi_complex_kitchen") + +# Last one(s): Check vcov adjustment of previous plot +# Manual version, passing via summary first... +p19a = ggiplot( + list( + 'TWFE' = summary(est_twfe_grp, vcov = "iid"), + 'Sun & Abraham (2020)' = summary(est_sa20_grp, vcov = "iid") + ), + main = 'Staggered treatment: Split mutli-sample', + ref.line = -1, + xlab = 'Time to treatment', + multi_style = 'facet', + geom_style = 'ribbon', + ci_level = c(.8, .95), + theme = theme_minimal() + theme( + text = element_text(family = 'HersheySans'), + plot.title = element_text(hjust = 0.5), + legend.position = 'none' + ) +) +# Next, passing as a convenience string (via ...) +p19b = ggiplot( + list('TWFE' = est_twfe_grp, 'Sun & Abraham (2020)' = est_sa20_grp), + vcov = "iid", + main = 'Staggered treatment: Split mutli-sample', + ref.line = -1, + xlab = 'Time to treatment', + multi_style = 'facet', + geom_style = 'ribbon', + ci_level = c(.8, .95), + theme = theme_minimal() + theme( + text = element_text(family = 'HersheySans'), + plot.title = element_text(hjust = 0.5), + legend.position = 'none' + ) +) +expect_snapshot_plot(p19a, label = "ggiplot_multi_complex_kitchen_iid") +expect_snapshot_plot(p19b, label = "ggiplot_multi_complex_kitchen_iid") diff --git a/man/ggcoefplot.Rd b/man/ggcoefplot.Rd index 23a513b..5009eb7 100644 --- a/man/ggcoefplot.Rd +++ b/man/ggcoefplot.Rd @@ -70,6 +70,10 @@ are requested for the default pointrange style). The default value is 0.2. channel. For example, we can make the CI band lighter with \code{ci.fill.par = list(alpha = 0.2)} (the default alpha is 0.3). \item \code{dict} a dictionary for overriding coefficient names. +\item \code{vcov}, \code{cluster} or \code{se} as alternative options for adjusting the +standard errors of the model object(s) on the fly. See \code{summary.fixest} for +details. Written here in superseding order; \code{cluster} will only be +considered if \code{vcov} is not null, etc. }} \item{aggr_eff}{A keyword string or numeric sequence, indicating whether diff --git a/man/iplot_data.Rd b/man/iplot_data.Rd index e967196..3e2a6e4 100644 --- a/man/iplot_data.Rd +++ b/man/iplot_data.Rd @@ -14,7 +14,10 @@ iplot_data( .internal.only.i = TRUE, .i.select = 1, .aggr_es = NULL, - .group = "auto" + .group = "auto", + .vcov = NULL, + .cluster = NULL, + .se = NULL ) coefplot_data( @@ -26,7 +29,10 @@ coefplot_data( .dict = fixest::getFixest_dict(), .internal.only.i = FALSE, .i.select = 1, - .aggr_es = "none" + .aggr_es = "none", + .vcov = NULL, + .cluster = NULL, + .se = NULL ) } \arguments{ @@ -78,6 +84,12 @@ valid uses: ⁠group=list(group_name=c(\"var_start\", \"var_end\"))⁠, ⁠group=list(group_name=1:2)) See the Details section of \code{?fixest::coefplot} for more.} + +\item{.vcov, .cluster, .se}{Alternative options for adjusting the standard +errors of the model object on the fly. See \code{summary.fixest} for details +(although note that the "." period prefix should be ignored in the latter's +argument documentation). Written here in superseding order; \code{.cluster} will +only be considered if \code{.vcov} is not null, etc.} } \value{ A data frame consisting of estimate values, confidence intervals,