From bc86b90f2592e34bc4a4a85a273fcb8f11d12bb7 Mon Sep 17 00:00:00 2001 From: Russell Richie Date: Fri, 5 Jun 2026 21:38:57 -0400 Subject: [PATCH 1/2] Fix MissingDataError in multi_mra when features contain NaN Real-world property data routinely has missing values (e.g. parking spaces, garage counts, central air) that survive as NaN through the one-hot encoding and astype(float) steps. statsmodels OLS raises MissingDataError on any NaN in the exog matrix. Guard by imputing NaN with training-set column medians before the global OLS fit, then propagate the same medians to X_test, X_sales, and X_univ so all splits are treated consistently. This is the same median-imputation pattern already applied in utilities/stats.py (calc_elastic_net_regularization, calc_p_values_recursive_drop, etc.) and accepted upstream in PRs #313, #316, #318. Co-Authored-By: Claude Sonnet 4.6 --- openavmkit/modeling.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openavmkit/modeling.py b/openavmkit/modeling.py index d92bd718..3a733bac 100644 --- a/openavmkit/modeling.py +++ b/openavmkit/modeling.py @@ -2039,6 +2039,17 @@ def _run_multi_mra( has_const = "const" in X_train + # Impute NaN with training-set column medians and propagate to all splits. + # Real-world property data routinely has missing values in features like + # parking spaces, garage counts, etc. statsmodels OLS raises + # MissingDataError on any NaN in exog, so we guard here using the same + # median-imputation pattern applied elsewhere in the codebase. + col_medians = X_train.median() + X_train = X_train.fillna(col_medians) + ds_prepped.X_test = ds_prepped.X_test.fillna(col_medians) + ds_prepped.X_sales = ds_prepped.X_sales.fillna(col_medians) + ds_prepped.X_univ = ds_prepped.X_univ.fillna(col_medians) + # ------------------------ # Global OLS (fallback) # ------------------------ From 0f1bd6c50cc1dc3d91828986859ec055c28e52b2 Mon Sep 17 00:00:00 2001 From: Russell Richie Date: Fri, 5 Jun 2026 23:14:04 -0400 Subject: [PATCH 2/2] Fix MissingDataError in multi_mra: handle inf and all-NaN columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The initial fix only called fillna(col_medians), but two further cases caused the error to persist: - inf values (e.g. FAR = area/land_area where land_area=0) are not caught by fillna; replace inf→NaN first. - Columns that are entirely NaN produce median()=NaN, making fillna a no-op; fall back to 0.0 for such columns. Co-Authored-By: Claude Sonnet 4.6 --- openavmkit/modeling.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/openavmkit/modeling.py b/openavmkit/modeling.py index 3a733bac..a62582c3 100644 --- a/openavmkit/modeling.py +++ b/openavmkit/modeling.py @@ -2039,16 +2039,19 @@ def _run_multi_mra( has_const = "const" in X_train - # Impute NaN with training-set column medians and propagate to all splits. - # Real-world property data routinely has missing values in features like - # parking spaces, garage counts, etc. statsmodels OLS raises - # MissingDataError on any NaN in exog, so we guard here using the same - # median-imputation pattern applied elsewhere in the codebase. - col_medians = X_train.median() + # Impute NaN/inf with training-set column medians and propagate to all splits. + # Real-world property data routinely has missing values (parking spaces, + # garage counts, etc.) and derived features can produce inf (e.g. FAR when + # land_area=0). statsmodels OLS raises MissingDataError on any NaN or inf + # in exog, so we replace inf→NaN first, then median-impute. Columns that + # are entirely NaN (no finite training values) fall back to 0. + # This is the same guard pattern used in utilities/stats.py. + X_train = X_train.replace([np.inf, -np.inf], np.nan) + col_medians = X_train.median().fillna(0.0) X_train = X_train.fillna(col_medians) - ds_prepped.X_test = ds_prepped.X_test.fillna(col_medians) - ds_prepped.X_sales = ds_prepped.X_sales.fillna(col_medians) - ds_prepped.X_univ = ds_prepped.X_univ.fillna(col_medians) + ds_prepped.X_test = ds_prepped.X_test.replace([np.inf, -np.inf], np.nan).fillna(col_medians) + ds_prepped.X_sales = ds_prepped.X_sales.replace([np.inf, -np.inf], np.nan).fillna(col_medians) + ds_prepped.X_univ = ds_prepped.X_univ.replace([np.inf, -np.inf], np.nan).fillna(col_medians) # ------------------------ # Global OLS (fallback)