diff --git a/tests/integration/test_integration_array.py b/tests/integration/test_integration_array.py index fbd5845..b6a80da 100644 --- a/tests/integration/test_integration_array.py +++ b/tests/integration/test_integration_array.py @@ -59,7 +59,7 @@ def test_plot(self, analysis, flavour, n): n_rot = 0 if flavour == 'cplx': cplx = True - if flavour == 'varmx': + elif flavour == 'varmx': n_rot = 10 if analysis == 'uni': model = MCA(self.A) diff --git a/tests/integration/test_integration_xarray.py b/tests/integration/test_integration_xarray.py index c2d29c2..458bb17 100644 --- a/tests/integration/test_integration_xarray.py +++ b/tests/integration/test_integration_xarray.py @@ -224,7 +224,7 @@ def test_getter(self, analysis, n, scaling, phase_shift, rotated): n_rot = 0 if analysis == 'cplx': cplx = True - if analysis == 'varmx': + elif analysis == 'varmx': n_rot = 10 model = xMCA(self.A, self.B) model.solve(complexify=cplx) @@ -247,7 +247,7 @@ def test_hom_het_patterns(self, analysis): n_rot = 0 if analysis == 'cplx': cplx = True - if analysis == 'varmx': + elif analysis == 'varmx': n_rot = 10 model = xMCA(self.A, self.B) model.solve(complexify=cplx) @@ -272,7 +272,7 @@ def test_field(self, analysis): n_rot = 0 if analysis == 'cplx': cplx = True - if analysis == 'varmx': + elif analysis == 'varmx': n_rot = 10 model = xMCA(self.A, self.B) model.solve(complexify=cplx) @@ -295,7 +295,7 @@ def test_field_scaling(self, analysis): n_rot = 0 if analysis == 'cplx': cplx = True - if analysis == 'varmx': + elif analysis == 'varmx': n_rot = 10 model = xMCA(self.A, self.B) result1 = model.fields(original_scale=True) @@ -333,7 +333,7 @@ def test_plot(self, analysis, flavour, n): n_rot = 0 if flavour == 'cplx': cplx = True - if flavour == 'varmx': + elif flavour == 'varmx': n_rot = 10 if analysis == 'uni': model = xMCA(self.A) @@ -465,7 +465,7 @@ def test_truncate(self, analysis, flavour, trunc): n_rot = 0 if flavour == 'cplx': cplx = True - if flavour == 'varmx': + elif flavour == 'varmx': n_rot = 10 if analysis == 'uni': model = xMCA(self.A) @@ -483,9 +483,7 @@ def test_truncate(self, analysis, flavour, trunc): def test_apply_weights(self): model = xMCA(self.A, self.B) - weights = {} - weights['left'] = self.A.coords['lat'] - weights['right'] = self.B.coords['lat'] + weights = {'left': self.A.coords['lat'], 'right': self.B.coords['lat']} model.apply_weights(**weights) def test_complex_solver(self): @@ -538,7 +536,7 @@ def test_significance_methods( n_rot = 0 if flavour == 'cplx': cplx = True - if flavour == 'varmx': + elif flavour == 'varmx': n_rot = 10 if analysis == 'uni': model = xMCA(self.A) diff --git a/xmca/array.py b/xmca/array.py index 7f41bdc..1f53e83 100755 --- a/xmca/array.py +++ b/xmca/array.py @@ -77,9 +77,8 @@ def __init__(self, *fields): if len(fields) > 2: raise ValueError("Too many fields. Pass 1 or 2 fields.") - if len(fields) == 2: - if fields[0].shape[0] != fields[1].shape[0]: - raise ValueError('''Time dimensions of given fields are different. + if len(fields) == 2 and fields[0].shape[0] != fields[1].shape[0]: + raise ValueError('''Time dimensions of given fields are different. Time series should have same time lengths.''') if not all(isinstance(f, np.ndarray) for f in fields): @@ -118,28 +117,25 @@ def __init__(self, *fields): # set meta information self._analysis = { - 'version' : __version__, - 'is_bivariate' : True if len(self._fields) > 1 else False, - # pre-processing - 'is_normalized' : False, - 'is_coslat_corrected' : False, - 'method' : 'pca', - # Complex solution - 'is_complex' : False, - 'extend' : False, - 'theta_period' : 365, - # Rotated solution - 'is_rotated' : False, - 'n_rot' : 0, - 'power' : 0, - # Truncated solution - 'is_truncated' : False, - 'is_truncated_at' : 0, - 'rank' : 0, - 'total_covariance' : 0.0, - 'total_squared_covariance' : 0.0 + 'version': __version__, + 'is_bivariate': len(self._fields) > 1, + 'is_normalized': False, + 'is_coslat_corrected': False, + 'method': 'pca', + 'is_complex': False, + 'extend': False, + 'theta_period': 365, + 'is_rotated': False, + 'n_rot': 0, + 'power': 0, + 'is_truncated': False, + 'is_truncated_at': 0, + 'rank': 0, + 'total_covariance': 0.0, + 'total_squared_covariance': 0.0, } + self._analysis['method'] = self._get_method_id() def _get_slice(self, input): @@ -221,11 +217,7 @@ def _set_no_nan_idx(self, data: Dict[str, np.ndarray]) -> None: def _remove_nan_cols( self, data: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]: - data_no_nan = {} - for k, field in data.items(): - data_no_nan[k] = remove_nan_cols(field) - - return data_no_nan + return {k: remove_nan_cols(field) for k, field in data.items()} def _reshape_to_2d( self, data: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]: @@ -240,10 +232,7 @@ def _reshape_to_2d( return data_2d def _get_method_id(self): - id = 'pca' - if self._analysis['is_bivariate']: - id = 'mca' - return id + return 'mca' if self._analysis['is_bivariate'] else 'pca' def _get_analysis_path(self, path=None): if path is None: @@ -371,9 +360,7 @@ def _theta_forecast(self, series): model = ThetaModel( series, period=period, deseasonalize=True, use_test=False ).fit() - forecast = model.forecast(steps=steps, theta=20) - - return forecast + return model.forecast(steps=steps, theta=20) def _get_reg_coefs(self, x, y): assert(x.shape[0] == y.shape[0]) @@ -483,9 +470,7 @@ def _svd(self, fields: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]: return Us, Ss, Vts def _get_min_mode(self, n=None, rotated=False): - n_modes = [] - n_modes.append(self._analysis['rank']) - + n_modes = [self._analysis['rank']] if n is not None: n_modes.append(n) @@ -495,9 +480,7 @@ def _get_min_mode(self, n=None, rotated=False): return np.min(n_modes) def _get_max_mode(self, n=None, rotated=False): - n_modes = [] - n_modes.append(self._analysis['rank']) - + n_modes = [self._analysis['rank']] if n is not None: n_modes[0] = n @@ -530,7 +513,7 @@ def solve(self, complexify=False, extend=False, period=1): steps for the exponential to decrease to 1/e. If no extension is selected, this parameter has no effect. Default is 1. ''' - if any([np.isnan(field).all() for field in self._fields.values()]): + if any(np.isnan(field).all() for field in self._fields.values()): raise RuntimeError(''' Fields are empty. Did you forget to load data? ''') @@ -577,9 +560,7 @@ def solve(self, complexify=False, extend=False, period=1): '''SVD failed. NaN entries may be the problem.''' ) - V = {} - V['left'] = VLeft - V['right'] = VRight + V = {'left': VLeft, 'right': VRight} # singular vectors (EOFs) = V self._V = {key: mt[key].conj().T @ V[key] for key in self._keys} @@ -616,11 +597,7 @@ def _get_V(self, n=None, rotated=True): if rotated: max_mode = self._analysis['n_rot'] else: - if isinstance(n, slice): - max_mode = n.stop - else: - max_mode = n - + max_mode = n.stop if isinstance(n, slice) else n keep_modes = self._get_slice(n) try: @@ -649,11 +626,7 @@ def _get_U(self, n=None, rotated=True): if rotated: max_mode = self._analysis['n_rot'] else: - if isinstance(n, slice): - max_mode = n.stop - else: - max_mode = n - + max_mode = n.stop if isinstance(n, slice) else n keep_modes = self._get_slice(n) fields = self._get_X() @@ -771,12 +744,11 @@ def _get_norm(self, n=None, sorted=True): def _get_variance(self, n=None, sorted=True): norm = self._get_norm(n=n, sorted=sorted) - if self._analysis['is_bivariate']: - var = norm['left'] * norm['right'] - else: - var = norm['left']**2 - - return var + return ( + norm['left'] * norm['right'] + if self._analysis['is_bivariate'] + else norm['left'] ** 2 + ) def rotate(self, n_rot, power=1, tol=1e-8): '''Perform Promax rotation on the first `n` EOFs. @@ -823,8 +795,7 @@ def rotate(self, n_rot, power=1, tol=1e-8): L_rot, R, Phi = promax(L, power, maxIter=1000, tol=tol) # calculate variance/reconstruct rotated "singular_values" - norm = {} - norm['left'] = np.linalg.norm(L_rot[:n_vars_left, :], axis=0) + norm = {'left': np.linalg.norm(L_rot[:n_vars_left, :], axis=0)} norm['right'] = np.linalg.norm(L_rot[n_vars_left:, :], axis=0) if not self._analysis['is_bivariate']: norm['right'] = norm['left'] @@ -980,8 +951,7 @@ def scf(self, n=None): ''' variance = self._variance[self._var_idx][:n] - scf = variance**2 / self._analysis['total_squared_covariance'] * 100 - return scf + return variance**2 / self._analysis['total_squared_covariance'] * 100 def explained_variance(self, n=None): '''Return the CF of the first `n` modes. @@ -1002,8 +972,7 @@ def explained_variance(self, n=None): ''' variance = self._get_variance(n=n, sorted=True) - exp_var = variance / self._analysis['total_covariance'] * 100 - return exp_var + return variance / self._analysis['total_covariance'] * 100 def pcs(self, n=None, scaling='None', phase_shift=0, rotated=True): '''Return the first `n` PCs. @@ -1117,11 +1086,7 @@ def spatial_phase(self, n=None, phase_shift=0, rotated=True): ''' eofs = self.eofs(n, phase_shift=phase_shift, rotated=rotated) - phases = {} - for key, eof in eofs.items(): - phases[key] = np.arctan2(eof.imag, eof.real).real - - return phases + return {key: np.arctan2(eof.imag, eof.real).real for key, eof in eofs.items()} def temporal_amplitude(self, n=None, scaling='None', rotated=True): '''Return the temporal amplitude time series for the first `n` PCs. @@ -1179,11 +1144,7 @@ def temporal_phase(self, n=None, phase_shift=0, rotated=True): ''' pcs = self.pcs(n, phase_shift=phase_shift, rotated=rotated) - phases = {} - for key, pc in pcs.items(): - phases[key] = np.arctan2(pc.imag, pc.real).real - - return phases + return {key: np.arctan2(pc.imag, pc.real).real for key, pc in pcs.items()} def homogeneous_patterns(self, n=None, phase_shift=0): pcs = self._get_pcs(n=n, phase_shift=phase_shift) @@ -1590,11 +1551,7 @@ def save_plot( Additional parameters provided to `matplotlib.pyplot.savefig`. ''' - if path is None: - output = 'mode{:}.png'.format(mode) - else: - output = path - + output = 'mode{:}.png'.format(mode) if path is None else path fig, axes = self.plot(mode=mode, **plot_kwargs) fig.subplots_adjust(left=0.06) plt.savefig(output, **save_kwargs) @@ -1636,27 +1593,26 @@ def _create_info_file(self, path): path_output = os.path.join(path, 'info.xmca') - file = open(path_output, 'w+') - file.write(wrap_str(file_header)) - file.write('\n# To load this analysis use:') - file.write('\n# from xmca.xarray import xMCA') - file.write('\n# mca = xMCA()') - file.write('\n# mca.load_analysis(PATH_TO_THIS_FILE)') - file.write('\n') - file.write(sep_line) - file.write(sep_line) - file.write('\n{:<20} : {:<57}'.format('created', now)) - file.write(sep_line) - for key, name in self._field_names.items(): - file.write('\n{:<20} : {:<57}'.format(key, str(name))) - file.write(sep_line) - for key, info in self._analysis.items(): - if key in [ - 'is_bivariate', 'is_complex', 'is_rotated', 'is_truncated' - ]: - file.write(sep_line) - file.write('\n{:<20} : {:<57}'.format(key, str(info))) - file.close() + with open(path_output, 'w+') as file: + file.write(wrap_str(file_header)) + file.write('\n# To load this analysis use:') + file.write('\n# from xmca.xarray import xMCA') + file.write('\n# mca = xMCA()') + file.write('\n# mca.load_analysis(PATH_TO_THIS_FILE)') + file.write('\n') + file.write(sep_line) + file.write(sep_line) + file.write('\n{:<20} : {:<57}'.format('created', now)) + file.write(sep_line) + for key, name in self._field_names.items(): + file.write('\n{:<20} : {:<57}'.format(key, str(name))) + file.write(sep_line) + for key, info in self._analysis.items(): + if key in [ + 'is_bivariate', 'is_complex', 'is_rotated', 'is_truncated' + ]: + file.write(sep_line) + file.write('\n{:<20} : {:<57}'.format(key, str(info))) def _get_file_names(self, format): @@ -1675,14 +1631,13 @@ def _get_file_names(self, format): singular_values = 'singular_values' singular_values = '.'.join([singular_values, format]) - file_names = { + return { 'fields' : fields, 'eofs' : eofs, 'pcs' : pcs, 'singular' : singular_values, 'norm' : norm } - return file_names def _save_data(self, data_array, path, *args, **kwargs): raise NotImplementedError('only works for `xarray`') @@ -1692,26 +1647,24 @@ def _set_analysis(self, key, value): key_type = type(self._analysis[key]) except KeyError: raise KeyError("Key `{}` not found in info file.".format(key)) - if key_type == bool: - self._analysis[key] = (value == 'True') - else: - self._analysis[key] = key_type(value) + self._analysis[key] = ( + (value == 'True') if key_type == bool else key_type(value) + ) def _set_info_from_file(self, path): - info_file = open(path, 'r') - lines = info_file.readlines() - for line in lines: - if (line[0] != '#'): - key = line.split(':')[0] - key = key.rstrip() - if key in ['left', 'right']: - name = line.split(':')[1].strip() - self._field_names[key] = name - if key in self._analysis.keys(): - value = line.split(':')[1].strip() - self._set_analysis(key, value) - info_file.close() + with open(path, 'r') as info_file: + lines = info_file.readlines() + for line in lines: + if (line[0] != '#'): + key = line.split(':')[0] + key = key.rstrip() + if key in ['left', 'right']: + name = line.split(':')[1].strip() + self._field_names[key] = name + if key in self._analysis.keys(): + value = line.split(':')[1].strip() + self._set_analysis(key, value) def rule_n(self, n_runs, n_modes=None): '''Apply *Rule N* by Overland and Preisendorfer, 1982. @@ -1750,10 +1703,8 @@ def rule_n(self, n_runs, n_modes=None): svals = [] - for i in tqdm(range(n_runs)): - data = {} - for k in self._keys: - data[k] = np.random.standard_normal([m[k], n[k]]) + for _ in tqdm(range(n_runs)): + data = {k: np.random.standard_normal([m[k], n[k]]) for k in self._keys} model = MCA(*list(data.values())) model.solve(complexify=complexify) if rotated: @@ -1915,7 +1866,7 @@ def bootstrapping( 'Set `on_right=False`.' ) raise ValueError(msg) from err - elif on_left and on_right: + elif on_left: concat = np.concatenate(list(X_surr.values()), axis=1) concat = block_bootstrap( concat, axis=axis, block_size=block_size, @@ -1923,11 +1874,6 @@ def bootstrapping( ) X_surr['left'] = concat[:, :X_surr['left'].shape[1]] X_surr['right'] = concat[:, X_surr['left'].shape[1]:] - else: - pass - # msg = 'Either `on_left` or `on_right` needs to be True.' - # raise ValueError(msg) - # perform analysis model = MCA(*list(X_surr.values())) model.solve( @@ -1967,11 +1913,7 @@ def load_analysis( ''' self._set_info_from_file(path) - if self._analysis['is_bivariate']: - self._keys = ['left', 'right'] - else: - self._keys = ['left'] - + self._keys = ['left', 'right'] if self._analysis['is_bivariate'] else ['left'] self._set_field_meta(fields) fields = self._reshape_to_2d(fields) self._set_no_nan_idx(fields) diff --git a/xmca/tools/array.py b/xmca/tools/array.py index 8037cad..b44e285 100644 --- a/xmca/tools/array.py +++ b/xmca/tools/array.py @@ -37,9 +37,7 @@ def get_nan_cols(arr: np.ndarray) -> np.ndarray: Index of columns with NaN entries from original data ''' - nan_index = np.isnan(arr).any(axis=0) - - return nan_index + return np.isnan(arr).any(axis=0) def remove_nan_cols(arr: np.ndarray) -> np.ndarray: @@ -57,9 +55,7 @@ def remove_nan_cols(arr: np.ndarray) -> np.ndarray: ''' idx = get_nan_cols(arr) - new_arr = arr[:, ~idx] - - return new_arr + return arr[:, ~idx] def has_nan_time_steps(array): diff --git a/xmca/tools/rotation.py b/xmca/tools/rotation.py index 1333cf3..6b08da1 100755 --- a/xmca/tools/rotation.py +++ b/xmca/tools/rotation.py @@ -49,7 +49,7 @@ def varimax(A, gamma=1, maxIter=1000, tol=1e-8): # seek for rotation matrix based on varimax criteria converged = False - for i in range(maxIter): + for _ in range(maxIter): d_old = d basis = A @ R diff --git a/xmca/tools/xarray.py b/xmca/tools/xarray.py index 8f49973..f0e9d54 100644 --- a/xmca/tools/xarray.py +++ b/xmca/tools/xarray.py @@ -25,9 +25,7 @@ def is_DataArray(data): Input data is of type `DataArray`. ''' - if(isinstance(data, xr.DataArray)): - pass - else: + if not (isinstance(data, xr.DataArray)): raise TypeError("Data format has to be xarray.DatArray.") diff --git a/xmca/xarray.py b/xmca/xarray.py index b32caa1..b02b1e9 100644 --- a/xmca/xarray.py +++ b/xmca/xarray.py @@ -170,12 +170,13 @@ def apply_coslat(self): ''' coords = self._field_coords - weights = {} - for key, coord in coords.items(): - # add small epsilon to assure correct handling of boundaries - # e.g. 90.00001 degrees results in a negative value for sqrt - epsilon = 1e-6 - weights[key] = np.sqrt(np.cos(np.deg2rad(coord['lat'])) + epsilon) + # add small epsilon to assure correct handling of boundaries + # e.g. 90.00001 degrees results in a negative value for sqrt + epsilon = 1e-6 + weights = { + key: np.sqrt(np.cos(np.deg2rad(coord['lat'])) + epsilon) + for key, coord in coords.items() + } if (not self._analysis['is_coslat_corrected']): self.apply_weights(**weights) @@ -967,9 +968,8 @@ def _create_gridspec( map_projs[k1] = {} for k2 in data.keys(): map_projs[k1][k2] = None - if k1 in ['eof', 'phase']: - if k2 in ['left', 'right']: - map_projs[k1][k2] = projection.get(k2, None) + if k1 in ['eof', 'phase'] and k2 in ['left', 'right']: + map_projs[k1][k2] = projection.get(k2, None) # create figure and axes fig = plt.figure(figsize=figsize, dpi=150)