Repository: statsmodels/statsmodels

Issue:
Adding more kernels in KDE
Statsmodels consists of various different kernels but few additions such as exponential kernel and quartic kernel could be a good addition to the group of kernels. 


The additions would be similar as other kernels, classes would be created in <code>statsmodels/sandbox/nonparametric/kernels.py</code>, which would be then called in the list of kernels in <code>statsmodel/nonparametric/kde.py</code>.




Discussion:
Do you have any code for those?

I added asymmetric kernels,  beta kernel (kernels for unit interval) and kernels for positive valued random variables, but they don't use an interface that is compatible with the older kernel functions in nonparametric, at least not with those used for MultivariateKDE.

Asymmetric kernels don't have the property that they depend on distance between points, which is assumed by KDEUnivariate.

Is exponential kernel an asymmetric kernel?

I think you are correct here, exponential kernel is an asymmetric kernel and could be added in <code>kernels_asymmetric.py</code>. Looking into the implementation of asymmetric kernels, wouldn't it be better to have a single class of these kernels instead of separate functions?

Re code, should I make a PR for quartic kernel?
> Looking into the implementation of asymmetric kernels, wouldn't it be better to have a single class of these kernels instead of separate functions?

Not until we have a better idea of how they fit in and will be reused.
I wrote them mainly to work out the math and see how well they do.
My current use of them is to support and compare with parametric multivariate distributions in the context of copulas, e.g. nonparametric Beta and Bernstein copulas/distributions.
`MultivariateKDE` could use them and uses separate functions for cdf and pdf. But I didn't like the signature of those function and used a different argument list. Tying them into MultivariateKDE would automatically (?) give us optimal bandwidth selection.

Exponential kernel might be a special case of an existing asymmetric kernel. But I don't remember those details.

> Re code, should I make a PR for quartic kernel?

Yes, that sounds useful. But I haven't looked at that part in a long time.

aside: 
I also wrote asymmetric kernel `rvs` functions for generating random samples, but the only one for which I found references is Beta kernel. I use that one in copulas. I didn't add the rvs functions to statsmodels yet because I don't have references, and I wrote them "by analogy".


Resolving patch:
diff --git a/statsmodels/nonparametric/kde.py b/statsmodels/nonparametric/kde.py
--- a/statsmodels/nonparametric/kde.py
+++ b/statsmodels/nonparametric/kde.py
@@ -33,6 +33,7 @@
     triw=kernels.Triweight,
     cos=kernels.Cosine,
     cos2=kernels.Cosine2,
+    tric=kernels.Tricube
 )
 
 
diff --git a/statsmodels/sandbox/nonparametric/kernels.py b/statsmodels/sandbox/nonparametric/kernels.py
--- a/statsmodels/sandbox/nonparametric/kernels.py
+++ b/statsmodels/sandbox/nonparametric/kernels.py
@@ -563,3 +563,16 @@ def __init__(self, h=1.0):
         self._L2Norm = 1.5
         self._kernel_var = 0.03267274151216444  # = 1/12. - 0.5 / np.pi**2
         self._order = 2
+
+class Tricube(CustomKernel):
+    """
+    Tricube Kernel
+
+    K(u) = 0.864197530864 * (1 - abs(x)**3)**3 between -1.0 and 1.0
+    """
+    def __init__(self,h=1.0):
+        CustomKernel.__init__(self,shape=lambda x: 0.864197530864 * (1 - abs(x)**3)**3,
+                              h=h, domain=[-1.0, 1.0], norm = 1.0)
+        self._L2Norm = 175.0/247.0
+        self._kernel_var = 35.0/243.0
+        self._order = 2


Test patch:
diff --git a/statsmodels/nonparametric/tests/test_kernels.py b/statsmodels/nonparametric/tests/test_kernels.py
--- a/statsmodels/nonparametric/tests/test_kernels.py
+++ b/statsmodels/nonparametric/tests/test_kernels.py
@@ -149,3 +149,19 @@ class TestBiweight(CheckKernelMixin):
     kern = kernels.Biweight()
     se_n_diff = 9
     low_rtol = 0.3
+
+
+def test_tricube():
+    # > library(kedd)
+    # > xx = c(-1., -0.75, -0.5, -0.25, 0., 0.25, 0.5, 0.75, 1.)
+    # > res = kernel.fun(x = xx, kernel="tricube",deriv.order=0)
+    # > res$kx
+
+    res_kx = [
+        0.0000000000000000, 0.1669853116259163, 0.5789448302469136,
+        0.8243179321289062, 0.8641975308641975, 0.8243179321289062,
+        0.5789448302469136, 0.1669853116259163, 0.0000000000000000
+        ]
+    xx = np.linspace(-1, 1, 9)
+    kx = kernels.Tricube()(xx)
+    assert_allclose(kx, res_kx, rtol=1e-10)