Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

AIOCG plots #57

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyrolite/data/models/AIOCG/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"AIOCG","axes":{"x":"(2Ca+5Fe+2Mn)+(2Ca+5Fe+2Mn+Mg+Si) molar","y":"K/(K+Na+0.5Ca) molar"},"fields":{"K":{"name":["K"],"poly":[[0,405.2],[103.2,405.2],[109.4,244.1],[51.6,235.0],[39.6,222.8],[45.6,194.4],[0,194.4]]},"KintNa":{"name":[""],"poly":[[0,194.4],[0,121.5],[145.7,121.5],[54.7,176.2],[45.6,194.4]]},"Na":{"name":["Na"],"poly":[[0,121.5],[145.7,121.5],[145.7,32.4],[157.9,0.0],[0,0.0]]},"NotAltered1":{"name":[""],"poly":[[291.6,145.9],[221.7,72.5],[294.7,30.5],[346.3,44.6],[352.3,54.7],[346.3,73.0],[334.1,101.3],[315.8,117.6]]},"NotAltered2":{"name":[""],"poly":[[291.6,145.9],[221.7,72.5],[294.7,30.5],[346.3,44.6],[352.3,54.7],[346.3,73.0],[334.1,101.3],[315.8,117.6]]},"Na-Ca-Fe-(Mg)":{"name":["Na-Ca-Fe-(Mg)"],"poly":[[145.7,121.5],[145.7,32.4],[157.9,0.0],[413,0.0],[400.8,48.7],[367.4,77.1],[352.3,117.6],[315.8,117.6],[334.1,101.3],[346.3,73.0],[352.3,54.7],[346.3,44.6],[294.7,30.5],[206.6,81.1]]},"KintK-Fe":{"name":[""],"poly":[[103.2,405.2],[109.4,244.1],[115.4,245.1],[197.5,223.5],[200.4,275.6],[194.4,405.2]]},"K-Fe":{"name":["K-Fe"],"poly":[[194.4,405.2],[200.4,275.6],[197.5,223.5],[200.4,222.8],[437.2,222.8],[431.2,328.1],[413,364.6],[413,405.2]]},"Ca-K-Fe":{"name":["Ca-K-Fe"],"poly":[[200.4,222.8],[291.6,145.9],[315.8,117.6],[352.3,117.6],[443.5,117.6],[437.2,222.8]]},"Ca-Fe-(Mg)":{"name":["Ca-Fe-(Mg)"],"poly":[[352.3,117.6],[367.4,77.1],[400.8,48.7],[413,0.0],[461.7,0.0],[445.4,70.8],[443.5,117.6]]},"Fe-rich_Ca-Fe":{"name":["Fe-rich Ca-Fe"],"poly":[[443.5,117.6],[445.4,70.8],[461.7,0.0],[607.4,0.0],[607.4,117.6]]},"Fe-rich_Ca-K-Fe":{"name":["Fe-rich Ca-K-Fe"],"poly":[[437.2,222.8],[443.5,117.6],[607.4,117.6],[607.4,222.8]]},"Fe-rich_K-Fe":{"name":["Fe-rich K-Fe"],"poly":[[413,405.2],[413,364.6],[431.2,328.1],[437.2,222.8],[607.4,222.8],[607.4,405.2]]}}}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A quick question about these coordinates - are they in pixels? It would be good to convert them back to molar proportions if so, and then just set the aspect of the axes on which the diagram is plotted to the 6-4 ratio which it's typically found in.

23 changes: 23 additions & 0 deletions pyrolite/geochem/alteration.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,26 @@ def WIP(df: pd.DataFrame):

"""
return 2 * df.Na2O / 0.35 + df.MgO / 0.9 + 2 * df.K2O / 0.25 + df.CaO / 0.7

def AIOCG_xy(df: pd.DataFrame):
NicolasPietteLauziere marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I think keeping the name to AIOCG is appropriate here; the xy aspect is implied in the diagram.

"""

Alteration diagram coordinates for iron oxide-copper-gold mineralisation
system. (molecular) [#ref_1]_
Returns X,Y coordinates to plot in discriminant diagram scaled to the
400x600 dimension of the background

References
----------
.. [#ref_1] Montreuil J F, Corriveau L, Grunsky E C (2013). Compositional
data analysis of hydrothermal alteration in IOCG systems, Great
Bear magmatic zone, Canada: to each alteration type its own
geochemical signature. Geochemistry: Exploration, Environment,
Analysis 13:229-247.
doi:`<http://dx.doi.org/10.1144/geochem2011-101>`__
"""
x = (2*df.Ca+5*df.Fe+2*df.Mn)/(2*df.Ca+5*df.Fe+2*df.Mn+df.Mg+df.Si)*600
y = df.K/(df.K+df.Na+0.5*df.Ca)*400
return x, y


61 changes: 61 additions & 0 deletions pyrolite/plot/templates/AIOCG.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import matplotlib.pyplot as plt
import numpy as np
from ...util.plot.axes import init_axes
from ...util.classification import AIOCG as AIOCGclassifier
NicolasPietteLauziere marked this conversation as resolved.
Show resolved Hide resolved
from ...util.meta import sphinx_doi_link, update_docstring_references, subkwargs
from ...util.log import Handle

logger = Handle(__name__)


@update_docstring_references
def AIOCG(ax=None, relim=True, color="k", **kwargs):
"""
Adds the AIOCG diagram from Montreuil et al. (2013) [#ref_1]_ to an axes.
NOTE to user:
x:y are scaled from 1:1 to 600:400 to account for plot dimension


Parameters
----------
ax : :class:`matplotlib.axes.Axes`
Axes to add the template on to.
relim : :class:`bool`
Whether to relimit axes to fit the built in ranges for this diagram.
color : :class:`str`
Line color for the diagram.

Returns
-------
ax : :class:`matplotlib.axes.Axes`

References
-----------
.. [#ref_1] Montreuil J F, Corriveau L, and Potter E G (2015). Formation of
albitite-hosted uranium within IOCG systems: the Southern Breccia,
Great Bear magmatic zone, Northwest Territories, Canada.
Mineralium Deposita, 50:293-325.
doi:`<https://doi.org/10.1007/s00126-014-0530-7>`__
"""
AIOCG_xlim, AIOCG_ylim = (0, 607.4), (0, 405.2)
if ax is None:
xlim, ylim = AIOCG_xlim, AIOCG_ylim
else:
# if the axes limits are not defaults, update to reflect the axes
ax_defaults = (0, 1)
ax_xlim, ax_ylim = ax.get_xlim(), ax.get_ylim()
xlim, ylim = (
[ax_xlim, AIOCG_xlim][np.allclose(ax_xlim, ax_defaults)],
[ax_ylim, AIOCG_ylim][np.allclose(ax_ylim, ax_defaults)],
)
ax = init_axes(ax=ax, **kwargs)

aiocg = AIOCGclassifier()
NicolasPietteLauziere marked this conversation as resolved.
Show resolved Hide resolved
aiocg.add_to_axes(ax=ax, **kwargs)
if relim:
ax.set_xlim(xlim)
ax.set_ylim(ylim)
return ax


AIOCG.__doc__ = AIOCG.__doc__.format(Montreuil2013=sphinx_doi_link("10.1007/s00126-014-0530-7"))
104 changes: 104 additions & 0 deletions pyrolite/util/classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,110 @@ def add_to_axes(self, ax=None, fill=False, axes_scale=100.0, labels=None, **kwar
ax.set_xlabel("$SiO_2$")
return ax

class AIOCG(PolygonClassifier):
"""
AIOCG Diagram classifier from Montreuil et al. (2013) [#ref_1]_.

Parameters
-----------
name : :class:`str`
A name for the classifier model.
axes : :class:`list` | :class:`tuple`
Names of the axes corresponding to the polygon coordinates.
fields : :class:`dict`
Dictionary describing indiviudal polygons, with identifiers as keys and
dictionaries containing 'name' and 'fields' items.
scale : :class:`float`
Default maximum scale for the axes. Typically 100 (wt%) or 1 (fractional).
xlim : :class:`tuple`
Default x-limits for this classifier for plotting.
ylim : :class:`tuple`
Default y-limits for this classifier for plotting.

References
-----------
.. [#ref_1] Montreuil J F, Corriveau L, and Potter E G (2015). Formation of
albitite-hosted uranium within IOCG systems: the Southern Breccia,
Great Bear magmatic zone, Northwest Territories, Canada.
Mineralium Deposita, 50:293-325.
doi:`<https://doi.org/10.1007/s00126-014-0530-7>`__
"""

@update_docstring_references
def __init__(self, **kwargs):
src = pyrolite_datafolder(subfolder="models") / "AIOCG" / "config.json"

with open(src, "r") as f:
config = json.load(f)
kw = dict(scale=100.0, xlim=[0, 607.4], ylim=[0, 405.2])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the scale should be 1.0 here; and if data coordinates (0-1) are separated from the plot aspect (6:4), the limits should both be (0, 1).

kw.update(kwargs)
poly_config = {**config, **kw}
super().__init__(**poly_config)

def add_to_axes(self, ax=None, fill=False, axes_scale=100.0, labels=None, **kwargs):
"""
Add the AIOCG fields from the classifier to an axis.

Parameters
----------
ax : :class:`matplotlib.axes.Axes`
Axis to add the polygons to.
fill : :class:`bool`
Whether to fill the polygons.
axes_scale : :class:`float`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the default scale for this is 1.0; and then you'll want to set the aspect of the Axes to 6:4?

Maximum scale for the axes. Typically 100 (for wt%) or 1 (fractional).
labels : :class:`str`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this classifier has different sets of labels, so if you do accept the labels kwarg, there should be a logger.info(...) or that it doesn't do anything, and generally I'd remove it from the docstring.

Which labels to add to the polygons (e.g. for TAS, 'volcanic', 'intrusive'
or the field 'ID').

Returns
--------
ax : :class:`matplotlib.axes.Axes`
"""
# use and override the default add_to_axes
ax = self._add_polygons_to_axes(
ax=ax, fill=fill, axes_scale=axes_scale, **kwargs
)
rescale_by = 1.0
if axes_scale is not None: # rescale polygons to fit ax
if not np.isclose(self.default_scale, axes_scale):
rescale_by = axes_scale / self.default_scale
if labels is not None:
for k, cfg in self.fields.items():
if cfg["poly"]:
verts = np.array(cfg["poly"]) * rescale_by
x, y = get_centroid(matplotlib.patches.Polygon(verts))
label = cfg["name"][0]
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For things which don't have names, you can leave the annotation step out:

if label:
    ax.annotate(
        ...
    )

ax.annotate(
"\n".join(label.split()),
xy=(x, y),
ha="center",
va="center",
**subkwargs(kwargs, ax.annotate, matplotlib.text.Text)
)


# if cfg["poly"]:
NicolasPietteLauziere marked this conversation as resolved.
Show resolved Hide resolved
# verts = np.array(cfg["poly"]) * rescale_by
# x, y = get_centroid(matplotlib.patches.Polygon(verts))
# if "volc" in labels: # use the volcanic name
# label = cfg["name"][0]
# elif "intr" in labels: # use the intrusive name
# label = cfg["name"][-1]
# else: # use the field identifier
# label = k
# ax.annotate(
# "\n".join(label.split()),
# xy=(x, y),
# ha="center",
# va="center",
# **subkwargs(kwargs, ax.annotate, matplotlib.text.Text)
# )


ax.set_ylabel("$K/(K+Na+0.5Ca) molar$")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can probably leave out the molar in the plot axes labels, or at least move it outside the $mathtext$ section so it's rendered as standard text.

ax.set_xlabel("$(2Ca+5Fe+2Mn)+(2Ca+5Fe+2Mn+Mg+Si) molar$")
return ax

class PeralkalinityClassifier(object):
def __init__(self):
Expand Down
14 changes: 14 additions & 0 deletions test/geochem/geochem_alteration.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,19 @@ def test_WIP(self):
df.loc[:, "WIP"] = WIP(df)



class TestAIOCG_xy(unittest.TestCase):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the function is renamed to AIOCG, this should be updated also.

"""Tests the AIOCG coordinate calculation."""

def setUp(self):
self.cols = ["Si", "Ca", "Mg", "Mn", "Fe", "Ti", "Na", "K", "Al"]
self.df = pd.DataFrame(
{k: v for k, v in zip(self.cols, np.random.rand(len(self.cols), 10))}
)

def test_AIOCG_xy(self):
df = self.df
df.loc[:, "AIOCG_x"], df.loc[:, "AIOCG_y"] = AIOCG_xy(df)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the alteration function is updated to return a dataframe, this will need to be updated to reflect that.

This should also check whether the molar proportions are within the range (0, 1) as expected.


if __name__ == "__main__":
unittest.main()
26 changes: 26 additions & 0 deletions test/plot/templates/plot_templates_AIOCG.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import unittest
import matplotlib.pyplot as plt
from pyrolite.plot.templates.AIOCG import AIOCG


class TestAIOCGPlot(unittest.TestCase):
def setUp(self):
pass

def test_TAS_default(self):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These method names still refer to TAS; they should be updated - you can leave out the name if you prefer and just refer to what kind of test is being performed (e.g.test_default, test_relim).

fig, axes = plt.subplots(1)
for ax in [None, axes]:
with self.subTest(ax=ax):
ax = AIOCG(ax)

def test_TAS_relim(self):
for relim in [True, False]:
with self.subTest(relim=relim):
ax = AIOCG(relim=relim)

def tearDown(self):
plt.close("all")


if __name__ == "__main__":
unittest.main()