Source code for kim.pre_analysis.pairwise_analysis

# Pairwise analysis using either mutual information or correlation coefficient
#
# Author: Peishi Jiang <shixijps@gmail.com>

import numpy as np
from joblib import Parallel, delayed

from .sst import shuffle_test
from .metric_calculator import MetricBase
from tqdm import tqdm

from jaxtyping import Array


[docs] def pairwise_analysis( xdata: Array, ydata: Array, metric_calculator: MetricBase, sst: bool=False, ntest: int=100, alpha: float=0.05, n_jobs: int=-1, seed_shuffle: int=1234, verbose: int=0 ): """Perform the pairwise analysis using either mutual information or correlation coefficient. Args: xdata (array-like): the predictors with shape (Ns, Nx) ydata (array-like): the predictands with shape (Ns, Ny) metric_calculator (class): the metric calculator sst (bool): whether to perform statistical significance test. Defaults to False. ntest (int): number of shuffled samples in sst. Defaults to 100. alpha (float): the significance level. Defaults to 0.05. n_jobs (int): the number of processers/threads used by joblib. Defaults to -1. seed_shuffle (int): the random seed number for doing shuffle test. Defaults to 1234. verbose (int): the verbosity level (0: normal, 1: debug). Defaults to 0. Returns: (array, array): the sensitivity, the sensitivity mask """ # Data dimensions assert xdata.shape[0] == ydata.shape[0], \ "xdata and ydata must be the same number of samples" # Ns = xdata.shape[0] Nx = xdata.shape[1] Ny = ydata.shape[1] # Initialize the return sensitivity values and masks sensitivity = np.zeros([Nx, Ny]) sensitivity_mask = np.ones([Nx, Ny], dtype='bool') if verbose == 1: print("Performing pairwise analysis to remove insensitive inputs ...") def analyze_one_input(x): # Sensitivity of one input x to every output, computed serially in one worker. # shuffle_test reseeds from seed_shuffle on every call, so the result is # independent of which worker runs it and identical to the serial version. sens_i = np.zeros(Ny) mask_i = np.ones(Ny, dtype='bool') for j in range(Ny): y = ydata[:,j] if not sst: sens_i[j] = metric_calculator(x, y) else: sens_i[j], mask_i[j] = shuffle_test( x, y, metric_calculator, None, ntest, alpha, n_jobs=1, random_seed=seed_shuffle ) return sens_i, mask_i # Parallelize over the Nx inputs. Each task (Ny shuffle tests of ~100 metric # evaluations) is large enough to amortize the dispatch cost, unlike the # previous scheme of one joblib call of ntest tiny tasks per (x, y) pair. results = Parallel(n_jobs=n_jobs, backend='loky')( delayed(analyze_one_input)(xdata[:,i]) for i in tqdm(range(Nx)) ) for i, (sens_i, mask_i) in enumerate(results): sensitivity[i, :] = sens_i sensitivity_mask[i, :] = mask_i return sensitivity, sensitivity_mask