• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

morganjwilliams / pyrolite / 5564819928

pending completion
5564819928

push

github

morganjwilliams
Merge branch 'release/0.3.3' into main

249 of 270 new or added lines in 48 files covered. (92.22%)

217 existing lines in 33 files now uncovered.

5971 of 6605 relevant lines covered (90.4%)

10.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.0
/pyrolite/plot/density/ternary.py
1
import inspect
12✔
2

3
import numpy as np
12✔
4

5
from ...comp.codata import ILR, close, inverse_ILR
12✔
6
from ...util.distributions import sample_kde
12✔
7
from ...util.log import Handle
12✔
8
from ...util.math import flattengrid
12✔
9
from ...util.plot.grid import bin_centres_to_edges
12✔
10

11
logger = Handle(__name__)
12✔
12

13

14
def ternary_heatmap(
12✔
15
    data,
16
    bins=20,
17
    mode="density",
18
    transform=ILR,
19
    inverse_transform=inverse_ILR,
20
    ternary_min_value=0.0001,  # 0.01%
21
    grid_border_frac=0.1,  # 110% range for grid
22
    grid=None,
23
    **kwargs
24
):
25
    """
26
    Heatmap for ternary diagrams. This invokes a 3D to 2D transform such as a
27
    log transform prior to creating a grid.
28

29
    Parameters
30
    -----------
31
    data : :class:`numpy.ndarray`
32
        Ternary data to obtain heatmap coords from.
33
    bins : :class:`int`
34
        Number of bins for the grid.
35
    mode : :class:`str`, :code:`{'histogram', 'density'}`
36
        Which mode to render the histogram/KDE in.
37
    transform : :class:`callable` | :class:`sklearn.base.TransformerMixin`
38
        Callable function or Transformer class.
39
    inverse_transform : :class:`callable`
40
        Inverse function for `transform`, necessary if transformer class not specified.
41
    ternary_min_value : :class:`float`
42
        Optional specification of minimum values within a ternary diagram to draw the
43
        transformed grid.
44
    grid_border_frac : :class:`float`
45
        Size of border around the grid, expressed as a fraction of the total grid range.
46
    grid : :class:`numpy.ndarray`
47
        Grid coordinates to sample at, if already calculated. For the density mode,
48
        this is a (nsamples, 2) array. For histograms, this is a two-member list of
49
        bin edges.
50

51
    Returns
52
    -------
53
    t, l, r : :class:`tuple` of :class:`numpy.ndarray`
54
        Ternary coordinates for the heatmap.
55
    H : :class:`numpy.ndarray`
56
        Histogram/density estimates for the coordinates.
57
    data : :class:`dict`
58
        Data dictonary with grid arrays and relevant information.
59

60
    Notes
61
    -----
62

63
    Zeros will not render in this heatmap, consider replacing zeros with small values
64
    or imputing them if they must be incorporated.
65
    """
66
    arr = close(data)  # should remove zeros/nans
12✔
67
    arr = arr[np.isfinite(arr).all(axis=1)]
12✔
68

69
    if inspect.isclass(transform):
12✔
70
        # TransformerMixin
71
        tcls = transform()
12✔
72
        tfm = tcls.transform
12✔
73
        itfm = tcls.inverse_transform
12✔
74
    else:
75
        # callable
76
        tfm = transform
12✔
77
        assert callable(inverse_transform)
12✔
78
        itfm = inverse_transform
12✔
79

80
    if grid is None:
12✔
81
        # minimum bounds on triangle
82
        mins = np.max(
12✔
83
            np.vstack([np.ones(3) * ternary_min_value, np.nanmin(arr, axis=0)]), axis=0
84
        )
85
        maxs = np.min(
12✔
86
            np.vstack([1 - np.ones(3) * ternary_min_value, np.nanmax(arr, axis=0)]),
87
            axis=0,
88
        )
89

90
        # let's construct a bounding triangle
91
        # three points defining the edges of what will be rendered
92
        ternbounds = np.vstack([mins, maxs]).T
12✔
93
        ternbound_points = np.array(
12✔
94
            [
95
                [
96
                    (1 - np.sum([ternbounds[i, 0] for z in range(3) if z != ix]))
97
                    if i == ix
98
                    else ternbounds[i, 0]
99
                    for i in range(3)
100
                ]
101
                for ix in range(3)
102
            ]
103
        )
104
        ternbound_points_tfmd = tfm(ternbound_points)
12✔
105

106
        tdata = tfm(arr)
12✔
107
        tfm_min, tfm_max = np.min(tdata, axis=0), np.max(tdata, axis=0)
12✔
108
        trng = tfm_max - tfm_min
12✔
109
        brdr = grid_border_frac / 2
12✔
110
        tfm_bin_centres = [
12✔
111
            np.linspace(
112
                np.nanmin(tdata[:, dim]) - brdr * trng[dim],  # small step back
113
                np.nanmax(tdata[:, dim]) + brdr * trng[dim],  # small step forward
114
                bins,
115
            )
116
            for dim in range(2)
117
        ]
118
        tfm_bin_edges = [bin_centres_to_edges(b) for b in tfm_bin_centres]
12✔
119

120
        tfm_centregrid = np.meshgrid(*tfm_bin_centres)
12✔
121
        tfm_edgegrid = np.meshgrid(*tfm_bin_edges)
12✔
122

123
        tern_centre_grid = itfm(flattengrid(tfm_centregrid))
12✔
124
        tern_edge_grid = itfm(flattengrid(tfm_edgegrid))
12✔
125

126
    if mode == "density":
12✔
127
        dgrid = grid or flattengrid(tfm_edgegrid)
12✔
128
        H = sample_kde(tdata, dgrid)
12✔
129
        H = H.reshape(tfm_edgegrid[0].shape)
12✔
130
        coords = tern_edge_grid
12✔
131
    elif "hist" in mode:
12✔
132
        hgrid = grid or tfm_bin_edges
12✔
133
        H, _ = np.histogramdd(tdata, bins=hgrid)
12✔
134
        H = H.T
12✔
135
        coords = tern_centre_grid
12✔
136
    elif "hex" in mode:
×
UNCOV
137
        raise NotImplementedError
×
138
    else:
UNCOV
139
        raise NotImplementedError
×
140

141
    data = dict(
12✔
142
        tfm_centres=tfm_centregrid,
143
        tfm_edges=tfm_edgegrid,
144
        tern_edges=tern_edge_grid,
145
        tern_centres=tern_centre_grid,
146
        tern_bound_points=ternbound_points,
147
        tfm_tern_bound_points=ternbound_points_tfmd,
148
        grid_transform=tfm,
149
        grid_inverse_transform=itfm,
150
    )
151
    H[~np.isfinite(H)] = 0
12✔
152
    return coords, H, data
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc