"""
Empirical likelihood inference on descriptive statistics
This module conducts hypothesis tests and constructs confidence
intervals for the mean, variance, skewness, kurtosis and correlation.
If matplotlib is installed, this module can also generate multivariate
confidence region plots as well as mean-variance contour plots.
See _OptFuncts docstring for technical details and optimization variable
definitions.
References
----------
Owen, A. (2001). "Empirical Likelihood." Chapman and Hall
"""
import itertools
from typing import NamedTuple
import warnings
import numpy as np
from scipy import optimize
from scipy.stats import chi2, kurtosis, skew
from statsmodels.base.optimizer import _fit_newton
from statsmodels.graphics import utils
from statsmodels.tools.validation import bool_like
[docs]
class EmpLikeTestResult(NamedTuple):
"""
Result of an empirical likelihood hypothesis test.
Returned by the ``test_*`` methods of
:class:`~statsmodels.emplike.descriptive.DescStatUV` and
:class:`~statsmodels.emplike.descriptive.DescStatMV`, and by
:meth:`statsmodels.emplike.originregress.OriginResults.el_test`.
Parameters
----------
llr : float
-2 times the log-likelihood ratio, the test statistic.
pvalue : float
The p-value of the test statistic.
weights : ndarray or None
The observation weights that maximize the likelihood under the
null hypothesis.
"""
llr: float
pvalue: float
weights: np.ndarray | None
def _warn_return_weights(name):
"""Emit the ``return_weights`` variable-arity FutureWarning for ``name``."""
warnings.warn(
f"{name} currently returns a plain tuple whose length depends on the "
"return_weights argument. In release 0.16.0 or after July 2027, "
"whichever is later, the default behavior will switch to always "
"returning an EmpLikeTestResult NamedTuple. Set result_object=True "
"to switch now, or result_object=False to keep the current behavior "
"and silence this warning.",
FutureWarning,
stacklevel=3,
)
[docs]
def DescStat(endog):
"""
Return an instance to conduct inference on descriptive statistics
Uses empirical likelihood. See DescStatUV and DescStatMV for more
information.
Parameters
----------
endog : array_like
Array of data
Returns
-------
DescStatUV or DescStatMV
If k=1, the function returns a univariate instance, DescStatUV.
If k>1, the function returns a multivariate instance, DescStatMV.
"""
endog = np.asarray(endog)
if endog.size == 0:
raise ValueError("endog must contain data")
if endog.ndim == 0:
endog = endog.reshape(1, 1)
elif endog.ndim == 1:
endog = endog.reshape(len(endog), 1)
elif endog.ndim > 2:
raise ValueError("endog must be 1D or 2D")
if endog.shape[1] == 1:
return DescStatUV(endog)
return DescStatMV(endog)
class _OptFuncts:
"""
A class that holds functions that are optimized/solved
The general setup of the class is simple. Any method that starts with
_opt_ creates a vector of estimating equations named est_vect such that
np.dot(p, (est_vect))=0 where p is the weight on each
observation as a 1 x n array and est_vect is n x k. Then _modif_Newton is
called to determine the optimal p by solving for the Lagrange multiplier
(eta) in the profile likelihood maximization problem. In the presence
of nuisance parameters, _opt_ functions are optimized over to profile
out the nuisance parameters.
Any method starting with _ci_limits calculates the log likelihood
ratio for a specific value of a parameter and then subtracts a
pre-specified critical value. This is solved so that llr - crit = 0.
"""
def __init__(self, endog):
pass
def _log_star(self, eta, est_vect, weights, nobs):
"""
Transform the log of observation probabilities
In terms of the Lagrange multiplier to the log 'star' of the
probabilities.
Parameters
----------
eta : ndarray, (1,m)
Lagrange multiplier in the profile likelihood maximization
est_vect : ndarray (n,k)
Estimating equations vector
weights : 1darray
Observation weights
nobs : int
Number of observations
Returns
-------
data_star : ndarray
The weighted logstar of the estimating equations
Notes
-----
This function is only a placeholder for the _fit_Newton.
The function value is not used in optimization and the optimal value
is disregarded when computing the log likelihood ratio.
"""
data_star = np.log(weights) + (np.sum(weights) + np.dot(est_vect, eta))
idx = data_star < 1.0 / nobs
not_idx = ~idx
nx = nobs * data_star[idx]
data_star[idx] = np.log(1.0 / nobs) - 1.5 + nx * (2.0 - nx / 2)
data_star[not_idx] = np.log(data_star[not_idx])
return data_star
def _hess(self, eta, est_vect, weights, nobs):
"""
Calculate the hessian of a weighted empirical likelihood problem
Parameters
----------
eta : ndarray, (1,m)
Lagrange multiplier in the profile likelihood maximization
est_vect : ndarray (n,k)
Estimating equations vector
weights : 1darray
Observation weights
nobs : int
Number of observations
Returns
-------
hess : m x m array
Weighted hessian used in _modif_newton
"""
# eta = np.squeeze(eta)
data_star_doub_prime = np.sum(weights) + np.dot(est_vect, eta)
idx = data_star_doub_prime < 1.0 / nobs
not_idx = ~idx
data_star_doub_prime[idx] = -(nobs**2)
data_star_doub_prime[not_idx] = -((data_star_doub_prime[not_idx]) ** -2)
wtd_dsdp = weights * data_star_doub_prime
return np.dot(est_vect.T, wtd_dsdp[:, None] * est_vect)
def _grad(self, eta, est_vect, weights, nobs):
"""
Calculate the gradient of a weighted empirical likelihood problem
Parameters
----------
eta : ndarray, (1,m)
Lagrange multiplier in the profile likelihood maximization
est_vect : ndarray, (n,k)
Estimating equations vector
weights : 1darray
Observation weights
nobs : int
Number of observations
Returns
-------
gradient : ndarray (m,1)
The gradient used in _modif_newton
"""
# eta = np.squeeze(eta)
data_star_prime = np.sum(weights) + np.dot(est_vect, eta)
idx = data_star_prime < 1.0 / nobs
not_idx = ~idx
data_star_prime[idx] = nobs * (2 - nobs * data_star_prime[idx])
data_star_prime[not_idx] = 1.0 / data_star_prime[not_idx]
return np.dot(weights * data_star_prime, est_vect)
def _modif_newton(self, eta, est_vect, weights):
"""
Modified Newton's method for maximizing the log 'star' equation
This function calls _fit_newton to find the optimal values of eta.
Parameters
----------
eta : ndarray, (1,m)
Lagrange multiplier in the profile likelihood maximization
est_vect : ndarray, (n,k)
Estimating equations vector
weights : 1darray
Observation weights
Returns
-------
params : 1xm array
Lagrange multiplier that maximizes the log-likelihood
"""
nobs = len(est_vect)
def f(x0):
return -np.sum(self._log_star(x0, est_vect, weights, nobs))
def grad(x0):
return -self._grad(x0, est_vect, weights, nobs)
def hess(x0):
return -self._hess(x0, est_vect, weights, nobs)
kwds = {"tol": 1e-8}
eta = eta.squeeze()
res = _fit_newton(f, grad, eta, (), kwds, hess=hess, maxiter=50, disp=0)
return res[0]
def _find_eta(self, eta):
"""
Find the root of sum(xi-h0)/(1+eta(xi-mu))
Solves for eta when computing ELR for univariate mean.
Parameters
----------
eta : float
Lagrange multiplier in the empirical likelihood maximization
Returns
-------
diff : float
The value of the estimating equation at eta. The root of
this function with respect to eta is used to construct the
likelihood ratio for the mean.
"""
return np.sum((self.endog - self.mu0) / (1.0 + eta * (self.endog - self.mu0)))
def _ci_limits_mu(self, mu):
"""
Calculate the difference between the log likelihood of mu_test and a
specified critical value
Parameters
----------
mu : float
Hypothesized value of the mean.
Returns
-------
diff : float
The difference between the log likelihood value of mu0 and
a specified value.
"""
return self.test_mean(mu, result_object=True).llr - self.r0
def _find_gamma(self, gamma):
"""
Finds gamma that satisfies
sum(log(n * w(gamma))) - log(r0) = 0
Used for confidence intervals for the mean
Parameters
----------
gamma : float
Lagrange multiplier when computing confidence interval
Returns
-------
diff : float
The difference between the log-likelihood when the Lagrange
multiplier is gamma and a pre-specified value
"""
denom = np.sum((self.endog - gamma) ** -1)
new_weights = (self.endog - gamma) ** -1 / denom
return -2 * np.sum(np.log(self.nobs * new_weights)) - self.r0
def _opt_var(self, nuisance_mu, pval=False):
"""
This is the function to be optimized over a nuisance mean parameter
to determine the likelihood ratio for the variance
Parameters
----------
nuisance_mu : float
Value of a nuisance mean parameter
pval : bool, optional
If True, return the p-value for the likelihood ratio instead
of the -2 x log-likelihood ratio. Used for contour plotting.
Default is False.
Returns
-------
llr : float
-2 x log likelihood of a pre-specified variance holding the
nuisance parameter constant. If pval is True, the p-value of
this statistic is returned instead.
"""
endog = self.endog
nobs = self.nobs
sig_data = (endog - nuisance_mu) ** 2 - self.sig2_0
mu_data = endog - nuisance_mu
est_vect = np.column_stack((mu_data, sig_data))
eta_star = self._modif_newton(
np.array([1.0 / nobs, 1.0 / nobs]), est_vect, np.ones(nobs) * (1.0 / nobs)
)
denom = 1 + np.dot(eta_star, est_vect.T)
self.new_weights = 1.0 / nobs * 1.0 / denom
llr = np.sum(np.log(nobs * self.new_weights))
if pval: # Used for contour plotting
return chi2.sf(-2 * llr, 1)
return -2 * llr
def _ci_limits_var(self, var):
"""
Used to determine the confidence intervals for the variance
It calls test_var and when called by an optimizer,
finds the value of sig2_0 that is chi2.ppf(significance-level)
Parameters
----------
var : float
Hypothesized value of the variance
Returns
-------
diff : float
The difference between the log likelihood ratio at var and a
pre-specified value.
"""
return self.test_var(var, result_object=True).llr - self.r0
def _opt_skew(self, nuis_params):
"""
Called by test_skew. This function is optimized over
nuisance parameters mu and sigma
Parameters
----------
nuis_params : 1darray
An array with a nuisance mean and variance parameter
Returns
-------
llr : float
The log likelihood ratio of a pre-specified skewness holding
the nuisance parameters constant.
"""
endog = self.endog
nobs = self.nobs
mu_data = endog - nuis_params[0]
sig_data = ((endog - nuis_params[0]) ** 2) - nuis_params[1]
skew_data = (
((endog - nuis_params[0]) ** 3) / (nuis_params[1] ** 1.5)
) - self.skew0
est_vect = np.column_stack((mu_data, sig_data, skew_data))
eta_star = self._modif_newton(
np.array([1.0 / nobs, 1.0 / nobs, 1.0 / nobs]),
est_vect,
np.ones(nobs) * (1.0 / nobs),
)
denom = 1.0 + np.dot(eta_star, est_vect.T)
self.new_weights = 1.0 / nobs * 1.0 / denom
llr = np.sum(np.log(nobs * self.new_weights))
return -2 * llr
def _opt_kurt(self, nuis_params):
"""
Called by test_kurt. This function is optimized over
nuisance parameters mu and sigma
Parameters
----------
nuis_params : 1darray
An array with a nuisance mean and variance parameter
Returns
-------
llr : float
The log likelihood ratio of a pre-specified kurtosis holding the
nuisance parameters constant
"""
endog = self.endog
nobs = self.nobs
mu_data = endog - nuis_params[0]
sig_data = ((endog - nuis_params[0]) ** 2) - nuis_params[1]
kurt_data = (
(((endog - nuis_params[0]) ** 4) / (nuis_params[1] ** 2)) - 3
) - self.kurt0
est_vect = np.column_stack((mu_data, sig_data, kurt_data))
eta_star = self._modif_newton(
np.array([1.0 / nobs, 1.0 / nobs, 1.0 / nobs]),
est_vect,
np.ones(nobs) * (1.0 / nobs),
)
denom = 1 + np.dot(eta_star, est_vect.T)
self.new_weights = 1.0 / nobs * 1.0 / denom
llr = np.sum(np.log(nobs * self.new_weights))
return -2 * llr
def _opt_skew_kurt(self, nuis_params):
"""
Called by test_joint_skew_kurt. This function is optimized over
nuisance parameters mu and sigma
Parameters
----------
nuis_params : 1darray
An array with a nuisance mean and variance parameter
Returns
-------
llr : float
The log likelihood ratio of a pre-specified skewness and
kurtosis holding the nuisance parameters constant.
"""
endog = self.endog
nobs = self.nobs
mu_data = endog - nuis_params[0]
sig_data = ((endog - nuis_params[0]) ** 2) - nuis_params[1]
skew_data = (
((endog - nuis_params[0]) ** 3) / (nuis_params[1] ** 1.5)
) - self.skew0
kurt_data = (
(((endog - nuis_params[0]) ** 4) / (nuis_params[1] ** 2)) - 3
) - self.kurt0
est_vect = np.column_stack((mu_data, sig_data, skew_data, kurt_data))
eta_star = self._modif_newton(
np.array([1.0 / nobs, 1.0 / nobs, 1.0 / nobs, 1.0 / nobs]),
est_vect,
np.ones(nobs) * (1.0 / nobs),
)
denom = 1.0 + np.dot(eta_star, est_vect.T)
self.new_weights = 1.0 / nobs * 1.0 / denom
llr = np.sum(np.log(nobs * self.new_weights))
return -2 * llr
def _ci_limits_skew(self, skew):
"""
Calculate the difference between the log likelihood ratio at skew
and a pre-specified critical value
Parameters
----------
skew : float
Hypothesized value of skewness
Returns
-------
diff : float
The difference between the log likelihood ratio at skew and a
pre-specified value.
"""
return self.test_skew(skew, result_object=True).llr - self.r0
def _ci_limits_kurt(self, kurt):
"""
Calculate the difference between the log likelihood ratio at kurt
and a pre-specified critical value
Parameters
----------
kurt : float
Hypothesized value of kurtosis
Returns
-------
diff : float
The difference between the log likelihood ratio at kurt and a
pre-specified value.
"""
return self.test_kurt(kurt, result_object=True).llr - self.r0
def _opt_correl(self, nuis_params, corr0, endog, nobs, x0, weights0):
"""
Calculate the log-likelihood ratio of the correlation coefficient
Called by test_corr. This function is optimized over nuisance
parameters mu1, sigma1, mu2 and sigma2.
Parameters
----------
nuis_params : 1darray
Array containing two nuisance means and two nuisance variances
corr0 : float
Hypothesized value of the correlation coefficient
endog : ndarray
Data with two columns, one for each variable
nobs : int
Number of observations
x0 : 1darray
Starting values for the Lagrange multiplier in _modif_newton
weights0 : 1darray
Starting values for the observation weights in _modif_newton
Returns
-------
llr : float
The log-likelihood of the correlation coefficient holding nuisance
parameters constant
"""
mu1_data, mu2_data = (endog - nuis_params[::2]).T
sig1_data = mu1_data**2 - nuis_params[1]
sig2_data = mu2_data**2 - nuis_params[3]
correl_data = (mu1_data * mu2_data) - corr0 * (
nuis_params[1] * nuis_params[3]
) ** 0.5
est_vect = np.column_stack(
(mu1_data, sig1_data, mu2_data, sig2_data, correl_data)
)
eta_star = self._modif_newton(x0, est_vect, weights0)
denom = 1.0 + np.dot(est_vect, eta_star)
self.new_weights = 1.0 / nobs * 1.0 / denom
llr = np.sum(np.log(nobs * self.new_weights))
return -2 * llr
def _ci_limits_corr(self, corr):
"""
Calculate the difference between the log likelihood ratio at corr
and a pre-specified critical value
Parameters
----------
corr : float
Hypothesized value of the correlation coefficient
Returns
-------
diff : float
The difference between the log likelihood ratio at corr and a
pre-specified value.
"""
return self.test_corr(corr, result_object=True).llr - self.r0
[docs]
class DescStatUV(_OptFuncts):
"""
A class to compute confidence intervals and hypothesis tests involving
mean, variance, kurtosis and skewness of a univariate random variable.
Parameters
----------
endog : 1darray
Data to be analyzed
Attributes
----------
endog : 1darray
Data to be analyzed
nobs : int
Number of observations
"""
def __init__(self, endog):
self.endog = np.squeeze(endog)
self.nobs = endog.shape[0]
[docs]
def test_mean(self, mu0, return_weights=False, *, result_object=None):
"""
Returns - 2 x log-likelihood ratio, p-value and weights
for a hypothesis test of the mean.
Parameters
----------
mu0 : float
Mean value to be tested
return_weights : bool, optional
If return_weights is True the function returns
the weights of the observations under the null hypothesis.
Default is False
result_object : bool, optional
Flag indicating whether to return the results as an
``EmpLikeTestResult`` NamedTuple instead of a plain tuple. When
``return_weights=True`` the NamedTuple holds the same three
elements as the legacy tuple, so it unpacks identically and is
always returned, with no warning. When ``return_weights=False``
the legacy two-element tuple is returned by default and a
``FutureWarning`` is issued.
.. deprecated:: 0.15.0
In release 0.16.0 or after July 2027, whichever is later, the
default will change to always return an
``EmpLikeTestResult``. Set ``result_object=True`` to opt in
now, or ``result_object=False`` to silence the warning and
keep the current return type.
Returns
-------
EmpLikeTestResult or tuple
If ``result_object=True`` or ``return_weights=True``, a
NamedTuple with fields ``llr``, ``pvalue`` and ``weights``. See
:class:`~statsmodels.emplike.descriptive.EmpLikeTestResult`.
Otherwise (the deprecated default), the plain ``(llr, pvalue)``
tuple.
"""
result_object = bool_like(result_object, "result_object", optional=True)
self.mu0 = mu0
endog = self.endog
nobs = self.nobs
eta_min = (1.0 - (1.0 / nobs)) / (self.mu0 - max(endog))
eta_max = (1.0 - (1.0 / nobs)) / (self.mu0 - min(endog))
eta_star = optimize.brentq(self._find_eta, eta_min, eta_max)
new_weights = (1.0 / nobs) * 1.0 / (1.0 + eta_star * (endog - self.mu0))
llr = -2 * np.sum(np.log(nobs * new_weights))
pval = chi2.sf(llr, 1)
if result_object is None and not return_weights:
_warn_return_weights("DescStatUV.test_mean")
if result_object or return_weights:
return EmpLikeTestResult(llr, pval, new_weights)
return llr, pval
[docs]
def ci_mean(
self,
sig=0.05,
method="gamma",
epsilon=10**-8,
gamma_low=-(10**10),
gamma_high=10**10,
):
"""
Returns the confidence interval for the mean
Parameters
----------
sig : float, optional
Significance level. Default is .05
method : str, optional
Root finding method, Can be 'nested-brent' or
'gamma'. Default is 'gamma'
'gamma' Tries to solve for the gamma parameter in the
Lagrange (see Owen pg 22) and then determine the weights.
'nested brent' uses Brent's method to find the confidence
intervals but must maximize the likelihood ratio on every
iteration.
gamma is generally much faster. If the optimization does not
converge, try expanding the gamma_high and gamma_low
variables.
gamma_low : float, optional
Lower bound for gamma when finding lower limit.
If function returns f(a) and f(b) must have different signs,
consider lowering gamma_low.
gamma_high : float, optional
Upper bound for gamma when finding upper limit.
If function returns f(a) and f(b) must have different signs,
consider raising gamma_high.
epsilon : float, optional
When using 'nested-brent', amount to decrease (increase)
from the maximum (minimum) of the data when
starting the search. This is to protect against the
likelihood ratio being zero at the maximum (minimum)
value of the data. If data is very small in absolute value
(<10 ``**`` -6) consider shrinking epsilon
When using 'gamma', amount to decrease (increase) the
minimum (maximum) by to start the search for gamma.
If function returns f(a) and f(b) must have different signs,
consider lowering epsilon.
Returns
-------
Interval : tuple of float
Confidence interval for the mean
"""
endog = self.endog
sig = 1 - sig
if method == "nested-brent":
self.r0 = chi2.ppf(sig, 1)
middle = np.mean(endog)
epsilon_u = (max(endog) - np.mean(endog)) * epsilon
epsilon_l = (np.mean(endog) - min(endog)) * epsilon
ulim = optimize.brentq(self._ci_limits_mu, middle, max(endog) - epsilon_u)
llim = optimize.brentq(self._ci_limits_mu, middle, min(endog) + epsilon_l)
return llim, ulim
if method == "gamma":
self.r0 = chi2.ppf(sig, 1)
gamma_star_l = optimize.brentq(
self._find_gamma, gamma_low, min(endog) - epsilon
)
gamma_star_u = optimize.brentq(
self._find_gamma, max(endog) + epsilon, gamma_high
)
weights_low = ((endog - gamma_star_l) ** -1) / np.sum(
(endog - gamma_star_l) ** -1
)
weights_high = ((endog - gamma_star_u) ** -1) / np.sum(
(endog - gamma_star_u) ** -1
)
mu_low = np.sum(weights_low * endog)
mu_high = np.sum(weights_high * endog)
return mu_low, mu_high
[docs]
def test_var(self, sig2_0, return_weights=False, *, result_object=None):
"""
Returns -2 x log-likelihood ratio and the p-value for the
hypothesized variance
Parameters
----------
sig2_0 : float
Hypothesized variance to be tested
return_weights : bool, optional
If True, returns the weights that maximize the
likelihood of observing sig2_0. Default is False
result_object : bool, optional
Flag indicating whether to return the results as an
``EmpLikeTestResult`` NamedTuple instead of a plain tuple. When
``return_weights=True`` the NamedTuple holds the same three
elements as the legacy tuple, so it unpacks identically and is
always returned, with no warning. When ``return_weights=False``
the legacy two-element tuple is returned by default and a
``FutureWarning`` is issued.
.. deprecated:: 0.15.0
In release 0.16.0 or after July 2027, whichever is later, the
default will change to always return an
``EmpLikeTestResult``. Set ``result_object=True`` to opt in
now, or ``result_object=False`` to silence the warning and
keep the current return type.
Returns
-------
EmpLikeTestResult or tuple
If ``result_object=True`` or ``return_weights=True``, a
NamedTuple with fields ``llr``, ``pvalue`` and ``weights``. See
:class:`~statsmodels.emplike.descriptive.EmpLikeTestResult`.
Otherwise (the deprecated default), the plain ``(llr, pvalue)``
tuple.
Examples
--------
>>> import numpy as np
>>> import statsmodels.api as sm
>>> random_numbers = np.random.standard_normal(1000)*100
>>> el_analysis = sm.emplike.DescStat(random_numbers)
>>> hyp_test = el_analysis.test_var(9500, result_object=True)
"""
result_object = bool_like(result_object, "result_object", optional=True)
self.sig2_0 = sig2_0
mu_max = max(self.endog)
mu_min = min(self.endog)
llr = optimize.fminbound(self._opt_var, mu_min, mu_max, full_output=1)[1]
p_val = chi2.sf(llr, 1)
if result_object is None and not return_weights:
_warn_return_weights("DescStatUV.test_var")
if result_object or return_weights:
return EmpLikeTestResult(llr, p_val, self.new_weights.T)
return llr, p_val
[docs]
def ci_var(self, lower_bound=None, upper_bound=None, sig=0.05):
"""
Returns the confidence interval for the variance
Parameters
----------
lower_bound : float, optional
The minimum value the lower confidence interval can
take. The p-value from test_var(lower_bound) must be lower
than 1 - significance level. Default is .99 confidence
limit assuming normality
upper_bound : float, optional
The maximum value the upper confidence interval
can take. The p-value from test_var(upper_bound) must be lower
than 1 - significance level. Default is .99 confidence
limit assuming normality
sig : float, optional
The significance level. Default is .05
Returns
-------
Interval : tuple of float
Confidence interval for the variance
Examples
--------
>>> import numpy as np
>>> import statsmodels.api as sm
>>> random_numbers = np.random.standard_normal(100)
>>> el_analysis = sm.emplike.DescStat(random_numbers)
>>> el_analysis.ci_var()
(0.7539322567470305, 1.229998852496268)
>>> el_analysis.ci_var(.5, 2)
(0.7539322567469926, 1.2299988524962664)
Notes
-----
If the function returns the error f(a) and f(b) must have
different signs, consider lowering lower_bound and raising
upper_bound.
"""
endog = self.endog
if upper_bound is None:
upper_bound = ((self.nobs - 1) * endog.var()) / (
chi2.ppf(0.0001, self.nobs - 1)
)
if lower_bound is None:
lower_bound = ((self.nobs - 1) * endog.var()) / (
chi2.ppf(0.9999, self.nobs - 1)
)
self.r0 = chi2.ppf(1 - sig, 1)
llim = optimize.brentq(self._ci_limits_var, lower_bound, endog.var())
ulim = optimize.brentq(self._ci_limits_var, endog.var(), upper_bound)
return llim, ulim
[docs]
def plot_contour(
self,
mu_low,
mu_high,
var_low,
var_high,
mu_step,
var_step,
levs=(0.001, 0.01, 0.05, 0.1, 0.2),
):
"""
Returns a plot of the confidence region for a univariate
mean and variance.
Parameters
----------
mu_low : float
Lowest value of the mean to plot
mu_high : float
Highest value of the mean to plot
var_low : float
Lowest value of the variance to plot
var_high : float
Highest value of the variance to plot
mu_step : float
Increments to evaluate the mean
var_step : float
Increments to evaluate the mean
levs : sequence of float, optional
Which values of significance the contour lines will be drawn.
Must be given in increasing order. Default is
(.001, .01, .05, .1, .2)
Returns
-------
Figure
The contour plot
"""
fig, ax = utils.create_mpl_ax()
ax.set_ylabel("Variance")
ax.set_xlabel("Mean")
mu_vect = list(np.arange(mu_low, mu_high, mu_step))
var_vect = list(np.arange(var_low, var_high, var_step))
z = []
for sig0 in var_vect:
self.sig2_0 = sig0
for mu0 in mu_vect:
z.append(self._opt_var(mu0, pval=True))
z = np.asarray(z).reshape(len(var_vect), len(mu_vect))
ax.contour(mu_vect, var_vect, z, levels=levs)
return fig
[docs]
def test_skew(self, skew0, return_weights=False, *, result_object=None):
"""
Returns -2 x log-likelihood and p-value for the hypothesized
skewness.
Parameters
----------
skew0 : float
Skewness value to be tested
return_weights : bool, optional
If True, function also returns the weights that
maximize the likelihood ratio. Default is False.
result_object : bool, optional
Flag indicating whether to return the results as an
``EmpLikeTestResult`` NamedTuple instead of a plain tuple. When
``return_weights=True`` the NamedTuple holds the same three
elements as the legacy tuple, so it unpacks identically and is
always returned, with no warning. When ``return_weights=False``
the legacy two-element tuple is returned by default and a
``FutureWarning`` is issued.
.. deprecated:: 0.15.0
In release 0.16.0 or after July 2027, whichever is later, the
default will change to always return an
``EmpLikeTestResult``. Set ``result_object=True`` to opt in
now, or ``result_object=False`` to silence the warning and
keep the current return type.
Returns
-------
EmpLikeTestResult or tuple
If ``result_object=True`` or ``return_weights=True``, a
NamedTuple with fields ``llr``, ``pvalue`` and ``weights``. See
:class:`~statsmodels.emplike.descriptive.EmpLikeTestResult`.
Otherwise (the deprecated default), the plain ``(llr, pvalue)``
tuple.
"""
result_object = bool_like(result_object, "result_object", optional=True)
self.skew0 = skew0
start_nuisance = np.array([self.endog.mean(), self.endog.var()])
llr = optimize.fmin_powell(
self._opt_skew, start_nuisance, full_output=1, disp=0
)[1]
p_val = chi2.sf(llr, 1)
if result_object is None and not return_weights:
_warn_return_weights("DescStatUV.test_skew")
if result_object or return_weights:
return EmpLikeTestResult(llr, p_val, self.new_weights.T)
return llr, p_val
[docs]
def test_kurt(self, kurt0, return_weights=False, *, result_object=None):
"""
Returns -2 x log-likelihood and the p-value for the hypothesized
kurtosis.
Parameters
----------
kurt0 : float
Kurtosis value to be tested
return_weights : bool, optional
If True, function also returns the weights that
maximize the likelihood ratio. Default is False.
result_object : bool, optional
Flag indicating whether to return the results as an
``EmpLikeTestResult`` NamedTuple instead of a plain tuple. When
``return_weights=True`` the NamedTuple holds the same three
elements as the legacy tuple, so it unpacks identically and is
always returned, with no warning. When ``return_weights=False``
the legacy two-element tuple is returned by default and a
``FutureWarning`` is issued.
.. deprecated:: 0.15.0
In release 0.16.0 or after July 2027, whichever is later, the
default will change to always return an
``EmpLikeTestResult``. Set ``result_object=True`` to opt in
now, or ``result_object=False`` to silence the warning and
keep the current return type.
Returns
-------
EmpLikeTestResult or tuple
If ``result_object=True`` or ``return_weights=True``, a
NamedTuple with fields ``llr``, ``pvalue`` and ``weights``. See
:class:`~statsmodels.emplike.descriptive.EmpLikeTestResult`.
Otherwise (the deprecated default), the plain ``(llr, pvalue)``
tuple.
"""
result_object = bool_like(result_object, "result_object", optional=True)
self.kurt0 = kurt0
start_nuisance = np.array([self.endog.mean(), self.endog.var()])
llr = optimize.fmin_powell(
self._opt_kurt, start_nuisance, full_output=1, disp=0
)[1]
p_val = chi2.sf(llr, 1)
if result_object is None and not return_weights:
_warn_return_weights("DescStatUV.test_kurt")
if result_object or return_weights:
return EmpLikeTestResult(llr, p_val, self.new_weights.T)
return llr, p_val
[docs]
def test_joint_skew_kurt(
self, skew0, kurt0, return_weights=False, *, result_object=None
):
"""
Returns - 2 x log-likelihood and the p-value for the joint
hypothesis test for skewness and kurtosis
Parameters
----------
skew0 : float
Skewness value to be tested
kurt0 : float
Kurtosis value to be tested
return_weights : bool, optional
If True, function also returns the weights that
maximize the likelihood ratio. Default is False.
result_object : bool, optional
Flag indicating whether to return the results as an
``EmpLikeTestResult`` NamedTuple instead of a plain tuple. When
``return_weights=True`` the NamedTuple holds the same three
elements as the legacy tuple, so it unpacks identically and is
always returned, with no warning. When ``return_weights=False``
the legacy two-element tuple is returned by default and a
``FutureWarning`` is issued.
.. deprecated:: 0.15.0
In release 0.16.0 or after July 2027, whichever is later, the
default will change to always return an
``EmpLikeTestResult``. Set ``result_object=True`` to opt in
now, or ``result_object=False`` to silence the warning and
keep the current return type.
Returns
-------
EmpLikeTestResult or tuple
If ``result_object=True`` or ``return_weights=True``, a
NamedTuple with fields ``llr``, ``pvalue`` and ``weights``. See
:class:`~statsmodels.emplike.descriptive.EmpLikeTestResult`.
Otherwise (the deprecated default), the plain ``(llr, pvalue)``
tuple.
"""
result_object = bool_like(result_object, "result_object", optional=True)
self.skew0 = skew0
self.kurt0 = kurt0
start_nuisance = np.array([self.endog.mean(), self.endog.var()])
llr = optimize.fmin_powell(
self._opt_skew_kurt, start_nuisance, full_output=1, disp=0
)[1]
p_val = chi2.sf(llr, 2)
if result_object is None and not return_weights:
_warn_return_weights("DescStatUV.test_joint_skew_kurt")
if result_object or return_weights:
return EmpLikeTestResult(llr, p_val, self.new_weights.T)
return llr, p_val
[docs]
def ci_skew(self, sig=0.05, upper_bound=None, lower_bound=None):
"""
Returns the confidence interval for skewness
Parameters
----------
sig : float, optional
The significance level. Default is .05
upper_bound : float, optional
Maximum value of skewness the upper limit can be.
Default is .99 confidence limit assuming normality.
lower_bound : float, optional
Minimum value of skewness the lower limit can be.
Default is .99 confidence level assuming normality.
Returns
-------
Interval : tuple of float
Confidence interval for the skewness
Notes
-----
If function returns f(a) and f(b) must have different signs, consider
expanding lower and upper bounds
"""
nobs = self.nobs
endog = self.endog
if upper_bound is None:
upper_bound = (
skew(endog)
+ 2.5
* (
(6.0 * nobs * (nobs - 1.0))
/ ((nobs - 2.0) * (nobs + 1.0) * (nobs + 3.0))
)
** 0.5
)
if lower_bound is None:
lower_bound = (
skew(endog)
- 2.5
* (
(6.0 * nobs * (nobs - 1.0))
/ ((nobs - 2.0) * (nobs + 1.0) * (nobs + 3.0))
)
** 0.5
)
self.r0 = chi2.ppf(1 - sig, 1)
llim = optimize.brentq(self._ci_limits_skew, lower_bound, skew(endog))
ulim = optimize.brentq(self._ci_limits_skew, skew(endog), upper_bound)
return llim, ulim
[docs]
def ci_kurt(self, sig=0.05, upper_bound=None, lower_bound=None):
"""
Returns the confidence interval for kurtosis
Parameters
----------
sig : float, optional
The significance level. Default is .05
upper_bound : float, optional
Maximum value of kurtosis the upper limit can be.
Default is .99 confidence limit assuming normality.
lower_bound : float, optional
Minimum value of kurtosis the lower limit can be.
Default is .99 confidence limit assuming normality.
Returns
-------
Interval : tuple of float
Lower and upper confidence limit
Notes
-----
For small n, upper_bound and lower_bound may have to be
provided by the user. Consider using test_kurt to find
values close to the desired significance level.
If function returns f(a) and f(b) must have different signs, consider
expanding the bounds.
"""
endog = self.endog
nobs = self.nobs
if upper_bound is None:
upper_bound = kurtosis(endog) + (
2.5
* (
2.0
* (
(6.0 * nobs * (nobs - 1.0))
/ ((nobs - 2.0) * (nobs + 1.0) * (nobs + 3.0))
)
** 0.5
)
* (((nobs**2.0) - 1.0) / ((nobs - 3.0) * (nobs + 5.0))) ** 0.5
)
if lower_bound is None:
lower_bound = kurtosis(endog) - (
2.5
* (
2.0
* (
(6.0 * nobs * (nobs - 1.0))
/ ((nobs - 2.0) * (nobs + 1.0) * (nobs + 3.0))
)
** 0.5
)
* (((nobs**2.0) - 1.0) / ((nobs - 3.0) * (nobs + 5.0))) ** 0.5
)
self.r0 = chi2.ppf(1 - sig, 1)
llim = optimize.brentq(self._ci_limits_kurt, lower_bound, kurtosis(endog))
ulim = optimize.brentq(self._ci_limits_kurt, kurtosis(endog), upper_bound)
return llim, ulim
[docs]
class DescStatMV(_OptFuncts):
"""
A class for conducting inference on multivariate means and correlation
Parameters
----------
endog : ndarray
Data to be analyzed
Attributes
----------
endog : ndarray
Data to be analyzed
nobs : int
Number of observations
"""
def __init__(self, endog):
self.endog = endog
self.nobs = endog.shape[0]
[docs]
def mv_test_mean(self, mu_array, return_weights=False, *, result_object=None):
"""
Returns -2 x log likelihood and the p-value
for a multivariate hypothesis test of the mean
Parameters
----------
mu_array : 1d array
Hypothesized values for the mean. Must have same number of
elements as columns in endog
return_weights : bool, optional
If True, returns the weights that maximize the
likelihood of mu_array. Default is False.
result_object : bool, optional
Flag indicating whether to return the results as an
``EmpLikeTestResult`` NamedTuple instead of a plain tuple. When
``return_weights=True`` the NamedTuple holds the same three
elements as the legacy tuple, so it unpacks identically and is
always returned, with no warning. When ``return_weights=False``
the legacy two-element tuple is returned by default and a
``FutureWarning`` is issued.
.. deprecated:: 0.15.0
In release 0.16.0 or after July 2027, whichever is later, the
default will change to always return an
``EmpLikeTestResult``. Set ``result_object=True`` to opt in
now, or ``result_object=False`` to silence the warning and
keep the current return type.
Returns
-------
EmpLikeTestResult or tuple
If ``result_object=True`` or ``return_weights=True``, a
NamedTuple with fields ``llr``, ``pvalue`` and ``weights``. See
:class:`~statsmodels.emplike.descriptive.EmpLikeTestResult`.
Otherwise (the deprecated default), the plain ``(llr, pvalue)``
tuple.
"""
result_object = bool_like(result_object, "result_object", optional=True)
endog = self.endog
nobs = self.nobs
if len(mu_array) != endog.shape[1]:
raise ValueError(
"mu_array must have the same number of "
"elements as the columns of the data."
)
mu_array = mu_array.reshape(1, endog.shape[1])
means = np.ones((endog.shape[0], endog.shape[1]))
means = mu_array * means
est_vect = endog - means
start_vals = 1.0 / nobs * np.ones(endog.shape[1])
eta_star = self._modif_newton(
start_vals, est_vect, np.ones(nobs) * (1.0 / nobs)
)
denom = 1 + np.dot(eta_star, est_vect.T)
self.new_weights = 1 / nobs * 1 / denom
llr = -2 * np.sum(np.log(nobs * self.new_weights))
p_val = chi2.sf(llr, mu_array.shape[1])
if result_object is None and not return_weights:
_warn_return_weights("DescStatMV.mv_test_mean")
if result_object or return_weights:
return EmpLikeTestResult(llr, p_val, self.new_weights.T)
return llr, p_val
[docs]
def mv_mean_contour(
self,
mu1_low,
mu1_upp,
mu2_low,
mu2_upp,
step1,
step2,
levs=(0.001, 0.01, 0.05, 0.1, 0.2),
var1_name=None,
var2_name=None,
plot_dta=False,
):
"""
Creates a confidence region plot for the mean of bivariate data
Parameters
----------
mu1_low : float
Minimum value of the mean for variable 1
mu1_upp : float
Maximum value of the mean for variable 1
mu2_low : float
Minimum value of the mean for variable 2
mu2_upp : float
Maximum value of the mean for variable 2
step1 : float
Increment of evaluations for variable 1
step2 : float
Increment of evaluations for variable 2
levs : sequence of float, optional
Levels to be drawn on the contour plot.
Default = (.001, .01, .05, .1, .2)
plot_dta : bool, optional
If True, makes a scatter plot of the data on
top of the contour plot. Default is False.
var1_name : str, optional
Name of variable 1 to be plotted on the x-axis
var2_name : str, optional
Name of variable 2 to be plotted on the y-axis
Notes
-----
The smaller the step size, the more accurate the intervals
will be
If the function returns optimization failed, consider narrowing
the boundaries of the plot
Examples
--------
>>> import statsmodels.api as sm
>>> two_rvs = np.random.standard_normal((20,2))
>>> el_analysis = sm.emplike.DescStat(two_rvs)
>>> contourp = el_analysis.mv_mean_contour(-2, 2, -2, 2, .1, .1)
>>> contourp.show()
"""
if self.endog.shape[1] != 2:
raise ValueError("Data must contain exactly two variables")
fig, ax = utils.create_mpl_ax()
if var2_name is None:
ax.set_ylabel("Variable 2")
else:
ax.set_ylabel(var2_name)
if var1_name is None:
ax.set_xlabel("Variable 1")
else:
ax.set_xlabel(var1_name)
x = np.arange(mu1_low, mu1_upp, step1)
y = np.arange(mu2_low, mu2_upp, step2)
pairs = itertools.product(x, y)
z = []
for i in pairs:
# levs are p-values (significance levels), so contour the
# p-value, not the unbounded -2 log-likelihood ratio.
z.append(self.mv_test_mean(np.asarray(i), result_object=True).pvalue)
X, Y = np.meshgrid(x, y)
z = np.asarray(z)
z = z.reshape(X.shape[1], Y.shape[0])
ax.contour(x, y, z.T, levels=levs)
if plot_dta:
ax.plot(self.endog[:, 0], self.endog[:, 1], "bo")
return fig
[docs]
def test_corr(self, corr0, return_weights=0, *, result_object=None):
"""
Returns -2 x log-likelihood ratio and p-value for the
correlation coefficient between 2 variables
Parameters
----------
corr0 : float
Hypothesized value to be tested
return_weights : bool, optional
If True, returns the weights that maximize
the log-likelihood at the hypothesized value
result_object : bool, optional
Flag indicating whether to return the results as an
``EmpLikeTestResult`` NamedTuple instead of a plain tuple. When
``return_weights=True`` the NamedTuple holds the same three
elements as the legacy tuple, so it unpacks identically and is
always returned, with no warning. When ``return_weights=False``
the legacy two-element tuple is returned by default and a
``FutureWarning`` is issued.
.. deprecated:: 0.15.0
In release 0.16.0 or after July 2027, whichever is later, the
default will change to always return an
``EmpLikeTestResult``. Set ``result_object=True`` to opt in
now, or ``result_object=False`` to silence the warning and
keep the current return type.
Returns
-------
EmpLikeTestResult or tuple
If ``result_object=True`` or ``return_weights=True``, a
NamedTuple with fields ``llr``, ``pvalue`` and ``weights``. See
:class:`~statsmodels.emplike.descriptive.EmpLikeTestResult`.
Otherwise (the deprecated default), the plain ``(llr, pvalue)``
tuple.
"""
result_object = bool_like(result_object, "result_object", optional=True)
nobs = self.nobs
endog = self.endog
if endog.shape[1] != 2:
raise NotImplementedError("Correlation matrix not yet implemented")
nuis0 = np.array(
[
endog[:, 0].mean(),
endog[:, 0].var(),
endog[:, 1].mean(),
endog[:, 1].var(),
]
)
x0 = np.zeros(5)
weights0 = np.array([1.0 / nobs] * int(nobs))
args = (corr0, endog, nobs, x0, weights0)
llr = optimize.fmin(self._opt_correl, nuis0, args=args, full_output=1, disp=0)[
1
]
p_val = chi2.sf(llr, 1)
if result_object is None and not return_weights:
_warn_return_weights("DescStatMV.test_corr")
if result_object or return_weights:
return EmpLikeTestResult(llr, p_val, self.new_weights.T)
return llr, p_val
[docs]
def ci_corr(self, sig=0.05, upper_bound=None, lower_bound=None):
"""
Returns the confidence intervals for the correlation coefficient
Parameters
----------
sig : float, optional
The significance level. Default is .05
upper_bound : float, optional
Maximum value the upper confidence limit can be.
Default is 99% confidence limit assuming normality.
lower_bound : float, optional
Minimum value the lower confidence limit can be.
Default is 99% confidence limit assuming normality.
Returns
-------
Interval : tuple of float
Confidence interval for the correlation
"""
endog = self.endog
nobs = self.nobs
self.r0 = chi2.ppf(1 - sig, 1)
point_est = np.corrcoef(endog[:, 0], endog[:, 1])[0, 1]
if upper_bound is None:
upper_bound = min(
0.999, point_est + 2.5 * ((1.0 - point_est**2.0) / (nobs - 2.0)) ** 0.5
)
if lower_bound is None:
lower_bound = max(
-0.999,
point_est - 2.5 * (np.sqrt((1.0 - point_est**2.0) / (nobs - 2.0))),
)
llim = optimize.brenth(self._ci_limits_corr, lower_bound, point_est)
ulim = optimize.brenth(self._ci_limits_corr, point_est, upper_bound)
return llim, ulim