diff-diff 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- diff_diff/__init__.py +12 -0
- diff_diff/estimators.py +701 -0
- diff_diff/results.py +170 -0
- diff_diff/utils.py +581 -0
- diff_diff-0.1.0.dist-info/METADATA +421 -0
- diff_diff-0.1.0.dist-info/RECORD +8 -0
- diff_diff-0.1.0.dist-info/WHEEL +5 -0
- diff_diff-0.1.0.dist-info/top_level.txt +1 -0
diff_diff/results.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Results classes for difference-in-differences estimation.
|
|
3
|
+
|
|
4
|
+
Provides statsmodels-style output with a more Pythonic interface.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import pandas as pd
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class DiDResults:
|
|
16
|
+
"""
|
|
17
|
+
Results from a Difference-in-Differences estimation.
|
|
18
|
+
|
|
19
|
+
Provides easy access to coefficients, standard errors, confidence intervals,
|
|
20
|
+
and summary statistics in a Pythonic way.
|
|
21
|
+
|
|
22
|
+
Attributes
|
|
23
|
+
----------
|
|
24
|
+
att : float
|
|
25
|
+
Average Treatment effect on the Treated (ATT).
|
|
26
|
+
se : float
|
|
27
|
+
Standard error of the ATT estimate.
|
|
28
|
+
t_stat : float
|
|
29
|
+
T-statistic for the ATT estimate.
|
|
30
|
+
p_value : float
|
|
31
|
+
P-value for the null hypothesis that ATT = 0.
|
|
32
|
+
conf_int : tuple[float, float]
|
|
33
|
+
Confidence interval for the ATT.
|
|
34
|
+
n_obs : int
|
|
35
|
+
Number of observations used in estimation.
|
|
36
|
+
n_treated : int
|
|
37
|
+
Number of treated units.
|
|
38
|
+
n_control : int
|
|
39
|
+
Number of control units.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
att: float
|
|
43
|
+
se: float
|
|
44
|
+
t_stat: float
|
|
45
|
+
p_value: float
|
|
46
|
+
conf_int: tuple
|
|
47
|
+
n_obs: int
|
|
48
|
+
n_treated: int
|
|
49
|
+
n_control: int
|
|
50
|
+
alpha: float = 0.05
|
|
51
|
+
coefficients: Optional[dict] = field(default=None)
|
|
52
|
+
vcov: Optional[np.ndarray] = field(default=None)
|
|
53
|
+
residuals: Optional[np.ndarray] = field(default=None)
|
|
54
|
+
fitted_values: Optional[np.ndarray] = field(default=None)
|
|
55
|
+
r_squared: Optional[float] = field(default=None)
|
|
56
|
+
|
|
57
|
+
def __repr__(self) -> str:
|
|
58
|
+
"""Concise string representation."""
|
|
59
|
+
sig = "***" if self.p_value < 0.001 else "**" if self.p_value < 0.01 else "*" if self.p_value < 0.05 else ""
|
|
60
|
+
return (
|
|
61
|
+
f"DiDResults(ATT={self.att:.4f}{sig}, "
|
|
62
|
+
f"SE={self.se:.4f}, "
|
|
63
|
+
f"p={self.p_value:.4f})"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def summary(self, alpha: Optional[float] = None) -> str:
|
|
67
|
+
"""
|
|
68
|
+
Generate a formatted summary of the estimation results.
|
|
69
|
+
|
|
70
|
+
Parameters
|
|
71
|
+
----------
|
|
72
|
+
alpha : float, optional
|
|
73
|
+
Significance level for confidence intervals. Defaults to the
|
|
74
|
+
alpha used during estimation.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
str
|
|
79
|
+
Formatted summary table.
|
|
80
|
+
"""
|
|
81
|
+
alpha = alpha or self.alpha
|
|
82
|
+
conf_level = int((1 - alpha) * 100)
|
|
83
|
+
|
|
84
|
+
lines = [
|
|
85
|
+
"=" * 70,
|
|
86
|
+
"Difference-in-Differences Estimation Results".center(70),
|
|
87
|
+
"=" * 70,
|
|
88
|
+
"",
|
|
89
|
+
f"{'Observations:':<25} {self.n_obs:>10}",
|
|
90
|
+
f"{'Treated units:':<25} {self.n_treated:>10}",
|
|
91
|
+
f"{'Control units:':<25} {self.n_control:>10}",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
if self.r_squared is not None:
|
|
95
|
+
lines.append(f"{'R-squared:':<25} {self.r_squared:>10.4f}")
|
|
96
|
+
|
|
97
|
+
lines.extend([
|
|
98
|
+
"",
|
|
99
|
+
"-" * 70,
|
|
100
|
+
f"{'Parameter':<15} {'Estimate':>12} {'Std. Err.':>12} {'t-stat':>10} {'P>|t|':>10}",
|
|
101
|
+
"-" * 70,
|
|
102
|
+
f"{'ATT':<15} {self.att:>12.4f} {self.se:>12.4f} {self.t_stat:>10.3f} {self.p_value:>10.4f}",
|
|
103
|
+
"-" * 70,
|
|
104
|
+
"",
|
|
105
|
+
f"{conf_level}% Confidence Interval: [{self.conf_int[0]:.4f}, {self.conf_int[1]:.4f}]",
|
|
106
|
+
])
|
|
107
|
+
|
|
108
|
+
# Add significance codes
|
|
109
|
+
lines.extend([
|
|
110
|
+
"",
|
|
111
|
+
"Signif. codes: '***' 0.001, '**' 0.01, '*' 0.05, '.' 0.1",
|
|
112
|
+
"=" * 70,
|
|
113
|
+
])
|
|
114
|
+
|
|
115
|
+
return "\n".join(lines)
|
|
116
|
+
|
|
117
|
+
def print_summary(self, alpha: Optional[float] = None) -> None:
|
|
118
|
+
"""Print the summary to stdout."""
|
|
119
|
+
print(self.summary(alpha))
|
|
120
|
+
|
|
121
|
+
def to_dict(self) -> dict:
|
|
122
|
+
"""
|
|
123
|
+
Convert results to a dictionary.
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
dict
|
|
128
|
+
Dictionary containing all estimation results.
|
|
129
|
+
"""
|
|
130
|
+
return {
|
|
131
|
+
"att": self.att,
|
|
132
|
+
"se": self.se,
|
|
133
|
+
"t_stat": self.t_stat,
|
|
134
|
+
"p_value": self.p_value,
|
|
135
|
+
"conf_int_lower": self.conf_int[0],
|
|
136
|
+
"conf_int_upper": self.conf_int[1],
|
|
137
|
+
"n_obs": self.n_obs,
|
|
138
|
+
"n_treated": self.n_treated,
|
|
139
|
+
"n_control": self.n_control,
|
|
140
|
+
"r_squared": self.r_squared,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
def to_dataframe(self) -> pd.DataFrame:
|
|
144
|
+
"""
|
|
145
|
+
Convert results to a pandas DataFrame.
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
pd.DataFrame
|
|
150
|
+
DataFrame with estimation results.
|
|
151
|
+
"""
|
|
152
|
+
return pd.DataFrame([self.to_dict()])
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def is_significant(self) -> bool:
|
|
156
|
+
"""Check if the ATT is statistically significant at the alpha level."""
|
|
157
|
+
return bool(self.p_value < self.alpha)
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def significance_stars(self) -> str:
|
|
161
|
+
"""Return significance stars based on p-value."""
|
|
162
|
+
if self.p_value < 0.001:
|
|
163
|
+
return "***"
|
|
164
|
+
elif self.p_value < 0.01:
|
|
165
|
+
return "**"
|
|
166
|
+
elif self.p_value < 0.05:
|
|
167
|
+
return "*"
|
|
168
|
+
elif self.p_value < 0.1:
|
|
169
|
+
return "."
|
|
170
|
+
return ""
|