diff --git a/.github/workflows/docstest.yml b/.github/workflows/docstest.yml index fe104125..770f0600 100644 --- a/.github/workflows/docstest.yml +++ b/.github/workflows/docstest.yml @@ -14,9 +14,10 @@ jobs: name: Build Docs runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: os: [ubuntu-latest] - python-version: [3.8, 3.9, "3.10", "3.11"] + python-version: [3.9, "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index 43f68e61..f210bc32 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -17,7 +17,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: [3.8, 3.9, "3.10", "3.11", "3.12"] + python-version: [3.9, "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 diff --git a/.gitignore b/.gitignore index d9212868..7c0cafc8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ __pycache__/ *.py[cod] *$py.class +.DS_Store # C extensions *.so diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..ad71f03e --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,97 @@ +cff-version: 1.2.0 +title: 'morganjwilliams/pyrolite: 0.3.5.post0' +message: Please cite the following works when using this software. +type: software +authors: + - family-names: Williams + given-names: Morgan + orcid: 'https://orcid.org/0000-0003-4764-9555' + - family-names: Buckle + given-names: Tom + - family-names: Myhill + given-names: Bob + orcid: 'https://orcid.org/0000-0001-9489-5236' + - family-names: Shi + given-names: Sarah + orcid: 'https://orcid.org/0009-0004-8997-7287' + - family-names: Nathwani + given-names: Chetan + orcid: 'https://orcid.org/0000-0003-1208-9041' + - family-names: Piette Lauziere + given-names: Nicolas + orcid: 'https://orcid.org/0000-0003-2812-0357' + - family-names: Lexa + given-names: Ondrej + orcid: 'https://orcid.org/0000-0003-4616-9154' + - family-names: Schoneveld + given-names: Louise + orcid: 'https://orcid.org/0000-0002-9324-1676' + - family-names: Afonso Rodrigues + given-names: Angela + orcid: 'https://orcid.org/0000-0003-3402-9621' + - family-names: Mues + given-names: Malte + orcid: 'https://orcid.org/0000-0002-6291-9886' + - family-names: Bentley + given-names: Martin + - family-names: Gentilini + given-names: Alessandro + - family-names: Scott + given-names: Richard +identifiers: + - type: doi + value: 10.5281/ZENODO.2545106 + - type: url + value: 'https://zenodo.org/doi/10.5281/zenodo.2545106' +url: 'https://zenodo.org/doi/10.5281/zenodo.2545106' +version: 0.3.5.post0 +date-released: '2024-02-28' +doi: 10.5281/ZENODO.2545106 +preferred-citation: + authors: + - family-names: Williams + given-names: Morgan + orcid: 'https://orcid.org/0000-0003-4764-9555' + - family-names: Schoneveld + given-names: Louise + orcid: 'https://orcid.org/0000-0002-9324-1676' + - family-names: Mao + given-names: Ya-Jing + orcid: 'https://orcid.org/0000-0002-2725-2158' + - family-names: Klump + given-names: Jens + occid: 'https://orcid.org/0000-0001-5911-6022' + - family-names: Gosses + given-names: Justin + orcid: 'https://orcid.org/0000-0002-5351-7295' + - family-names: Dalton + given-names: Hayden + orcid: 'https://orcid.org/0000-0003-2114-9894' + - family-names: Bath + given-names: Adam + orcid: 'https://orcid.org/0000-0003-0882-0807' + - family-names: Barnes + given-names: Steve + orcid: 'https://orcid.org/0000-0002-4912-9177' + doi: 10.21105/joss.02314 + identifiers: + - type: doi + value: 10.21105/joss.02314 + - type: url + value: 'http://dx.doi.org/10.21105/joss.02314' + - type: other + value: 'urn:issn:2475-9066' + title: 'pyrolite: Python for geochemistry' + url: 'http://dx.doi.org/10.21105/joss.02314' + database: Crossref + date-published: '2020-06-09' + year: 2020 + month: 6 + issn: 2475-9066 + issue: '50' + journal: Journal of Open Source Software + publisher: + name: The Open Journal + start: '2314' + type: article + volume: '5' diff --git a/docs/source/dev/changelog.rst b/docs/source/dev/changelog.rst index d0e798da..7c53bda2 100644 --- a/docs/source/dev/changelog.rst +++ b/docs/source/dev/changelog.rst @@ -10,6 +10,18 @@ All notable changes to this project will be documented here. If you're keen to check something out before its released, you can use a `development install `__ . +`0.3.6`_ +---------- + +* **PR Merged**: `Sarah Shi `__ contributed a PR to + better handle values below zero during compositional renormalisation (to 1 or 100%; + `#104 `__). + The functions :func:`pyrolite.comp.codata.close` and :func:`pyrolite.comp.codata.renormalise` + will now warn where values below zero exist, and replace these with `numpy.nan`. +* Added a `CITATION.cff` file to the repository. +* Various administrative changes (formatting, linting, meta-file management), minor bugfixes + and addressing various deprecation/other warnings. + `0.3.5`_ ---------- @@ -26,8 +38,8 @@ All notable changes to this project will be documented here. allow series and dataframes with `Pm` data to be used with the `pyrolite.pyrochem` REE accessors (i.e., the column will not be dropped if it exists; `#100 `__). - This PR aslo included an update for the `pyrolite.util.lambdas` functions to be more - flexible in terms of access, including as indvidual series (allowing performant + This PR also included an update for the `pyrolite.util.lambdas` functions to be more + flexible in terms of access, including as individual series (allowing performant usage with `pandarallel `__). * Updates to `pyrolite.pyrochem` accessors to be more flexible, generally allowing usage with both DataFrame and Series objects (e.g. `df.pyrochem.REE`, @@ -41,13 +53,13 @@ All notable changes to this project will be documented here. (`#91 `__), update the TAS field names in the JSON template (`#92 `__), - improve the formatting and scaling of TAS diagrams. + improve the formatting and scaling of TAS diagrams (`#93 `__), add an option to add field labels in the visual centre of polygons (rather than the 'centroid'; `#94 `__), update some usage of :mod:`matplotlib` (`#96 `__), - and allow selective plotting of indiviudal fields when adding a classification + and allow selective plotting of individual fields when adding a classification diagram to axes (`#98 `__). @@ -1398,8 +1410,9 @@ All notable changes to this project will be documented here. `GitHub `__ for reference, but were :code:`alpha` versions which were never considered stable. -.. _Development: https://github.com/morganjwilliams/pyrolite/compare/0.3.5...develop -.. _0.3.5: https://github.com/morganjwilliams/pyrolite/compare/0.3.4...0.3.5 +.. _Development: https://github.com/morganjwilliams/pyrolite/compare/0.3.6...develop +.. _0.3.6: https://github.com/morganjwilliams/pyrolite/compare/0.3.5.post0...0.3.6 +.. _0.3.5: https://github.com/morganjwilliams/pyrolite/compare/0.3.4...0.3.5.post0 .. _0.3.4: https://github.com/morganjwilliams/pyrolite/compare/0.3.3...0.3.4 .. _0.3.3: https://github.com/morganjwilliams/pyrolite/compare/0.3.2...0.3.3 .. _0.3.2: https://github.com/morganjwilliams/pyrolite/compare/0.3.1...0.3.2 diff --git a/docs/source/dev/development.rst b/docs/source/dev/development.rst index 18caad40..e963faf1 100644 --- a/docs/source/dev/development.rst +++ b/docs/source/dev/development.rst @@ -5,7 +5,7 @@ Development History and Planning -------------------------------- * `Changelog `__ -* `Future `__ +* `Roadmap `__ Contributing diff --git a/docs/source/dev/future.rst b/docs/source/dev/roadmap.rst similarity index 99% rename from docs/source/dev/future.rst rename to docs/source/dev/roadmap.rst index 930e9287..a92a3962 100644 --- a/docs/source/dev/future.rst +++ b/docs/source/dev/roadmap.rst @@ -1,4 +1,4 @@ -Future +Roadmap ======== This page details some of the under-development and planned features for @@ -63,7 +63,6 @@ planned be integrated over the longer term. * assessing uncertainties - Governance and Documentation ------------------------------ diff --git a/docs/source/gallery/tutorials/templates.py b/docs/source/gallery/tutorials/templates.py new file mode 100644 index 00000000..c3f41608 --- /dev/null +++ b/docs/source/gallery/tutorials/templates.py @@ -0,0 +1,13 @@ +""" +Creating Classification Diagrams +================================== + +* Create JSON files +* Add util.classification +* Add plot.templates file + +""" +####################################################################################### +# First let's pull in a simple dataset to use throughout these examples: +# +from pyrolite.util.synthetic import normal_frame diff --git a/docs/source/index.rst b/docs/source/index.rst index 470bfe0d..301f0cd1 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -17,7 +17,7 @@ and numerous auxiliary utilities. - There's also a quick `installation guide <./installation.html>`__, a list of `recent changes <./dev/changelog.html>`__ and some notes on - where the project is heading in the near `future <./dev/future.html>`__. + where the project is heading in the `roadmap <./dev/roadmap.html>`__. - If you're interested in `contributing to the project <./dev/contributing.html>`__, there are many potential avenues, whether you're experienced with python or not. @@ -73,7 +73,7 @@ make use of your geochemical data to build and test geological models. dev/development dev/changelog - dev/future + dev/roadmap dev/conduct dev/contributing dev/contributors diff --git a/environment.yml b/environment.yml index 3714637f..7f98c8db 100644 --- a/environment.yml +++ b/environment.yml @@ -5,7 +5,7 @@ channels: - defaults dependencies: - - python=3.10 + - python=3.11 # pandas and scientific python - numpy - numpydoc diff --git a/pyproject.toml b/pyproject.toml index 72d8bb12..93acc6bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dependencies = [ "numpydoc", "tinydb>4.1", # >4.1 required for read-only access mode for JSON storage "periodictable", - "matplotlib", + "matplotlib<3.9", "mpltern>=0.4.0", "scipy>=1.2", # uses scipy.optimize.Bounds, added around 1.2 "sympy>=1.7", @@ -39,17 +39,11 @@ dependencies = [ "requests", # used by alphaMELTS utilities, util.web ] -[tool.setuptools.packages] -find = { exclude = ['test*', "docs*"] } # "**/__pycache__/*" -# include = ["Aitchison/*.py"], - -[tool.setuptools.dynamic] -readme = { file = "README.md", content-type = "text/markdown" } - [project.urls] -"Issue tracker" = "https://github.com/morganjwilliams/pyrolite/issues" -"Documentation" = "https://pyrolite.readthedocs.io/" -"Code" = "https://github.com/morganjwilliams/pyrolite" +Issues = "https://github.com/morganjwilliams/pyrolite/issues" +Documentation = "https://pyrolite.readthedocs.io/" +Repository = "https://github.com/morganjwilliams/pyrolite" +Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md" [project.optional-dependencies] docs = [ @@ -78,8 +72,12 @@ dev = [ "build", ] -[metadata] -url = "https://github.com/morganjwilliams/pyrolite" +[tool.setuptools.packages] +find = { exclude = ['test*', "docs*"] } # "**/__pycache__/*" +# include = ["Aitchison/*.py"], + +[tool.setuptools.dynamic] +readme = { file = "README.md", content-type = "text/markdown" } [tool.coverage.run] relative_files = true @@ -103,6 +101,6 @@ parentdir_prefix = "pyrolite-" [tool.pytest.ini_options] python_files = "*.py" -addopts = "--cov=pyrolite --cov-report html --cov-report xml --cov-report term-missing --cov-config .coveragerc" +addopts = "--cov=pyrolite --cov-report html --cov-report xml --cov-report term-missing" testpaths = ["test"] pythonpath = ["."] diff --git a/pyrolite/comp/aggregate.py b/pyrolite/comp/aggregate.py index 54f90b1b..8abb7285 100644 --- a/pyrolite/comp/aggregate.py +++ b/pyrolite/comp/aggregate.py @@ -233,7 +233,7 @@ def np_cross_ratios(X: np.ndarray, debug=False): diags = ratios[:, np.arange(dims), np.arange(dims)] # check all diags are 1. assert np.allclose(diags, 1.0) - except: + except AssertionError: # check all diags are 1. or nan assert np.allclose(diags[~np.isnan(diags)], 1.0) diff --git a/pyrolite/comp/codata.py b/pyrolite/comp/codata.py index da66632e..e3c87a0c 100644 --- a/pyrolite/comp/codata.py +++ b/pyrolite/comp/codata.py @@ -1,3 +1,5 @@ +import warnings + import numpy as np import pandas as pd import scipy.special @@ -34,15 +36,24 @@ def close(X: np.ndarray, sumf=np.sum): Notes ------ - * Does not check for non-positive entries. + Checks for non-positive entries and replaces zeros with NaN values. """ + if np.any(X <= 0): + warnings.warn( + "Non-positive entries found. Closure operation assumes all positive entries.", + UserWarning, + ) + if X.ndim == 2: - C = np.array(sumf(X, axis=1))[:, np.newaxis] + C = np.array(sumf(X, axis=1), dtype=float)[:, np.newaxis] else: - C = np.array(sumf(X, axis=0)) + C = np.array(sumf(X), dtype=float) + # Replace zero sums with NaN to prevent division by zero C[np.isclose(C, 0)] = np.nan + + # Return the array closed to sum to 1 return np.divide(X, C) @@ -66,16 +77,31 @@ def renormalise(df: pd.DataFrame, components: list = [], scale=100.0): :class:`pandas.DataFrame` Renormalized dataframe. """ + dfc = df.copy(deep=True) if components: - cmpnts = [c for c in components if c in dfc.columns] - dfc.loc[:, cmpnts] = scale * dfc.loc[:, cmpnts].divide( - dfc.loc[:, cmpnts].sum(axis=1).replace(0, np.nan), axis=0 + if not all(col in dfc.columns for col in components): + raise ValueError("Not all specified components exist in the DataFrame.") + dfc = dfc[components] + + if (dfc <= 0).any().any(): + warnings.warn( + "Non-positive entries found in specified components. " + "Negative values have been replaced with NaN. " + "Renormalisation assumes all positive entries.", + UserWarning, ) - return dfc - else: - dfc = dfc.divide(dfc.sum(axis=1).replace(0, 100.0), axis=0) * scale - return dfc + + # Replace negative values with NaN + dfc[dfc < 0] = np.nan + + # Renormalise all columns if no components are specified + sum_rows = dfc.sum(axis=1) + # Handle division by zero by replacing zeros with NaN + sum_rows.replace(0, np.nan, inplace=True) + dfc = dfc.divide(sum_rows, axis=0) * scale + + return dfc def ALR(X: np.ndarray, ind: int = -1, null_col=False): @@ -329,9 +355,11 @@ def get_ALR_labels(df, mode="simple", ind=-1, **kwargs): names = [ r"{} / {}".format( - c - if c not in __sympy_protected_variables__ - else __sympy_protected_variables__[c], + ( + c + if c not in __sympy_protected_variables__ + else __sympy_protected_variables__[c] + ), df.columns[ind], ) for c in df.columns @@ -381,9 +409,11 @@ def get_CLR_labels(df, mode="simple", **kwargs): names = [ r"{} / γ".format( - c - if c not in __sympy_protected_variables__ - else __sympy_protected_variables__[c], + ( + c + if c not in __sympy_protected_variables__ + else __sympy_protected_variables__[c] + ), ) for c in df.columns ] @@ -444,9 +474,11 @@ def get_ILR_labels(df, mode="latex", **kwargs): # sub in Phi (the CLR normalisation variable) names = [ r"{} / γ".format( - c - if c not in __sympy_protected_variables__ - else __sympy_protected_variables__[c], + ( + c + if c not in __sympy_protected_variables__ + else __sympy_protected_variables__[c] + ), ) for c in df.columns ] @@ -644,7 +676,7 @@ def inverse_sphere(θ): if ix == p: S = 1.0 else: - S = np.product(sinθ[:, ix:], axis=1) + S = np.prod(sinθ[:, ix:], axis=1) y[:, ix] = C * S ys = y**2 diff --git a/pyrolite/geochem/alteration.py b/pyrolite/geochem/alteration.py index f6b6f922..e6795384 100644 --- a/pyrolite/geochem/alteration.py +++ b/pyrolite/geochem/alteration.py @@ -1,6 +1,7 @@ """ Functions for calcuating indexes of chemical alteration. """ + import pandas as pd from ..util.log import Handle diff --git a/pyrolite/geochem/ind.py b/pyrolite/geochem/ind.py index 29d4fdc6..6243d852 100644 --- a/pyrolite/geochem/ind.py +++ b/pyrolite/geochem/ind.py @@ -6,6 +6,7 @@ ------ * Incompatibility indexes for spider plot ordering. """ + import re import numpy as np @@ -254,9 +255,11 @@ def simple_oxides(cation, output="string"): # for 3.6+, could use f'{cation}{1}O{c//2}', f'{cation}{2}O{c}' oxides = [ - str(cation) + str(1) + "O" + str(c // 2) - if not c % 2 - else str(cation) + str(2) + "O" + str(c) + ( + str(cation) + str(1) + "O" + str(c // 2) + if not c % 2 + else str(cation) + str(2) + "O" + str(c) + ) for c in ions ] oxides = [pt.formula(ox) for ox in oxides] @@ -291,7 +294,7 @@ def get_cations(component: str, exclude=[], total_suffix="T"): exclude += ["O"] atms = pt.formula(component).atoms - cations = [el for el in atms.keys() if not el.__str__() in exclude] + cations = [el for el in atms.keys() if el.__str__() not in exclude] return cations @@ -398,7 +401,7 @@ def get_ionic_radii( variant=[], source="shannon", pauling=True, - **kwargs + **kwargs, ): """ Function to obtain ionic radii for a given ion and coordination [#ref_1]_ @@ -464,7 +467,7 @@ def get_ionic_radii( variant=variant, source=source, pauling=pauling, - **kwargs + **kwargs, ) for e in element ] diff --git a/pyrolite/geochem/norm.py b/pyrolite/geochem/norm.py index d2ea359e..adf898b4 100644 --- a/pyrolite/geochem/norm.py +++ b/pyrolite/geochem/norm.py @@ -1,6 +1,7 @@ """ Reference compostitions and compositional normalisation. """ + import json from pathlib import Path diff --git a/pyrolite/geochem/parse.py b/pyrolite/geochem/parse.py index e33b3492..26eccffc 100644 --- a/pyrolite/geochem/parse.py +++ b/pyrolite/geochem/parse.py @@ -18,7 +18,7 @@ logger = Handle(__name__) -def is_isotoperatio(s): +def is_isotoperatio(s, require_split=False, split_on=r"[\s_]+"): """ Check if text is plausibly an isotope ratio. @@ -42,7 +42,7 @@ def is_isotoperatio(s): return False -def repr_isotope_ratio(isotope_ratio): +def repr_isotope_ratio(text): """ Format an isotope ratio pair as a string. @@ -60,12 +60,12 @@ def repr_isotope_ratio(isotope_ratio): Consider returning additional text outside of the match (e.g. 87Sr/86Sri should include the 'i'). """ - if not is_isotoperatio(isotope_ratio): - return isotope_ratio + if not is_isotoperatio(text): + return text else: - if isinstance(isotope_ratio, str): - isotope_ratio = get_isotopes(isotope_ratio) - num, den = isotope_ratio + if isinstance(text, str): + text = get_isotopes(text) + num, den = text isomatch = r"([0-9][0-9]?[0-9]?)" elmatch = r"([a-zA-Z][a-zA-Z]?)" num_iso, num_el = re.findall(isomatch, num)[0], re.findall(elmatch, num)[0] @@ -126,12 +126,14 @@ def tochem(strings: list, abbrv=["ID", "IGSN"], split_on=r"[\s_]+"): # translate elements and oxides # elements second, Co guaranteed to override CO for python 3.6 + + chems = _common_oxides | _common_elements trans = {str(e).upper(): str(e) for e in chems} strings = [trans[str(h).upper()] if str(h).upper() in trans else h for h in strings] # translate potential isotope ratios - strings = [h if (h in chems) else repr_isotope_ratio(h) for h in strings] + split_pattern = re.compile(split_on) + strings = [h if (h in chems) else (repr_isotope_ratio(h) if split_pattern.match(h) is not None else h) for h in strings] if listified: strings = strings[0] return strings diff --git a/pyrolite/geochem/quality/__init__.py b/pyrolite/geochem/quality/__init__.py index 9905e742..8e5df7d5 100644 --- a/pyrolite/geochem/quality/__init__.py +++ b/pyrolite/geochem/quality/__init__.py @@ -2,7 +2,7 @@ Submodule for data quality checking and assurance. Todo ------- +---- * Identifying interval data by identifying dominant low-end periodicity at scales similar to the lowest values This is largely a metadata thing for single points, but a tractable problem for diff --git a/pyrolite/geochem/transform.py b/pyrolite/geochem/transform.py index b1bcb2ae..0443556f 100644 --- a/pyrolite/geochem/transform.py +++ b/pyrolite/geochem/transform.py @@ -2,6 +2,8 @@ Functions for converting, transforming and parameterizing geochemical data. """ +from collections import Counter + import numpy as np import pandas as pd import periodictable as pt @@ -12,13 +14,7 @@ from ..util.meta import update_docstring_references from ..util.text import remove_suffix, titlecase from ..util.types import iscollection -from .ind import ( - _common_elements, - _common_oxides, - get_cations, - get_ionic_radii, - simple_oxides, -) +from .ind import _common_elements, _common_oxides, get_cations, simple_oxides from .norm import Composition, get_reference_composition logger = Handle(__name__) @@ -102,7 +98,7 @@ def devolatilise( :class:`pandas.DataFrame` Transformed dataframe. """ - keep = [i for i in df.columns if not i in exclude] + keep = [i for i in df.columns if i not in exclude] if renorm: return renormalise(df.loc[:, keep]) else: @@ -141,7 +137,7 @@ def oxide_conversion(oxin, oxout, molecular=False): # Assertion of simple oxide assert (len(in_els) == len(out_els)) & (len(in_els) == 1) assert in_els == out_els # Need to be dealing with the same element! - except: + except AssertionError: raise ValueError("Incompatible compounds: {} --> {}".format(in_els, out_els)) # Moles of product vs. moles of reactant cation_coefficient = list(inatoms.values())[0] / list(outatoms.values())[0] @@ -342,7 +338,7 @@ def aggregate_element( for t in targetnames: if t not in _df: - _df[t] = 0 # avoid missing column errors + _df[t] = 0.0 # avoid missing column errors coeff = np.array(coeff) if coeff.ndim == 2: @@ -603,7 +599,7 @@ def lambda_lnREE( norm_df.loc[(norm_df <= 0.0).any(axis=1), :] = np.nan # remove zero or below norm_df.loc[:, ree] = np.log(norm_df.loc[:, ree]) - if not (sigmas is None): + if sigmas is not None: if isinstance(sigmas, pd.Series): # convert this to an array sigmas = sigmas[ree].values @@ -707,7 +703,7 @@ def convert_chemistry( # and speciated components #################################################################################### output_compositional = [ - i for i in to if i not in coupled_sets + noncomp + new_ratios + i for i in to if i not in (coupled_sets + noncomp + new_ratios) ] # check that these are all unique components assert len(set(output_compositional)) == len( @@ -753,6 +749,16 @@ def convert_chemistry( **kwargs, ) + # TODO: warning for duplication should also be crossed over into speciated components above.. + _duplicated_cations = [ + str(k) + for k, v in Counter( # get the first cation in each component, and count duplicates + [get_cations(t, total_suffix=total_suffix)[0] for t in output_compositional] + ).items() + if v > 1 + ] + if _duplicated_cations: + logger.warning("Cations duplicated in compositional components: {}. The output retains this duplication!".format(','.join(_duplicated_cations))) # Aggregate the singular compositional items, then get new columns for item in output_compositional: df = aggregate_element(df, to=item, logdata=logdata, molecular=molecular) @@ -788,3 +794,5 @@ def convert_chemistry( else: logger.debug("Recalculation Done. Data not renormalised.") return df.loc[:, output_columns] + logger.debug("Recalculation Done. Data not renormalised.") + return df.loc[:, output_columns] diff --git a/pyrolite/mineral/lattice.py b/pyrolite/mineral/lattice.py index 5693823a..3850ab5c 100644 --- a/pyrolite/mineral/lattice.py +++ b/pyrolite/mineral/lattice.py @@ -27,6 +27,7 @@ doi: {hazen1979} """ + import numpy as np from ..util.log import Handle diff --git a/pyrolite/mineral/mindb.py b/pyrolite/mineral/mindb.py index 32bfe35c..16387c4b 100644 --- a/pyrolite/mineral/mindb.py +++ b/pyrolite/mineral/mindb.py @@ -6,6 +6,7 @@ Accessing and modifying the database across multiple with multiple threads/processes *could* result in database corruption (e.g. through repeated truncation etc). """ + import functools from pathlib import Path diff --git a/pyrolite/mineral/normative.py b/pyrolite/mineral/normative.py index 2cd907ab..04dd4062 100644 --- a/pyrolite/mineral/normative.py +++ b/pyrolite/mineral/normative.py @@ -7,6 +7,7 @@ from ..comp.codata import close, renormalise from ..geochem.transform import convert_chemistry, to_molecular +from ..util.classification import TAS from ..util.log import Handle from ..util.pd import to_frame from ..util.units import scale @@ -201,8 +202,6 @@ def endmember_decompose( # CIPW Norm and Related functions ################################################################################ -from ..util.classification import TAS - # fuctions which map to Fe2O3/FeO ratios. _MiddlemostTASRatios = dict( F=lambda t, x: 0.4 if x[1] > 10 else 0.3, @@ -814,7 +813,8 @@ def corr_m_wt(oxide): ############################################################################ # Calculate normative components ############################################################################ - + _index = df.index + df = {c: df[c] for c in df.columns} # Normative Zircon df["Z"] = df["ZrO2"] df["Y"] = df["Z"] @@ -1041,7 +1041,7 @@ def corr_m_wt(oxide): df["Hm"] = df["Fe2O3"] # Subdivision of some normative minerals - df["MgFe_O"] = df[["FeO", "MgO"]].sum(axis=1) + df["MgFe_O"] = df["FeO"] + df["MgO"] df["MgO_ratio"] = df["MgO"] / df["MgFe_O"] df["FeO_ratio"] = df["FeO"] / df["MgFe_O"] @@ -1177,6 +1177,7 @@ def corr_m_wt(oxide): df["Mg-Di"] = df["Di"] * df["MgO_ratio"] df["Mg-Ol"] = df["Ol"] * df["MgO_ratio"] + df = pd.DataFrame(df, index=_index) # reconstruct df as a dataframe ############################################################################ # calculate free component molecular abundances ############################################################################ diff --git a/pyrolite/mineral/sites.py b/pyrolite/mineral/sites.py index b487debd..4f5e67c2 100644 --- a/pyrolite/mineral/sites.py +++ b/pyrolite/mineral/sites.py @@ -85,7 +85,7 @@ def __init__( affinities={"Si{4+}": 0, "Al{3+}": 1, "Fe{3+}": 2}, *args, mode="cation", - **kwargs + **kwargs, ): super().__init__(name, coordination, *args, affinities=affinities, **kwargs) diff --git a/pyrolite/plot/__init__.py b/pyrolite/plot/__init__.py index 1271af2c..ed2a0675 100644 --- a/pyrolite/plot/__init__.py +++ b/pyrolite/plot/__init__.py @@ -1,6 +1,7 @@ """ Submodule with various plotting and visualisation functions. """ + import warnings import matplotlib @@ -56,7 +57,7 @@ def _check_components(obj, components=None, check_size=True, valid_sizes=[2, 3]) if components is None: components = obj.columns.values - except: + except AssertionError: msg = "Suggest components or provide a slice of the dataframe." raise AssertionError(msg) return components @@ -455,6 +456,7 @@ def spider( ax = init_axes(ax=ax, **kwargs) if hasattr(ax, "_pyrolite_components"): + # TODO: handle spider diagrams which have specified components pass ax = spider.spider( diff --git a/pyrolite/plot/density/__init__.py b/pyrolite/plot/density/__init__.py index 8b244f32..e983fead 100644 --- a/pyrolite/plot/density/__init__.py +++ b/pyrolite/plot/density/__init__.py @@ -1,6 +1,7 @@ """ Kernel desnity estimation plots for geochemical data. """ + import copy import matplotlib.pyplot as plt @@ -38,7 +39,7 @@ def density( shading="auto", vmin=0.0, colorbar=False, - **kwargs + **kwargs, ): """ Creates diagramatic representation of data density and/or frequency for either @@ -90,6 +91,7 @@ def density( :class:`matplotlib.axes.Axes` Axes on which the densityplot is plotted. + .. seealso:: Functions: @@ -159,7 +161,7 @@ def density( logx=logx, logy=logy, extent=extent, - **subkwargs(kwargs, DensityGrid) + **subkwargs(kwargs, DensityGrid), ) if mode == "hexbin": # extent values are exponents (i.e. 3 -> 10**3) @@ -171,7 +173,7 @@ def density( extent=grid.get_hex_extent(), xscale=["linear", "log"][logx], yscale=["linear", "log"][logy], - **subkwargs(kwargs, ax.hexbin) + **subkwargs(kwargs, ax.hexbin), ) elif mode == "hist2d": @@ -182,7 +184,7 @@ def density( range=grid.get_range(), cmap=cmap, cmin=[0, 1][vmin > 0], - **subkwargs(kwargs, ax.hist2d) + **subkwargs(kwargs, ax.hist2d), ) mappable = im @@ -192,7 +194,7 @@ def density( xtransform=[lambda x: x, np.log][logx], ytransform=[lambda y: y, np.log][logy], mode="edges", - **subkwargs(kwargs, grid.kdefrom) + **subkwargs(kwargs, grid.kdefrom), ) if percentiles: # 98th percentile @@ -210,7 +212,7 @@ def density( cmap=cmap, vmin=vmin, shading=shading, - **subkwargs(kwargs, pcolor) + **subkwargs(kwargs, pcolor), ) mappable.set_edgecolor(background_color) mappable.set_linestyle("None") @@ -225,7 +227,7 @@ def density( percentiles=percentiles, cmap=cmap, vmin=vmin, - **kwargs + **kwargs, ) if relim and (extent is not None): ax.axis(extent) @@ -256,7 +258,7 @@ def density( cmap=cmap, vmin=vmin, shading=shading, - **subkwargs(kwargs, pcolor) + **subkwargs(kwargs, pcolor), ) mappable = tri_poly_collection @@ -269,7 +271,7 @@ def density( percentiles=percentiles, cmap=cmap, vmin=vmin, - **kwargs + **kwargs, ) ax.set_aspect("equal") else: @@ -292,7 +294,7 @@ def _add_contours( cmap=DEFAULT_CONT_COLORMAP, vmin=0.0, extent=None, - **kwargs + **kwargs, ): """ Add density-based contours to a plot. @@ -310,7 +312,7 @@ def _add_contours( percentiles=levels, extent=extent, cmap=cmap, - **kwargs + **kwargs, ) mappable = _cs else: diff --git a/pyrolite/plot/density/grid.py b/pyrolite/plot/density/grid.py index 118f7352..38279ed7 100644 --- a/pyrolite/plot/density/grid.py +++ b/pyrolite/plot/density/grid.py @@ -46,6 +46,15 @@ def __init__( self.xmin, self.xmax, self.ymin, self.ymax = self.extent_from_xy(x, y) else: self.xmin, self.xmax, self.ymin, self.ymax = extent + # validation + self.xmin, self.xmax = ( + min([self.xmin, self.xmax]), + max([self.xmin, self.xmax]), + ) + self.ymin, self.ymax = ( + min([self.ymin, self.ymax]), + max([self.ymin, self.ymax]), + ) self.xstep = self.get_xstep() self.ystep = self.get_ystep() @@ -65,15 +74,17 @@ def calculate_grid(self): def get_ystep(self): if self.logy: - return (self.ymax / self.ymin) / self.ybins + step = (self.ymax / self.ymin) / self.ybins else: - return (self.ymax - self.ymin) / self.ybins + step = (self.ymax - self.ymin) / self.ybins + return step def get_xstep(self): if self.logx: - return (self.xmax / self.xmin) / self.xbins + step = (self.xmax / self.xmin) / self.xbins else: - return (self.xmax - self.xmin) / self.xbins + step = (self.xmax - self.xmin) / self.xbins + return step def extent_from_xy(self, x, y, coverage_scale=None): cov = coverage_scale or self.coverage_scale diff --git a/pyrolite/util/math.py b/pyrolite/util/math.py index c98b19eb..cd104192 100644 --- a/pyrolite/util/math.py +++ b/pyrolite/util/math.py @@ -424,7 +424,8 @@ def signify_digit(n, unc=None, leeway=0, low_filter=True): if round_to <= 0: fmt = int else: - fmt = lambda x: x + def fmt(x): + return x sig_n = round(n, round_to) if low_filter and sig_n == 0.0: return np.nan @@ -604,12 +605,12 @@ def solve_ratios(*eqs, evaluate=True): Solve a ternary system (top-left-right) given two constraints on two ratios, which together describe intersecting lines/a point. """ - t, l, r = sympy.symbols("t l r") + t, L, r = sympy.symbols("t l r") def to_sympy(t): # rearrange to have =0 equvalent expressions return sympy.sympify("-".join(t.split("="))) result = sympy.solve( - [to_sympy(e) for e in eqs] + [to_sympy("t + l + r = 1")], (t, l, r) + [to_sympy(e) for e in eqs] + [to_sympy("t + l + r = 1")], (t, L, r) ) return list(result.values()) diff --git a/pyrolite/util/pd.py b/pyrolite/util/pd.py index afafb7a4..fb960955 100644 --- a/pyrolite/util/pd.py +++ b/pyrolite/util/pd.py @@ -225,21 +225,19 @@ def outliers( """ """ if not cols: cols = df.columns - colfltr = (df.dtypes == float) & ([i in cols for i in df.columns]) + _df = df.select_dtypes(include=[np.number]) + _df = _df.loc[:, [i in cols for i in _df.columns]] low, high = np.min(quantile_select), np.max(quantile_select) if not logquantile: - quantile = df.loc[:, colfltr].quantile([low, high]) + quantile = _df.quantile([low, high]) else: - quantile = df.loc[:, colfltr].apply(np.log).quantile([low, high]) + quantile = _df.apply(np.log).quantile([low, high]) whereout = ( - df.loc[:, colfltr] - .apply(detect, args=(quantile, quantile_select), axis=0) - .sum(axis=1) - > 0 + _df.apply(detect, args=(quantile, quantile_select), axis=0).sum(axis=1) > 0 ) if not exclude: whereout = np.logical_not(whereout) - return df.loc[whereout, colfltr] + return _df.loc[whereout, :] def concat_columns(df, columns=None, astype=str, **kwargs): diff --git a/pyrolite/util/resampling.py b/pyrolite/util/resampling.py index cf08618e..8074b7b4 100644 --- a/pyrolite/util/resampling.py +++ b/pyrolite/util/resampling.py @@ -403,7 +403,7 @@ def _metric_name(metric): msg = "Gaussian Process boostrapping not yet implemented." raise NotImplementedError(msg) else: - msg = "Bootstrap method {} not recognised.".format(boostrap_method) + msg = "Bootstrap method {} not recognised.".format(bootstrap_method) raise NotImplementedError(msg) # whether to independently estimate metric values for individual categories? diff --git a/pyrolite/util/time.py b/pyrolite/util/time.py index 95ac7375..c07b390a 100644 --- a/pyrolite/util/time.py +++ b/pyrolite/util/time.py @@ -251,19 +251,22 @@ def named_age(self, age, level="Specific", **kwargs): """ level = titlecase(level) - wthn_rng = lambda x: (age <= x.Start) & (age >= x.End) + + def wthn_rng(x): + return (age <= x.Start) & (age >= x.End) + relevant = self.data.loc[self.data.apply(wthn_rng, axis=1).values, :] if level == "Specific": # take the rightmost grouping relevant = relevant.loc[:, self.levels] counts = (~pd.isnull(relevant)).count(axis=1) if sum(counts == counts.max()) > 1: idx_rel_row = counts.index[ - max([ix for (ix, r) in enumerate(counts) if r == counts[0]]) + max([ix for (ix, r) in enumerate(counts) if r == counts.iloc[0]]) ] else: idx_rel_row = counts.idxmax() rel_row = relevant.loc[idx_rel_row, :] - return age_name(rel_row[~pd.isnull(rel_row)], **kwargs) + return age_name(rel_row[~pd.isnull(rel_row)].to_list(), **kwargs) else: unique_values = relevant.loc[:, level].unique() return unique_values[~pd.isnull(unique_values)][0] diff --git a/test/comp/comp_aggregate.py b/test/comp/comp_aggregate.py index f1ed674c..b2e98b55 100644 --- a/test/comp/comp_aggregate.py +++ b/test/comp/comp_aggregate.py @@ -1,9 +1,18 @@ -import logging import unittest import numpy as np - -from pyrolite.comp.aggregate import * +import pandas as pd + +from pyrolite.comp.aggregate import ( + compositional_mean, + cross_ratios, + get_full_column, + nan_weighted_compositional_mean, + nan_weighted_mean, + np_cross_ratios, + standardise_aggregate, + weights_from_array, +) from pyrolite.util.synthetic import normal_frame diff --git a/test/comp/comp_codata.py b/test/comp/comp_codata.py index 6784d5fb..6b2653ea 100644 --- a/test/comp/comp_codata.py +++ b/test/comp/comp_codata.py @@ -1,8 +1,23 @@ import unittest import numpy as np - -from pyrolite.comp.codata import * +import pandas as pd + +from pyrolite.comp.codata import ( + ALR, + CLR, + ILR, + boxcox, + close, + get_ILR_labels, + inverse_ALR, + inverse_boxcox, + inverse_CLR, + inverse_ILR, + inverse_sphere, + renormalise, + sphere, +) from pyrolite.util.synthetic import normal_frame diff --git a/test/comp/comp_impute.py b/test/comp/comp_impute.py index 899cf536..6b01c4ed 100644 --- a/test/comp/comp_impute.py +++ b/test/comp/comp_impute.py @@ -1,17 +1,10 @@ import unittest import numpy as np -import pandas as pd -from pyrolite.comp.aggregate import np_cross_ratios -from pyrolite.comp.impute import EMCOMP, _little_sweep, _multisweep, _reg_sweep +from pyrolite.comp.impute import EMCOMP, _little_sweep, _multisweep from pyrolite.util.math import augmented_covariance_matrix -from pyrolite.util.synthetic import ( - normal_frame, - normal_series, - random_composition, - random_cov_matrix, -) +from pyrolite.util.synthetic import random_composition, random_cov_matrix class TestRegSweep(unittest.TestCase): diff --git a/test/geochem/geochem_alteration.py b/test/geochem/geochem_alteration.py index 709751fb..8c8d083f 100644 --- a/test/geochem/geochem_alteration.py +++ b/test/geochem/geochem_alteration.py @@ -1,8 +1,18 @@ import unittest import numpy as np +import pandas as pd -from pyrolite.geochem.alteration import * +from pyrolite.geochem.alteration import ( + CCPI, + CIA, + CIW, + PIA, + SAR, + WIP, + IshikawaAltIndex, + SiTiIndex, +) class TestAlterationIndicies(unittest.TestCase): @@ -56,4 +66,4 @@ def test_CCPI_default(self): if __name__ == "__main__": - unittest.main() + unittest.main() \ No newline at end of file diff --git a/test/geochem/geochem_ind.py b/test/geochem/geochem_ind.py index 4e2f288a..87929848 100644 --- a/test/geochem/geochem_ind.py +++ b/test/geochem/geochem_ind.py @@ -1,6 +1,18 @@ import unittest -from pyrolite.geochem.ind import * +import periodictable as pt + +from pyrolite.geochem.ind import ( + REE, + REY, + by_incompatibility, + by_number, + common_elements, + common_oxides, + get_cations, + get_ionic_radii, + simple_oxides, +) class TestGetCations(unittest.TestCase): diff --git a/test/geochem/geochem_isotope.py b/test/geochem/geochem_isotope.py index e678066e..c6a6ff4f 100644 --- a/test/geochem/geochem_isotope.py +++ b/test/geochem/geochem_isotope.py @@ -5,5 +5,4 @@ class TestDeadtimeCorrection(unittest.TestCase): def test_default(self): - pass deadtime_correction(10000, 20) diff --git a/test/geochem/geochem_norm.py b/test/geochem/geochem_norm.py index 61a1368e..78ea7b39 100644 --- a/test/geochem/geochem_norm.py +++ b/test/geochem/geochem_norm.py @@ -1,8 +1,5 @@ import unittest -import numpy as np - -import pyrolite from pyrolite.geochem.norm import ( Composition, all_reference_compositions, @@ -11,8 +8,6 @@ update_database, ) from pyrolite.util.general import remove_tempdir, temp_path -from pyrolite.util.meta import pyrolite_datafolder -from pyrolite.util.synthetic import normal_frame class TestComposition(unittest.TestCase): diff --git a/test/geochem/geochem_parse.py b/test/geochem/geochem_parse.py index 9f97f8a7..eb33d543 100644 --- a/test/geochem/geochem_parse.py +++ b/test/geochem/geochem_parse.py @@ -4,7 +4,12 @@ import pandas as pd from pyrolite.geochem.ind import REE -from pyrolite.geochem.parse import * +from pyrolite.geochem.parse import ( + check_multiple_cation_inclusion, + ischem, + repr_isotope_ratio, + tochem, +) class TestIsChem(unittest.TestCase): diff --git a/test/geochem/geochem_pyrochem.py b/test/geochem/geochem_pyrochem.py index e91db243..ce516c90 100644 --- a/test/geochem/geochem_pyrochem.py +++ b/test/geochem/geochem_pyrochem.py @@ -6,9 +6,7 @@ import pyrolite.geochem from pyrolite.comp.codata import renormalise from pyrolite.geochem.norm import get_reference_composition -from pyrolite.util.synthetic import normal_frame, normal_series - -# [print("# " + i) for i in dir(df.pyrochem) if "__" not in i and not i.startswith("_")] +from pyrolite.util.synthetic import normal_frame class TestPyrochem(unittest.TestCase): diff --git a/test/geochem/geochem_transform.py b/test/geochem/geochem_transform.py index 5d6d24c6..509816a9 100644 --- a/test/geochem/geochem_transform.py +++ b/test/geochem/geochem_transform.py @@ -2,7 +2,7 @@ import numpy as np -from pyrolite.geochem.ind import REE +from pyrolite.geochem.ind import REE, get_ionic_radii from pyrolite.geochem.norm import get_reference_composition from pyrolite.geochem.transform import * from pyrolite.util.lambdas import orthogonal_polynomial_constants diff --git a/test/mineral/mineral_mindb.py b/test/mineral/mineral_mindb.py index 9776f3f7..2b3dc72b 100644 --- a/test/mineral/mineral_mindb.py +++ b/test/mineral/mineral_mindb.py @@ -46,7 +46,7 @@ def test_get_olivine(self): self.assertIsInstance(out, pd.DataFrame) @unittest.expectedFailure - def test_get_olivine(self): + def test_get_tourmaline(self): out = get_mineral_group("tourmaline") self.assertIsInstance(out, pd.DataFrame) diff --git a/test/mineral/mineral_normative.py b/test/mineral/mineral_normative.py index 72d172be..0e995c92 100644 --- a/test/mineral/mineral_normative.py +++ b/test/mineral/mineral_normative.py @@ -3,14 +3,11 @@ import numpy as np import pandas as pd -from pyrolite.mineral.normative import ( +from pyrolite.mineral.normative import ( # MiddlemostOxRatio, _aggregate_components, _update_molecular_masses, CIPW_norm, LeMaitre_Fe_correction, LeMaitreOxRatio, Middlemost_Fe_correction, - MiddlemostOxRatio, - _aggregate_components, - _update_molecular_masses, endmember_decompose, unmix, ) diff --git a/test/mineral/mineral_sites.py b/test/mineral/mineral_sites.py index 569f69ff..de3c01b0 100644 --- a/test/mineral/mineral_sites.py +++ b/test/mineral/mineral_sites.py @@ -1,6 +1,6 @@ import unittest -from pyrolite.mineral.sites import * +from pyrolite.mineral.sites import AX, IX, MX, OX, TX, VX, Site class TestSites(unittest.TestCase): diff --git a/test/mineral/mineral_template.py b/test/mineral/mineral_template.py index b1bba1be..d2dd3ab0 100644 --- a/test/mineral/mineral_template.py +++ b/test/mineral/mineral_template.py @@ -1,7 +1,11 @@ import unittest -from pyrolite.mineral.sites import * -from pyrolite.mineral.template import * +import numpy as np +import pandas as pd +import periodictable as pt + +from pyrolite.mineral.sites import MX, OX, TX +from pyrolite.mineral.template import OLIVINE, PYROXENE, Mineral, MineralTemplate class TestMineralTemplate(unittest.TestCase): diff --git a/test/mineral/mineral_transform.py b/test/mineral/mineral_transform.py index 27cc043b..f504fafc 100644 --- a/test/mineral/mineral_transform.py +++ b/test/mineral/mineral_transform.py @@ -1,6 +1,13 @@ import unittest -from pyrolite.mineral.transform import * +import pandas as pd +import periodictable as pt + +from pyrolite.mineral.transform import ( + formula_to_elemental, + merge_formulae, + recalc_cations, +) class TestFormula2Elemental(unittest.TestCase): diff --git a/test/plot/plot_biplot.py b/test/plot/plot_biplot.py index 99fc36e8..578710f1 100644 --- a/test/plot/plot_biplot.py +++ b/test/plot/plot_biplot.py @@ -1,13 +1,8 @@ import logging import unittest -import matplotlib.axes -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd - -from pyrolite.data.Aitchison import * -from pyrolite.plot.biplot import * +from pyrolite.data.Aitchison import load_hongite +from pyrolite.plot.biplot import compositional_biplot logger = logging.getLogger(__name__) diff --git a/test/plot/plot_color.py b/test/plot/plot_color.py index 1dc9d4af..89f04a37 100644 --- a/test/plot/plot_color.py +++ b/test/plot/plot_color.py @@ -3,7 +3,7 @@ import matplotlib.colors import numpy as np -from pyrolite.plot.color import * +from pyrolite.plot.color import get_cmode, process_color class TestProcessColor(unittest.TestCase): diff --git a/test/plot/plot_parallel.py b/test/plot/plot_parallel.py index 0372eba2..d8685268 100644 --- a/test/plot/plot_parallel.py +++ b/test/plot/plot_parallel.py @@ -1,9 +1,6 @@ import unittest import matplotlib.axes -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd import pyrolite.data.Aitchison from pyrolite.plot.parallel import parallel diff --git a/test/plot/plot_pyroplot.py b/test/plot/plot_pyroplot.py index 507b7d1c..40af003b 100644 --- a/test/plot/plot_pyroplot.py +++ b/test/plot/plot_pyroplot.py @@ -5,11 +5,9 @@ import matplotlib.pyplot as plt import mpltern.ternary import numpy as np -import pandas as pd -from numpy.random import multivariate_normal from pyrolite.geochem import REE -from pyrolite.plot import density, pyroplot, spider +from pyrolite.plot import pyroplot from pyrolite.util.synthetic import normal_frame logger = logging.getLogger(__name__) diff --git a/test/plot/plot_spider.py b/test/plot/plot_spider.py index cc3704cd..a82c61b0 100644 --- a/test/plot/plot_spider.py +++ b/test/plot/plot_spider.py @@ -4,7 +4,6 @@ import matplotlib.axes import matplotlib.pyplot as plt import numpy as np -import pandas as pd from pyrolite.geochem import REE from pyrolite.plot.spider import REE_v_radii, spider diff --git a/test/plot/plot_stem.py b/test/plot/plot_stem.py index 1f524fb2..fb27c45e 100644 --- a/test/plot/plot_stem.py +++ b/test/plot/plot_stem.py @@ -4,7 +4,6 @@ import matplotlib.axes import matplotlib.pyplot as plt import numpy as np -import pandas as pd from pyrolite.plot.stem import stem diff --git a/test/util/util_classification.py b/test/util/util_classification.py index 16d71694..6c026bd4 100644 --- a/test/util/util_classification.py +++ b/test/util/util_classification.py @@ -1,13 +1,16 @@ import unittest + import matplotlib.pyplot as plt import pandas as pd -from pyrolite.util.classification import (TAS, - USDASoilTexture, - QAP, - FeldsparTernary, - JensenPlot, - PeralkalinityClassifier) +from pyrolite.util.classification import ( + QAP, + TAS, + FeldsparTernary, + JensenPlot, + PeralkalinityClassifier, + USDASoilTexture, +) from pyrolite.util.synthetic import normal_frame, random_cov_matrix @@ -50,6 +53,9 @@ def test_classifer_predict(self): _ = classes.apply(lambda x: cm.fields.get(x, {"name": None})["name"]) self.assertFalse(pd.isnull(classes).all()) + def tearDown(self): + plt.close("all") + class TestUSDASoilTexture(unittest.TestCase): def setUp(self): @@ -77,6 +83,9 @@ def test_classifer_predict(self): classes = cm.predict(df, data_scale=1.0) self.assertFalse(pd.isnull(classes).all()) + def tearDown(self): + plt.close("all") + class TestQAP(unittest.TestCase): def setUp(self): @@ -104,6 +113,9 @@ def test_classifer_predict(self): classes = cm.predict(df) self.assertFalse(pd.isnull(classes).all()) + def tearDown(self): + plt.close("all") + class TestFeldsparTernary(unittest.TestCase): def setUp(self): @@ -131,6 +143,9 @@ def test_classifer_predict(self): classes = cm.predict(df) self.assertFalse(pd.isnull(classes).all()) + def tearDown(self): + plt.close("all") + class TestJensenPlot(unittest.TestCase): def setUp(self): @@ -159,6 +174,9 @@ def test_classifer_predict(self): classes = cm.predict(df) self.assertFalse(pd.isnull(classes).all()) + def tearDown(self): + plt.close("all") + class TestPeralkalinity(unittest.TestCase): """Test the peralkalinity classifier.""" @@ -175,6 +193,9 @@ def test_classifer_predict(self): cm = PeralkalinityClassifier() df.loc[:, "Peralk"] = cm.predict(df) + def tearDown(self): + plt.close("all") + if __name__ == "__main__": unittest.main() diff --git a/test/util/util_env.py b/test/util/util_env.py index b80fa035..9441f1f4 100644 --- a/test/util/util_env.py +++ b/test/util/util_env.py @@ -1,5 +1,4 @@ import os -import sys import unittest from pyrolite.util.env import validate_update_envvar, validate_value @@ -10,7 +9,8 @@ def setUp(self): self.value = 10 def test_single_validator(self): - v = lambda x: x > 0 + def v(x): + return x > 0 expect = True self.assertTrue(validate_value(self.value, v) is expect) diff --git a/test/util/util_general.py b/test/util/util_general.py index 927b7c29..741f5084 100644 --- a/test/util/util_general.py +++ b/test/util/util_general.py @@ -1,11 +1,8 @@ import unittest -from pathlib import Path -from pyrolite.util.general import ( +from pyrolite.util.general import ( # copy_file, remove_tempdir Timewith, - copy_file, flatten_dict, - remove_tempdir, swap_item, temp_path, ) diff --git a/test/util/util_log.py b/test/util/util_log.py index a17a00e6..73dfd3b0 100644 --- a/test/util/util_log.py +++ b/test/util/util_log.py @@ -3,7 +3,7 @@ import unittest from io import StringIO -import pyrolite.plot # for logging checks +import pyrolite.plot # for logging checks # noqa: F401 from pyrolite.util.log import Handle, ToLogger, stream_log @@ -77,14 +77,16 @@ def test_set_level(self): logger = Handle("pyrolite", level=level) self.assertTrue(logger.level == val) + class TestToLogger(unittest.TestCase): def test_default(self): - logger= Handle(__name__, level='DEBUG') + logger = Handle(__name__, level="DEBUG") - with ToLogger(logger, 'INFO') as f: - f.write('Logging output from stream.') + with ToLogger(logger, "INFO") as f: + f.write("Logging output from stream.") f.flush() + if __name__ == "__main__": unittest.main() diff --git a/test/util/util_math.py b/test/util/util_math.py index 12735343..45993cc7 100644 --- a/test/util/util_math.py +++ b/test/util/util_math.py @@ -4,7 +4,26 @@ import pandas as pd from sympy import tensorcontraction -from pyrolite.util.math import * +from pyrolite.util.math import ( + augmented_covariance_matrix, + equal_within_significance, + grid_from_ranges, + helmert_basis, + interpolate_line, + is_numeric, + isclose, + linrng_, + linspc_, + logrng_, + logspc_, + most_precise, + nancov, + on_finite, + round_sig, + significant_figures, + signify_digit, + symbolic_helmert_basis, +) from pyrolite.util.synthetic import random_cov_matrix diff --git a/test/util/util_meta.py b/test/util/util_meta.py index 6b0bfb30..ede5a271 100644 --- a/test/util/util_meta.py +++ b/test/util/util_meta.py @@ -1,4 +1,3 @@ -import sys import unittest from pathlib import Path diff --git a/test/util/util_multip.py b/test/util/util_multip.py index 73d3ae97..6a105b10 100644 --- a/test/util/util_multip.py +++ b/test/util/util_multip.py @@ -1,9 +1,6 @@ import platform import unittest -import numpy as np -import pandas as pd - from pyrolite.util.multip import combine_choices, func_wrapper, multiprocess @@ -49,7 +46,8 @@ def test_construct(self): @unittest.skipIf( - platform.system() in ["Windows", 'Darwin'], "Bug with multiprocessing testing on Windows" + platform.system() in ["Windows", "Darwin"], + "Bug with multiprocessing testing on Windows", ) class TestMultiprocess(unittest.TestCase): """Tests the multiprocess utility function.""" diff --git a/test/util/util_pd.py b/test/util/util_pd.py index 901aa977..135cd97e 100644 --- a/test/util/util_pd.py +++ b/test/util/util_pd.py @@ -1,14 +1,21 @@ -import os -import time import unittest -from pathlib import Path import numpy as np import pandas as pd from pyrolite.util.general import remove_tempdir, temp_path from pyrolite.util.meta import subkwargs -from pyrolite.util.pd import * +from pyrolite.util.pd import ( + accumulate, + concat_columns, + df_from_csvs, + outliers, + read_table, + to_frame, + to_numeric, + to_ser, + uniques_from_concat, +) from pyrolite.util.synthetic import normal_frame, normal_series @@ -133,15 +140,12 @@ def test_numeric(self): def test_error_methods(self): df = self.df df.loc[0, "SiO2"] = "Low" - for method in ["ignore", "raise", "coerce"]: + for method in ["raise", "coerce"]: with self.subTest(method=method): try: result = to_numeric(df, errors=method) - self.assertTrue(method in ["ignore", "coerce"]) - if method == "ignore": - self.assertTrue(result.loc[0, "SiO2"] == "Low") - else: - self.assertTrue(pd.isnull(result.loc[0, "SiO2"])) + self.assertTrue(method in ["coerce"]) + self.assertTrue(pd.isnull(result.loc[0, "SiO2"])) except ValueError: # should raise with can't parse 'low' self.assertTrue(method == "raise") diff --git a/test/util/util_resampling.py b/test/util/util_resampling.py index 0c3e9317..5c59542f 100644 --- a/test/util/util_resampling.py +++ b/test/util/util_resampling.py @@ -4,14 +4,12 @@ import numpy as np import pandas as pd -from pyrolite.util.resampling import ( - _segmented_univariate_distance_matrix, +from pyrolite.util.resampling import ( # _segmented_univariate_distance_matrix add_age_noise, get_spatiotemporal_resampling_weights, spatiotemporal_bootstrap_resample, univariate_distance_matrix, ) -from pyrolite.util.spatial import great_circle_distance from pyrolite.util.synthetic import normal_frame df = normal_frame() diff --git a/test/util/util_spatial.py b/test/util/util_spatial.py index 571d6fe8..98768c3d 100644 --- a/test/util/util_spatial.py +++ b/test/util/util_spatial.py @@ -1,17 +1,21 @@ import unittest -import matplotlib.pyplot as plt import numpy as np -import pandas as pd -try: - import cartopy.crs as ccrs - - HAVE_CARTOPY = True -except ImportError: - HAVE_CARTOPY = False from pyrolite.util.math import isclose # nan-equalling isclose -from pyrolite.util.spatial import * +from pyrolite.util.spatial import ( + NSEW_2_bounds, + great_circle_distance, + levenshtein_distance, + piecewise, + spatiotemporal_split, +) + +# try: +# import cartopy.crs as ccrs +# HAVE_CARTOPY = True +# except ImportError: +# HAVE_CARTOPY = False class TestGreatCircleDistance(unittest.TestCase): diff --git a/test/util/util_synthetic.py b/test/util/util_synthetic.py index 2be7d7f0..5a7b8301 100644 --- a/test/util/util_synthetic.py +++ b/test/util/util_synthetic.py @@ -3,7 +3,11 @@ import numpy as np import pandas as pd -from pyrolite.util.synthetic import * +from pyrolite.util.synthetic import ( + example_spider_data, + random_composition, + random_cov_matrix, +) class TestExampleSpiderData(unittest.TestCase): diff --git a/test/util/util_text.py b/test/util/util_text.py index a205bb33..aa908a6f 100644 --- a/test/util/util_text.py +++ b/test/util/util_text.py @@ -1,9 +1,19 @@ import unittest import numpy as np -import pandas as pd -from pyrolite.util.text import * +from pyrolite.util.text import ( + int_to_alpha, + normalise_whitespace, + parse_entry, + quoted_string, + remove_prefix, + slugify, + split_records, + string_variations, + titlecase, + to_width, +) class TestRemovePrefix(unittest.TestCase): diff --git a/test/util/util_types.py b/test/util/util_types.py index 2eaf4e21..9f915245 100644 --- a/test/util/util_types.py +++ b/test/util/util_types.py @@ -1,9 +1,8 @@ import unittest import numpy as np -import pandas as pd -from pyrolite.util.types import * +from pyrolite.util.types import iscollection class TestIscollection(unittest.TestCase): diff --git a/test/util/util_units.py b/test/util/util_units.py index 9f1663e4..f156156a 100644 --- a/test/util/util_units.py +++ b/test/util/util_units.py @@ -1,10 +1,8 @@ import unittest import numpy as np +import pandas as pd -import pyrolite -from pyrolite.util.synthetic import normal_frame -from pyrolite.util.units import * from pyrolite.util.units import __UNITS__, scale