correlation-network 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,56 @@
1
+ """correlation-network: Build and analyze association networks from DataFrames."""
2
+
3
+ from importlib.metadata import version
4
+
5
+ from correlation_network._types import (
6
+ AssociationMethod,
7
+ BinningMethod,
8
+ CentralityMetric,
9
+ LayoutAlgorithm,
10
+ )
11
+ from correlation_network.association import (
12
+ AssociationStrategy,
13
+ AutoAssociationStrategy,
14
+ CorrelationRatioStrategy,
15
+ CramersVStrategy,
16
+ KendallStrategy,
17
+ MutualInformationStrategy,
18
+ PearsonStrategy,
19
+ PhiKStrategy,
20
+ SpearmanStrategy,
21
+ )
22
+ from correlation_network.centrality import CentralityAnalyzer
23
+ from correlation_network.exceptions import (
24
+ CorrelationNetworkError,
25
+ InsufficientColumnsError,
26
+ NoCompleteObservationsError,
27
+ NotFittedError,
28
+ )
29
+ from correlation_network.network import CorrelationNetwork
30
+ from correlation_network.visualization import NetworkVisualizer
31
+
32
+ # Read from the installed metadata so pyproject.toml stays the single source
33
+ __version__ = version("correlation-network")
34
+
35
+ __all__ = [
36
+ "AssociationMethod",
37
+ "AssociationStrategy",
38
+ "AutoAssociationStrategy",
39
+ "BinningMethod",
40
+ "CentralityAnalyzer",
41
+ "CentralityMetric",
42
+ "CorrelationNetwork",
43
+ "CorrelationNetworkError",
44
+ "CorrelationRatioStrategy",
45
+ "CramersVStrategy",
46
+ "InsufficientColumnsError",
47
+ "KendallStrategy",
48
+ "LayoutAlgorithm",
49
+ "MutualInformationStrategy",
50
+ "NetworkVisualizer",
51
+ "NoCompleteObservationsError",
52
+ "NotFittedError",
53
+ "PearsonStrategy",
54
+ "PhiKStrategy",
55
+ "SpearmanStrategy",
56
+ ]
@@ -0,0 +1,65 @@
1
+ """Shared graph utility functions."""
2
+
3
+ import networkx as nx
4
+
5
+ # Floor for converted distances. A |weight| of 1.0 would otherwise map to a
6
+ # zero-length edge, collapsing two nodes into the same point for path-based
7
+ # metrics and making closeness degenerate.
8
+ _MIN_DISTANCE = 1e-6
9
+
10
+
11
+ def distance_graph(graph: nx.Graph) -> nx.Graph:
12
+ """Return a copy with edge weights converted to distances.
13
+
14
+ Association strength and graph distance run in opposite directions: a
15
+ strong association (``|w|`` near 1) means two nodes are *close*, while
16
+ NetworkX shortest-path algorithms read the edge attribute as a *cost*.
17
+ Passing raw association values to Dijkstra therefore routes paths through
18
+ the weakest edges. This helper applies ``distance = 1 - |weight|`` so that
19
+ strong associations become short distances.
20
+
21
+ Parameters
22
+ ----------
23
+ graph : nx.Graph
24
+ A weighted undirected graph whose weights are association values.
25
+
26
+ Returns
27
+ -------
28
+ nx.Graph
29
+ Copy with edge weights replaced by distances, floored at a small
30
+ positive value so no edge has zero or negative length.
31
+ """
32
+ g = graph.copy()
33
+ nx.set_edge_attributes(
34
+ g,
35
+ values={
36
+ (u, v): max(_MIN_DISTANCE, 1.0 - abs(d.get("weight", 1.0)))
37
+ for u, v, d in g.edges(data=True)
38
+ },
39
+ name="weight",
40
+ )
41
+ return g
42
+
43
+
44
+ def abs_weight_graph(graph: nx.Graph) -> nx.Graph:
45
+ """Return a copy of the graph with absolute edge weights.
46
+
47
+ Useful for algorithms (Dijkstra, layout) that require non-negative weights.
48
+
49
+ Parameters
50
+ ----------
51
+ graph : nx.Graph
52
+ A weighted undirected graph.
53
+
54
+ Returns
55
+ -------
56
+ nx.Graph
57
+ Copy with all edge weights replaced by their absolute values.
58
+ """
59
+ g = graph.copy()
60
+ nx.set_edge_attributes(
61
+ g,
62
+ values={(u, v): abs(d.get("weight", 1)) for u, v, d in g.edges(data=True)},
63
+ name="weight",
64
+ )
65
+ return g
@@ -0,0 +1,34 @@
1
+ """Type aliases and literal types."""
2
+
3
+ from typing import Literal
4
+
5
+ CorrelationMethod = Literal["pearson", "spearman", "kendall"]
6
+
7
+ AssociationMethod = Literal[
8
+ "pearson",
9
+ "spearman",
10
+ "kendall",
11
+ "cramers_v",
12
+ "correlation_ratio",
13
+ "phik",
14
+ "mutual_information",
15
+ "auto",
16
+ ]
17
+
18
+ CentralityMetric = Literal[
19
+ "degree",
20
+ "strength",
21
+ "betweenness",
22
+ "eigenvector",
23
+ "closeness",
24
+ ]
25
+
26
+ LayoutAlgorithm = Literal[
27
+ "kamada_kawai",
28
+ "spring",
29
+ "circular",
30
+ "shell",
31
+ "spectral",
32
+ ]
33
+
34
+ BinningMethod = Literal["uniform", "quantile"]
@@ -0,0 +1,118 @@
1
+ """Input validation utilities."""
2
+
3
+ from collections.abc import Collection
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ from correlation_network.exceptions import NoCompleteObservationsError, NotFittedError
9
+
10
+ NOT_FITTED_MESSAGE = (
11
+ "The instance is not fitted yet. "
12
+ "Call 'fit' with appropriate arguments before using the estimator."
13
+ )
14
+
15
+
16
+ def check_is_fitted(
17
+ estimator: object,
18
+ attributes: set[str] | None = None,
19
+ message: str = NOT_FITTED_MESSAGE,
20
+ ) -> None:
21
+ """Check whether an estimator has been fitted.
22
+
23
+ Parameters
24
+ ----------
25
+ estimator : object
26
+ The estimator instance to check.
27
+ attributes : set[str] | None
28
+ Fitted attributes to look for. Defaults to ``{"adjacency_matrix_"}``.
29
+ message : str
30
+ Error message raised when the estimator is not fitted.
31
+
32
+ Raises
33
+ ------
34
+ NotFittedError
35
+ If none of the expected fitted attributes are found.
36
+ """
37
+ if attributes is None:
38
+ attributes = {"adjacency_matrix_"}
39
+ object_attrs = set(vars(estimator))
40
+ if not attributes.intersection(object_attrs):
41
+ raise NotFittedError(message)
42
+
43
+
44
+ def complete_pair(x: pd.Series, y: pd.Series) -> tuple[pd.Series, pd.Series]:
45
+ """Restrict two series to the rows where both values are present.
46
+
47
+ Parameters
48
+ ----------
49
+ x : pd.Series
50
+ First column of the pair.
51
+ y : pd.Series
52
+ Second column of the pair, aligned with ``x``.
53
+
54
+ Returns
55
+ -------
56
+ tuple[pd.Series, pd.Series]
57
+ Both series, filtered to their pairwise-complete rows.
58
+
59
+ Raises
60
+ ------
61
+ NoCompleteObservationsError
62
+ If the two series share no rows where both values are present.
63
+ """
64
+ valid = x.notna() & y.notna()
65
+ if not valid.any():
66
+ raise NoCompleteObservationsError(columns=(str(x.name), str(y.name)))
67
+ return x[valid], y[valid]
68
+
69
+
70
+ def check_pairwise_overlap(dataframe: pd.DataFrame) -> None:
71
+ """Ensure every column pair shares at least one complete row.
72
+
73
+ Matrix-level counterpart of :func:`complete_pair`, for strategies that
74
+ delegate the pairwise computation to pandas, which reports such pairs as
75
+ ``NaN`` rather than raising.
76
+
77
+ Parameters
78
+ ----------
79
+ dataframe : pd.DataFrame
80
+ Columns to check.
81
+
82
+ Raises
83
+ ------
84
+ NoCompleteObservationsError
85
+ For the first column pair with no rows where both values are present.
86
+ """
87
+ present = dataframe.notna().to_numpy()
88
+ if len(present) and present.all():
89
+ return
90
+ # Float matmul goes through BLAS; integer matmul does not and takes
91
+ # seconds on large frames. Counts stay exact far beyond realistic sizes.
92
+ present = present.astype(np.float64)
93
+ overlap = present.T @ present
94
+ rows, cols = np.nonzero(np.triu(overlap == 0, k=1))
95
+ if rows.size:
96
+ names = dataframe.columns
97
+ raise NoCompleteObservationsError(columns=(str(names[rows[0]]), str(names[cols[0]])))
98
+
99
+
100
+ def check_option(name: str, value: object, options: Collection[str]) -> None:
101
+ """Ensure a string argument is one of the supported options.
102
+
103
+ Parameters
104
+ ----------
105
+ name : str
106
+ Argument name, used in the error message.
107
+ value : object
108
+ Value received.
109
+ options : Collection[str]
110
+ Accepted values.
111
+
112
+ Raises
113
+ ------
114
+ ValueError
115
+ If ``value`` is not in ``options``.
116
+ """
117
+ if value not in options:
118
+ raise ValueError(f"Unknown {name} {value!r}. Available options: {tuple(options)}")
@@ -0,0 +1,29 @@
1
+ """Association strategies for computing pairwise relationships."""
2
+
3
+ from correlation_network.association._auto import AutoAssociationStrategy
4
+ from correlation_network.association._base import AssociationStrategy
5
+ from correlation_network.association._categorical import (
6
+ CorrelationRatioStrategy,
7
+ CramersVStrategy,
8
+ )
9
+ from correlation_network.association._correlation import (
10
+ KendallStrategy,
11
+ PearsonStrategy,
12
+ SpearmanStrategy,
13
+ )
14
+ from correlation_network.association._universal import (
15
+ MutualInformationStrategy,
16
+ PhiKStrategy,
17
+ )
18
+
19
+ __all__ = [
20
+ "AssociationStrategy",
21
+ "AutoAssociationStrategy",
22
+ "CorrelationRatioStrategy",
23
+ "CramersVStrategy",
24
+ "KendallStrategy",
25
+ "MutualInformationStrategy",
26
+ "PearsonStrategy",
27
+ "PhiKStrategy",
28
+ "SpearmanStrategy",
29
+ ]
@@ -0,0 +1,104 @@
1
+ """Auto-detection strategy that dispatches by column type."""
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+
6
+ from correlation_network.association._base import AssociationStrategy
7
+ from correlation_network.association._categorical import (
8
+ CorrelationRatioStrategy,
9
+ CramersVStrategy,
10
+ )
11
+ from correlation_network.association._correlation import SpearmanStrategy
12
+ from correlation_network.association._dtypes import (
13
+ categorical_columns,
14
+ numeric_columns,
15
+ )
16
+
17
+
18
+ class AutoAssociationStrategy:
19
+ """Automatically selects the appropriate association measure per column pair.
20
+
21
+ Default dispatch:
22
+ - numeric-numeric: Spearman correlation
23
+ - categorical-categorical: Cramer's V
24
+ - categorical-numeric: Correlation ratio (eta)
25
+
26
+ Bool and string columns count as categorical.
27
+
28
+ Notes
29
+ -----
30
+ Numeric-numeric correlations are reduced to their **absolute value**, so
31
+ the resulting matrix is in [0, 1] and the sign of a negative correlation
32
+ is lost. This keeps the three measures on a comparable scale (Cramer's V
33
+ and eta are already non-negative), but means ``method="auto"`` never
34
+ produces negative edge weights — the positive/negative edge colouring in
35
+ :class:`~correlation_network.NetworkVisualizer` has no effect on such a
36
+ graph. Use an explicit correlation method to retain the sign.
37
+
38
+ Parameters
39
+ ----------
40
+ numeric_strategy : AssociationStrategy | None
41
+ Strategy for numeric-numeric pairs. Defaults to SpearmanStrategy.
42
+ categorical_strategy : AssociationStrategy | None
43
+ Strategy for categorical-categorical pairs. Defaults to CramersVStrategy.
44
+ mixed_strategy : AssociationStrategy | None
45
+ Strategy for categorical-numeric pairs. Defaults to CorrelationRatioStrategy.
46
+ """
47
+
48
+ def __init__(
49
+ self,
50
+ numeric_strategy: AssociationStrategy | None = None,
51
+ categorical_strategy: AssociationStrategy | None = None,
52
+ mixed_strategy: AssociationStrategy | None = None,
53
+ ) -> None:
54
+ self._numeric = numeric_strategy if numeric_strategy is not None else SpearmanStrategy()
55
+ self._categorical = (
56
+ categorical_strategy if categorical_strategy is not None else CramersVStrategy()
57
+ )
58
+ self._mixed = mixed_strategy if mixed_strategy is not None else CorrelationRatioStrategy()
59
+
60
+ def compute(self, dataframe: pd.DataFrame) -> pd.DataFrame:
61
+ """Compute a combined association matrix using type-aware dispatch.
62
+
63
+ Parameters
64
+ ----------
65
+ dataframe : pd.DataFrame
66
+ Input data with numeric and/or categorical columns.
67
+
68
+ Returns
69
+ -------
70
+ pd.DataFrame
71
+ Symmetric association matrix combining all column types.
72
+ """
73
+ num_cols = numeric_columns(dataframe)
74
+ cat_cols = categorical_columns(dataframe)
75
+ all_cols = num_cols + cat_cols
76
+ n = len(all_cols)
77
+ matrix = np.eye(n)
78
+ col_index = {col: i for i, col in enumerate(all_cols)}
79
+
80
+ # Numeric-numeric block: assign via numpy fancy indexing
81
+ if len(num_cols) > 1:
82
+ num_matrix = self._numeric.compute(dataframe[num_cols])
83
+ num_idx = np.array([col_index[c] for c in num_cols])
84
+ matrix[np.ix_(num_idx, num_idx)] = num_matrix.abs().values
85
+
86
+ # Categorical-categorical block: assign via numpy fancy indexing
87
+ if len(cat_cols) > 1:
88
+ cat_matrix = self._categorical.compute(dataframe[cat_cols])
89
+ cat_idx = np.array([col_index[c] for c in cat_cols])
90
+ matrix[np.ix_(cat_idx, cat_idx)] = cat_matrix.values
91
+
92
+ # Mixed block (cat-num): assign via numpy fancy indexing
93
+ if num_cols and cat_cols:
94
+ mixed_matrix = self._mixed.compute(dataframe[num_cols + cat_cols])
95
+ cat_idx = np.array([col_index[c] for c in cat_cols])
96
+ num_idx = np.array([col_index[c] for c in num_cols])
97
+ sub = mixed_matrix.loc[cat_cols, num_cols].values
98
+ matrix[np.ix_(cat_idx, num_idx)] = sub
99
+ matrix[np.ix_(num_idx, cat_idx)] = sub.T
100
+
101
+ # Restore diagonal after block assignments
102
+ np.fill_diagonal(matrix, 1.0)
103
+
104
+ return pd.DataFrame(data=matrix, index=all_cols, columns=all_cols)
@@ -0,0 +1,25 @@
1
+ """Base protocol for association strategies."""
2
+
3
+ from typing import Protocol, runtime_checkable
4
+
5
+ import pandas as pd
6
+
7
+
8
+ @runtime_checkable
9
+ class AssociationStrategy(Protocol):
10
+ """Protocol that all association strategies must implement."""
11
+
12
+ def compute(self, dataframe: pd.DataFrame) -> pd.DataFrame:
13
+ """Compute a symmetric association matrix from a DataFrame.
14
+
15
+ Parameters
16
+ ----------
17
+ dataframe : pd.DataFrame
18
+ Input data with columns to compute associations for.
19
+
20
+ Returns
21
+ -------
22
+ pd.DataFrame
23
+ Symmetric matrix of association values.
24
+ """
25
+ ...
@@ -0,0 +1,174 @@
1
+ """Association strategies for categorical and mixed-type pairs."""
2
+
3
+ from itertools import combinations
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from scipy import stats
8
+
9
+ from correlation_network._validation import complete_pair
10
+ from correlation_network.association._dtypes import (
11
+ categorical_columns,
12
+ numeric_columns,
13
+ )
14
+ from correlation_network.exceptions import InsufficientColumnsError
15
+
16
+
17
+ class CramersVStrategy:
18
+ """Cramer's V association measure for categorical-categorical pairs.
19
+
20
+ Computes the bias-corrected Cramer's V for all pairs of categorical
21
+ columns, returning values in [0, 1].
22
+ """
23
+
24
+ def compute(self, dataframe: pd.DataFrame) -> pd.DataFrame:
25
+ """Compute Cramer's V matrix for categorical columns.
26
+
27
+ Parameters
28
+ ----------
29
+ dataframe : pd.DataFrame
30
+ Input data. Only object, category, bool and string columns are
31
+ used.
32
+
33
+ Returns
34
+ -------
35
+ pd.DataFrame
36
+ Symmetric matrix of Cramer's V values in [0, 1].
37
+
38
+ Raises
39
+ ------
40
+ InsufficientColumnsError
41
+ If fewer than 2 categorical columns are present.
42
+ NoCompleteObservationsError
43
+ If a pair has no rows where both columns are present.
44
+ """
45
+ categorical_cols = categorical_columns(dataframe)
46
+ if len(categorical_cols) < 2:
47
+ raise InsufficientColumnsError(
48
+ method="cramers_v",
49
+ required="at least 2 categorical columns",
50
+ found=categorical_cols,
51
+ )
52
+ n_cols = len(categorical_cols)
53
+ matrix = np.eye(n_cols)
54
+
55
+ for i, j in combinations(range(n_cols), 2):
56
+ value = self._cramers_v(
57
+ x=dataframe[categorical_cols[i]],
58
+ y=dataframe[categorical_cols[j]],
59
+ )
60
+ matrix[i, j] = value
61
+ matrix[j, i] = value
62
+
63
+ return pd.DataFrame(data=matrix, index=categorical_cols, columns=categorical_cols)
64
+
65
+ @staticmethod
66
+ def _cramers_v(x: pd.Series, y: pd.Series) -> float:
67
+ """Compute bias-corrected Cramer's V between two categorical series."""
68
+ x, y = complete_pair(x, y)
69
+ contingency = pd.crosstab(index=x, columns=y)
70
+ # Yates' continuity correction (scipy's default on 2x2 tables) shrinks
71
+ # chi2 and would bias V downward on top of the explicit correction below.
72
+ chi2 = stats.chi2_contingency(observed=contingency, correction=False)[0]
73
+ n = contingency.values.sum()
74
+ r, k = contingency.shape
75
+
76
+ # Bias correction
77
+ phi2 = chi2 / n
78
+ phi2_corrected = max(0.0, phi2 - ((k - 1) * (r - 1)) / (n - 1))
79
+ r_corrected = r - ((r - 1) ** 2) / (n - 1)
80
+ k_corrected = k - ((k - 1) ** 2) / (n - 1)
81
+ denominator = min(k_corrected - 1, r_corrected - 1)
82
+
83
+ if denominator <= 0:
84
+ return 0.0
85
+ return np.sqrt(phi2_corrected / denominator)
86
+
87
+
88
+ class CorrelationRatioStrategy:
89
+ """Correlation ratio (eta) for categorical-numeric pairs.
90
+
91
+ For each pair of (categorical, numeric) columns, computes the
92
+ correlation ratio in [0, 1].
93
+ """
94
+
95
+ def compute(self, dataframe: pd.DataFrame) -> pd.DataFrame:
96
+ """Compute correlation ratio matrix for mixed-type columns.
97
+
98
+ Parameters
99
+ ----------
100
+ dataframe : pd.DataFrame
101
+ Input data with both categorical and numeric columns.
102
+
103
+ Each pair is evaluated on the rows where both columns are present,
104
+ matching the pairwise-complete convention the other strategies use.
105
+
106
+ Returns
107
+ -------
108
+ pd.DataFrame
109
+ Symmetric matrix of correlation ratio values in [0, 1].
110
+
111
+ Raises
112
+ ------
113
+ InsufficientColumnsError
114
+ If either no categorical or no numeric column is present.
115
+ NoCompleteObservationsError
116
+ If a pair has no rows where both columns are present.
117
+ """
118
+ cat_cols = categorical_columns(dataframe)
119
+ num_cols = numeric_columns(dataframe)
120
+ if not cat_cols or not num_cols:
121
+ raise InsufficientColumnsError(
122
+ method="correlation_ratio",
123
+ required="at least 1 categorical and 1 numeric column",
124
+ found=cat_cols + num_cols,
125
+ )
126
+ all_cols = cat_cols + num_cols
127
+ n = len(all_cols)
128
+ matrix = np.zeros((n, n))
129
+ col_index = {col: i for i, col in enumerate(all_cols)}
130
+
131
+ # Per-column stats cannot be pre-computed: each pair is restricted to
132
+ # its own complete rows, so the grand mean and total sum of squares
133
+ # depend on which categorical column it is paired with.
134
+ for cat_col in cat_cols:
135
+ for num_col in num_cols:
136
+ value = self._correlation_ratio(
137
+ categories=dataframe[cat_col],
138
+ values=dataframe[num_col],
139
+ )
140
+ ci = col_index[cat_col]
141
+ ni = col_index[num_col]
142
+ matrix[ci, ni] = value
143
+ matrix[ni, ci] = value
144
+
145
+ np.fill_diagonal(matrix, 1.0)
146
+
147
+ return pd.DataFrame(data=matrix, index=all_cols, columns=all_cols)
148
+
149
+ @staticmethod
150
+ def _correlation_ratio(categories: pd.Series, values: pd.Series) -> float:
151
+ """Compute correlation ratio (eta) for a categorical-numeric pair.
152
+
153
+ Restricted to rows where both columns are present. Computing the total
154
+ sum of squares over every row while the between-group sum only covers
155
+ rows with a known category would divide two quantities measured on
156
+ different populations, understating the association.
157
+
158
+ Raises
159
+ ------
160
+ NoCompleteObservationsError
161
+ If the two columns share no rows where both values are present.
162
+ """
163
+ categories, values = complete_pair(categories, values)
164
+
165
+ grand_mean = values.mean()
166
+ ss_total = ((values - grand_mean) ** 2).sum()
167
+ # A constant column has no variance for the categories to explain.
168
+ # That is uninformative rather than undefined, so report no association.
169
+ if ss_total == 0:
170
+ return 0.0
171
+
172
+ group_stats = values.groupby(categories, observed=True).agg(["mean", "count"])
173
+ ss_between = (group_stats["count"] * (group_stats["mean"] - grand_mean) ** 2).sum()
174
+ return np.sqrt(ss_between / ss_total)
@@ -0,0 +1,65 @@
1
+ """Correlation-based association strategies for numeric-numeric pairs."""
2
+
3
+ import pandas as pd
4
+
5
+ from correlation_network._types import CorrelationMethod
6
+ from correlation_network._validation import check_pairwise_overlap
7
+ from correlation_network.association._dtypes import numeric_columns
8
+ from correlation_network.exceptions import InsufficientColumnsError
9
+
10
+
11
+ class _CorrelationStrategy:
12
+ """Base class for pandas correlation methods."""
13
+
14
+ _method: CorrelationMethod
15
+
16
+ def compute(self, dataframe: pd.DataFrame) -> pd.DataFrame:
17
+ """Compute correlation matrix using pandas.
18
+
19
+ Parameters
20
+ ----------
21
+ dataframe : pd.DataFrame
22
+ Input data with numeric columns.
23
+
24
+ Returns
25
+ -------
26
+ pd.DataFrame
27
+ Symmetric correlation matrix with values in [-1, 1].
28
+
29
+ Raises
30
+ ------
31
+ InsufficientColumnsError
32
+ If fewer than 2 numeric columns are present.
33
+ NoCompleteObservationsError
34
+ If a pair has no rows where both columns are present.
35
+ """
36
+ numeric_cols = numeric_columns(dataframe)
37
+ if len(numeric_cols) < 2:
38
+ raise InsufficientColumnsError(
39
+ method=self._method,
40
+ required="at least 2 numeric columns",
41
+ found=numeric_cols,
42
+ )
43
+ matrix = dataframe.corr(method=self._method, numeric_only=True)
44
+ # numeric_only also keeps bool columns, so check the columns pandas
45
+ # actually used rather than numeric_cols
46
+ check_pairwise_overlap(dataframe[matrix.columns])
47
+ return matrix
48
+
49
+
50
+ class PearsonStrategy(_CorrelationStrategy):
51
+ """Pearson linear correlation coefficient."""
52
+
53
+ _method: CorrelationMethod = "pearson"
54
+
55
+
56
+ class SpearmanStrategy(_CorrelationStrategy):
57
+ """Spearman rank correlation coefficient."""
58
+
59
+ _method: CorrelationMethod = "spearman"
60
+
61
+
62
+ class KendallStrategy(_CorrelationStrategy):
63
+ """Kendall tau rank correlation coefficient."""
64
+
65
+ _method: CorrelationMethod = "kendall"