├── .Rbuildignore
├── .github
├── .gitignore
└── workflows
│ ├── R-CMD-check.yaml
│ └── pkgdown.yaml
├── .gitignore
├── CRAN-SUBMISSION
├── DESCRIPTION
├── NAMESPACE
├── NEWS.md
├── R
├── aggr_es.R
├── ggcoefplot.R
├── ggiplot.R
├── iplot_data.R
└── utils.R
├── README.Rmd
├── README.md
├── _pkgdown.yml
├── cran-comments.md
├── ggfixest.Rproj
├── inst
└── tinytest
│ ├── _tinysnapshot
│ ├── ggcoefplot_did.svg
│ ├── ggcoefplot_did_iid.svg
│ ├── ggcoefplot_group_names_prefix.svg
│ ├── ggcoefplot_group_nonames.svg
│ ├── ggcoefplot_group_none.svg
│ ├── ggcoefplot_groupnames.svg
│ ├── ggcoefplot_interactions.svg
│ ├── ggcoefplot_interactions_multici.svg
│ ├── ggcoefplot_multi.svg
│ ├── ggcoefplot_multi_facet.svg
│ ├── ggcoefplot_simple.svg
│ ├── ggiplot_list.svg
│ ├── ggiplot_multi_complex.svg
│ ├── ggiplot_multi_complex_kitchen.svg
│ ├── ggiplot_multi_complex_kitchen_iid.svg
│ ├── ggiplot_multi_complex_mci.svg
│ ├── ggiplot_multi_csw.svg
│ ├── ggiplot_multi_csw_facet.svg
│ ├── ggiplot_multi_facet.svg
│ ├── ggiplot_multi_facet_ribbon.svg
│ ├── ggiplot_multi_lhs.svg
│ ├── ggiplot_multi_lhs_csw.svg
│ ├── ggiplot_multi_lhs_csw_facet.svg
│ ├── ggiplot_multi_lhs_facet.svg
│ ├── ggiplot_multi_lhs_facet_ribbon.svg
│ ├── ggiplot_multi_single.svg
│ ├── ggiplot_multi_single_kitchen.svg
│ ├── ggiplot_multi_single_kitchen_ribbon.svg
│ ├── ggiplot_multi_single_ribbon.svg
│ ├── ggiplot_multi_single_unnamed.svg
│ ├── ggiplot_multi_sw_pt_join1.svg
│ ├── ggiplot_multi_sw_pt_join2.svg
│ ├── ggiplot_simple.svg
│ ├── ggiplot_simple_errorbar.svg
│ ├── ggiplot_simple_mci.svg
│ ├── ggiplot_simple_mci_ribbon.svg
│ ├── ggiplot_simple_ribbon.svg
│ ├── ggiplot_stagg_mci.svg
│ └── ggiplot_stagg_mci_ribbon.svg
│ ├── test_aggr_es.R
│ ├── test_fixest_multi.R
│ ├── test_ggcoefplot.R
│ ├── test_ggiplot.R
│ ├── test_iplot_data.R
│ ├── test_nthreads.R
│ └── tinysnapshot_helpers.R
├── man
├── aggr_es.Rd
├── figures
│ ├── README-coefplot1-1.png
│ ├── README-coefplot2-1.png
│ ├── README-es1-1.png
│ ├── README-es2-1.png
│ └── README-es3-1.png
├── ggcoefplot.Rd
└── iplot_data.Rd
├── tests
└── tinytest.R
└── vignettes
├── .gitignore
└── ggiplot.Rmd
/.Rbuildignore:
--------------------------------------------------------------------------------
1 | ^ggfixest\.Rproj$
2 | ^\.Rproj\.user$
3 | ^README\.Rmd$
4 | ^README\.html$
5 | ^LICENSE\.md$
6 | ^_pkgdown\.yml$
7 | ^docs$
8 | ^pkgdown$
9 | ^\.github$
10 | ^TESTING$
11 | ^.vscode$
12 | ^cran-comments.md
13 | ^CRAN-SUBMISSION$
14 |
--------------------------------------------------------------------------------
/.github/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 |
--------------------------------------------------------------------------------
/.github/workflows/R-CMD-check.yaml:
--------------------------------------------------------------------------------
1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3 | on:
4 | push:
5 | branches: [main, master]
6 | pull_request:
7 | branches: [main, master]
8 |
9 | name: ci
10 |
11 | jobs:
12 | R-CMD-check:
13 | runs-on: ${{ matrix.config.os }}
14 |
15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }})
16 |
17 | strategy:
18 | fail-fast: false
19 | matrix:
20 | config:
21 | # - {os: macos-latest, r: 'release'}
22 | # - {os: windows-latest, r: 'release'}
23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
24 | - {os: ubuntu-latest, r: 'release'}
25 | # - {os: ubuntu-latest, r: 'oldrel-1'}
26 |
27 | env:
28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
29 | R_KEEP_PKG_SOURCE: yes
30 |
31 | steps:
32 | - uses: actions/checkout@v3
33 |
34 | - uses: r-lib/actions/setup-pandoc@v2
35 |
36 | - uses: r-lib/actions/setup-r@v2
37 | with:
38 | r-version: ${{ matrix.config.r }}
39 | http-user-agent: ${{ matrix.config.http-user-agent }}
40 | use-public-rspm: true
41 |
42 | - uses: r-lib/actions/setup-r-dependencies@v2
43 | with:
44 | extra-packages: any::rcmdcheck
45 | needs: check
46 |
47 | - uses: r-lib/actions/check-r-package@v2
48 | with:
49 | upload-snapshots: true
50 |
--------------------------------------------------------------------------------
/.github/workflows/pkgdown.yaml:
--------------------------------------------------------------------------------
1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
3 | on:
4 | push:
5 | branches: [main, master]
6 | pull_request:
7 | branches: [main, master]
8 | release:
9 | types: [published]
10 | workflow_dispatch:
11 |
12 | name: pkgdown
13 |
14 | jobs:
15 | pkgdown:
16 | runs-on: ubuntu-latest
17 | # Only restrict concurrency for non-PR jobs
18 | concurrency:
19 | group: pkgdown-${{ github.event_name != 'pull_request' || github.run_id }}
20 | env:
21 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
22 | steps:
23 | - uses: actions/checkout@v3
24 |
25 | - uses: r-lib/actions/setup-pandoc@v2
26 |
27 | - uses: r-lib/actions/setup-r@v2
28 | with:
29 | use-public-rspm: true
30 |
31 | - uses: r-lib/actions/setup-r-dependencies@v2
32 | with:
33 | pak-version: devel
34 | extra-packages: any::pkgdown, local::.
35 | needs: website
36 |
37 | - name: Build site
38 | run: pkgdown::build_site_github_pages(new_process = FALSE, install = FALSE)
39 | shell: Rscript {0}
40 |
41 | - name: Deploy to GitHub pages 🚀
42 | if: github.event_name != 'pull_request'
43 | uses: JamesIves/github-pages-deploy-action@v4.4.1
44 | with:
45 | clean: false
46 | branch: gh-pages
47 | folder: docs
48 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .Rdata
4 | .httr-oauth
5 | .DS_Store
6 | .Renviron
7 | .vscode
8 | docs
9 | inst/doc
10 | inst/tinytest/_tinysnapshot_review
11 | inst/tinytest/_tinysnapshot_MacOS
12 | README.html
13 | TESTING
14 |
--------------------------------------------------------------------------------
/CRAN-SUBMISSION:
--------------------------------------------------------------------------------
1 | Version: 0.3.0
2 | Date: 2025-05-14 02:08:14 UTC
3 | SHA: 1806f1d281d6898a1e552a9a33ce5947ec919bba
4 |
--------------------------------------------------------------------------------
/DESCRIPTION:
--------------------------------------------------------------------------------
1 | Package: ggfixest
2 | Title: Dedicated 'ggplot2' Methods for 'fixest' Objects
3 | Version: 0.3.0
4 | Date: 2025-05-13
5 | Authors@R: c(
6 | person(
7 | given = "Grant",
8 | family = "McDermott",
9 | role = c("aut", "cre"),
10 | email = "gmcd@amazon.com",
11 | comment = c(ORCID = "0000-0001-7883-8573")
12 | ),
13 | person(
14 | given = "Laurent",
15 | family = "Berge",
16 | role = "ctb",
17 | email = "laurent.berge@u-bordeaux.fr"
18 | ),
19 | person(
20 | given = "Teun",
21 | family = "van den Brand",
22 | role = "ctb",
23 | comment = c(ORCID = "0000-0002-9335-7468")
24 | )
25 | )
26 | Description: Provides 'ggplot2' equivalents of fixest::coefplot() and fixest::iplot(),
27 | for producing nice coefficient plots and interaction plots. Enables some
28 | additional functionality and convenience features, including grouped
29 | multi-'fixest' object faceting and programmatic updates to existing plots
30 | (e.g., themes and aesthetics).
31 | License: GPL-3
32 | Encoding: UTF-8
33 | LazyData: true
34 | Roxygen: list(markdown = TRUE)
35 | RoxygenNote: 7.3.2
36 | URL: https://grantmcdermott.com/ggfixest/
37 | BugReports: https://github.com/grantmcdermott/ggfixest/issues
38 | Depends:
39 | ggplot2 (>= 2.2.0),
40 | fixest (>= 0.11.2)
41 | Imports:
42 | dreamerr,
43 | scales,
44 | marginaleffects (>= 0.10.0),
45 | stats,
46 | utils,
47 | legendry (>= 0.2)
48 | Suggests:
49 | knitr,
50 | rmarkdown,
51 | tinytest (>= 1.4.1),
52 | tinysnapshot (>= 0.0.3),
53 | magick,
54 | rsvg,
55 | svglite (>= 2.2.0),
56 | fontquiver,
57 | data.table
58 | VignetteBuilder: knitr
59 |
--------------------------------------------------------------------------------
/NAMESPACE:
--------------------------------------------------------------------------------
1 | # Generated by roxygen2: do not edit by hand
2 |
3 | export(aggr_es)
4 | export(ggcoefplot)
5 | export(ggiplot)
6 | export(iplot_data)
7 | import(fixest)
8 | import(ggplot2)
9 | importFrom(fixest,coefplot)
10 | importFrom(fixest,iplot)
11 | importFrom(marginaleffects,hypotheses)
12 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | # ggfixest 0.3.0
2 |
3 | ### New features
4 |
5 | - The new `aggr_es(..., period = "diff")` convenience keyword argument allows
6 | users to estimate the difference between the (mean) post- and pre-treatment
7 | periods. Thanks to @FBrunamonti for the suggestion. (#52).
8 |
9 | ### Internals
10 |
11 | - Bump `svglite` dependency version and update test snapshots. (#51)
12 |
13 | # ggfixest 0.2.0
14 |
15 | ### Bug fixes
16 |
17 | - Fix missing "hypothesis" attribute for `aggr_es` objects. (#43)
18 | - Fix dodge misalignment between points and lines with multi fixest objects. (#44)
19 |
20 | ### Internals
21 |
22 | - Replace `ggh4x` dependency with `legendry`. (#41 @teunbrand)
23 |
24 | ### Misc
25 |
26 | - Minor website and documentation improvements.
27 |
28 | # ggfixest 0.1.0
29 |
30 | First CRAN release!
31 |
32 | ### New features
33 |
34 | - The `aggr_es` function now supports numeric sequences for aggregating a
35 | specific subset of periods, in addition to the existing keyword strings like
36 | "pre" or "post". This functionality also passes through to the higher order
37 | plotting functions that call `aggr_es` under the hood. For example,
38 | `ggiplot(est, aggr_eff = 6:8)`. (#33)
39 | - Users can now adjust standard errors for model objects on-the-fly at plot
40 | time, by passing an appropriate argument, e.g. `ggcoefplot(est, vcov = "hc1")`.
41 | These on-the-fly adjustments are done via `summary.fixest`, and so the effect is
42 | just the same as passing an adjusted object directly, e.g.
43 | `ggcoefplot(summary(est, vcov = "hc1"))`. However, it may prove more convenient
44 | for simultaneously adjusting a list of multiple models, e.g.
45 | `ggcoefplot(list(est1, est2, est3), vcov = "hc1")`. (#35)
46 |
47 | # ggfixest 0.0.3
48 |
49 | ### Breaking change
50 |
51 | - The package name has been changed to **ggfixest** (#29).
52 |
53 | # ggiplot 0.0.2
54 |
55 | ### New features
56 |
57 | - Support for `ggcoefplot`, a ggplot equivalent of `coefplot` (#28).
58 | - Support `pt.size` argument for controlling the size of point markers (#27).
59 | Thanks @jcvdav.
60 | - Support `keep` and `drop` arguments for subsetting coefficients (#22).
61 |
62 | ### Bug fixes and breaking changes
63 |
64 | - Fix naming mismatch in multiple estimation with different time periods (#10).
65 | Thanks @brockmwilson.
66 | - Slight tweak to default theme, which now uses dotted grid lines to more
67 | closely match `iplot()` (#e5cf0b0).
68 | - Correctly parse formula-transformed dependent variable names, e.g. `log(y`
69 | (#20).
70 | - The confidence intervals for some figures may be slightly wider due to
71 | upstream changes in fixest (#25; see also
72 | https://github.com/lrberge/fixest/pull/408).
73 |
74 | ### Internals
75 |
76 | - Add a (visual) test suite (#12 with several increments thereafter). Thanks to
77 | @vincentarelbundock for ~~tinyviztest~~
78 | [tinysnapshot](https://github.com/vincentarelbundock/tinysnapshot)!
79 | - Switch to `marginaleffects::hypotheses()` internally for `aggr_es()` to match
80 | the upstream changes in **marginaleffects**.
81 | - Simplify multi_fixest object parsing (#19).
82 | - Minor documentation improvements.
83 |
84 | # ggiplot 0.0.1
85 |
86 | * Tweaks to plot output, including integer breaks on x-axis (where appropriate)
87 | and allow additional user-level control (e.g. CI alpha or width levels)
88 | * Support multiple confidence levels (#2, #5)
89 | * Support multiple LHS variables (#1)
90 | * Added a `NEWS.md` file to track changes to the package.
91 |
--------------------------------------------------------------------------------
/R/aggr_es.R:
--------------------------------------------------------------------------------
1 | #' @title Aggregates event-study treatment effects.
2 | #'
3 | #' @md
4 | #' @description Aggregates post- (and/or pre-) treatment effects of an
5 | #' "event-study" estimation, also known as a dynamic difference-in-differences
6 | #' (DDiD) model. The event-study should have been estimated using the `fixest`
7 | #' package, which provides a specialised `i()` operator for this class
8 | #' of models. By default, the function will return the average post-treatment
9 | #' effect (i.e., across multiple periods). However, it can also return the
10 | #' cumulative post-treatment effect and can be used to aggregate pre-treatment
11 | #' effects too.
12 | #' @param object A model object of class `fixest`, where the `i()` operator has
13 | #' been used to facilitate an "event-study" DiD design. See Examples.
14 | #' @param period Keyword string or numeric sequence. Which group of periods
15 | #' are we aggregating? Accepts the following convenience strings: `"post"` (the
16 | #' default), `"pre"`, `"both"`, or `"diff` (for the difference between the post
17 | #' and pre periods). Alternatively, can also be a numeric sequence that
18 | #' designates an explicit subset of periods in the data (e.g. `6:8`).
19 | #' @param rhs Numeric. The null hypothesis value. Defaults to 0.
20 | #' @param aggregation Character string. The aggregation type. Either `"mean"`
21 | #' (the default) or `"cumulative"`.
22 | #' @param abbr_term Logical. Should the leading "term" column of the return
23 | #' data frame be abbreviated? The default is `TRUE`. If `FALSE`, then the term
24 | #' column will retain the full hypothesis test string as per usual with
25 | #' [`marginaleffects::hypotheses()`]. Note that this information is retained as
26 | #' an attribute of the return object, regardless.
27 | #' @param ... Additional arguments passed to [`marginaleffects::hypotheses()`].
28 | #' @seealso [marginaleffects::hypotheses()], which this function is ultimately a
29 | #' convenience wrapper around.
30 | #' @return A "tidy" data frame of aggregated (pre and/or post) treatment
31 | #' effects, plus inferential information about standard errors, confidence
32 | #' intervals, etc. Potentially useful information about the underlying
33 | #' hypothesis test is also provided as an attribute. See Examples.
34 | #' @importFrom marginaleffects hypotheses
35 | #' @export
36 | #' @examples
37 | #' library(ggfixest) ## Will load fixest too
38 | #'
39 | #' est = feols(y ~ x1 + i(period, treat, 5) | id + period, base_did)
40 | #'
41 | #' # Default hypothesis test is a null mean post-treatment effect
42 | #' (post_mean = aggr_es(est))
43 | #'
44 | #' # The underlying hypothesis is saved as an attribute
45 | #' attr(post_mean, "hypothesis")
46 | #'
47 | #' # Other hypothesis and aggregation options
48 | #' aggr_es(est, period = "pre") # pre period instead of post
49 | #' aggr_es(est, period = "both") # pre & post periods separately
50 | #' aggr_es(est, period = "diff") # post vs pre difference
51 | #' aggr_es(est, period = 6:8) # specific subset of periods
52 | #' aggr_es(est, period = "pre", rhs = -1) # pre period with H0 value of 1
53 | #' aggr_es(est, aggregation = "cumulative") # cumulative instead of mean effects
54 | #' # Etc.
55 | #'
56 | aggr_es = function(object,
57 | period = c("post", "pre", "both", "diff"),
58 | rhs = 0,
59 | aggregation = c("mean", "cumulative"),
60 | abbr_term = TRUE,
61 | ...) {
62 | aggregation = match.arg(aggregation)
63 | # if (!(any(period %in% c("post", "pre", "both")) || is.numeric(period))) {
64 | # stop('The `period` argument must be one of c("post", "pre", "both"), or a numeric sequence.')
65 | # }
66 | if (!is.numeric(period)) {
67 | period = match.arg(period)
68 | }
69 |
70 | fixest_obj = inherits(object, "fixest")
71 | if (!fixest_obj) stop("Please provide a valid fixest object.")
72 | mm = object$model_matrix_info[[1]]
73 | if (is.null(mm)) {
74 | stop(
75 | "This function only works with fixest objects that were ",
76 | "created with the i() operator. See `?fixest::i()`."
77 | )
78 | }
79 | coefs = mm$coef_names_full
80 | ref_id = mm$ref_id[1]
81 | ## Store our periods in a list to make the below lapply call easier
82 | if (is.numeric(period)) {
83 | if (!all(period %in% mm$items)) {
84 | stop(
85 | '\nSupplied period sequence does not match periods in model object.',
86 | '\nUser-supplied periods: ', period,
87 | 'Model periods: ', mm$items
88 | )
89 | }
90 | if (any(period %in% mm$ref)) {
91 | warning("\nThe reference period, ", mm$ref, ", cannot be included in the aggregation and will be dropped.")
92 | period = setdiff(period, mm$ref)
93 | }
94 | idx = list(match(period, mm$items))
95 | names(idx) = paste("periods", paste(range(period), collapse=":"))
96 | } else if (period == "post") {
97 | idx = list("post" = (ref_id + 1):length(coefs))
98 | } else if (period == "pre") {
99 | idx = list("pre" = 1:(ref_id - 1)) # ref period is dropped from the model
100 | } else if (period %in% c("both", "diff")) {
101 | idx = list("pre" = 1:(ref_id - 1), "post" = (ref_id + 1):length(coefs))
102 | if (period == "diff") {
103 | idx = rev(idx) # we want post - pre
104 | rhs = NULL
105 | }
106 | }
107 |
108 | hstring = sapply(
109 | idx,
110 | function(i) gen_hstring(
111 | coefs,
112 | periods = i,
113 | aggregation = aggregation,
114 | rhs = rhs
115 | )
116 | )
117 | if (!is.numeric(period) && period == "diff") hstring = paste(hstring, collapse = " = ")
118 |
119 | res = hypotheses(object, hypothesis = hstring, ...)
120 | if (abbr_term) {
121 | if (is.numeric(period)) {
122 | res$term = paste0(names(idx), " (", aggregation, ")")
123 | } else if (period == "diff") {
124 | res$term = "difference (post vs pre mean)"
125 | } else {
126 | res$term = paste0(names(idx), "-treatment (", aggregation, ")")
127 | }
128 | }
129 |
130 | ## Remove "hypothesis" column and rather save as an attribute
131 | res$hypothesis = NULL
132 | attr(res, "hypothesis") = hstring
133 |
134 | return(res)
135 | }
136 |
137 | gen_hstring = function(coefs, periods = NULL, aggregation = "mean", rhs = 0) {
138 | if (is.null(periods)) periods = 1:length(coefs)
139 | coefs = coefs[periods]
140 | hstring = paste(paste0("`", coefs, "`"), collapse = " + ")
141 | if (aggregation == "mean") hstring = paste0("(", hstring, ")/", length(coefs))
142 | if (!is.null(rhs)) hstring = paste(hstring, "=", rhs)
143 | return(hstring)
144 | }
145 |
--------------------------------------------------------------------------------
/R/utils.R:
--------------------------------------------------------------------------------
1 | #' @keywords internal
2 | oxford = function(some_vec) {
3 | if (length(some_vec)>2) {
4 | paste0("[", paste(utils::head(some_vec, -1), collapse = ", "), ", & ", utils::tail(some_vec, 1), "]")
5 | } else if (length(some_vec)==2) {
6 | paste0("[", some_vec[1], " & ", some_vec[2], "]")
7 | } else {
8 | paste(some_vec)
9 | }
10 | }
11 |
12 | ##
13 | ## Unexported functions borrowed from fixest
14 |
15 | #' @keywords internal
16 | escape_regex = function(x){
17 | # escape special characters in regular expressions => to make it as "fixed"
18 |
19 | res = gsub("((?<=[^\\\\])|(?<=^))(\\$|\\.|\\+|\\(|\\)|\\[|\\]|\\?|\\^)", "\\\\\\2", x, perl = TRUE)
20 | res
21 | }
22 |
23 |
24 | #' @keywords internal
25 | dict_apply = function(x, dict = NULL){
26 |
27 | dreamerr::check_arg(dict, "NULL named character vector no na", .message = "The argument `dict` must be a dictionnary, ie a named vector (eg dict=c(\"old_name\"=\"new_name\")")
28 |
29 | if(missing(dict) || length(dict) == 0){
30 | return(x)
31 | }
32 |
33 | # We make the dictionary names space agnostic, adds a handful of us only
34 | if(any(grepl(" ", x, fixed = TRUE))){
35 | x_tmp = gsub(" ", "", x, fixed = TRUE)
36 | } else {
37 | x_tmp = x
38 | }
39 |
40 | if(any(grepl(" ", names(dict), fixed = TRUE))){
41 | names(dict) = gsub(" ", "", names(dict), fixed = TRUE)
42 | if(anyDuplicated(names(dict))){
43 | dup = duplicated(names(dict))
44 | stop("The dictionary `dict` contains several items with the same names, it concerns ",
45 | dreamerr::enumerate_items(names(dict)[dup]), " (note that spaces are ignored).")
46 | }
47 | }
48 |
49 | who = x_tmp %in% names(dict)
50 | x[who] = dict[as.character(x_tmp[who])]
51 | x
52 | }
53 |
54 |
55 | #' @keywords internal
56 | replace_and_make_callable = function(text, varlist, text_as_expr = FALSE){
57 | # used to make a character string callable and substitutes variables inside text with the variables
58 | # in varlist
59 | # ex: "Interacted with __var__" becomes "Interacted with x_beta"
60 | # or: "&paste(\"Interacted with \", x[beta])"
61 |
62 | if(length(text) > 1) stop("Internal problem: length of text should not be greater than 1.")
63 |
64 | text_split = strsplit(paste0(text, " "), "__")[[1]]
65 |
66 | if(length(text_split) < 3){
67 | # Nothing to be done!
68 | return(text)
69 | } else {
70 | # We need to replace the variables
71 | is_var = seq_along(text_split) %% 2 == 0
72 | my_variables = text_split[is_var]
73 |
74 | if(length(varlist) == 0 || any(!my_variables %in% names(varlist))){
75 | info = "No special variable is available for this estimation."
76 | if(length(varlist) > 0){
77 | info = paste0("In this estimation, the only special variable", dreamerr::enumerate_items(paste0("__", names(varlist), "__"), "s.is.start"), ". ")
78 | }
79 |
80 | # warning(info, enumerate_items(paste0("__", setdiff(my_variables, names(varlist)), "__"), "is"), " not valid, thus ignored.", call. = FALSE)
81 |
82 | return("")
83 |
84 | not_var = !my_variables %in% names(varlist)
85 | is_var[is_var][not_var] = FALSE
86 | my_variables = intersect(my_variables, names(varlist))
87 | if(length(my_variables) == 0){
88 | return(text)
89 | }
90 | }
91 |
92 | my_variables_values = varlist[my_variables]
93 |
94 | if(any(lengths(varlist[my_variables]) > 1)){
95 | qui = which(lengths(varlist[my_variables]) > 1)[1]
96 | n_val = lengths(varlist[my_variables])[qui]
97 | warning("The special variable __", my_variables[qui], "__ takes ", n_val, " values, only the first is used.", call. = FALSE)
98 | my_variables_values = sapply(my_variables_values, function(x) x[1])
99 | } else {
100 | my_variables_values = unlist(my_variables_values)
101 | }
102 |
103 | # we prepare the result (we drop the last space)
104 | n = length(text_split)
105 | text_new = text_split
106 | text_new[n] = gsub(" $", "", text_new[n])
107 | if(nchar(text_new[n]) == 0){
108 | text_new = text_new[-n]
109 | is_var = is_var[-n]
110 | }
111 |
112 |
113 | # Do the variables contain expressions?
114 | is_expr = grepl("^&", my_variables_values)
115 | if(any(is_expr)){
116 |
117 | expr_drop = function(x){
118 | if(grepl("^&", x)){
119 | res = gsub("^&", "", x)
120 | if(grepl("^expression\\(", res)){
121 | res = gsub("(^expression\\()|(\\)$)", "", res)
122 | } else if(grepl("^substitute\\(", res)){
123 | res = deparse(eval(str2lang(res)))
124 | }
125 | } else {
126 | res = x
127 | }
128 | res
129 | }
130 |
131 | my_vars = sapply(my_variables_values, expr_drop)
132 |
133 | if(text_as_expr){
134 | text_new[is_var][is_expr] = my_vars[is_expr]
135 |
136 | if(all(is_expr)){
137 | text_new = paste0("&expression(", paste(text_new, collapse = " "), ")")
138 | } else {
139 | my_var_no_expr = paste0('"', my_vars, '"')[!is_expr]
140 | new_names = paste0("x___", seq_along(my_var_no_expr))
141 | text_new[is_var][!is_expr] = new_names
142 | text_new = paste0("&substitute(", paste(text_new, collapse = " "), ", list(", paste0(new_names, "=", my_var_no_expr, collapse = ", "), "))")
143 | }
144 | } else {
145 | text_new = paste0('"', text_new, '"')
146 | text_new[is_var][is_expr] = my_vars[is_expr]
147 |
148 | if(all(is_expr)){
149 | text_new = paste0("&expression(paste(", paste(text_new, collapse = ", "), "))")
150 | } else {
151 | my_var_no_expr = paste0('"', my_vars, '"')[!is_expr]
152 | new_names = paste0("x___", seq_along(my_var_no_expr))
153 | text_new[is_var][!is_expr] = new_names
154 | text_new = paste0("&substitute(paste(", paste(text_new, collapse = ", "), "), list(", paste0(new_names, "=", my_var_no_expr, collapse = ", "), "))")
155 | }
156 |
157 | }
158 |
159 | return(text_new)
160 | } else {
161 | # They don't contain expressions => fine, we just replace with the variables
162 | if(text_as_expr){
163 | my_vars = paste0('"', my_variables_values, '"')
164 | new_names = paste0("x___", seq_along(my_vars))
165 | text_new[is_var] = new_names
166 | text_new = paste0("&substitute(", paste(text_new, collapse = ""), ", list(", paste0(new_names, "=", my_vars, collapse = ", "), "))")
167 | } else {
168 | text_new[is_var] = my_variables_values
169 | text_new = paste(text_new, collapse = "")
170 | }
171 |
172 | return(text_new)
173 | }
174 |
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/README.Rmd:
--------------------------------------------------------------------------------
1 | ---
2 | output: github_document
3 | ---
4 |
5 |
6 |
7 | ```{r, include = FALSE}
8 | knitr::opts_chunk$set(
9 | collapse = TRUE,
10 | comment = "#>",
11 | fig.path = "man/figures/README-",
12 | dpi = 300,
13 | out.width = "100%"
14 | )
15 | ```
16 |
17 | # ggfixest
18 |
19 |
20 | [](https://CRAN.R-project.org/package=ggfixest)
21 | [](https://grantmcdermott.r-universe.dev)
22 | [](https://cran.r-project.org/web/checks/check_results_ggfixest.html)
23 | [](https://github.com/grantmcdermott/ggfixest/actions/workflows/R-CMD-check.yaml)
24 | [](https://grantmcdermott.com/ggfixest/index.html)
25 | [](https://grantmcdermott.com/ggfixest/dev/index.html)
26 |
27 |
28 | The **ggfixest** package provides dedicated **ggplot2** plotting methods for
29 | **fixest** objects. Specifically, it provides drop-in "gg" equivalents of the
30 | latter's [`coefplot`](https://lrberge.github.io/fixest/reference/coefplot.html)
31 | and [`iplot`](https://lrberge.github.io/fixest/reference/coefplot.html)
32 | base plotting functions.
33 |
34 | The goal of **ggfixest** is to produce nice looking coefficient plots and
35 | interaction plots---including
36 | [event study](https://theeffectbook.net/ch-EventStudies.html)
37 | plots---with minimal effort and scope for further customization.
38 |
39 | ## Installation
40 |
41 | The stable version of **ggfixest** is available on CRAN.
42 |
43 | ``` r
44 | install.packages("ggfixest")
45 | ```
46 |
47 | Or, you can grab the latest development version from R-universe.
48 |
49 | ``` r
50 | install.packages("ggfixest", repos = "https://grantmcdermott.r-universe.dev")
51 | ```
52 |
53 | ## Quickstart
54 |
55 | The [package website](https://grantmcdermott.com/ggfixest/)
56 | provides a number of examples in the help documentation. (Also available by
57 | typing `?ggcoefplot` or `?ggiplot` in your R console.) But here are a few
58 | quickstart examples to whet your appetite.
59 |
60 | Start by loading the **ggfixest** package.
61 |
62 | ```{r pkgload}
63 | library(ggfixest)
64 | ```
65 |
66 | Note that this automatically loads **ggplot2** and **fixest** as required
67 | dependencies too. As the package name suggests, **ggfixest** _only_ supports
68 | **fixest** model objects.^[For other model classes, a more generic visualization
69 | package/tool like
70 | [**see**](https://easystats.github.io/see/articles/parameters.html) or
71 | [**modelsummary**](https://modelsummary.com/vignettes/modelplot.html)
72 | would be more appropriate.]
73 |
74 | ### Coefficient plots
75 |
76 | Use `ggcoefplot` to draw basic coefficient plots.
77 |
78 | ```{r coefplot1, message=FALSE}
79 | est = feols(
80 | Petal.Length ~ Petal.Width + Sepal.Length + Sepal.Width + Species,
81 | data = iris
82 | )
83 |
84 | # coefplot(est) ## base version
85 | ggcoefplot(est) ## this package
86 | ```
87 |
88 | The above plot call and output should look very familiar to regular **fixest**
89 | users. Like its base equivalent, `ggcoefplot` can be heavily customized and
90 | contains various shortcuts for common operations. For example, we can use regex
91 | to control the coefficient grouping logic.
92 |
93 | ```{r coefplot2, message=FALSE}
94 | ggcoefplot(est, group = list(Sepal = "^^Sepal.", Species = "^^Species"))
95 | ```
96 |
97 | ### Event study plots
98 |
99 | The `ggiplot` function is a special case of `ggocoefplot` that only plots
100 | coefficients with factor levels or interactions (specifically, those created
101 | with the [`i`](https://lrberge.github.io/fixest/reference/i.html)
102 | operator). This is especially useful for producing event study plots in a
103 | difference-in-differences (DiD) setup.
104 |
105 | ```{r es1, message=FALSE}
106 | est_did = feols(y ~ x1 + i(period, treat, 5) | id+period, base_did)
107 |
108 | # iplot(est_did) ## base version
109 | ggiplot(est_did) ## this package
110 | ```
111 |
112 | Again, the above plot call and output should look very familiar to regular
113 | **fixest** users. But note that `ggiplot` supports several features that are
114 | not available in the base `iplot` version. For example, plotting multiple
115 | confidence intervals and aggregate treatments effects.
116 |
117 | ```{r es2}
118 | ggiplot(
119 | est_did,
120 | ci_level = c(.8, .95),
121 | aggr_eff = "post", aggr_eff.par = list(col = "orange")
122 | )
123 |
124 | ```
125 |
126 | And you can get quite fancy, combining lists of complex multiple estimation
127 | objects with custom themes, and so on.
128 |
129 | ```{r es3}
130 | base_stagg_grp = base_stagg
131 | base_stagg_grp$grp = ifelse(base_stagg_grp$id %% 2 == 0, 'Evens', 'Odds')
132 |
133 | est_twfe_grp = feols(
134 | y ~ x1 + i(time_to_treatment, treated, ref = c(-1, -1000)) | id + year,
135 | data = base_stagg_grp, split = ~grp
136 | )
137 |
138 | est_sa20_grp = feols(
139 | y ~ x1 + sunab(year_treated, year) | id + year,
140 | data = base_stagg_grp, split = ~grp
141 | )
142 |
143 | ggiplot(
144 | list("TWFE" = est_twfe_grp, "Sun & Abraham (2020)" = est_sa20_grp),
145 | ref.line = -1,
146 | main = "Staggered treatment: Split mutli-sample",
147 | xlab = "Time to treatment",
148 | multi_style = "facet",
149 | geom_style = "ribbon",
150 | facet_args = list(labeller = labeller(id = \(x) gsub(".*: ", "", x))),
151 | theme = theme_minimal() +
152 | theme(
153 | text = element_text(family = "HersheySans"),
154 | plot.title = element_text(hjust = 0.5),
155 | legend.position = "none"
156 | )
157 | )
158 | ```
159 |
160 | For more `ggiplot` examples and comparisons with its base counterpart, see the
161 | detailed [vignette](http://grantmcdermott.com/ggfixest/articles/ggiplot.html) on
162 | the package homepage (or, by typing `vignette("ggiplot")` in your R console).
163 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # ggfixest
5 |
6 |
7 |
8 | [](https://CRAN.R-project.org/package=ggfixest)
10 | [](https://grantmcdermott.r-universe.dev)
12 | [](https://cran.r-project.org/web/checks/check_results_ggfixest.html)
14 | [](https://github.com/grantmcdermott/ggfixest/actions/workflows/R-CMD-check.yaml)
15 | [](https://grantmcdermott.com/ggfixest/index.html)
16 | [](https://grantmcdermott.com/ggfixest/dev/index.html)
17 |
18 |
19 | The **ggfixest** package provides dedicated **ggplot2** plotting methods
20 | for **fixest** objects. Specifically, it provides drop-in “gg”
21 | equivalents of the latter’s
22 | [`coefplot`](https://lrberge.github.io/fixest/reference/coefplot.html)
23 | and [`iplot`](https://lrberge.github.io/fixest/reference/coefplot.html)
24 | base plotting functions.
25 |
26 | The goal of **ggfixest** is to produce nice looking coefficient plots
27 | and interaction plots—including [event
28 | study](https://theeffectbook.net/ch-EventStudies.html) plots—with
29 | minimal effort and scope for further customization.
30 |
31 | ## Installation
32 |
33 | The stable version of **ggfixest** is available on CRAN.
34 |
35 | ``` r
36 | install.packages("ggfixest")
37 | ```
38 |
39 | Or, you can grab the latest development version from R-universe.
40 |
41 | ``` r
42 | install.packages("ggfixest", repos = "https://grantmcdermott.r-universe.dev")
43 | ```
44 |
45 | ## Quickstart
46 |
47 | The [package website](https://grantmcdermott.com/ggfixest/) provides a
48 | number of examples in the help documentation. (Also available by typing
49 | `?ggcoefplot` or `?ggiplot` in your R console.) But here are a few
50 | quickstart examples to whet your appetite.
51 |
52 | Start by loading the **ggfixest** package.
53 |
54 | ``` r
55 | library(ggfixest)
56 | #> Loading required package: ggplot2
57 | #> Loading required package: fixest
58 | ```
59 |
60 | Note that this automatically loads **ggplot2** and **fixest** as
61 | required dependencies too. As the package name suggests, **ggfixest**
62 | *only* supports **fixest** model objects.[^1]
63 |
64 | ### Coefficient plots
65 |
66 | Use `ggcoefplot` to draw basic coefficient plots.
67 |
68 | ``` r
69 | est = feols(
70 | Petal.Length ~ Petal.Width + Sepal.Length + Sepal.Width + Species,
71 | data = iris
72 | )
73 |
74 | # coefplot(est) ## base version
75 | ggcoefplot(est) ## this package
76 | ```
77 |
78 |
79 |
80 | The above plot call and output should look very familiar to regular
81 | **fixest** users. Like its base equivalent, `ggcoefplot` can be heavily
82 | customized and contains various shortcuts for common operations. For
83 | example, we can use regex to control the coefficient grouping logic.
84 |
85 | ``` r
86 | ggcoefplot(est, group = list(Sepal = "^^Sepal.", Species = "^^Species"))
87 | ```
88 |
89 |
90 |
91 | ### Event study plots
92 |
93 | The `ggiplot` function is a special case of `ggocoefplot` that only
94 | plots coefficients with factor levels or interactions (specifically,
95 | those created with the
96 | [`i`](https://lrberge.github.io/fixest/reference/i.html) operator). This
97 | is especially useful for producing event study plots in a
98 | difference-in-differences (DiD) setup.
99 |
100 | ``` r
101 | est_did = feols(y ~ x1 + i(period, treat, 5) | id+period, base_did)
102 |
103 | # iplot(est_did) ## base version
104 | ggiplot(est_did) ## this package
105 | ```
106 |
107 |
108 |
109 | Again, the above plot call and output should look very familiar to
110 | regular **fixest** users. But note that `ggiplot` supports several
111 | features that are not available in the base `iplot` version. For
112 | example, plotting multiple confidence intervals and aggregate treatments
113 | effects.
114 |
115 | ``` r
116 | ggiplot(
117 | est_did,
118 | ci_level = c(.8, .95),
119 | aggr_eff = "post", aggr_eff.par = list(col = "orange")
120 | )
121 | ```
122 |
123 |
124 |
125 | And you can get quite fancy, combining lists of complex multiple
126 | estimation objects with custom themes, and so on.
127 |
128 | ``` r
129 | base_stagg_grp = base_stagg
130 | base_stagg_grp$grp = ifelse(base_stagg_grp$id %% 2 == 0, 'Evens', 'Odds')
131 |
132 | est_twfe_grp = feols(
133 | y ~ x1 + i(time_to_treatment, treated, ref = c(-1, -1000)) | id + year,
134 | data = base_stagg_grp, split = ~grp
135 | )
136 |
137 | est_sa20_grp = feols(
138 | y ~ x1 + sunab(year_treated, year) | id + year,
139 | data = base_stagg_grp, split = ~grp
140 | )
141 |
142 | ggiplot(
143 | list("TWFE" = est_twfe_grp, "Sun & Abraham (2020)" = est_sa20_grp),
144 | ref.line = -1,
145 | main = "Staggered treatment: Split mutli-sample",
146 | xlab = "Time to treatment",
147 | multi_style = "facet",
148 | geom_style = "ribbon",
149 | facet_args = list(labeller = labeller(id = \(x) gsub(".*: ", "", x))),
150 | theme = theme_minimal() +
151 | theme(
152 | text = element_text(family = "HersheySans"),
153 | plot.title = element_text(hjust = 0.5),
154 | legend.position = "none"
155 | )
156 | )
157 | ```
158 |
159 |
160 |
161 | For more `ggiplot` examples and comparisons with its base counterpart,
162 | see the detailed
163 | [vignette](http://grantmcdermott.com/ggfixest/articles/ggiplot.html) on
164 | the package homepage (or, by typing `vignette("ggiplot")` in your R
165 | console).
166 |
167 | [^1]: For other model classes, a more generic visualization package/tool
168 | like
169 | [**see**](https://easystats.github.io/see/articles/parameters.html)
170 | or
171 | [**modelsummary**](https://modelsummary.com/vignettes/modelplot.html)
172 | would be more appropriate.
173 |
--------------------------------------------------------------------------------
/_pkgdown.yml:
--------------------------------------------------------------------------------
1 | url: https://grantmcdermott.com/ggiplot/
2 |
3 | home:
4 | title: Dedicated 'ggplot2' Methods for 'fixest' Objects
5 |
6 | template:
7 | bootstrap: 5
8 |
9 | development:
10 | mode: auto
11 |
12 | navbar:
13 | title: ~
14 | type: default
15 | left:
16 | - text: Tutorials
17 | href: articles/index.html
18 | menu:
19 | - text: Comparing ggiplot to iplot
20 | href: articles/ggiplot.html
21 | - text: Functions
22 | href: reference/index.html
23 | - text: News
24 | href: news/index.html
25 |
26 | reference:
27 | - subtitle: Plotting
28 | desc: >
29 | The main user-facing functions of the package.
30 | contents:
31 | - ggcoefplot
32 | - subtitle: Utilities
33 | desc: >
34 | Helper functions that are used as inputs to the main plotting functions.
35 | contents:
36 | - aggr_es
37 | - iplot_data
38 |
--------------------------------------------------------------------------------
/cran-comments.md:
--------------------------------------------------------------------------------
1 | ## Overview
2 |
3 | `ggfixest` 0.3.0 is a minor update that address some CRAN errors due to a
4 | dependency change.
5 |
6 | ## Test environments
7 |
8 | * Local: Arch Linux
9 | * GitHub Actions (ubuntu-22.04): oldrel-1, release, devel
10 | * Win Builder
11 |
12 | ## R CMD check results
13 |
14 | 0 errors | 0 warnings | 0 notes
15 |
--------------------------------------------------------------------------------
/ggfixest.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 | ProjectId: cdb8efca-dc0f-40de-876c-0a55b5e57744
3 |
4 | RestoreWorkspace: No
5 | SaveWorkspace: No
6 | AlwaysSaveHistory: Default
7 |
8 | EnableCodeIndexing: Yes
9 | UseSpacesForTab: No
10 | NumSpacesForTab: 2
11 | Encoding: UTF-8
12 |
13 | RnwWeave: Sweave
14 | LaTeX: XeLaTeX
15 |
16 | AutoAppendNewline: Yes
17 | StripTrailingWhitespace: Yes
18 | LineEndingConversion: Posix
19 |
20 | BuildType: Package
21 | PackageUseDevtools: Yes
22 | PackageInstallArgs: --no-multiarch --with-keep.source
23 | PackageRoxygenize: rd,collate,namespace
24 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggcoefplot_did.svg:
--------------------------------------------------------------------------------
1 |
2 |
93 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggcoefplot_did_iid.svg:
--------------------------------------------------------------------------------
1 |
2 |
93 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggcoefplot_group_names_prefix.svg:
--------------------------------------------------------------------------------
1 |
2 |
83 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggcoefplot_group_nonames.svg:
--------------------------------------------------------------------------------
1 |
2 |
83 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggcoefplot_group_none.svg:
--------------------------------------------------------------------------------
1 |
2 |
79 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggcoefplot_groupnames.svg:
--------------------------------------------------------------------------------
1 |
2 |
83 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggcoefplot_interactions.svg:
--------------------------------------------------------------------------------
1 |
2 |
79 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggcoefplot_interactions_multici.svg:
--------------------------------------------------------------------------------
1 |
2 |
99 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggcoefplot_multi.svg:
--------------------------------------------------------------------------------
1 |
2 |
89 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggcoefplot_simple.svg:
--------------------------------------------------------------------------------
1 |
2 |
72 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggiplot_list.svg:
--------------------------------------------------------------------------------
1 |
2 |
89 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggiplot_multi_csw.svg:
--------------------------------------------------------------------------------
1 |
2 |
81 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggiplot_multi_csw_facet.svg:
--------------------------------------------------------------------------------
1 |
2 |
120 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggiplot_multi_lhs.svg:
--------------------------------------------------------------------------------
1 |
2 |
79 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggiplot_multi_lhs_csw.svg:
--------------------------------------------------------------------------------
1 |
2 |
100 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggiplot_multi_lhs_facet.svg:
--------------------------------------------------------------------------------
1 |
2 |
118 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggiplot_simple.svg:
--------------------------------------------------------------------------------
1 |
2 |
83 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggiplot_simple_errorbar.svg:
--------------------------------------------------------------------------------
1 |
2 |
103 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggiplot_simple_mci_ribbon.svg:
--------------------------------------------------------------------------------
1 |
2 |
71 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggiplot_simple_ribbon.svg:
--------------------------------------------------------------------------------
1 |
2 |
67 |
--------------------------------------------------------------------------------
/inst/tinytest/_tinysnapshot/ggiplot_stagg_mci_ribbon.svg:
--------------------------------------------------------------------------------
1 |
2 |
79 |
--------------------------------------------------------------------------------
/inst/tinytest/test_aggr_es.R:
--------------------------------------------------------------------------------
1 | library(ggfixest)
2 | library(tinytest)
3 |
4 | #
5 | # Datasets and models ----
6 |
7 | data("base_did", package = "fixest")
8 |
9 | est = fixest::feols(
10 | fml = y ~ x1 + i(period, treat, 5) | id + period,
11 | data = base_did
12 | )
13 |
14 | aggr_post = aggr_es(est) # default post
15 | aggr_cum = aggr_es(est, aggregation = "cumulative") # cumulative instead of mean effects
16 | aggr_pre = aggr_es(est, period = "pre") # pre period instead of post
17 | aggr_both = aggr_es(est, period = "both") # pre & post periods separately
18 | aggr_diff = aggr_es(est, period = "diff") # difference (post vs pre)
19 | aggr_rhs1 = aggr_es(est, period = "pre", rhs = -1) # pre period with H0 value of 1
20 |
21 | #
22 | # Known output ----
23 |
24 | aggr_post_known = data.frame(
25 | term = "post-treatment (mean)",
26 | estimate = 3.906554122950695,
27 | std.error = 0.8598575665281263,
28 | statistic = 4.543257249830702,
29 | p.value = 5.5391585244779775e-06,
30 | s.value = 17.461901741925015,
31 | conf.low = 2.2212642607213144,
32 | conf.high = 5.591843985180075
33 | )
34 |
35 |
36 | aggr_cum_known = data.frame(
37 | term = "post-treatment (cumulative)",
38 | estimate = 19.532770614753474,
39 | std.error = 4.299287821377354,
40 | statistic = 4.543257261733131,
41 | p.value = 5.539158211582727e-06,
42 | s.value = 17.461901823419783,
43 | conf.low = 11.106321325682188,
44 | conf.high = 27.95921990382476
45 | )
46 |
47 | aggr_pre_known = data.frame(
48 | term = "pre-treatment (mean)",
49 | estimate = -1.1798706992411545,
50 | std.error = 0.8561963882056884,
51 | statistic = -1.3780374637106132,
52 | p.value = 0.16819172163573184,
53 | s.value = 2.5718213967199377,
54 | conf.low = -2.857984783817578,
55 | conf.high = 0.49824338533526924
56 | )
57 |
58 | aggr_both_known = data.frame(
59 | term = c("pre-treatment (mean)", "post-treatment (mean)"),
60 | estimate = c(-1.1798706992411545, 3.906554122950695),
61 | std.error = c(0.8561963882056884, 0.8598575665281263),
62 | statistic = c(-1.3780374637106132, 4.543257249830702),
63 | p.value = c(0.16819172163573184, 5.5391585244779775e-06),
64 | s.value = c(2.5718213967199377, 17.461901741925015),
65 | conf.low = c(-2.857984783817578, 2.2212642607213144),
66 | conf.high = c(0.49824338533526924, 5.591843985180075)
67 | )
68 |
69 | aggr_diff_known = data.frame(
70 | term = "difference (post vs pre mean)",
71 | estimate = 5.08642482219185,
72 | std.error = 0.47207477585529,
73 | statistic = 10.7746168241598,
74 | p.value = 4.53667090889737e-27,
75 | s.value = 87.5104245518892,
76 | conf.low = 4.16117526350566,
77 | conf.high = 6.01167438087804
78 | )
79 |
80 | aggr_rhs1_known = data.frame(
81 | term = "pre-treatment (mean)",
82 | estimate = -0.1798706992411545,
83 | std.error = 0.8561963882056884,
84 | statistic = -0.21008112358206216,
85 | p.value = 0.8336043579365446,
86 | s.value = 0.26256527509602834,
87 | conf.low = -1.8579847838175783,
88 | conf.high = 1.4982433853352692
89 | )
90 |
91 |
92 | #
93 | # tests ----
94 | tol = 1e-4
95 |
96 | for (col in c("term", "estimate", "std.error", "statistic", "p.value", "s.value",
97 | "conf.low", "conf.high")) {
98 | expect_equivalent(aggr_post[[col]], aggr_post_known[[col]], tolerance = tol)
99 | expect_equivalent(aggr_cum[[col]], aggr_cum_known[[col]], tolerance = tol)
100 | expect_equivalent(aggr_pre[[col]], aggr_pre_known[[col]], tolerance = tol)
101 | expect_equivalent(aggr_both[[col]], aggr_both_known[[col]], tolerance = tol)
102 | expect_equivalent(aggr_diff[[col]], aggr_diff_known[[col]], tolerance = tol)
103 | expect_equivalent(aggr_rhs1[[col]], aggr_rhs1_known[[col]], tolerance = tol)
104 | }
105 |
--------------------------------------------------------------------------------
/inst/tinytest/test_fixest_multi.R:
--------------------------------------------------------------------------------
1 | source("tinysnapshot_helpers.R")
2 | using("tinysnapshot")
3 | if (Sys.info()["sysname"] != "Linux") exit_file("Linux snapshots")
4 |
5 | library(ggfixest)
6 |
7 | multi_lhs = feols(c(mpg, wt) ~ i(vs, drat), mtcars)
8 |
9 | p1a = ggiplot(multi_lhs, ref.line = 0.5)
10 | p1b = ggiplot(multi_lhs, multi_style = "facet", ref.line = 0.5)
11 | p1c = ggiplot(multi_lhs, multi_style = "facet", geom_style = "ribbon", ref.line = 0.5)
12 | expect_snapshot_plot(p1a, label = "ggiplot_multi_lhs")
13 | expect_snapshot_plot(p1b, label = "ggiplot_multi_lhs_facet")
14 | expect_snapshot_plot(p1c, label = "ggiplot_multi_lhs_facet_ribbon")
15 |
16 | multi_csw = feols(mpg ~ csw(disp, qsec) + i(vs, drat), mtcars)
17 |
18 | p2a = ggiplot(multi_csw, ref.line = 0.5)
19 | p2b = ggiplot(multi_csw, multi_style = "facet", ref.line = 0.5)
20 | expect_snapshot_plot(p2a, label = "ggiplot_multi_csw")
21 | expect_snapshot_plot(p2b, label = "ggiplot_multi_csw_facet")
22 |
23 | multi_lhs_csw = feols(c(mpg, wt) ~ csw(disp, qsec) + i(vs, drat), mtcars)
24 |
25 | p3a = ggiplot(multi_lhs_csw, ref.line = 0.5)
26 | p3b = ggiplot(multi_lhs_csw, multi_style = "facet", ref.line = 0.5)
27 | expect_snapshot_plot(p3a, label = "ggiplot_multi_lhs_csw")
28 | expect_snapshot_plot(p3b, label = "ggiplot_multi_lhs_csw_facet")
29 |
--------------------------------------------------------------------------------
/inst/tinytest/test_ggcoefplot.R:
--------------------------------------------------------------------------------
1 | source("tinysnapshot_helpers.R")
2 | using("tinysnapshot")
3 | if (Sys.info()["sysname"] != "Linux") exit_file("Linux snapshots")
4 |
5 | library(ggfixest)
6 | library(tinytest)
7 |
8 |
9 | # NB: 1st test runs will fail, but write the targets to file. 2nd run(s) should
10 | # pass.
11 |
12 | #
13 | ## Simple ggcoeflot ----
14 |
15 | est = feols(Petal.Length ~ Petal.Width + i(Species), iris)
16 | p = ggcoefplot(est)
17 | expect_snapshot_plot(p, label = "ggcoefplot_simple")
18 |
19 | #
20 | ## i-based Interactions ----
21 |
22 | est_i = feols(mpg ~ 0 + i(cyl, wt):disp + i(am, hp), mtcars)
23 | p_i1 = ggcoefplot(est_i)
24 | expect_snapshot_plot(p_i1, label = "ggcoefplot_interactions")
25 | p_i2 = ggcoefplot(est_i, ci_level = c(.8, .95))
26 | expect_snapshot_plot(p_i2, label = "ggcoefplot_interactions_multici")
27 |
28 | #
29 | ## Multi estimation (with interactions) ----
30 |
31 | est_multi = feols(c(mpg, hp) ~ i(cyl, wt), mtcars)
32 | p_multi = ggcoefplot(est_multi)
33 | expect_snapshot_plot(p_multi, label = "ggcoefplot_multi")
34 | p_multi_facet = ggcoefplot(est_multi, multi_style = "facet")
35 | expect_snapshot_plot(p_multi_facet, label = "ggcoefplot_multi_facet")
36 |
37 |
38 | #
39 | ## Grouping ----
40 |
41 | est_grp = feols(Petal.Length ~ Petal.Width + Sepal.Length + Sepal.Width + Species, iris)
42 |
43 | p_grp1 = ggcoefplot(est_grp) # no groups
44 | expect_snapshot_plot(p_grp1, label = "ggcoefplot_group_none")
45 | p_grp2 = ggcoefplot(est_grp, group = list("Sepal", "Species")) # group, no names
46 | expect_snapshot_plot(p_grp2, label = "ggcoefplot_group_nonames")
47 | p_grp3 = ggcoefplot(est_grp, group = list(Sepal = "Sepal", Species = "Species")) # group + names
48 | expect_snapshot_plot(p_grp3, label = "ggcoefplot_groupnames")
49 | p_grp4 = ggcoefplot(est_grp, group = list(Sepal = "^^Sepal.", Species = "^^Species")) # group + ^^names
50 | expect_snapshot_plot(p_grp4, label = "ggcoefplot_group_names_prefix")
51 |
52 | #
53 | ## DiD (mostly to check auto grouping) ----
54 |
55 | data("base_did", package = "fixest")
56 | est_did = feols(y ~ x1 + i(period, treat, 5) | id + period, base_did)
57 | p_did = ggcoefplot(est_did)
58 | expect_snapshot_plot(p_did, label = "ggcoefplot_did")
59 |
60 |
61 | #
62 | ## vcov adjustment (passed through ...) ----
63 |
64 | p_did_iid_summ = ggcoefplot(summary(est_did, vcov = "iid")) # manual approach
65 | p_did_iid = ggcoefplot(est_did, vcov = "iid") # passed through "..."
66 | expect_snapshot_plot(p_did_iid_summ, label = "ggcoefplot_did_iid")
67 | expect_snapshot_plot(p_did_iid, label = "ggcoefplot_did_iid") # should be identical
68 |
69 |
--------------------------------------------------------------------------------
/inst/tinytest/test_ggiplot.R:
--------------------------------------------------------------------------------
1 | source("tinysnapshot_helpers.R")
2 | using("tinysnapshot")
3 | if (Sys.info()["sysname"] != "Linux") exit_file("Linux snapshots")
4 |
5 | library(ggfixest)
6 | library(tinytest)
7 |
8 | #
9 | # Datasets and models ----
10 |
11 | data("base_did", package = "fixest")
12 | data("base_stagg", package = "fixest")
13 | base_stagg_grp = base_stagg
14 | base_stagg_grp$grp = ifelse(base_stagg_grp$id %% 2 == 0, 'Evens', 'Odds')
15 |
16 | est = fixest::feols(
17 | fml = y ~ x1 + i(period, treat, 5) | id + period,
18 | data = base_did
19 | )
20 |
21 | est_twfe = fixest::feols(
22 | y ~ x1 + i(time_to_treatment, treated, ref = c(-1, -1000)) | id + year,
23 | base_stagg
24 | )
25 |
26 | est_twfe_grp = fixest::feols(
27 | y ~ x1 + i(time_to_treatment, treated, ref = c(-1, -1000)) | id + year,
28 | base_stagg_grp,
29 | split = ~ grp
30 | )
31 |
32 | est_sa20_grp = fixest::feols(
33 | y ~ x1 + sunab(year_treated, year) | id + year,
34 | base_stagg_grp,
35 | split = ~ grp
36 | )
37 |
38 | # NB: 1st test runs will fail, but write the targets to file. 2nd run(s) should
39 | # pass.
40 |
41 | #
42 | ## Simple ggiplot ----
43 |
44 | p1 = ggiplot(est)
45 | p2 = ggiplot(est, geom_style = "errorbar")
46 | p3 = ggiplot(est, geom_style = "ribbon", pt.pch = NA)
47 | p4 = ggiplot(est, ci_level = c(.8,.95))
48 | p5 = ggiplot(est, ci_level = c(.8,.95), geom_style = 'ribbon', pt.pch = NA)
49 | p6 = ggiplot(list(est))
50 | expect_snapshot_plot(p1, label = "ggiplot_simple")
51 | expect_snapshot_plot(p2, label = "ggiplot_simple_errorbar")
52 | expect_snapshot_plot(p3, label = "ggiplot_simple_ribbon")
53 | expect_snapshot_plot(p4, label = "ggiplot_simple_mci")
54 | expect_snapshot_plot(p5, label = "ggiplot_simple_mci_ribbon")
55 | expect_snapshot_plot(p6, label = "ggiplot_list")
56 |
57 | #
58 | ## Staggered treatment DiD (common use-case) ----
59 |
60 | p7 = ggiplot(
61 | est_twfe,
62 | main = 'Staggered treatment', ref.line = -1, pt.join = TRUE,
63 | ci_level = c(.8,.95)
64 | )
65 | p8 = ggiplot(
66 | list('TWFE' = est_twfe),
67 | main = 'Staggered treatment', ref.line = -1, pt.join = TRUE,
68 | geom_style = "ribbon", pt.pch = NA,
69 | ci_level = c(.8,.95)
70 | )
71 | expect_snapshot_plot(p7, label = "ggiplot_stagg_mci")
72 | expect_snapshot_plot(p8, label = "ggiplot_stagg_mci_ribbon")
73 |
74 | #
75 | # Multi plots (single panel) ----
76 |
77 | est_sa20 = fixest::feols(
78 | y ~ x1 + sunab(year_treated, year) | id + year,
79 | base_stagg
80 | )
81 |
82 | p9 = ggiplot(
83 | list('TWFE' = est_twfe, 'Sun & Abraham (2020)' = est_sa20)
84 | )
85 | p10 = ggiplot(
86 | list(est_twfe, est_sa20)
87 | )
88 | p11 = ggiplot(
89 | list('TWFE' = est_twfe, 'Sun & Abraham (2020)' = est_sa20),
90 | geom_style = "ribbon"
91 | )
92 | p12 = ggiplot(
93 | list('TWFE' = est_twfe, 'Sun & Abraham (2020)' = est_sa20),
94 | main = 'Staggered treatment', ref.line = -1, pt.join = TRUE,
95 | ci_level = c(.8, .95)
96 | )
97 | p13 = ggiplot(
98 | list('TWFE' = est_twfe, 'Sun & Abraham (2020)' = est_sa20),
99 | main = 'Staggered treatment', ref.line = -1, pt.join = TRUE,
100 | ci_level = c(.8, .95), geom_style = 'ribbon'
101 | )
102 | expect_snapshot_plot(p9, label = "ggiplot_multi_single")
103 | expect_snapshot_plot(p10, label = "ggiplot_multi_single_unnamed")
104 | expect_snapshot_plot(p11, label = "ggiplot_multi_single_ribbon")
105 | expect_snapshot_plot(p12, label = "ggiplot_multi_single_kitchen")
106 | expect_snapshot_plot(p13, label = "ggiplot_multi_single_kitchen_ribbon")
107 |
108 | #
109 | # Multi plots (facetted) ----
110 |
111 | p14 = ggiplot(
112 | list('TWFE' = est_twfe, 'Sun & Abraham (2020)' = est_sa20),
113 | main = 'Staggered treatment', ref.line = -1, pt.join = TRUE,
114 | ci_level = c(.8, .95), multi_style = 'facet'
115 | )
116 | p15 = ggiplot(
117 | list('TWFE' = est_twfe, 'Sun & Abraham (2020)' = est_sa20),
118 | main = 'Staggered treatment', ref.line = -1, pt.join = TRUE,
119 | ci_level = c(.8, .95), multi_style = 'facet',
120 | geom_style = 'ribbon'
121 | )
122 | expect_snapshot_plot(p14, label = "ggiplot_multi_facet")
123 | expect_snapshot_plot(p15, label = "ggiplot_multi_facet_ribbon")
124 |
125 | #
126 | # Multi plots and split samples (complex) ----
127 |
128 | p16 = ggiplot(
129 | list('TWFE' = est_twfe_grp, 'Sun & Abraham (2020)' = est_sa20_grp),
130 | main = 'Staggered treatment: Split mutli-sample',
131 | ref.line = -1,
132 | pt.join = TRUE)
133 | p17 = ggiplot(
134 | list('TWFE' = est_twfe_grp, 'Sun & Abraham (2020)' = est_sa20_grp),
135 | main = 'Staggered treatment: Split mutli-sample',
136 | ref.line = -1, pt.join = TRUE,
137 | ci_level = c(.8, .95)
138 | )
139 | p18 = ggiplot(
140 | list('TWFE' = est_twfe_grp, 'Sun & Abraham (2020)' = est_sa20_grp),
141 | main = 'Staggered treatment: Split mutli-sample',
142 | ref.line = -1,
143 | xlab = 'Time to treatment',
144 | multi_style = 'facet',
145 | geom_style = 'ribbon',
146 | ci_level = c(.8, .95),
147 | theme = theme_minimal() + theme(
148 | text = element_text(family = 'HersheySans'),
149 | plot.title = element_text(hjust = 0.5),
150 | legend.position = 'none'
151 | )
152 | )
153 | expect_snapshot_plot(p16, label = "ggiplot_multi_complex")
154 | expect_snapshot_plot(p17, label = "ggiplot_multi_complex_mci")
155 | expect_snapshot_plot(p18, label = "ggiplot_multi_complex_kitchen")
156 |
157 | # Last one(s): Check vcov adjustment of previous plot
158 | # Manual version, passing via summary first...
159 | p19a = ggiplot(
160 | list(
161 | 'TWFE' = summary(est_twfe_grp, vcov = "iid"),
162 | 'Sun & Abraham (2020)' = summary(est_sa20_grp, vcov = "iid")
163 | ),
164 | main = 'Staggered treatment: Split mutli-sample',
165 | ref.line = -1,
166 | xlab = 'Time to treatment',
167 | multi_style = 'facet',
168 | geom_style = 'ribbon',
169 | ci_level = c(.8, .95),
170 | theme = theme_minimal() + theme(
171 | text = element_text(family = 'HersheySans'),
172 | plot.title = element_text(hjust = 0.5),
173 | legend.position = 'none'
174 | )
175 | )
176 | # Next, passing as a convenience string (via ...)
177 | p19b = ggiplot(
178 | list('TWFE' = est_twfe_grp, 'Sun & Abraham (2020)' = est_sa20_grp),
179 | vcov = "iid",
180 | main = 'Staggered treatment: Split mutli-sample',
181 | ref.line = -1,
182 | xlab = 'Time to treatment',
183 | multi_style = 'facet',
184 | geom_style = 'ribbon',
185 | ci_level = c(.8, .95),
186 | theme = theme_minimal() + theme(
187 | text = element_text(family = 'HersheySans'),
188 | plot.title = element_text(hjust = 0.5),
189 | legend.position = 'none'
190 | )
191 | )
192 | expect_snapshot_plot(p19a, label = "ggiplot_multi_complex_kitchen_iid")
193 | expect_snapshot_plot(p19b, label = "ggiplot_multi_complex_kitchen_iid")
194 |
195 | #
196 | # Making sure pt.join works with sw() and is not sensitive to ordering
197 |
198 | # https://github.com/grantmcdermott/ggfixest/issues/40#issuecomment-2444436162
199 | base_did$y2 = base_did$y*1.5
200 | base_did$y3 = base_did$y*2
201 |
202 | m20a = fixest::feols(y ~ x1 + i(period, treat, 5) | sw0(period, id), base_did)
203 | m20b = fixest::feols(y ~ x1 + i(period, treat, 5) | sw0(id, period), base_did)
204 | p20a = ggiplot(m20a, pt.join = TRUE)
205 | p20b = ggiplot(m20b, pt.join = TRUE)
206 | expect_snapshot_plot(p20a, label = "ggiplot_multi_sw_pt_join1")
207 | expect_snapshot_plot(p20b, label = "ggiplot_multi_sw_pt_join2")
208 |
--------------------------------------------------------------------------------
/inst/tinytest/test_iplot_data.R:
--------------------------------------------------------------------------------
1 | library(ggfixest)
2 | library(tinytest)
3 |
4 | #
5 | # Datasets and models ----
6 |
7 | data("base_did", package = "fixest")
8 |
9 | est = fixest::feols(
10 | fml = y ~ x1 + i(period, treat, 5) | id + period,
11 | data = base_did
12 | )
13 |
14 | est_log = fixest::feols(
15 | fml = log(y) ~ x1 + i(period, treat, 5) | id + period,
16 | data = base_did,
17 | subset = ~ y >= 0
18 | )
19 |
20 |
21 | iplot_data_est = iplot_data(est)
22 | iplot_data_est_log = iplot_data(est_log)
23 |
24 | #
25 | # Known output ----
26 |
27 | # est |> iplot_data() |> dput()
28 | iplot_data_est_known = structure(
29 | list(
30 | estimate = c(-1.40304548628387, -1.24751078444385, -0.273205962244887, -1.79572056399201, 0, 0.784452028314083, 3.59889733159794, 3.81176621201594, 4.73142620090947, 6.60622884191604 ),
31 | ci_low = c(-3.60401957599237, -3.41454238816103, -2.46757561261468, -3.95250166364274, 0, -1.25420738946099, 1.41517633761945, 1.33873906080257, 2.55652832963455, 4.38497957631853),
32 | ci_high = c(0.79792860342463, 0.919520819273338, 1.92116368812491, 0.361060535658718, 0, 2.82311144608915, 5.78261832557643, 6.28479336322932, 6.9063240721844, 8.82747810751355),
33 | estimate_names = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
34 | estimate_names_raw = c("period::1:treat", "period::2:treat", "period::3:treat", "period::4:treat", "period::5:treat", "period::6:treat", "period::7:treat", "period::8:treat", "period::9:treat", "period::10:treat"),
35 | is_ref = c(FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE),
36 | x = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
37 | id = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
38 | y = c(-1.40304548628387, -1.24751078444385, -0.273205962244887, -1.79572056399201, 0, 0.784452028314083, 3.59889733159794, 3.81176621201594, 4.73142620090947, 6.60622884191604),
39 | lhs = c("y", "y", "y", "y", "y", "y", "y", "y", "y", "y"),
40 | ci_level = c(0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95)
41 | ),
42 | row.names = c("1", "2", "3", "4", "5", "6", "7", "8", "9", "10"),
43 | class = "data.frame"
44 | )
45 |
46 |
47 | # est_log |> iplot_data() |> dput()
48 | iplot_data_est_log_known = structure(
49 | list(
50 | estimate = c(0.141945718569522, -0.417790820616548, 0.429214017066127, -0.224501302353741, 0, 0.521077110055194, 0.929997803535568, 0.605730868274549, 1.00494204591818, 1.17999113089822),
51 | ci_low = c(-0.753702664910143, -1.45184513904486, -0.51466249593292, -1.14227560283322, 0, -0.324231108616199, 0.166487349184158, -0.184924971478124, 0.250280645886709, 0.34075563112618),
52 | ci_high = c(1.03759410204919, 0.616263497811766, 1.37309053006517, 0.693272998125739, 0, 1.36638532872659, 1.69350825788698, 1.39638670802722, 1.75960344594965, 2.01922663067026),
53 | estimate_names = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
54 | estimate_names_raw = c("period::1:treat", "period::2:treat", "period::3:treat", "period::4:treat", "period::5:treat", "period::6:treat", "period::7:treat", "period::8:treat", "period::9:treat", "period::10:treat"),
55 | is_ref = c(FALSE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, FALSE, FALSE, FALSE),
56 | x = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
57 | id = c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1), y = c(0.141945718569522, -0.417790820616548, 0.429214017066127, -0.224501302353741, 0, 0.521077110055194, 0.929997803535568, 0.605730868274549, 1.00494204591818, 1.17999113089822),
58 | lhs = c("log(y)", "log(y)", "log(y)", "log(y)", "log(y)", "log(y)", "log(y)", "log(y)", "log(y)", "log(y)"),
59 | ci_level = c(0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95)
60 | ),
61 | row.names = c("1", "2", "3", "4", "5", "6", "7", "8", "9", "10"),
62 | class = "data.frame"
63 | )
64 |
65 | #
66 | # tests ----
67 | tol = 1e-6
68 |
69 | for (col in c("estimate", "ci_low", "ci_high", "estimate_names",
70 | "estimate_names_raw", "is_ref", "x", "id", "y", "lhs", "ci_level")) {
71 | expect_equivalent(iplot_data_est[[col]], iplot_data_est_known[[col]], tolerance = tol)
72 | expect_equivalent(iplot_data_est_log[[col]], iplot_data_est_log_known[[col]], tolerance = tol)
73 | }
74 |
--------------------------------------------------------------------------------
/inst/tinytest/test_nthreads.R:
--------------------------------------------------------------------------------
1 | library(tinytest)
2 |
3 | exit_if_not(any(grepl("_R_CHECK", names(Sys.getenv()), fixed = TRUE)))
4 |
5 | # fixest
6 | if ( requireNamespace("fixest", quietly=TRUE) ){
7 | library(fixest)
8 | nfx = getFixest_nthreads()
9 | expect_equal(nfx, 1, info = "Check fixest threads")
10 | }
11 |
12 | # data.table
13 | if (requireNamespace("data.table", quietly = TRUE)) {
14 | library(data.table)
15 | nDT = getDTthreads()
16 | expect_equal(nDT, 1, info = "Check data.table threads")
17 | }
18 |
19 | # magick
20 | if (requireNamespace("magick", quietly = TRUE)) {
21 | library(magick)
22 | nmg = magick:::magick_threads(1)
23 | expect_equal(nmg, 1, info = "Check magick threads")
24 | }
25 |
--------------------------------------------------------------------------------
/inst/tinytest/tinysnapshot_helpers.R:
--------------------------------------------------------------------------------
1 | library(tinytest)
2 | library(tinysnapshot)
3 | options("tinysnapshot_os" = "Linux")
4 | options("tinysnapshot_device" = "svglite")
5 | options("tinysnapshot_device_args" = list(user_fonts = fontquiver::font_families("Liberation")))
6 |
--------------------------------------------------------------------------------
/man/aggr_es.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/aggr_es.R
3 | \name{aggr_es}
4 | \alias{aggr_es}
5 | \title{Aggregates event-study treatment effects.}
6 | \usage{
7 | aggr_es(
8 | object,
9 | period = c("post", "pre", "both", "diff"),
10 | rhs = 0,
11 | aggregation = c("mean", "cumulative"),
12 | abbr_term = TRUE,
13 | ...
14 | )
15 | }
16 | \arguments{
17 | \item{object}{A model object of class \code{fixest}, where the \code{i()} operator has
18 | been used to facilitate an "event-study" DiD design. See Examples.}
19 |
20 | \item{period}{Keyword string or numeric sequence. Which group of periods
21 | are we aggregating? Accepts the following convenience strings: \code{"post"} (the
22 | default), \code{"pre"}, \code{"both"}, or \verb{"diff} (for the difference between the post
23 | and pre periods). Alternatively, can also be a numeric sequence that
24 | designates an explicit subset of periods in the data (e.g. \code{6:8}).}
25 |
26 | \item{rhs}{Numeric. The null hypothesis value. Defaults to 0.}
27 |
28 | \item{aggregation}{Character string. The aggregation type. Either \code{"mean"}
29 | (the default) or \code{"cumulative"}.}
30 |
31 | \item{abbr_term}{Logical. Should the leading "term" column of the return
32 | data frame be abbreviated? The default is \code{TRUE}. If \code{FALSE}, then the term
33 | column will retain the full hypothesis test string as per usual with
34 | \code{\link[marginaleffects:hypotheses]{marginaleffects::hypotheses()}}. Note that this information is retained as
35 | an attribute of the return object, regardless.}
36 |
37 | \item{...}{Additional arguments passed to \code{\link[marginaleffects:hypotheses]{marginaleffects::hypotheses()}}.}
38 | }
39 | \value{
40 | A "tidy" data frame of aggregated (pre and/or post) treatment
41 | effects, plus inferential information about standard errors, confidence
42 | intervals, etc. Potentially useful information about the underlying
43 | hypothesis test is also provided as an attribute. See Examples.
44 | }
45 | \description{
46 | Aggregates post- (and/or pre-) treatment effects of an
47 | "event-study" estimation, also known as a dynamic difference-in-differences
48 | (DDiD) model. The event-study should have been estimated using the \code{fixest}
49 | package, which provides a specialised \code{i()} operator for this class
50 | of models. By default, the function will return the average post-treatment
51 | effect (i.e., across multiple periods). However, it can also return the
52 | cumulative post-treatment effect and can be used to aggregate pre-treatment
53 | effects too.
54 | }
55 | \examples{
56 | library(ggfixest) ## Will load fixest too
57 |
58 | est = feols(y ~ x1 + i(period, treat, 5) | id + period, base_did)
59 |
60 | # Default hypothesis test is a null mean post-treatment effect
61 | (post_mean = aggr_es(est))
62 |
63 | # The underlying hypothesis is saved as an attribute
64 | attr(post_mean, "hypothesis")
65 |
66 | # Other hypothesis and aggregation options
67 | aggr_es(est, period = "pre") # pre period instead of post
68 | aggr_es(est, period = "both") # pre & post periods separately
69 | aggr_es(est, period = "diff") # post vs pre difference
70 | aggr_es(est, period = 6:8) # specific subset of periods
71 | aggr_es(est, period = "pre", rhs = -1) # pre period with H0 value of 1
72 | aggr_es(est, aggregation = "cumulative") # cumulative instead of mean effects
73 | # Etc.
74 |
75 | }
76 | \seealso{
77 | \code{\link[marginaleffects:hypotheses]{marginaleffects::hypotheses()}}, which this function is ultimately a
78 | convenience wrapper around.
79 | }
80 |
--------------------------------------------------------------------------------
/man/figures/README-coefplot1-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grantmcdermott/ggfixest/3c8d6df3672e6f09af746c826ce49f7d4f2f3031/man/figures/README-coefplot1-1.png
--------------------------------------------------------------------------------
/man/figures/README-coefplot2-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grantmcdermott/ggfixest/3c8d6df3672e6f09af746c826ce49f7d4f2f3031/man/figures/README-coefplot2-1.png
--------------------------------------------------------------------------------
/man/figures/README-es1-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grantmcdermott/ggfixest/3c8d6df3672e6f09af746c826ce49f7d4f2f3031/man/figures/README-es1-1.png
--------------------------------------------------------------------------------
/man/figures/README-es2-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grantmcdermott/ggfixest/3c8d6df3672e6f09af746c826ce49f7d4f2f3031/man/figures/README-es2-1.png
--------------------------------------------------------------------------------
/man/figures/README-es3-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/grantmcdermott/ggfixest/3c8d6df3672e6f09af746c826ce49f7d4f2f3031/man/figures/README-es3-1.png
--------------------------------------------------------------------------------
/man/iplot_data.Rd:
--------------------------------------------------------------------------------
1 | % Generated by roxygen2: do not edit by hand
2 | % Please edit documentation in R/iplot_data.R
3 | \name{iplot_data}
4 | \alias{iplot_data}
5 | \alias{coefplot_data}
6 | \title{Internal function for grabbing and preparing iplot data.}
7 | \usage{
8 | iplot_data(
9 | object,
10 | .ci_level = 0.95,
11 | .keep = NULL,
12 | .drop = NULL,
13 | .dict = fixest::getFixest_dict(),
14 | .internal.only.i = TRUE,
15 | .i.select = 1,
16 | .aggr_es = NULL,
17 | .group = "auto",
18 | .vcov = NULL,
19 | .cluster = NULL,
20 | .se = NULL
21 | )
22 |
23 | coefplot_data(
24 | object,
25 | .ci_level = 0.95,
26 | .keep = NULL,
27 | .drop = NULL,
28 | .group = "auto",
29 | .dict = fixest::getFixest_dict(),
30 | .internal.only.i = FALSE,
31 | .i.select = 1,
32 | .aggr_es = "none",
33 | .vcov = NULL,
34 | .cluster = NULL,
35 | .se = NULL
36 | )
37 | }
38 | \arguments{
39 | \item{object}{A model object of class \code{fixest} or \code{fixest_multi}, where
40 | the \code{i()} operator has been used to construct an interaction, or set of
41 | interactions.}
42 |
43 | \item{.ci_level}{A number between 0 and 1 indicating the desired confidence
44 | level, Defaults to 0.95.}
45 |
46 | \item{.keep}{Character vector used to subset the coefficients of interest.
47 | Passed down to \code{fixest::iplot(..., keep = .keep)} and should take the form of
48 | an acceptable regular expression.}
49 |
50 | \item{.drop}{Character vector used to subset the coefficients of interest
51 | (complement of \code{.keep}). Passed down to \code{fixest::iplot(..., drop = .drop)}
52 | and should take the form of an acceptable regular expression.}
53 |
54 | \item{.dict}{A dictionary (i.e. named character vector or a logical scalar).
55 | Used for changing coefficient names. Defaults to the values in
56 | \code{getFixest_dict()}. See the \code{?fixest::coefplot} documentation for more
57 | information. Note: This argument applies dictionary changes directly to the
58 | return object for \code{coefplot_data}. However, it is ignored for \code{iplot_data},
59 | since we want to preserve the numeric ordering for potential event study
60 | plots. (And imposing an ordered factor would create its own downstream
61 | problems in the case of continuous plot features like ribbons.) Instead, any
62 | dictionary replacement for \code{ggiplot} is deferred to the actual plot call and
63 | applied directly to the labels.}
64 |
65 | \item{.internal.only.i}{Logical variable used for some internal function
66 | handling when passing on to coefplot/iplot.}
67 |
68 | \item{.i.select}{Integer scalar, default is 1. In (gg)iplot, used to select
69 | which variable created with i() to select. Only used when there are several
70 | variables created with i. This is an index, just try increasing numbers to
71 | hopefully obtain what you want. Passed down to
72 | \code{fixest::iplot(..., i.select = .i.select)}}
73 |
74 | \item{.aggr_es}{A keyword string or numeric sequence indicating whether the
75 | aggregated mean treatment effects for some subset of the model should be
76 | added as a column to the returned data frame. Passed to
77 | \code{aggr_es(..., aggregation = "mean")}.}
78 |
79 | \item{.group}{A list, default is missing. Each element of the list reports
80 | the coefficients to be grouped while the name of the element is the group
81 | name. Passed down to \code{fixest::coefplot(..., group = .group)}. Example of
82 | valid uses:
83 | \itemize{
84 | \item group=list(group_name="pattern")
85 | \item group=list(group_name=c("var_start", "var_end"))
86 | \item group=list(group_name=1:2)
87 | \item See the Details section of \code{?fixest::coefplot} for more.
88 | }}
89 |
90 | \item{.vcov, .cluster, .se}{Alternative options for adjusting the standard
91 | errors of the model object on the fly. See \code{summary.fixest} for details
92 | (although note that the "." period prefix should be ignored in the latter's
93 | argument documentation). Written here in superseding order; \code{.cluster} will
94 | only be considered if \code{.vcov} is not null, etc.}
95 | }
96 | \value{
97 | A data frame consisting of estimate values, confidence intervals,
98 | relative x-axis positions, and other aesthetic information needed to draw
99 | a ggplot2 object.
100 | }
101 | \description{
102 | Grabs the underlying data used to construct \code{fixest::iplot},
103 | with some added functionality and tweaks for the \code{ggiplot} equivalents.
104 | }
105 | \details{
106 | This function is a wrapper around
107 | \code{fixest::iplot(..., only.params = TRUE)}, but with various checks and tweaks
108 | to better facilitate plotting with \code{ggplot2} and handling of complex object
109 | types (e.g. lists of fixest_multi models)
110 | }
111 | \section{Functions}{
112 | \itemize{
113 | \item \code{coefplot_data()}: Internal function for grabbing and preparing coefplot data
114 |
115 | }}
116 | \examples{
117 | library(fixest)
118 |
119 | est_did = feols(y ~ x1 + i(period, treat, 5) | id+period,
120 | data = base_did)
121 | iplot(est_did, only.params = TRUE) # The "base" version
122 | iplot_data(est_did) # The wrapper provided by this package
123 |
124 | # Illustrative fixest_multi case, where the sample has been split by odd and
125 | # even ID numbers.
126 | est_split = feols(y ~ x1 + i(period, treat, 5) | id+period,
127 | data = base_did, split = ~id\%\%2)
128 | iplot(est_split, only.params = TRUE) # The "base" version
129 | iplot_data(est_split) # The wrapper provided by this package
130 |
131 | }
132 | \seealso{
133 | \code{\link[fixest:coefplot]{fixest::iplot()}}, \code{\link[=aggr_es]{aggr_es()}}.
134 | }
135 |
--------------------------------------------------------------------------------
/tests/tinytest.R:
--------------------------------------------------------------------------------
1 | ## Throttle CPU threads if R CMD check (for CRAN)
2 |
3 | if (any(grepl("_R_CHECK", names(Sys.getenv()), fixed = TRUE))) {
4 | # fixest
5 | if (requireNamespace("fixest", quietly = TRUE)) {
6 | library(fixest)
7 | setFixest_nthreads(1)
8 | }
9 |
10 | # data.table
11 | if (requireNamespace("data.table", quietly = TRUE)) {
12 | library(data.table)
13 | setDTthreads(1)
14 | }
15 |
16 | # magick
17 | if (requireNamespace("magick", quietly = TRUE)) {
18 | library(magick)
19 | magick:::magick_threads(1)
20 | }
21 | }
22 |
23 |
24 | # Run tinytest suite
25 |
26 | if ( requireNamespace("tinytest", quietly=TRUE) ){
27 |
28 | tinytest::test_package("ggfixest")
29 |
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/vignettes/.gitignore:
--------------------------------------------------------------------------------
1 | *.html
2 | *.R
3 |
--------------------------------------------------------------------------------