Skip to main content

petropandas

Python testing codecov

A pandas-accessor library for processing electron microprobe (EMPA) mineral analyses — convert oxide wt% to APFU, compute structural formulas, estimate Fe³⁺/Fe²⁺, calculate end-members, validate stoichiometry, and produce publication-ready plots.

Install

uv sync

Optional dependencies

uv sync --extra lab    # Jupyter notebook workflows
uv sync --extra tests  # running the test suite
uv sync --extra docs   # building the documentation

Contributors should instead run uv sync --dev, which installs all of the above plus ruff and pre-commit.

On Linux, pandas' clipboard I/O (pandas.read_clipboard() / DataFrame.to_clipboard()) needs a system-level clipboard utility — install xclip or xsel via your system package manager (e.g. sudo apt install xclip).

Quick start

from petropandas import pd, Grt
from petropandas.data import minerals as df
# minerals is a DataFrame of example analyses

Filter to oxide columns:

df.oxides()

Re-order columns in standard petrological order (SiO₂ first, volatiles last):

df.oxides.sorted()

Select rows by searching for "Garnet" in column "Mineral":

g = df.oxides.select("Garnet", on="Mineral")

Compute element APFU with Fe³⁺/Fe²⁺ splitting for garnet (12 oxygens):

g.mineral.apfu(Grt)

Site-allocated structural formula:

g.mineral.site_allocations(Grt)

End-member proportions:

g.mineral.end_members(Grt)

Validate analysis quality against ideal stoichiometry:

g.mineral.check_stoichiometry(Grt)

Returns a DataFrame of 0–1 scores per criterion (1 = perfect fit).

Supported minerals

Mineral Instance O₂ Cations Fe method End-members
Garnet Grt 12 8 Droop Prp, Alm, Sps
Garnet (Fe³⁺) GrtFe3 12 8 Matrix inversion Prp, Alm, Sps, Grs, Adr, Uvr
Feldspar Fsp 8 5 An, Ab, Or
Clinopyroxene Cpx 6 4 Droop Jd, Ae, Wo, En, Fs
Orthopyroxene Opx 6 4 Droop MgTs, Wo, En, Fs
Muscovite Ms 11 7 Al-Cel, Fe-Al-Cel, Prl, Mrg, Pg, Ms, Trioct
Biotite Bt 11 7 Phl, Ann, Eas, Sid, Dioct
Staurolite St 48 Fe-St, Mg-St, Zn-St, Mn-St
Chlorite Chl 14* Clin, Cham, Mg-Sud, Fe-Sud
Epidote Ep 12.5 8 FeO→Fe₂O₃ Czo, Ep, Pmn, Muk, Taw
Amphibole Amp 23 15 Schumacher Tr, Act, Ed, F-Ed, Prg, F-Prg, Tsch, Rct, Win, Glau, F-Glau, Rieb, Mg-Rieb
Titanite Ttn 5 3 FeO→Fe₂O₃ Ttn, Al-Ttn, Fe-Ttn, Mal, Other
Chloritoid Cld 12 8 Droop Cld, Mgcld, Mncld
Cordierite Crd 18 11 H₂O-Crd, Mg-Crd, Fe-Crd, Mn-Crd
Ilmenite Ilm 3 2 Droop Ilm, Gk, Pph, Hem, Chr
Spinel Spl 4 3 Droop Spl, Herc, Chrm, Mtc, Gahn, Frank, Jac, Ulv, Spss

* Chlorite uses 28-charge normalization (n_oxygens=14 effective).

All 16 instances above are also registered in mdb (from petropandas import mdb), a small lookup registry:

from petropandas import mdb

list(mdb.all())  # every built-in Mineral instance
mdb.names  # ["Garnet", "GarnetFe3", "Feldspar", ...]
mdb.abbreviations  # ["Grt", "GrtFe3", "Fsp", ...]
mdb.by_name("garnet")  # Grt (case-insensitive)
mdb.by_abbreviation("GRT")  # Grt (case-insensitive)
repr(mdb)  # "Mineral database (16 minerals available)"

mdb only covers these 16 built-ins, not the hpxeos a-x phases below.

THERMOCALC activity-composition (a-x) solution models are available through the petropandas.hpxeos subpackage, covering three real THERMOCALC axfiles — hpxeos.metapelite, hpxeos.metabasite, hpxeos.igneous — each exposing ready-to-use TC_<abbreviation> instances (e.g. TC_g, TC_pl4tr) alongside their Phase classes:

from petropandas.hpxeos.metapelite import TC_g

df.mineral.apfu(TC_g)
df.mineral.site_allocations(TC_g)
df.mineral.end_members(TC_g)
df.mineral.check_stoichiometry(TC_g)

Phase subclasses with order-disorder variables (e.g. Biotite Q, Augite Qfm/Qal) accept an optional order_parameters dict, passed through df.mineral.end_members(mineral, order_parameters={...}).

API reference

OxidesAccessor, MolesAccessor, CationsAccessor, and BulkAccessor share a common base with methods, all operating on the accessor's data in whatever petro_units it's currently in (no forced conversion) and tagging the result with that same unit — df.mineral does not have these:

Method Description
df.<accessor>.mean(*, groupby=None, weights=None) Mean across rows; groupby is a column name, weights is a column name or an array-like of numbers (list/numpy.ndarray/pandas.Series, matched to rows by position)
df.<accessor>.sum(*, groupby=None) Sum across rows; groupby is a column name
df.<accessor>.reframe(columns) Exactly the given ordered columns; missing ones filled with 0.0
df.<accessor>.normalize(to=100.0) Normalise rows to sum to to

OxidesAccessor (df.oxides)

Method Description
df.oxides() Return copy with only recognised oxide columns (wt%)
df.oxides.sorted() Return oxide wt% with columns in petrological order
df.oxides.split_valence(elem, method, n_oxy, ideal_cat) Split element into low/high charge oxides (wt%)
df.oxides.oxidize(o_excess) Split FeO into FeO/Fe₂O₃ by excess oxygen (THERMOCALC)
df.oxides.reduce() Merge Fe₂O₃ back into FeO equivalent
df.oxides.apatite_correction() Remove CaO bound in apatite, zero P₂O₅

MolesAccessor (df.moles)

Method Description
df.moles() Return oxide columns as molar proportions

CationsAccessor (df.cations)

Method Description
df.cations(n_oxygens=N) Atoms per formula unit (oxygen basis)
df.cations(n_cations=N) Atoms per formula unit (cation basis)

All callable accessors auto-convert from the current petro_units attr. Chains work seamlessly: df.oxides().moles().oxides() roundtrips back to wt%. df.cations(n_oxygens=N).oxides() converts APFU back to oxide wt% (ratios preserved).

MineralAccessor (df.mineral)

Method Description
df.mineral.apfu(mineral) Element APFU with valence splits for a mineral; columns ordered by decreasing charge then increasing ionic radius (T → M → A/B/X)
df.mineral.site_allocations(mineral) Site allocations with hierarchical (site, cation) columns
df.mineral.end_members(mineral) End-member proportions (%)
df.mineral.check_stoichiometry(mineral) Stoichiometry validation scores (0–1)
df.mineral.stoichiometry_quality(mineral) Single 0–1 quality score: mean of cation_deviation, site_vacancies, leftover_cations

BulkAccessor (df.bulk)

Method Description
df.bulk() Return cleaned copy in wt%
df.bulk.cipw() Simple CIPW normative mineralogy
df.bulk.alumina_saturation(classify=False) A/NK and A/CNK molar ratios; optional Shand classification
df.bulk.oxide_ratios() Common ratios (Mg#, FeOT, total alkalis, K/Na, etc.)
df.bulk.fractionate(profile, fraction, *, mineral=None, order="core-to-rim") Subtract a fractionating mineral (volume-integrated from a radial EPMA profile) from the bulk via molar mass balance
df.bulk.TCbulk(*, system, ...) THERMOCALC bulk script output
df.bulk.Perplexbulk(*, system, ...) PerpleX thermodynamic component list output
df.bulk.MAGEMin(*, db, ...) MAGEMin bulk input file output

Series accessor (series.mineral)

Method Description
series.mineral.is_oxide True if column is a recognised oxide
series.mineral.element Element symbol
series.mineral.molecular_weight Molecular weight
series.mineral.to_mole() Convert one column to moles
series.mineral.to_cation(n_oxygens, total_oxygens) Convert one column to APFU

Plotting

Three plot classes are available for visualising microprobe and end-member data:

from petropandas import ScatterPlot, TernaryPlot, ProfilePlot

ScatterPlot

X/y scatter plot where axes are pandas.eval() expressions over DataFrame columns.

s = ScatterPlot("Prp", "Sps+Grs")
s.add(garnet_df, label="Garnet 1")
s.add(other_df, label="Garnet 2")
fig, ax = s.render()

TernaryPlot

Ternary diagram built on plain matplotlib (no mpltern dependency). Axes are pandas.eval() expressions.

s = TernaryPlot("Prp", "Sps", "Grs")
s.add(garnet_df, label="Garnet 1")
fig, ax = s.render()

ProfilePlot

Line plot of DataFrame columns against their index, with optional dual y-axes for columns spanning different value scales.

s = ProfilePlot(secondary_columns=["Alm"])
s.add(profile_df, label="Profile 1")
fig, ax = s.render()

All three inherit from BasePlot and share add(), render(), show(), and savefig() methods.

Configuration

Global defaults are stored in the ppconfig singleton (petropandas.PPConfig):

Setting Default Controls
default_system "MnNCKFMASHTO" Default thermodynamic system for TCbulk / Perplexbulk
default_oxygen 0.01 Default ferric oxygen for TCbulk / Perplexbulk / MAGEMin
default_H2O -1.0 Default water wt% (-1 = auto from deficit)
default_db "mp" Default MAGEMin database
default_sys_in "mol" Default MAGEMin unit system
from petropandas import ppconfig

ppconfig.default_db = "ig"
ppconfig.default_system = "KFMASH"
ppconfig.reset()  # restore all defaults

Mutations are global and persist for the session.

Stoichiometry checking

df.mineral.check_stoichiometry(mineral) returns a DataFrame scored 0–1 for each criterion:

Criterion What it checks
analytical_total Oxide wt% sum vs mineral-specific ideal range
cation_deviation Total APFU vs ideal cation count
charge_balance Total positive charge vs expected from oxygen count
fe3+_validity Fe³⁺ and Fe²⁺ non-negative after valence splitting
site_vacancies Mean site occupancy fraction
leftover_cations Fraction of APFU not assigned to any site
tetrahedral_fill T-site sum vs T-site capacity

Inapplicable criteria are dropped (all-NaN columns removed).

df.mineral.stoichiometry_quality(mineral) condenses three of those criteria — cation_deviation, site_vacancies, leftover_cations — into a single 0–1 score (their mean) per analysis, as a pandas.Series. NaN if the mineral doesn't define ideal_cations (so cation_deviation is NaN).

Fe³⁺/Fe²⁺ estimation methods

  • Droop (1987): method="droop" — charge-balance approach on total cations
  • Schumacher (1991): method="schumacher" — oxygen-excess approach
  • FeO→Fe₂O₃ conversion: Epidote and Titanite internally convert all FeO to Fe₂O₃ before normalisation
  • Spinel: Merges Fe₂O₃ into FeO before Droop split (inverse spinel model)
  • df.oxides.split_valence(): General-purpose valence splitting on any element
  • df.oxides.oxidize(o_excess): THERMOCALC excess-oxygen convention (Fe₂O₃ = 2 × o_excess)

Conventions

  • Ion column names use periodictable notation: Fe{2+}, Fe{3+}, Si{4+}, Na{+}
  • Structural formula columns: (site, cation) tuples (e.g. ("Z", "Si{4+}"), ("X", "Fe{2+}"))
  • Unit tracking via df.attrs["petro_units"]: "wt%" (default) → "moles""apfu"
  • Callable accessors (df.oxides(), df.moles(), df.cations()) auto-convert from current units
  • Mineral instances are configuration objects — stateless, reusable
  • str(mineral) returns mineral.abbreviation (e.g. "Grt", matching the instance variable it's exported under); repr(mineral) returns mineral.name (e.g. "Garnet")
  • Internal modules are underscore-prefixed (_calc.py, _core.py, _minerals.py, _plotting.py)

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

petropandas-0.2.1.tar.gz (168.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

petropandas-0.2.1-py3-none-any.whl (172.7 kB view details)

Uploaded Python 3

File details

Details for the file petropandas-0.2.1.tar.gz.

File metadata

  • Download URL: petropandas-0.2.1.tar.gz
  • Upload date:
  • Size: 168.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for petropandas-0.2.1.tar.gz
Algorithm Hash digest
SHA256 e43e463383e61678c62e4c45c5dddaecc9c45a722ef1dbcbe094dde27a35da33
MD5 4f002a87d391e89f21bbc8c6e72daddc
BLAKE2b-256 ce44c948070177a59510429fb5ccb73374659136a616d11e48b5473733b35026

See more details on using hashes here.

File details

Details for the file petropandas-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: petropandas-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 172.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for petropandas-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 2c74f3d737d593d1b64f96a9026e51c1e32ff0606b9b2482acac2cea51ddc5bb
MD5 f1441e749f609abf082de603f61cecfe
BLAKE2b-256 cd623625b8662dff3559a940c2ab55c45e04fff2b12e12ddf1eecff197df0575

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.1 This release

2 files

0.2.0

2 files

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page