• 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

91.4
/pyrolite/plot/density/grid.py
1
import numpy as np
12✔
2

3
from ...util.distributions import sample_kde
12✔
4
from ...util.log import Handle
12✔
5
from ...util.math import flattengrid, linrng_, linspc_, logrng_, logspc_
12✔
6
from ...util.plot.grid import bin_centres_to_edges
12✔
7

8
logger = Handle(__name__)
12✔
9

10

11
class DensityGrid(object):
12✔
12
    def __init__(
12✔
13
        self, x, y, extent=None, bins=50, logx=False, logy=False, coverage_scale=1.2
14
    ):
15
        """
16
        Build a grid of x-y coordinates for use in evaluating KDE functions.
17

18
        Parameters
19
        -----------
20
        x : :class:`np.ndarray`
21
        y : :class:`np.ndarray`
22
        extent : :class:`list`
23
            Optionally-specified extent for the grid in the form (xmin, xmax, ymin, ymax).
24
        bins : :class:`int` | :class:`tuple`
25
            Number of bins for the grid. Can optionally specify
26
            a tuple with (xbins, ybins).
27
        logx : :class:`bool`
28
            Whether to use a log-spaced index for the x dimension of the grid.
29
        logy: :class:`bool`
30
            Whether to use a log-spaced index for the y dimension of the grid.
31
        coverage_scale : :class:`float`
32
            Multiplier for the range of the grid relative to the data. If >1, the grid
33
            will extend beyond the data.
34
        """
35
        # limits
36
        self.logx = logx
12✔
37
        self.logy = logy
12✔
38
        if not isinstance(bins, int):
12✔
39
            assert len(bins) == 2  # x-y bins
×
UNCOV
40
            self.xbins, self.ybins = bins
×
41
        else:
42
            self.xbins, self.ybins = bins, bins
12✔
43
        self.coverage_scale = coverage_scale
12✔
44

45
        if extent is None:
12✔
46
            self.xmin, self.xmax, self.ymin, self.ymax = self.extent_from_xy(x, y)
12✔
47
        else:
UNCOV
48
            self.xmin, self.xmax, self.ymin, self.ymax = extent
×
49

50
        self.xstep = self.get_xstep()
12✔
51
        self.ystep = self.get_ystep()
12✔
52

53
        if self.logx:
12✔
54
            assert self.xmin > 0.0
12✔
55
            assert (self.xmin / self.xstep) > 0.0
12✔
56
        if self.logy:
12✔
57
            assert self.ymin > 0.0
12✔
58
            assert (self.ymin / self.ystep) > 0.0
12✔
59

60
        self.calculate_grid()
12✔
61

62
    def calculate_grid(self):
12✔
63
        self.get_centre_grid()
12✔
64
        self.get_edge_grid()
12✔
65

66
    def get_ystep(self):
12✔
67
        if self.logy:
12✔
68
            return (self.ymax / self.ymin) / self.ybins
12✔
69
        else:
70
            return (self.ymax - self.ymin) / self.ybins
12✔
71

72
    def get_xstep(self):
12✔
73
        if self.logx:
12✔
74
            return (self.xmax / self.xmin) / self.xbins
12✔
75
        else:
76
            return (self.xmax - self.xmin) / self.xbins
12✔
77

78
    def extent_from_xy(self, x, y, coverage_scale=None):
12✔
79
        cov = coverage_scale or self.coverage_scale
12✔
80
        expand_grid = (cov - 1.0) / 2
12✔
81
        return [
12✔
82
            *[linrng_, logrng_][self.logx](x, exp=expand_grid),
83
            *[linrng_, logrng_][self.logy](y, exp=expand_grid),
84
        ]
85

86
    def get_xrange(self):
12✔
87
        return self.xmin, self.xmax
12✔
88

89
    def get_yrange(self):
12✔
90
        return self.ymin, self.ymax
12✔
91

92
    def get_extent(self):
12✔
UNCOV
93
        return [*self.get_xrange(), *self.get_yrange()]
×
94

95
    def get_range(self):
12✔
96
        return [[*self.get_xrange()], [*self.get_yrange()]]
12✔
97

98
    def update_grid_centre_ticks(self):
12✔
99
        if self.logx:
12✔
100
            self.grid_xc = logspc_(self.xmin, self.xmax, 1.0, self.xbins)
12✔
101
        else:
102
            self.grid_xc = linspc_(self.xmin, self.xmax, self.xstep, self.xbins)
12✔
103

104
        if self.logy:
12✔
105
            self.grid_yc = logspc_(self.ymin, self.ymax, 1.0, self.ybins)
12✔
106
        else:
107
            self.grid_yc = linspc_(self.ymin, self.ymax, self.ystep, self.ybins)
12✔
108

109
    def update_grid_edge_ticks(self):
12✔
110
        self.update_grid_centre_ticks()
12✔
111
        if self.logx:
12✔
112
            self.grid_xe = np.exp(bin_centres_to_edges(np.log(np.sort(self.grid_xc))))
12✔
113
        else:
114
            self.grid_xe = bin_centres_to_edges(np.sort(self.grid_xc))
12✔
115

116
        if self.logy:
12✔
117
            self.grid_ye = np.exp(bin_centres_to_edges(np.log(np.sort(self.grid_yc))))
12✔
118
        else:
119
            self.grid_ye = bin_centres_to_edges(np.sort(self.grid_yc))
12✔
120

121
    def get_centre_grid(self):
12✔
122
        self.update_grid_centre_ticks()
12✔
123
        self.grid_xci, self.grid_yci = np.meshgrid(self.grid_xc, self.grid_yc)
12✔
124

125
    def get_edge_grid(self):
12✔
126
        self.update_grid_edge_ticks()
12✔
127
        self.grid_xei, self.grid_yei = np.meshgrid(self.grid_xe, self.grid_ye)
12✔
128

129
    def get_hex_extent(self):
12✔
130
        if self.logx:
12✔
131
            xex = [np.log(self.xmin / self.xstep), np.log(self.xmax * self.xstep)]
12✔
132
        else:
133
            xex = [self.xmin - self.xstep, self.xmax + self.xstep]
12✔
134

135
        if self.logy:
12✔
136
            yex = [np.log(self.ymin / self.ystep), np.log(self.ymax * self.ystep)]
12✔
137
        else:
138
            yex = [self.ymin - self.ystep, self.ymax + self.ystep]
12✔
139
        return xex + yex
12✔
140

141
    def kdefrom(
12✔
142
        self,
143
        xy,
144
        xtransform=lambda x: x,
145
        ytransform=lambda x: x,
146
        mode="centres",
147
        bw_method=None,
148
    ):
149
        """
150
        Take an x-y array and sample a KDE on the grid.
151
        """
152
        arr = xy.copy()
12✔
153
        # generate x grid over range spanned by log(x)
154
        arr[:, 0] = xtransform(xy[:, 0])
12✔
155
        # generate y grid over range spanned by log(y)
156
        arr[:, 1] = ytransform(xy[:, 1])
12✔
157
        if mode == "centres":
12✔
158
            assert np.isfinite(self.grid_xc).all() and np.isfinite(self.grid_yc).all()
×
UNCOV
159
            zi = sample_kde(
×
160
                arr,
161
                flattengrid(
162
                    np.meshgrid(xtransform(self.grid_xc), ytransform(self.grid_yc))
163
                ),
164
                bw_method=bw_method,
165
            )
UNCOV
166
            zi = zi.reshape(self.grid_xci.shape)
×
167
        elif mode == "edges":
12✔
168
            assert np.isfinite(self.grid_xe).all() and np.isfinite(self.grid_ye).all()
12✔
169
            zi = sample_kde(
12✔
170
                arr,
171
                flattengrid(
172
                    np.meshgrid(xtransform(self.grid_xe), ytransform(self.grid_ye))
173
                ),
174
                bw_method=bw_method,
175
            )
176
            zi = zi.reshape(self.grid_xei.shape)
12✔
177
        else:
UNCOV
178
            raise NotImplementedError("Valid modes are 'centres' and 'edges'.")
×
179
        return zi
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