Consider the following Banana function.

def getLogFuncBanana(point):

    import numpy as np
    from scipy.stats import multivariate_normal as mvn
    from scipy.special import logsumexp
    NPAR = 2                        # sum(Banana,gaussian) normalization factor
    normfac = 0.3                   # sum(Banana,gaussian) normalization factor
    lognormfac = np.log(normfac)    # sum(Banana,gaussian) normalization factor
    a, b = 0.7, 1.5                 # parameters of the Banana function
    MeanB = [ -5.0 , 0. ]           # mean vector of Banana function
    MeanG = [  3.5 , 0. ]           # mean vector of Gaussian function
    CovMatB     = np.reshape([0.25,0.,0.,0.81], newshape = (NPAR,NPAR))    # Covariance matrix of Banana function
    CovMatG     = np.reshape([0.15,0.,0.,0.15], newshape = (NPAR,NPAR))    # Covariance matrix of Gaussian function

    LogProb = np.zeros(2)

    # transformed parameters that transform the Gaussian to the Banana function

    pointSkewed = [ -point[0], +point[1] ]

    # Gaussian function

    LogProb[0] = lognormfac + mvn.logpdf(x = pointSkewed, mean = MeanG, cov = CovMatG) # logProbBanana

    # Do variable transformations for the Skewed-Gaussian (banana) function.

    pointSkewed[1]  = pointSkewed[1] * a
    pointSkewed[0]  = pointSkewed[0] / a - (pointSkewed[1]**2 + a**2) * b

    # Banana function

    LogProb[1] = mvn.logpdf(x = pointSkewed, mean = MeanB, cov = CovMatB) # logProbBanana

    return logsumexp(LogProb)

We wish to generate random sample from the distribution function represented by this Python function. We do so via the ParaMonte library’s ParaDRAM MCMC sampler,

!pip install --upgrade --user paramonte
import paramonte as pm
sim = pm.paradram()
sim.spec.chainSize = 30000
sim.runSampler( ndim = 2
              , getLogFunc = getLogFuncBanana

This sampler outputs an MCMC chain that we can subsequently visualize,

%matplotlib notebook
import numpy as np
chain = sim.readChain(renabled = True)[0]
chain.df["Banana Function Value"] = np.exp(chain.df.SampleLogFunc.values)

chain.plot.contour3.savefig(fname = "bananaFuncContour3.png")

chain.plot.scatter3.scatter.kws.s = 0.03
chain.plot.scatter3.scatter.kws.cmap = "winter"
chain.plot.scatter3(zcolumns = "Banana Function Value", ccolumns = "Banana Function Value")
chain.plot.scatter3.savefig(fname = "bananaFuncScatter3.png")



Now, this MCMC chain is a time-series data, meaning that we can compute its autocorrelation (for each data attribute). The ParaMonte library does this for us automatically which we can visualize via,

chain.stats.autocorr.plot.line.savefig(fname = "bananaCompactChainACF.png")


Obviously, the three attributes of this chain are autocorrelated. But, we can remove traces of autocorrelation by choosing an appropriate step by which we jump over (skip) the data to thin (or reduce or decorrelate or refine the chain). Choose such an appropriate step size and refine the data in chain.df and then compute the autocorrelation of the refined data via scipy.signal.correlate function. Then, visualize it similar to the above illustration by the ParaMonte library to ensure the refinement process has truly removed the autocorrelation from your data.

import numpy as np
from scipy.signal import correlate

attribute = attribute - np.mean(attribute)

nlag = len(attribute) - 1
acf = np.zeros(nlag)

acf = correlate ( attribute
                , attribute
                , mode = "full"
acf = acf / acf[0]