@@ -45,6 +45,7 @@ class ExperimentalDesign:
45
45
"""Base class for experiment designs"""
46
46
47
47
model = None
48
+ expt_type = None
48
49
outcome_variable_name = None
49
50
50
51
def __init__ (self , model = None , ** kwargs ):
@@ -53,6 +54,24 @@ def __init__(self, model=None, **kwargs):
53
54
if self .model is None :
54
55
raise ValueError ("fitting_model not set or passed." )
55
56
57
+ def print_coefficients (self , round_to = None ) -> None :
58
+ """
59
+ Prints the model coefficients
60
+
61
+ :param round_to:
62
+ Number of decimals used to round results. Defaults to 2. Use "None" to return raw numbers.
63
+ """
64
+ print ("Model coefficients:" )
65
+ # Determine the width of the longest label
66
+ max_label_length = max (len (name ) for name in self .labels )
67
+ # Print each coefficient with formatted alignment
68
+ for name , val in zip (self .labels , self .model .coef_ [0 ]):
69
+ # Left-align the name
70
+ formatted_name = f"{ name :<{max_label_length }} "
71
+ # Right-align the value with width 10
72
+ formatted_val = f"{ round_num (val , round_to ):>10} "
73
+ print (f" { formatted_name } \t { formatted_val } " )
74
+
56
75
57
76
class PrePostFit (ExperimentalDesign , PrePostFitDataValidator ):
58
77
"""
@@ -95,6 +114,8 @@ def __init__(
95
114
super ().__init__ (model = model , ** kwargs )
96
115
self ._input_validation (data , treatment_time )
97
116
self .treatment_time = treatment_time
117
+ # set experiment type - usually done in subclasses
118
+ self .expt_type = "Pre-Post Fit"
98
119
# split data in to pre and post intervention
99
120
self .datapre = data [data .index < self .treatment_time ]
100
121
self .datapost = data [data .index >= self .treatment_time ]
@@ -103,10 +124,10 @@ def __init__(
103
124
104
125
# set things up with pre-intervention data
105
126
y , X = dmatrices (formula , self .datapre )
127
+ self .outcome_variable_name = y .design_info .column_names [0 ]
106
128
self ._y_design_info = y .design_info
107
129
self ._x_design_info = X .design_info
108
130
self .labels = X .design_info .column_names
109
- self .outcome_variable_name = y .design_info .column_names [0 ]
110
131
self .pre_y , self .pre_X = np .asarray (y ), np .asarray (X )
111
132
# process post-intervention data
112
133
(new_y , new_x ) = build_design_matrices (
@@ -222,6 +243,18 @@ def plot_coeffs(self):
222
243
palette = sns .color_palette ("husl" ),
223
244
)
224
245
246
+ def summary (self , round_to = None ) -> None :
247
+ """
248
+ Print text output summarising the results
249
+
250
+ :param round_to:
251
+ Number of decimals used to round results. Defaults to 2. Use "None" to return raw numbers.
252
+ """
253
+
254
+ print (f"{ self .expt_type :=^80} " )
255
+ print (f"Formula: { self .formula } " )
256
+ self .print_coefficients (round_to )
257
+
225
258
226
259
class InterruptedTimeSeries (PrePostFit ):
227
260
"""
@@ -253,7 +286,6 @@ class InterruptedTimeSeries(PrePostFit):
253
286
... formula="y ~ 1 + t + C(month)",
254
287
... model = LinearRegression()
255
288
... )
256
-
257
289
"""
258
290
259
291
expt_type = "Interrupted Time Series"
@@ -351,6 +383,7 @@ def __init__(
351
383
):
352
384
super ().__init__ (model = model , ** kwargs )
353
385
self .data = data
386
+ self .expt_type = "Difference in Differences"
354
387
self .formula = formula
355
388
self .time_variable_name = time_variable_name
356
389
self .group_variable_name = group_variable_name
@@ -509,6 +542,20 @@ def plot(self, round_to=None):
509
542
ax .legend (fontsize = LEGEND_FONT_SIZE )
510
543
return (fig , ax )
511
544
545
+ def summary (self , round_to = None ) -> None :
546
+ """
547
+ Print text output summarising the results.
548
+
549
+ :param round_to:
550
+ Number of decimals used to round results. Defaults to 2. Use "None" to return raw numbers.
551
+ """
552
+
553
+ print (f"{ self .expt_type :=^80} " )
554
+ print (f"Formula: { self .formula } " )
555
+ print ("\n Results:" )
556
+ print (f"Causal impact = { round_num (self .causal_impact [0 ], round_to )} " )
557
+ self .print_coefficients (round_to )
558
+
512
559
513
560
class RegressionDiscontinuity (ExperimentalDesign , RDDataValidator ):
514
561
"""
@@ -542,17 +589,6 @@ class RegressionDiscontinuity(ExperimentalDesign, RDDataValidator):
542
589
... model=LinearRegression(),
543
590
... treatment_threshold=0.5,
544
591
... )
545
- >>> result.summary() # doctest: +NORMALIZE_WHITESPACE,+NUMBER
546
- Difference in Differences experiment
547
- Formula: y ~ 1 + x + treated
548
- Running variable: x
549
- Threshold on running variable: 0.5
550
- Results:
551
- Discontinuity at threshold = 0.19
552
- Model coefficients:
553
- Intercept 0.0
554
- treated[T.True] 0.19
555
- x 1.23
556
592
"""
557
593
558
594
def __init__ (
@@ -687,16 +723,18 @@ def plot(self, round_to=None):
687
723
ax .legend (fontsize = LEGEND_FONT_SIZE )
688
724
return (fig , ax )
689
725
690
- def summary (self ) :
726
+ def summary (self , round_to = None ) -> None :
691
727
"""
692
728
Print text output summarising the results
729
+
730
+ :param round_to:
731
+ Number of decimals used to round results. Defaults to 2. Use "None" to return raw numbers.
693
732
"""
694
733
print ("Difference in Differences experiment" )
695
734
print (f"Formula: { self .formula } " )
696
735
print (f"Running variable: { self .running_variable_name } " )
697
736
print (f"Threshold on running variable: { self .treatment_threshold } " )
698
737
print ("\n Results:" )
699
738
print (f"Discontinuity at threshold = { self .discontinuity_at_threshold :.2f} " )
700
- print ("Model coefficients:" )
701
- for name , val in zip (self .labels , self .model .coef_ [0 ]):
702
- print (f"\t { name } \t \t { val } " )
739
+ print ("\n " )
740
+ self .print_coefficients (round_to )
0 commit comments