Skip to content

GIN(): loop counter used as a column index, so cluster search depends on column order #271

Description

@Sullivan07043

Summary

In causallearn/search/HiddenCausal/GIN/GIN.py, the GIN residual is tested against data[:, [z]] where z is a loop counter over positions, not a variable index. The residual is therefore tested against the first len(remain_var_set) columns of data, which include the candidate cluster's own columns, instead of against the remaining variables. The returned clusters then depend on the column order of the input, which the GIN condition does not.

Where

Cluster search, lines 67-68:

remain_var_set = var_set - set(cluster)
e = cal_e_with_gin(data, cov, list(cluster), list(remain_var_set))
pvals = []
for z in range(len(remain_var_set)):
    pvals.append(indep_test(data[:, [z]], e[:, None], method=indep_test_method))

Causal-order phase, lines 98-99, same pattern:

for z in range(len(Z + cluster_i2)):
    pvals.append(indep_test(data[:, [z]], e[:, None], method=indep_test_method))

cal_e_with_gin is given the correct variable lists, so only the independence-test loop is affected. GIN_MI in the same file does not use positional indexing.

Reproduction

The GIN condition is a statement about variables, so relabelling columns must not change the clusters, up to that relabelling. It does:

import numpy as np
from causallearn.search.HiddenCausal.GIN.GIN import GIN

def two_factor(n=500, seed=0):
    rng = np.random.default_rng(seed)
    L1 = rng.uniform(-1, 1, n)
    L2 = 0.6 * L1 + rng.uniform(-1, 1, n)
    cols = []
    for L in (L1, L2):
        for lam in (0.9, 0.8, 0.7):
            cols.append(lam * L + 0.5 * rng.uniform(-1, 1, n))
    return np.column_stack(cols)

X = two_factor()
perm = np.array([3, 4, 5, 0, 1, 2])          # swap the two factor blocks
_, order_a = GIN(X, indep_test_method='hsic', alpha=0.05)
_, order_b = GIN(X[:, perm], indep_test_method='hsic', alpha=0.05)
print([sorted(c) for c in order_a])                       # []
print([sorted(perm[i] for i in c) for c in order_b])      # [[1, 2]]

Two latent factors, three pure indicators each, uniform (non-Gaussian) noise. The same data in a different column order gives no clusters in one case and one cluster in the other.

Suggested fix

Iterate over the variables rather than over positions:

remain = sorted(remain_var_set)
for z in remain:
    pvals.append(indep_test(data[:, [z]], e[:, None], method=indep_test_method))

and likewise for z in Z + cluster_i2: in the causal-order phase.

Environment

causal-learn 0.1.4.7 at commit dd13787, Python 3.12, numpy 2.x.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions