"""
Multivariate Conditional and Unconditional Kernel Density Estimation
with Mixed Data Types
References
----------
[1] Racine, J., Li, Q. Nonparametric econometrics: theory and practice.
Princeton University Press. (2007)
[2] Racine, Jeff. "Nonparametric Econometrics: A Primer," Foundation
and Trends in Econometrics: Vol 3: No 1, pp1-88. (2008)
http://dx.doi.org/10.1561/0800000009
[3] Racine, J., Li, Q. "Nonparametric Estimation of Distributions
with Categorical and Continuous Data." Working Paper. (2000)
[4] Racine, J. Li, Q. "Kernel Estimation of Multivariate Conditional
Distributions Annals of Economics and Finance 5, 211-235 (2004)
[5] Liu, R., Yang, L. "Kernel estimation of multivariate
cumulative distribution function."
Journal of Nonparametric Statistics (2008)
[6] Li, R., Ju, G. "Nonparametric Estimation of Multivariate CDF
with Categorical and Continuous Data." Working Paper
[7] Li, Q., Racine, J. "Cross-validated local linear nonparametric
regression" Statistica Sinica 14(2004), pp. 485-512
[8] Racine, J.: "Consistent Significance Testing for Nonparametric
Regression" Journal of Business & Economics Statistics
[9] Racine, J., Hart, J., Li, Q., "Testing the Significance of
Categorical Predictor Variables in Nonparametric Regression
Models", 2006, Econometric Reviews 25, 523-544
"""
from statsmodels.compat.pandas import deprecate_kwarg
# TODO: make default behavior efficient=True above a certain n_obs
import numpy as np
from . import kernels
from ._kernel_base import (
EstimatorSettings,
GenericKDE,
LeaveOneOut,
_adjust_shape,
gpke,
initialize_generator,
kernel_func,
)
__all__ = ["EstimatorSettings", "KDEMultivariate", "KDEMultivariateConditional"]
def _gpke_pairwise(
bw,
data,
data_predict,
var_type,
ckertype="gaussian",
okertype="wangryzin",
ukertype="aitchisonaitken",
):
"""Product kernel evaluated pairwise, elementwise between two samples.
Like :func:`statsmodels.nonparametric._kernel_base.gpke`, except that
`data_predict` is a 2-D array with the same shape as `data`: row ``k`` of
`data` is paired with row ``k`` of `data_predict`, instead of `gpke`'s
assumption that `data_predict` is a single evaluation point shared by
every row of `data`.
"""
kertypes = dict(c=ckertype, o=okertype, u=ukertype)
Kval = np.empty(data.shape)
for ii, vtype in enumerate(var_type):
func = kernel_func[kertypes[vtype]]
Kval[:, ii] = func(bw[ii], data[:, ii], data_predict[:, ii])
iscontinuous = np.array([c == "c" for c in var_type])
return Kval.prod(axis=1) / np.prod(bw[iscontinuous])
[docs]
class KDEMultivariate(GenericKDE):
"""
Multivariate kernel density estimator
This density estimator can handle univariate as well as multivariate data,
including mixed continuous / ordered discrete / unordered discrete data.
It also provides cross-validated bandwidth selection methods (least
squares, maximum likelihood).
Parameters
----------
data : list of ndarrays or 2-D ndarray
The training data for the Kernel Density Estimation, used to determine
the bandwidth(s). If a 2-D array, should be of shape
(num_observations, num_variables). If a list, each list element is a
separate observation.
var_type : str
The type of the variables:
- c : continuous
- u : unordered (discrete)
- o : ordered (discrete)
The string should contain a type specifier for each variable, so for
example ``var_type='ccuo'``.
bw : array_like or str, optional
If an array, it is a fixed user-specified bandwidth. If a string,
should be one of:
- normal_reference: normal reference rule of thumb (default)
- cv_ml: cross validation maximum likelihood
- cv_ls: cross validation least squares
The bandwidths that are selected by one of the methods are kept in
the range for which the kernels of the discrete variables are
defined. The bandwidth of an unordered variable is at most
``(levels - 1) / levels``, where ``levels`` is the number of distinct
values of the variable, and the bandwidth of an ordered variable is
less than 1. A user-specified bandwidth is used as given.
.. versionchanged:: 0.15.1
The bandwidths of discrete variables that are selected by one of
the methods are bounded. They can have been outside the range
of the kernels, so that the density estimate was not a
probability mass function.
defaults : EstimatorSettings instance, optional
The default values for (efficient) bandwidth estimation.
rng : int, array_like of int, numpy.random.Generator, or numpy.random.RandomState, optional
If `rng` is None, the legacy global (singleton) ``RandomState``
provided by ``numpy.random`` is used; this behavior is
deprecated and will change to creating a new ``Generator``
using fresh entropy from the operating system in a future
release. If `rng` is an int or array of ints, a new
``Generator`` is created, seeded with `rng`. If `rng` is
already a ``Generator`` or ``RandomState`` instance, that
instance is used.
.. deprecated:: 0.15.0
In release 0.17.0 or after January 2028, whichever comes sooner,
using None will initialize a new numpy.random.default_rng using
system entropy.
Attributes
----------
bw : array_like
The bandwidth parameters.
See Also
--------
KDEMultivariateConditional
Examples
--------
>>> import statsmodels.api as sm
>>> nobs = 300
>>> np.random.seed(1234) # Seed random generator
>>> c1 = np.random.normal(size=(nobs,1))
>>> c2 = np.random.normal(2, 1, size=(nobs,1))
Estimate a bivariate distribution and display the bandwidth found:
>>> dens_u = sm.nonparametric.KDEMultivariate(data=[c1,c2],
... var_type='cc', bw='normal_reference')
>>> dens_u.bw
array([ 0.39967419, 0.38423292])
"""
@deprecate_kwarg("seed", "rng")
def __init__(self, data, var_type, bw=None, defaults=None, *, rng=None):
self.var_type = var_type
self.k_vars = len(self.var_type)
self.data = _adjust_shape(data, self.k_vars)
self.data_type = var_type
self.nobs, self.k_vars = np.shape(self.data)
if self.nobs <= self.k_vars:
raise ValueError(
"The number of observations must be larger "
"than the number of variables."
)
defaults = EstimatorSettings() if defaults is None else defaults
self._set_defaults(defaults)
self._generator = initialize_generator(rng)
if not self.efficient:
self.bw = self._compute_bw(bw)
else:
self.bw = self._compute_efficient(bw)
def __repr__(self):
"""Provide something sane to print"""
rpr = "KDE instance\n"
rpr += "Number of variables: k_vars = " + str(self.k_vars) + "\n"
rpr += "Number of samples: nobs = " + str(self.nobs) + "\n"
rpr += "Variable types: " + self.var_type + "\n"
rpr += "BW selection method: " + self._bw_method + "\n"
return rpr
[docs]
def loo_likelihood(self, bw, func=lambda x: x):
r"""
Returns the leave-one-out likelihood function
The leave-one-out likelihood function for the unconditional KDE.
Parameters
----------
bw : ndarray
The value for the bandwidth parameter(s).
func : callable, optional
Function to transform the likelihood values (before summing); for
the log likelihood, use ``func=np.log``. Default is ``f(x) = x``.
Returns
-------
L : float
The value of the leave-one-out function for the data.
Notes
-----
The leave-one-out kernel estimator of :math:`f_{-i}` is:
.. math:: f_{-i}(X_{i})=\frac{1}{(n-1)h}
\sum_{j=1,j\neq i}K_{h}(X_{i},X_{j})
where :math:`K_{h}` represents the generalized product kernel
estimator:
.. math:: K_{h}(X_{i},X_{j}) =
\prod_{s=1}^{q}h_{s}^{-1}k\left(\frac{X_{is}-X_{js}}{h_{s}}\right)
"""
LOO = LeaveOneOut(self.data)
L = 0
for i, X_not_i in enumerate(LOO):
f_i = gpke(
bw, data=-X_not_i, data_predict=-self.data[i, :], var_type=self.var_type
)
L += func(f_i)
return -L
[docs]
def pdf(self, data_predict=None):
r"""
Evaluate the probability density function
Parameters
----------
data_predict : array_like, optional
Points to evaluate at. If unspecified, the training data is used.
Returns
-------
pdf_est : ndarray
Probability density function evaluated at `data_predict`.
Notes
-----
The probability density is given by the generalized product kernel
estimator:
.. math:: K_{h}(X_{i},X_{j}) =
\prod_{s=1}^{q}h_{s}^{-1}k\left(\frac{X_{is}-X_{js}}{h_{s}}\right)
"""
if data_predict is None:
data_predict = self.data
else:
data_predict = _adjust_shape(data_predict, self.k_vars)
pdf_est = []
for i in range(np.shape(data_predict)[0]):
pdf_est.append(
gpke(
self.bw,
data=self.data,
data_predict=data_predict[i, :],
var_type=self.var_type,
)
/ self.nobs
)
pdf_est = np.squeeze(pdf_est)
return pdf_est
[docs]
def cdf(self, data_predict=None):
r"""
Evaluate the cumulative distribution function
Parameters
----------
data_predict : array_like, optional
Points to evaluate at. If unspecified, the training data is used.
Returns
-------
cdf_est : ndarray
The estimate of the cdf.
Notes
-----
See https://en.wikipedia.org/wiki/Cumulative_distribution_function
For more details on the estimation see Ref. [5] in module docstring.
The multivariate CDF for mixed data (continuous and ordered/unordered
discrete) is estimated by:
.. math::
F(x^{c},x^{d})=n^{-1}\sum_{i=1}^{n}\left[G(\frac{x^{c}-X_{i}}{h})\sum_{u\leq x^{d}}L(X_{i}^{d},x_{i}^{d}, \lambda)\right]
where G() is the product kernel CDF estimator for the continuous
and L() for the discrete variables.
Used bandwidth is ``self.bw``.
"""
if data_predict is None:
data_predict = self.data
else:
data_predict = _adjust_shape(data_predict, self.k_vars)
cdf_est = []
for i in range(np.shape(data_predict)[0]):
cdf_est.append(
gpke(
self.bw,
data=self.data,
data_predict=data_predict[i, :],
var_type=self.var_type,
ckertype="gaussian_cdf",
ukertype="aitchisonaitken_cdf",
okertype="wangryzin_cdf",
)
/ self.nobs
)
cdf_est = np.squeeze(cdf_est)
return cdf_est
[docs]
def imse(self, bw):
r"""
Returns the Integrated Mean Square Error for the unconditional KDE
Parameters
----------
bw : ndarray
The bandwidth parameter(s).
Returns
-------
CV : float
The cross-validation objective function.
Notes
-----
See p. 27 in [1]_ for details on how to handle the multivariate
estimation with mixed data types see p.6 in [2]_.
The formula for the cross-validation objective function is:
.. math:: CV=\frac{1}{n^{2}}\sum_{i=1}^{n}\sum_{j=1}^{N}
\bar{K}_{h}(X_{i},X_{j})-\frac{2}{n(n-1)}\sum_{i=1}^{n}
\sum_{j=1,j\neq i}^{N}K_{h}(X_{i},X_{j})
Where :math:`\bar{K}_{h}` is the multivariate product convolution
kernel (consult [2]_ for mixed data types).
References
----------
.. [1] Racine, J., Li, Q. Nonparametric econometrics: theory and
practice. Princeton University Press. (2007)
.. [2] Racine, J., Li, Q. "Nonparametric Estimation of Distributions
with Categorical and Continuous Data." Working Paper. (2000)
"""
# F = 0
# for i in range(self.nobs):
# k_bar_sum = gpke(bw, data=-self.data,
# data_predict=-self.data[i, :],
# var_type=self.var_type,
# ckertype='gauss_convolution',
# okertype='wangryzin_convolution',
# ukertype='aitchisonaitken_convolution')
# F += k_bar_sum
# there is a + because loo_likelihood returns the negative
# return (F / self.nobs**2 + self.loo_likelihood(bw) * \
# 2 / ((self.nobs) * (self.nobs - 1)))
# The code below is equivalent to the commented-out code above. It's
# about 20% faster due to some code being moved outside the for-loops
# and shared by gpke() and loo_likelihood().
F = 0
kertypes = dict(
c=kernels.gaussian_convolution,
o=kernels.wang_ryzin_convolution,
u=kernels.aitchison_aitken_convolution,
)
nobs = self.nobs
data = -self.data
var_type = self.var_type
ix_cont = np.array([c == "c" for c in var_type])
_bw_cont_product = bw[ix_cont].prod()
Kval = np.empty(data.shape)
for i in range(nobs):
for ii, vtype in enumerate(var_type):
Kval[:, ii] = kertypes[vtype](bw[ii], data[:, ii], data[i, ii])
dens = Kval.prod(axis=1) / _bw_cont_product
k_bar_sum = dens.sum(axis=0)
F += k_bar_sum # sum of prod kernel over nobs
kertypes = dict(
c=kernels.gaussian, o=kernels.wang_ryzin, u=kernels.aitchison_aitken
)
LOO = LeaveOneOut(self.data)
L = 0 # leave-one-out likelihood
Kval = np.empty((data.shape[0] - 1, data.shape[1]))
for i, X_not_i in enumerate(LOO):
for ii, vtype in enumerate(var_type):
Kval[:, ii] = kertypes[vtype](bw[ii], -X_not_i[:, ii], data[i, ii])
dens = Kval.prod(axis=1) / _bw_cont_product
L += dens.sum(axis=0)
# CV objective function, eq. (2.4) of Ref. [3]
return F / nobs**2 - 2 * L / (nobs * (nobs - 1))
def _get_class_vars_type(self):
"""Helper method to be able to pass needed vars to _compute_subset"""
class_type = "KDEMultivariate"
class_vars = (self.var_type,)
return class_type, class_vars
[docs]
class KDEMultivariateConditional(GenericKDE):
"""
Conditional multivariate kernel density estimator
Calculates ``P(Y_1,Y_2,...Y_n | X_1,X_2...X_m) =
P(X_1, X_2,...X_n, Y_1, Y_2,..., Y_m)/P(X_1, X_2,..., X_m)``.
The conditional density is by definition the ratio of the two densities,
see [1]_.
Parameters
----------
endog : list of ndarrays or 2-D ndarray
The training data for the dependent variables, used to determine
the bandwidth(s). If a 2-D array, should be of shape
(num_observations, num_variables). If a list, each list element is a
separate observation.
exog : list of ndarrays or 2-D ndarray
The training data for the independent variable; same shape as `endog`.
dep_type : str
The type of the dependent variables:
- c : Continuous
- u : Unordered (Discrete)
- o : Ordered (Discrete)
The string should contain a type specifier for each variable, so for
example ``dep_type='ccuo'``.
indep_type : str
The type of the independent variables; specified like `dep_type`.
bw : array_like or str
If an array, it is a fixed user-specified bandwidth. If a string,
should be one of:
- normal_reference: normal reference rule of thumb (default)
- cv_ml: cross validation maximum likelihood
- cv_ls: cross validation least squares
The bandwidths that are selected by one of the methods are kept in
the range for which the kernels of the discrete variables are
defined. The bandwidth of an unordered variable is at most
``(levels - 1) / levels``, where ``levels`` is the number of distinct
values of the variable, and the bandwidth of an ordered variable is
less than 1. A user-specified bandwidth is used as given.
.. versionchanged:: 0.15.1
The bandwidths of discrete variables that are selected by one of
the methods are bounded. They can have been outside the range
of the kernels, so that the density estimate was not a
probability mass function.
defaults : EstimatorSettings instance, optional
The default values for the efficient bandwidth estimation
rng : int, array_like of int, numpy.random.Generator, or numpy.random.RandomState, optional
If `rng` is None, the legacy global (singleton) ``RandomState``
provided by ``numpy.random`` is used; this behavior is
deprecated and will change to creating a new ``Generator``
using fresh entropy from the operating system in a future
release. If `rng` is an int or array of ints, a new
``Generator`` is created, seeded with `rng`. If `rng` is
already a ``Generator`` or ``RandomState`` instance, that
instance is used.
.. deprecated:: 0.15.0
In release 0.17.0 or after January 2028, whichever comes sooner,
using None will initialize a new numpy.random.default_rng using
system entropy.
Attributes
----------
bw : array_like
The bandwidth parameters
See Also
--------
KDEMultivariate
References
----------
.. [1] https://en.wikipedia.org/wiki/Conditional_probability_distribution
Examples
--------
>>> import statsmodels.api as sm
>>> nobs = 300
>>> c1 = np.random.normal(size=(nobs,1))
>>> c2 = np.random.normal(2,1,size=(nobs,1))
>>> dens_c = sm.nonparametric.KDEMultivariateConditional(endog=[c1],
... exog=[c2], dep_type='c', indep_type='c', bw='normal_reference')
>>> dens_c.bw # show computed bandwidth
array([ 0.41223484, 0.40976931])
"""
@deprecate_kwarg("seed", "rng")
def __init__(
self, endog, exog, dep_type, indep_type, bw, defaults=None, *, rng=None
):
self.dep_type = dep_type
self.indep_type = indep_type
self.data_type = dep_type + indep_type
self.k_dep = len(self.dep_type)
self.k_indep = len(self.indep_type)
self.endog = _adjust_shape(endog, self.k_dep)
self.exog = _adjust_shape(exog, self.k_indep)
self.nobs, self.k_dep = np.shape(self.endog)
self.data = np.column_stack((self.endog, self.exog))
self.k_vars = np.shape(self.data)[1]
self._generator = initialize_generator(rng)
defaults = EstimatorSettings() if defaults is None else defaults
self._set_defaults(defaults)
if not self.efficient:
self.bw = self._compute_bw(bw)
else:
self.bw = self._compute_efficient(bw)
def __repr__(self):
"""Provide something sane to print"""
rpr = "KDEMultivariateConditional instance\n"
rpr += "Number of independent variables: k_indep = " + str(self.k_indep) + "\n"
rpr += "Number of dependent variables: k_dep = " + str(self.k_dep) + "\n"
rpr += "Number of observations: nobs = " + str(self.nobs) + "\n"
rpr += "Independent variable types: " + self.indep_type + "\n"
rpr += "Dependent variable types: " + self.dep_type + "\n"
rpr += "BW selection method: " + self._bw_method + "\n"
return rpr
[docs]
def loo_likelihood(self, bw, func=lambda x: x):
"""
Returns the leave-one-out conditional likelihood of the data
If `func` is not equal to the default, what's calculated is a function
of the leave-one-out conditional likelihood.
Parameters
----------
bw : ndarray
The bandwidth parameter(s).
func : callable, optional
Function to transform the likelihood values (before summing); for
the log likelihood, use ``func=np.log``. Default is ``f(x) = x``.
Returns
-------
L : float
The value of the leave-one-out function for the data.
Notes
-----
Similar to ``KDE.loo_likelihood``, but substitute ``f(y|x)=f(x,y)/f(x)``
for ``f(x)``.
"""
yLOO = LeaveOneOut(self.data)
xLOO = LeaveOneOut(self.exog).__iter__()
L = 0
for i, Y_j in enumerate(yLOO):
X_not_i = next(xLOO)
f_yx = gpke(
bw,
data=-Y_j,
data_predict=-self.data[i, :],
var_type=(self.dep_type + self.indep_type),
)
f_x = gpke(
bw[self.k_dep :],
data=-X_not_i,
data_predict=-self.exog[i, :],
var_type=self.indep_type,
)
f_i = f_yx / f_x
L += func(f_i)
return -L
[docs]
def pdf(self, endog_predict=None, exog_predict=None):
r"""
Evaluate the probability density function
Parameters
----------
endog_predict : array_like, optional
Evaluation data for the dependent variables. If unspecified, the
training data is used.
exog_predict : array_like, optional
Evaluation data for the independent variables.
Returns
-------
pdf : ndarray
The value of the probability density at `endog_predict` and `exog_predict`.
Notes
-----
The formula for the conditional probability density is:
.. math:: f(y|x)=\frac{f(x,y)}{f(x)}
with
.. math:: f(x)=\prod_{s=1}^{q}h_{s}^{-1}k
\left(\frac{x_{is}-x_{js}}{h_{s}}\right)
where :math:`k` is the appropriate kernel for each variable.
"""
if endog_predict is None:
endog_predict = self.endog
else:
endog_predict = _adjust_shape(endog_predict, self.k_dep)
if exog_predict is None:
exog_predict = self.exog
else:
exog_predict = _adjust_shape(exog_predict, self.k_indep)
pdf_est = []
data_predict = np.column_stack((endog_predict, exog_predict))
for i in range(np.shape(data_predict)[0]):
f_yx = gpke(
self.bw,
data=self.data,
data_predict=data_predict[i, :],
var_type=(self.dep_type + self.indep_type),
)
f_x = gpke(
self.bw[self.k_dep :],
data=self.exog,
data_predict=exog_predict[i, :],
var_type=self.indep_type,
)
pdf_est.append(f_yx / f_x)
return np.squeeze(pdf_est)
[docs]
def cdf(self, endog_predict=None, exog_predict=None):
r"""
Cumulative distribution function for the conditional density
Parameters
----------
endog_predict : array_like, optional
The evaluation dependent variables at which the cdf is estimated.
If not specified the training dependent variables are used.
exog_predict : array_like, optional
The evaluation independent variables at which the cdf is estimated.
If not specified the training independent variables are used.
Returns
-------
cdf_est : ndarray
The estimate of the cdf.
Notes
-----
For more details on the estimation see [2]_, and p.181 in [1]_.
The multivariate conditional CDF for mixed data (continuous and
ordered/unordered discrete) is estimated by:
.. math::
F(y|x)=\frac{n^{-1}\sum_{i=1}^{n}G(\frac{y-Y_{i}}{h_{0}}) W_{h}(X_{i},x)}{\widehat{\mu}(x)}
where G() is the product kernel CDF estimator for the dependent (y)
variable(s) and W() is the product kernel CDF estimator for the
independent variable(s).
References
----------
.. [1] Racine, J., Li, Q. Nonparametric econometrics: theory and
practice. Princeton University Press. (2007)
.. [2] Liu, R., Yang, L. "Kernel estimation of multivariate cumulative
distribution function." Journal of Nonparametric
Statistics (2008)
"""
if endog_predict is None:
endog_predict = self.endog
else:
endog_predict = _adjust_shape(endog_predict, self.k_dep)
if exog_predict is None:
exog_predict = self.exog
else:
exog_predict = _adjust_shape(exog_predict, self.k_indep)
N_data_predict = np.shape(exog_predict)[0]
cdf_est = np.empty(N_data_predict)
for i in range(N_data_predict):
cdf_endog = gpke(
self.bw[0 : self.k_dep],
data=self.endog,
data_predict=endog_predict[i, :],
var_type=self.dep_type,
ckertype="gaussian_cdf",
ukertype="aitchisonaitken_cdf",
okertype="wangryzin_cdf",
tosum=False,
)
cdf_exog = gpke(
self.bw[self.k_dep :],
data=self.exog,
data_predict=exog_predict[i, :],
var_type=self.indep_type,
tosum=False,
)
S = (cdf_endog * cdf_exog).sum(axis=0)
# the sum of cdf_exog is nobs * mu_x, the estimate of f(x)
cdf_est[i] = S / cdf_exog.sum(axis=0)
return cdf_est
[docs]
def imse(self, bw):
r"""
The integrated mean square error for the conditional KDE
Parameters
----------
bw : ndarray
The bandwidth parameter(s).
Returns
-------
CV : float
The cross-validation objective function.
Notes
-----
For more details see pp. 156-166 in [1]_. For details on how to
handle the mixed variable types see [2]_.
The formula for the cross-validation objective function for mixed
variable types is:
.. math:: CV(h,\lambda)=\frac{1}{n}\sum_{l=1}^{n}
\frac{G_{-l}(X_{l})}{\left[\mu_{-l}(X_{l})\right]^{2}}-
\frac{2}{n}\sum_{l=1}^{n}\frac{f_{-l}(X_{l},Y_{l})}{\mu_{-l}(X_{l})}
where
.. math:: G_{-l}(X_{l}) = n^{-2}\sum_{i\neq l}\sum_{j\neq l}
K_{X_{i},X_{l}} K_{X_{j},X_{l}}K_{Y_{i},Y_{j}}^{(2)}
where :math:`K_{X_{i},X_{l}}` is the multivariate product kernel and
:math:`\mu_{-l}(X_{l})` is the leave-one-out estimator of the pdf.
:math:`K_{Y_{i},Y_{j}}^{(2)}` is the convolution kernel.
The value of the function is minimized by the ``_cv_ls`` method of the
`GenericKDE` class to return the bw estimates that minimize the
distance between the estimated and "true" probability density.
References
----------
.. [1] Racine, J., Li, Q. Nonparametric econometrics: theory and
practice. Princeton University Press. (2007)
.. [2] Racine, J., Li, Q. "Nonparametric Estimation of Distributions
with Categorical and Continuous Data." Working Paper. (2000)
"""
zLOO = LeaveOneOut(self.data)
CV = 0
nobs = float(self.nobs)
expander = np.ones((self.nobs - 1, 1))
for ii, Z in enumerate(zLOO):
X = Z[:, self.k_dep :]
Y = Z[:, : self.k_dep]
Ye_L = np.kron(Y, expander)
Ye_R = np.kron(expander, Y)
K_X_Xl = gpke(
bw[self.k_dep :],
data=X,
data_predict=self.exog[ii, :],
var_type=self.indep_type,
tosum=False,
)
# the kernel values of the expanded data kron(X, expander) and
# kron(expander, X) only repeat the values of the nobs - 1 rows
K_Xi_Xl = np.repeat(K_X_Xl, self.nobs - 1)
K_Xj_Xl = np.tile(K_X_Xl, self.nobs - 1)
# NOTE: this is *not* a plain gpke call: Ye_R holds one
# evaluation point per row of Ye_L (every (i, j) pair among the
# leave-one-out observations), not a single shared evaluation
# point, so the pairing has to be done elementwise.
K2_Yi_Yj = _gpke_pairwise(
bw[0 : self.k_dep],
data=Ye_L,
data_predict=Ye_R,
var_type=self.dep_type,
ckertype="gauss_convolution",
okertype="wangryzin_convolution",
ukertype="aitchisonaitken_convolution",
)
G = (K_Xi_Xl * K_Xj_Xl * K2_Yi_Yj).sum() / nobs**2
f_X_Y = (
gpke(
bw,
data=-Z,
data_predict=-self.data[ii, :],
var_type=(self.dep_type + self.indep_type),
)
/ nobs
)
m_x = (
gpke(
bw[self.k_dep :],
data=-X,
data_predict=-self.exog[ii, :],
var_type=self.indep_type,
)
/ nobs
)
CV += (G / m_x**2) - 2 * (f_X_Y / m_x)
return CV / nobs
def _get_class_vars_type(self):
"""Helper method to be able to pass needed vars to _compute_subset"""
class_type = "KDEMultivariateConditional"
class_vars = (self.k_dep, self.dep_type, self.indep_type)
return class_type, class_vars