Skip to content

Add weights option to event_study #920

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e3f37b5
Update project toml with conda-forge available R packages
May 17, 2025
19e6411
Added new pytest marks for r_against_core and r_against_extended
May 17, 2025
992d289
Updated packages to install for extended R environment
May 17, 2025
3fa3f8a
Added test markers in pytest init and related pixi dev tasks
May 17, 2025
8ed74df
Moved the extended mark of a fixture to the relevant function so pyte…
May 18, 2025
b2d206a
Adjusted extended R test scripts to skip over modules not properly in…
May 18, 2025
20ae811
Updated R requirements to correct install issues.
May 18, 2025
2c1de9a
Added skip summary on tasks that may cover R tests
May 18, 2025
fa50b3d
Updated the documentation around changes to R tests.
May 18, 2025
e0e2d06
Added R as dependency to docs as well to avoid need for global install
May 18, 2025
234412a
UNTESTED: Updated git workflow actions to reflect new R install?
May 18, 2025
836a696
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 18, 2025
8baa1e6
pixi lock
s3alfisc May 19, 2025
250cb55
Merge branch 'py-econometrics:master' into master
shapiromh May 20, 2025
8477d40
Made changes to make car a core R package
May 20, 2025
96e3363
Added check on mpdata availability
May 20, 2025
86e5867
Fix: forgot to label car tests as core instead of extended
May 20, 2025
6e234e5
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 20, 2025
83e5382
Merge branch 'py-econometrics:master' into master
shapiromh May 21, 2025
ee6c656
Merge branch 'py-econometrics:master' into master
shapiromh May 25, 2025
6d81f2c
Merge branch 'py-econometrics:master' into master
shapiromh May 27, 2025
4f935ab
Quick fix for saturated event study invoking summary() error
May 27, 2025
01885ff
Updated event study methods to take an aweights argument.
May 28, 2025
0552263
Added saturated and twfe weight tests
May 28, 2025
6e1d3be
Fixed a linter complaint
May 28, 2025
7f93451
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 28, 2025
de75f8d
Update pyfixest/did/saturated_twfe.py
shapiromh May 29, 2025
86c279f
fix CI
s3alfisc May 30, 2025
d59f05b
update pyproject.toml, lock
s3alfisc May 30, 2025
2cb6b90
Merge branch 'master' into fix_att
s3alfisc May 30, 2025
eb10ec7
pixi lock
s3alfisc May 30, 2025
0d1f698
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 30, 2025
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
18 changes: 6 additions & 12 deletions .github/workflows/ci-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ jobs:
run: |
pixi run tests-regular

- name: Build coverage.xml
run: pixi run coverage-report

- name: Upload coverage to Codecov (partial)
uses: codecov/codecov-action@v4
with:
Expand Down Expand Up @@ -101,6 +104,9 @@ jobs:
pixi run tests-against-r-core
pixi run tests-against-r-extended

- name: Build coverage.xml
run: pixi run coverage-report

- name: Upload coverage to Codecov (partial)
uses: codecov/codecov-action@v4
with:
Expand All @@ -109,18 +115,6 @@ jobs:
flags: tests-vs-r
files: coverage.xml

merge_coverage:
name: "Merge Coverage"
runs-on: ubuntu-latest
needs: [test, test_slow]
steps:
- name: Final coverage merge
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
partial: false
flags: final

build-docs:
name: "Build Docs"
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion pixi.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 12 additions & 6 deletions pyfixest/did/did.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ class DID(ABC):
YYYYMMDDHHMMSS, i.e. it must be possible to compare two dates via '>'.
Datetime variables are currently not accepted. Never treated units must
have a value of 0.
xfml : str
cluster : Optional[str]
The name of the cluster variable.
weights : Optional[str]
Default is None. Weights for WLS estimation. If None, all observations
are weighted equally. If a string, the name of the column in `data` that
contains the weights. Must be analytic weights for now.
xfml : Optional[str]
The formula for the covariates.
att : str
att : Optional[bool], default=True
Whether to estimate the average treatment effect on the treated (ATT) or
the canonical event study design with all leads and lags. Default is True.
cluster : str
The name of the cluster variable.
"""

@abstractmethod
Expand All @@ -44,8 +48,9 @@ def __init__(
tname: str,
gname: str,
cluster: Optional[str] = None,
weights: Optional[str] = None,
xfml: Optional[str] = None,
att: bool = True,
att: Optional[bool] = True,
):
# do some checks here

Expand All @@ -57,9 +62,10 @@ def __init__(
self._xfml = xfml
self._att = att
self._cluster = cluster
self._weights = weights
self._weights_type = "aweights"

# check if tname and gname are of type int (either int 64, 32, 8)

for var in [self._tname, self._gname]:
if self._data[var].dtype not in [
"int64",
Expand Down
34 changes: 16 additions & 18 deletions pyfixest/did/did2s.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,18 @@ class DID2S(DID):
YYYYMMDDHHMMSS, i.e. it must be possible to compare two dates via '>'.
Datetime variables are currently not accepted. Never treated units
must have a value of 0.
xfml : str
The formula for the covariates.
att : str
Whether to estimate the pooled average treatment effect on the treated
(ATT) or the canonical event study design with all leads and lags / the
ATT for each period. Default is True.
cluster : str
The name of the cluster variable.
weights : Optional[str].
weights : Optional[str]
Default is None. Weights for WLS estimation. If None, all observations
are weighted equally. If a string, the name of the column in `data` that
contains the weights.
contains the weights. Must be analytic weights for now.
xfml : Optional[str]
The formula for the covariates.
att : Optional[bool], default=True
Whether to estimate the pooled average treatment effect on the treated
(ATT) or the canonical event study design with all leads and lags / the
ATT for each period. Default is True.
"""

def __init__(
Expand All @@ -59,8 +59,8 @@ def __init__(
gname: str,
cluster: str,
weights: Optional[str] = None,
att: bool = True,
xfml: Optional[str] = None,
att: Optional[bool] = True,
):
super().__init__(
data=data,
Expand All @@ -71,6 +71,7 @@ def __init__(
xfml=xfml,
att=att,
cluster=cluster,
weights=weights,
)

self._estimator = "did2s"
Expand All @@ -86,17 +87,14 @@ def __init__(
self._first_u = np.array([])
self._second_u = np.array([])

# column name with weights. None by default
self._weights_name = weights

def estimate(self):
"""Estimate the two-step DID2S model."""
return _did2s_estimate(
data=self._data,
yname=self._yname,
_first_stage=self._fml1,
_second_stage=self._fml2,
weights=self._weights_name,
weights=self._weights,
treatment="is_treated",
) # returns triple Feols, first_u, second_u

Expand All @@ -121,7 +119,7 @@ def vcov(self):
first_u=self._first_u,
second_u=self._second_u,
cluster=self._cluster,
weights=self._weights_name,
weights=self._weights,
)

def iplot(
Expand Down Expand Up @@ -175,10 +173,10 @@ def _did2s_estimate(
The formula for the second stage.
treatment: str
The name of the treatment variable. Must be boolean.
weights : Optional[str].
weights : Optional[str]
Default is None. Weights for WLS estimation. If None, all observations
are weighted equally. If a string, the name of the column in `data` that
contains the weights.
contains the weights. Must be analytic weights for now.

Returns
-------
Expand Down Expand Up @@ -283,10 +281,10 @@ def _did2s_vcov(
The second stage residuals.
cluster: str
The name of the cluster variable.
weights : Optional[str].
weights : Optional[str]
Default is None. Weights for WLS estimation. If None, all observations
are weighted equally. If a string, the name of the column in `data` that
contains the weights.
contains the weights. Must be analytic weights for now.

Returns
-------
Expand Down
36 changes: 23 additions & 13 deletions pyfixest/did/estimation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional, Union
from typing import Literal, Optional, Union

import pandas as pd

Expand All @@ -15,17 +15,19 @@ def event_study(
idname: str,
tname: str,
gname: str,
xfml: Optional[str] = None,
cluster: Optional[str] = None,
estimator: Optional[str] = "twfe",
weights: Optional[str] = None,
xfml: Optional[str] = None,
att: Optional[bool] = True,
estimator: Optional[Literal["did2s", "twfe", "saturated"]] = "twfe",
):
"""
Estimate Event Study Model.

This function allows for the estimation of treatment effects using different
estimators. Currently, it supports "twfe" for the two-way fixed effects
estimator and "did2s" for Gardner's two-step DID2S estimator. Other estimators
estimator, "did2s" for Gardner's two-step DID2S estimator, and "saturated" for
a Sun & Abraham staggered event study estimator. Other estimators
are in development.

Parameters
Expand All @@ -42,14 +44,18 @@ def event_study(
Unit-specific time of initial treatment.
cluster: Optional[str]
The name of the cluster variable. If None, defaults to idname.
xfml : str
weights : Optional[str]
Default is None. Weights for WLS estimation. If None, all observations
are weighted equally. If a string, the name of the column in `data` that
contains the weights. Must be analytic weights for now.
xfml : Optional[str]
The formula for the covariates.
estimator : str
The estimator to use. Options are "did2s", "twfe", and "saturated".
att : bool, optional
att : Optional[bool]
If True, estimates the average treatment effect on the treated (ATT).
If False, estimates the canonical event study design with all leads and
lags. Default is True.
estimator : Optional[str], default="twfe"
The estimator to use. Options are "did2s", "twfe", and "saturated".

Returns
-------
Expand Down Expand Up @@ -96,10 +102,11 @@ def event_study(
assert isinstance(idname, str), "idname must be a string"
assert isinstance(tname, str), "tname must be a string"
assert isinstance(gname, str), "gname must be a string"
assert isinstance(cluster, str) or cluster is None, "cluster must be a string"
assert isinstance(weights, str) or weights is None, "weights must be a string"
assert isinstance(xfml, str) or xfml is None, "xfml must be a string or None"
assert isinstance(estimator, str), "estimator must be a string"
assert isinstance(att, bool), "att must be a boolean"
assert isinstance(cluster, str) or cluster is None, "cluster must be a string"
assert isinstance(estimator, str), "estimator must be a string"

cluster = idname if cluster is None else cluster

Expand All @@ -110,9 +117,10 @@ def event_study(
idname=idname,
tname=tname,
gname=gname,
cluster=cluster,
weights=weights,
xfml=xfml,
att=att,
cluster=cluster,
)

fit, did2s._first_u, did2s._second_u = did2s.estimate()
Expand All @@ -130,9 +138,10 @@ def event_study(
idname=idname,
tname=tname,
gname=gname,
cluster=cluster,
weights=weights,
xfml=xfml,
att=att,
cluster=cluster,
)
fit = twfe.estimate()
fit._yname = twfe._yname
Expand All @@ -151,9 +160,10 @@ def event_study(
idname=idname,
tname=tname,
gname=gname,
cluster=cluster,
weights=weights,
xfml=xfml,
att=att,
cluster=cluster,
)
fit = saturated.estimate()
vcov = fit.vcov(vcov={"CRV1": cluster})
Expand Down
Loading
Loading