pyrolite 0.0.14__zip
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__init__.py +10 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/__init__.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/_version.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/alteration.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/classification.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/compositions.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/geochem.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/melts.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/norm.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/normalisation.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/plot.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/_version.py +21 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/alteration.py +66 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/classification.py +222 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/__init__.py +9 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/__pycache__/__init__.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/__pycache__/aggregate.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/__pycache__/codata.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/__pycache__/impute.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/__pycache__/renorm.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/aggregate.py +391 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/codata.py +266 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/impute.py +82 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/renorm.py +40 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/compositions.py +524 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/georoc/GEOROC_CFB_Dataset_List.csv +42 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/georoc/GEOROC_Convergent_Dataset_List.csv +42 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/georoc/GEOROC_OBFB_Dataset_List.csv +5 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/georoc/GEOROC_OIB_Dataset_List.csv +49 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/georoc/GEOROC_OceanicPlateau_Dataset_List.csv +18 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/georoc/contents.json +1 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/melts/__pycache__/env.cpython-35.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/melts/__pycache__/env.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/melts/env.py +1063 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.Ba.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.Bs.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.F.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.O1.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.O2.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.O3.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.Pc.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.Ph.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.R.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.S1.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.S2.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.S3.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.T1.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.T2.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.U1.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.U2.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.U3.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.modelfields +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.nan.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.none.modelfield +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS.clsf.gz +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/aphanitic.clsf.gz +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/gabbroic.clsf.gz +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/peralkalinity.clsf.gz +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/phaneritic.clsf.gz +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/ultramafic.clsf.gz +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/refcomp/CH_PalmeONeill2014.csv +95 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/refcomp/DDMM_WorkmanHart2005.csv +105 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/refcomp/DMM_WorkmanHart2005.csv +105 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/refcomp/DM_SaltersStrake2004.csv +95 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/refcomp/EDMM_WorkmanHart2005.csv +105 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/refcomp/PM_PalmeONeill2014.csv +95 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/timescale/geotimescale_spans.csv +180 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/geochem.py +821 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/melts.py +92 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/__init__.py +10 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/__pycache__/__init__.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/__pycache__/db.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/__pycache__/ions.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/__pycache__/mineral.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/__pycache__/sites.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/db.py +88 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/ions.py +78 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/mineral.py +587 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/sites.py +134 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/norm.py +224 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/normalisation.py +204 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/plot.py +514 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__init__.py +13 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/__init__.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/database.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/env.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/general.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/georoc.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/math.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/melts.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/multip.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/multiprocessing.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/pd.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/plot.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/skl.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/spatial.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/text.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/time.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/wfs.cpython-36.pyc +0 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/database.py +88 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/env.py +81 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/general.py +266 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/georoc.py +444 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/math.py +371 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/melts.py +397 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/multip.py +29 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/multiprocessing.py +29 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/pd.py +214 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/plot.py +345 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/skl.py +847 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/spatial.py +91 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/text.py +207 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/time.py +224 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/wfs.py +10 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite-0.0.14-py3.6.egg-info/PKG-INFO +61 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite-0.0.14-py3.6.egg-info/SOURCES.txt +83 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite-0.0.14-py3.6.egg-info/dependency_links.txt +1 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite-0.0.14-py3.6.egg-info/requires.txt +47 -0
- ProgramData/Anaconda3_64/Lib/site-packages/pyrolite-0.0.14-py3.6.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import pandas_flavor as pf
|
|
3
|
+
import numpy as np
|
|
4
|
+
import mpmath
|
|
5
|
+
import periodictable as pt
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
import functools
|
|
8
|
+
import re
|
|
9
|
+
from .comp.codata import renormalise
|
|
10
|
+
from .norm import ReferenceCompositions, RefComp, scale_multiplier
|
|
11
|
+
from .util.text import titlecase
|
|
12
|
+
from .util.pd import to_frame
|
|
13
|
+
from .util.math import *
|
|
14
|
+
from .util.general import iscollection
|
|
15
|
+
import logging
|
|
16
|
+
|
|
17
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_radii(el):
|
|
22
|
+
"""Convenience function for ionic radii."""
|
|
23
|
+
if isinstance(el, list):
|
|
24
|
+
return [get_radii(e) for e in el]
|
|
25
|
+
elif not isinstance(el, str):
|
|
26
|
+
el = str(el)
|
|
27
|
+
return _RADII[el]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def ischem(s):
|
|
31
|
+
"""
|
|
32
|
+
Checks if a string corresponds to chemical component (compositional).
|
|
33
|
+
Here simply checking whether it is a common element or oxide.
|
|
34
|
+
|
|
35
|
+
TODO: Implement checking for other compounds, e.g. carbonates.
|
|
36
|
+
"""
|
|
37
|
+
chems = set(map(str.upper, (__common_elements__ | __common_oxides__)))
|
|
38
|
+
if isinstance(s, list):
|
|
39
|
+
return [str(st).upper() in chems for st in s]
|
|
40
|
+
else:
|
|
41
|
+
return str(s).upper() in chems
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def is_isotoperatio(text):
|
|
45
|
+
"""Check if text is plausibly an isotope ratio."""
|
|
46
|
+
if text not in __common_oxides__:
|
|
47
|
+
isotopes = get_isotopes(text)
|
|
48
|
+
return len(isotopes) == 2
|
|
49
|
+
else:
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_isotopes(ratio_text):
|
|
54
|
+
"""Regex for isotope ratios."""
|
|
55
|
+
forward_isotope = r"([a-zA-Z][a-zA-Z]?[0-9][0-9]?[0-9]?)"
|
|
56
|
+
backward_isotope = r"([0-9][0-9]?[0-9]?[a-zA-Z][a-zA-Z]?)"
|
|
57
|
+
fw = re.findall(forward_isotope, ratio_text)
|
|
58
|
+
bw = re.findall(backward_isotope, ratio_text)
|
|
59
|
+
lfw, lbw = len(fw), len(bw)
|
|
60
|
+
if (lfw > 1 and lbw > 1) or ((lfw < 2) and (lbw < 2)):
|
|
61
|
+
return []
|
|
62
|
+
elif lfw == 2:
|
|
63
|
+
return fw
|
|
64
|
+
elif lbw == 2:
|
|
65
|
+
return bw
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def repr_isotope_ratio(isotope_ratio):
|
|
69
|
+
"""
|
|
70
|
+
Format an isotope ratio pair as a string.
|
|
71
|
+
|
|
72
|
+
Parameters
|
|
73
|
+
-----------
|
|
74
|
+
isotope_ratio : tuple
|
|
75
|
+
Numerator, denominator pair.
|
|
76
|
+
"""
|
|
77
|
+
if not is_isotoperatio(isotope_ratio):
|
|
78
|
+
return isotope_ratio
|
|
79
|
+
else:
|
|
80
|
+
if isinstance(isotope_ratio, str):
|
|
81
|
+
isotope_ratio = get_isotopes(isotope_ratio)
|
|
82
|
+
num, den = isotope_ratio
|
|
83
|
+
isomatch = r"([0-9][0-9]?[0-9]?)"
|
|
84
|
+
elmatch = r"([a-zA-Z][a-zA-Z]?)"
|
|
85
|
+
num_iso, num_el = re.findall(isomatch, num)[0], re.findall(elmatch, num)[0]
|
|
86
|
+
den_iso, den_el = re.findall(isomatch, den)[0], re.findall(elmatch, den)[0]
|
|
87
|
+
return "{}{}{}{}".format(num_iso, titlecase(num_el), den_iso, titlecase(den_el))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def tochem(strings: list, abbrv=["ID", "IGSN"], split_on="[\s_]+"):
|
|
91
|
+
"""
|
|
92
|
+
Converts a list of strings containing come chemical compounds to
|
|
93
|
+
appropriate case.
|
|
94
|
+
"""
|
|
95
|
+
# accomodate single string passed
|
|
96
|
+
if not type(strings) in [list, pd.core.indexes.base.Index]:
|
|
97
|
+
strings = [strings]
|
|
98
|
+
|
|
99
|
+
# translate elements and oxides
|
|
100
|
+
chems = __common_elements__ | __common_oxides__
|
|
101
|
+
trans = {str(e).upper(): str(e) for e in chems}
|
|
102
|
+
strings = [trans[str(h).upper()] if str(h).upper() in trans else h for h in strings]
|
|
103
|
+
|
|
104
|
+
# translate potential isotope ratios
|
|
105
|
+
strings = [repr_isotope_ratio(h) for h in strings]
|
|
106
|
+
return strings
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pf.register_series_method
|
|
110
|
+
@pf.register_dataframe_method
|
|
111
|
+
def to_molecular(df: pd.DataFrame, renorm=True):
|
|
112
|
+
"""
|
|
113
|
+
Converts mass quantities to molar quantities of the same order.
|
|
114
|
+
E.g.:
|
|
115
|
+
mass% --> mol%
|
|
116
|
+
mass-ppm --> mol-ppm
|
|
117
|
+
"""
|
|
118
|
+
df = to_frame(df)
|
|
119
|
+
MWs = [pt.formula(c).mass for c in df.columns]
|
|
120
|
+
if renorm:
|
|
121
|
+
return renormalise(df.div(MWs))
|
|
122
|
+
else:
|
|
123
|
+
return df.div(MWs)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pf.register_series_method
|
|
127
|
+
@pf.register_dataframe_method
|
|
128
|
+
def to_weight(df: pd.DataFrame, renorm=True):
|
|
129
|
+
"""
|
|
130
|
+
Converts molar quantities to mass quantities of the same order.
|
|
131
|
+
E.g.:
|
|
132
|
+
mol% --> mass%
|
|
133
|
+
mol-ppm --> mass-ppm
|
|
134
|
+
"""
|
|
135
|
+
df = to_frame(df)
|
|
136
|
+
MWs = [pt.formula(c).mass for c in df.columns]
|
|
137
|
+
if renorm:
|
|
138
|
+
return renormalise(df.multiply(MWs))
|
|
139
|
+
else:
|
|
140
|
+
return df.multiply(MWs)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def get_cations(oxide: str, exclude=[]):
|
|
144
|
+
"""
|
|
145
|
+
Returns the principal cations in an oxide component.
|
|
146
|
+
|
|
147
|
+
Todo: Consider implementing periodictable style return.
|
|
148
|
+
"""
|
|
149
|
+
if "O" not in exclude:
|
|
150
|
+
exclude += ["O"]
|
|
151
|
+
atms = pt.formula(oxide).atoms
|
|
152
|
+
cations = [el for el in atms.keys() if not el.__str__() in exclude]
|
|
153
|
+
return cations
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def common_elements(cutoff=92, output="string", order=None, as_set=False):
|
|
157
|
+
"""
|
|
158
|
+
Provides a list of elements up to a particular cutoff (default: including U)
|
|
159
|
+
Output options are 'formula', or 'string'.
|
|
160
|
+
|
|
161
|
+
Todo: implement ordering for e.g. incompatibility.
|
|
162
|
+
"""
|
|
163
|
+
elements = [el for el in pt.elements if not (str(el) == "n" or el.number > cutoff)]
|
|
164
|
+
|
|
165
|
+
if as_set:
|
|
166
|
+
return set(map(str, elements))
|
|
167
|
+
else:
|
|
168
|
+
if not output == "formula":
|
|
169
|
+
elements = list(map(str, elements))
|
|
170
|
+
|
|
171
|
+
if order is not None:
|
|
172
|
+
sort_function = order
|
|
173
|
+
elements = list(elements).sort(key=sort_function)
|
|
174
|
+
|
|
175
|
+
return elements
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def REE(output="string", include_extras=False):
|
|
179
|
+
"""
|
|
180
|
+
Provides the list of Rare Earth Elements
|
|
181
|
+
Output options are 'formula', or strings.
|
|
182
|
+
|
|
183
|
+
Todo: add include extras such as Y.
|
|
184
|
+
"""
|
|
185
|
+
elements = [
|
|
186
|
+
"La",
|
|
187
|
+
"Ce",
|
|
188
|
+
"Pr",
|
|
189
|
+
"Nd",
|
|
190
|
+
"Pm",
|
|
191
|
+
"Sm",
|
|
192
|
+
"Eu",
|
|
193
|
+
"Gd",
|
|
194
|
+
"Tb",
|
|
195
|
+
"Dy",
|
|
196
|
+
"Ho",
|
|
197
|
+
"Er",
|
|
198
|
+
"Tm",
|
|
199
|
+
"Yb",
|
|
200
|
+
"Lu",
|
|
201
|
+
]
|
|
202
|
+
if output == "formula":
|
|
203
|
+
elements = [getattr(pt, el) for el in elements]
|
|
204
|
+
return elements
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def common_oxides(
|
|
208
|
+
elements: list = [],
|
|
209
|
+
output="string",
|
|
210
|
+
addition: list = ["FeOT", "Fe2O3T", "LOI"],
|
|
211
|
+
exclude=["O", "He", "Ne", "Ar", "Kr", "Xe"],
|
|
212
|
+
as_set=False,
|
|
213
|
+
):
|
|
214
|
+
"""
|
|
215
|
+
Creates a list of oxides based on a list of elements.
|
|
216
|
+
Output options are 'formula', or strings.
|
|
217
|
+
|
|
218
|
+
Note: currently return FeOT and LOI even for element lists
|
|
219
|
+
not including iron or water - potential upgrade!
|
|
220
|
+
|
|
221
|
+
Todo: element verification
|
|
222
|
+
"""
|
|
223
|
+
if not elements:
|
|
224
|
+
elements = [
|
|
225
|
+
el for el in common_elements(output="formula") if not str(el) in exclude
|
|
226
|
+
]
|
|
227
|
+
else:
|
|
228
|
+
# Check that all elements input are indeed elements..
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
oxides = [ox for el in elements for ox in simple_oxides(el, output=output)]
|
|
232
|
+
|
|
233
|
+
if as_set:
|
|
234
|
+
return set(map(str, oxides + addition))
|
|
235
|
+
else:
|
|
236
|
+
if output != "formula":
|
|
237
|
+
oxides = list(map(str, oxides + addition))
|
|
238
|
+
return oxides
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def simple_oxides(cation, output="string"):
|
|
242
|
+
"""
|
|
243
|
+
Creates a list of oxides for a cationic element
|
|
244
|
+
(oxide of ions with c=1+ and above).
|
|
245
|
+
"""
|
|
246
|
+
try:
|
|
247
|
+
if not isinstance(cation, pt.core.Element):
|
|
248
|
+
catstr = titlecase(cation) # edge case of lowercase str such as 'cs'
|
|
249
|
+
cation = getattr(pt, catstr)
|
|
250
|
+
except AttributeError:
|
|
251
|
+
raise Exception("You must select a cation to obtain oxides.")
|
|
252
|
+
ions = [c for c in cation.ions if c > 0] # Use only positive charges
|
|
253
|
+
|
|
254
|
+
# for 3.6+, could use f'{cation}{1}O{c//2}', f'{cation}{2}O{c}'
|
|
255
|
+
oxides = [
|
|
256
|
+
str(cation) + str(1) + "O" + str(c // 2)
|
|
257
|
+
if not c % 2
|
|
258
|
+
else str(cation) + str(2) + "O" + str(c)
|
|
259
|
+
for c in ions
|
|
260
|
+
]
|
|
261
|
+
oxides = [pt.formula(ox) for ox in oxides]
|
|
262
|
+
import periodictable
|
|
263
|
+
if not output == "formula":
|
|
264
|
+
oxides = [str(ox) for ox in oxides]
|
|
265
|
+
return oxides
|
|
266
|
+
|
|
267
|
+
@pf.register_series_method
|
|
268
|
+
@pf.register_dataframe_method
|
|
269
|
+
def devolatilise(
|
|
270
|
+
df: pd.DataFrame,
|
|
271
|
+
exclude=["H2O", "H2O_PLUS", "H2O_MINUS", "CO2", "LOI"],
|
|
272
|
+
renorm=True,
|
|
273
|
+
):
|
|
274
|
+
"""
|
|
275
|
+
Recalculates components after exclusion of volatile phases (e.g. H2O, CO2).
|
|
276
|
+
"""
|
|
277
|
+
keep = [i for i in df.columns if not i in exclude]
|
|
278
|
+
if renorm:
|
|
279
|
+
return renormalise(df.loc[:, keep])
|
|
280
|
+
else:
|
|
281
|
+
return df.loc[:, keep]
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def oxide_conversion(oxin, oxout):
|
|
285
|
+
"""
|
|
286
|
+
Generates a function to convert oxide components between
|
|
287
|
+
two elemental oxides, for use in redox recalculations.
|
|
288
|
+
"""
|
|
289
|
+
if not (isinstance(oxin, pt.formulas.Formula) or isinstance(oxin, pt.core.Element)):
|
|
290
|
+
oxin = pt.formula(oxin)
|
|
291
|
+
if not (
|
|
292
|
+
isinstance(oxout, pt.formulas.Formula) or isinstance(oxout, pt.core.Element)
|
|
293
|
+
):
|
|
294
|
+
oxout = pt.formula(oxout)
|
|
295
|
+
|
|
296
|
+
inatoms = {k: v for (k, v) in oxin.atoms.items() if not k.__str__() == "O"}
|
|
297
|
+
in_els = inatoms.keys()
|
|
298
|
+
outatoms = {k: v for (k, v) in oxout.atoms.items() if not k.__str__() == "O"}
|
|
299
|
+
out_els = outatoms.keys()
|
|
300
|
+
assert len(inatoms) == len(outatoms) == 1 # Assertion of simple oxide
|
|
301
|
+
assert in_els == out_els # Need to be dealilng with the same element!
|
|
302
|
+
# Moles of product vs. moles of reactant
|
|
303
|
+
cation_coefficient = list(inatoms.values())[0] / list(outatoms.values())[0]
|
|
304
|
+
|
|
305
|
+
def convert_series(dfser: pd.Series, molecular=False):
|
|
306
|
+
if molecular:
|
|
307
|
+
factor = cation_coefficient
|
|
308
|
+
else:
|
|
309
|
+
factor = cation_coefficient * oxout.mass / oxin.mass
|
|
310
|
+
converted = dfser * factor
|
|
311
|
+
return converted
|
|
312
|
+
|
|
313
|
+
doc = "Convert series from " + str(oxin) + " to " + str(oxout)
|
|
314
|
+
convert_series.__doc__ = doc
|
|
315
|
+
return convert_series
|
|
316
|
+
|
|
317
|
+
@pf.register_series_method
|
|
318
|
+
@pf.register_dataframe_method
|
|
319
|
+
def recalculate_Fe(
|
|
320
|
+
df: pd.DataFrame, to_species="FeOT", renorm=True, total_suffix="T", logdata=False
|
|
321
|
+
):
|
|
322
|
+
"""
|
|
323
|
+
Recalculates abundances of iron, and normalises a dataframe to contain only one
|
|
324
|
+
oxide species.
|
|
325
|
+
|
|
326
|
+
Consider reimplementing total suffix as a lambda formatting function
|
|
327
|
+
to deal with cases of prefixes, capitalisation etc.
|
|
328
|
+
|
|
329
|
+
Automatic generation of multiple redox species from dataframes
|
|
330
|
+
would also be a natural improvement.
|
|
331
|
+
|
|
332
|
+
# todo: update to incorporate Fe and transformation from multiple oxides to one
|
|
333
|
+
"""
|
|
334
|
+
# Assuming either (a single column) or (FeO + Fe2O3) are reported
|
|
335
|
+
# Fe columns - FeO, Fe2O3, FeOT, Fe2O3T
|
|
336
|
+
FeO = pt.formula("FeO")
|
|
337
|
+
Fe2O3 = pt.formula("Fe2O3")
|
|
338
|
+
out_species = pt.formula(to_species.strip(total_suffix))
|
|
339
|
+
|
|
340
|
+
dfc = df.copy(deep=True)
|
|
341
|
+
ox_species = ["Fe2O3", "Fe2O3"]
|
|
342
|
+
ox_species += [i + total_suffix for i in ox_species]
|
|
343
|
+
ox_in_df = [i for i in ox_species if i in dfc.columns]
|
|
344
|
+
red_species = ["Fe", "FeO"]
|
|
345
|
+
red_species += [i + total_suffix for i in red_species]
|
|
346
|
+
red_in_df = [i for i in red_species if i in dfc.columns]
|
|
347
|
+
|
|
348
|
+
if logdata:
|
|
349
|
+
dfc.loc[:, ox_in_df + red_in_df] = dfc.loc[:, ox_in_df + red_in_df].applymap(
|
|
350
|
+
np.exp
|
|
351
|
+
)
|
|
352
|
+
fe_species = ox_in_df + red_in_df
|
|
353
|
+
|
|
354
|
+
out_sum = np.zeros(df.index.size)
|
|
355
|
+
|
|
356
|
+
for f in fe_species:
|
|
357
|
+
conv = oxide_conversion(pt.formula(f.strip(total_suffix)), out_species)
|
|
358
|
+
component = dfc.loc[:, f].fillna(0).apply(conv)
|
|
359
|
+
component[component < 0] = 0
|
|
360
|
+
out_sum += component
|
|
361
|
+
|
|
362
|
+
out_sum[out_sum <= 0.0] = np.nan
|
|
363
|
+
if logdata:
|
|
364
|
+
out_sum = np.exp(out_sum)
|
|
365
|
+
|
|
366
|
+
dfc.loc[:, to_species] = out_sum
|
|
367
|
+
dfc = dfc.drop(columns=[i for i in fe_species if not i == to_species])
|
|
368
|
+
if renorm:
|
|
369
|
+
return renormalise(dfc)
|
|
370
|
+
else:
|
|
371
|
+
return dfc
|
|
372
|
+
|
|
373
|
+
@pf.register_series_method
|
|
374
|
+
@pf.register_dataframe_method
|
|
375
|
+
def recalculate_redox(
|
|
376
|
+
df: pd.DataFrame, to_oxidised=True, renorm=True, total_suffix="T", logdata=False
|
|
377
|
+
):
|
|
378
|
+
"""
|
|
379
|
+
Recalculates abundances of redox-sensitive components (particularly Fe),
|
|
380
|
+
and normalises a dataframe to contain only one oxide species for a given
|
|
381
|
+
element.
|
|
382
|
+
|
|
383
|
+
Consider reimplementing total suffix as a lambda formatting function
|
|
384
|
+
to deal with cases of prefixes, capitalisation etc.
|
|
385
|
+
|
|
386
|
+
Automatic generation of multiple redox species from dataframes
|
|
387
|
+
would also be a natural improvement.
|
|
388
|
+
|
|
389
|
+
"""
|
|
390
|
+
# Assuming either (a single column) or (FeO + Fe2O3) are reported
|
|
391
|
+
# Fe columns - FeO, Fe2O3, FeOT, Fe2O3T
|
|
392
|
+
FeO = pt.formula("FeO")
|
|
393
|
+
Fe2O3 = pt.formula("Fe2O3")
|
|
394
|
+
dfc = df.copy(deep=True)
|
|
395
|
+
ox_species = ["Fe2O3", "Fe2O3" + total_suffix]
|
|
396
|
+
ox_in_df = [i for i in ox_species if i in dfc.columns]
|
|
397
|
+
red_species = ["FeO", "FeO" + total_suffix]
|
|
398
|
+
red_in_df = [i for i in red_species if i in dfc.columns]
|
|
399
|
+
if logdata:
|
|
400
|
+
dfc.loc[:, ox_in_df + red_in_df] = dfc.loc[:, ox_in_df + red_in_df].applymap(
|
|
401
|
+
np.exp
|
|
402
|
+
)
|
|
403
|
+
if to_oxidised:
|
|
404
|
+
key = "Fe2O3T"
|
|
405
|
+
oxFe = oxide_conversion(FeO, Fe2O3)
|
|
406
|
+
Fe2O3T = dfc.loc[:, ox_in_df].fillna(0).sum(axis=1) + oxFe(
|
|
407
|
+
dfc.loc[:, red_in_df].fillna(0)
|
|
408
|
+
).sum(axis=1)
|
|
409
|
+
dfc.loc[:, key] = Fe2O3T
|
|
410
|
+
Fe2O3T[Fe2O3T <= 0] = np.nan
|
|
411
|
+
to_drop = red_in_df + [i for i in ox_in_df if not i.endswith(total_suffix)]
|
|
412
|
+
else:
|
|
413
|
+
key = "FeOT"
|
|
414
|
+
reduceFe = oxide_conversion(Fe2O3, FeO)
|
|
415
|
+
FeOT = dfc.loc[:, red_in_df].fillna(0).sum(axis=1) + reduceFe(
|
|
416
|
+
dfc.loc[:, ox_in_df].fillna(0)
|
|
417
|
+
).sum(axis=1)
|
|
418
|
+
FeOT[FeOT <= 0] = np.nan
|
|
419
|
+
dfc.loc[:, key] = FeOT
|
|
420
|
+
to_drop = ox_in_df + [i for i in red_in_df if not i.endswith(total_suffix)]
|
|
421
|
+
|
|
422
|
+
if logdata:
|
|
423
|
+
dfc.loc[:, key] = np.exp(dfc.loc[:, key].values)
|
|
424
|
+
|
|
425
|
+
dfc = dfc.drop(columns=to_drop)
|
|
426
|
+
|
|
427
|
+
if renorm:
|
|
428
|
+
return renormalise(dfc)
|
|
429
|
+
else:
|
|
430
|
+
return dfc
|
|
431
|
+
|
|
432
|
+
@pf.register_series_method
|
|
433
|
+
@pf.register_dataframe_method
|
|
434
|
+
def aggregate_cation(
|
|
435
|
+
df: pd.DataFrame,
|
|
436
|
+
cation=None,
|
|
437
|
+
oxide=None,
|
|
438
|
+
form="oxide",
|
|
439
|
+
unit_scale=scale_multiplier("Wt%", "Wt%"),
|
|
440
|
+
logdata=False,
|
|
441
|
+
):
|
|
442
|
+
"""
|
|
443
|
+
Aggregates cation information from oxide and elemental components
|
|
444
|
+
to a single series. Allows simultaneous scaling (e.g. from ppm to wt%).
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
df : pd.DataFrame
|
|
449
|
+
DataFrame for which to aggregate cation data.
|
|
450
|
+
cation : str
|
|
451
|
+
Name of cation to aggregate.
|
|
452
|
+
oxide:
|
|
453
|
+
Name of oxide to aggregate.
|
|
454
|
+
form: {'oxide', 'element'}
|
|
455
|
+
Whether to aggregate to oxide or elemental form.
|
|
456
|
+
unit_scale:
|
|
457
|
+
The scale factor difference between the components. Unity if both have the same
|
|
458
|
+
units. Can be converted using scale_multiplier: e.g.
|
|
459
|
+
scale_multiplier("Wt%", "ppm")
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
Todo
|
|
463
|
+
-------
|
|
464
|
+
Needs to also implement a 'molecular' version.
|
|
465
|
+
|
|
466
|
+
"""
|
|
467
|
+
|
|
468
|
+
dfc = df.copy()
|
|
469
|
+
# Should first check that neither the element or oxide is present more than once
|
|
470
|
+
assert not ((cation is None) and (oxide is None))
|
|
471
|
+
if cation is not None and oxide is not None:
|
|
472
|
+
elstr = str(cation)
|
|
473
|
+
oxstr = str(oxide)
|
|
474
|
+
elif oxide is not None:
|
|
475
|
+
oxstr = str(oxide)
|
|
476
|
+
elstr = str(get_cations(oxide)[0])
|
|
477
|
+
elif cation is not None:
|
|
478
|
+
elstr = str(cation)
|
|
479
|
+
potential_ox = simple_oxides(elstr)
|
|
480
|
+
oxstr = [o for o in dfc.columns if o in potential_ox][0]
|
|
481
|
+
assert oxstr, "Oxidation state unknown. " \
|
|
482
|
+
"Please specify desired oxide from {}.".format(potential_ox)
|
|
483
|
+
|
|
484
|
+
el, ox = pt.formula(elstr), pt.formula(oxstr)
|
|
485
|
+
|
|
486
|
+
for c in [elstr, oxstr]:
|
|
487
|
+
if not c in df.columns:
|
|
488
|
+
logger.info("Adding {} column.".format(c))
|
|
489
|
+
dfc[c] = np.nan
|
|
490
|
+
|
|
491
|
+
eldata = dfc.loc[:, elstr].values
|
|
492
|
+
oxdata = dfc.loc[:, oxstr].values
|
|
493
|
+
if logdata:
|
|
494
|
+
eldata = np.exp(eldata)
|
|
495
|
+
oxdata = np.exp(oxdata)
|
|
496
|
+
|
|
497
|
+
if form == "oxide":
|
|
498
|
+
if unit_scale is None:
|
|
499
|
+
unit_scale = 1.0
|
|
500
|
+
assert unit_scale > 0
|
|
501
|
+
convert_function = oxide_conversion(ox, el)
|
|
502
|
+
conv_values = convert_function(eldata) * unit_scale
|
|
503
|
+
totals = np.nansum(np.vstack((oxdata, conv_values)), axis=0)
|
|
504
|
+
elif form == "element":
|
|
505
|
+
if unit_scale is None:
|
|
506
|
+
unit_scale = 1.0
|
|
507
|
+
assert unit_scale > 0
|
|
508
|
+
convert_function = oxide_conversion(el, ox)
|
|
509
|
+
conv_values = convert_function(oxdata) * unit_scale
|
|
510
|
+
totals = np.nansum(np.vstack((eldata, conv_values)), axis=0)
|
|
511
|
+
|
|
512
|
+
totals[np.isclose(totals, 0)] = np.nan
|
|
513
|
+
|
|
514
|
+
if logdata:
|
|
515
|
+
totals = np.log(totals)
|
|
516
|
+
|
|
517
|
+
if form == "oxide":
|
|
518
|
+
dfc.loc[:, oxstr] = totals
|
|
519
|
+
dfc.drop(columns=[elstr], inplace=True)
|
|
520
|
+
assert elstr not in dfc.columns
|
|
521
|
+
else:
|
|
522
|
+
dfc.loc[:, elstr] = totals
|
|
523
|
+
dfc.drop(columns=[oxstr], inplace=True)
|
|
524
|
+
assert oxstr not in dfc.columns
|
|
525
|
+
|
|
526
|
+
return dfc
|
|
527
|
+
|
|
528
|
+
@pf.register_series_method
|
|
529
|
+
@pf.register_dataframe_method
|
|
530
|
+
def check_multiple_cation_inclusion(df, exclude=["LOI", "FeOT", "Fe2O3T"]):
|
|
531
|
+
"""
|
|
532
|
+
Returns cations which are present in both oxide and elemental form.
|
|
533
|
+
|
|
534
|
+
Todo: Options for output (string/formula).
|
|
535
|
+
"""
|
|
536
|
+
major_components = [i for i in __common_oxides__ if i in df.columns]
|
|
537
|
+
elements_as_majors = [
|
|
538
|
+
get_cations(oxide)[0] for oxide in major_components if not oxide in exclude
|
|
539
|
+
]
|
|
540
|
+
elements_as_traces = [
|
|
541
|
+
c for c in common_elements(output="formula") if str(c) in df.columns
|
|
542
|
+
]
|
|
543
|
+
return set([el for el in elements_as_majors if el in elements_as_traces])
|
|
544
|
+
|
|
545
|
+
@pf.register_series_method
|
|
546
|
+
@pf.register_dataframe_method
|
|
547
|
+
def convert_chemistry(df, columns=[], logdata=False, renorm=False):
|
|
548
|
+
"""
|
|
549
|
+
Tries to convert a dataframe with one set of components to another.
|
|
550
|
+
|
|
551
|
+
Parameters
|
|
552
|
+
-----------
|
|
553
|
+
df : pd.DataFrame
|
|
554
|
+
Dataframe to convert.
|
|
555
|
+
columns : list, set
|
|
556
|
+
Set of columns to try to extract from the dataframe.
|
|
557
|
+
"""
|
|
558
|
+
current = df.columns
|
|
559
|
+
ok = [i for i in columns if i in current]
|
|
560
|
+
get = [i for i in columns if i not in current]
|
|
561
|
+
multiples = check_multiple_cation_inclusion(df)
|
|
562
|
+
oxides = common_oxides(addition=[])
|
|
563
|
+
elements = common_elements()
|
|
564
|
+
Fe_parts = ["Fe", "FeO", "Fe2O3", "Fe2O3T", "FeOT"]
|
|
565
|
+
|
|
566
|
+
# Aggregate the columns which are otherwise OK
|
|
567
|
+
for o in ok:
|
|
568
|
+
if o in oxides + elements:
|
|
569
|
+
elem = get_cations(o)[0]
|
|
570
|
+
if elem in multiples:
|
|
571
|
+
if o in oxides:
|
|
572
|
+
df = aggregate_cation(
|
|
573
|
+
df, cation=elem, oxide=o, form="oxide", logdata=logdata
|
|
574
|
+
)
|
|
575
|
+
logger.info("Aggregating from {} to {}".format(elem, o))
|
|
576
|
+
else:
|
|
577
|
+
potential_oxides = simple_oxides(o)
|
|
578
|
+
present_oxides = [p for p in potential_oxides if p in current]
|
|
579
|
+
for ox in present_oxides: # aggregate all the relevant oxides
|
|
580
|
+
df = aggregate_cation(
|
|
581
|
+
df, cation=o, oxide=ox, form="element", logdata=logdata
|
|
582
|
+
)
|
|
583
|
+
logger.info("Aggregating from {} to {}".format(ox, o))
|
|
584
|
+
if o in Fe_parts:
|
|
585
|
+
pass
|
|
586
|
+
|
|
587
|
+
# --- Try to get the new columns ----
|
|
588
|
+
for g in get:
|
|
589
|
+
if g in oxides:
|
|
590
|
+
elem = get_cations(g)[0]
|
|
591
|
+
oxide = g
|
|
592
|
+
logger.info(
|
|
593
|
+
"Getting new column {oxide} from {elem}".format(oxide=oxide, elem=elem)
|
|
594
|
+
)
|
|
595
|
+
df = aggregate_cation(
|
|
596
|
+
df, cation=elem, oxide=oxide, form="oxide", logdata=logdata
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
elif g in elements:
|
|
600
|
+
elem = g
|
|
601
|
+
potential_oxides = simple_oxides(g)
|
|
602
|
+
present_oxides = [p for p in potential_oxides if p in current]
|
|
603
|
+
for ox in present_oxides: # aggregate all the relevant oxides
|
|
604
|
+
logger.info(
|
|
605
|
+
"Getting new column {elem} from {oxide}".format(oxide=ox, elem=elem)
|
|
606
|
+
)
|
|
607
|
+
df = aggregate_cation(
|
|
608
|
+
df, cation=elem, oxide=ox, form="element", logdata=logdata
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
# --- Try to get the new columns - iron redox section ----
|
|
612
|
+
get_fe = [i for i in columns if i in Fe_parts]
|
|
613
|
+
for f in get_fe:
|
|
614
|
+
current_Fe = [i for i in Fe_parts if i in df.columns]
|
|
615
|
+
c_fe_str = ", ".join(current_Fe)
|
|
616
|
+
df = recalculate_Fe(df, to_species=f, renorm=False, logdata=logdata)
|
|
617
|
+
logger.info("Reducing {} to {}.".format(c_fe_str, f))
|
|
618
|
+
|
|
619
|
+
ratios = [i for i in columns if "/" in i and i in get]
|
|
620
|
+
|
|
621
|
+
for r in ratios:
|
|
622
|
+
logger.info("Adding Ratio: {}".format(r))
|
|
623
|
+
num, den = r.split("/")
|
|
624
|
+
df.loc[:, r] = df.loc[:, num] / df.loc[:, den]
|
|
625
|
+
# df = add_ratio(df, r)
|
|
626
|
+
|
|
627
|
+
remaining = [i for i in columns if i not in df.columns]
|
|
628
|
+
assert not len(remaining), "Columns not attained: {}".format(", ".join(remaining))
|
|
629
|
+
if renorm:
|
|
630
|
+
logger.info("Recalculation Done, Renormalising")
|
|
631
|
+
return renormalise(df.loc[:, columns])
|
|
632
|
+
else:
|
|
633
|
+
logger.info("Recalculation Done.")
|
|
634
|
+
return df.loc[:, columns]
|
|
635
|
+
|
|
636
|
+
@pf.register_series_method
|
|
637
|
+
@pf.register_dataframe_method
|
|
638
|
+
def add_ratio(
|
|
639
|
+
df: pd.DataFrame, ratio: str, alias: str = "", norm_to=None, convert=lambda x: x
|
|
640
|
+
):
|
|
641
|
+
"""
|
|
642
|
+
Add a ratio of components A and B, given in the form of string 'A/B'.
|
|
643
|
+
Returned series be assigned an alias name.
|
|
644
|
+
|
|
645
|
+
Parameters
|
|
646
|
+
-----------
|
|
647
|
+
df: pd.DataFrame
|
|
648
|
+
Dataframe to append ratio to.
|
|
649
|
+
ratio: str
|
|
650
|
+
String decription of ratio in the form A/B[_n].
|
|
651
|
+
alias: str
|
|
652
|
+
Alternate name for ratio to be used as column name.
|
|
653
|
+
norm_to: {None, RefComp, str}
|
|
654
|
+
Reference composition to normalise to.
|
|
655
|
+
convert:
|
|
656
|
+
Data processing function to be calculated prior to ratio.
|
|
657
|
+
"""
|
|
658
|
+
|
|
659
|
+
num, den = ratio.split("/")
|
|
660
|
+
_to_norm = False
|
|
661
|
+
if den.lower().endswith("_n"):
|
|
662
|
+
den = titlecase(den.lower().replace("_n", ""))
|
|
663
|
+
_to_norm = True
|
|
664
|
+
assert titlecase(num) in df.columns
|
|
665
|
+
assert titlecase(den) in df.columns
|
|
666
|
+
|
|
667
|
+
if _to_norm or (norm_to is not None):
|
|
668
|
+
if isinstance(norm_to, str):
|
|
669
|
+
norm = ReferenceCompositions()[norm_to]
|
|
670
|
+
num_n, den_n = norm[num].value, norm[den].value
|
|
671
|
+
elif isinstance(norm_to, RefComp):
|
|
672
|
+
num_n, den_n = norm_to[num].value, norm_to[den].value
|
|
673
|
+
elif iscollection(norm_to): # list, iterable, pd.Index etc
|
|
674
|
+
num_n, den_n = norm_to
|
|
675
|
+
else:
|
|
676
|
+
norm = ReferenceCompositions()["Chondrite_PON"]
|
|
677
|
+
num_n, den_n = norm[num].value, norm[den].value
|
|
678
|
+
|
|
679
|
+
name = [ratio if not alias else alias][0]
|
|
680
|
+
conv = convert(df.loc[:, [num, den]])
|
|
681
|
+
conv.loc[(conv[den] == 0.0) | (conv[num] == 0.0), den] = np.nan # avoid 0, inf
|
|
682
|
+
df.loc[:, name] = conv.loc[:, num] / conv.loc[:, den]
|
|
683
|
+
return df
|
|
684
|
+
|
|
685
|
+
@pf.register_series_method
|
|
686
|
+
@pf.register_dataframe_method
|
|
687
|
+
def add_MgNo(df: pd.DataFrame, molecularIn=False, elemental=False, components=False):
|
|
688
|
+
|
|
689
|
+
if not molecularIn:
|
|
690
|
+
if components:
|
|
691
|
+
# Iron is split into species
|
|
692
|
+
df.loc[:, "Mg#"] = (
|
|
693
|
+
df["MgO"]
|
|
694
|
+
/ pt.formula("MgO").mass
|
|
695
|
+
/ (
|
|
696
|
+
df["MgO"] / pt.formula("MgO").mass
|
|
697
|
+
+ df["FeO"] / pt.formula("FeO").mass
|
|
698
|
+
)
|
|
699
|
+
)
|
|
700
|
+
else:
|
|
701
|
+
# Total iron is used
|
|
702
|
+
assert "FeOT" in df.columns
|
|
703
|
+
df.loc[:, "Mg#"] = (
|
|
704
|
+
df["MgO"]
|
|
705
|
+
/ pt.formula("MgO").mass
|
|
706
|
+
/ (
|
|
707
|
+
df["MgO"] / pt.formula("MgO").mass
|
|
708
|
+
+ df["FeOT"] / pt.formula("FeO").mass
|
|
709
|
+
)
|
|
710
|
+
)
|
|
711
|
+
else:
|
|
712
|
+
if not elemental:
|
|
713
|
+
# Molecular Oxides
|
|
714
|
+
df.loc[:, "Mg#"] = df["MgO"] / (df["MgO"] + df["FeO"])
|
|
715
|
+
else:
|
|
716
|
+
# Molecular Elemental
|
|
717
|
+
df.loc[:, "Mg#"] = df["Mg"] / (df["Mg"] + df["Fe"])
|
|
718
|
+
|
|
719
|
+
@pf.register_series_method
|
|
720
|
+
@pf.register_dataframe_method
|
|
721
|
+
def lambda_lnREE(
|
|
722
|
+
df,
|
|
723
|
+
norm_to="Chondrite_PON",
|
|
724
|
+
exclude=["Pm", "Eu"],
|
|
725
|
+
params=None,
|
|
726
|
+
degree=5,
|
|
727
|
+
append=[],
|
|
728
|
+
**kwargs
|
|
729
|
+
):
|
|
730
|
+
"""
|
|
731
|
+
Calculates lambda coefficients for a given set of REE data, normalised
|
|
732
|
+
to a specific composition. Lambda factors are given for the
|
|
733
|
+
radii vs. ln(REE/NORM) polynomical combination.
|
|
734
|
+
|
|
735
|
+
TODO: Operate only on valid rows.
|
|
736
|
+
"""
|
|
737
|
+
non_null_cols = df.columns[~df.isnull().all(axis=0)]
|
|
738
|
+
ree = [
|
|
739
|
+
i
|
|
740
|
+
for i in REE()
|
|
741
|
+
if i in df.columns
|
|
742
|
+
and (not str(i) in exclude)
|
|
743
|
+
and (str(i) in non_null_cols or i in non_null_cols)
|
|
744
|
+
] # no promethium
|
|
745
|
+
radii = np.array(get_radii(ree))
|
|
746
|
+
|
|
747
|
+
if params is None:
|
|
748
|
+
params = OP_constants(radii, degree=degree)
|
|
749
|
+
else:
|
|
750
|
+
degree = len(params)
|
|
751
|
+
|
|
752
|
+
null_in_row = pd.isnull(df.loc[:, ree]).any(axis=1)
|
|
753
|
+
norm_df = df.loc[~null_in_row, ree].copy() # initialize normdf
|
|
754
|
+
|
|
755
|
+
labels = [chr(955) + str(d) for d in range(degree)]
|
|
756
|
+
|
|
757
|
+
if norm_to is not None: # None = already normalised data
|
|
758
|
+
if isinstance(norm_to, str):
|
|
759
|
+
norm = ReferenceCompositions()[norm_to]
|
|
760
|
+
norm_abund = np.array([norm[str(el)].value for el in ree])
|
|
761
|
+
elif isinstance(norm_to, RefComp):
|
|
762
|
+
norm_abund = np.array([getattr(norm_to, str(e)) for e in ree])
|
|
763
|
+
else: # list, iterable, pd.Index etc
|
|
764
|
+
norm_abund = np.array([i for i in norm_abund])
|
|
765
|
+
assert len(norm_abund) == len(ree)
|
|
766
|
+
|
|
767
|
+
norm_df.loc[:, ree] = np.divide(norm_df.loc[:, ree].values, norm_abund)
|
|
768
|
+
|
|
769
|
+
norm_df.loc[(norm_df <= 0.0).any(axis=1), :] = np.nan # remove zero or below
|
|
770
|
+
norm_df.loc[:, ree] = norm_df.loc[:, ree].applymap(np.log)
|
|
771
|
+
|
|
772
|
+
lambdadf = pd.DataFrame(index=df.index, columns=labels)
|
|
773
|
+
lambda_partial = functools.partial(
|
|
774
|
+
lambdas, xs=radii, params=params, degree=degree, **kwargs
|
|
775
|
+
) # pass kwargs to lambdas
|
|
776
|
+
# apply along rows
|
|
777
|
+
lambdadf.loc[~null_in_row, labels] = np.apply_along_axis(
|
|
778
|
+
lambda_partial, 1, norm_df.values
|
|
779
|
+
)
|
|
780
|
+
lambdadf.loc[(lambdadf == 0.0).all(axis=1), :] = np.nan
|
|
781
|
+
if append:
|
|
782
|
+
# append the smooth f(radii) function to the dataframe
|
|
783
|
+
func_partial = functools.partial(
|
|
784
|
+
lambda_poly_func, pxs=radii, params=params, degree=degree
|
|
785
|
+
)
|
|
786
|
+
if "function" in append:
|
|
787
|
+
lambdadf["lambda_poly_func"] = np.apply_along_axis(
|
|
788
|
+
func_partial, 1, lambdadf.values
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
lambdadf = lambdadf.apply(pd.to_numeric, errors="coerce")
|
|
792
|
+
assert lambdadf.index.size == df.index.size
|
|
793
|
+
return lambdadf
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
__common_oxides__ = common_oxides(as_set=True)
|
|
797
|
+
__common_elements__ = common_elements(as_set=True)
|
|
798
|
+
__REE = REE()
|
|
799
|
+
_RADII = {
|
|
800
|
+
str(k): v
|
|
801
|
+
for (k, v) in zip(
|
|
802
|
+
REE(),
|
|
803
|
+
[
|
|
804
|
+
1.160,
|
|
805
|
+
1.143,
|
|
806
|
+
1.126,
|
|
807
|
+
1.109,
|
|
808
|
+
1.093,
|
|
809
|
+
1.079,
|
|
810
|
+
1.066,
|
|
811
|
+
1.053,
|
|
812
|
+
1.040,
|
|
813
|
+
1.027,
|
|
814
|
+
1.015,
|
|
815
|
+
1.004,
|
|
816
|
+
0.994,
|
|
817
|
+
0.985,
|
|
818
|
+
0.977,
|
|
819
|
+
],
|
|
820
|
+
)
|
|
821
|
+
}
|