diff-diff 2.0.1__tar.gz → 2.0.2__tar.gz
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-2.0.1 → diff_diff-2.0.2}/PKG-INFO +1 -1
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/__init__.py +1 -1
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/staggered.py +157 -6
- {diff_diff-2.0.1 → diff_diff-2.0.2}/pyproject.toml +1 -1
- {diff_diff-2.0.1 → diff_diff-2.0.2}/rust/Cargo.lock +12 -12
- {diff_diff-2.0.1 → diff_diff-2.0.2}/README.md +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/_backend.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/bacon.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/diagnostics.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/estimators.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/honest_did.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/linalg.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/power.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/prep.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/pretrends.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/results.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/sun_abraham.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/synthetic_did.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/triple_diff.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/twfe.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/utils.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/diff_diff/visualization.py +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/rust/Cargo.toml +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/rust/src/bootstrap.rs +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/rust/src/lib.rs +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/rust/src/linalg.rs +0 -0
- {diff_diff-2.0.1 → diff_diff-2.0.2}/rust/src/weights.rs +0 -0
|
@@ -1479,29 +1479,36 @@ class CallawaySantAnna:
|
|
|
1479
1479
|
|
|
1480
1480
|
Standard errors are computed using influence function aggregation,
|
|
1481
1481
|
which properly accounts for covariances across (g,t) pairs due to
|
|
1482
|
-
shared control units. This
|
|
1482
|
+
shared control units. This includes the wif (weight influence function)
|
|
1483
|
+
adjustment from R's `did` package that accounts for uncertainty in
|
|
1484
|
+
estimating the group-size weights.
|
|
1483
1485
|
"""
|
|
1484
1486
|
effects = []
|
|
1485
1487
|
weights_list = []
|
|
1486
1488
|
gt_pairs = []
|
|
1489
|
+
groups_for_gt = []
|
|
1487
1490
|
|
|
1488
1491
|
for (g, t), data in group_time_effects.items():
|
|
1489
1492
|
effects.append(data['effect'])
|
|
1490
1493
|
weights_list.append(data['n_treated'])
|
|
1491
1494
|
gt_pairs.append((g, t))
|
|
1495
|
+
groups_for_gt.append(g)
|
|
1492
1496
|
|
|
1493
1497
|
effects = np.array(effects)
|
|
1494
1498
|
weights = np.array(weights_list, dtype=float)
|
|
1499
|
+
groups_for_gt = np.array(groups_for_gt)
|
|
1495
1500
|
|
|
1496
1501
|
# Normalize weights
|
|
1497
|
-
|
|
1502
|
+
total_weight = np.sum(weights)
|
|
1503
|
+
weights_norm = weights / total_weight
|
|
1498
1504
|
|
|
1499
1505
|
# Weighted average
|
|
1500
|
-
overall_att = np.sum(
|
|
1506
|
+
overall_att = np.sum(weights_norm * effects)
|
|
1501
1507
|
|
|
1502
|
-
# Compute SE using influence function aggregation
|
|
1503
|
-
overall_se = self.
|
|
1504
|
-
gt_pairs,
|
|
1508
|
+
# Compute SE using influence function aggregation with wif adjustment
|
|
1509
|
+
overall_se = self._compute_aggregated_se_with_wif(
|
|
1510
|
+
gt_pairs, weights_norm, effects, groups_for_gt,
|
|
1511
|
+
influence_func_info, df, unit
|
|
1505
1512
|
)
|
|
1506
1513
|
|
|
1507
1514
|
return overall_att, overall_se
|
|
@@ -1566,6 +1573,150 @@ class CallawaySantAnna:
|
|
|
1566
1573
|
variance = np.sum(psi_overall ** 2)
|
|
1567
1574
|
return np.sqrt(variance)
|
|
1568
1575
|
|
|
1576
|
+
def _compute_aggregated_se_with_wif(
|
|
1577
|
+
self,
|
|
1578
|
+
gt_pairs: List[Tuple[Any, Any]],
|
|
1579
|
+
weights: np.ndarray,
|
|
1580
|
+
effects: np.ndarray,
|
|
1581
|
+
groups_for_gt: np.ndarray,
|
|
1582
|
+
influence_func_info: Dict,
|
|
1583
|
+
df: pd.DataFrame,
|
|
1584
|
+
unit: str,
|
|
1585
|
+
) -> float:
|
|
1586
|
+
"""
|
|
1587
|
+
Compute SE with weight influence function (wif) adjustment.
|
|
1588
|
+
|
|
1589
|
+
This matches R's `did` package approach for "simple" aggregation,
|
|
1590
|
+
which accounts for uncertainty in estimating group-size weights.
|
|
1591
|
+
|
|
1592
|
+
The wif adjustment adds variance due to the fact that aggregation
|
|
1593
|
+
weights w_g = n_g / N depend on estimated group sizes.
|
|
1594
|
+
|
|
1595
|
+
Formula (matching R's did::aggte):
|
|
1596
|
+
agg_inf_i = Σ_k w_k × inf_i_k + wif_i × ATT_k
|
|
1597
|
+
se = sqrt(mean(agg_inf^2) / n)
|
|
1598
|
+
|
|
1599
|
+
where:
|
|
1600
|
+
- k indexes "keepers" (post-treatment (g,t) pairs)
|
|
1601
|
+
- w_k = pg[k] / sum(pg[keepers]) where pg = n_g / n_all
|
|
1602
|
+
- wif captures how unit i influences the weight estimation
|
|
1603
|
+
"""
|
|
1604
|
+
if not influence_func_info:
|
|
1605
|
+
return 0.0
|
|
1606
|
+
|
|
1607
|
+
# Build unit index mapping
|
|
1608
|
+
all_units = set()
|
|
1609
|
+
for (g, t) in gt_pairs:
|
|
1610
|
+
if (g, t) in influence_func_info:
|
|
1611
|
+
info = influence_func_info[(g, t)]
|
|
1612
|
+
all_units.update(info['treated_units'])
|
|
1613
|
+
all_units.update(info['control_units'])
|
|
1614
|
+
|
|
1615
|
+
if not all_units:
|
|
1616
|
+
return 0.0
|
|
1617
|
+
|
|
1618
|
+
all_units = sorted(all_units)
|
|
1619
|
+
n_units = len(all_units)
|
|
1620
|
+
unit_to_idx = {u: i for i, u in enumerate(all_units)}
|
|
1621
|
+
|
|
1622
|
+
# Get unique groups and their information
|
|
1623
|
+
unique_groups = sorted(set(groups_for_gt))
|
|
1624
|
+
group_to_idx = {g: i for i, g in enumerate(unique_groups)}
|
|
1625
|
+
|
|
1626
|
+
# Compute group-level probabilities matching R's formula:
|
|
1627
|
+
# pg[g] = n_g / n_all (fraction of ALL units in group g)
|
|
1628
|
+
# This differs from our old formula which used n_g / total_treated
|
|
1629
|
+
group_sizes = {}
|
|
1630
|
+
for g in unique_groups:
|
|
1631
|
+
treated_in_g = df[df['first_treat'] == g][unit].nunique()
|
|
1632
|
+
group_sizes[g] = treated_in_g
|
|
1633
|
+
|
|
1634
|
+
# pg indexed by group
|
|
1635
|
+
pg_by_group = np.array([group_sizes[g] / n_units for g in unique_groups])
|
|
1636
|
+
|
|
1637
|
+
# pg indexed by keeper (each (g,t) pair gets its group's pg)
|
|
1638
|
+
# This matches R's: pg <- pgg[match(group, originalglist)]
|
|
1639
|
+
pg_keepers = np.array([pg_by_group[group_to_idx[g]] for g in groups_for_gt])
|
|
1640
|
+
sum_pg_keepers = np.sum(pg_keepers)
|
|
1641
|
+
|
|
1642
|
+
# Standard aggregated influence (without wif)
|
|
1643
|
+
psi_standard = np.zeros(n_units)
|
|
1644
|
+
|
|
1645
|
+
for j, (g, t) in enumerate(gt_pairs):
|
|
1646
|
+
if (g, t) not in influence_func_info:
|
|
1647
|
+
continue
|
|
1648
|
+
|
|
1649
|
+
info = influence_func_info[(g, t)]
|
|
1650
|
+
w = weights[j]
|
|
1651
|
+
|
|
1652
|
+
for i, uid in enumerate(info['treated_units']):
|
|
1653
|
+
idx = unit_to_idx[uid]
|
|
1654
|
+
psi_standard[idx] += w * info['treated_inf'][i]
|
|
1655
|
+
|
|
1656
|
+
for i, uid in enumerate(info['control_units']):
|
|
1657
|
+
idx = unit_to_idx[uid]
|
|
1658
|
+
psi_standard[idx] += w * info['control_inf'][i]
|
|
1659
|
+
|
|
1660
|
+
# Build unit-group membership indicator
|
|
1661
|
+
unit_groups = {}
|
|
1662
|
+
for uid in all_units:
|
|
1663
|
+
unit_first_treat = df[df[unit] == uid]['first_treat'].iloc[0]
|
|
1664
|
+
if unit_first_treat in unique_groups:
|
|
1665
|
+
unit_groups[uid] = unit_first_treat
|
|
1666
|
+
else:
|
|
1667
|
+
unit_groups[uid] = None # Never-treated or other
|
|
1668
|
+
|
|
1669
|
+
# Compute wif using R's exact formula (iterate over keepers, not groups)
|
|
1670
|
+
# R's wif function:
|
|
1671
|
+
# if1[i,k] = (indicator(G_i == group_k) - pg[k]) / sum(pg[keepers])
|
|
1672
|
+
# if2[i,k] = indicator_sum[i] * pg[k] / sum(pg[keepers])^2
|
|
1673
|
+
# wif[i,k] = if1[i,k] - if2[i,k]
|
|
1674
|
+
#
|
|
1675
|
+
# Then: wif_contrib[i] = sum_k(wif[i,k] * att[k])
|
|
1676
|
+
|
|
1677
|
+
n_keepers = len(gt_pairs)
|
|
1678
|
+
wif_contrib = np.zeros(n_units)
|
|
1679
|
+
|
|
1680
|
+
# Pre-compute indicator_sum for each unit
|
|
1681
|
+
# indicator_sum[i] = sum_k(indicator(G_i == group_k) - pg[k])
|
|
1682
|
+
indicator_sum = np.zeros(n_units)
|
|
1683
|
+
for j, g in enumerate(groups_for_gt):
|
|
1684
|
+
pg_k = pg_keepers[j]
|
|
1685
|
+
for uid in all_units:
|
|
1686
|
+
i = unit_to_idx[uid]
|
|
1687
|
+
unit_g = unit_groups[uid]
|
|
1688
|
+
indicator = 1.0 if unit_g == g else 0.0
|
|
1689
|
+
indicator_sum[i] += (indicator - pg_k)
|
|
1690
|
+
|
|
1691
|
+
# Compute wif contribution for each keeper
|
|
1692
|
+
for j, (g, t) in enumerate(gt_pairs):
|
|
1693
|
+
pg_k = pg_keepers[j]
|
|
1694
|
+
att_k = effects[j]
|
|
1695
|
+
|
|
1696
|
+
for uid in all_units:
|
|
1697
|
+
i = unit_to_idx[uid]
|
|
1698
|
+
unit_g = unit_groups[uid]
|
|
1699
|
+
indicator = 1.0 if unit_g == g else 0.0
|
|
1700
|
+
|
|
1701
|
+
# R's formula for wif
|
|
1702
|
+
if1_ik = (indicator - pg_k) / sum_pg_keepers
|
|
1703
|
+
if2_ik = indicator_sum[i] * pg_k / (sum_pg_keepers ** 2)
|
|
1704
|
+
wif_ik = if1_ik - if2_ik
|
|
1705
|
+
|
|
1706
|
+
# Add contribution: wif[i,k] * att[k]
|
|
1707
|
+
wif_contrib[i] += wif_ik * att_k
|
|
1708
|
+
|
|
1709
|
+
# Scale by 1/n_units to match R's getSE formula: sqrt(mean(IF^2)/n)
|
|
1710
|
+
psi_wif = wif_contrib / n_units
|
|
1711
|
+
|
|
1712
|
+
# Combine standard and wif terms
|
|
1713
|
+
psi_total = psi_standard + psi_wif
|
|
1714
|
+
|
|
1715
|
+
# Compute variance and SE
|
|
1716
|
+
# R's formula: sqrt(mean(IF^2) / n) = sqrt(sum(IF^2) / n^2)
|
|
1717
|
+
variance = np.sum(psi_total ** 2)
|
|
1718
|
+
return np.sqrt(variance)
|
|
1719
|
+
|
|
1569
1720
|
def _aggregate_event_study(
|
|
1570
1721
|
self,
|
|
1571
1722
|
group_time_effects: Dict,
|
|
@@ -131,9 +131,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
|
131
131
|
|
|
132
132
|
[[package]]
|
|
133
133
|
name = "chrono"
|
|
134
|
-
version = "0.4.
|
|
134
|
+
version = "0.4.43"
|
|
135
135
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
136
|
-
checksum = "
|
|
136
|
+
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
|
137
137
|
dependencies = [
|
|
138
138
|
"iana-time-zone",
|
|
139
139
|
"js-sys",
|
|
@@ -709,9 +709,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
|
|
709
709
|
|
|
710
710
|
[[package]]
|
|
711
711
|
name = "js-sys"
|
|
712
|
-
version = "0.3.
|
|
712
|
+
version = "0.3.85"
|
|
713
713
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
714
|
-
checksum = "
|
|
714
|
+
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
|
715
715
|
dependencies = [
|
|
716
716
|
"once_cell",
|
|
717
717
|
"wasm-bindgen",
|
|
@@ -1895,9 +1895,9 @@ dependencies = [
|
|
|
1895
1895
|
|
|
1896
1896
|
[[package]]
|
|
1897
1897
|
name = "wasm-bindgen"
|
|
1898
|
-
version = "0.2.
|
|
1898
|
+
version = "0.2.108"
|
|
1899
1899
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1900
|
-
checksum = "
|
|
1900
|
+
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
|
1901
1901
|
dependencies = [
|
|
1902
1902
|
"cfg-if",
|
|
1903
1903
|
"once_cell",
|
|
@@ -1908,9 +1908,9 @@ dependencies = [
|
|
|
1908
1908
|
|
|
1909
1909
|
[[package]]
|
|
1910
1910
|
name = "wasm-bindgen-macro"
|
|
1911
|
-
version = "0.2.
|
|
1911
|
+
version = "0.2.108"
|
|
1912
1912
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1913
|
-
checksum = "
|
|
1913
|
+
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
|
1914
1914
|
dependencies = [
|
|
1915
1915
|
"quote",
|
|
1916
1916
|
"wasm-bindgen-macro-support",
|
|
@@ -1918,9 +1918,9 @@ dependencies = [
|
|
|
1918
1918
|
|
|
1919
1919
|
[[package]]
|
|
1920
1920
|
name = "wasm-bindgen-macro-support"
|
|
1921
|
-
version = "0.2.
|
|
1921
|
+
version = "0.2.108"
|
|
1922
1922
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1923
|
-
checksum = "
|
|
1923
|
+
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
|
1924
1924
|
dependencies = [
|
|
1925
1925
|
"bumpalo",
|
|
1926
1926
|
"proc-macro2",
|
|
@@ -1931,9 +1931,9 @@ dependencies = [
|
|
|
1931
1931
|
|
|
1932
1932
|
[[package]]
|
|
1933
1933
|
name = "wasm-bindgen-shared"
|
|
1934
|
-
version = "0.2.
|
|
1934
|
+
version = "0.2.108"
|
|
1935
1935
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1936
|
-
checksum = "
|
|
1936
|
+
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
|
1937
1937
|
dependencies = [
|
|
1938
1938
|
"unicode-ident",
|
|
1939
1939
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|