def test_disequilibrium2(): """ Test that while the XOR distribution has non-zero disequilibrium, all its marginals have zero disequilibrium (are uniform). """ assert disequilibrium(d2) > 0 for rvs in combinations(flatten(d2.rvs), 2): assert disequilibrium(d2, rvs) == pytest.approx(0)
def test_disequilibrium2(): """ Test that while the XOR distribution has non-zero disequilibrium, all its marginals have zero disequilibrium (are uniform). """ assert_true(disequilibrium(d2) > 0) for rvs in combinations(flatten(d2.rvs), 2): assert_almost_equal(disequilibrium(d2, rvs), 0)
def __init__(self, dist, inputs=None, output=None, reds=None, pis=None, **kwargs): """ Parameters ---------- dist : Distribution The distribution to compute the decomposition on. inputs : iter of iters, None The set of input variables. If None, `dist.rvs` less indices in `output` is used. output : iter, None The output variable. If None, `dist.rvs[-1]` is used. reds : dict, None Redundancy values pre-assessed. pis : dict, None Partial information values pre-assessed. """ self._dist = dist if output is None: output = dist.rvs[-1] if inputs is None: inputs = [var for var in dist.rvs if var[0] not in output] self._inputs = tuple(map(tuple, inputs)) self._output = tuple(output) self._kwargs = kwargs self._lattice = full_constraint_lattice(self._inputs) # To compute the Mobius inversion reusing dit's code, we reverse the # lattice, compute Mobius, and reverse it back again. self._lattice = self._lattice.reverse() self._lattice.root = next(iter(nx.topological_sort(self._lattice))) self._total = coinformation( self._dist, [list(flatten(self._inputs)), self._output]) self._reds = {} if reds is None else reds self._pis = {} if pis is None else pis self._compute() self._lattice = self._lattice.reverse() self._lattice.root = next(iter(nx.topological_sort(self._lattice)))
def _total_correlation_ksg_scipy(data, rvs, crvs=None, k=4, noise=1e-10): """ Compute the total correlation from observations. The total correlation is computed between the columns specified in `rvs`, given the columns specified in `crvs`. This utilizes the KSG kNN density estimator, and works on discrete, continuous, and mixed data. Parameters ---------- data : np.array A set of observations of a distribution. rvs : iterable of iterables The columns for which the total correlation is to be computed. crvs : iterable The columns upon which the total correlation should be conditioned. k : int The number of nearest neighbors to use in estimating the local kernel density. noise : float The standard deviation of the normally-distributed noise to add to the data. Returns ------- tc : float The total correlation of `rvs` given `crvs`. Notes ----- The total correlation is computed in bits, not nats as most KSG estimators do. """ # KSG suggest adding noise (to break symmetries?) data = _fuzz(data, noise) if crvs is None: crvs = [] digamma_N = digamma(len(data)) log_2 = np.log(2) all_rvs = list(flatten(rvs)) + crvs rvs = [rv + crvs for rv in rvs] d_rvs = [len(data[0, rv]) for rv in rvs] tree = cKDTree(data[:, all_rvs]) tree_rvs = [cKDTree(data[:, rv]) for rv in rvs] epsilons = tree.query(data[:, all_rvs], k + 1, p=np.inf)[0][:, -1] # k+1 because of self n_rvs = [ np.array([ len(t.query_ball_point(point, epsilon, p=np.inf)) for point, epsilon in zip(data[:, rv], epsilons) ]) for rv, t in zip(rvs, tree_rvs) ] log_epsilons = np.log(epsilons) h_rvs = [-digamma(n_rv).mean() for n_rv, d in zip(n_rvs, d_rvs)] h_all = -digamma(k) if crvs: tree_crvs = cKDTree(data[:, crvs]) n_crvs = np.array([ len(tree_crvs.query_ball_point(point, epsilon, p=np.inf)) for point, epsilon in zip(data[:, crvs], epsilons) ]) h_crvs = -digamma(n_crvs).mean() else: h_rvs = [ h_rv + digamma_N + d * (log_2 - log_epsilons).mean() for h_rv, d in zip(h_rvs, d_rvs) ] h_all += digamma_N + sum(d_rvs) * (log_2 - log_epsilons).mean() h_crvs = 0 tc = sum(h_rv - h_crvs for h_rv in h_rvs) - (h_all - h_crvs) return tc / log_2
def _total_correlation_ksg_scipy(data, rvs, crvs=None, k=4, noise=1e-10): """ Compute the total correlation from observations. The total correlation is computed between the columns specified in `rvs`, given the columns specified in `crvs`. This utilizes the KSG kNN density estimator, and works on discrete, continuous, and mixed data. Parameters ---------- data : np.array A set of observations of a distribution. rvs : iterable of iterables The columns for which the total correlation is to be computed. crvs : iterable The columns upon which the total correlation should be conditioned. k : int The number of nearest neighbors to use in estimating the local kernel density. noise : float The standard deviation of the normally-distributed noise to add to the data. Returns ------- tc : float The total correlation of `rvs` given `crvs`. Notes ----- The total correlation is computed in bits, not nats as most KSG estimators do. """ # KSG suggest adding noise (to break symmetries?) data = _fuzz(data, noise) if crvs is None: crvs = [] digamma_N = digamma(len(data)) log_2 = np.log(2) all_rvs = list(flatten(rvs)) + crvs rvs = [rv + crvs for rv in rvs] d_rvs = [len(data[0, rv]) for rv in rvs] tree = cKDTree(data[:, all_rvs]) tree_rvs = [cKDTree(data[:, rv]) for rv in rvs] epsilons = tree.query(data[:, all_rvs], k + 1, p=np.inf)[0][:, -1] # k+1 because of self n_rvs = [ np.array([len(t.query_ball_point(point, epsilon, p=np.inf)) for point, epsilon in zip(data[:, rv], epsilons)]) for rv, t in zip(rvs, tree_rvs)] log_epsilons = np.log(epsilons) h_rvs = [-digamma(n_rv).mean() for n_rv, d in zip(n_rvs, d_rvs)] h_all = -digamma(k) if crvs: tree_crvs = cKDTree(data[:, crvs]) n_crvs = np.array([len(tree_crvs.query_ball_point(point, epsilon, p=np.inf)) for point, epsilon in zip(data[:, crvs], epsilons)]) h_crvs = -digamma(n_crvs).mean() else: h_rvs = [h_rv + digamma_N + d * (log_2 - log_epsilons).mean() for h_rv, d in zip(h_rvs, d_rvs)] h_all += digamma_N + sum(d_rvs) * (log_2 - log_epsilons).mean() h_crvs = 0 tc = sum([h_rv - h_crvs for h_rv in h_rvs]) - (h_all - h_crvs) return tc / log_2