post-analysis-toolkit 1.0.2__tar.gz → 1.0.4__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.
Files changed (30) hide show
  1. {post_analysis_toolkit-1.0.2/post_analysis_toolkit.egg-info → post_analysis_toolkit-1.0.4}/PKG-INFO +2 -2
  2. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/notebooks/did_its_template.ipynb +114 -64
  3. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/README.md +1 -1
  4. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/__init__.py +1 -1
  5. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/causal_impact.py +18 -2
  6. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/did.py +32 -1
  7. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/its.py +3 -0
  8. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/visualization.py +28 -0
  9. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4/post_analysis_toolkit.egg-info}/PKG-INFO +2 -2
  10. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/pyproject.toml +1 -1
  11. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/MANIFEST.in +0 -0
  12. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/README.md +0 -0
  13. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/MDE_METHODOLOGY.md +0 -0
  14. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/REFERENCE.html +0 -0
  15. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/REFERENCE.md +0 -0
  16. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/compat.py +0 -0
  17. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/contracts.py +0 -0
  18. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/demo.py +0 -0
  19. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/mde.py +0 -0
  20. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/mde_resampling.py +0 -0
  21. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/observational.py +0 -0
  22. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/rdd.py +0 -0
  23. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/synthetic_control.py +0 -0
  24. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/triple_difference.py +0 -0
  25. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit/utils.py +0 -0
  26. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit.egg-info/SOURCES.txt +0 -0
  27. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit.egg-info/dependency_links.txt +0 -0
  28. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit.egg-info/requires.txt +0 -0
  29. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/post_analysis_toolkit.egg-info/top_level.txt +0 -0
  30. {post_analysis_toolkit-1.0.2 → post_analysis_toolkit-1.0.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: post-analysis-toolkit
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: Reusable helpers for post-analysis, quasi-causal designs, and diagnostics.
5
5
  Author: OpenAI Codex
6
6
  Requires-Python: >=3.11
@@ -1151,4 +1151,4 @@ its_result = fit_its(
1151
1151
 
1152
1152
  ## Версия
1153
1153
 
1154
- Текущая версия пакета: `1.0.2`
1154
+ Текущая версия пакета: `1.0.4`
@@ -34,10 +34,10 @@
34
34
  "id": "979f6a5a",
35
35
  "metadata": {
36
36
  "execution": {
37
- "iopub.execute_input": "2026-08-18T18:11:50.654235Z",
38
- "iopub.status.busy": "2026-08-18T18:11:50.654132Z",
39
- "iopub.status.idle": "2026-08-18T18:11:53.158388Z",
40
- "shell.execute_reply": "2026-08-18T18:11:53.157694Z"
37
+ "iopub.execute_input": "2026-08-19T05:10:57.124787Z",
38
+ "iopub.status.busy": "2026-08-19T05:10:57.124594Z",
39
+ "iopub.status.idle": "2026-08-19T05:11:00.044911Z",
40
+ "shell.execute_reply": "2026-08-19T05:11:00.044081Z"
41
41
  }
42
42
  },
43
43
  "outputs": [],
@@ -106,10 +106,10 @@
106
106
  "id": "187f6644",
107
107
  "metadata": {
108
108
  "execution": {
109
- "iopub.execute_input": "2026-08-18T18:11:53.160094Z",
110
- "iopub.status.busy": "2026-08-18T18:11:53.159864Z",
111
- "iopub.status.idle": "2026-08-18T18:11:53.173591Z",
112
- "shell.execute_reply": "2026-08-18T18:11:53.173100Z"
109
+ "iopub.execute_input": "2026-08-19T05:11:00.046648Z",
110
+ "iopub.status.busy": "2026-08-19T05:11:00.046396Z",
111
+ "iopub.status.idle": "2026-08-19T05:11:00.061703Z",
112
+ "shell.execute_reply": "2026-08-19T05:11:00.061146Z"
113
113
  }
114
114
  },
115
115
  "outputs": [
@@ -210,10 +210,10 @@
210
210
  "id": "52678d87",
211
211
  "metadata": {
212
212
  "execution": {
213
- "iopub.execute_input": "2026-08-18T18:11:53.174840Z",
214
- "iopub.status.busy": "2026-08-18T18:11:53.174679Z",
215
- "iopub.status.idle": "2026-08-18T18:11:53.584409Z",
216
- "shell.execute_reply": "2026-08-18T18:11:53.583776Z"
213
+ "iopub.execute_input": "2026-08-19T05:11:00.063608Z",
214
+ "iopub.status.busy": "2026-08-19T05:11:00.063363Z",
215
+ "iopub.status.idle": "2026-08-19T05:11:03.162282Z",
216
+ "shell.execute_reply": "2026-08-19T05:11:03.161245Z"
217
217
  }
218
218
  },
219
219
  "outputs": [
@@ -323,6 +323,24 @@
323
323
  },
324
324
  "metadata": {},
325
325
  "output_type": "display_data"
326
+ },
327
+ {
328
+ "data": {
329
+ "text/plain": [
330
+ "<Figure size 1800x400 with 3 Axes>"
331
+ ]
332
+ },
333
+ "metadata": {},
334
+ "output_type": "display_data"
335
+ },
336
+ {
337
+ "data": {
338
+ "text/plain": [
339
+ "<Figure size 1000x400 with 1 Axes>"
340
+ ]
341
+ },
342
+ "metadata": {},
343
+ "output_type": "display_data"
326
344
  }
327
345
  ],
328
346
  "source": [
@@ -336,7 +354,9 @@
336
354
  " unit_col=\"pizzeria\",\n",
337
355
  " fpr_placebo_count=200,\n",
338
356
  ")\n",
339
- "display(show_checks(did_diagnostics.checks))"
357
+ "display(show_checks(did_diagnostics.checks))\n",
358
+ "display(did_diagnostics.figures[\"pretrend_triptych\"])\n",
359
+ "display(did_diagnostics.figures[\"event_study\"])"
340
360
  ]
341
361
  },
342
362
  {
@@ -345,10 +365,10 @@
345
365
  "id": "c8f0a026",
346
366
  "metadata": {
347
367
  "execution": {
348
- "iopub.execute_input": "2026-08-18T18:11:53.585824Z",
349
- "iopub.status.busy": "2026-08-18T18:11:53.585695Z",
350
- "iopub.status.idle": "2026-08-18T18:11:53.619227Z",
351
- "shell.execute_reply": "2026-08-18T18:11:53.618524Z"
368
+ "iopub.execute_input": "2026-08-19T05:11:03.164560Z",
369
+ "iopub.status.busy": "2026-08-19T05:11:03.164357Z",
370
+ "iopub.status.idle": "2026-08-19T05:11:03.237609Z",
371
+ "shell.execute_reply": "2026-08-19T05:11:03.236585Z"
352
372
  }
353
373
  },
354
374
  "outputs": [
@@ -1527,6 +1547,15 @@
1527
1547
  },
1528
1548
  "metadata": {},
1529
1549
  "output_type": "display_data"
1550
+ },
1551
+ {
1552
+ "data": {
1553
+ "text/plain": [
1554
+ "<Figure size 1200x400 with 1 Axes>"
1555
+ ]
1556
+ },
1557
+ "metadata": {},
1558
+ "output_type": "display_data"
1530
1559
  }
1531
1560
  ],
1532
1561
  "source": [
@@ -1541,7 +1570,8 @@
1541
1570
  " **MDE_SETTINGS,\n",
1542
1571
  ")\n",
1543
1572
  "display(show_summary(did_result.summary))\n",
1544
- "display(show_frame(did_result.details[\"coefficients\"]))"
1573
+ "display(show_frame(did_result.details[\"coefficients\"]))\n",
1574
+ "display(did_result.figures[\"did_effect\"])"
1545
1575
  ]
1546
1576
  },
1547
1577
  {
@@ -1560,10 +1590,10 @@
1560
1590
  "id": "6ff8ba4a",
1561
1591
  "metadata": {
1562
1592
  "execution": {
1563
- "iopub.execute_input": "2026-08-18T18:11:53.621238Z",
1564
- "iopub.status.busy": "2026-08-18T18:11:53.621084Z",
1565
- "iopub.status.idle": "2026-08-18T18:11:53.624363Z",
1566
- "shell.execute_reply": "2026-08-18T18:11:53.623869Z"
1593
+ "iopub.execute_input": "2026-08-19T05:11:03.239531Z",
1594
+ "iopub.status.busy": "2026-08-19T05:11:03.239433Z",
1595
+ "iopub.status.idle": "2026-08-19T05:11:03.242564Z",
1596
+ "shell.execute_reply": "2026-08-19T05:11:03.241766Z"
1567
1597
  }
1568
1598
  },
1569
1599
  "outputs": [
@@ -1599,10 +1629,10 @@
1599
1629
  "id": "523adfbb",
1600
1630
  "metadata": {
1601
1631
  "execution": {
1602
- "iopub.execute_input": "2026-08-18T18:11:53.626301Z",
1603
- "iopub.status.busy": "2026-08-18T18:11:53.626183Z",
1604
- "iopub.status.idle": "2026-08-18T18:11:53.728843Z",
1605
- "shell.execute_reply": "2026-08-18T18:11:53.728381Z"
1632
+ "iopub.execute_input": "2026-08-19T05:11:03.244624Z",
1633
+ "iopub.status.busy": "2026-08-19T05:11:03.244433Z",
1634
+ "iopub.status.idle": "2026-08-19T05:11:03.528854Z",
1635
+ "shell.execute_reply": "2026-08-19T05:11:03.528266Z"
1606
1636
  }
1607
1637
  },
1608
1638
  "outputs": [
@@ -1729,10 +1759,10 @@
1729
1759
  "id": "87833fe4",
1730
1760
  "metadata": {
1731
1761
  "execution": {
1732
- "iopub.execute_input": "2026-08-18T18:11:53.730596Z",
1733
- "iopub.status.busy": "2026-08-18T18:11:53.730458Z",
1734
- "iopub.status.idle": "2026-08-18T18:11:53.736414Z",
1735
- "shell.execute_reply": "2026-08-18T18:11:53.735867Z"
1762
+ "iopub.execute_input": "2026-08-19T05:11:03.531370Z",
1763
+ "iopub.status.busy": "2026-08-19T05:11:03.531223Z",
1764
+ "iopub.status.idle": "2026-08-19T05:11:03.537615Z",
1765
+ "shell.execute_reply": "2026-08-19T05:11:03.536709Z"
1736
1766
  }
1737
1767
  },
1738
1768
  "outputs": [
@@ -1810,10 +1840,10 @@
1810
1840
  "id": "ebf028ab",
1811
1841
  "metadata": {
1812
1842
  "execution": {
1813
- "iopub.execute_input": "2026-08-18T18:11:53.738152Z",
1814
- "iopub.status.busy": "2026-08-18T18:11:53.737945Z",
1815
- "iopub.status.idle": "2026-08-18T18:11:56.847453Z",
1816
- "shell.execute_reply": "2026-08-18T18:11:56.846755Z"
1843
+ "iopub.execute_input": "2026-08-19T05:11:03.539639Z",
1844
+ "iopub.status.busy": "2026-08-19T05:11:03.539467Z",
1845
+ "iopub.status.idle": "2026-08-19T05:11:06.924485Z",
1846
+ "shell.execute_reply": "2026-08-19T05:11:06.923547Z"
1817
1847
  }
1818
1848
  },
1819
1849
  "outputs": [
@@ -1951,6 +1981,15 @@
1951
1981
  },
1952
1982
  "metadata": {},
1953
1983
  "output_type": "display_data"
1984
+ },
1985
+ {
1986
+ "data": {
1987
+ "text/plain": [
1988
+ "<Figure size 1200x400 with 1 Axes>"
1989
+ ]
1990
+ },
1991
+ "metadata": {},
1992
+ "output_type": "display_data"
1954
1993
  }
1955
1994
  ],
1956
1995
  "source": [
@@ -1961,7 +2000,8 @@
1961
2000
  " intervention_time=INTERVENTION_TIME,\n",
1962
2001
  " fpr_placebo_count=200,\n",
1963
2002
  ")\n",
1964
- "display(show_checks(its_diagnostics.checks))"
2003
+ "display(show_checks(its_diagnostics.checks))\n",
2004
+ "display(its_diagnostics.figures[\"pre_fit\"])"
1965
2005
  ]
1966
2006
  },
1967
2007
  {
@@ -1970,10 +2010,10 @@
1970
2010
  "id": "50affca8",
1971
2011
  "metadata": {
1972
2012
  "execution": {
1973
- "iopub.execute_input": "2026-08-18T18:11:56.848757Z",
1974
- "iopub.status.busy": "2026-08-18T18:11:56.848655Z",
1975
- "iopub.status.idle": "2026-08-18T18:11:56.919201Z",
1976
- "shell.execute_reply": "2026-08-18T18:11:56.918600Z"
2013
+ "iopub.execute_input": "2026-08-19T05:11:06.925931Z",
2014
+ "iopub.status.busy": "2026-08-19T05:11:06.925795Z",
2015
+ "iopub.status.idle": "2026-08-19T05:11:06.996372Z",
2016
+ "shell.execute_reply": "2026-08-19T05:11:06.995573Z"
1977
2017
  }
1978
2018
  },
1979
2019
  "outputs": [
@@ -2257,10 +2297,10 @@
2257
2297
  "id": "9c501c9b",
2258
2298
  "metadata": {
2259
2299
  "execution": {
2260
- "iopub.execute_input": "2026-08-18T18:11:56.920545Z",
2261
- "iopub.status.busy": "2026-08-18T18:11:56.920432Z",
2262
- "iopub.status.idle": "2026-08-18T18:11:56.922989Z",
2263
- "shell.execute_reply": "2026-08-18T18:11:56.922573Z"
2300
+ "iopub.execute_input": "2026-08-19T05:11:06.998607Z",
2301
+ "iopub.status.busy": "2026-08-19T05:11:06.998379Z",
2302
+ "iopub.status.idle": "2026-08-19T05:11:07.002063Z",
2303
+ "shell.execute_reply": "2026-08-19T05:11:07.001109Z"
2264
2304
  }
2265
2305
  },
2266
2306
  "outputs": [
@@ -2296,10 +2336,10 @@
2296
2336
  "id": "5bd00664",
2297
2337
  "metadata": {
2298
2338
  "execution": {
2299
- "iopub.execute_input": "2026-08-18T18:11:56.924309Z",
2300
- "iopub.status.busy": "2026-08-18T18:11:56.924166Z",
2301
- "iopub.status.idle": "2026-08-18T18:11:57.193944Z",
2302
- "shell.execute_reply": "2026-08-18T18:11:57.193385Z"
2339
+ "iopub.execute_input": "2026-08-19T05:11:07.004105Z",
2340
+ "iopub.status.busy": "2026-08-19T05:11:07.003957Z",
2341
+ "iopub.status.idle": "2026-08-19T05:11:07.272721Z",
2342
+ "shell.execute_reply": "2026-08-19T05:11:07.271568Z"
2303
2343
  }
2304
2344
  },
2305
2345
  "outputs": [
@@ -2470,10 +2510,10 @@
2470
2510
  "id": "015ecfd6",
2471
2511
  "metadata": {
2472
2512
  "execution": {
2473
- "iopub.execute_input": "2026-08-18T18:11:57.195405Z",
2474
- "iopub.status.busy": "2026-08-18T18:11:57.195291Z",
2475
- "iopub.status.idle": "2026-08-18T18:11:59.704930Z",
2476
- "shell.execute_reply": "2026-08-18T18:11:59.704063Z"
2513
+ "iopub.execute_input": "2026-08-19T05:11:07.274753Z",
2514
+ "iopub.status.busy": "2026-08-19T05:11:07.274601Z",
2515
+ "iopub.status.idle": "2026-08-19T05:11:09.908425Z",
2516
+ "shell.execute_reply": "2026-08-19T05:11:09.907535Z"
2477
2517
  }
2478
2518
  },
2479
2519
  "outputs": [
@@ -2597,6 +2637,15 @@
2597
2637
  },
2598
2638
  "metadata": {},
2599
2639
  "output_type": "display_data"
2640
+ },
2641
+ {
2642
+ "data": {
2643
+ "text/plain": [
2644
+ "<Figure size 1200x400 with 1 Axes>"
2645
+ ]
2646
+ },
2647
+ "metadata": {},
2648
+ "output_type": "display_data"
2600
2649
  }
2601
2650
  ],
2602
2651
  "source": [
@@ -2610,7 +2659,8 @@
2610
2659
  " max_controls=4,\n",
2611
2660
  " fpr_placebo_count=200,\n",
2612
2661
  ")\n",
2613
- "display(show_checks(causal_impact_assumptions.checks))"
2662
+ "display(show_checks(causal_impact_assumptions.checks))\n",
2663
+ "display(causal_impact_assumptions.figures[\"pre_fit\"])"
2614
2664
  ]
2615
2665
  },
2616
2666
  {
@@ -2619,10 +2669,10 @@
2619
2669
  "id": "f71db402",
2620
2670
  "metadata": {
2621
2671
  "execution": {
2622
- "iopub.execute_input": "2026-08-18T18:11:59.706397Z",
2623
- "iopub.status.busy": "2026-08-18T18:11:59.706262Z",
2624
- "iopub.status.idle": "2026-08-18T18:11:59.784124Z",
2625
- "shell.execute_reply": "2026-08-18T18:11:59.783600Z"
2672
+ "iopub.execute_input": "2026-08-19T05:11:09.910444Z",
2673
+ "iopub.status.busy": "2026-08-19T05:11:09.910270Z",
2674
+ "iopub.status.idle": "2026-08-19T05:11:09.990487Z",
2675
+ "shell.execute_reply": "2026-08-19T05:11:09.989567Z"
2626
2676
  }
2627
2677
  },
2628
2678
  "outputs": [
@@ -2916,10 +2966,10 @@
2916
2966
  "id": "2eec09cf",
2917
2967
  "metadata": {
2918
2968
  "execution": {
2919
- "iopub.execute_input": "2026-08-18T18:11:59.785431Z",
2920
- "iopub.status.busy": "2026-08-18T18:11:59.785313Z",
2921
- "iopub.status.idle": "2026-08-18T18:11:59.788363Z",
2922
- "shell.execute_reply": "2026-08-18T18:11:59.787576Z"
2969
+ "iopub.execute_input": "2026-08-19T05:11:09.992523Z",
2970
+ "iopub.status.busy": "2026-08-19T05:11:09.992414Z",
2971
+ "iopub.status.idle": "2026-08-19T05:11:09.995718Z",
2972
+ "shell.execute_reply": "2026-08-19T05:11:09.995173Z"
2923
2973
  }
2924
2974
  },
2925
2975
  "outputs": [
@@ -2956,10 +3006,10 @@
2956
3006
  "id": "eb6ee5b7",
2957
3007
  "metadata": {
2958
3008
  "execution": {
2959
- "iopub.execute_input": "2026-08-18T18:11:59.790020Z",
2960
- "iopub.status.busy": "2026-08-18T18:11:59.789874Z",
2961
- "iopub.status.idle": "2026-08-18T18:12:00.604892Z",
2962
- "shell.execute_reply": "2026-08-18T18:12:00.604198Z"
3009
+ "iopub.execute_input": "2026-08-19T05:11:09.997830Z",
3010
+ "iopub.status.busy": "2026-08-19T05:11:09.997647Z",
3011
+ "iopub.status.idle": "2026-08-19T05:11:10.919476Z",
3012
+ "shell.execute_reply": "2026-08-19T05:11:10.918196Z"
2963
3013
  }
2964
3014
  },
2965
3015
  "outputs": [
@@ -1131,4 +1131,4 @@ its_result = fit_its(
1131
1131
 
1132
1132
  ## Версия
1133
1133
 
1134
- Текущая версия пакета: `1.0.2`
1134
+ Текущая версия пакета: `1.0.4`
@@ -2,7 +2,7 @@ from post_analysis_toolkit.compat import ensure_runtime_compat
2
2
 
3
3
  ensure_runtime_compat()
4
4
 
5
- __version__ = "1.0.2"
5
+ __version__ = "1.0.4"
6
6
 
7
7
  from post_analysis_toolkit.causal_impact import (
8
8
  check_causal_impact_assumptions,
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  from collections.abc import Iterable
4
4
  from contextlib import redirect_stdout
5
5
  import io
6
+ import warnings
6
7
 
7
8
  import numpy as np
8
9
  import pandas as pd
@@ -159,6 +160,8 @@ def _causal_impact_placebo_fpr(
159
160
  alpha: float = 0.05,
160
161
  ) -> tuple[int, int]:
161
162
  dates = sorted(pd.to_datetime(df[time_col]).drop_duplicates())
163
+ if placebo_count <= 0:
164
+ return 0, 0
162
165
  pre_dates = [date for date in dates if date < intervention_time]
163
166
  placebo_dates = [
164
167
  date for date in pre_dates
@@ -189,6 +192,7 @@ def _causal_impact_placebo_fpr(
189
192
  upper = float(result.summary.iloc[0]["average_abs_effect_upper"])
190
193
  interval_low, interval_high = sorted((lower, upper)) if np.isfinite(lower) and np.isfinite(upper) else (np.nan, np.nan)
191
194
  significant = (np.isfinite(tail_area) and tail_area < alpha) or (np.isfinite(interval_low) and np.isfinite(interval_high) and (interval_low > 0 or interval_high < 0))
195
+ close_method_result_figures(result)
192
196
  if np.isfinite(tail_area) or (np.isfinite(lower) and np.isfinite(upper)):
193
197
  runs += 1
194
198
  false_positives += int(significant)
@@ -430,8 +434,20 @@ def fit_causal_impact(
430
434
  _ensure_nonempty_analysis_periods(ci_df, pre_period, post_period)
431
435
 
432
436
  validation_stats = _split_pre_validation(ci_df, pre_period)
433
- ci_model = CausalImpact(ci_df, list(pre_period), list(post_period))
434
- ci_model.run()
437
+ # statsmodels' HP filter emits a harmless SparseEfficiencyWarning on
438
+ # Databricks' scipy/statsmodels combinations. CausalImpact does not
439
+ # expose the HP-filter matrix, so there is no actionable input here;
440
+ # suppress only this narrowly identified warning around the third-party
441
+ # model execution (including placebo/robustness calls through this
442
+ # function).
443
+ with warnings.catch_warnings():
444
+ warnings.filterwarnings(
445
+ "ignore",
446
+ message=r"spsolve requires A be CSC or CSR matrix format",
447
+ module=r"statsmodels\.tsa\.filters\.hp_filter",
448
+ )
449
+ ci_model = CausalImpact(ci_df, list(pre_period), list(post_period))
450
+ ci_model.run()
435
451
  summary = _extract_ci_summary(ci_model, post_period)
436
452
  post_obs = int(((ci_df.index >= pd.Timestamp(post_period[0])) & (ci_df.index <= pd.Timestamp(post_period[1]))).sum())
437
453
  baseline = float(ci_df.loc[(ci_df.index >= pd.Timestamp(pre_period[0])) & (ci_df.index <= pd.Timestamp(pre_period[1])), "y"].mean())
@@ -43,7 +43,7 @@ from post_analysis_toolkit.utils import (
43
43
  safe_corr,
44
44
  restrict_to_pre_period_window,
45
45
  )
46
- from post_analysis_toolkit.visualization import plot_estimate_series, plot_event_study, plot_pretrend_triptych
46
+ from post_analysis_toolkit.visualization import plot_did_effect, plot_estimate_series, plot_event_study, plot_pretrend_triptych
47
47
 
48
48
 
49
49
  def _prepare_did_data(
@@ -420,6 +420,26 @@ def fit_did_regression(
420
420
  .sort_values("term")
421
421
  .reset_index(drop=True)
422
422
  )
423
+ grouped = (
424
+ work_df.assign(_time=pd.to_datetime(work_df[time_col]))
425
+ .groupby(["_time", treated_col], as_index=False)[analysis_outcome_col]
426
+ .mean()
427
+ )
428
+ treated_series = grouped.loc[grouped[treated_col].astype(int) == 1, ["_time", analysis_outcome_col]].rename(columns={analysis_outcome_col: "treated_actual"})
429
+ control_series = grouped.loc[grouped[treated_col].astype(int) == 0, ["_time", analysis_outcome_col]].rename(columns={analysis_outcome_col: "control_actual"})
430
+ did_series = treated_series.merge(control_series, on="_time", how="outer").sort_values("_time")
431
+ intervention_ts = pd.Timestamp(work_df.loc[work_df[post_col].astype(int) == 1, time_col].min()) if post_obs else None
432
+ if intervention_ts is not None:
433
+ treated_pre = float(did_series.loc[did_series["_time"] < intervention_ts, "treated_actual"].mean())
434
+ control_pre = float(did_series.loc[did_series["_time"] < intervention_ts, "control_actual"].mean())
435
+ did_series["treated_counterfactual"] = np.where(
436
+ did_series["_time"] >= intervention_ts,
437
+ treated_pre + did_series["control_actual"] - control_pre,
438
+ np.nan,
439
+ )
440
+ else:
441
+ did_series["treated_counterfactual"] = np.nan
442
+ did_series = did_series.rename(columns={"_time": time_col})
423
443
  return MethodResult(
424
444
  method=label,
425
445
  summary=summary_df,
@@ -428,6 +448,14 @@ def fit_did_regression(
428
448
  "analysis_frame": work_df,
429
449
  "mde_power_curve": mde["power_curve"],
430
450
  },
451
+ figures={
452
+ "did_effect": plot_did_effect(
453
+ did_series,
454
+ time_col=time_col,
455
+ intervention_time=intervention_ts,
456
+ title=f"{label} | {outcome_col}",
457
+ )
458
+ },
431
459
  metadata={
432
460
  "fit": fit,
433
461
  "mde_mode": mde_mode,
@@ -827,6 +855,8 @@ def _did_placebo_fpr(
827
855
  ) -> tuple[int, int]:
828
856
  pre_df = work_df.loc[work_df[time_col] < intervention_time].copy()
829
857
  dates = sorted(pre_df[time_col].drop_duplicates())
858
+ if not dates or placebo_count <= 0:
859
+ return 0, 0
830
860
  placebo_dates = [date for date in dates if date >= dates[0] + pd.Timedelta(days=28)]
831
861
  if placebo_dates:
832
862
  indexes = np.linspace(0, len(placebo_dates) - 1, min(placebo_count, len(placebo_dates)), dtype=int)
@@ -849,6 +879,7 @@ def _did_placebo_fpr(
849
879
  mde_mode="none",
850
880
  )
851
881
  pvalues.append(float(result.summary.iloc[0]["p_value"]))
882
+ close_method_result_figures(result)
852
883
  except Exception:
853
884
  continue
854
885
  return len(pvalues), int(sum(p < alpha for p in pvalues))
@@ -208,6 +208,8 @@ def _its_placebo_fpr(
208
208
  ) -> tuple[int, int]:
209
209
  pre_df = work_df.loc[work_df[time_col] < intervention_time].copy()
210
210
  dates = sorted(pre_df[time_col].drop_duplicates())
211
+ if not dates or placebo_count <= 0:
212
+ return 0, 0
211
213
  placebo_dates = [
212
214
  date for date in dates
213
215
  if date >= dates[0] + pd.Timedelta(days=42)
@@ -230,6 +232,7 @@ def _its_placebo_fpr(
230
232
  )
231
233
  coefficients = result.details["coefficients"]
232
234
  level = coefficients.loc[coefficients["term"] == "post_flag", "p_value"]
235
+ close_method_result_figures(result)
233
236
  if level.empty:
234
237
  continue
235
238
  runs += 1
@@ -95,6 +95,34 @@ def plot_actual_vs_counterfactual(
95
95
  return fig
96
96
 
97
97
 
98
+ def plot_did_effect(
99
+ series_df: pd.DataFrame,
100
+ time_col: str,
101
+ treated_actual_col: str = "treated_actual",
102
+ control_actual_col: str = "control_actual",
103
+ treated_counterfactual_col: str = "treated_counterfactual",
104
+ intervention_time: str | pd.Timestamp | None = None,
105
+ title: str = "DiD effect",
106
+ ):
107
+ """Plot DiD group means, treated counterfactual, and post-period gap."""
108
+ plot_df = series_df.sort_values(time_col).copy()
109
+ fig, ax = plt.subplots(1, 1, figsize=(12, 4))
110
+ ax.plot(plot_df[time_col], plot_df[treated_actual_col], label="treated actual", linewidth=2)
111
+ ax.plot(plot_df[time_col], plot_df[control_actual_col], label="control actual", linewidth=2)
112
+ post_mask = plot_df[treated_counterfactual_col].notna()
113
+ ax.plot(plot_df.loc[post_mask, time_col], plot_df.loc[post_mask, treated_counterfactual_col], label="treated counterfactual", linestyle="--", linewidth=2)
114
+ ax.fill_between(plot_df.loc[post_mask, time_col], plot_df.loc[post_mask, treated_actual_col], plot_df.loc[post_mask, treated_counterfactual_col], alpha=0.2, label="DiD effect")
115
+ if intervention_time is not None:
116
+ ax.axvline(pd.Timestamp(intervention_time), color="black", linestyle="--", linewidth=1)
117
+ ax.set_title(title)
118
+ ax.set_xlabel(time_col)
119
+ ax.set_ylabel("outcome")
120
+ ax.grid(alpha=0.25)
121
+ ax.legend()
122
+ plt.tight_layout()
123
+ return fig
124
+
125
+
98
126
  def plot_gap_series(
99
127
  gap_df: pd.DataFrame,
100
128
  time_col: str,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: post-analysis-toolkit
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: Reusable helpers for post-analysis, quasi-causal designs, and diagnostics.
5
5
  Author: OpenAI Codex
6
6
  Requires-Python: >=3.11
@@ -1151,4 +1151,4 @@ its_result = fit_its(
1151
1151
 
1152
1152
  ## Версия
1153
1153
 
1154
- Текущая версия пакета: `1.0.2`
1154
+ Текущая версия пакета: `1.0.4`
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "post-analysis-toolkit"
7
- version = "1.0.2"
7
+ version = "1.0.4"
8
8
  description = "Reusable helpers for post-analysis, quasi-causal designs, and diagnostics."
9
9
  readme = "post_analysis_toolkit/README.md"
10
10
  requires-python = ">=3.11"