Passed
Push — main ( cee75c...37036d )
by Douglas
02:08
created

mandos.analysis.plots   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 339
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 269
dl 0
loc 339
rs 9.28
c 0
b 0
f 0
wmc 39

14 Methods

Rating   Name   Duplication   Size   Complexity  
A PlotOptions.width_and_height() 0 3 1
C _CatPlotter.get_kwargs() 0 29 10
A ScorePlotter._plot_fold() 0 36 3
A _HeatPlotter.__post_init__() 0 3 2
A ProjectionPlotter.plot() 0 22 3
A MandosPlotter._figure() 0 6 1
A _RelPlotter.get_kwargs() 0 7 2
A TauPlotter.plot() 0 20 2
A HeatmapPlotter.plot() 0 9 2
A _HeatPlotter.get_kwargs() 0 16 2
A ScorePlotter._plot_regular() 0 16 1
A CorrPlotter.plot() 0 31 3
A MandosPlotter.__post_init__() 0 8 4
A ScorePlotter.plot() 0 13 3
1
"""
2
Plots.
3
"""
4
import enum
5
from collections import Mapping
0 ignored issues
show
Bug introduced by
The name Mapping does not seem to exist in module collections.
Loading history...
6
from dataclasses import dataclass
7
from pathlib import Path
8
from typing import Any, Optional, Tuple, Union
9
10
import numpy as np
0 ignored issues
show
introduced by
Unable to import 'numpy'
Loading history...
11
from typeddfs import TypedDf, AffinityMatrixDf
0 ignored issues
show
introduced by
Unable to import 'typeddfs'
Loading history...
12
from matplotlib.colors import Colormap
0 ignored issues
show
introduced by
Unable to import 'matplotlib.colors'
Loading history...
13
14
from mandos.analysis._plot_utils import MandosPlotStyling, plt, sns, Figure
15
from mandos.model.utils import CleverEnum
16
from mandos.analysis.io_defns import (
17
    PhiPsiSimilarityDfLongForm,
18
    PsiProjectedDf,
19
    EnrichmentDf,
20
    ConcordanceDf,
21
    SimilarityDfShortForm,
22
)
23
24
25
EN_DASH = "–"
26
27
28
@enum.unique
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
29
class RelPlotType(CleverEnum):
30
    scatter = 1
31
    line = 2
32
    regression = 3
33
34
35
@enum.unique
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
36
class CatPlotType(CleverEnum):
37
    bar = 1
0 ignored issues
show
introduced by
Black listed name "bar"
Loading history...
38
    fold = 2
39
    box = 3
40
    violin = 4
41
    strip = 5
42
    swarm = 6
43
44
45
@dataclass(frozen=True, repr=True)
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
46
class PlotOptions:
47
    size: Optional[str]
48
    stylesheet: Optional[Path]
49
    rc: Mapping[str, Any]
0 ignored issues
show
Coding Style Naming introduced by
Attribute name "rc" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
50
    hue: Optional[str]
51
    palette: Union[None, Colormap, Mapping[str, str]]
52
    extra: Mapping[str, Any]
53
54
    @property
55
    def width_and_height(self) -> Tuple[float, float]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
56
        return MandosPlotStyling.fig_width_and_height(self.size)
57
58
59
@dataclass(frozen=True, repr=True)
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
60
class MandosPlotter:
61
    """"""
62
63
    rc: PlotOptions
0 ignored issues
show
Coding Style Naming introduced by
Attribute name "rc" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
64
65
    def __post_init__(self):
66
        if sns is None or plt is None:
67
            raise ImportError(
68
                "Seaborn and matplotlib required for plotting. Install the 'plots' extra."
69
            )
70
        bad_kwargs = set(self.__dict__.keys()).intersection(self.rc.extra.keys())
71
        if len(bad_kwargs) > 0:
72
            raise ValueError(f"Overlapping args in extra: {bad_kwargs}")
73
74
    def _figure(self):
75
        width, height = self.rc.width_and_height
76
        fig = plt.gca()
77
        fig.set_figwidth(width)
78
        fig.set_figheight(height)
79
        return fig
80
81
82
@dataclass(frozen=True, repr=True)
83
class _CatPlotter(MandosPlotter):
84
    kind: CatPlotType
85
    group: bool
86
    ci: float
0 ignored issues
show
Coding Style Naming introduced by
Attribute name "ci" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
87
    boot: int
88
    seed: Optional[int]
89
90
    def get_kwargs(
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
91
        self, n_rows: int, n_categories: int, more: Mapping[str, Any]
0 ignored issues
show
Unused Code introduced by
The argument n_rows seems to be unused.
Loading history...
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
92
    ) -> Mapping[str, Any]:
93
        kwargs = dict(dropna=False)
94
        kwargs.update(**more)
95
        # the aspect probably doesn't matter much, but it definitely shouldn't be 1
96
        kwargs["aspect"] = n_categories
97
        if self.kind is CatPlotType.violin:
98
            kwargs.update(inner="quartile")
99
        if self.kind in [CatPlotType.bar, CatPlotType.box, CatPlotType.violin]:
100
            kwargs.update(saturation=1.0)
101
        if self.kind in [CatPlotType.swarm, CatPlotType.strip]:
102
            kwargs.update(edgecolor="black")
103
        if self.kind in [CatPlotType.bar, CatPlotType.fold]:
104
            kwargs.update(errcolor="black")
105
        if self.kind in [
106
            CatPlotType.bar,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
107
            CatPlotType.fold,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
108
            CatPlotType.swarm,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
109
            CatPlotType.strip,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
110
            CatPlotType.violin,
0 ignored issues
show
Coding Style introduced by
Wrong hanging indentation before block (add 4 spaces).
Loading history...
111
        ]:
112
            kwargs.update(dodge=self.group)
113
        if self.group and self.kind is CatPlotType.violin and n_categories == 2:
114
            kwargs.update(dodge=False, split=True)
115
        if self.rc.extra is not None:
116
            kwargs.update(**self.rc.extra)
117
        kwargs.update(seed=self.seed, ci=self.ci, n_boot=self.boot)
118
        return kwargs
119
120
121
@dataclass(frozen=True, repr=True)
122
class _RelPlotter(MandosPlotter):
123
    kind: RelPlotType
124
    ci: float
0 ignored issues
show
Coding Style Naming introduced by
Attribute name "ci" doesn't conform to snake_case naming style ('([^\\W\\dA-Z][^\\WA-Z]2,|_[^\\WA-Z]*|__[^\\WA-Z\\d_][^\\WA-Z]+__)$' pattern)

This check looks for invalid names for a range of different identifiers.

You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements.

If your project includes a Pylint configuration file, the settings contained in that file take precedence.

To find out more about Pylint, please refer to their site.

Loading history...
125
    boot: int
126
    seed: Optional[int]
127
128
    def get_kwargs(self, n_rows: int, n_cols: int, more: Mapping[str, Any]) -> Mapping[str, Any]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
Unused Code introduced by
The argument n_rows seems to be unused.
Loading history...
Unused Code introduced by
The argument n_cols seems to be unused.
Loading history...
129
        kwargs = dict(dropna=False, dashes=False)
130
        kwargs.update(**more)
131
        if self.rc.extra is not None:
132
            kwargs.update(**self.rc.extra)
133
        kwargs.update(seed=self.seed, ci=self.ci, n_boot=self.boot)
134
        return kwargs
135
136
137
@dataclass(frozen=True, repr=True)
138
class _HeatPlotter(MandosPlotter):
139
    vmin_percentile: float = 0
140
    vmax_percentile: float = 100
141
142
    def __post_init__(self):
143
        if self.rc.extra.get("mask") is not None:
144
            raise ValueError(f"Cannot set mask in {self.__class__.__name__}")
145
146
    def get_kwargs(self, data: AffinityMatrixDf, more: Mapping[str, Any]) -> Mapping[str, Any]:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
147
        vmin = np.quantile(data.flatten(), self.vmin_percentile / 100)
148
        vmax = np.quantile(data.flatten(), self.vmax_percentile / 100)
149
        mask = data.values == np.nan
150
        kwargs = dict(
151
            vmin=vmin,
152
            vmax=vmax,
153
            square=True,
154
            mask=mask,
155
            hue=self.rc.hue,
156
            palette=self.rc.palette,
157
        )
158
        if self.rc.extra is not None:
159
            kwargs.update(**self.rc.extra)
160
        kwargs.update(**more)
161
        return kwargs
162
163
164
@dataclass(frozen=True, repr=True)
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
165
class ScorePlotter(_CatPlotter):
166
    """"""
167
168
    def plot(self, data: EnrichmentDf) -> Figure:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
169
        data = data.copy()
170
        data: TypedDf = data
171
        data.only("score_name")  # make sure
172
        data[f"object{EN_DASH}predicate"] = data["object"] + " " + data["predicate"]
173
        data[f"predicate{EN_DASH}object"] = data["predicate"] + " " + data["object"]
174
        data = data.sort_natural(f"object{EN_DASH}predicate")
175
        with MandosPlotStyling.context(*self.rc.rc):
0 ignored issues
show
introduced by
Context manager 'generator' doesn't implement __enter__ and __exit__.
Loading history...
176
            if self.kind is CatPlotType.fold:
177
                self._plot_fold(data)
178
            else:
179
                self._plot_regular(data)
180
        return self._figure()
181
182
    def _plot_fold(self, data: EnrichmentDf):
183
        kwargs = dict(
184
            color="black",
185
            saturation=1,
186
            errcolor="black",
187
            dropna=False,
188
            ci=None,
189
            hue=self.rc.hue,
190
            palette=self.rc.palette,
191
        )
192
        if self.rc.extra is not None:
193
            kwargs.update({k: v for k, v in self.rc.extra if k != "saturation"})
194
        sns.catplot(
195
            kind="bar",
196
            x=f"predicate{EN_DASH}object",
197
            y="background",
198
            data=data,
199
            row="key",
200
            **kwargs,
201
        )
202
        kwargs = dict(
203
            color="black",
204
            saturation=0.3,
205
            errcolor="black",
206
            hue=self.rc.hue,
207
            palette=self.rc.palette,
208
        )
209
        if self.rc.extra is not None:
210
            kwargs.update(self.rc.extra)
211
        sns.catplot(
212
            kind="bar",
213
            data=data,
214
            x=f"predicate{EN_DASH}object",
215
            y="value",
216
            row="key",
217
            **kwargs,
218
        )
219
220
    def _plot_regular(self, data: EnrichmentDf):
221
        keys = data["keys"].unique()
222
        defaults = dict(
223
            saturation=1,
224
            errcolor="black",
225
            hue=self.rc.hue,
226
            palette=self.rc.palette,
227
        )
228
        kwargs = self.get_kwargs(len(keys), 1, defaults)
229
        sns.catplot(
230
            kind=self.kind.name,
231
            data=data,
232
            x=f"predicate{EN_DASH}object",
233
            y="value",
234
            row="key",
235
            **kwargs,
236
        )
237
238
239
@dataclass(frozen=True, repr=True)
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
240
class TauPlotter(_CatPlotter):
241
    """ """
242
243
    def plot(self, data: ConcordanceDf) -> Figure:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
244
        phis = data["phi"].unique()
245
        # psis = data["psi"].unique()
246
        defaults = dict(
247
            saturation=1,
248
            errcolor="black",
249
            hue=self.rc.hue,
250
            palette=self.rc.palette,
251
        )
252
        kwargs = self.get_kwargs(len(phis), 1, defaults)
253
        with MandosPlotStyling.context(*self.rc.rc):
0 ignored issues
show
introduced by
Context manager 'generator' doesn't implement __enter__ and __exit__.
Loading history...
254
            sns.catplot(
255
                kind=self.kind.name,
256
                data=data,
257
                x="psi",
258
                y="tau",
259
                row="phi",
260
                **kwargs,
261
            )
262
        return self._figure()
263
264
265
@dataclass(frozen=True, repr=True)
0 ignored issues
show
Documentation introduced by
Empty class docstring
Loading history...
266
class CorrPlotter(_RelPlotter):
267
    """"""
268
269
    def plot(self, data: PhiPsiSimilarityDfLongForm) -> Figure:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
270
        phis = data["phi"].unique()
271
        psis = data["psi"].unique()
272
        with MandosPlotStyling.context(*self.rc.rc):
0 ignored issues
show
introduced by
Context manager 'generator' doesn't implement __enter__ and __exit__.
Loading history...
273
            if self.kind is RelPlotType.regression:
274
                defaults = dict(
275
                    truncate=True,
276
                    hue=self.rc.hue,
277
                    palette=self.rc.palette,
278
                )
279
                kwargs = self.get_kwargs(len(phis), len(psis), defaults)
280
                sns.lmplot(
281
                    data=data,
282
                    x="phi_value",
283
                    y="psi_value",
284
                    row="phi",
285
                    col="psi",
286
                    **kwargs,
287
                )
288
            else:
289
                kwargs = self.get_kwargs(len(phis), len(psis), {})
290
                sns.relplot(
291
                    kind=self.kind.name,
292
                    data=data,
293
                    x="phi_value",
294
                    y="psi_value",
295
                    row="phi",
296
                    col="psi",
297
                    **kwargs,
298
                )
299
        return self._figure()
300
301
302
@dataclass(frozen=True, repr=True)
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
303
class HeatmapPlotter(_HeatPlotter):
304
    def plot(self, data: SimilarityDfShortForm) -> Figure:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
305
        data = data.triangle()
306
        kwargs = self.get_kwargs(data, {})
307
        with MandosPlotStyling.context(*self.rc.rc):
0 ignored issues
show
introduced by
Context manager 'generator' doesn't implement __enter__ and __exit__.
Loading history...
308
            sns.heatmap(
309
                data,
310
                **kwargs,
311
            )
312
        return self._figure()
313
314
315
@dataclass(frozen=True, repr=True)
0 ignored issues
show
introduced by
Missing class docstring
Loading history...
316
class ProjectionPlotter(MandosPlotter):
317
    def plot(self, data: PsiProjectedDf) -> Figure:
0 ignored issues
show
introduced by
Missing function or method docstring
Loading history...
318
        psis = set(data["psi"].unique())
319
        width, height = MandosPlotStyling.fig_width_and_height(self.rc.size)
320
        aspect = width / height
321
        col_wrap = int(np.ceil(np.sqrt(len(psis)) * aspect))
322
        kwargs = dict(
323
            col_wrap=col_wrap,
324
            hue=self.rc.hue,
325
            palette=self.rc.palette,
326
        )
327
        if self.rc.extra is not None:
328
            kwargs.update(**self.rc.extra)
329
        with MandosPlotStyling.context(*self.rc.rc):
0 ignored issues
show
introduced by
Context manager 'generator' doesn't implement __enter__ and __exit__.
Loading history...
330
            sns.relplot(
331
                kind="scatter",
332
                data=data,
333
                x="x",
334
                y="y",
335
                col="psi",
336
                **kwargs,
337
            )
338
        return self._figure()
339