eem-python 1.2__py3-none-any.whl → 1.3__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.
@@ -1,10 +1,10 @@
1
1
  """
2
2
  Functions for EEM preprocessing and post-processing
3
3
  Author: Yongmin Hu (yongmin.hu@eawag.ch, yongminhu@outlook.com)
4
- Last update: 2024-02-13
4
+ Last update: 2024-07-03
5
5
  """
6
+ from matplotlib import pyplot as plt
6
7
 
7
- from eempy.read_data import *
8
8
  from eempy.utils import *
9
9
  import scipy.stats as stats
10
10
  import random
@@ -13,10 +13,12 @@ import numpy as np
13
13
  import itertools
14
14
  import string
15
15
  import warnings
16
+ import json
16
17
  from math import sqrt
17
18
  from sklearn.metrics import mean_squared_error, explained_variance_score, r2_score
18
19
  # from sklearn.ensemble import IsolationForest
19
20
  # from sklearn import svm
21
+ from sklearn.decomposition import PCA, NMF
20
22
  from sklearn.linear_model import LinearRegression
21
23
  from scipy.ndimage import gaussian_filter
22
24
  from scipy.interpolate import RegularGridInterpolator, interp1d, griddata
@@ -54,12 +56,12 @@ def process_eem_stack(eem_stack, f, **kwargs):
54
56
  The processed EEM stack.
55
57
  other_outputs: tuple, optional
56
58
  If the EEM processing function have more than 1 returns, the rest of the returns will be stored in a tuple,
57
- where each element is the return of EEM processing function applied on one EEM.
59
+ where each element is the return of EEM processing function applied to every EEM.
58
60
  """
59
61
  processed_eem_stack = []
60
62
  other_outputs = []
61
63
 
62
- #------Absorbance and blank are two parameters that are potentially sample-specific--------
64
+ # ------Absorbance and blank are two parameters that are potentially sample-specific--------
63
65
  if "absorbance" in kwargs:
64
66
  abs = kwargs.pop('absorbance')
65
67
  abs_passed = True
@@ -192,7 +194,7 @@ def eem_gaussian_filter(intensity, sigma=1, truncate=3):
192
194
  return intensity_filtered
193
195
 
194
196
 
195
- def eem_cutting(intensity, ex_range, em_range, em_min, em_max, ex_min, ex_max):
197
+ def eem_cutting(intensity, ex_range_old, em_range_old, ex_min_new, ex_max_new, em_min_new, em_max_new):
196
198
  """
197
199
  To cut the EEM.
198
200
 
@@ -200,17 +202,17 @@ def eem_cutting(intensity, ex_range, em_range, em_min, em_max, ex_min, ex_max):
200
202
  ----------
201
203
  intensity: np.ndarray (2d)
202
204
  The EEM.
203
- ex_range: np.ndarray (1d)
205
+ ex_range_old: np.ndarray (1d)
204
206
  The excitation wavelengths.
205
- em_range: np.ndarray (1d)
207
+ em_range_old: np.ndarray (1d)
206
208
  The emission wavelengths.
207
- ex_min: float
209
+ ex_min_new: float
208
210
  The lower boundary of excitation wavelength of the EEM after cutting.
209
- ex_max: float
211
+ ex_max_new: float
210
212
  The upper boundary of excitation wavelength of the EEM after cutting.
211
- em_min: float
213
+ em_min_new: float
212
214
  The lower boundary of emission wavelength of the EEM after cutting.
213
- em_max: float
215
+ em_max_new: float
214
216
  The upper boundary of emission wavelength of the EEM after cutting.
215
217
 
216
218
  Returns
@@ -222,14 +224,14 @@ def eem_cutting(intensity, ex_range, em_range, em_min, em_max, ex_min, ex_max):
222
224
  em_range_cut:np.ndarray
223
225
  The cut em wavelengths.
224
226
  """
225
- em_min_idx = dichotomy_search(em_range, em_min)
226
- em_max_idx = dichotomy_search(em_range, em_max)
227
- ex_min_idx = dichotomy_search(ex_range, ex_min)
228
- ex_max_idx = dichotomy_search(ex_range, ex_max)
229
- intensity_cut = intensity[ex_range.shape[0] - ex_max_idx - 1:ex_range.shape[0] - ex_min_idx,
230
- em_min_idx:em_max_idx + 1]
231
- em_range_cut = em_range[em_min_idx:em_max_idx + 1]
232
- ex_range_cut = ex_range[ex_min_idx:ex_max_idx + 1]
227
+ em_min_idx = dichotomy_search(em_range_old, em_min_new)
228
+ em_max_idx = dichotomy_search(em_range_old, em_max_new)
229
+ ex_min_idx = dichotomy_search(ex_range_old, ex_min_new)
230
+ ex_max_idx = dichotomy_search(ex_range_old, ex_max_new)
231
+ intensity_cut = intensity[ex_range_old.shape[0] - ex_max_idx - 1:ex_range_old.shape[0] - ex_min_idx,
232
+ em_min_idx:em_max_idx + 1]
233
+ em_range_cut = em_range_old[em_min_idx:em_max_idx + 1]
234
+ ex_range_cut = ex_range_old[ex_min_idx:ex_max_idx + 1]
233
235
  return intensity_cut, ex_range_cut, em_range_cut
234
236
 
235
237
 
@@ -259,7 +261,6 @@ def eem_nan_imputing(intensity, ex_range, em_range, method: str = 'linear', fill
259
261
  xx = x[~np.isnan(intensity)].flatten()
260
262
  yy = y[~np.isnan(intensity)].flatten()
261
263
  zz = intensity[~np.isnan(intensity)].flatten()
262
- intensity_imputed = None
263
264
  if isinstance(fill_value, float):
264
265
  intensity_imputed = griddata((xx, yy), zz, (x, y), method=method, fill_value=fill_value)
265
266
  elif fill_value == 'linear_ex':
@@ -285,8 +286,8 @@ def eem_nan_imputing(intensity, ex_range, em_range, method: str = 'linear', fill
285
286
  return intensity_imputed
286
287
 
287
288
 
288
- def eem_raman_normalization(intensity, blank=None, ex_range_blank=None, em_range_blank=None, from_blank=False,
289
- integration_time=1, ex_lb=349, ex_ub=351, bandwidth=1800, bandwidth_type='wavenumber',
289
+ def eem_raman_normalization(intensity, blank=None, ex_range_blank=None, em_range_blank=None, from_blank=True,
290
+ integration_time=1, ex_target=350, bandwidth=5,
290
291
  rsu_standard=20000, manual_rsu: Optional[float] = 1):
291
292
  """
292
293
  Normalize the EEM using the Raman scattering unit (RSU) given directly or calculated from a blank EEM.
@@ -307,14 +308,10 @@ def eem_raman_normalization(intensity, blank=None, ex_range_blank=None, em_range
307
308
  Whether to calculate the RSU from a blank. If False, manual_rsu will be used.
308
309
  integration_time: float
309
310
  The integration time of the blank measurement.
310
- ex_lb: float
311
- The lower boundary of excitation wavelength range within which the RSU is calculated.
312
- ex_ub: float
313
- The upper boundary of excitation wavelength range within which the RSU is calculated.
311
+ ex_target: float
312
+ The excitation wavelength at which the RSU is calculated.
314
313
  bandwidth: float
315
314
  The bandwidth of Raman scattering peak.
316
- bandwidth_type: str, {"wavenumber", "wavelength"}
317
- The type of bandwidth. "wavenumber": (1/nm); "wavelength": (nm).
318
315
  rsu_standard: float
319
316
  A factor used to divide the raw RSU. This is used to control the magnitude of RSU so that the normalized
320
317
  intensity of EEM would not be numerically too high or too low.
@@ -331,28 +328,24 @@ def eem_raman_normalization(intensity, blank=None, ex_range_blank=None, em_range
331
328
  if not from_blank:
332
329
  return intensity / manual_rsu, manual_rsu
333
330
  else:
334
- ex_range_cut = ex_range_blank[(ex_range_blank >= ex_lb) & (ex_range_blank <= ex_ub)]
335
- rsu_tot = 0
336
- for ex in ex_range_cut.tolist():
337
- if bandwidth_type == 'wavenumber':
338
- em_target = -ex / (0.00036 * ex - 1)
339
- wn_target = 10000000 / em_target
340
- em_lb = 10000000 / (wn_target + bandwidth)
341
- em_rb = 10000000 / (wn_target - bandwidth)
342
- rsu, _ = eem_regional_integration(blank, ex_range_blank, em_range_blank,
343
- ex_min=ex, ex_max=ex, em_min=em_lb, em_max=em_rb)
344
- elif bandwidth_type == 'wavelength':
345
- rsu, _ = eem_regional_integration(blank, ex_range_blank, em_range_blank,
346
- ex_min=ex, ex_max=ex, em_min=ex - bandwidth, em_max=ex + bandwidth)
347
- else:
348
- raise ValueError("'bandwidth_type' should be either 'wavenumber' or 'wavelength'.")
349
- rsu_tot += rsu
350
- rsu_final = rsu_tot / (integration_time * rsu_standard)
331
+ em_target = -ex_target / (0.00036 * ex_target - 1)
332
+ rsu, _ = eem_regional_integration(blank, ex_range_blank, em_range_blank,
333
+ ex_min=ex_target, ex_max=ex_target, em_min=em_target - bandwidth / 2,
334
+ em_max=em_target + bandwidth / 2)
335
+ # elif bandwidth_type == 'wavenumber':
336
+ # em_target = -ex / (0.00036 * ex - 1)
337
+ # wn_target = 10000000 / em_target
338
+ # em_lb = 10000000 / (wn_target + bandwidth)
339
+ # em_rb = 10000000 / (wn_target - bandwidth)
340
+ # rsu, _ = eem_regional_integration(blank, ex_range_blank, em_range_blank,
341
+ # ex_min=ex, ex_max=ex, em_min=em_lb, em_max=em_rb)
342
+ rsu_final = rsu / (integration_time * rsu_standard)
351
343
  intensity_normalized = intensity / rsu_final
352
344
  return intensity_normalized, rsu_final
353
345
 
354
346
 
355
- def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_method='linear', interpolation_axis='grid'):
347
+ def eem_raman_scattering_removal(intensity, ex_range, em_range, width=5, interpolation_method='linear',
348
+ interpolation_dimension='2d'):
356
349
  """
357
350
  Remove and interpolate the Raman scattering.
358
351
 
@@ -368,9 +361,9 @@ def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_meth
368
361
  The width of Raman scattering.
369
362
  interpolation_method: str, {"linear", "cubic", "nan"}
370
363
  The method used to interpolate the Raman scattering.
371
- interpolation_axis: str, {"ex", "em", "grid"}
372
- The axis along which the Raman scattering is interpolated. "ex": interpolation is conducted along the excitation
373
- wavelength; "em": interpolation is conducted along the emission wavelength; "grid": interpolation is conducted
364
+ interpolation_dimension: str, {"1d-ex", "1d-em", "2d"}
365
+ The axis along which the Raman scattering is interpolated. "1d-ex": interpolation is conducted along the excitation
366
+ wavelength; "1d-em": interpolation is conducted along the emission wavelength; "2d": interpolation is conducted
374
367
  on the 2D grid of both excitation and emission wavelengths.
375
368
 
376
369
  Returns
@@ -381,6 +374,7 @@ def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_meth
381
374
  Indicate the pixels that are interpolated. 0: pixel is interpolated; 1: pixel is not interpolated.
382
375
  """
383
376
  intensity_masked = np.array(intensity)
377
+ width = width / 2
384
378
  raman_mask = np.ones(intensity.shape)
385
379
  lambda_em = -ex_range / (0.00036 * ex_range - 1)
386
380
  tol_emidx = int(np.round(width / (em_range[1] - em_range[0])))
@@ -389,17 +383,17 @@ def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_meth
389
383
  if lambda_em[s] <= em_range[0] <= lambda_em[s] + width:
390
384
  emidx = dichotomy_search(em_range, lambda_em[s] + width)
391
385
  raman_mask[exidx, 0: emidx + 1] = 0
392
- elif lambda_em[s] - width <= em_range[0]:
386
+ elif lambda_em[s] - width <= em_range[0] < lambda_em[s]:
393
387
  emidx = dichotomy_search(em_range, lambda_em[s])
394
388
  raman_mask[exidx, 0: emidx + tol_emidx + 1] = 0
395
- else:
389
+ elif em_range[0] < lambda_em[s] - width < em_range[-1]:
396
390
  emidx = dichotomy_search(em_range, lambda_em[s] - width)
397
- raman_mask[exidx, emidx: emidx + 2 * tol_emidx + 1] = 0
391
+ raman_mask[exidx, emidx: min(em_range.shape[0], emidx + 2 * tol_emidx + 1)] = 0
398
392
 
399
393
  if interpolation_method == 'nan':
400
394
  intensity_masked[np.where(raman_mask == 0)] = np.nan
401
395
  else:
402
- if interpolation_axis == 'ex':
396
+ if interpolation_dimension == '1d-ex':
403
397
  for j in range(0, intensity.shape[1]):
404
398
  try:
405
399
  x = np.flipud(ex_range)[np.where(raman_mask[:, j] == 1)]
@@ -410,7 +404,7 @@ def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_meth
410
404
  except ValueError:
411
405
  continue
412
406
 
413
- if interpolation_axis == 'em':
407
+ if interpolation_dimension == '1d-em':
414
408
  for i in range(0, intensity.shape[0]):
415
409
  try:
416
410
  x = em_range[np.where(raman_mask[i, :] == 1)]
@@ -421,7 +415,7 @@ def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_meth
421
415
  except ValueError:
422
416
  continue
423
417
 
424
- if interpolation_axis == 'grid':
418
+ if interpolation_dimension == '2d':
425
419
  old_nan = np.isnan(intensity)
426
420
  intensity_masked[np.where(raman_mask == 0)] = np.nan
427
421
  intensity_masked = eem_nan_imputing(intensity_masked, ex_range, em_range, method=interpolation_method)
@@ -430,9 +424,10 @@ def eem_raman_masking(intensity, ex_range, em_range, width=5, interpolation_meth
430
424
  return intensity_masked, raman_mask
431
425
 
432
426
 
433
- def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15,
434
- interpolation_axis_o1='grid', interpolation_axis_o2='grid', interpolation_method_o1='zero',
435
- interpolation_method_o2='linear'):
427
+ def eem_rayleigh_scattering_removal(intensity, ex_range, em_range, width_o1=15, width_o2=15,
428
+ interpolation_dimension_o1='2d', interpolation_dimension_o2='2d',
429
+ interpolation_method_o1='zero',
430
+ interpolation_method_o2='linear'):
436
431
  """
437
432
  Remove and interpolate the Rayleigh scattering.
438
433
 
@@ -448,11 +443,11 @@ def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15
448
443
  The width or 1st order Rayleigh scattering.
449
444
  width_o2: float
450
445
  The width or 2nd order Rayleigh scattering.
451
- interpolation_axis_o1: str, {"ex", "em", "grid"}
446
+ interpolation_dimension_o1: str, {"1d-ex", "1d-em", "2d"}
452
447
  The axis along which the 1st order Rayleigh scattering is interpolated. "ex": interpolation is conducted along
453
- the excitation wavelength; "em": interpolation is conducted along the emission wavelength; "grid": interpolation
448
+ the excitation wavelength; "em": interpolation is conducted along the emission wavelength; "2d": interpolation
454
449
  is conducted on the 2D grid of both excitation and emission wavelengths.
455
- interpolation_axis_o2: str, {"ex", "em", "grid"}
450
+ interpolation_dimension_o2: str, {"1d-ex", "1d-em", "2d"}
456
451
  The axis along which the 2nd order Rayleigh scattering is interpolated.
457
452
  interpolation_method_o1: str, {"linear", "cubic", "nan"}
458
453
  The method used to interpolate the 1st order Rayleigh scattering.
@@ -473,6 +468,9 @@ def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15
473
468
  intensity_masked = np.array(intensity)
474
469
  rayleigh_mask_o1 = np.ones(intensity.shape)
475
470
  rayleigh_mask_o2 = np.ones(intensity.shape)
471
+ # convert the entire width to half-width
472
+ width_o1 = width_o1 / 2
473
+ width_o2 = width_o2 / 2
476
474
  lambda_em_o1 = ex_range
477
475
  tol_emidx_o1 = int(np.round(width_o1 / (em_range[1] - em_range[0])))
478
476
  for s in range(0, intensity_masked.shape[0]):
@@ -480,13 +478,14 @@ def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15
480
478
  if lambda_em_o1[s] <= em_range[0] <= lambda_em_o1[s] + width_o1:
481
479
  emidx = dichotomy_search(em_range, lambda_em_o1[s] + width_o1)
482
480
  rayleigh_mask_o1[exidx, 0:emidx + 1] = 0
483
- elif lambda_em_o1[s] - width_o1 <= em_range[0]:
481
+ elif lambda_em_o1[s] - width_o1 <= em_range[0] < lambda_em_o1[s]:
484
482
  emidx = dichotomy_search(em_range, lambda_em_o1[s])
485
483
  rayleigh_mask_o1[exidx, 0: emidx + tol_emidx_o1 + 1] = 0
486
- else:
487
- emidx = dichotomy_search(em_range, lambda_em_o1[s])
488
- rayleigh_mask_o1[exidx, emidx: emidx + tol_emidx_o1 + 1] = 0
484
+ elif em_range[0] < lambda_em_o1[s] - width_o1 < em_range[-1]:
485
+ emidx = dichotomy_search(em_range, lambda_em_o1[s] - width_o1)
486
+ rayleigh_mask_o1[exidx, emidx: min(em_range.shape[0], emidx + 2 * tol_emidx_o1 + 1)] = 0
489
487
  intensity_masked[exidx, 0: emidx] = 0
488
+
490
489
  lambda_em_o2 = ex_range * 2
491
490
  tol_emidx_o2 = int(np.round(width_o2 / (em_range[1] - em_range[0])))
492
491
  for s in range(0, intensity_masked.shape[0]):
@@ -494,22 +493,24 @@ def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15
494
493
  if lambda_em_o2[s] <= em_range[0] <= lambda_em_o2[s] + width_o2:
495
494
  emidx = dichotomy_search(em_range, lambda_em_o2[s] + width_o2)
496
495
  rayleigh_mask_o2[exidx, 0:emidx + 1] = 0
497
- elif lambda_em_o2[s] - width_o2 <= em_range[0]:
496
+ elif lambda_em_o2[s] - width_o2 <= em_range[0] < lambda_em_o2[s]:
498
497
  emidx = dichotomy_search(em_range, lambda_em_o2[s])
499
498
  rayleigh_mask_o2[exidx, 0: emidx + tol_emidx_o2 + 1] = 0
500
- else:
499
+ elif em_range[0] < lambda_em_o2[s] - width_o2 <= em_range[-1]:
501
500
  emidx = dichotomy_search(em_range, lambda_em_o2[s] - width_o2)
502
- rayleigh_mask_o2[exidx, emidx: emidx + 2 * tol_emidx_o2 + 1] = 0
501
+ rayleigh_mask_o2[exidx, emidx: min(em_range.shape[0], emidx + 2 * tol_emidx_o2 + 1)] = 0
503
502
 
504
- for axis, itp, mask in zip([interpolation_axis_o1, interpolation_axis_o2],
503
+ for axis, itp, mask in zip([interpolation_dimension_o1, interpolation_dimension_o2],
505
504
  [interpolation_method_o1, interpolation_method_o2],
506
505
  [rayleigh_mask_o1, rayleigh_mask_o2]):
507
506
  if itp == 'zero':
508
507
  intensity_masked[np.where(mask == 0)] = 0
509
508
  elif itp == 'nan':
510
509
  intensity_masked[np.where(mask == 0)] = np.nan
510
+ elif itp == 'none':
511
+ pass
511
512
  else:
512
- if axis == 'ex':
513
+ if axis == '1d-ex':
513
514
  for j in range(0, intensity.shape[1]):
514
515
  try:
515
516
  x = np.flipud(ex_range)[np.where(mask[:, j] == 1)]
@@ -519,7 +520,7 @@ def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15
519
520
  intensity_masked[:, j] = y_predict
520
521
  except ValueError:
521
522
  continue
522
- if axis == 'em':
523
+ if axis == '1d-em':
523
524
  for i in range(0, intensity.shape[0]):
524
525
  try:
525
526
  x = em_range[np.where(mask[i, :] == 1)]
@@ -529,18 +530,19 @@ def eem_rayleigh_masking(intensity, ex_range, em_range, width_o1=15, width_o2=15
529
530
  intensity_masked[i, :] = y_predict
530
531
  except ValueError:
531
532
  continue
532
- if axis == 'grid':
533
+ if axis == '2d':
533
534
  old_nan = np.isnan(intensity)
534
535
  old_nan_o1 = np.isnan(intensity_masked)
535
536
  intensity_masked[np.where(mask == 0)] = np.nan
536
- intensity_masked = eem_nan_imputing(intensity_masked, ex_range, em_range, method=itp, fill_value='linear_ex')
537
+ intensity_masked = eem_nan_imputing(intensity_masked, ex_range, em_range, method=itp,
538
+ fill_value='linear_ex')
537
539
  # restore the nan values in non-raman-scattering region
538
540
  intensity_masked[old_nan] = np.nan
539
541
  intensity_masked[old_nan_o1] = np.nan
540
542
  return intensity_masked, rayleigh_mask_o1, rayleigh_mask_o2
541
543
 
542
544
 
543
- def eem_ife_correction(intensity, ex_range, em_range, absorbance, ex_range_abs, cuvette_length=1):
545
+ def eem_ife_correction(intensity, ex_range_eem, em_range_eem, absorbance, ex_range_abs):
544
546
  """
545
547
  Correct the inner filter effect (IFE).
546
548
 
@@ -548,31 +550,29 @@ def eem_ife_correction(intensity, ex_range, em_range, absorbance, ex_range_abs,
548
550
  ----------
549
551
  intensity: np.ndarray (2d)
550
552
  The EEM.
551
- ex_range: np.ndarray (1d)
553
+ ex_range_eem: np.ndarray (1d)
552
554
  The excitation wavelengths of EEM.
553
- em_range: np.ndarray (1d)
555
+ em_range_eem: np.ndarray (1d)
554
556
  The emission wavelengths of EEM.
555
557
  absorbance: np.ndarray
556
- The absorbance. If this function is called alone, an array of shape (i, ) should be passed, where i is the
557
- length of the absorbance spectrum. If this function is called with process_eem_stack(), an array of shape (n, i)
558
+ The absorbance. If this function is called by itself, an array of shape (i, ) should be passed, where i is the
559
+ length of the absorbance spectrum. If this function is called by process_eem_stack(), an array of shape (n, i)
558
560
  should be passed, where n is the number samples, and i is the length of the absorbance spectrum.
559
561
  ex_range_abs: np.ndarray (1d)
560
562
  The excitation wavelengths of absorbance.
561
- cuvette_length: float
562
- The length of cuvette in measurement.
563
563
 
564
564
  Returns
565
565
  -------
566
566
  intensity_corrected: np.ndarray
567
567
  The corrected EEM.
568
568
  """
569
- if absorbance.ndim == 1:
570
- absorbance_reshape = absorbance.reshape((1, absorbance.shape[0]))
569
+ # if absorbance.ndim == 1:
570
+ # absorbance_reshape = absorbance.reshape((1, absorbance.shape[0]))
571
571
  f1 = interp1d(ex_range_abs, absorbance, kind='linear', bounds_error=False, fill_value='extrapolate')
572
- absorbance_ex = np.fliplr(np.array([f1(ex_range)]))
573
- absorbance_em = np.array([f1(em_range)])
574
- ife_factors = 10 ** (cuvette_length * (absorbance_ex.T.dot(np.ones(absorbance_em.shape)) +
575
- np.ones(absorbance_ex.shape).T.dot(absorbance_em)))
572
+ absorbance_ex = np.fliplr(np.array([f1(ex_range_eem)]))
573
+ absorbance_em = np.array([f1(em_range_eem)])
574
+ ife_factors = 10 ** ((absorbance_ex.T.dot(np.ones(absorbance_em.shape)) +
575
+ np.ones(absorbance_ex.shape).T.dot(absorbance_em)) / 2)
576
576
  intensity_corrected = intensity * ife_factors
577
577
  return intensity_corrected
578
578
 
@@ -605,9 +605,15 @@ def eem_regional_integration(intensity, ex_range, em_range, ex_min, ex_max, em_m
605
605
  avg_regional_intensity:
606
606
  The average fluorescence intensity in the region.
607
607
  """
608
- intensity_cut, em_range_cut, ex_range_cut = eem_cutting(intensity, ex_range, em_range,
609
- em_min=em_min, em_max=em_max,
610
- ex_min=ex_min, ex_max=ex_max)
608
+
609
+ ex_range_interpolated = np.sort(np.unique(np.concatenate([ex_range, [ex_min, ex_max]])))
610
+ em_range_interpolated = np.sort(np.unique(np.concatenate([em_range, [em_min, em_max]])))
611
+ intensity_interpolated = eem_interpolation(intensity, ex_range, em_range, ex_range_interpolated,
612
+ em_range_interpolated, method='linear')
613
+ intensity_cut, ex_range_cut, em_range_cut = eem_cutting(intensity_interpolated, ex_range_interpolated,
614
+ em_range_interpolated,
615
+ ex_min_new=ex_min, ex_max_new=ex_max,
616
+ em_min_new=ex_min, em_max_new=em_max)
611
617
  if intensity_cut.shape[0] == 1:
612
618
  integration = np.trapz(intensity_cut, em_range_cut, axis=1)
613
619
  elif intensity_cut.shape[1] == 1:
@@ -647,12 +653,9 @@ def eem_interpolation(intensity, ex_range_old, em_range_old, ex_range_new, em_ra
647
653
  intensity_interpolated: np.ndarray
648
654
  The interpolated EEM.
649
655
  """
650
- interp = RegularGridInterpolator((ex_range_old[::-1], em_range_old), intensity, method=method)
651
- x, y = np.meshgrid(em_range_new, ex_range_new[::-1])
652
- xx = x.flatten()
653
- yy = y.flatten()
654
- coordinates_new = np.concatenate([xx[:, np.newaxis], yy[:, np.newaxis]], axis=1)
655
- intensity_interpolated = interp(coordinates_new).reshape(ex_range_new.shape[0], em_range_new.shape[0])
656
+ interp = RegularGridInterpolator((ex_range_old[::-1], em_range_old), intensity, method=method, bounds_error=False)
657
+ x, y = np.meshgrid(ex_range_new[::-1], em_range_new, indexing='ij')
658
+ intensity_interpolated = interp((x, y)).reshape(ex_range_new.shape[0], em_range_new.shape[0])
656
659
  return intensity_interpolated
657
660
 
658
661
 
@@ -740,8 +743,8 @@ def eems_tf_normalization(intensity):
740
743
  # return label
741
744
 
742
745
 
743
- def eems_fit_components(eem_stack, component_stack, fit_intercept=False):
744
- assert eem_stack.shape[1:] == component_stack.shape, "EEM and component have different shapes"
746
+ def eems_fit_components(eem_stack, component_stack, fit_intercept=False, positive=False):
747
+ assert eem_stack.shape[1:] == component_stack.shape[1:], "EEM and component have different shapes"
745
748
  score_sample = []
746
749
  fmax_sample = []
747
750
  max_values = np.amax(component_stack, axis=(1, 2))
@@ -749,7 +752,7 @@ def eems_fit_components(eem_stack, component_stack, fit_intercept=False):
749
752
  for i in range(eem_stack.shape[0]):
750
753
  y_true = eem_stack[i].reshape([-1])
751
754
  x = component_stack.reshape([component_stack.shape[0], -1]).T
752
- reg = LinearRegression(fit_intercept=fit_intercept)
755
+ reg = LinearRegression(fit_intercept=fit_intercept, positive=positive)
753
756
  reg.fit(x, y_true)
754
757
  y_pred = reg.predict(x)
755
758
  eem_stack_pred[i, :, :] = y_pred.reshape((eem_stack.shape[1], eem_stack.shape[2]))
@@ -808,6 +811,12 @@ class EEMDataset:
808
811
  self.index = index
809
812
  self.extent = (self.em_range.min(), self.em_range.max(), self.ex_range.min(), self.ex_range.max())
810
813
 
814
+ def to_json_serializable(self):
815
+ self.eem_stack = self.eem_stack.tolist()
816
+ self.ex_range = self.ex_range.tolist()
817
+ self.em_range = self.em_range.tolist()
818
+ self.ref = self.ref.tolist() if self.ref else None
819
+
811
820
  # --------------------EEM dataset features--------------------
812
821
  def zscore(self):
813
822
  """
@@ -919,10 +928,15 @@ class EEMDataset:
919
928
  em_actual = self.em_range[em_idx]
920
929
  return fi, ex_actual, em_actual
921
930
 
922
- def correlation(self):
931
+ def correlation(self, fit_intercept=True):
923
932
  """
924
933
  Analyze the correlation between reference and fluorescence intensity at each pair of ex/em.
925
934
 
935
+ Params
936
+ -------
937
+ fit_intercept: bool, optional
938
+ Whether to fit the intercept for linear regression.
939
+
926
940
  Returns
927
941
  -------
928
942
  corr_dict: dict
@@ -931,20 +945,28 @@ class EEMDataset:
931
945
  m = self.eem_stack
932
946
  x = self.ref
933
947
  x = x.reshape(m.shape[0], 1)
934
- w, b, r2, pc, pc_p, sc, sc_p = [np.full((m.shape[1], m.shape[2]), fill_value=np.nan)] * 7
948
+ w = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
949
+ b = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
950
+ r2 = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
951
+ pc = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
952
+ pc_p = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
953
+ sc = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
954
+ sc_p = np.full((m.shape[1], m.shape[2]), fill_value=np.nan)
935
955
  e = np.full(m.shape, fill_value=np.nan)
936
956
  for i in range(m.shape[1]):
937
957
  for j in range(m.shape[2]):
938
958
  try:
939
959
  y = (m[:, i, j])
940
- reg = LinearRegression()
960
+ reg = LinearRegression(fit_intercept=fit_intercept)
941
961
  reg.fit(x, y)
942
962
  w[i, j] = reg.coef_
943
963
  b[i, j] = reg.intercept_
944
964
  r2[i, j] = reg.score(x, y)
945
965
  e[:, i, j] = reg.predict(x) - y
946
- pc[i, j], pc_p[i, j] = stats.pearsonr(x, y)
947
- sc[i, j], sc_p[i, j] = stats.spearmanr(x, y)
966
+ pc[i, j] = stats.pearsonr(x.reshape(-1), y).statistic
967
+ pc_p[i, j] = stats.pearsonr(x.reshape(-1), y).pvalue
968
+ sc[i, j] = stats.spearmanr(x.reshape(-1), y).statistic
969
+ sc_p[i, j] = stats.spearmanr(x.reshape(-1), y).pvalue
948
970
  except ValueError:
949
971
  pass
950
972
  corr_dict = {'slope': w, 'intercept': b, 'r_square': r2, 'linear regression residual': e,
@@ -984,7 +1006,7 @@ class EEMDataset:
984
1006
 
985
1007
  Parameters
986
1008
  ----------
987
- sigma, truncate
1009
+ sigma, truncate:
988
1010
  See eempy.eem_processing.eem_gaussian_filter
989
1011
  copy: bool
990
1012
  if False, overwrite the EEMDataset object with the processed EEMs.
@@ -1005,7 +1027,7 @@ class EEMDataset:
1005
1027
 
1006
1028
  Parameters
1007
1029
  ----------
1008
- ex_min, ex_max, em_min, em_max, fill_value
1030
+ ex_min, ex_max, em_min, em_max, fill_value:
1009
1031
  See eempy.eem_processing.eem_region_masking
1010
1032
  copy: bool
1011
1033
  if False, overwrite the EEMDataset object with the processed EEMs.
@@ -1015,9 +1037,11 @@ class EEMDataset:
1015
1037
  eem_stack_masked: np.ndarray
1016
1038
  The masked EEM.
1017
1039
  """
1018
- eem_stack_masked, _ = process_eem_stack(self.eem_stack, eem_region_masking, ex_range=self.ex_range,
1019
- em_range=self.em_range, ex_min=ex_min, ex_max=ex_max, em_min=em_min,
1020
- em_max=em_max, fill_value=fill_value)
1040
+ eem_stack_masked, _ = process_eem_stack(
1041
+ self.eem_stack, eem_region_masking, ex_range=self.ex_range,
1042
+ em_range=self.em_range, ex_min=ex_min, ex_max=ex_max, em_min=em_min,
1043
+ em_max=em_max, fill_value=fill_value
1044
+ )
1021
1045
  if not copy:
1022
1046
  self.eem_stack = eem_stack_masked
1023
1047
  return eem_stack_masked
@@ -1028,7 +1052,7 @@ class EEMDataset:
1028
1052
 
1029
1053
  Parameters
1030
1054
  ----------
1031
- ex_min, ex_max, em_min, em_max
1055
+ ex_min, ex_max, em_min, em_max:
1032
1056
  See eempy.eem_processing.eem_cutting
1033
1057
  copy: bool
1034
1058
  if False, overwrite the EEMDataset object with the processed EEMs.
@@ -1042,9 +1066,12 @@ class EEMDataset:
1042
1066
  em_range_cut:np.ndarray
1043
1067
  The cut em wavelengths.
1044
1068
  """
1045
- eem_stack_cut, new_ranges = process_eem_stack(self.eem_stack, eem_cutting, ex_range=self.ex_range,
1046
- em_range=self.em_range,
1047
- ex_min=ex_min, ex_max=ex_max, em_min=em_min, em_max=em_max)
1069
+ eem_stack_cut, new_ranges = process_eem_stack(
1070
+ self.eem_stack, eem_cutting, ex_range_old=self.ex_range,
1071
+ em_range_old=self.em_range,
1072
+ ex_min_new=ex_min, ex_max_new=ex_max, em_min_new=em_min,
1073
+ em_max_new=em_max
1074
+ )
1048
1075
  if not copy:
1049
1076
  self.eem_stack = eem_stack_cut
1050
1077
  self.ex_range = new_ranges[0][0]
@@ -1074,7 +1101,7 @@ class EEMDataset:
1074
1101
  return eem_stack_imputed
1075
1102
 
1076
1103
  def raman_normalization(self, ex_range_blank=None, em_range_blank=None, blank=None, from_blank=False,
1077
- integration_time=1, ex_lb=349, ex_ub=351, bandwidth_type='wavenumber', bandwidth=1800,
1104
+ integration_time=1, ex_target=350, bandwidth=5,
1078
1105
  rsu_standard=20000, manual_rsu=1, copy=True):
1079
1106
  """
1080
1107
  Normalize the EEM using the Raman scattering unit (RSU) given directly or calculated from a blank EEM.
@@ -1082,7 +1109,7 @@ class EEMDataset:
1082
1109
 
1083
1110
  Parameters
1084
1111
  ----------
1085
- blank, ex_range_blank, em_range_blank, from_blank, integration_time, ex_lb, ex_ub, bandwidth, bandwidth_type
1112
+ blank, ex_range_blank, em_range_blank, from_blank, integration_time, ex_target, bandwidth
1086
1113
  See eempy.eem_processing.eem_raman_normalization
1087
1114
  rsu_standard, manual_rsu
1088
1115
  See eempy.eem_processing.eem_raman_normalization
@@ -1094,14 +1121,15 @@ class EEMDataset:
1094
1121
  eem_stack_normalized: np.ndarray
1095
1122
  The normalized EEM.
1096
1123
  """
1097
- eem_stack_normalized = process_eem_stack(self.eem_stack, eem_raman_normalization, ex_range_blank=ex_range_blank,
1098
- em_range_blank=em_range_blank, blank=blank, from_blank=from_blank,
1099
- integration_time=integration_time, ex_lb=ex_lb, ex_ub=ex_ub,
1100
- bandwidth_type=bandwidth_type, bandwidth=bandwidth,
1101
- rsu_standard=rsu_standard, manual_rsu=manual_rsu)
1124
+ eem_stack_normalized, rsu = process_eem_stack(
1125
+ self.eem_stack, eem_raman_normalization, ex_range_blank=ex_range_blank,
1126
+ em_range_blank=em_range_blank, blank=blank, from_blank=from_blank,
1127
+ integration_time=integration_time, ex_target=ex_target,
1128
+ bandwidth=bandwidth, rsu_standard=rsu_standard, manual_rsu=manual_rsu
1129
+ )
1102
1130
  if not copy:
1103
1131
  self.eem_stack = eem_stack_normalized
1104
- return eem_stack_normalized
1132
+ return eem_stack_normalized, rsu
1105
1133
 
1106
1134
  def tf_normalization(self, copy=True):
1107
1135
  """
@@ -1124,14 +1152,14 @@ class EEMDataset:
1124
1152
  self.eem_stack = eem_stack_normalized
1125
1153
  return eem_stack_normalized, weights
1126
1154
 
1127
- def raman_masking(self, width=5, interpolation_method='linear', interpolation_axis='grid', copy=True):
1155
+ def raman_scattering_removal(self, width=5, interpolation_method='linear', interpolation_dimension='2d', copy=True):
1128
1156
  """
1129
1157
  Remove and interpolate the Raman scattering.
1130
1158
 
1131
1159
  Parameters
1132
1160
  ----------
1133
- width, interpolation_method, interpolation_axis
1134
- See eempy.eem_processing.eem_raman_masking
1161
+ width, interpolation_method, interpolation_dimension:
1162
+ See eempy.eem_processing.eem_raman_scattering_removal
1135
1163
  copy: bool
1136
1164
  if False, overwrite the EEMDataset object with the processed EEMs.
1137
1165
 
@@ -1140,23 +1168,26 @@ class EEMDataset:
1140
1168
  eem_stack_masked: np.ndarray
1141
1169
  The EEM with Raman scattering interpolated.
1142
1170
  """
1143
- eem_stack_masked, _ = process_eem_stack(self.eem_stack, eem_raman_masking, ex_range=self.ex_range,
1144
- em_range=self.em_range, width=width,
1145
- interpolation_method=interpolation_method,
1146
- interpolation_axis=interpolation_axis)
1171
+ eem_stack_masked, _ = process_eem_stack(
1172
+ self.eem_stack, eem_raman_scattering_removal, ex_range=self.ex_range,
1173
+ em_range=self.em_range, width=width,
1174
+ interpolation_method=interpolation_method,
1175
+ interpolation_dimension=interpolation_dimension
1176
+ )
1147
1177
  if not copy:
1148
1178
  self.eem_stack = eem_stack_masked
1149
1179
  return eem_stack_masked
1150
1180
 
1151
- def rayleigh_masking(self, width_o1=15, width_o2=15, interpolation_axis_o1='grid', interpolation_axis_o2='grid',
1152
- interpolation_method_o1='zero', interpolation_method_o2='linear', copy=True):
1181
+ def rayleigh_scattering_removal(self, width_o1=15, width_o2=15, interpolation_dimension_o1='2d',
1182
+ interpolation_dimension_o2='2d', interpolation_method_o1='zero',
1183
+ interpolation_method_o2='linear', copy=True):
1153
1184
  """
1154
1185
  Remove and interpolate the Rayleigh scattering.
1155
1186
 
1156
1187
  Parameters
1157
1188
  ----------
1158
- width_o1, width_o2, interpolation_axis_o1, interpolation_axis_o2, interpolation_method_o1, interpolation_method_o2
1159
- See eempy.eem_processing.eem_rayleigh_masking
1189
+ width_o1, width_o2, interpolation_dimension_o1, interpolation_dimension_o2, interpolation_method_o1, interpolation_method_o2:
1190
+ See eempy.eem_processing.eem_rayleigh_scattering_removal
1160
1191
  copy: bool
1161
1192
  if False, overwrite the EEMDataset object with the processed EEMs.
1162
1193
 
@@ -1165,23 +1196,26 @@ class EEMDataset:
1165
1196
  eem_stack_masked: np.ndarray
1166
1197
  The EEM with Rayleigh scattering interpolated.
1167
1198
  """
1168
- eem_stack_masked, _ = process_eem_stack(self.eem_stack, eem_rayleigh_masking, ex_range=self.ex_range,
1169
- em_range=self.em_range, width_o1=width_o1,
1170
- width_o2=width_o2, interpolation_axis_o1=interpolation_axis_o1,
1171
- interpolation_axis_o2=interpolation_axis_o2,
1172
- interpolation_method_o1=interpolation_method_o1,
1173
- interpolation_method_o2=interpolation_method_o2)
1199
+ eem_stack_masked, _ = process_eem_stack(
1200
+ self.eem_stack, eem_rayleigh_scattering_removal, ex_range=self.ex_range,
1201
+ em_range=self.em_range, width_o1=width_o1,
1202
+ width_o2=width_o2,
1203
+ interpolation_dimension_o1=interpolation_dimension_o1,
1204
+ interpolation_dimension_o2=interpolation_dimension_o2,
1205
+ interpolation_method_o1=interpolation_method_o1,
1206
+ interpolation_method_o2=interpolation_method_o2
1207
+ )
1174
1208
  if not copy:
1175
1209
  self.eem_stack = eem_stack_masked
1176
1210
  return eem_stack_masked
1177
1211
 
1178
- def ife_correction(self, absorbance, ex_range_abs, cuvette_length=1, copy=True):
1212
+ def ife_correction(self, absorbance, ex_range_abs, copy=True):
1179
1213
  """
1180
1214
  Correct the inner filter effect (IFE).
1181
1215
 
1182
1216
  Parameters
1183
1217
  ----------
1184
- absorbance, ex_range_abs, cuvette_length
1218
+ absorbance, ex_range_abs:
1185
1219
  See eempy.eem_processing.eem_ife_correction
1186
1220
  copy: bool
1187
1221
  if False, overwrite the EEMDataset object with the processed EEMs.
@@ -1191,9 +1225,11 @@ class EEMDataset:
1191
1225
  eem_stack_corrected: np.ndarray
1192
1226
  The corrected EEM.
1193
1227
  """
1194
- eem_stack_corrected = process_eem_stack(self.eem_stack, eem_ife_correction, ex_range=self.ex_range,
1195
- em_range=self.em_range, absorbance=absorbance,
1196
- ex_range_abs=ex_range_abs, cuvette_length=cuvette_length)
1228
+ eem_stack_corrected = process_eem_stack(
1229
+ self.eem_stack, eem_ife_correction, ex_range_eem=self.ex_range,
1230
+ em_range_eem=self.em_range, absorbance=absorbance,
1231
+ ex_range_abs=ex_range_abs
1232
+ )
1197
1233
  if not copy:
1198
1234
  self.eem_stack = eem_stack_corrected
1199
1235
  return eem_stack_corrected
@@ -1206,7 +1242,7 @@ class EEMDataset:
1206
1242
 
1207
1243
  Parameters
1208
1244
  ----------
1209
- ex_range_new, em_range_new, method
1245
+ ex_range_new, em_range_new, method:
1210
1246
  See eempy.eem_processing.eem_interpolation
1211
1247
  copy: bool
1212
1248
  if False, overwrite the EEMDataset object with the processed EEMs.
@@ -1216,9 +1252,11 @@ class EEMDataset:
1216
1252
  eem_stack_interpolated: np.ndarray
1217
1253
  The interpolated EEM.
1218
1254
  """
1219
- eem_stack_interpolated = process_eem_stack(self.eem_stack, eem_interpolation, ex_range_old=self.ex_range,
1220
- em_range_old=self.em_range, ex_range_new=ex_range_new,
1221
- em_range_new=em_range_new, method=method)
1255
+ eem_stack_interpolated = process_eem_stack(
1256
+ self.eem_stack, eem_interpolation, ex_range_old=self.ex_range,
1257
+ em_range_old=self.em_range, ex_range_new=ex_range_new,
1258
+ em_range_new=em_range_new, method=method
1259
+ )
1222
1260
  if not copy:
1223
1261
  self.eem_stack = eem_stack_interpolated
1224
1262
  self.ex_range = ex_range_new
@@ -1326,6 +1364,14 @@ class EEMDataset:
1326
1364
  return eem_stack_new, index_new, ref_new, selected_indices
1327
1365
 
1328
1366
  def sort_by_index(self):
1367
+ """
1368
+ Sort the sample order of eem_stack, index and reference (if exists) by the index.
1369
+
1370
+ Returns
1371
+ -------
1372
+ sorted_indices: np.ndarray
1373
+ The sorted sample order
1374
+ """
1329
1375
  sorted_indices = np.argsort(self.index)
1330
1376
  self.eem_stack = self.eem_stack[sorted_indices]
1331
1377
  if self.ref:
@@ -1334,13 +1380,31 @@ class EEMDataset:
1334
1380
  return sorted_indices
1335
1381
 
1336
1382
  def sort_by_ref(self):
1383
+ """
1384
+ Sort the sample order of eem_stack, reference and index (if exists) by the reference.
1385
+
1386
+ Returns
1387
+ -------
1388
+ sorted_indices: np.ndarray
1389
+ The sorted sample order
1390
+ """
1337
1391
  sorted_indices = np.argsort(self.ref)
1338
1392
  self.eem_stack = self.eem_stack[sorted_indices]
1339
1393
  if self.index:
1340
1394
  self.index = self.index[sorted_indices]
1341
- self.ref = sorted(self.ref)
1395
+ self.ref = np.array(sorted(self.ref))
1342
1396
  return sorted_indices
1343
1397
 
1398
+ def filter_by_index(self, keyword):
1399
+ """
1400
+ Select the samples whose indexes contain the given keyword.
1401
+
1402
+ Returns
1403
+ -------
1404
+
1405
+ """
1406
+ return
1407
+
1344
1408
 
1345
1409
  def combine_eem_datasets(list_eem_datasets):
1346
1410
  """
@@ -1363,8 +1427,11 @@ def combine_eem_datasets(list_eem_datasets):
1363
1427
  em_range_0 = list_eem_datasets[0].em_range
1364
1428
  for d in list_eem_datasets:
1365
1429
  eem_stack_combined.append(d.eem_stack)
1366
- ref_combined.append(d.ref)
1367
- index_combined = index_combined + d.index
1430
+ ref_combined.append(d.ref if d.ref else np.array(d.eem_stack.shape[0] * [np.nan]))
1431
+ if d.index:
1432
+ index_combined = index_combined + d.index
1433
+ else:
1434
+ index_combined = index_combined + ['N/A' for i in range(d.eem_stack.shape[0])]
1368
1435
  if not np.array_equal(d.ex_range, ex_range_0) or not np.array_equal(d.em_range, em_range_0):
1369
1436
  warnings.warn(
1370
1437
  'ex_range and em_range of the datasets must be identical. If you want to combine EEM datasets '
@@ -1409,7 +1476,7 @@ class PARAFAC:
1409
1476
  fmax: pandas.DataFrame
1410
1477
  Fmax table.
1411
1478
  component_stack: np.ndarray
1412
- PARAFAC Components.
1479
+ PARAFAC components.
1413
1480
  cptensors: tensorly CPTensor
1414
1481
  The output of PARAFAC in the form of tensorly CPTensor.
1415
1482
  eem_stack_train: np.ndarray
@@ -1466,9 +1533,9 @@ class PARAFAC:
1466
1533
  if not self.non_negativity:
1467
1534
  if np.isnan(eem_dataset.eem_stack).any():
1468
1535
  mask = np.where(np.isnan(eem_dataset.eem_stack), 0, 1)
1469
- cptensors, _ = parafac(eem_dataset.eem_stack, rank=self.rank, mask=mask, init=self.init)
1536
+ cptensors = parafac(eem_dataset.eem_stack, rank=self.rank, mask=mask, init=self.init)
1470
1537
  else:
1471
- cptensors, _ = parafac(eem_dataset.eem_stack, rank=self.rank, init=self.init)
1538
+ cptensors = parafac(eem_dataset.eem_stack, rank=self.rank, init=self.init)
1472
1539
  else:
1473
1540
  if np.isnan(eem_dataset.eem_stack).any():
1474
1541
  mask = np.where(np.isnan(eem_dataset.eem_stack), 0, 1)
@@ -1517,7 +1584,7 @@ class PARAFAC:
1517
1584
  ex_loadings = pd.DataFrame(np.flipud(b), index=eem_dataset.ex_range)
1518
1585
  em_loadings = pd.DataFrame(c, index=eem_dataset.em_range)
1519
1586
  if self.sort_em:
1520
- em_peaks = [c[1] for c in em_loadings.idxmax()]
1587
+ em_peaks = [c for c in em_loadings.idxmax()]
1521
1588
  peak_rank = list(enumerate(stats.rankdata(em_peaks)))
1522
1589
  order = [i[0] for i in sorted(peak_rank, key=lambda x: x[1])]
1523
1590
  component_stack = component_stack[order]
@@ -1633,7 +1700,7 @@ class PARAFAC:
1633
1700
 
1634
1701
  Returns
1635
1702
  -------
1636
- ev: float
1703
+ cc: float
1637
1704
  core consistency
1638
1705
  """
1639
1706
  cc = core_consistency(self.cptensors, self.eem_stack_train)
@@ -1656,13 +1723,16 @@ class PARAFAC:
1656
1723
  """
1657
1724
  if mode == 'ex':
1658
1725
  lvr = compute_leverage(self.ex_loadings)
1726
+ lvr.columns = ['leverage-ex']
1659
1727
  elif mode == 'em':
1660
1728
  lvr = compute_leverage(self.em_loadings)
1729
+ lvr.columns = ['leverage-em']
1661
1730
  elif mode == 'sample':
1662
1731
  lvr = compute_leverage(self.score)
1732
+ lvr.columns = ['leverage-sample']
1663
1733
  else:
1664
1734
  raise ValueError("'mode' should be 'ex' or 'em' or 'sample'.")
1665
- lvr.index = lvr.index.set_levels(['leverage of {m}'.format(m=mode)] * len(lvr.index.levels[0]), level=0)
1735
+ # lvr.index = lvr.index.set_levels(['leverage of {m}'.format(m=mode)] * len(lvr.index.levels[0]), level=0)
1666
1736
  return lvr
1667
1737
 
1668
1738
  def sample_rmse(self):
@@ -1836,27 +1906,27 @@ def align_parafac_components(models_dict: dict, ex_ref: pd.DataFrame, em_ref: pd
1836
1906
  component_labels_ref = ex_ref.columns
1837
1907
  models_dict_new = {}
1838
1908
  for model_label, model in models_dict.items():
1839
- m_sim_ex = loadings_similarity(model, ex_ref, wavelength_alignment=wavelength_alignment)
1840
- m_sim_em = loadings_similarity(model, em_ref, wavelength_alignment=wavelength_alignment)
1909
+ m_sim_ex = loadings_similarity(model.ex_loadings, ex_ref, wavelength_alignment=wavelength_alignment)
1910
+ m_sim_em = loadings_similarity(model.em_loadings, em_ref, wavelength_alignment=wavelength_alignment)
1841
1911
  m_sim = (m_sim_ex + m_sim_em) / 2
1842
1912
  ex_var, em_var = (model.ex_loadings, model.em_loadings)
1843
1913
  matched_index = []
1844
1914
  m_sim_copy = m_sim.copy()
1845
1915
  if ex_var.shape[1] <= ex_ref.shape[1]:
1846
1916
  for n_var in range(ex_var.shape[1]):
1847
- max_index = np.argmax(m_sim[n_var, :])
1917
+ max_index = np.argmax(m_sim.iloc[n_var, :])
1848
1918
  while max_index in matched_index:
1849
- m_sim_copy[n_var, max_index] = 0
1850
- max_index = np.argmax(m_sim_copy[n_var, :])
1919
+ m_sim_copy.iloc[n_var, max_index] = 0
1920
+ max_index = np.argmax(m_sim_copy.iloc[n_var, :])
1851
1921
  matched_index.append(max_index)
1852
1922
  component_labels_var = [component_labels_ref[i] for i in matched_index]
1853
1923
  permutation = get_indices_smallest_to_largest(matched_index)
1854
1924
  else:
1855
1925
  for n_ref in range(ex_ref.shape[1]):
1856
- max_index = np.argmax(m_sim[:, n_ref])
1926
+ max_index = np.argmax(m_sim.iloc[:, n_ref])
1857
1927
  while max_index in matched_index:
1858
- m_sim_copy[max_index, n_ref] = 0
1859
- max_index = np.argmax(m_sim_copy[:, n_ref])
1928
+ m_sim_copy.iloc[max_index, n_ref] = 0
1929
+ max_index = np.argmax(m_sim_copy.iloc[:, n_ref])
1860
1930
  matched_index.append(max_index)
1861
1931
  non_ordered_index = list(set([i for i in range(ex_var.shape[1])]) - set(matched_index))
1862
1932
  permutation = matched_index + non_ordered_index
@@ -1872,9 +1942,9 @@ def align_parafac_components(models_dict: dict, ex_ref: pd.DataFrame, em_ref: pd
1872
1942
  model.em_loadings = model.em_loadings.iloc[:, permutation]
1873
1943
  model.fmax = model.fmax.iloc[:, permutation]
1874
1944
  model.component_stack = model.component_stack[permutation, :, :]
1875
- model.cptensor = permute_cp_tensor(model.cptensor, permutation)
1945
+ model.cptensor = permute_cp_tensor(model.cptensors, permutation)
1876
1946
  models_dict_new[model_label] = model
1877
- return models_dict_new
1947
+ return models_dict_new
1878
1948
 
1879
1949
 
1880
1950
  class SplitValidation:
@@ -1906,8 +1976,8 @@ class SplitValidation:
1906
1976
  Dictionary of PARAFAC models established on sub-datasets.
1907
1977
  """
1908
1978
 
1909
- def __init__(self, rank, n_split, combination_size, rule, similarity_metric='TCC', non_negativity=True,
1910
- tf_normalization=True):
1979
+ def __init__(self, rank, n_split=4, combination_size='half', rule='random', similarity_metric='TCC',
1980
+ non_negativity=True, tf_normalization=True):
1911
1981
  # ---------------Parameters-------------------
1912
1982
  self.rank = rank
1913
1983
  self.n_split = n_split
@@ -1944,7 +2014,6 @@ class SplitValidation:
1944
2014
  models[label] = model_subdataset
1945
2015
  subsets[label] = subdataset
1946
2016
  models = align_parafac_components(models, model_complete.ex_loadings, model_complete.em_loadings)
1947
-
1948
2017
  self.eem_subsets = subsets
1949
2018
  self.subset_specific_models = models
1950
2019
  return self
@@ -1971,8 +2040,16 @@ class SplitValidation:
1971
2040
  pair_labels = '{m1} vs. {m2}'.format(m1=labels[k], m2=labels[-1 - k])
1972
2041
  similarities_ex[pair_labels] = sims_ex
1973
2042
  similarities_em[pair_labels] = sims_em
1974
- similarities_ex = pd.DataFrame(similarities_ex, columns=['C{i}'.format(i=i + 1) for i in range(self.rank)])
1975
- similarities_em = pd.DataFrame(similarities_em, columns=['C{i}'.format(i=i + 1) for i in range(self.rank)])
2043
+ similarities_ex = pd.DataFrame.from_dict(
2044
+ similarities_ex, orient='index',
2045
+ columns=['Similarities in C{i}-ex'.format(i=i + 1) for i in range(self.rank)]
2046
+ )
2047
+ similarities_ex.index.name = 'Test'
2048
+ similarities_em = pd.DataFrame.from_dict(
2049
+ similarities_em, orient='index',
2050
+ columns=['Similarities in C{i}-em'.format(i=i + 1) for i in range(self.rank)]
2051
+ )
2052
+ similarities_em.index.name = 'Test'
1976
2053
  return similarities_ex, similarities_em
1977
2054
 
1978
2055
 
@@ -2046,7 +2123,7 @@ class KPARAFACs:
2046
2123
  self.tf_normalization = tf_normalization
2047
2124
  self.loadings_normalization = loadings_normalization
2048
2125
  self.sort_em = sort_em
2049
- self.dropout = None
2126
+ self.subsampling_portion = None
2050
2127
  self.n_runs = None
2051
2128
  self.consensus_conversion_power = None
2052
2129
 
@@ -2273,7 +2350,7 @@ class KPARAFACs:
2273
2350
  cluster_specific_models[j] = model
2274
2351
 
2275
2352
  self.n_runs = n_runs
2276
- self.dropout = subsampling_portion
2353
+ self.subsampling_portion = subsampling_portion
2277
2354
  self.consensus_conversion_power = consensus_conversion_power
2278
2355
  self.label_history = label_history
2279
2356
  self.error_history = error_history
@@ -2282,3 +2359,363 @@ class KPARAFACs:
2282
2359
  self.cluster_specific_models = cluster_specific_models
2283
2360
  self.consensus_matrix = consensus_matrix
2284
2361
  self.consensus_matrix_sorted = consensus_matrix_sorted
2362
+
2363
+ def predict(self, eem_dataset: EEMDataset):
2364
+ """
2365
+ Fit the cluster-specific models to a given EEM dataset. Each EEM in the EEM dataset is fitted to the model that
2366
+ produce the least RMSE.
2367
+
2368
+ Parameters
2369
+ ----------
2370
+ eem_dataset: EEMDataset
2371
+ EEM dataset.
2372
+
2373
+ Returns
2374
+ -------
2375
+ best_model_label: pd.DataFrame
2376
+ The best-fit model for every EEM.
2377
+ score_all: pd.DataFrame
2378
+ The score fitted with each cluster-specific model.
2379
+ fmax_all: pd.DataFrame
2380
+ The fmax fitted with each cluster-specific model.
2381
+ sample_error: pd.DataFrame
2382
+ The RMSE fitted with each cluster-specific model.
2383
+ """
2384
+
2385
+ sample_error = []
2386
+ score_all = []
2387
+ fmax_all = []
2388
+
2389
+ for label, m in self.cluster_specific_models.items():
2390
+ score_m, fmax_m, eem_stack_re_m = m.predict(eem_dataset)
2391
+ res = m.eem_stack_train - eem_stack_re_m
2392
+ n_pixels = m.eem_stack_train.shape[1] * m.eem_stack_train.shape[2]
2393
+ rmse = sqrt(np.sum(res ** 2, axis=(1, 2)) / n_pixels)
2394
+ sample_error.append(rmse)
2395
+ score_all.append(score_m)
2396
+ fmax_all.append(fmax_m)
2397
+
2398
+ score_all = pd.DataFrame(np.array(score_all), index=eem_dataset.index,
2399
+ columns=list(self.cluster_specific_models.keys()))
2400
+ fmax_all = pd.DataFrame(np.array(fmax_all), index=eem_dataset.index,
2401
+ columns=list(self.cluster_specific_models.keys()))
2402
+ best_model_idx = np.argmin(sample_error, axis=0)
2403
+ # least_model_errors = np.min(sample_error, axis=0)
2404
+ # score_opt = np.array([score_all[i, j] for j, i in enumerate(best_model_idx)])
2405
+ # fmax_opt = np.array([fmax_all[i, j] for j, i in enumerate(best_model_idx)])
2406
+ best_model_label = np.array([list(self.cluster_specific_models.keys())[idx] for idx in best_model_idx])
2407
+ best_model_label = pd.DataFrame(best_model_label, index=eem_dataset.index, columns=['best-fit model'])
2408
+ sample_error = pd.DataFrame(np.array(sample_error), index=eem_dataset.index,
2409
+ columns=list(self.cluster_specific_models.keys()))
2410
+
2411
+ return best_model_label, score_all, fmax_all, sample_error
2412
+
2413
+
2414
+ # class EEMPCA:
2415
+ #
2416
+ # def __init__(self, n_components):
2417
+ # self.n_components = n_components
2418
+ # self.score = None
2419
+ # self.components = None
2420
+ #
2421
+ # def fit(self, eem_dataset: EEMDataset):
2422
+ # decomposer = PCA(n_components=self.n_components)
2423
+ # n_samples = eem_dataset.eem_stack.shape[0]
2424
+ # X = eem_dataset.eem_stack.reshape([n_samples, -1])
2425
+ # score = decomposer.fit_transform(X)
2426
+ # score = pd.DataFrame(score, index=eem_dataset.index,
2427
+ # columns=["component {i}".format(i=i + 1) for i in range(self.n_components)])
2428
+ # components = decomposer.components_.reshape([self.n_components, eem_dataset.eem_stack.shape[1],
2429
+ # eem_dataset.eem_stack.shape[2]])
2430
+ # self.score = score
2431
+ # self.components = components
2432
+ #
2433
+ # return self
2434
+ #
2435
+ #
2436
+ # class EEMNMF:
2437
+ #
2438
+ # def __init__(self, n_components, alpha_W, alpha_H, l1_ratio):
2439
+ # self.n_components = n_components
2440
+ # self.alpha_W = alpha_W
2441
+ # self.alpha_H = alpha_H
2442
+ # self.l1_ratio = l1_ratio
2443
+ # self.score = None
2444
+ # self.components = None
2445
+ #
2446
+ # def fit(self, eem_dataset: EEMDataset):
2447
+ # decomposer = NMF(n_components=self.n_components, alpha_W=self.alpha_W, alpha_H=self.alpha_H,
2448
+ # l1_ratio=self.l1_ratio)
2449
+ # n_samples = eem_dataset.eem_stack.shape[0]
2450
+ # X = eem_dataset.eem_stack.reshape([n_samples, -1])
2451
+ # score = decomposer.fit_transform(X)
2452
+ # score = pd.DataFrame(score, index=eem_dataset.index,
2453
+ # columns=["component {i}".format(i=i + 1) for i in range(self.n_components)])
2454
+ # components = decomposer.components_.reshape([self.n_components, eem_dataset.eem_stack.shape[1],
2455
+ # eem_dataset.eem_stack.shape[2]])
2456
+ # self.score = score
2457
+ # self.components = components
2458
+ #
2459
+ # return self
2460
+
2461
+
2462
+ class EEMNMF:
2463
+
2464
+ def __init__(self, n_components, solver='cd', beta_loss='frobenius', alpha_W=0, alpha_H=0, l1_ratio=1,
2465
+ normalization='pixel_std'):
2466
+ self.n_components = n_components
2467
+ self.solver = solver
2468
+ self.beta_loss = beta_loss
2469
+ self.alpha_W = alpha_W
2470
+ self.alpha_H = alpha_H
2471
+ self.l1_ratio = l1_ratio
2472
+ self.normalization = normalization
2473
+ self.eem_stack_unfolded = None
2474
+ self.nmf_score = None
2475
+ self.nnls_score = None
2476
+ self.components = None
2477
+ self.decomposer = None
2478
+ self.residual = None
2479
+ self.normalization_factor_std = None
2480
+ self.normalization_factor_max = None
2481
+ self.reconstruction_error = None
2482
+ self.eem_dataset_train = None
2483
+
2484
+ def fit(self, eem_dataset, sort_em=True):
2485
+ decomposer = NMF(n_components=self.n_components, solver=self.solver, beta_loss=self.beta_loss,
2486
+ alpha_W=self.alpha_W, alpha_H=self.alpha_H,
2487
+ l1_ratio=self.l1_ratio)
2488
+ n_samples = eem_dataset.eem_stack.shape[0]
2489
+ X = eem_dataset.eem_stack.reshape([n_samples, -1])
2490
+ # if self.normalization == 'intensity_max':
2491
+ # factor = np.max(X, axis=1)[:, np.newaxis]
2492
+ # X = X / factor
2493
+ # score = decomposer.fit_transform(X) * factor
2494
+ if self.normalization == 'pixel_std':
2495
+ factor_std = np.std(X, axis=0)
2496
+ X = X / factor_std
2497
+ X[np.isnan(X)] = 0
2498
+ nmf_score = decomposer.fit_transform(X)
2499
+ else:
2500
+ nmf_score = decomposer.fit_transform(X)
2501
+ factor_std = None
2502
+ factor_max = None
2503
+ nmf_score = pd.DataFrame(nmf_score, index=eem_dataset.index,
2504
+ columns=["component {i}".format(i=i + 1) for i in range(self.n_components)])
2505
+ if self.normalization == 'pixel_std':
2506
+ components = decomposer.components_ * factor_std
2507
+ else:
2508
+ components = decomposer.components_
2509
+ factor_max = np.max(components, axis=1)
2510
+ components = components / factor_max[:, None]
2511
+ components = components.reshape([self.n_components, eem_dataset.eem_stack.shape[1],
2512
+ eem_dataset.eem_stack.shape[2]])
2513
+ nmf_score = nmf_score.mul(factor_max, axis=1)
2514
+ _, nnls_score, _ = eems_fit_components(eem_dataset.eem_stack, components,
2515
+ fit_intercept=False, positive=True)
2516
+ nnls_score = pd.DataFrame(nnls_score, index=eem_dataset.index,
2517
+ columns=["component {i}".format(i=i + 1) for i in range(self.n_components)])
2518
+ if sort_em:
2519
+ em_peaks = []
2520
+ for i in range(self.n_components):
2521
+ flat_max_index = components[i].argmax()
2522
+ row_index, col_index = np.unravel_index(flat_max_index, components[i].shape)
2523
+ em_peaks.append(col_index)
2524
+ peak_rank = list(enumerate(stats.rankdata(em_peaks)))
2525
+ order = [i[0] for i in sorted(peak_rank, key=lambda x: x[1])]
2526
+ components = components[order]
2527
+ nmf_score = pd.DataFrame({'component {r} score'.format(r=i + 1): nmf_score.iloc[:, order[i]]
2528
+ for i in range(self.n_components)})
2529
+ nnls_score = pd.DataFrame({'component {r} score'.format(r=i + 1): nnls_score.iloc[:, order[i]]
2530
+ for i in range(self.n_components)})
2531
+ self.nmf_score = nmf_score
2532
+ self.nnls_score = nnls_score
2533
+ self.components = components
2534
+ self.decomposer = decomposer
2535
+ self.eem_stack_unfolded = X
2536
+ self.normalization_factor_std = factor_std
2537
+ self.normalization_factor_max = factor_max
2538
+ self.reconstruction_error = decomposer.reconstruction_err_
2539
+ self.eem_dataset_train = eem_dataset
2540
+ return self
2541
+
2542
+ def calculate_residual(self, score_type='nmf'):
2543
+ if score_type == 'nmf':
2544
+ X_new = self.decomposer.fit_transform(self.eem_stack_unfolded)
2545
+ X_reversed = self.decomposer.inverse_transform(X_new) * self.normalization_factor
2546
+ n_samples = self.eem_dataset_train.eem_stack.shape[0]
2547
+ residual = X_reversed - self.eem_stack_unfolded
2548
+ residual = residual.reshape([n_samples, self.eem_dataset_train.eem_stack.shape[1],
2549
+ self.eem_dataset_train.eem_stack.shape[2]])
2550
+ self.residual = residual
2551
+ # elif score_type=='nnls':
2552
+ # X_new = self.decomposer.fit_transform(self.eem_stack_unfolded)
2553
+ # X_reversed = self.decomposer.inverse_transform(X_new)*self.normalization_factor
2554
+ # n_samples = eem_dataset.eem_stack.shape[0]
2555
+ # residual = X_reversed - self.eem_stack_unfolded
2556
+ # residual = residual.reshape([n_samples, eem_dataset.eem_stack.shape[1], eem_dataset.eem_stack.shape[2]])
2557
+ # self.residual = residual
2558
+ return residual
2559
+
2560
+ def greedy_selection(self, eem_dataset_train, eem_dataset_test, direction='backwards',
2561
+ criteria: str = 'reconstruction_error', true_values=None, axis=0, n_steps='max',
2562
+ index_groups=None):
2563
+ eem_stack = eem_dataset_train.eem_stack
2564
+ if index_groups == None:
2565
+ index_groups = [[i] for i in range(eem_stack.shape[axis])]
2566
+ if n_steps == 'max':
2567
+ n_steps = len(index_groups)
2568
+ eem_datasets_sequence = []
2569
+ err_sequence = []
2570
+ fmax_sequence = []
2571
+ g_tot = []
2572
+
2573
+ for step in range(n_steps):
2574
+ err_list = []
2575
+ eem_dataset_sub_list = []
2576
+ fmax_list = []
2577
+ if direction == 'forwards':
2578
+ eem_stack_sub = []
2579
+ elif direction == 'backwards':
2580
+ eem_stack_sub = eem_stack
2581
+ if direction == 'backwards' and step == 0:
2582
+ eem_dataset_sub = eem_dataset_train
2583
+ self.fit(eem_dataset_sub)
2584
+ score, fmax, eem_stack_pred = eems_fit_components(eem_dataset_test.eem_stack, self.components,
2585
+ fit_intercept=False, positive=True)
2586
+ if criteria == 'reconstruction_error':
2587
+ residual = eem_stack_pred - eem_dataset_test.eem_stack
2588
+
2589
+ elif criteria == 'fmax_error':
2590
+ assert true_values.shape == (eem_dataset_test.eem_stack.shape[0], self.n_components), \
2591
+ "True values should have a shape of (n_test_samples, n_components)"
2592
+ residual = fmax - np.array(true_values)
2593
+
2594
+ elif criteria == 'component_error':
2595
+ assert true_values.shape == (self.n_components, eem_dataset_test.eem_stack.shape[1],
2596
+ eem_dataset_test.eem_stack.shape[2]), \
2597
+ "True values should have a shape of (n_components, n_ex, n_em)"
2598
+ residual = self.components - np.array(true_values)
2599
+
2600
+ error = (np.sum(residual ** 2) / np.size(residual)) ** 0.5
2601
+ err_list.append(error)
2602
+ fmax_list.append(fmax)
2603
+ eem_dataset_sub_list.append(eem_dataset_sub)
2604
+
2605
+ else:
2606
+ for g in index_groups:
2607
+ if direction == 'forwards':
2608
+ eem_stack_g = np.take(eem_stack, g, axis=axis)
2609
+ eem_stack_sub.append(eem_stack_g.tolist())
2610
+ if axis == 0:
2611
+ if eem_dataset_train.index:
2612
+ index_sub = [eem_dataset_train.index[i] for i in g_tot + g]
2613
+ else:
2614
+ index_sub = None
2615
+ if eem_dataset_train.ref:
2616
+ ref_sub = eem_dataset_train.ref[g_tot + g]
2617
+ else:
2618
+ ref_sub = None
2619
+ else:
2620
+ index_sub = eem_dataset_train.index
2621
+ ref_sub = eem_dataset_train.ref
2622
+ eem_dataset_sub = EEMDataset(eem_stack=np.array(eem_stack_sub),
2623
+ ex_range=eem_dataset_train.ex_range,
2624
+ em_range=eem_dataset_train.em_range, index=index_sub, ref=ref_sub)
2625
+
2626
+ if direction == 'backwards':
2627
+
2628
+ eem_stack_sub = np.delete(eem_stack, g_tot + g, axis=axis)
2629
+ if axis == 0:
2630
+ if eem_dataset_train.index:
2631
+ index_sub = [eem_dataset_train.index[i] for i in range(eem_stack.shape[0]) if
2632
+ i not in g_tot + g]
2633
+ else:
2634
+ index_sub = None
2635
+ if eem_dataset_train.ref:
2636
+ ref_sub = np.delete(eem_dataset_train.ref, g_tot + g)
2637
+ else:
2638
+ ref_sub = None
2639
+ else:
2640
+ index_sub = eem_dataset_train.index
2641
+ ref_sub = eem_dataset_train.ref
2642
+ eem_dataset_sub = EEMDataset(eem_stack=eem_stack_sub, ex_range=eem_dataset_train.ex_range,
2643
+ em_range=eem_dataset_train.em_range, index=index_sub, ref=ref_sub)
2644
+ self.fit(eem_dataset_sub)
2645
+ # plot_eem(self.components[0], eem_dataset_test.ex_range, eem_dataset_test.em_range, auto_intensity_range=False,
2646
+ # vmin=0, vmax=1)
2647
+ score, fmax, eem_stack_pred = eems_fit_components(eem_dataset_test.eem_stack, self.components,
2648
+ fit_intercept=False, positive=True)
2649
+ if criteria == 'reconstruction_error':
2650
+ residual = eem_stack_pred - eem_dataset_test.eem_stack
2651
+
2652
+ elif criteria == 'fmax_error':
2653
+ assert true_values.shape == (eem_dataset_test.eem_stack.shape[0], self.n_components), \
2654
+ "True values should have a shape of (n_test_samples, n_components)"
2655
+ residual = fmax - np.array(true_values)
2656
+
2657
+ elif criteria == 'component_error':
2658
+ assert true_values.shape == (self.n_components, eem_dataset_test.eem_stack.shape[1],
2659
+ eem_dataset_test.eem_stack.shape[2]), \
2660
+ "True values should have a shape of (n_components, n_ex, n_em)"
2661
+ residual = self.components - np.array(true_values)
2662
+
2663
+ error = (np.sum(residual ** 2) / np.size(residual)) ** 0.5
2664
+ err_list.append(error)
2665
+ fmax_list.append(fmax)
2666
+ eem_dataset_sub_list.append(eem_dataset_sub)
2667
+
2668
+ least_err_idx = err_list.index(min(err_list))
2669
+ err_sequence.append(min(err_list))
2670
+ fmax_sequence.append(pd.DataFrame(fmax_list[least_err_idx], index=eem_dataset_test.index,
2671
+ columns=["component {i}".format(i=i + 1) for i in
2672
+ range(self.n_components)]))
2673
+ eem_datasets_sequence.append(eem_dataset_sub_list[least_err_idx])
2674
+ if direction == 'backwards' and step == 0:
2675
+ continue
2676
+ else:
2677
+ g_tot += index_groups[least_err_idx]
2678
+ index_groups.pop(least_err_idx)
2679
+
2680
+ return eem_datasets_sequence, err_sequence, fmax_sequence
2681
+
2682
+
2683
+ class EEMPCA:
2684
+
2685
+ def __init__(self, n_components):
2686
+ self.n_components = n_components
2687
+ self.eem_stack_unfolded = None
2688
+ self.score = None
2689
+ self.components = None
2690
+ self.decomposer = None
2691
+ self.residual = None
2692
+ self.normalization_factor = None
2693
+ self.eem_stack_train = None
2694
+
2695
+ def fit(self, eem_dataset: EEMDataset, normalization=None):
2696
+ decomposer = PCA(n_components=self.n_components)
2697
+ n_samples = eem_dataset.eem_stack.shape[0]
2698
+ X = eem_dataset.eem_stack.reshape([n_samples, -1])
2699
+ score = decomposer.fit_transform(X)
2700
+ score = pd.DataFrame(score, index=eem_dataset.index,
2701
+ columns=["component {i}".format(i=i + 1) for i in range(self.n_components)])
2702
+ components = decomposer.components_.reshape([self.n_components, eem_dataset.eem_stack.shape[1],
2703
+ eem_dataset.eem_stack.shape[2]])
2704
+ self.score = score
2705
+ self.components = components
2706
+ self.decomposer = decomposer
2707
+ self.eem_stack_unfolded = X
2708
+ self.eem_stack_train = eem_dataset
2709
+ return self
2710
+
2711
+ def calculate_residual(self):
2712
+ X_new = self.decomposer.fit_transform(self.eem_stack_unfolded)
2713
+ X_reversed = self.decomposer.inverse_transform(X_new)
2714
+ n_samples = self.eem_stack_train.eem_stack.shape[0]
2715
+ residual = X_reversed - self.eem_stack_unfolded
2716
+ residual = residual.reshape([n_samples, self.eem_stack_train.eem_stack.shape[1],
2717
+ self.eem_stack_train.eem_stack.shape[2]])
2718
+ self.residual = residual
2719
+ return residual
2720
+
2721
+