Source code for acore.correlation_analysis

import itertools
from typing import NamedTuple

import numpy as np
import pandas as pd
import pingouin as pg
from scipy import stats
from scipy.special import betainc

import acore.utils as utils
from acore.multiple_testing import apply_pvalue_correction


[docs] class CorrelationCoefficient(NamedTuple): coefficient: float pvalue: float def __str__(self): return ( f"CorrelationCoefficient(coefficient={self.coefficient:.4f}," f" pvalue={self.pvalue:.4f})" )
[docs] def corr_lower_triangle(df: pd.DataFrame, **kwargs) -> pd.DataFrame: """Compute the correlation matrix, returning only unique values (lower triangle). Passes kwargs to [pandas.DataFrame.corr](pandas.DataFrame.corr) method. """ corr_df = df.corr(**kwargs) lower_triangle = pd.DataFrame(np.tril(np.ones(corr_df.shape), -1)).astype(bool) lower_triangle.index, lower_triangle.columns = corr_df.index, corr_df.columns return corr_df.where(lower_triangle)
[docs] def calculate_correlations(x, y, method="pearson"): """ Calculates a Spearman (nonparametric) or a Pearson (parametric) correlation coefficient and p-value to test for non-correlation. :param numpy.ndarray x: array 1 :param numpy.ndarray y: array 2 :param str method: chooses which kind of correlation method to run :return: Tuple with two floats, correlation coefficient and two-tailed p-value. Example:: result = calculate_correlations(x, y, method='pearson') """ if method == "pearson": coefficient, pvalue = stats.pearsonr(x, y) elif method == "spearman": coefficient, pvalue = stats.spearmanr(x, y) return CorrelationCoefficient(coefficient, pvalue)
[docs] def run_correlation( df, alpha=0.05, subject=None, group="group", method="pearson", correction="fdr_bh", numeric_only=True, dropna=True, ): """ This function calculates pairwise correlations for columns in dataframe, and returns it in the shape of a edge list with 'weight' as correlation score, and the ajusted p-values. :param df: pandas dataframe with samples as rows and features as columns. :param str subject: name of column containing subject identifiers. :param str group: name of column containing group identifiers. :param str method: method to use for correlation calculation ('pearson', 'spearman'). :param float alpha: error rate. Values velow alpha are considered significant. :param str correction: type of correction see apply_pvalue_correction for methods :param bool numeric_only: if True, only numeric columns are considered for correlation calculation. :param bool dropna: if True, columns with NaN values are dropped before correlation calculation. :return: Pandas dataframe with columns: 'node1', 'node2', 'weight', 'padj' and 'rejected'. Example:: result = run_correlation(df, alpha=0.05, subject='subject', group='group', method='pearson', correction='fdr_bh') """ correlation = pd.DataFrame() # The Repeated measurements correlation calculation is too time consuming so it # only runs if the number of features is less than 200 if subject is not None and utils.check_is_paired(df, subject, group): if len(df[subject].unique()) > 2: if len(df.columns) < 200: correlation = run_rm_correlation( df, alpha=alpha, subject=subject, correction=correction ) return correlation if dropna: df = df.dropna(axis=1, how="any") if numeric_only: df = df.select_dtypes(include="number") if df.empty: raise ValueError("Input dataframe has no data.") r, p = run_efficient_correlation(df, method=method) rdf = pd.DataFrame(r, index=df.columns, columns=df.columns) pdf = pd.DataFrame(p, index=df.columns, columns=df.columns) correlation = utils.convertToEdgeList(rdf, ["node1", "node2", "weight"]) pvalues = utils.convertToEdgeList(pdf, ["node1", "node2", "pvalue"]) correlation = pd.merge(correlation, pvalues, on=["node1", "node2"]) rejected, padj = apply_pvalue_correction( correlation["pvalue"].tolist(), alpha=alpha, method=correction ) correlation["padj"] = padj correlation["rejected"] = rejected correlation["rejected"] = correlation["rejected"].astype(bool) return correlation
[docs] def run_multi_correlation( df_dict, alpha=0.05, subject="subject", on=["subject", "biological_sample"], group="group", method="pearson", correction="fdr_bh", ): """ This function merges all input dataframes and calculates pairwise correlations for all columns. :param dict df_dict: dictionary of pandas dataframes with samples as rows and features as columns. :param str subject: name of the column containing subject identifiers. :param str group: name of the column containing group identifiers. :param list on: column names to join dataframes on (must be found in all dataframes). :param str method: method to use for correlation calculation ('pearson', 'spearman'). :param float alpha: error rate. Values velow alpha are considered significant. :param str correction: type of correction see apply_pvalue_correction for methods :return: Pandas dataframe with columns: 'node1', 'node2', 'weight', 'padj' and 'rejected'. Example:: result = run_multi_correlation(df_dict, alpha=0.05, subject='subject', on=['subject', 'biological_sample'], group='group', method='pearson', correction='fdr_bh') """ multidf = pd.DataFrame() correlation = None for dtype in df_dict: if multidf.empty: if isinstance(df_dict[dtype], pd.DataFrame): multidf = df_dict[dtype] else: if isinstance(df_dict[dtype], pd.DataFrame): multidf = pd.merge(multidf, df_dict[dtype], how="inner", on=on) if not multidf.empty: correlation = run_correlation( multidf, alpha=alpha, subject=subject, group=group, method=method, correction=correction, ) return correlation
[docs] def calculate_rm_correlation(df, x, y, subject): """ Computes correlation and p-values between two columns a and b in df with repeated measures (rm). :param df: pandas dataframe with subjects as rows and two features and columns. :param str x: feature a name. :param str y: feature b name. :param subject: column name containing the covariate variable. :return: Tuple with values for: feature a, feature b, correlation, p-value and degrees of freedom. Example:: result = calculate_rm_correlation(df, x='feature a', y='feature b', subject='subject') """ result = pg.rm_corr(data=df, x=x, y=y, subject=subject) return ( x, y, result["r"].values[0], result["pval"].values[0], result["dof"].values[0], )
[docs] def run_rm_correlation(df, alpha=0.05, subject="subject", correction="fdr_bh"): """ Computes pairwise repeated measurements correlations for all columns in dataframe, and returns results as an edge list with 'weight' as correlation score, p-values, degrees of freedom and ajusted p-values. :param df: pandas dataframe with samples as rows and features as columns. :param str subject: name of column containing subject identifiers. :param float alpha: error rate. Values velow alpha are considered significant. :param str correction: type of correction type see apply_pvalue_correction for methods :return: Pandas dataframe with columns: 'node1', 'node2', 'weight', 'pvalue', 'dof', 'padj' and 'rejected'. Example:: result = run_rm_correlation(df, alpha=0.05, subject='subject', correction='fdr_bh') """ rows = [] if df.empty: raise ValueError("Input dataframe is empty.") df = df.set_index(subject)._get_numeric_data().dropna(axis=1) df.columns = df.columns.astype(str) combinations = itertools.combinations(df.columns, 2) df = df.reset_index() for x, y in combinations: row = [x, y] subset = df[[x, y, subject]] row.extend(pg.rm_corr(subset, x, y, subject).values.tolist()[0]) rows.append(row) correlation = pd.DataFrame( rows, columns=["node1", "node2", "weight", "dof", "pvalue", "CI95%", "power"], ) rejected, padj = apply_pvalue_correction( correlation["pvalue"].tolist(), alpha=alpha, method=correction ) correlation["padj"] = padj correlation["rejected"] = rejected correlation = correlation[correlation.rejected] correlation["padj"] = correlation["padj"].apply(lambda x: str(round(x, 5))) return correlation
[docs] def calculate_pvalue_correlation_old(r: pd.DataFrame, n_obs: int) -> pd.DataFrame: """Calculate p-values for Pearson correlation using a all values from the correlation matrix. Parameters ---------- r : pd.DataFrame Correlation matrix. n_obs : int Number of observations used to calculate the correlation matrix (assumes no missing values). Returns ------- pd.DataFrame p-value matrix assuming fixed number of observations. """ upper_idx = np.triu_indices(r.shape[0], 1) rf = r[upper_idx] df = n_obs - 2 ts = rf * rf * (df / (1 - rf * rf)) pf = betainc(0.5 * df, 0.5, df / (df + ts)) p = np.zeros(shape=r.shape) p[np.triu_indices(p.shape[0], 1)] = pf p[np.tril_indices(p.shape[0], -1)] = pf p[np.diag_indices(p.shape[0])] = np.zeros(p.shape[0]) return p
[docs] def calculate_pvalue_correlation(r, n_obs): """Calculate p-values for Pearson correlation using a all values from the correlation matrix. Tested against Pearson correlation using a all values from the correlation matrix, see https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.pearsonr.html Parameters ---------- r : pd.DataFrame Correlation matrix. n_obs : int Number of observations used to calculate the correlation matrix (assumes no missing values). Returns ------- pd.DataFrame p-value matrix assuming fixed number of observations. """ upper_idx = np.triu_indices_from(r, k=1) if n_obs < 3: msg = "Need at least three observations to compute correlation p-values." raise ValueError(msg) df = n_obs - 2 rf = r[upper_idx] denom = np.clip(1 - np.square(rf), np.finfo(float).eps, None) ts = np.square(rf) * (df / denom) pf = betainc(0.5 * df, 0.5, df / (df + ts)) # Initialize with ones so that diagonal p-values are 1 (non-significant) p = np.ones_like(r) p[upper_idx] = pf p[(upper_idx[1], upper_idx[0])] = pf return p
[docs] def run_efficient_correlation(data, method="pearson"): """ Calculates pairwise correlations and returns lower triangle of the matrix with correlation values and p-values. For pearson correlation, p-values are calculated assuming a fixed number of observations. :param data: pandas dataframe with samples as index and features as columns (numeric data only). :param str method: method to use for correlation calculation ('pearson', 'spearman'). :return: Two numpy arrays: correlation and p-values. Example:: result = run_efficient_correlation(data, method='pearson') """ matrix = data if method == "pearson": r = np.corrcoef(matrix, rowvar=False) p = None elif method == "spearman": r, p = stats.spearmanr(matrix, axis=0) if p is None: p = calculate_pvalue_correlation(r, n_obs=data.shape[0]) diagonal = np.triu_indices(r.shape[0], 1) r[diagonal] = np.nan p[diagonal] = np.nan return r, p