@@ -598,6 +598,156 @@ def _newton_armijo_mv_nw_numba(
598598 return h
599599
600600
601+ def nw_predict (
602+ x_train : ArrayLike ,
603+ y_train : ArrayLike ,
604+ x_test : ArrayLike ,
605+ h : float ,
606+ kernel : str = "gauss" ,
607+ ) -> NDArray [Any ]:
608+ """Nadaraya-Watson kernel regression predictions.
609+
610+ Parameters
611+ ----------
612+ x_train
613+ Training predictor values (1D array-like).
614+ y_train
615+ Training response values (1D array-like).
616+ x_test
617+ Test predictor values where predictions are desired (1D array-like).
618+ h
619+ Bandwidth (obtained from nw_bandwidth).
620+ kernel
621+ Kernel function: "gauss", "epan", "unif", "biweight", "triweight", or "cosine".
622+
623+ Returns
624+ -------
625+ NDArray
626+ Predicted values at x_test locations.
627+
628+ Examples
629+ --------
630+ >>> import numpy as np
631+ >>> x = np.linspace(-2, 2, 200)
632+ >>> y = np.sin(x) + 0.1 * np.random.randn(len(x))
633+ >>> h = nw_bandwidth(x, y)
634+ >>> y_pred = nw_predict(x, y, x, h) # in-sample predictions
635+
636+ Notes
637+ -----
638+ This function returns point estimates only. No confidence intervals or
639+ standard errors are provided. Key assumptions: smooth regression function,
640+ IID observations, design density bounded away from zero in region of interest.
641+
642+ For inference, consider bootstrap resampling or see statsmodels.nonparametric.
643+ """
644+ x_tr = np .asarray (x_train , dtype = float ).ravel ()
645+ y_tr = np .asarray (y_train , dtype = float ).ravel ()
646+ x_te = np .asarray (x_test , dtype = float ).ravel ()
647+
648+ if len (x_tr ) != len (y_tr ):
649+ raise ValueError (f"x_train and y_train must have same length, got { len (x_tr )} and { len (y_tr )} " )
650+ if kernel not in _KERNELS :
651+ raise ValueError (f"kernel must be one of { list (_KERNELS .keys ())} , got { kernel !r} " )
652+
653+ K , _ , _ , _ , _ , _ = _KERNELS [kernel ]
654+
655+ u = (x_te [:, None ] - x_tr [None , :]) / h
656+ w = K (u )
657+
658+ w_sum = w .sum (axis = 1 )
659+ w_sum_safe = np .where (w_sum == 0 , np .finfo (float ).eps , w_sum )
660+ y_pred = (w @ y_tr ) / w_sum_safe
661+
662+ zero_weight_mask = w_sum == 0
663+ if np .any (zero_weight_mask ):
664+ y_pred [zero_weight_mask ] = np .mean (y_tr )
665+
666+ return y_pred
667+
668+
669+ def nw_predict_mv (
670+ data_train : ArrayLike ,
671+ y_train : ArrayLike ,
672+ data_test : ArrayLike ,
673+ h : float ,
674+ kernel : str = "gauss" ,
675+ ) -> NDArray [Any ]:
676+ """Multivariate Nadaraya-Watson kernel regression predictions using product kernel.
677+
678+ Parameters
679+ ----------
680+ data_train
681+ Training predictor values, shape (n_train, d).
682+ y_train
683+ Training response values (1D array of length n_train).
684+ data_test
685+ Test predictor values where predictions are desired, shape (n_test, d).
686+ h
687+ Bandwidth (scalar, applied to all dimensions; obtained from nw_bandwidth_mv).
688+ kernel
689+ Kernel function: "gauss", "epan", "unif", "biweight", "triweight", or "cosine".
690+
691+ Returns
692+ -------
693+ NDArray
694+ Predicted values at data_test locations.
695+
696+ Examples
697+ --------
698+ >>> import numpy as np
699+ >>> data = np.random.randn(500, 2)
700+ >>> y = np.sin(data[:, 0]) + 0.5 * data[:, 1] + 0.3 * np.random.randn(500)
701+ >>> h = nw_bandwidth_mv(data, y)
702+ >>> y_pred = nw_predict_mv(data, y, data, h) # in-sample predictions
703+
704+ Notes
705+ -----
706+ This function returns point estimates only. No confidence intervals or
707+ standard errors are provided. Key assumptions: smooth regression function,
708+ IID observations, design density bounded away from zero in region of interest.
709+ Uses product kernel with isotropic bandwidth; data should be standardized
710+ for best results.
711+
712+ For inference, consider bootstrap resampling or see statsmodels.nonparametric.
713+ """
714+ data_tr = np .asarray (data_train , dtype = float )
715+ y_tr = np .asarray (y_train , dtype = float ).ravel ()
716+ data_te = np .asarray (data_test , dtype = float )
717+
718+ if data_tr .ndim == 1 :
719+ data_tr = data_tr .reshape (- 1 , 1 )
720+ if data_te .ndim == 1 :
721+ data_te = data_te .reshape (- 1 , 1 )
722+
723+ if data_tr .ndim != 2 :
724+ raise ValueError (f"data_train must be 2D array, got shape { data_tr .shape } " )
725+ if data_te .ndim != 2 :
726+ raise ValueError (f"data_test must be 2D array, got shape { data_te .shape } " )
727+ if len (data_tr ) != len (y_tr ):
728+ raise ValueError (f"data_train and y_train must have same length, got { len (data_tr )} and { len (y_tr )} " )
729+ if data_tr .shape [1 ] != data_te .shape [1 ]:
730+ raise ValueError (f"data_train and data_test must have same number of dimensions, got { data_tr .shape [1 ]} and { data_te .shape [1 ]} " )
731+ if kernel not in _KERNELS :
732+ raise ValueError (f"kernel must be one of { list (_KERNELS .keys ())} , got { kernel !r} " )
733+
734+ K , _ , _ , _ , _ , _ = _KERNELS [kernel ]
735+
736+ U = (data_te [:, None , :] - data_tr [None , :, :]) / h
737+ K_vals = K (U )
738+ w = np .prod (K_vals , axis = 2 )
739+
740+ w_sum = w .sum (axis = 1 )
741+ w_sum_safe = np .where (w_sum == 0 , np .finfo (float ).eps , w_sum )
742+ y_pred = (w @ y_tr ) / w_sum_safe
743+
744+ zero_weight_mask = w_sum == 0
745+ if np .any (zero_weight_mask ):
746+ y_pred [zero_weight_mask ] = np .mean (y_tr )
747+
748+ return y_pred
749+
750+
601751def nw_bandwidth_mv (
602752 data : ArrayLike ,
603753 y : ArrayLike ,
0 commit comments