baltic utility functions

This module provides baltic utility functions with no Tree dependency in their signature: date conversion, plot scale bars and time grids, node bar/piechart/treemap annotations, tanglegram tip-order optimization (untangle), root-to-tip regression, colormap helpers, bezier/gradient polygon drawing, and highest posterior density (hpd) intervals.

Notes

This version of baltic (v1.0 (Cedar)) contains many API changes from previous versions, and is not backwards-compatible. If you find pieces of documentation that refer to the old API, please let us know and we will try to update them with the next update.

Attributes

loggerlogging.Logger

Default logger which will be passed to other baltic functions.

baltic.bt_utils.branch_to_json(curNode, treeType, traits, mostRecentDate, treeDict=None)

Format a baltic.branchLike.BranchLike object into a dictionary suitable for Auspice JSON.

Parameters

curNodebaltic.branchLike.BranchLike

Current branch being serialized.

treeType{‘divergence’, ‘time’}

Interpretation of branch lengths in the exported tree.

traitsiterable[str]

Trait names to include in the exported node attributes.

mostRecentDatefloat

Most recent sampling date used when exporting time-based confidence intervals.

treeDictdict, optional

Existing dictionary to populate during recursive export. Callers normally leave this unset.

Returns

dict

Auspice-shaped node dictionary for curNode, with its descendants nested under children.

Examples

>>> import baltic as bt
>>> from baltic import bt_utils
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.0);", treeType="time")
>>> _ = ll.traverse_tree()
>>> ll.set_absolute_time(2020.0)
>>> payload = bt_utils.branch_to_json(ll.root, "time", [], ll.mostRecent)
>>> sorted(payload.keys())
['children', 'node_attrs']
baltic.bt_utils.calendar_to_decimal_date(date, fmt='%Y-%m-%d', variable=False)

Convert a calendar date of a specified format into a decimal number.

This is the inverse of decimal_to_calendar_date().

Parameters

datestr

Date string to be converted.

fmtstr, default=”%Y-%m-%d”

String encoding the format of the input date. Must be parsable by Python’s datetime module.

variablebool, default=False

Set to True when dates may be of variable lengths (e.g. when looping over ["2025-01-01", "2025-02"]). Will use highest precision available. With False a date that does not match fmt exactly raises ValueError.

Returns

float or tuple[float, tuple[float, float]]

With variable=False, the decimal year. With variable=True, a (midpoint, (earliest, latest)) pair: the bounds span the whole month for YYYY-MM input and the whole year for YYYY, and collapse to the date itself when a full date was given.

As a special case, a falsy fmt short-circuits the conversion and returns date unchanged, so the result is then a str.

Raises

ValueError

If date does not match fmt and variable is False.

Examples

>>> from baltic import bt_utils
>>> bt_utils.calendar_to_decimal_date("1253-07-06")
1253.509589041096
>>> midpoint, bounds = bt_utils.calendar_to_decimal_date("2020-03", variable=True)
>>> round(midpoint, 3)
2020.205
>>> len(bounds)
2

Attribution

The exact-date conversion is adapted from the dt2t implementation in the Stack Overflow answer “Convert fractional years to a real date in Python” by user unutbu, posted 10 October 2013. Handling of partial and uncertain dates was subsequently added for baltic.

The original Stack Overflow contribution is distributed under the Creative Commons Attribution-ShareAlike 3.0 license (CC BY-SA 3.0).

Source: https://stackoverflow.com/a/19306024 License: https://creativecommons.org/licenses/by-sa/3.0/

baltic.bt_utils.clean_axes(ax, hideSpines=['left', 'top', 'right', 'bottom'], removeTickLabels='both')

Remove selected spines, suppress ticks and ticklabels on x, y or both axes.

It is often used after baltic.tree.Tree.plot_tree().

Parameters

axmatplotlib.axes.Axes

Axes to modify.

hideSpineslist[str], default=[‘left’, ‘top’, ‘right’, ‘bottom’]

Spine names to hide.

removeTickLabels{‘x’, ‘y’, ‘both’, ‘none’}, default=”both”

Tick-label groups to remove.

Returns

matplotlib.axes.Axes

The cleaned axes.

Examples

>>> import matplotlib.pyplot as plt
>>> from baltic import bt_utils
>>> fig, ax = plt.subplots()
>>> bt_utils.clean_axes(ax, hideSpines=["top", "right"], removeTickLabels="x")
<...Axes...>
baltic.bt_utils.convert_date_format(dateString, startFormat, endFormat)

Reformat a date string from one datetime format to another.

This helper is commonly paired with calendar_to_decimal_date() when preparing axis labels.

Parameters

dateStringstr

Input date string.

startFormatstr

Format used to parse dateString.

endFormatstr

Format used to render the output string.

Returns

str

Reformatted date string.

Examples

>>> from baltic import bt_utils
>>> bt_utils.convert_date_format("2020-03-15", "%Y-%m-%d", "%b %Y")
'Mar 2020'
baltic.bt_utils.decimal_to_calendar_date(timepoint, fmt='%Y-%m-%d')

Convert a decimal year value to a formatted calendar date string.

Parameters

timepointfloat

Decimal year to convert.

fmtstr, default=”%Y-%m-%d”

Output date format passed to datetime.datetime.strftime().

Returns

str

Formatted calendar date.

Raises

ValueError

If timepoint falls outside datetime’s supported range of years 1 to 9999. Decimal dates from deep-time or BCE trees are therefore not convertible; generate_calendar_timeline() handles those with its deep-time spacing options instead.

Examples

>>> from baltic import bt_utils
>>> bt_utils.decimal_to_calendar_date(2020.5)
'2020-07-02'

Attribution

Adapted from the Stack Overflow answer “decimal years to datetime in Python” by Jon Clements, posted 3 January 2014. Modified for use in baltic and for configurable output formatting.

The original Stack Overflow contribution is distributed under the Creative Commons Attribution-ShareAlike 3.0 license (CC BY-SA 3.0).

Source: https://stackoverflow.com/a/20911144 License: https://creativecommons.org/licenses/by-sa/3.0/

baltic.bt_utils.desaturate(colour, desat=0.65, out='auto')

Desaturate a colour by scaling its HSV saturation component.

Use desaturate_cmap() to apply the same transformation to an entire colormap.

Parameters

colourstr or tuple

Input colour specification.

desatfloat, default=0.65

Saturation multiplier in the interval [0, 1].

out{‘auto’, ‘hex’, ‘rgb’, ‘rgba’}, default=’auto’

Output format for the desaturated colour. 'auto' matches the form of colour, returning a hex string for string input and a tuple of the same arity for tuple input.

Returns

str or tuple

Desaturated colour in the requested format.

Raises

ValueError

If desat lies outside [0, 1].

TypeError

If colour is neither a colour string nor a 3- or 4-element RGB(A) sequence.

Examples

>>> from baltic import bt_utils
>>> bt_utils.desaturate("#ff0000", desat=0.5)
'#ff8080'
baltic.bt_utils.desaturate_cmap(cmap, desat=0.65)

Create a desaturated version of a matplotlib colormap.

This applies desaturate() across sampled colours from the input map.

Parameters

cmapmatplotlib.colors.Colormap

Colormap to desaturate.

desatfloat, default=0.65

Saturation multiplier applied to sampled colours.

Returns

matplotlib.colors.ListedColormap

Desaturated colormap.

Examples

>>> import matplotlib as mpl
>>> from baltic import bt_utils
>>> cmap = bt_utils.desaturate_cmap(mpl.cm.viridis, desat=0.4)
>>> cmap.N
256
baltic.bt_utils.draw_gradient_polygon(ax, polygonXY, extent, colour, minAlpha=0.0, maxAlpha=1.0, n=256, axis='y', origin='lower', reverse=False, zorder=0, interpolation='bicubic', addPatch=True, patchKwargs=None, imshowKwargs=None)

Draw an RGBA gradient image and clip it to a polygon.

Parameters

axmatplotlib.axes.Axes

Axes on which to draw the clipped gradient.

polygonXYarray-like

Polygon vertices used as the clipping path.

extentsequence[float]

Image extent passed to matplotlib.axes.Axes.imshow() as [xmin, xmax, ymin, ymax].

colourcolor

Base color used for the gradient.

minAlpha, maxAlphafloat, optional

Alpha range used to build the gradient ramp.

nint, default=256

Number of gradient samples.

axis{‘x’, ‘y’}, default=”y”

Direction along which alpha should vary.

origin{‘lower’, ‘upper’}, default=”lower”

Image origin passed to imshow.

reversebool, default=False

If True, reverse the alpha ramp.

zorderfloat, default=0

Z-order for the gradient image and default clipping patch.

interpolationstr, default=”bicubic”

Interpolation mode used by imshow.

addPatchbool, default=True

If True, add the clipping patch to the axes.

patchKwargsdict, optional

Additional keyword arguments forwarded to the polygon patch.

imshowKwargsdict, optional

Additional keyword arguments forwarded to imshow.

Returns

tuple

(image, patch) for the gradient image and the clipping polygon.

Examples

>>> import matplotlib.pyplot as plt
>>> from baltic import bt_utils
>>> fig, ax = plt.subplots()
>>> im, patch = bt_utils.draw_gradient_polygon(
...     ax,
...     polygonXY=[(0, 0), (1, 0), (1, 1), (0, 1)],
...     extent=[0, 1, 0, 1],
...     colour="steelblue",
... )
>>> patch.__class__.__name__
'Polygon'
baltic.bt_utils.five_point_bezier(points, precision=50)

Quartic Bézier curve (5 control points). Returns arrays of x and y coordinates.

This geometry helper supports routines such as baltic.curonia.plot_gradient_clade_tree().

Parameters

pointssequence[tuple[float, float]]

Five control points defining the quartic Bézier curve.

precisionint, default=50

Number of samples to evaluate along the curve.

Returns

tuple[numpy.ndarray, numpy.ndarray]

Arrays of x and y coordinates sampled along the curve.

Examples

>>> from baltic import bt_utils
>>> xs, ys = bt_utils.five_point_bezier([(0, 0), (1, 0), (1, 1), (2, 1), (2, 0)], precision=5)
>>> len(xs), len(ys)
(5, 5)
baltic.bt_utils.format_time_grid(ax, timeline, inputDateFmt='%Y-%m-%d', outputFmtFxn=None, labelPosition='mid', axis='x', **kwargs)

Format tick positions and labels for a time-grid axis.

This helper complements plot_time_grid() on the same axes.

Parameters

axmatplotlib.axes.Axes

Axes whose ticks should be updated.

timelinelist[str] or list[float]

Ordered list of calendar dates, or plain decimal years for a deep-time timeline (decades, millennia, or millions of years; see generate_calendar_timeline()), defining grid boundaries.

inputDateFmtstr, default=”%Y-%m-%d”

Date format used to parse entries in timeline. Ignored when timeline already holds plain decimal years.

outputFmtFxncallable, optional

Function used to convert each timeline entry into a label string. Defaults to a month/year formatter for calendar-string timelines, or (for deep-time timelines) a formatter that picks a single unit – years, kya, or Ma – for the whole axis based on the largest boundary magnitude, e.g. -3 Ma for 3 million years before year 0.

labelPosition{‘left’, ‘mid’}, default=”mid”

Whether labels should be placed on boundaries or interval midpoints.

axis{‘x’, ‘y’}, default=”x”

Axis whose ticks should be updated.

**kwargsdict, optional

Additional keyword arguments forwarded to the tick label setters.

Returns

matplotlib.axes.Axes

The modified matplotlib Axes object.

Examples

>>> import matplotlib.pyplot as plt
>>> from baltic import bt_utils
>>> fig, ax = plt.subplots()
>>> timeline = ["2020-01-01", "2020-04-01", "2020-07-01", "2020-10-01"]
>>> bt_utils.format_time_grid(ax, timeline)
<...Axes...>

Deep-time timelines (plain decimal years, e.g. spanning millennia or millions of years) are labelled with an automatically-chosen unit instead of a calendar format:

>>> deepTimeline = bt_utils.generate_calendar_timeline(-3_200_000, -2_800_000, spacing=(200, 'kyr'))
>>> bt_utils.format_time_grid(ax, deepTimeline)
<...Axes...>
baltic.bt_utils.generate_calendar_timeline(startDateStr, endDateStr, spacing='monthly', dateFmt='%Y-%m-%d', roundDates=True)

Generate a list of calendar breakpoints between two dates.

The output is designed for plot_time_grid() and format_time_grid().

Parameters

startDateStrstr or float

Start date of the interval. For deep-time spacing (see below) this is a plain decimal year (e.g. -44000) rather than a calendar-formatted string, since years <=0 or >9999 cannot be parsed by datetime.strptime.

endDateStrstr or float

End date of the interval. Same convention as startDateStr.

spacing{‘yearly’, ‘monthly’, ‘weekly’, ‘decadal’, ‘centennial’, ‘millennial’}, int, or (n, unit) tuple, default=”monthly”

Calendar spacing to use.

  • 'yearly', 'monthly' or 'weekly', or an int number of days: sub-annual/annual spacing, resolved via datetime as before. Requires dates within datetime’s year 1-9999 range.

  • 'decadal', 'centennial' or 'millennial': fixed 10/100/1000-year spacing.

  • (n, unit), e.g. (500, 'kyr') or (2, 'Myr'): arbitrary deep-time spacing, with unit one of 'years', 'decades', 'centuries', 'millennia', 'kyr', 'Myr' or 'Gyr'.

The three deep-time forms never touch datetime and therefore support timelines spanning years <=0 (BCE/before-present) or >9999, which BALTIC represents as negative or very large decimal dates (as parsed from BEAST trees).

dateFmtstr, default=”%Y-%m-%d”

Date format used to parse inputs and format outputs. Ignored for deep-time spacing, where boundaries are plain decimal years.

roundDatesbool, default=True

Whether to align the timeline to calendar boundaries when possible. For deep-time spacing this rounds down to the nearest multiple of the step (e.g. the nearest earlier millennium boundary).

Returns

list[str] or list[float]

Sequence of formatted date strings, or (for deep-time spacing) a sequence of plain decimal years.

Examples

>>> from baltic import bt_utils
>>> bt_utils.generate_calendar_timeline("2020-01-01", "2020-04-01", spacing="monthly")
['2020-01-01', '2020-02-01', '2020-03-01', '2020-04-01']
>>> bt_utils.generate_calendar_timeline(-44000, -41000, spacing="millennial")
[-44000, -43000, -42000, -41000]
>>> bt_utils.generate_calendar_timeline(-3_200_000, -2_800_000, spacing=(200, 'kyr'))
[-3200000, -3000000, -2800000]
baltic.bt_utils.get_path_effects(mainColour='k', outlineColour='w', mainWeight=0.5, outlineWeight=4)

Construct a simple stroked text/line path effect stack.

These effects are useful with baltic.tree.Tree.plot_text().

Parameters

mainColourcolor, default=”k”

Foreground colour for the inner stroke.

outlineColourcolor, default=”w”

Colour for the outer stroke.

mainWeightfloat, default=0.5

Line width of the inner stroke.

outlineWeightfloat, default=4

Line width of the outer stroke.

Returns

list

Matplotlib path-effect objects.

Examples

>>> from baltic import bt_utils
>>> effects = bt_utils.get_path_effects(mainColour="black", outlineColour="white")
>>> len(effects)
2
baltic.bt_utils.hpd(data, level=0.95)

Compute the highest posterior density interval for a sample.

This summary is used by plotting helpers such as baltic.curonia.plot_skygrid().

Parameters

datasequence[float]

Posterior samples. Must support len(), so a generator has to be materialised first.

levelfloat, default=0.95

Posterior mass to include in the interval, between 0 and 1. Values above 1 are not validated and raise IndexError.

Returns

tuple[float, float] or None

Lower and upper bounds of the highest posterior density interval, or None when round(level * len(data)) is below 2, which is the case for a single sample or a level near zero.

Notes

This is the empirical HPD: the narrowest window containing round(level * n) of the sorted samples. Both bounds are therefore observed data values rather than interpolated quantiles, and the interval is only meaningful for a unimodal posterior – on a bimodal sample it collapses onto whichever mode is tightest rather than reporting a disjoint region.

Original implementation copyright (C) 2010 Joseph Heled.

Examples

>>> from baltic import bt_utils
>>> bt_utils.hpd([1, 2, 2, 3, 4], level=0.8)
(1, 3)

The bounds are always observed values, and a bimodal sample yields a degenerate interval over one mode:

>>> bt_utils.hpd([1.0, 2.0, 2.5, 3.0, 10.0], level=0.6)
(2.0, 3.0)
>>> bt_utils.hpd([0, 0, 0, 0, 10, 10, 10, 10], level=0.5)
(0, 0)

Attribution

Adapted from biopy.bayesianStats.hpd.

Copyright (C) 2010 Joseph Heled. Original author: Joseph Heled <jheled@gmail.com>.

The upstream source directs users to its GPL v3 and LGPL v3 copying terms. The package metadata identifies the package license as “LGPL (V3)”, although its license classifier inconsistently names AGPL v3. The precise upstream licensing designation should therefore be confirmed with the copyright holder before relying on a single SPDX identifier.

Source: https://github.com/jheled/biopy/blob/master/biopy/bayesianStats.py GPL terms: https://github.com/jheled/biopy/blob/master/gpl.txt LGPL terms: https://github.com/jheled/biopy/blob/master/lgpl.txt Package metadata: https://github.com/jheled/biopy/blob/master/setup.py

baltic.bt_utils.make_cmap(colours, position=None, name='custom_cmap')
Create a colormap from mixed color formats:
  • RGB float tuples (0–1)

  • RGB int tuples (0–255)

  • Hex strings “#RRGGBB” or “RRGGBB”

  • HTML/CSS names (“red”, “steelblue”)

  • Matplotlib shorthand (“r”, “C0”)

The resulting colormap can be passed to desaturate_cmap().

Parameters

colourssequence

Sequence of colors to interpolate between.

positionsequence[float], optional

Positions associated with each color. Must start at 0 and end at 1 when provided.

namestr, default=”custom_cmap”

Name assigned to the resulting colormap.

Returns

matplotlib.colors.LinearSegmentedColormap

Colormap built from the provided colors.

Examples

>>> from baltic import bt_utils
>>> cmap = bt_utils.make_cmap(["#0000ff", "#ffffff", "#ff0000"])
>>> cmap.name
'custom_cmap'
baltic.bt_utils.plot_node_bar(ax, node, traitName, traitColourDict, xyFxn=None, height=10, width=0.2, otherThres=0.0, connectNode=True, connectingCorner='lower middle', orientation='vertical', **kwargs)

Plot a stacked bar summarizing discrete trait probabilities for a node.

Parameters

axmatplotlib.axes.Axes

Axes on which the bar should be drawn.

nodebaltic.branchLike.BranchLike

Branch whose trait probabilities should be displayed.

traitNamestr

Trait prefix used to locate .set and .set.prob values.

traitColourDictdict

Mapping from trait state to display colour.

xyFxncallable, optional

Function returning the anchor coordinates for the bar.

heightfloat, default=10

Total span of the stacked bar.

widthfloat, default=0.2

Width of the bar orthogonal to height.

otherThresfloat, default=0.0

Probability threshold below which states are grouped into other.

connectNodebool, default=True

If True, draw a dashed connector back to the node location.

connectingCornerstr, default=”lower middle”

Corner of the bar used as the connector origin.

orientation{‘vertical’, ‘horizontal’}, default=”vertical”

Orientation of the stacked bar.

**kwargsdict, optional

Additional keyword arguments forwarded to matplotlib.patches.Rectangle.

Returns

None

Patches are added to ax in place. Note this differs from plot_node_piechart() and plot_node_treemap(), which return the axes.

Examples

>>> import matplotlib.pyplot as plt
>>> from baltic import bt_utils
>>> class DummyNode:
...     x, y = 0.0, 0.0
...     traits = {"location.set": ["A", "B"], "location.set.prob": [0.7, 0.3]}
>>> fig, ax = plt.subplots()
>>> bt_utils.plot_node_bar(ax, DummyNode(), "location", {"A": "tab:blue", "B": "tab:orange"})
baltic.bt_utils.plot_node_piechart(ax, node, traitName, traitColourDict, centerFxn=None, radius=0.5, other_thres=0.0, **kwargs)

Plot a pie chart summarizing discrete trait probabilities for a node.

Parameters

axmatplotlib.axes.Axes

Axes on which the pie chart should be drawn.

nodebaltic.branchLike.BranchLike

Branch whose trait probabilities should be displayed.

traitNamestr

Trait prefix used to locate .set and .set.prob values.

traitColourDictdict

Mapping from trait state to display colour.

centerFxncallable, optional

Function returning the chart center.

radiusfloat, default=0.5

Pie chart radius.

other_thresfloat, default=0.0

Probability threshold below which states are grouped into other.

**kwargsdict, optional

Additional keyword arguments forwarded to matplotlib.axes.Axes.pie().

Returns

matplotlib.axes.Axes

The axes with the pie wedges added.

Examples

>>> import matplotlib.pyplot as plt
>>> from baltic import bt_utils
>>> class DummyNode:
...     x, y = 0.0, 0.0
...     traits = {"location.set": ["A", "B"], "location.set.prob": [0.7, 0.3]}
>>> fig, ax = plt.subplots()
>>> bt_utils.plot_node_piechart(ax, DummyNode(), "location", {"A": "tab:blue", "B": "tab:orange"})
<...Axes...>
baltic.bt_utils.plot_node_treemap(ax, node, traitName, traitColourDict, height, width, centerFxn=None, area=1.0, other_thres=0.0, **kwargs)

Plot a treemap summarizing discrete trait probabilities for a node.

Parameters

axmatplotlib.axes.Axes

Axes on which the treemap should be drawn.

nodebaltic.branchLike.BranchLike

Branch whose trait probabilities should be displayed.

traitNamestr

Trait prefix used to locate .set and .set.prob values.

traitColourDictdict

Mapping from trait state to display colour.

height, widthfloat

Size of the treemap rectangle.

centerFxncallable, optional

Function returning the rectangle center.

areafloat, default=1.0

Included for API compatibility with related plotting helpers.

other_thresfloat, default=0.0

Probability threshold below which states are grouped into other.

**kwargsdict, optional

Additional keyword arguments forwarded to matplotlib.patches.Rectangle.

Notes

Requires squarify, imported lazily inside this function. It ships with the baltic conda environment but is not in requirements.txt, so a pip-only install may need pip install squarify.

Returns

matplotlib.axes.Axes

The axes with the treemap patches added.

Examples

>>> import matplotlib.pyplot as plt
>>> from baltic import bt_utils
>>> class DummyNode:
...     x, y = 0.0, 0.0
...     traits = {"location.set": ["A", "B"], "location.set.prob": [0.7, 0.3]}
>>> fig, ax = plt.subplots()
>>> bt_utils.plot_node_treemap(ax, DummyNode(), "location", {"A": "tab:blue", "B": "tab:orange"}, height=1.0, width=1.0)
<...Axes...>
baltic.bt_utils.plot_scale_bar(ax, xy, L=None, tree=None, alnL=None, textXY=None, unitText=None, style='simple', orientation='horizontal', ySpan=None, fancyWidth=0.1, lineKwargs=None, textKwargs=None)

Plot a scale bar on the given axes.

Parameters

axmatplotlib.axes.Axes

Axes on which the scale bar will be plotted.

xytuple[float, float]

Coordinates of the starting point of the scale bar.

Lfloat, optional

Length of the scale bar. If not provided, it will be inferred from the tree or default to 0.001.

treeTree, optional

A baltic tree object used to infer the scale bar length and units if L is not provided.

alnLint, optional

Alignment length used to convert branch lengths (in substitutions per site) to mutation counts.

textXYtuple[float, float], optional

Coordinates for the scale bar label. If not provided, defaults to a position near the scale bar.

unitTextstr, optional

Text describing the units of the scale bar. If not provided, defaults to “subs/site” for divergence trees or “years” for time trees.

style{‘simple’, ‘fancy’}, default=”simple”

Style of the scale bar. Defaults to 'simple'.

orientation{‘horizontal’, ‘vertical’}, default=”horizontal”

Orientation of the scale bar. Defaults to 'horizontal'.

ySpanfloat, optional

Vertical span of the scale bar, used to calculate default label positions. If not provided, it will be inferred from the tree.

fancyWidthfloat, default=0.1

Width of the terminal markers for style='fancy' expressed as a fraction of the scale-bar length.

lineKwargsdict, optional

Additional keyword arguments passed to the matplotlib line plotting function for the scale bar.

textKwargsdict, optional

Additional keyword arguments passed to the matplotlib text plotting function for the scale bar label.

Returns

matplotlib.axes.Axes

The modified matplotlib Axes object.

Notes

  • If both L and tree are provided, L takes precedence.

  • If alnL is provided for a divergence tree, the scale bar will be labeled in mutation counts instead of substitutions per site.

  • The style parameter determines whether the scale bar has simple or fancy end markers.

  • The orientation parameter determines whether the scale bar is drawn horizontally or vertically.

Raises

ValueError

If an invalid style or orientation is provided.

Warnings

  • If neither L nor tree is provided, the scale bar defaults to a length of 0.001 with units of “subs/site”.

  • If both tree and ySpan are provided, ySpan will be ignored in favor of the tree’s ySpan.

Examples

>>> import matplotlib.pyplot as plt
>>> import baltic as bt
>>> from baltic import bt_utils
>>> ll = bt.make_tree("((A:1.0,B:1.0):1.0,C:1.5);", treeType="divergence")
>>> _ = ll.traverse_tree()
>>> fig, ax = plt.subplots()
>>> ll.plot_tree(ax)
<...Axes...>
>>> bt_utils.plot_scale_bar(ax, xy=(0.1, 0.2), tree=ll)
<...Axes...>
baltic.bt_utils.plot_time_grid(ax, timeline, dateFmt='%Y-%m-%d', colourFxn=None, colour=None, edgeColourFxn=None, edgeColour=None, axis='x', **kwargs)

Shade alternating time intervals on an axis.

This helper is typically used with timelines from generate_calendar_timeline().

Parameters

axmatplotlib.axes.Axes

Axes on which the spans should be drawn.

timelinelist[str], list[float], or range

Time boundaries as calendar strings, or as plain decimal years (e.g. from a deep-time generate_calendar_timeline() call spanning decades, millennia, or millions of years).

dateFmtstr, default=”%Y-%m-%d”

Date format used when timeline contains calendar strings. Ignored when timeline already holds plain decimal years.

colourFxn, edgeColourFxncallable, optional

Functions that map interval indices to face and edge colours.

colour, edgeColourcolor, optional

Constant face and edge colours used when the corresponding functions are not provided.

axis{‘x’, ‘y’}, default=”x”

Axis along which spans should be drawn.

**kwargsdict, optional

Additional keyword arguments forwarded to ax.axvspan or ax.axhspan.

Returns

matplotlib.axes.Axes

The modified matplotlib Axes object.

Examples

>>> import matplotlib.pyplot as plt
>>> from baltic import bt_utils
>>> fig, ax = plt.subplots()
>>> timeline = ["2020-01-01", "2020-04-01", "2020-07-01", "2020-10-01"]
>>> bt_utils.plot_time_grid(ax, timeline, colour="lightgray")
<...Axes...>

Deep-time timelines (e.g. from generate_calendar_timeline(..., spacing="millennial")) are plain decimal years and are shaded the same way, without any dateFmt:

>>> deepTimeline = bt_utils.generate_calendar_timeline(-44000, -41000, spacing="millennial")
>>> bt_utils.plot_time_grid(ax, deepTimeline, colour="lightgray")
<...Axes...>
baltic.bt_utils.plot_tmrca_posterior(ax, tmrcaFile, tmrcaName='age(root)', burnin=None, yCoord=None, fullViolin=True, hpdLvl=0.95, precision=100, kdeWidth=3, normalise=True, orientation='horizontal', connectNode=False, node=None, violinKwargs={}, outlineKwargs={}, connectionLineKwargs={})

Plot a KDE-based posterior density for a TMRCA statistic from a log file.

Parameters

axmatplotlib.axes.Axes

Axes on which the posterior should be drawn.

tmrcaFilestr

Path to the tab-delimited log file containing posterior samples.

tmrcaNamestr, default=”age(root)”

Column name to extract from the log file.

burninint, optional

Minimum state value to retain from the log.

yCoordfloat, optional

Anchor coordinate for plotting the density.

fullViolinbool, default=True

If True, draw the full violin; otherwise draw a half violin.

hpdLvlfloat, default=0.95

Highest posterior density mass to report.

precisionint, default=100

Number of x positions used to evaluate the KDE.

kdeWidthfloat, default=3

Width scaling applied to the KDE curve.

normalisebool, default=True

If True, normalise the KDE height before scaling by kdeWidth.

orientation{‘horizontal’, ‘vertical’}, default=”horizontal”

Orientation of the violin plot.

connectNodebool, default=False

If True, connect the posterior summary back to node.

nodebaltic.branchLike.BranchLike, optional

Branch to connect to when connectNode is enabled.

violinKwargs, outlineKwargs, connectionLineKwargsdict, optional

Keyword arguments forwarded to the violin fill, outline, and connector line artists.

Returns

matplotlib.axes.Axes

The modified matplotlib Axes object.

Examples

>>> import matplotlib.pyplot as plt
>>> from baltic import bt_utils
>>> fig, ax = plt.subplots()
>>> bt_utils.plot_tmrca_posterior(ax, "tmrca.log", tmrcaName="age(root)", burnin=1000000)
<...Axes...>
baltic.bt_utils.project_polar_vector(x, y, radians, length)

Translate a point by a vector specified in polar coordinates.

This helper complements project_to_polar() for circular tree layouts.

Parameters

x, yfloat

Starting point.

radiansfloat

Direction of the vector in radians.

lengthfloat

Vector length.

Returns

tuple[float, float]

Endpoint coordinates.

Examples

Angles are measured counter-clockwise from the positive x axis, so a quarter turn of length 2 moves straight up. The x component is rounded here because it is a floating-point residue rather than an exact zero.

>>> import math
>>> from baltic import bt_utils
>>> x, y = bt_utils.project_polar_vector(0.0, 0.0, math.pi / 2, 2.0)
>>> round(x, 12), round(y, 12)
(0.0, 2.0)
>>> tuple(round(v, 12) for v in bt_utils.project_polar_vector(1.0, 1.0, 0.0, 3.0))
(4.0, 1.0)
baltic.bt_utils.project_to_polar(x, y, yRange, circleStart=0.0, circleFraction=1.0)

Convert rectangular tree coordinates to Cartesian coordinates on a circle.

This projection underlies circular layouts in baltic.tree.Tree.plot_tree().

Parameters

xfloat

Radial distance from the origin.

yfloat

Position along the non-informative tree axis.

yRangefloat

Total span of the non-informative axis.

circleStartfloat, default=0.0

Starting angular offset as a fraction of a full turn.

circleFractionfloat, default=1.0

Fraction of the circle used by the layout.

Returns

tuple[float, float]

Projected Cartesian coordinates.

Examples

>>> from baltic import bt_utils
>>> bt_utils.project_to_polar(1.0, 0.0, 10.0)
(0.0, 1.0)
baltic.bt_utils.state_collapse_tree(tree, switchFxn, keepLast=True, adjustEarlyHeights=False)

Return a deepcopied and reduced version of the tree provided where subtrees are labelled identically when branches evaluate switchFxn to False. Also known by the name of Phylotype maps/trees.

Parameters

treebaltic.tree.Tree

Tree to collapse by partition state.

switchFxncallable

Function that receives a branch and returns True when a new partition should start at that branch.

keepLastbool, default=True

If True, retain the most recent descendant branch for each partition. If False, retain the earliest descendant instead.

adjustEarlyHeightsbool, default=False

If True and keepLast is False, adjust the retained early descendants to end at the most recent representative height.

Returns

baltic.tree.Tree

A new, deep-copied tree. The tree passed in is left unmodified.

Examples

>>> import baltic as bt
>>> from baltic import bt_utils
>>> ll = bt.make_tree("(((A:1.0,B:1.0):1.0,C:1.0):1.0,D:1.0);", treeType="divergence")
>>> ll.sort_branches()
>>> for branch in ll.Objects:
...     branch.traits["state"] = "X" if getattr(branch, "name", "").startswith(("A", "B")) else "Y"
>>> collapsed = bt_utils.state_collapse_tree(
...     ll,
...     switchFxn=lambda k: k.traits.get("state") != k.parent.traits.get("state") if k.parent else True,
... )
>>> len(collapsed.get_external()) <= len(ll.get_external())
True
baltic.bt_utils.to_scientific_notation_str(value, decimalPlaces=2, latex=True, omitPowerWhenZero=True)

Format number in scientific notation as str.

Parameters

valuefloat

Very large or very small number to be formatted.

decimalPlacesint

How many significant digits to report. Defaults to 2.

latexbool

Whether to format output str to LaTeX “$1.23\times10^{3}$” or plain text “1.23 x 10^3”. Defaults to True.

omitPowerWhenZerobool

Whether to add the exponent when exponent is 0. Defaults to True.

Returns

str

Scientifically formatted string

Examples

>>> from baltic import bt_utils
>>> bt_utils.to_scientific_notation_str(3000000, latex=False)
'3.00 x 10^6'
>>> bt_utils.to_scientific_notation_str(0.0012)
'$1.20\\times10^{-3}$'
>>> bt_utils.to_scientific_notation_str(2, latex=False, omitPowerWhenZero=True)
'2.00'
baltic.bt_utils.unnest(nodeList, towardsRoot=True)

Remove nested nodes from a selection until descendant sets no longer overlap.

Parameters

nodeListiterable[baltic.branchLike.BranchLike]

Nodes or leaf-like branches to filter.

towardsRootbool, default=True

If True, preferentially keep deeper nodes; otherwise keep more tip-proximal entries.

Returns

list

Filtered list in which no entry’s descendant tips overlap another’s. Used by baltic.tree.Tree.condense_tree() to pick the outermost collapsible nodes.

Examples

>>> import baltic as bt
>>> from baltic import bt_utils
>>> ll = bt.make_tree("(((A:1.0,B:1.0):1.0,C:1.0):1.0,D:1.0);", treeType="divergence")
>>> _ = ll.traverse_tree()
>>> nodes = [ll.find_MRCA("A", "B"), ll.find_MRCA("A", "B", "C")]
>>> kept = bt_utils.unnest(nodes, towardsRoot=True)
>>> len(kept)
1
baltic.bt_utils.untangle(tree, reference, min_shared=2, maxPolytomy=9)

Reorder internal node children across multiple trees to reduce tip crossing.

Parameters

treebaltic.tree.Tree

Tree whose child orderings will be updated.

referencebaltic.tree.Tree

Reference tree whose tip ordering guides the untangling.

min_sharedint, default=2

Minimum number of shared descendant tips required before a child set is considered in the local ordering score.

maxPolytomyint, default=9

Largest number of children for which the exhaustive permutation search is attempted. Nodes with more children than this are skipped silently and keep their existing child order, avoiding a factorial blow-up.

Returns

baltic.tree.Tree

The same tree object, with child orderings updated in place. The return value is a convenience for chaining, not a copy, and reference is not modified.

Examples

>>> import baltic as bt
>>> from baltic import bt_utils
>>> tree = bt.make_tree("(((A:1.0,B:1.0):1.0,C:1.0):1.0,D:1.0);", treeType="divergence")
>>> reference = bt.make_tree("((A:1.0,C:1.0):1.0,(B:1.0,D:1.0):1.0);", treeType="divergence")
>>> _ = tree.traverse_tree()
>>> _ = reference.traverse_tree()
>>> untangled = bt_utils.untangle(tree, reference)
>>> untangled is tree
True
baltic.bt_utils.untangle_trees(trees, iterations=10, maxPolytomy=8, bidirectional=True)

Untangle a list of trees for tanglegram visualisation.

This repeatedly applies untangle() across the tree list.

Parameters

treeslist[Tree]

Trees ordered as they will appear in the tanglegram. Trees are modified in place.

iterationsint, default=10

Number of global passes along the chain.

maxPolytomyint, default=8

Maximum polytomy size to brute-force while reordering child sets.

bidirectionalbool, default=True

Whether to do backward passes as well as forward passes.

Returns

list[Tree]

The same list, untangled.

Examples

>>> import baltic as bt
>>> from baltic import bt_utils
>>> trees = [
...     bt.make_tree("((A:1.0,B:1.0):1.0,C:1.0);", treeType="divergence"),
...     bt.make_tree("((A:1.0,C:1.0):1.0,B:1.0);", treeType="divergence"),
... ]
>>> result = bt_utils.untangle_trees(trees, iterations=1)
>>> len(result)
2