turtles-glms 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.
- turtles/__init__.py +17 -0
- turtles/_utils.py +110 -0
- turtles/plotting/__init__.py +14 -0
- turtles/plotting/_plotting.py +61 -0
- turtles/preprocess/__init__.py +17 -0
- turtles/preprocess/_transform.py +81 -0
- turtles/stats/__init__.py +23 -0
- turtles/stats/_stats.py +276 -0
- turtles/stats/glms/__init__.py +14 -0
- turtles/stats/glms/_base.py +733 -0
- turtles/stats/glms/_logreg.py +199 -0
- turtles/stats/glms/_mlr.py +550 -0
- turtles/stats/glms/_poisson.py +274 -0
- turtles_glms-1.0.0.dist-info/METADATA +103 -0
- turtles_glms-1.0.0.dist-info/RECORD +18 -0
- turtles_glms-1.0.0.dist-info/WHEEL +5 -0
- turtles_glms-1.0.0.dist-info/licenses/LICENSE +21 -0
- turtles_glms-1.0.0.dist-info/top_level.txt +1 -0
turtles/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure global settings for package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = version("turtles-glms")
|
|
9
|
+
except PackageNotFoundError:
|
|
10
|
+
__version__ = "0.0.0"
|
|
11
|
+
|
|
12
|
+
# modules
|
|
13
|
+
__all__ = [
|
|
14
|
+
"preprocess",
|
|
15
|
+
"stats",
|
|
16
|
+
"plotting"
|
|
17
|
+
]
|
turtles/_utils.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""
|
|
2
|
+
General utility functions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import (
|
|
6
|
+
Dict,
|
|
7
|
+
Tuple,
|
|
8
|
+
Any,
|
|
9
|
+
Union
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _shape_check(x: np.ndarray, var_name: str, dim: int = 1):
|
|
16
|
+
"""
|
|
17
|
+
Check if numpy array has at least `dim` dimensions.
|
|
18
|
+
|
|
19
|
+
x : np.ndarray
|
|
20
|
+
Numpy matrix.
|
|
21
|
+
var_name : str
|
|
22
|
+
The variable name to return in the error message.
|
|
23
|
+
dim : int, default 2
|
|
24
|
+
The min number of dimensions in the array.
|
|
25
|
+
|
|
26
|
+
Raises
|
|
27
|
+
------
|
|
28
|
+
ValueError
|
|
29
|
+
If the array has less than `dim` dimensions.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
None
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
if np.ndim(x) == 1 or x.shape[1] < dim:
|
|
37
|
+
raise ValueError(
|
|
38
|
+
f"'{var_name}' must contain more than {dim} dimensions."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _validate_args(
|
|
43
|
+
map: Dict[str, Tuple[Any, Union[type, Tuple[type, ...]]]]
|
|
44
|
+
):
|
|
45
|
+
"""
|
|
46
|
+
Simple type validation of a set of input arguments.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
map : Dict[str, Tuple[Any, Union[type, Tuple[type, ...]]]]
|
|
51
|
+
A dictionary where keys are the argument names and the values are
|
|
52
|
+
tuples containing:
|
|
53
|
+
- the object to check
|
|
54
|
+
- the expected type or a tuple of types
|
|
55
|
+
|
|
56
|
+
Raises
|
|
57
|
+
------
|
|
58
|
+
TypeError
|
|
59
|
+
If the type of an argument does not match the expected type.
|
|
60
|
+
|
|
61
|
+
Example
|
|
62
|
+
-------
|
|
63
|
+
_validate_args(
|
|
64
|
+
{
|
|
65
|
+
'X': (X, np.array),
|
|
66
|
+
'Y': (Y, (int, float))
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
"""
|
|
70
|
+
for key, (obj, expected_type) in map.items():
|
|
71
|
+
if isinstance(expected_type, tuple):
|
|
72
|
+
if not any(isinstance(obj, t) for t in expected_type):
|
|
73
|
+
raise TypeError(
|
|
74
|
+
f"Parameter '{key}' must be one of the types {expected_type}; "
|
|
75
|
+
f"received {type(obj)}"
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
if not isinstance(obj, expected_type):
|
|
79
|
+
raise TypeError(
|
|
80
|
+
f"Parameter '{key}' must be of type {expected_type}; "
|
|
81
|
+
f"received {type(obj)}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _add_intercept(X: np.ndarray) -> np.ndarray:
|
|
86
|
+
"""
|
|
87
|
+
Add intercept to the design matrix. The intercept is simply
|
|
88
|
+
a column of ones.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
X : np.ndarray, shape (M, N)
|
|
93
|
+
Design matrix with M rows (samples) and N columns (features).
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
np.ndarray, shape (M, N+1)
|
|
98
|
+
Design matrix with the intercept as the first column.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
_validate_args(
|
|
102
|
+
{
|
|
103
|
+
"X": (X, np.ndarray)
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
m = X.shape[0]
|
|
108
|
+
intercept = np.ones(m).reshape(m, 1)
|
|
109
|
+
X = np.concatenate((intercept, X), axis=1)
|
|
110
|
+
return X
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Plotting functions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
|
|
8
|
+
from .._utils import _validate_args
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def plot_y_vs_x(
|
|
12
|
+
x: np.ndarray,
|
|
13
|
+
y: np.ndarray,
|
|
14
|
+
title: str = "Dependent vs. Independent",
|
|
15
|
+
xlabel: str = "Independent",
|
|
16
|
+
ylabel: str = "Dependent"
|
|
17
|
+
):
|
|
18
|
+
"""
|
|
19
|
+
Scatter plot of a variable (y) against another variable (x).
|
|
20
|
+
|
|
21
|
+
Input arrays must be the same shape.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
x : np.ndarray
|
|
26
|
+
Input array for x-axis.
|
|
27
|
+
y : np.ndarray
|
|
28
|
+
Input aray for y-axis.
|
|
29
|
+
title : str
|
|
30
|
+
Plot title. Defaults to 'Dependent vs. Independent'.
|
|
31
|
+
xlabel : str
|
|
32
|
+
Plot label for x-axis. Defaults to 'Independent'.
|
|
33
|
+
ylabel : str
|
|
34
|
+
Plot label for y axis. Defaults to 'Dependent'.
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
None
|
|
39
|
+
Displays scatter plot.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
_validate_args(
|
|
43
|
+
{
|
|
44
|
+
"x": (x, np.ndarray),
|
|
45
|
+
"y": (y, np.ndarray),
|
|
46
|
+
"title": (title, str),
|
|
47
|
+
"xlabel": (xlabel, str),
|
|
48
|
+
"ylabel": (ylabel, str)
|
|
49
|
+
}
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
plt.scatter(
|
|
53
|
+
x=x,
|
|
54
|
+
y=y,
|
|
55
|
+
color="blue",
|
|
56
|
+
edgecolor="black"
|
|
57
|
+
)
|
|
58
|
+
plt.title(title)
|
|
59
|
+
plt.xlabel(xlabel)
|
|
60
|
+
plt.ylabel(ylabel)
|
|
61
|
+
plt.show()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Preprocess module.
|
|
3
|
+
|
|
4
|
+
Functions support common data transformation tasks to prep datasets
|
|
5
|
+
for GLMs.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from ._transform import (
|
|
9
|
+
one_hot_encode
|
|
10
|
+
)
|
|
11
|
+
from .._utils import _add_intercept as add_intercept
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"one_hot_encode",
|
|
16
|
+
"add_intercept"
|
|
17
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Functions to support preprocessing/transformations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import List, Tuple, Union
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
from .._utils import (
|
|
11
|
+
_validate_args,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def one_hot_encode(
|
|
16
|
+
df: pd.DataFrame,
|
|
17
|
+
columns: List[str],
|
|
18
|
+
drop_first: bool = True,
|
|
19
|
+
return_df: bool = True
|
|
20
|
+
) -> Union[pd.DataFrame, Tuple[np.ndarray, List[str]]]:
|
|
21
|
+
"""
|
|
22
|
+
One Hot Encode categorical variables in a Pandas DataFrame, also known
|
|
23
|
+
as dummy variables or indicator variables.
|
|
24
|
+
|
|
25
|
+
This is essentially just a wrapper around the Pandas `get_dummies()`
|
|
26
|
+
function, but it optioanlly returns a Numpy matrix, making the resulting
|
|
27
|
+
object immediately compatible with the `turtles` GLM classes.
|
|
28
|
+
|
|
29
|
+
https://pandas.pydata.org/docs/reference/api/pandas.get_dummies.html
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
df : pd.DataFrame, shape (M, N)
|
|
34
|
+
Pandas DataFrame containing data to be encoded.
|
|
35
|
+
columns : List[str]
|
|
36
|
+
List of column names in `df` to be encoded. Column values must be
|
|
37
|
+
strings or categorical.
|
|
38
|
+
drop_first : bool, default True
|
|
39
|
+
Whether to drop the first category from the resulting table. Dropping
|
|
40
|
+
the first category is useful when building a GLM, because the dropped
|
|
41
|
+
category will then be treated as the 'base case' or 'reference category'
|
|
42
|
+
for model coefficient interpretation.
|
|
43
|
+
return_df : bool, default True
|
|
44
|
+
Optionally return a pandas DataFrame. If False, a Tuple of a numpy
|
|
45
|
+
matrix and resulting column names is returned.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
Union[pd.DataFrame, Tuple[np.ndarray, List[str]]]
|
|
50
|
+
|
|
51
|
+
If `return_df` = True, a pandas DataFrame is returned, containing the
|
|
52
|
+
encoded columns.
|
|
53
|
+
|
|
54
|
+
If `return_df` = False, a Tuple is returned, where the new matrix is
|
|
55
|
+
in position [0] and the list of new column names in position [1].
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
_validate_args(
|
|
59
|
+
{
|
|
60
|
+
"df": (df, pd.DataFrame),
|
|
61
|
+
"columns": (columns, list),
|
|
62
|
+
"drop_first": (drop_first, bool),
|
|
63
|
+
"return_df": (return_df, bool)
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
temp = df.copy()
|
|
68
|
+
|
|
69
|
+
one_hot_encoded_df = pd.get_dummies(
|
|
70
|
+
temp,
|
|
71
|
+
columns=columns,
|
|
72
|
+
drop_first=drop_first,
|
|
73
|
+
dtype=int
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if return_df:
|
|
77
|
+
return one_hot_encoded_df
|
|
78
|
+
|
|
79
|
+
column_names = list(one_hot_encoded_df.columns)
|
|
80
|
+
|
|
81
|
+
return one_hot_encoded_df.to_numpy(), column_names
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Stats module.
|
|
3
|
+
|
|
4
|
+
These functions support various statistical tasks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ._stats import (
|
|
8
|
+
calculate_errors,
|
|
9
|
+
variance_inflation_factor,
|
|
10
|
+
pearson_corr,
|
|
11
|
+
covariance_matrix,
|
|
12
|
+
pca
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"glms", # submodule
|
|
18
|
+
"calculate_errors",
|
|
19
|
+
"variance_inflation_factor",
|
|
20
|
+
"pearson_corr",
|
|
21
|
+
"covariance_matrix",
|
|
22
|
+
"pca"
|
|
23
|
+
]
|
turtles/stats/_stats.py
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Various statistical functions.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Tuple, Dict, List
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
from scipy.stats import pearsonr
|
|
10
|
+
from scipy.sparse.linalg import eigs
|
|
11
|
+
|
|
12
|
+
from .._utils import (
|
|
13
|
+
_validate_args,
|
|
14
|
+
_add_intercept,
|
|
15
|
+
_shape_check
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def calculate_errors(
|
|
20
|
+
y_true: np.ndarray,
|
|
21
|
+
y_pred: np.ndarray,
|
|
22
|
+
m: Optional[int] = None
|
|
23
|
+
) -> Dict[str, float]:
|
|
24
|
+
"""
|
|
25
|
+
Calculate errors for arrays containing continuous values. Not suitable
|
|
26
|
+
for classification cases.
|
|
27
|
+
|
|
28
|
+
1. Sum of Squared Errors (or Residual Sum of Squares)
|
|
29
|
+
2. Mean Squared Error
|
|
30
|
+
3. Root Mean Squared Error
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
y_true : np.ndarray
|
|
35
|
+
True target array. Must be the same shape as y_pred and have 1 dimension
|
|
36
|
+
or be flattened.
|
|
37
|
+
y_pred : np.ndarray
|
|
38
|
+
Predicted values target array. Must be the same shape as y_true and have
|
|
39
|
+
1 dimension or be flattened.
|
|
40
|
+
m : int
|
|
41
|
+
Number of observations. If using this function to calculate model fit
|
|
42
|
+
statistics, 'm' should be the degrees of freedom.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
Dict[str, float]
|
|
47
|
+
Dictionary where {key:value} is {"error name": value}.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
m = m or max(y_true.shape)
|
|
51
|
+
|
|
52
|
+
_validate_args(
|
|
53
|
+
{
|
|
54
|
+
"y_true": (y_true, np.ndarray),
|
|
55
|
+
"y_pred": (y_pred, np.ndarray),
|
|
56
|
+
"m": (m, int)
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
errors = y_true - y_pred
|
|
61
|
+
sse = np.sum(errors ** 2).item()
|
|
62
|
+
mse = sse / m
|
|
63
|
+
rmse = np.sqrt(mse).item()
|
|
64
|
+
|
|
65
|
+
response = {
|
|
66
|
+
"Sum of Squared Errors": sse,
|
|
67
|
+
"Mean Squared Error": mse,
|
|
68
|
+
"Root Mean Squared Error": rmse
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return response
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def variance_inflation_factor(
|
|
75
|
+
X: np.ndarray,
|
|
76
|
+
var_names: Optional[List[str]] = None
|
|
77
|
+
) -> pd.DataFrame:
|
|
78
|
+
"""
|
|
79
|
+
Calculate Variance Inflation Factor (VIF) for all predictors in the
|
|
80
|
+
input matrix.
|
|
81
|
+
|
|
82
|
+
Fits one linear model for each dimension, where that single dimension
|
|
83
|
+
is the dependent variable, and all other dimensions (including an intercept)
|
|
84
|
+
are the independent variables.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
X : np.ndarray, shape (m, n)
|
|
89
|
+
Design matrix. Should not include an intercept.
|
|
90
|
+
var_names : Optional[List[str]]
|
|
91
|
+
Variable names. If not passed, defaults to `x_i` where `i` is the order
|
|
92
|
+
of the dimension in the input matrix. Names must appear in the order
|
|
93
|
+
that they do in the input matrix.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
pd.DataFrame
|
|
98
|
+
Pandas DataFrame containing the Coefficient (str), R-squared (float),
|
|
99
|
+
VIF (float).
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
_validate_args(
|
|
103
|
+
{
|
|
104
|
+
"X": (X, np.ndarray)
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
_shape_check(X, "X", 1)
|
|
108
|
+
|
|
109
|
+
vifs = {
|
|
110
|
+
"Coefficient": [],
|
|
111
|
+
"R-squared": [],
|
|
112
|
+
"VIF": []
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for idx in range(X.shape[1]):
|
|
116
|
+
# create design matrix -- all except one predictor
|
|
117
|
+
exog = np.delete(X, idx, axis=1)
|
|
118
|
+
# target -- the one predictor
|
|
119
|
+
endog = X[:, idx]
|
|
120
|
+
|
|
121
|
+
# add intercept to design matrix
|
|
122
|
+
exog = _add_intercept(exog)
|
|
123
|
+
|
|
124
|
+
# fit model -- one predictor vs all others
|
|
125
|
+
coefs = np.linalg.inv(exog.T @ exog) @ exog.T @ endog
|
|
126
|
+
|
|
127
|
+
# calculate residuals
|
|
128
|
+
fitted_vals = exog @ coefs.T
|
|
129
|
+
residuals = endog - fitted_vals
|
|
130
|
+
|
|
131
|
+
# Redisual Sum of Squares and Sum of Squares Total
|
|
132
|
+
rss = np.sum(residuals ** 2)
|
|
133
|
+
sst = np.sum((endog - np.mean(endog))**2)
|
|
134
|
+
|
|
135
|
+
# R-squared
|
|
136
|
+
r2 = 1 - rss / sst
|
|
137
|
+
vifs["R-squared"].append(r2.item())
|
|
138
|
+
|
|
139
|
+
# VIF
|
|
140
|
+
vif = 1 / (1 - r2)
|
|
141
|
+
vifs["VIF"].append(vif.item())
|
|
142
|
+
|
|
143
|
+
vifs["Coefficient"].append(f"x{idx}")
|
|
144
|
+
|
|
145
|
+
if var_names is not None:
|
|
146
|
+
vifs["Coefficient"] = var_names
|
|
147
|
+
|
|
148
|
+
return pd.DataFrame(vifs)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def pearson_corr(
|
|
152
|
+
x: np.ndarray,
|
|
153
|
+
y: np.ndarray,
|
|
154
|
+
) -> Tuple[float, float]:
|
|
155
|
+
"""
|
|
156
|
+
Calculate the Pearson Correlation Coefficient.
|
|
157
|
+
|
|
158
|
+
This is basically a wrapper around the SciPy function.
|
|
159
|
+
|
|
160
|
+
https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.pearsonr.html
|
|
161
|
+
|
|
162
|
+
Parameters
|
|
163
|
+
----------
|
|
164
|
+
x : np.ndarray
|
|
165
|
+
First input array. Usually a single independent variable.
|
|
166
|
+
y : np.ndarray
|
|
167
|
+
Second input array. A single independent or dependent variable.
|
|
168
|
+
|
|
169
|
+
Returns
|
|
170
|
+
-------
|
|
171
|
+
Tuple[float, float]
|
|
172
|
+
Tuple containing the correlation coefficient in position [0],
|
|
173
|
+
and the p-value in position [1].
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
_validate_args(
|
|
177
|
+
{
|
|
178
|
+
"x": (x, np.ndarray),
|
|
179
|
+
"y": (y, np.ndarray)
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
result = pearsonr(x=x.flatten(), y=y.flatten())
|
|
184
|
+
return (result.statistic.item(), result.pvalue.item())
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def covariance_matrix(X: np.ndarray, return_xc=False) -> np.ndarray:
|
|
188
|
+
"""
|
|
189
|
+
Calculate the covariance between variables in a matrix.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
X : np.ndarray
|
|
194
|
+
Design matrix of shape (M, N).
|
|
195
|
+
return_xc : bool, defaults to False
|
|
196
|
+
Optionally return the centered matrix.
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
C : np.ndarray
|
|
201
|
+
Covariance matrix of size (N, N).
|
|
202
|
+
X_c : np.ndarray
|
|
203
|
+
Centered input matrix.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
_validate_args(
|
|
207
|
+
{
|
|
208
|
+
"X": (X, np.ndarray),
|
|
209
|
+
"return_xc": (return_xc, bool)
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
_shape_check(X, "X", 2)
|
|
213
|
+
|
|
214
|
+
# estimate mean for each variable / column
|
|
215
|
+
m = X.shape[0]
|
|
216
|
+
X_t = X.T
|
|
217
|
+
avg = np.mean(X_t, axis=1)
|
|
218
|
+
|
|
219
|
+
# 'center' the matrix
|
|
220
|
+
X_c = X_t - avg[:, None]
|
|
221
|
+
|
|
222
|
+
# covariance matrix
|
|
223
|
+
C = (X_c @ X_c.T)/m
|
|
224
|
+
|
|
225
|
+
if return_xc:
|
|
226
|
+
return C, X_c
|
|
227
|
+
|
|
228
|
+
return C
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def pca(X: np.ndarray, p: int) -> np.ndarray:
|
|
232
|
+
"""
|
|
233
|
+
Principal Component Analysis.
|
|
234
|
+
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
X : np.ndarray, shape (M, N)
|
|
238
|
+
Input data (design matrix) for calculating pricipal components.
|
|
239
|
+
p : int
|
|
240
|
+
Number of principle components to return. Must be less than or
|
|
241
|
+
equal to the number of dimensions in X.
|
|
242
|
+
|
|
243
|
+
Raises
|
|
244
|
+
------
|
|
245
|
+
ValueError
|
|
246
|
+
If the requested number of principal components exceeds the number
|
|
247
|
+
of dimensions in the input matrix.
|
|
248
|
+
|
|
249
|
+
Returns
|
|
250
|
+
-------
|
|
251
|
+
np.ndarray, shape (M, p)
|
|
252
|
+
Matrix with p dimensions.
|
|
253
|
+
"""
|
|
254
|
+
|
|
255
|
+
_validate_args(
|
|
256
|
+
{
|
|
257
|
+
"X": (X, np.ndarray),
|
|
258
|
+
"p": (p, int)
|
|
259
|
+
}
|
|
260
|
+
)
|
|
261
|
+
_shape_check(X, "X", 2)
|
|
262
|
+
|
|
263
|
+
if p > X.shape[1]:
|
|
264
|
+
raise ValueError("'p' must be <= 'X' dimensions.")
|
|
265
|
+
|
|
266
|
+
# get covariance matrix
|
|
267
|
+
C, X_c = covariance_matrix(X, return_xc=True)
|
|
268
|
+
|
|
269
|
+
# calculate eigenvalues, eigenvectors
|
|
270
|
+
s, w = eigs(C, p)
|
|
271
|
+
s = s.real
|
|
272
|
+
s = np.reshape(s, (p, 1))
|
|
273
|
+
w = w.real
|
|
274
|
+
pca = w.T @ X_c
|
|
275
|
+
|
|
276
|
+
return pca.T
|