andrey.pnl#
- andrey.pnl(data, *, alpha=None)[source]#
Test the direction of a variable pair with the post-nonlinear (PNL) model.
PNL [1] [2] fits
y = f2(f1(x) + e)in each direction. An HSIC test checks whether the estimated noiseeis independent of the proposed cause. The two p-values determine the edge, and the two test statistics decide a tie.- Parameters:
data (
ArrayLike) – The two variables, one per column: column 0 isxand column 1 isy. Coerced tofloat64. A DataFrame’s column names become the structure’slabels.alpha (
float|None) –Nonechooses the direction with the larger p-value, or with the smaller test statistic when the p-values are equal. A significance level such as0.05draws an edge only when exactly one p-value exceeds it. See Notes.
- Returns:
Contains a two-node
GraphStructureof kinddagwith the chosen edge, or no edge.metadata["algorithm"]is"PNL";pval_forwardis the independence p-value forx -> yandpval_backwardis the p-value fory -> x;stat_forwardandstat_backwardare the HSIC statistics of the same two tests, each divided by its mean under independence.- Return type:
- Raises:
ValueError – If
datadoes not have 2 columns, or ifalphais not in(0, 1).
Notes
With
alpha=None, the edge points in the direction with the larger p-value. On a large sample, or a pair that fits neither direction, both p-values can be zero; equal p-values leave the choice to the HSIC statistics, each divided by its mean under independence, and the edge points in the direction with the smaller one, the weaker dependence between the proposed cause and its estimated noise. Equal statistics give no edge. The rule therefore produces an edge even for unrelated variables or a pair whose direction cannot be identified, such as a linear Gaussian pair. Mooij et al. (2016) [4] compare p-values to avoid choosing a significance level and found the comparison among the best methods in their benchmark of additive-noise models.With a significance level, only the p-values decide; a direction passes when its p-value exceeds
alpha:only
x -> ypasses: the edgex -> y;only
y -> xpasses: the edgey -> x;both pass: no edge, since neither direction is rejected;
neither passes: no edge, since both directions are rejected.
Zhang and Hyvarinen (2010) [2] use a level of 0.01 for PNL; Hoyer et al. (2008) [3] use 0.02 for additive-noise models. The result depends on
alpha. Estimated noise can appear dependent on the true cause, so a standard level can reject the true direction; no theory establishes the right level (Mooij et al., 2016). Both p-values and both statistics remain inmetadata.PNL fits each transform as a sum of smooth basis functions, so the fit is deterministic but can miss a sharply varying nonlinearity.
The kernel matrices of the two HSIC tests run on a CUDA GPU, in float64, under the
cudabackend, or underautofromANDREY_PNL_GPU_THRESHOLDrows squared once the GPU is running in the process; the fits stay on the CPU, and the p-values then match the CPU’s to rounding.On the CPU,
num_workersabove 1 (andrey.configorANDREY_NUM_WORKERS) fits the two directions on two threads, each limited to one BLAS thread; with one worker the BLAS threads are not limited. On a machine with several cores the fits then round differently, so the p-values and statistics with one worker and with several can differ in their last digits, and the direction can differ when the two p-values nearly tie. Each thread holds threen x nfloat64 matrices; when the threads asked for would hold more than 4 GiB, fewer run, with aPerformanceWarning.Limitations. Measured on simulated cause-effect pairs with a post-nonlinear mechanism, a linear inner transform and an invertible nonlinear outer one, and Laplace noise, from 200 to 5,000 rows. It named the true direction on three pairs in four at 200 rows and on every pair from 1,000 rows. The median fit took 0.8 s at 5,000 rows, on 4 cores. Each HSIC test builds kernel matrices with one row and one column per sample, so time and memory grow with the square of the row count: each worker thread holds three such float64 matrices, 600 MB at 5,000 rows.
References
Examples
>>> import numpy as np >>> from andrey import pnl >>> rng = np.random.default_rng(2) >>> x = rng.uniform(-1, 1, 300) >>> y = np.tanh(x**3 + 0.3 * rng.uniform(-1, 1, 300)) >>> out = pnl(np.column_stack([x, y])) >>> out.structure.oriented_edges() [(0, 1, 'directed')] >>> out.metadata["pval_forward"] > 0.05 >= out.metadata["pval_backward"] True