Geological Timescale

pyrolite includes a simple geological timescale, based on a recent verion of the International Chronostratigraphic Chart [1]. The Timescale class can be used to look up names for specific geological ages, to look up times for known geological age names and to access a reference table for all of these.

First we’ll create a timescale:

from pyrolite.util.time import Timescale

ts = Timescale()

From this we can look up the names of ages (in million years, or Ma):

ts.named_age(1212.1)
'Ectasian'

As geological age names are hierarchical, the name you give an age depends on what level you’re looking at. By default, the timescale will return the most specific non-null level. The levels accessible within the timescale are listed as an attribute:

['Supereon', 'Eon', 'Era', 'Period', 'Superepoch', 'Epoch', 'Age']

These can be used to refine the output names to your desired level of specificity (noting that for some ages, the levels which are accessible can differ; see the chart):

ts.named_age(1212.1, level="Epoch")
'Ectasian'

The timescale can also do the inverse for you, and return the timing information for a given named age:

ts.text2age("Holocene")
(0.0117, 0.0)

We can use this to create a simple template to visualise the geological timescale:

import pandas as pd
import matplotlib.pyplot as plt

fig, ax = plt.subplots(1, figsize=(5, 10))

for ix, level in enumerate(ts.levels):
    ldf = ts.data.loc[ts.data.Level == level, :]
    for pix, period in ldf.iterrows():
        ax.bar(
            ix,
            period.Start - period.End,
            facecolor=period.Color,
            bottom=period.End,
            width=1,
            edgecolor="k",
        )

ax.set_xticks(range(len(ts.levels)))
ax.set_xticklabels(ts.levels, rotation=60)
ax.xaxis.set_ticks_position("top")
ax.set_ylabel("Age (Ma)")
ax.invert_yaxis()
timescale

This doesn’t quite look like the geological timescale you may be used to. We can improve on the output somewhat with a bit of customisation for the positioning. Notably, this is less readable, but produces something closer to what we’re after. Some of this may soon be integrated as a Timescale method, if there’s interest.

import numpy as np
from matplotlib.patches import Rectangle

# first let's set up some x-limits for the different timescale levels
xlims = {
    "Eon": (0, 1),
    "Era": (1, 2),
    "Period": (2, 3),
    "Superepoch": (3, 4),
    "Epoch": (3, 5),
    "Age": (5, 7),
}


fig, ax = plt.subplots(1, figsize=(4, 10))

for ix, level in enumerate(ts.levels[::-1]):
    if level in xlims:
        ldf = ts.data.loc[ts.data.Level == level, :]
        for pix, period in ldf.iterrows():
            left, right = xlims[level]
            time = np.mean(ts.text2age(period.Name))
            if ix != len(ts.levels) - 1:
                general_bound = None
                _ix = ix
                while general_bound is None:
                    try:
                        _ix += 1
                        bound_level = ts.levels[::-1][_ix]
                        general_bound = ts.named_age(time, level=bound_level)
                    except IndexError:
                        break
                if bound_level in xlims:
                    _l, _r = xlims[bound_level]
                    if _r > left:
                        left = _r

            rect = Rectangle(
                (left, period.End),
                right - left,
                period.Start - period.End,
                facecolor=period.Color,
                edgecolor="k",
            )
            ax.add_artist(rect)

ax.set_xticks([np.mean(xlims[lvl]) for lvl in xlims.keys()])
ax.set_xticklabels(xlims.keys(), rotation=60)
ax.xaxis.set_ticks_position("top")
ax.set_xlim(0, 7)
ax.set_ylabel("Age (Ma)")
ax.set_ylim(500, 0)
timescale
(500.0, 0.0)

Total running time of the script: (0 minutes 1.020 seconds)