Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f7104f1
v2.3.1.904: fix time-varying weights, add fix_weights param, reduce n…
pedrohcgs Apr 1, 2026
4b4724f
Fix IF aggregation bug in slow-mode fix_weights="varying", add SE con…
pedrohcgs Apr 1, 2026
550993d
Fix CI: modernize test workflow, add concurrency to bump-version, use…
pedrohcgs Apr 1, 2026
6abc765
Fix test-coverage workflow: update to v2 actions
pedrohcgs Apr 1, 2026
f44e401
Revert "Fix test-coverage workflow: update to v2 actions"
pedrohcgs Apr 1, 2026
03eff17
Fix test-coverage workflow: update to v2 actions
pedrohcgs Apr 1, 2026
2c29400
v2.3.1.904 fixes: guard custom est_method + fix_weights=varying, comp…
pedrohcgs Apr 2, 2026
09fcb4c
Replace deprecated geom_errorbarh() with geom_errorbar() in splot()
pedrohcgs Apr 2, 2026
6012b04
Fix fast/slow mode parity: keep NA cells on estimation failure instea…
pedrohcgs Apr 2, 2026
60e2e32
Merge branch 'v2.3.1.902-bugfixes' of https://github.com/bcallaway11/…
pedrohcgs Apr 2, 2026
beeb729
Fix RC custom-estimator test IF length, restore geom_errorbarh, updat…
pedrohcgs Apr 2, 2026
92db4fb
Fix RC custom-estimator test IF length, restore geom_errorbarh, updat…
pedrohcgs Apr 2, 2026
06e3974
Fix RC custom-estimator test IF length, restore geom_errorbarh, updat…
pedrohcgs Apr 2, 2026
f874296
Address Copilot review: remove unused n1_rc, use uniqueN, fix test cl…
pedrohcgs Apr 3, 2026
cc68339
Replace deprecated geom_errorbarh() with geom_errorbar(orientation="y")
pedrohcgs Apr 3, 2026
def1de9
Fix bare logit symbol, add ggplot2 version-gated fallback for geom_er…
pedrohcgs Apr 3, 2026
33366a0
Fix overlap guards for varying path, tighten validation, harden workflow
pedrohcgs Apr 3, 2026
9ee3409
Fix workflow PR number matching, document varying-mode covariate beha…
pedrohcgs Apr 3, 2026
447c3e5
Fix fix_weights="varying" to use pre-period covariates only
pedrohcgs Apr 3, 2026
879b147
Fix fix_weights="varying" covariate period for universal base period
pedrohcgs Apr 4, 2026
d33aaed
Address reviewer non-blocking notes
pedrohcgs Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/bump-version.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Bump dev version on PR merge

on:
pull_request:
types: [closed]
branches: [master, main]

concurrency:
group: bump-version
cancel-in-progress: false

jobs:
bump-version:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest

permissions:
contents: write

steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}
fetch-depth: 0

- name: Bump dev version in DESCRIPTION
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
# Idempotency: check if this PR already triggered a bump (exact match)
if git log --oneline ${{ github.event.pull_request.base.ref }} | grep -qF "(PR #${PR_NUMBER})"; then
echo "Version already bumped for PR #${PR_NUMBER}, skipping."
exit 0
fi

# Extract current version
current=$(grep '^Version:' DESCRIPTION | sed 's/Version: //')
echo "Current version: $current"

# Split into parts
IFS='.' read -ra parts <<< "$current"
major="${parts[0]}"
minor="${parts[1]}"
patch="${parts[2]}"
dev="${parts[3]:-0}"

# Increment dev version
new_dev=$((dev + 1))
new_version="${major}.${minor}.${patch}.${new_dev}"
echo "New version: $new_version"

# Update DESCRIPTION
sed -i "s/^Version: .*/Version: ${new_version}/" DESCRIPTION

# Configure git
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

# Commit and push with retry for race conditions
git add DESCRIPTION
git diff --cached --quiet && { echo "No changes to commit"; exit 0; }
git commit -m "Bump version to ${new_version} (PR #${PR_NUMBER})"

# Pull latest before pushing to handle concurrent merges
git pull --rebase origin ${{ github.event.pull_request.base.ref }} || true
git push
16 changes: 11 additions & 5 deletions .github/workflows/test-coverage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,22 @@ jobs:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- uses: r-lib/actions/setup-r@v1
- uses: r-lib/actions/setup-r@v2
with:
use-public-rspm: true

- uses: r-lib/actions/setup-r-dependencies@v1
- uses: r-lib/actions/setup-r-dependencies@v2
with:
extra-packages: covr
extra-packages: any::covr
needs: coverage

- name: Test coverage
run: covr::codecov()
run: |
covr::codecov(
quiet = FALSE,
clean = FALSE,
install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package")
)
shell: Rscript {0}
45 changes: 12 additions & 33 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,24 @@ jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3
env:
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}

- name: Set up R
uses: r-lib/actions/setup-r@v2
steps:
- uses: actions/checkout@v4

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libharfbuzz-dev libfribidi-dev libfreetype6-dev libcurl4-openssl-dev libpng-dev libtiff-dev libjpeg-dev libfontconfig1-dev
- uses: r-lib/actions/setup-r@v2
with:
use-public-rspm: true

- name: Cache R packages
uses: actions/cache@v3
- uses: r-lib/actions/setup-r-dependencies@v2
with:
path: ${{ env.R_LIBS_USER }}
key: ${{ runner.os }}-r-${{ hashFiles('**/DESCRIPTION') }}-${{ matrix.config.r }}
restore-keys: |
${{ runner.os }}-r-${{ matrix.config.r }}-
${{ runner.os }}-r-
extra-packages: any::devtools, any::testthat
needs: check

- name: Install R package dependencies
run: |
if [ "${{ runner.os }}" == "Windows" ]; then
cmd.exe /c "R -e \"install.packages('devtools'); devtools::install_deps(dependencies = TRUE)\""
cmd.exe /c "R -e \"install.packages('tidyverse'); devtools::install_deps(dependencies = TRUE)\""
cmd.exe /c "R -e \"install.packages('remotes')\""
cmd.exe /c "R -e \"install.packages('callr')\""
else
R -e "install.packages('devtools'); devtools::install_deps(dependencies = TRUE)"
R -e "install.packages('tidyverse'); devtools::install_deps(dependencies = TRUE)"
R -e "install.packages('remotes')"
R -e "install.packages('callr')"
fi

- name: Install did package
run: R CMD INSTALL .

- name: Run tests
run: |
run: |
R -e "devtools::test()"
R -e "testthat::test_dir('tests/testthat', reporter = 'check', package = 'did')"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ desktop.ini
did.Rproj
..Rcheck/
.claude/
CLAUDE.md
.vscode/
.revdep_manual/
vignettes/*_cache/
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: did
Title: Treatment Effects with Multiple Periods and Groups
Version: 2.3.1.903
Version: 2.3.1.904
Authors@R: c(person("Brantly", "Callaway", email = "brantly.callaway@uga.edu", role = c("aut", "cre")), person("Pedro H. C.", "Sant'Anna", email="pedro.santanna@emory.edu", role = c("aut")))
URL: https://bcallaway11.github.io/did/, https://github.com/bcallaway11/did/
Description: The standard Difference-in-Differences (DID) setup involves two periods and two groups -- a treated group and untreated group. Many applications of DID methods involve more than two periods and have individuals that are treated at different points in time. This package contains tools for computing average treatment effect parameters in Difference in Differences setups with more than two periods and with variation in treatment timing using the methods developed in Callaway and Sant'Anna (2021) <doi:10.1016/j.jeconom.2020.12.001>. The main parameters are group-time average treatment effects which are the average treatment effect for a particular group at a a particular time. These can be aggregated into a fewer number of treatment effect parameters, and the package deals with the cases where there is selective treatment timing, dynamic treatment effects, calendar time effects, or combinations of these. There are also functions for testing the Difference in Differences assumption, and plotting group-time average treatment effects.
Expand Down
27 changes: 24 additions & 3 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@ export(splot)
export(test.mboot)
export(tidy)
export(trimmer)
import(BMisc)
import(data.table)
import(fastglm)
import(ggplot2)
import(stats)
import(utils)
importFrom(BMisc,TorF)
importFrom(BMisc,getListElement)
importFrom(BMisc,makeBalancedPanel)
importFrom(BMisc,multiplier_bootstrap)
importFrom(BMisc,rhs.vars)
importFrom(BMisc,toformula)
importFrom(DRDID,drdid_panel)
importFrom(DRDID,drdid_rc)
importFrom(DRDID,reg_did_panel)
Expand All @@ -55,5 +58,23 @@ importFrom(generics,glance)
importFrom(generics,tidy)
importFrom(methods,as)
importFrom(methods,is)
importFrom(stats,aggregate)
importFrom(stats,binomial)
importFrom(stats,complete.cases)
importFrom(stats,cov)
importFrom(stats,ecdf)
importFrom(stats,glm)
importFrom(stats,model.frame)
importFrom(stats,model.matrix)
importFrom(stats,na.pass)
importFrom(stats,nobs)
importFrom(stats,pchisq)
importFrom(stats,pnorm)
importFrom(stats,predict)
importFrom(stats,qnorm)
importFrom(stats,quantile)
importFrom(stats,rnorm)
importFrom(stats,setNames)
importFrom(stats,var)
importFrom(tidyr,gather)
importFrom(utils,globalVariables)
50 changes: 50 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
# did 2.3.1.904

* Fixed bug where `faster_mode = TRUE` and `faster_mode = FALSE` produced different ATT estimates when sampling weights (`weightsname`) vary across time. The fast path was always using first-period weights; it now correctly uses the same period's weights as the slow path

* New `fix_weights` argument in `att_gt()` gives users explicit control over how time-varying sampling weights are resolved in each 2x2 DiD comparison. Options: `NULL` (default, preserves existing behavior), `"varying"` (per-observation weights using RC estimators), `"base_period"` (fix at g-1 for all cells), `"first_period"` (fix at first period). See `?att_gt` for details

* Runtime message when time-varying weights are detected in balanced panel data, directing users to the `fix_weights` argument

* Reduced namespace pollution: replaced blanket `import(stats)`, `import(utils)`, and `import(BMisc)` with selective `importFrom()` calls. The `did` package no longer re-exports `stats::filter` or `stats::lag`, which previously masked `dplyr::filter` and `dplyr::lag` when both packages were loaded

* Fixed `aggte()` crash (`"Error in get(gname): invalid first argument"`) when the user's group column is literally named `gname` and `dreamerr` >= 1.5.0 is installed. The issue was `dreamerr` intercepting `data.table`'s `get()` inside `[.data.table`; replaced with `set()` which is immune to this

* Expanded `weightsname` documentation explaining how time-varying weights are handled differently for balanced panels vs. repeated cross sections and unbalanced panels

* Added `nobs()` S3 methods for `MP` and `AGGTEobj` objects, returning the number of unique cross-sectional units as an integer

* Added `statistic` (t-statistic) and `p.value` (pointwise, two-sided) columns to `tidy()` output for both `MP` and `AGGTEobj` objects, following `broom` conventions

* Fixed `glance.MP()` returning `NULL` for `ngroup` and `ntime` when `faster_mode = TRUE` (DIDparams2 uses different field names than DIDparams)

* Fixed influence function aggregation bug in `fix_weights = "varying"` on balanced panel: the slow path now correctly uses `rowsum()` by unit ID instead of assuming stacked ordering

* Fixed `fix_weights = "base_period"` / `"first_period"` for unbalanced panels: corrected influence function length mismatch after weight-based unit dropping, and fast path now properly excludes units missing from the target period

* Added validation: `fix_weights = "base_period"` and `"first_period"` are blocked for repeated cross sections (`panel = FALSE`) with a clear error message

* Added validation: `fix_weights = "varying"` is blocked when using a custom `est_method` function, since the varying path uses RC estimators internally with a different function signature

* Completed namespace cleanup: added missing `@importFrom` for `stats::ecdf`, `stats::glm`, `stats::predict`; registered `.w`, `D`, `N`, `post`, `weights` as `globalVariables` for data.table column references. `R CMD check` now passes with 0 code-related NOTEs

* Replaced fragile `exists("use_rc_for_weights")` with direct `dp2$fix_weights` check in `compute.att_gt2()`

* Fixed `data.table` `.checkTypos` crash in `get_wide_data()` when user column names match local variable names (e.g., column named `tname`)

* Replaced `get()` with `c()` in time-varying weight detection grouping to avoid potential `dreamerr` interception

* Inference tests (`test-inference.R`): switched to HTTPS mirror, added `requireNamespace` verification, wrapped install in `skip_on_cran` guard, added proper temp directory cleanup

* Added GitHub Action to auto-bump dev version in DESCRIPTION on PR merge

* Substantially expanded test suite covering `glance()`, `ggdid()`, error handling, edge cases, all aggregation types, and systematic `faster_mode` consistency across 36 parameter combinations. Test suite now runs with 0 warnings (previously 66+)

# did 2.3.1.903

* Added `nobs()` S3 methods and `statistic`/`p.value` columns to `tidy()` output (superseded by 2.3.1.904 entry above)

# did 2.3.1.902

* Bug fixes, diagnostic improvements, and JEL replication tests

# did 2.3.1.901

* `att_gt()` now accepts `...` (dots) for passing additional arguments to custom `est_method` functions
Expand Down
2 changes: 2 additions & 0 deletions R/DIDparams.R
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ DIDparams <- function(yname,
control_group,
anticipation=0,
weightsname=NULL,
fix_weights=NULL,
alp=0.05,
bstrap=TRUE,
biters=1000,
Expand Down Expand Up @@ -54,6 +55,7 @@ DIDparams <- function(yname,
control_group=control_group,
anticipation=anticipation,
weightsname=weightsname,
fix_weights=fix_weights,
alp=alp,
bstrap=bstrap,
biters=biters,
Expand Down
4 changes: 4 additions & 0 deletions R/DIDparams2.R
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ DIDparams2 <- function(did_tensors, args, call=NULL) {
covariates_matrix <- did_tensors$covariates_matrix
cluster_vector <- did_tensors$cluster
weights_vector <- did_tensors$weights
weights_tensor <- did_tensors$weights_tensor
fix_weights <- args$fix_weights


out <- list(yname=yname,
Expand Down Expand Up @@ -89,6 +91,8 @@ DIDparams2 <- function(did_tensors, args, call=NULL) {
covariates_matrix = covariates_matrix,
cluster_vector=cluster_vector,
weights_vector=weights_vector,
weights_tensor=weights_tensor,
fix_weights=fix_weights,
call=call)
class(out) <- "DIDparams"
return(out)
Expand Down
Loading
Loading