py-why / py-why/causal-learn

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

Open Beginner friendly
#271 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
1.7k
Forks
274
PR merge metrics
No merged PRs in 30d

Description

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 dd1378775f24f8cc6c2c4a2bb90b4423953930f1, Python 3.12, numpy 2.x.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in causallearn/search/HiddenCausal/GIN/GIN.py at the cluster-search loop around lines 67-68, then inspect the analogous causal-order loop around lines 98-99. Run the supplied two-factor permutation reproduction with GIN and verify that both loops use the intended variable indices so clusters remain consistent under column relabelling.

Written by the indexing model from the issue text.

Assessment

Tech stack
numpy, python
Domain
machine-learning
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
84/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.