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 29.5† — Fe-St, Mg-St, Zn-St, Mn-St
Chlorite Chl 14* 10 — Clin, Cham, Pnn, Nim, 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). † Staurolite's 29.5 reflects typical partial vacancy at its divalent Y-site (crystallographic maximum is 30, across T=8/M=18/Y=4).

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.variables(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={...}). df.mineral.variables(phase) returns the a-x pipeline's two intermediate stages — site fractions (e.g. Biotite's Fe, Mg, AlOct) followed by the derived compositional variables (x, y, z, m, Q, ...), in that column order — between site allocation and end-member proportions, for a Phase instance only (not plain Mineral instances, which have no site_fractions/variables methods); any order-disorder variable defaults to 0.0 (fully disordered) — call phase.variables(...) directly to supply 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
df.<accessor>.calc(new_col, expr) or .calc({new_col: expr, ...}) Add one or more columns computed from pandas.eval()-style expressions (later expressions may reference earlier new_cols in the same call); ion-notation names are auto-quoted ("Fe{2+} + Mg{2+}" works with no backticks), other special-character column names still need backtick-quoting

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)
df.cations.total_charge() Total positive charge per row, summed over ion-named columns

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 f"{name}[{abbreviation}] cations={ideal_cations} n_oxygens={n_oxygens}" (e.g. "Garnet[Grt] cations=8 n_oxygens=12")
  • Internal modules are underscore-prefixed (_calc.py, _core.py, _minerals.py, _plotting.py)

Metadata

Release files for petropandas 0.2.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for petropandas 0.2.3
File Size Uploaded
petropandas-0.2.3.tar.gz 174.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for petropandas 0.2.3
File Interpreter ABI Platform
petropandas-0.2.3-py3-none-any.whl Python 3 none any Details

Total release size: 349.6 kB

Release files / petropandas-0.2.3.tar.gz

Download URL petropandas-0.2.3.tar.gz
Size 174.3 kB
Tags Source
SHA-256 checksum
How to use checksums
50e1a762ff51d2a1c2f9cdb76fb0e54fbf9d0c36a3d5cca09ebaf28863d92810
BLAKE2b-256 checksum
How to use checksums
1756d7f8181f09450b5f1e81be594b23ec8fffd1984bf3d99ad0d848f8f53584
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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}

Release files / petropandas-0.2.3-py3-none-any.whl

Download URL petropandas-0.2.3-py3-none-any.whl
Size 175.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
631eec4cbae7e5d23f86d163c25519f665750143d42fb45aa9d424462119c070
BLAKE2b-256 checksum
How to use checksums
2f565b4affc471933b64e775b119e01451671e603e3df80a4ff250036d3c8cce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.10 {"installer":{"name":"uv","version":"0.12.10","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}

Release history Release notifications | RSS feed

This release

0.2.3 This release

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release 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