304 lines
8.4 KiB
Python
304 lines
8.4 KiB
Python
|
"""helper functions conversion between moments
|
||
|
|
||
|
contains:
|
||
|
|
||
|
* conversion between central and non-central moments, skew, kurtosis and
|
||
|
cummulants
|
||
|
* cov2corr : convert covariance matrix to correlation matrix
|
||
|
|
||
|
|
||
|
Author: Josef Perktold
|
||
|
License: BSD-3
|
||
|
|
||
|
"""
|
||
|
|
||
|
import numpy as np
|
||
|
from scipy.special import comb
|
||
|
|
||
|
|
||
|
def _convert_to_multidim(x):
|
||
|
if any([isinstance(x, list), isinstance(x, tuple)]):
|
||
|
return np.array(x)
|
||
|
elif isinstance(x, np.ndarray):
|
||
|
return x
|
||
|
else:
|
||
|
# something strange was passed and the function probably
|
||
|
# will fall, maybe insert an exception?
|
||
|
return x
|
||
|
|
||
|
|
||
|
def _convert_from_multidim(x, totype=list):
|
||
|
if len(x.shape) < 2:
|
||
|
return totype(x)
|
||
|
return x.T
|
||
|
|
||
|
|
||
|
def mc2mnc(mc):
|
||
|
"""convert central to non-central moments, uses recursive formula
|
||
|
optionally adjusts first moment to return mean
|
||
|
"""
|
||
|
x = _convert_to_multidim(mc)
|
||
|
|
||
|
def _local_counts(mc):
|
||
|
mean = mc[0]
|
||
|
mc = [1] + list(mc) # add zero moment = 1
|
||
|
mc[1] = 0 # define central mean as zero for formula
|
||
|
mnc = [1, mean] # zero and first raw moments
|
||
|
for nn, m in enumerate(mc[2:]):
|
||
|
n = nn + 2
|
||
|
mnc.append(0)
|
||
|
for k in range(n + 1):
|
||
|
mnc[n] += comb(n, k, exact=True) * mc[k] * mean ** (n - k)
|
||
|
return mnc[1:]
|
||
|
|
||
|
res = np.apply_along_axis(_local_counts, 0, x)
|
||
|
# for backward compatibility convert 1-dim output to list/tuple
|
||
|
return _convert_from_multidim(res)
|
||
|
|
||
|
|
||
|
def mnc2mc(mnc, wmean=True):
|
||
|
"""convert non-central to central moments, uses recursive formula
|
||
|
optionally adjusts first moment to return mean
|
||
|
"""
|
||
|
X = _convert_to_multidim(mnc)
|
||
|
|
||
|
def _local_counts(mnc):
|
||
|
mean = mnc[0]
|
||
|
mnc = [1] + list(mnc) # add zero moment = 1
|
||
|
mu = []
|
||
|
for n, m in enumerate(mnc):
|
||
|
mu.append(0)
|
||
|
for k in range(n + 1):
|
||
|
sgn_comb = (-1) ** (n - k) * comb(n, k, exact=True)
|
||
|
mu[n] += sgn_comb * mnc[k] * mean ** (n - k)
|
||
|
if wmean:
|
||
|
mu[1] = mean
|
||
|
return mu[1:]
|
||
|
|
||
|
res = np.apply_along_axis(_local_counts, 0, X)
|
||
|
# for backward compatibility convert 1-dim output to list/tuple
|
||
|
return _convert_from_multidim(res)
|
||
|
|
||
|
|
||
|
def cum2mc(kappa):
|
||
|
"""convert non-central moments to cumulants
|
||
|
recursive formula produces as many cumulants as moments
|
||
|
|
||
|
References
|
||
|
----------
|
||
|
Kenneth Lange: Numerical Analysis for Statisticians, page 40
|
||
|
"""
|
||
|
X = _convert_to_multidim(kappa)
|
||
|
|
||
|
def _local_counts(kappa):
|
||
|
mc = [1, 0.0] # _kappa[0]] #insert 0-moment and mean
|
||
|
kappa0 = kappa[0]
|
||
|
kappa = [1] + list(kappa)
|
||
|
for nn, m in enumerate(kappa[2:]):
|
||
|
n = nn + 2
|
||
|
mc.append(0)
|
||
|
for k in range(n - 1):
|
||
|
mc[n] += comb(n - 1, k, exact=True) * kappa[n - k] * mc[k]
|
||
|
mc[1] = kappa0 # insert mean as first moments by convention
|
||
|
return mc[1:]
|
||
|
|
||
|
res = np.apply_along_axis(_local_counts, 0, X)
|
||
|
# for backward compatibility convert 1-dim output to list/tuple
|
||
|
return _convert_from_multidim(res)
|
||
|
|
||
|
|
||
|
def mnc2cum(mnc):
|
||
|
"""convert non-central moments to cumulants
|
||
|
recursive formula produces as many cumulants as moments
|
||
|
|
||
|
https://en.wikipedia.org/wiki/Cumulant#Cumulants_and_moments
|
||
|
"""
|
||
|
X = _convert_to_multidim(mnc)
|
||
|
|
||
|
def _local_counts(mnc):
|
||
|
mnc = [1] + list(mnc)
|
||
|
kappa = [1]
|
||
|
for nn, m in enumerate(mnc[1:]):
|
||
|
n = nn + 1
|
||
|
kappa.append(m)
|
||
|
for k in range(1, n):
|
||
|
num_ways = comb(n - 1, k - 1, exact=True)
|
||
|
kappa[n] -= num_ways * kappa[k] * mnc[n - k]
|
||
|
return kappa[1:]
|
||
|
|
||
|
res = np.apply_along_axis(_local_counts, 0, X)
|
||
|
# for backward compatibility convert 1-dim output to list/tuple
|
||
|
return _convert_from_multidim(res)
|
||
|
|
||
|
|
||
|
def mc2cum(mc):
|
||
|
"""
|
||
|
just chained because I have still the test case
|
||
|
"""
|
||
|
first_step = mc2mnc(mc)
|
||
|
if isinstance(first_step, np.ndarray):
|
||
|
first_step = first_step.T
|
||
|
return mnc2cum(first_step)
|
||
|
# return np.apply_along_axis(lambda x: mnc2cum(mc2mnc(x)), 0, mc)
|
||
|
|
||
|
|
||
|
def mvsk2mc(args):
|
||
|
"""convert mean, variance, skew, kurtosis to central moments"""
|
||
|
X = _convert_to_multidim(args)
|
||
|
|
||
|
def _local_counts(args):
|
||
|
mu, sig2, sk, kur = args
|
||
|
cnt = [None] * 4
|
||
|
cnt[0] = mu
|
||
|
cnt[1] = sig2
|
||
|
cnt[2] = sk * sig2 ** 1.5
|
||
|
cnt[3] = (kur + 3.0) * sig2 ** 2.0
|
||
|
return tuple(cnt)
|
||
|
|
||
|
res = np.apply_along_axis(_local_counts, 0, X)
|
||
|
# for backward compatibility convert 1-dim output to list/tuple
|
||
|
return _convert_from_multidim(res, tuple)
|
||
|
|
||
|
|
||
|
def mvsk2mnc(args):
|
||
|
"""convert mean, variance, skew, kurtosis to non-central moments"""
|
||
|
X = _convert_to_multidim(args)
|
||
|
|
||
|
def _local_counts(args):
|
||
|
mc, mc2, skew, kurt = args
|
||
|
mnc = mc
|
||
|
mnc2 = mc2 + mc * mc
|
||
|
mc3 = skew * (mc2 ** 1.5) # 3rd central moment
|
||
|
mnc3 = mc3 + 3 * mc * mc2 + mc ** 3 # 3rd non-central moment
|
||
|
mc4 = (kurt + 3.0) * (mc2 ** 2.0) # 4th central moment
|
||
|
mnc4 = mc4 + 4 * mc * mc3 + 6 * mc * mc * mc2 + mc ** 4
|
||
|
return (mnc, mnc2, mnc3, mnc4)
|
||
|
|
||
|
res = np.apply_along_axis(_local_counts, 0, X)
|
||
|
# for backward compatibility convert 1-dim output to list/tuple
|
||
|
return _convert_from_multidim(res, tuple)
|
||
|
|
||
|
|
||
|
def mc2mvsk(args):
|
||
|
"""convert central moments to mean, variance, skew, kurtosis"""
|
||
|
X = _convert_to_multidim(args)
|
||
|
|
||
|
def _local_counts(args):
|
||
|
mc, mc2, mc3, mc4 = args
|
||
|
skew = np.divide(mc3, mc2 ** 1.5)
|
||
|
kurt = np.divide(mc4, mc2 ** 2.0) - 3.0
|
||
|
return (mc, mc2, skew, kurt)
|
||
|
|
||
|
res = np.apply_along_axis(_local_counts, 0, X)
|
||
|
# for backward compatibility convert 1-dim output to list/tuple
|
||
|
return _convert_from_multidim(res, tuple)
|
||
|
|
||
|
|
||
|
def mnc2mvsk(args):
|
||
|
"""convert central moments to mean, variance, skew, kurtosis
|
||
|
"""
|
||
|
X = _convert_to_multidim(args)
|
||
|
|
||
|
def _local_counts(args):
|
||
|
# convert four non-central moments to central moments
|
||
|
mnc, mnc2, mnc3, mnc4 = args
|
||
|
mc = mnc
|
||
|
mc2 = mnc2 - mnc * mnc
|
||
|
mc3 = mnc3 - (3 * mc * mc2 + mc ** 3) # 3rd central moment
|
||
|
mc4 = mnc4 - (4 * mc * mc3 + 6 * mc * mc * mc2 + mc ** 4)
|
||
|
return mc2mvsk((mc, mc2, mc3, mc4))
|
||
|
|
||
|
res = np.apply_along_axis(_local_counts, 0, X)
|
||
|
# for backward compatibility convert 1-dim output to list/tuple
|
||
|
return _convert_from_multidim(res, tuple)
|
||
|
|
||
|
# def mnc2mc(args):
|
||
|
# """convert four non-central moments to central moments
|
||
|
# """
|
||
|
# mnc, mnc2, mnc3, mnc4 = args
|
||
|
# mc = mnc
|
||
|
# mc2 = mnc2 - mnc*mnc
|
||
|
# mc3 = mnc3 - (3*mc*mc2+mc**3) # 3rd central moment
|
||
|
# mc4 = mnc4 - (4*mc*mc3+6*mc*mc*mc2+mc**4)
|
||
|
# return mc, mc2, mc
|
||
|
|
||
|
# TODO: no return, did it get lost in cut-paste?
|
||
|
|
||
|
|
||
|
def cov2corr(cov, return_std=False):
|
||
|
"""
|
||
|
convert covariance matrix to correlation matrix
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
cov : array_like, 2d
|
||
|
covariance matrix, see Notes
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
corr : ndarray (subclass)
|
||
|
correlation matrix
|
||
|
return_std : bool
|
||
|
If this is true then the standard deviation is also returned.
|
||
|
By default only the correlation matrix is returned.
|
||
|
|
||
|
Notes
|
||
|
-----
|
||
|
This function does not convert subclasses of ndarrays. This requires that
|
||
|
division is defined elementwise. np.ma.array and np.matrix are allowed.
|
||
|
"""
|
||
|
cov = np.asanyarray(cov)
|
||
|
std_ = np.sqrt(np.diag(cov))
|
||
|
corr = cov / np.outer(std_, std_)
|
||
|
if return_std:
|
||
|
return corr, std_
|
||
|
else:
|
||
|
return corr
|
||
|
|
||
|
|
||
|
def corr2cov(corr, std):
|
||
|
"""
|
||
|
convert correlation matrix to covariance matrix given standard deviation
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
corr : array_like, 2d
|
||
|
correlation matrix, see Notes
|
||
|
std : array_like, 1d
|
||
|
standard deviation
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
cov : ndarray (subclass)
|
||
|
covariance matrix
|
||
|
|
||
|
Notes
|
||
|
-----
|
||
|
This function does not convert subclasses of ndarrays. This requires
|
||
|
that multiplication is defined elementwise. np.ma.array are allowed, but
|
||
|
not matrices.
|
||
|
"""
|
||
|
corr = np.asanyarray(corr)
|
||
|
std_ = np.asanyarray(std)
|
||
|
cov = corr * np.outer(std_, std_)
|
||
|
return cov
|
||
|
|
||
|
|
||
|
def se_cov(cov):
|
||
|
"""
|
||
|
get standard deviation from covariance matrix
|
||
|
|
||
|
just a shorthand function np.sqrt(np.diag(cov))
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
cov : array_like, square
|
||
|
covariance matrix
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
std : ndarray
|
||
|
standard deviation from diagonal of cov
|
||
|
"""
|
||
|
return np.sqrt(np.diag(cov))
|