diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3a5bc30..5c725e4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,14 +10,14 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.6", "3.7", "3.8", "3.9"] + python-version: ["3.8", "3.9", "3.10", "3.11"] os: [windows-latest, ubuntu-latest, macos-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 5585c65..9376cc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,38 @@ # Changelog All significant changes to the software will be documented here. -## [0.3.27-dev] - LIVE +## [0.3.30-dev] - LIVE ### TODO - implement different analyte lists for different stages (raw, ratios, mass_fractions), OR improve analyte_checker functionality. This has to propagate throught to filter assignment. +## [0.3.30] - 12/01/2024 + +### Changed + - Changed `sklearn` to `scikit-learn` in requirements.txt to accommodate change in upstream package name + - Moved from setup.py to setup.cfg for package metadata + - Changed version labelling from `__version__` to `VERSION` to match pypi expectations. + + +## [0.3.29] + +More pandas 2.0 'indexing with set' compatibility updates... + +### Changed + - modify dataframe indexing in filt_obj to work with pandas>=2.0.0 + + + +## [0.3.28] + +### Changed + - minor updates to work with pandas>=2.0.0 + - plotting updates to use `.get_subplotspec()` when checking row/col status. + - minor revisions to GitHub Actions scripts + + +## [0.3.27] missed... oops! + ## [0.3.26] ### Changed diff --git a/Supplement/comparison_tools/plots.py b/Supplement/comparison_tools/plots.py index 5f5fffe..1ca0baa 100644 --- a/Supplement/comparison_tools/plots.py +++ b/Supplement/comparison_tools/plots.py @@ -135,13 +135,13 @@ def comparison_plots(df, els=['Mg', 'Sr', 'Ba', 'Al', 'Mn']): ax.plot(xlim, xlim, c='k', ls='dashed', alpha=0.6) for ax in axs[i]: - if ax.is_last_row(): + if ax.get_subplotspec().is_last_row(): hax.set_xlabel('Residual') tax.set_xlabel('Reference User') lax.set_xlabel('Reference User') hax.legend(fontsize=8) - if ax.is_first_row(): + if ax.get_subplotspec().is_first_row(): tax.set_title('Manual Test User', loc='left') lax.set_title('LAtools Test User', loc='left') @@ -232,15 +232,15 @@ def residual_plots(df, rep_stats=None, els=['Mg', 'Sr', 'Ba', 'Al', 'Mn']): if rep_stats is not None: ax.axhspan(-rep_stats[e][0] * 2, rep_stats[e][0] * 2, color=(0,0,0,0.2), zorder=-1) - if not ax.is_first_col(): + if not ax.get_subplotspec().is_first_col(): ax.set_yticklabels([]) - if ax.is_last_row(): + if ax.get_subplotspec().is_last_row(): hax.set_xlabel('Density') tax.set_xlabel('Reference User') lax.set_xlabel('Reference User') - if ax.is_first_row(): + if ax.get_subplotspec().is_first_row(): tax.set_title('Manual Test User', loc='left') lax.set_title('LAtools Test User', loc='left') @@ -374,13 +374,13 @@ def bland_altman_plots(df, rep_stats=None, els=['Mg', 'Sr', 'Ba', 'Al', 'Mn']): for ax in axs[i]: ax.set_ylim(ylim) - if ax.is_first_col(): + if ax.get_subplotspec().is_first_col(): ax.set_ylabel(e + ' ('+ u + ')\nResidual') else: ax.set_ylabel('') ax.set_yticklabels([]) - if ax.is_last_row(): + if ax.get_subplotspec().is_last_row(): tax.set_xlabel('Mean') lax.set_xlabel('Mean') hax.set_xlabel('Residual Density') @@ -388,7 +388,7 @@ def bland_altman_plots(df, rep_stats=None, els=['Mg', 'Sr', 'Ba', 'Al', 'Mn']): else: ax.set_xlabel('') - if ax.is_first_row(): + if ax.get_subplotspec().is_first_row(): tax.set_title('Manual Test User', loc='left') lax.set_title('LAtools Test User', loc='left') hax.set_title('Residuals', loc='left') diff --git a/Supplement/comparison_tools/plots_1sample.py b/Supplement/comparison_tools/plots_1sample.py index 454098b..48e9055 100644 --- a/Supplement/comparison_tools/plots_1sample.py +++ b/Supplement/comparison_tools/plots_1sample.py @@ -81,12 +81,12 @@ def comparison_plots(df, els=['Mg', 'Sr', 'Al', 'Mn', 'Fe', 'Cu', 'Zn', 'B']): lax.plot(xlim, xlim, c='k', ls='dashed', alpha=0.6) for ax in axs[i]: - if ax.is_last_row(): + if ax.get_subplotspec().is_last_row(): hax.set_xlabel('Residual') lax.set_xlabel('Iolite User') hax.legend(fontsize=8) - if ax.is_first_row(): + if ax.get_subplotspec().is_first_row(): lax.set_title('LAtools', loc='left') fig.tight_layout() @@ -164,14 +164,14 @@ def residual_plots(df, rep_stats=None, els=['Mg', 'Sr', 'Al', 'Mn', 'Fe', 'Cu', if rep_stats is not None: ax.axhspan(-rep_stats[e][0] * 2, rep_stats[e][0] * 2, color=(0,0,0,0.2), zorder=-1) - if not ax.is_first_col(): + if not ax.get_subplotspec().is_first_col(): ax.set_yticklabels([]) - if ax.is_last_row(): + if ax.get_subplotspec().is_last_row(): hax.set_xlabel('Density') lax.set_xlabel('Iolite User') - if ax.is_first_row(): + if ax.get_subplotspec().is_first_row(): lax.set_title('LAtools', loc='left') fig.tight_layout() diff --git a/docs/source/conf.py b/docs/source/conf.py index 327017b..ab7f075 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -77,9 +77,9 @@ # built documents. # # The short X.Y version. -version = latools.__version__ +version = latools.VERSION # The full version, including alpha/beta/rc tags. -release = latools.__version__ +release = latools.VERSION # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/latools/D_obj.py b/latools/D_obj.py index 774bd56..7879f82 100644 --- a/latools/D_obj.py +++ b/latools/D_obj.py @@ -1550,13 +1550,13 @@ def crossplot(self, analytes=None, bins=25, lognorm=True, filt=True, colourful=T ax.xaxis.set_visible(False) ax.yaxis.set_visible(False) - if ax.is_first_col(): + if ax.get_subplotspec().is_first_col(): ax.yaxis.set_ticks_position('left') - if ax.is_last_col(): + if ax.get_subplotspec().is_last_col(): ax.yaxis.set_ticks_position('right') - if ax.is_first_row(): + if ax.get_subplotspec().is_first_row(): ax.xaxis.set_ticks_position('top') - if ax.is_last_row(): + if ax.get_subplotspec().is_last_row(): ax.xaxis.set_ticks_position('bottom') # set up colour scales @@ -1664,13 +1664,13 @@ def crossplot_filters(self, filter_string, analytes=None): ax.xaxis.set_visible(False) ax.yaxis.set_visible(False) - if ax.is_first_col(): + if ax.get_subplotspec().is_first_col(): ax.yaxis.set_ticks_position('left') - if ax.is_last_col(): + if ax.get_subplotspec().is_last_col(): ax.yaxis.set_ticks_position('right') - if ax.is_first_row(): + if ax.get_subplotspec().is_first_row(): ax.xaxis.set_ticks_position('top') - if ax.is_last_row(): + if ax.get_subplotspec().is_last_row(): ax.xaxis.set_ticks_position('bottom') # isolate nominal_values for all analytes diff --git a/latools/__init__.py b/latools/__init__.py index 347791e..b64e39d 100644 --- a/latools/__init__.py +++ b/latools/__init__.py @@ -17,7 +17,7 @@ from .helpers import chemistry from . import preprocessing -__version__ = '0.3.26' +VERSION = '0.3.30' def cite(output='text'): """ diff --git a/latools/filtering/filt_obj.py b/latools/filtering/filt_obj.py index 990637a..72dd945 100644 --- a/latools/filtering/filt_obj.py +++ b/latools/filtering/filt_obj.py @@ -117,7 +117,7 @@ def check_analytes(self, analytes=None, single=False, allow_multiples=False): return analytes[0] return valid.pop() else: - return valid + return list(valid) def add(self, name, filt, info='', params=(), setn=None): """ @@ -286,9 +286,9 @@ def make_analyte(self, analyte): boolean filter """ analyte = self.check_analytes(analyte) - + key = [] - for n, f in self.filter_table[analyte].index[self.filter_table[analyte].any(1)]: + for n, f in self.filter_table[analyte].index[self.filter_table[analyte].any(axis=1)]: key.append(f'{n}:{f}') return self.make_fromkey('&'.join(key)) diff --git a/latools/helpers/plot.py b/latools/helpers/plot.py index cdc6092..01c1dc7 100644 --- a/latools/helpers/plot.py +++ b/latools/helpers/plot.py @@ -322,13 +322,13 @@ def crossplot(dat, keys=None, lognorm=True, bins=25, figsize=(12, 12), ax.xaxis.set_visible(False) ax.yaxis.set_visible(False) - if ax.is_first_col(): + if ax.get_subplotspec().is_first_col(): ax.yaxis.set_ticks_position('left') - if ax.is_last_col(): + if ax.get_subplotspec().is_last_col(): ax.yaxis.set_ticks_position('right') - if ax.is_first_row(): + if ax.get_subplotspec().is_first_row(): ax.xaxis.set_ticks_position('top') - if ax.is_last_row(): + if ax.get_subplotspec().is_last_row(): ax.xaxis.set_ticks_position('bottom') # set up colour scales @@ -473,7 +473,7 @@ def histograms(dat, keys=None, bins=25, logy=False, cmap=None, ncol=4): ax.set_ylim(1, ax.get_ylim()[1]) - if ax.is_first_col(): + if ax.get_subplotspec().is_first_col(): ax.set_ylabel(ylab) ax.set_yticklabels([]) @@ -701,11 +701,11 @@ def autorange_plot(t, sig, gwin=7, swin=None, win=30, ax.text(.95, .95, '{} (off)'.format(n), ha='right', va='top', transform=ax.transAxes) - if ax.is_last_row(): + if ax.get_subplotspec().is_last_row(): ax.set_xlabel('Time (s)') - if ax.is_first_col(): + if ax.get_subplotspec().is_first_col(): ax.set_ylabel('Gradient (x)') - if ax.is_last_col(): + if ax.get_subplotspec().is_last_col(): tax.set_ylabel('Signal (line)') if fail: diff --git a/latools/latools.py b/latools/latools.py index 19c774d..6e7d3a8 100644 --- a/latools/latools.py +++ b/latools/latools.py @@ -23,6 +23,7 @@ import uncertainties as unc import uncertainties.unumpy as un +from uncertainties.unumpy import nominal_values, std_devs from sklearn.preprocessing import minmax_scale, scale from sklearn.cluster import KMeans @@ -42,7 +43,7 @@ from .helpers import logging from .helpers.logging import _log from .helpers.config import read_configuration, config_locator -from .helpers.stat_fns import * +from .helpers.stat_fns import gauss_weighted_stats, R2calc, stderr, un_interp1d, H15_mean, H15_std, H15_se from .helpers import utils from .helpers import srm as srms from .helpers.progressbars import progressbar @@ -990,10 +991,9 @@ def bkg_calc_weightedmean(self, analytes=None, weight_fwhm=600, * 'calibrated': ratio data calibrated to standards, created by self.calibrate. """ if analytes is None: - analytes = self.analytes self.bkg = Bunch() - elif isinstance(analytes, str): - analytes = [analytes] + + analytes = self._analyte_checker(analytes=analytes) self.get_background(n_min=n_min, n_max=n_max, bkg_filter=bkg_filter, @@ -1014,9 +1014,9 @@ def bkg_calc_weightedmean(self, analytes=None, weight_fwhm=600, self.bkg['calc']['uTime'] = bkg_t # TODO : calculation then dict assignment is clumsy... - mean, std, stderr = gauss_weighted_stats(self.bkg['raw'].uTime, - self.bkg['raw'].loc[:, analytes].values, - self.bkg['calc']['uTime'], + mean, std, stderr = gauss_weighted_stats(x=self.bkg['raw'].uTime.values, + yarray=self.bkg['raw'].loc[:, list(analytes)].values, + x_new=self.bkg['calc']['uTime'], fwhm=weight_fwhm) self.bkg_interps = {} @@ -1073,10 +1073,9 @@ def bkg_calc_interp1d(self, analytes=None, kind=1, n_min=10, n_max=None, cstep=3 * 'calibrated': ratio data calibrated to standards, created by self.calibrate. """ if analytes is None: - analytes = self.analytes self.bkg = Bunch() - elif isinstance(analytes, str): - analytes = [analytes] + + analytes = self._analyte_checker(analytes=analytes) self.get_background(n_min=n_min, n_max=n_max, bkg_filter=bkg_filter, @@ -1585,12 +1584,12 @@ def srm_id_auto(self, srms_used=['NIST610', 'NIST612', 'NIST614'], analytes=None analytes = list(analytes) # get and scale mean srm values for all analytes - srmid = self.srmtab.loc[:, idx[analytes, 'mean']] + srmid = self.srmtab.loc[:, idx[list(analytes), 'mean']] _srmid = scale(np.log(srmid)) srm_labels = srmid.index.values # get and scale measured srm values for all analytes - stdid = self.stdtab.loc[:, idx[analytes, 'mean']] + stdid = self.stdtab.loc[:, idx[list(analytes), 'mean']] _stdid = scale(np.log(stdid)) _stdid[np.isnan(_stdid)] = -12 @@ -1615,7 +1614,7 @@ def srm_build_calib_table(self): caltab = self.stdtab.reset_index() caltab.set_index(['gTime', 'uTime'], inplace=True) levels = ['meas_' + c if c != '' else c for c in caltab.columns.levels[1]] - caltab.columns.set_levels(levels, 1, inplace=True) + caltab.columns = caltab.columns.set_levels(levels, level=1) for a in self.analyte_ratios: caltab.loc[:, (a, 'srm_mean')] = self.srmtab.loc[caltab.SRM, (a, 'mean')].values @@ -1685,7 +1684,7 @@ def calibrate(self, analytes=None, drift_correct=True, # make container for calibration params gTime = np.asanyarray(self.caltab.index.levels[0]) if not hasattr(self, 'calib_params'): - self.calib_params = pd.DataFrame(columns=pd.MultiIndex.from_product([analytes, ['m']]), + self.calib_params = pd.DataFrame(columns=pd.MultiIndex.from_product([list(analytes), ['m']]), index=gTime) if zero_intercept: @@ -1757,8 +1756,8 @@ def calibrate(self, analytes=None, drift_correct=True, maxuT = np.max([d.uTime.max() for d in self.data.values()]) # calculate max uTime self.calib_params.loc[maxuT, :] = self.calib_params.loc[self.calib_params.index.max(), :] # sort indices for slice access - self.calib_params.sort_index(1, inplace=True) - self.calib_params.sort_index(0, inplace=True) + self.calib_params.sort_index(axis=1, inplace=True) + self.calib_params.sort_index(axis=0, inplace=True) # calculcate interpolators for applying calibrations self.calib_ps = Bunch() @@ -3167,7 +3166,7 @@ def gradient_histogram(self, analytes=None, win=15, filt=False, bins=None, sampl ax.axvline(0, ls='dashed', lw=1, c=(0,0,0,0.7)) ax.set_title(a, loc='left') - if ax.is_first_col(): + if ax.get_subplotspec().is_first_col(): ax.set_ylabel('N') ax.set_xlabel(u + '/s') @@ -3399,7 +3398,7 @@ def filter_effect(self, analytes=None, stats=['mean', 'std'], filt=True): comp = comp.join(pd.concat(rats, 1)) comp.sort_index(1, inplace=True) - return comp.loc[:, (pd.IndexSlice[:], pd.IndexSlice[analytes])] + return comp.loc[:, (pd.IndexSlice[:], pd.IndexSlice[list(analytes)])] def crossplot_filters(self, filter_string, analytes=None, samples=None, subset=None, filt=None): @@ -3442,13 +3441,13 @@ def crossplot_filters(self, filter_string, analytes=None, ax.xaxis.set_visible(False) ax.yaxis.set_visible(False) - if ax.is_first_col(): + if ax.get_subplotspec().is_first_col(): ax.yaxis.set_ticks_position('left') - if ax.is_last_col(): + if ax.get_subplotspec().is_last_col(): ax.yaxis.set_ticks_position('right') - if ax.is_first_row(): + if ax.get_subplotspec().is_first_row(): ax.xaxis.set_ticks_position('top') - if ax.is_last_row(): + if ax.get_subplotspec().is_last_row(): ax.xaxis.set_ticks_position('bottom') cmlist = ['Blues', 'BuGn', 'BuPu', 'GnBu', @@ -3561,7 +3560,7 @@ def trace_plots(self, analytes=None, samples=None, ranges=False, if outdir is None: outdir = os.path.join(self.report_dir, focus_stage) if not os.path.isdir(outdir): - os.mkdir(outdir) + os.makedirs(outdir) analytes = self.analytes_sorted(analytes, focus_stage=focus_stage) @@ -3586,10 +3585,10 @@ def trace_plots(self, analytes=None, samples=None, ranges=False, # ax.axvspan(l, u, color='r', alpha=0.1) # for l, u in s.bkgrng: # ax.axvspan(l, u, color='k', alpha=0.1) - f.savefig(os.path.join(outdir, s + '_traces.pdf')) + with open(os.path.join(outdir, s + '_traces.pdf'), 'wb') as file: + f.savefig(file) # TODO: on older(?) computers raises # 'OSError: [Errno 24] Too many open files' - plt.close(f) prog.update() return @@ -3880,7 +3879,7 @@ def sample_stats(self, analytes=None, filt=True, with self.pbar.set(total=len(samples), desc='Calculating Stats') as prog: for s in samples: - self.data[s].sample_stats(analytes, filt=filt, + self.data[s].sample_stats(analytes, filt=1616, stat_fns=stat_fns, eachtrace=eachtrace, focus_stage=focus_stage) @@ -4030,13 +4029,13 @@ def getstats(self, filename=None, samples=None, subset=None, ablation_time=False nms = np.array([nm] * reps.size) # make sub - dataframe stdf = pd.DataFrame(self.stats[nm][s].T, - columns=self.stats[nm]['analytes'], + columns=list(self.stats[nm]['analytes']), index=[ss, nms, reps]) stdf.index.set_names(['statistic', 'sample', 'rep'], inplace=True) else: stdf = pd.DataFrame(self.stats[nm][s], - index=self.stats[nm]['analytes'], + index=list(self.stats[nm]['analytes']), columns=[[s], [nm]]).T stdf.index.set_names(['statistic', 'sample'], diff --git a/requirements.txt b/requirements.txt index 813cc91..fbeb189 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ tqdm>=4.14 matplotlib>=2.1 scikit-learn>=0.19 ipython -tqdm \ No newline at end of file +tqdm +setuptools \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..d504583 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,30 @@ +[metadata] +name = latools +version = attr: latools.VERSION +author = Oscar Branson +author_email = ob266@cam.ac.uk +description = Tools for LA-ICPMS data analysis. +long_description = file: README.md +long_description_content_type = text/markdown +url = https://github.com/oscarbranson/latools +license = MIT +project_urls = + PalaeoCarb Project = https://github.com/PalaeoCarb +classifiers = + Programming Language :: Python :: 3 + Intended Audience :: Science/Research + Topic :: Scientific/Engineering + Programming Language :: Python :: 3 + +[options] +python_requires = >3.6 +install_requires = + numpy + pandas + matplotlib + uncertainties + scikit-learn + scipy + Ipython + setuptools +include_package_data = True diff --git a/setup.py b/setup.py index a732e82..45642b9 100644 --- a/setup.py +++ b/setup.py @@ -1,45 +1,3 @@ from setuptools import setup, find_packages -# bcause TravisCI was being a jerK -try: - from latools import __version__ -except: - __version__ = "version_missing" - -with open("README.md", "r") as fh: - long_description = fh.read() - -setup(name='latools', - version=__version__, - description='Tools for LA-ICPMS data analysis.', - long_description=long_description, - long_description_content_type="text/markdown", - url='https://github.com/oscarbranson/latools', - author='Oscar Branson', - author_email='oscarbranson@gmail.com', - license='MIT', - packages=find_packages(), - classifiers=['Development Status :: 4 - Beta', - 'Intended Audience :: Science/Research', - 'Topic :: Scientific/Engineering', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 3', - ], - python_requires='>3.6', - install_requires=['numpy', - 'pandas', - 'matplotlib', - 'uncertainties', - 'sklearn', - 'scipy', - 'Ipython', - 'configparser', - 'tqdm' - ], - package_data={ - 'latools': ['latools.cfg', - 'resources/*', - 'resources/data_formats/*', - 'resources/test_data/*'], - }, - zip_safe=False) +setup(packages=find_packages()) diff --git a/tests/test_beginnersGuide.py b/tests/test_beginnersGuide.py index 1589370..9083057 100644 --- a/tests/test_beginnersGuide.py +++ b/tests/test_beginnersGuide.py @@ -8,11 +8,11 @@ class test_docscode(unittest.TestCase): print('\n\nTesting Beginners Guide code examples.') if not os.path.exists('./latools_demo_tmp'): os.mkdir('./latools_demo_tmp') - os.chdir('latools_demo_tmp') + # os.chdir('latools_demo_tmp') - la.get_example_data('./latools_demo_tmp') + la.get_example_data('./latools_demo_tmp/latools_demo_tmp') - eg = la.analyse(data_path='./latools_demo_tmp', # the location of your data + eg = la.analyse(data_path='./latools_demo_tmp/latools_demo_tmp', # the location of your data config='UCD-AGILENT', # the configuration to use internal_standard='Ca43', # the internal standard in your analyses srm_identifier='STD') # the text that identifies which files contain standard reference materials @@ -62,8 +62,8 @@ class test_docscode(unittest.TestCase): # OK To change line numbers after here. # clean up - os.chdir('..') - shutil.rmtree('latools_demo_tmp') + # os.chdir('..') + shutil.rmtree('./latools_demo_tmp/latools_demo_tmp') print('\nDone.\n\n')