Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the McrAR API more sklearn-like #32

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 54 additions & 42 deletions pymcr/mcr.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,22 @@ class McrAR:
exit_tol_n_above_min : bool
Exited iterations due to maximum number of half-iterations for which
the error metric increased above the minimum error
C : ndarray
Initial C matrix estimate. Only provide initial C OR S^T.

ST : ndarray
Initial S^T matrix estimate. Only provide initial C OR S^T.

st_fix : list
The spectral component numbers to keep fixed.

c_fix : list
The concentration component numbers to keep fixed.

c_first : bool
Calculate C first when both C and ST are provided. c_fix and st_fix
must also be provided in this circumstance.


Notes
-----
Expand All @@ -136,11 +152,31 @@ def __init__(self, c_regr=OLS(), st_regr=OLS(), c_fit_kwargs={},
st_constraints=[ConstraintNonneg()],
max_iter=50, err_fcn=mse,
tol_increase=0.0, tol_n_increase=10, tol_err_change=None,
tol_n_above_min=10
tol_n_above_min=10, C=None, ST=None, c_fix=None, st_fix=None, c_first=True,
):
"""
Multivariate Curve Resolution - Alternating Regression
"""
# Ensure only C or ST provided
if (C is None) & (ST is None):
raise TypeError('C or ST estimate must be provided')
elif (C is not None) & (ST is not None) & ((c_fix is None) |
(st_fix is None)):
err_str1 = 'Only C or ST estimate must be provided, '
raise TypeError(
err_str1 + 'unless c_fix and st_fix are both provided')
else:
if C is not None:
C = _np.asanyarray(C)
C = C.reshape([_np.prod([C.shape[:-1]]), -1])
if ST is not None:
ST = _np.asanyarray(ST)
ST = ST.reshape([-1, _np.prod([ST.shape[1:]])])
self.C_ = C
self.ST_ = ST
self.c_fix = c_fix
self.st_fix = st_fix
self.c_first = c_first

self.max_iter = max_iter

Expand All @@ -160,9 +196,6 @@ def __init__(self, c_regr=OLS(), st_regr=OLS(), c_fit_kwargs={},
self.c_fit_kwargs = c_fit_kwargs
self.st_fit_kwargs = st_fit_kwargs

self.C_ = None
self.ST_ = None

self.C_opt_ = None
self.ST_opt_ = None
self.n_iter_opt = None
Expand Down Expand Up @@ -203,6 +236,13 @@ def _check_regr(self, mth):
raise ValueError('Input class '
'{} does not have a \'fit\' method'.format(mth))

def transform(self, data, **kwargs):
return self.C_opt_

@property
def components_(self):
return self.ST_opt_

@property
def D_(self):
""" D matrix with current C and S^T matrices """
Expand Down Expand Up @@ -244,8 +284,7 @@ def _ismin_err(self, val):
else:
return ([val > x for x in self.err].count(True) == 0)

def fit(self, D, C=None, ST=None, st_fix=None, c_fix=None, c_first=True,
verbose=False, post_iter_fcn=None, post_half_fcn=None):
def fit(self, D, verbose=False, post_iter_fcn=None, post_half_fcn=None):
"""
Perform MCR-AR. D = CS^T. Solve for C and S^T iteratively.

Expand All @@ -254,22 +293,6 @@ def fit(self, D, C=None, ST=None, st_fix=None, c_fix=None, c_first=True,
D : ndarray
D matrix

C : ndarray
Initial C matrix estimate. Only provide initial C OR S^T.

ST : ndarray
Initial S^T matrix estimate. Only provide initial C OR S^T.

st_fix : list
The spectral component numbers to keep fixed.

c_fix : list
The concentration component numbers to keep fixed.

c_first : bool
Calculate C first when both C and ST are provided. c_fix and st_fix
must also be provided in this circumstance.

verbose : bool
Log iteration and per-least squares err results. See Notes.

Expand All @@ -294,17 +317,6 @@ def fit(self, D, C=None, ST=None, st_fix=None, c_fix=None, c_first=True,
else:
_logger.setLevel(_logging.INFO)

# Ensure only C or ST provided
if (C is None) & (ST is None):
raise TypeError('C or ST estimate must be provided')
elif (C is not None) & (ST is not None) & ((c_fix is None) |
(st_fix is None)):
err_str1 = 'Only C or ST estimate must be provided, '
raise TypeError(
err_str1 + 'unless c_fix and st_fix are both provided')
else:
self.C_ = C
self.ST_ = ST

self.n_increase = 0
self.n_above_min = 0
Expand All @@ -317,7 +329,7 @@ def fit(self, D, C=None, ST=None, st_fix=None, c_fix=None, c_first=True,
self.n_iter = num + 1

# Both st and c provided, but c_first is False
if both_condition & (num == 0) & (not c_first):
if both_condition & (num == 0) & (not self.c_first):
special_skip_c = True
else:
special_skip_c = False
Expand All @@ -333,16 +345,16 @@ def fit(self, D, C=None, ST=None, st_fix=None, c_fix=None, c_first=True,
C_temp = self.c_regressor.coef_

# Apply fixed C's
if c_fix:
C_temp[:, c_fix] = self.C_[:, c_fix]
if self.c_fix:
C_temp[:, self.c_fix] = self.C_[:, self.c_fix]

# Apply c-constraints
for constr in self.c_constraints:
C_temp = constr.transform(C_temp)

# Apply fixed C's
if c_fix:
C_temp[:, c_fix] = self.C_[:, c_fix]
if self.c_fix:
C_temp[:, self.c_fix] = self.C_[:, self.c_fix]

D_calc = _np.dot(C_temp, self.ST_)

Expand Down Expand Up @@ -419,16 +431,16 @@ def fit(self, D, C=None, ST=None, st_fix=None, c_fix=None, c_first=True,
ST_temp = self.st_regressor.coef_.T

# Apply fixed ST's
if st_fix:
ST_temp[st_fix] = self.ST_[st_fix]
if self.st_fix:
ST_temp[self.st_fix] = self.ST_[self.st_fix]

# Apply ST-constraints
for constr in self.st_constraints:
ST_temp = constr.transform(ST_temp)

# Apply fixed ST's
if st_fix:
ST_temp[st_fix] = self.ST_[st_fix]
if self.st_fix:
ST_temp[self.st_fix] = self.ST_[self.st_fix]

D_calc = _np.dot(self.C_, ST_temp)

Expand Down