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

morganjwilliams / pyrolite / 20546482500

29 Oct 2024 02:20AM UTC coverage: 91.571% (-0.07%) from 91.64%
20546482500

push

github

morganjwilliams
Merge branch 'release/0.3.6' into main

53 of 62 new or added lines in 12 files covered. (85.48%)

3 existing lines in 2 files now uncovered.

6225 of 6798 relevant lines covered (91.57%)

5.49 hits per line

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

89.69
/pyrolite/plot/density/grid.py
1
import numpy as np
6✔
2

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

8
logger = Handle(__name__)
6✔
9

10

11
class DensityGrid(object):
6✔
12
    def __init__(
6✔
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
6✔
37
        self.logy = logy
6✔
38
        if not isinstance(bins, int):
6✔
39
            assert len(bins) == 2  # x-y bins
×
40
            self.xbins, self.ybins = bins
×
41
        else:
42
            self.xbins, self.ybins = bins, bins
6✔
43
        self.coverage_scale = coverage_scale
6✔
44

45
        if extent is None:
6✔
46
            self.xmin, self.xmax, self.ymin, self.ymax = self.extent_from_xy(x, y)
6✔
47
        else:
48
            self.xmin, self.xmax, self.ymin, self.ymax = extent
×
49
            # validation
NEW
50
            self.xmin, self.xmax = (
×
51
                min([self.xmin, self.xmax]),
52
                max([self.xmin, self.xmax]),
53
            )
NEW
54
            self.ymin, self.ymax = (
×
55
                min([self.ymin, self.ymax]),
56
                max([self.ymin, self.ymax]),
57
            )
58

59
        self.xstep = self.get_xstep()
6✔
60
        self.ystep = self.get_ystep()
6✔
61

62
        if self.logx:
6✔
63
            assert self.xmin > 0.0
6✔
64
            assert (self.xmin / self.xstep) > 0.0
6✔
65
        if self.logy:
6✔
66
            assert self.ymin > 0.0
6✔
67
            assert (self.ymin / self.ystep) > 0.0
6✔
68

69
        self.calculate_grid()
6✔
70

71
    def calculate_grid(self):
6✔
72
        self.get_centre_grid()
6✔
73
        self.get_edge_grid()
6✔
74

75
    def get_ystep(self):
6✔
76
        if self.logy:
6✔
77
            step = (self.ymax / self.ymin) / self.ybins
6✔
78
        else:
79
            step = (self.ymax - self.ymin) / self.ybins
6✔
80
        return step
6✔
81

82
    def get_xstep(self):
6✔
83
        if self.logx:
6✔
84
            step = (self.xmax / self.xmin) / self.xbins
6✔
85
        else:
86
            step = (self.xmax - self.xmin) / self.xbins
6✔
87
        return step
6✔
88

89
    def extent_from_xy(self, x, y, coverage_scale=None):
6✔
90
        cov = coverage_scale or self.coverage_scale
6✔
91
        expand_grid = (cov - 1.0) / 2
6✔
92
        return [
6✔
93
            *[linrng_, logrng_][self.logx](x, exp=expand_grid),
94
            *[linrng_, logrng_][self.logy](y, exp=expand_grid),
95
        ]
96

97
    def get_xrange(self):
6✔
98
        return self.xmin, self.xmax
6✔
99

100
    def get_yrange(self):
6✔
101
        return self.ymin, self.ymax
6✔
102

103
    def get_extent(self):
6✔
104
        return [*self.get_xrange(), *self.get_yrange()]
×
105

106
    def get_range(self):
6✔
107
        return [[*self.get_xrange()], [*self.get_yrange()]]
6✔
108

109
    def update_grid_centre_ticks(self):
6✔
110
        if self.logx:
6✔
111
            self.grid_xc = logspc_(self.xmin, self.xmax, 1.0, self.xbins)
6✔
112
        else:
113
            self.grid_xc = linspc_(self.xmin, self.xmax, self.xstep, self.xbins)
6✔
114

115
        if self.logy:
6✔
116
            self.grid_yc = logspc_(self.ymin, self.ymax, 1.0, self.ybins)
6✔
117
        else:
118
            self.grid_yc = linspc_(self.ymin, self.ymax, self.ystep, self.ybins)
6✔
119

120
    def update_grid_edge_ticks(self):
6✔
121
        self.update_grid_centre_ticks()
6✔
122
        if self.logx:
6✔
123
            self.grid_xe = np.exp(bin_centres_to_edges(np.log(np.sort(self.grid_xc))))
6✔
124
        else:
125
            self.grid_xe = bin_centres_to_edges(np.sort(self.grid_xc))
6✔
126

127
        if self.logy:
6✔
128
            self.grid_ye = np.exp(bin_centres_to_edges(np.log(np.sort(self.grid_yc))))
6✔
129
        else:
130
            self.grid_ye = bin_centres_to_edges(np.sort(self.grid_yc))
6✔
131

132
    def get_centre_grid(self):
6✔
133
        self.update_grid_centre_ticks()
6✔
134
        self.grid_xci, self.grid_yci = np.meshgrid(self.grid_xc, self.grid_yc)
6✔
135

136
    def get_edge_grid(self):
6✔
137
        self.update_grid_edge_ticks()
6✔
138
        self.grid_xei, self.grid_yei = np.meshgrid(self.grid_xe, self.grid_ye)
6✔
139

140
    def get_hex_extent(self):
6✔
141
        if self.logx:
6✔
142
            xex = [np.log(self.xmin / self.xstep), np.log(self.xmax * self.xstep)]
6✔
143
        else:
144
            xex = [self.xmin - self.xstep, self.xmax + self.xstep]
6✔
145

146
        if self.logy:
6✔
147
            yex = [np.log(self.ymin / self.ystep), np.log(self.ymax * self.ystep)]
6✔
148
        else:
149
            yex = [self.ymin - self.ystep, self.ymax + self.ystep]
6✔
150
        return xex + yex
6✔
151

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