diff --git a/lifelines/fitters/__init__.py b/lifelines/fitters/__init__.py index 87db76201..d05f8ba92 100644 --- a/lifelines/fitters/__init__.py +++ b/lifelines/fitters/__init__.py @@ -295,7 +295,7 @@ def __init__(self, *args, **kwargs): @property def AIC_(self) -> float: - return -2 * self.log_likelihood_ + 2 * self.params_.shape[0] + return -2 * self.log_likelihood_ + 2 * self._fitted_parameters_.shape[0] def _check_cumulative_hazard_is_monotone_and_positive(self, durations, values): class_name = self._class_name @@ -1940,7 +1940,7 @@ def _compute_variance_matrix(self) -> np.array: Some ways to possible ways fix this: 0. Are there any lifelines warnings outputted during the `fit`? - 1. Inspect your DataFrame: does everything look as expected? + 1. Inspect your DataFrame: does everything look as expected? Do you need to add/drop a constant (intercept) column? 2. Is there high-collinearity in the dataset? Try using the variance inflation factor (VIF) to find redundant variables. 3. Trying adding a small penalizer (or changing it, if already present). Example: `%s(penalizer=0.01).fit(...)`. 4. Are there any extreme outliers? Try modeling them or dropping them to see if it helps convergence. @@ -2130,8 +2130,8 @@ def print_summary(self, decimals=2, style=None, **kwargs): sr = self.log_likelihood_ratio_test() footers = [] - if CensoringType.is_right_censoring(self): - footers.apoend(("Concordance", "{:.{prec}f}".format(self.concordance_index_, prec=decimals))) + if utils.CensoringType.is_right_censoring(self): + footers.append(("Concordance", "{:.{prec}f}".format(self.concordance_index_, prec=decimals))) footers.extend( [ diff --git a/lifelines/tests/test_estimation.py b/lifelines/tests/test_estimation.py index ad15c1f63..2c63a4386 100644 --- a/lifelines/tests/test_estimation.py +++ b/lifelines/tests/test_estimation.py @@ -228,6 +228,14 @@ def _cumulative_hazard(self, params, times): assert coef - std > lower assert coef + std < upper + def test_AIC_on_models(self, known_parametric_univariate_fitters): + T = np.random.exponential(1, size=1000) + + for fitter in known_parametric_univariate_fitters: + f = fitter().fit(T) + assert f.AIC_ > 0 + npt.assert_allclose(f.AIC_, -2 * f.log_likelihood_ + 2 * f.summary.shape[0]) + def test_models_can_handle_really_large_duration_values(self, known_parametric_univariate_fitters): T1 = np.random.exponential(1e12, size=1000) T2 = np.random.exponential(1e12, size=1000) @@ -1613,18 +1621,22 @@ def rossi(self): rossi["_int"] = 1.0 return rossi + def test_AIC_on_models(self, rossi): + model = WeibullAFTFitter(fit_intercept=False).fit(rossi, "week", "arrest") + npt.assert_allclose(model.AIC_, -2 * model.log_likelihood_ + 2 * model.summary.shape[0]) + def test_penalizer_can_be_an_array(self, rossi): - wf_array = WeibullAFTFitter(penalizer=0.01 * np.ones(7)).fit(rossi, "week", "arrest") - wf_float = WeibullAFTFitter(penalizer=0.01).fit(rossi, "week", "arrest") + wf_array = WeibullAFTFitter(penalizer=0.01 * np.ones(7), fit_intercept=False).fit(rossi, "week", "arrest") + wf_float = WeibullAFTFitter(penalizer=0.01, fit_intercept=False).fit(rossi, "week", "arrest") assert_frame_equal(wf_array.summary, wf_float.summary) def test_penalizer_can_be_an_array_and_check_it_behaves_as_expected(self, rossi): penalty = np.array([0, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]) - wf_array = WeibullAFTFitter(penalizer=penalty).fit(rossi, "week", "arrest") - wf_float = WeibullAFTFitter(penalizer=0.01).fit(rossi, "week", "arrest") + wf_array = WeibullAFTFitter(penalizer=penalty, fit_intercept=False).fit(rossi, "week", "arrest") + wf_float = WeibullAFTFitter(penalizer=0.01, fit_intercept=False).fit(rossi, "week", "arrest") assert abs(wf_array.summary.loc[("lambda_", "fin"), "coef"]) > abs(wf_float.summary.loc[("lambda_", "fin"), "coef"]) @@ -2696,6 +2708,10 @@ def test_penalizer_can_be_an_array_and_check_it_behaves_as_expected(self, rossi) assert abs(cph_array.summary.loc["fin", "coef"]) > abs(cph_float.summary.loc["fin", "coef"]) + def test_AIC_partial_(self, cph, rossi): + cph.fit(rossi, "week", "arrest") + npt.assert_allclose(cph.AIC_partial_, -2 * cph.log_likelihood_ + 2 * cph.summary.shape[0]) + def test_compute_followup_hazard_ratios(self, cph, cph_spline, rossi): cph.fit(rossi, "week", "arrest") cph.compute_followup_hazard_ratios(rossi, [15, 25, 35, 45])