theseusplot 0.1.0__tar.gz → 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {theseusplot-0.1.0 → theseusplot-0.1.1}/.github/workflows/publish.yml +3 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/PKG-INFO +21 -13
- theseusplot-0.1.1/README-figures/factor_column-13.png +0 -0
- theseusplot-0.1.1/README-figures/overview-1.png +0 -0
- theseusplot-0.1.1/README-figures/plot_carrier-5.png +0 -0
- theseusplot-0.1.1/README-figures/plot_carrier_n-7.png +0 -0
- theseusplot-0.1.1/README-figures/plot_dep_delay-9.png +0 -0
- theseusplot-0.1.1/README-figures/plot_dep_delay_n-11.png +0 -0
- theseusplot-0.1.1/README-figures/plot_origin-3.png +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/README.Rmd +19 -4
- {theseusplot-0.1.0 → theseusplot-0.1.1}/README.md +20 -12
- {theseusplot-0.1.0 → theseusplot-0.1.1}/pyproject.toml +1 -1
- theseusplot-0.1.1/scripts/prepare_pypi_readme.py +21 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/src/theseusplot/_ship.py +12 -9
- theseusplot-0.1.1/tests/test_plot.py +255 -0
- theseusplot-0.1.0/README-figures/factor_column-13.png +0 -0
- theseusplot-0.1.0/README-figures/overview-1.png +0 -0
- theseusplot-0.1.0/README-figures/plot_carrier-5.png +0 -0
- theseusplot-0.1.0/README-figures/plot_carrier_n-7.png +0 -0
- theseusplot-0.1.0/README-figures/plot_dep_delay-9.png +0 -0
- theseusplot-0.1.0/README-figures/plot_dep_delay_n-11.png +0 -0
- theseusplot-0.1.0/README-figures/plot_origin-3.png +0 -0
- theseusplot-0.1.0/tests/test_plot.py +0 -118
- {theseusplot-0.1.0 → theseusplot-0.1.1}/.github/workflows/ci.yml +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/TheseusPlot_py.Rproj +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/src/theseusplot/__init__.py +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/src/theseusplot/_config.py +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/src/theseusplot/py.typed +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/tests/fixtures/table_factor_order.json +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/tests/fixtures/table_missing_asymmetric_categories.json +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/tests/fixtures/table_top_n_aggregation.json +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/tests/test_continuous_binning.py +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/tests/test_contribution_algorithm.py +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/tests/test_fixtures.py +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/tests/test_public_api.py +0 -0
- {theseusplot-0.1.0 → theseusplot-0.1.1}/tests/test_table_behavior.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: theseusplot
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: Python port of TheseusPlot for decomposing differences in rate metrics.
|
|
5
5
|
Author: TheseusPlot.py contributors
|
|
6
6
|
License: MIT
|
|
@@ -23,8 +23,9 @@ Description-Content-Type: text/markdown
|
|
|
23
23
|
|
|
24
24
|
<!-- badges: start -->
|
|
25
25
|
|
|
26
|
+
[](https://pypi.org/project/theseusplot/)
|
|
27
|
+
[](https://pepy.tech/project/theseusplot)
|
|
26
28
|
[](https://github.com/hoxo-m/TheseusPlot_py/actions/workflows/ci.yml)
|
|
27
|
-
|
|
28
29
|
<!-- badges: end -->
|
|
29
30
|
|
|
30
31
|
## 1. Overview
|
|
@@ -58,7 +59,7 @@ Thus, the contribution of the female group is -0.2 percentage points.
|
|
|
58
59
|
|
|
59
60
|
When visualized, the results appear as follows:
|
|
60
61
|
|
|
61
|
-
<img src="README-figures/overview-1.png" alt="" width="500" />
|
|
62
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/overview-1.png" alt="" width="500" />
|
|
62
63
|
|
|
63
64
|
From this plot, we can see that the decline in the metric is primarily
|
|
64
65
|
driven by the male group. We call this visualization the “Theseus Plot.”
|
|
@@ -68,18 +69,25 @@ Theseus Plots for various attributes.
|
|
|
68
69
|
|
|
69
70
|
## 2. Installation
|
|
70
71
|
|
|
71
|
-
You can install the
|
|
72
|
-
[
|
|
72
|
+
You can install the **theseusplot** package from
|
|
73
|
+
[PyPI](https://pypi.org/project/theseusplot/) with:
|
|
73
74
|
|
|
74
75
|
``` bash
|
|
75
|
-
python -m pip install
|
|
76
|
+
python -m pip install theseusplot
|
|
76
77
|
```
|
|
77
78
|
|
|
78
79
|
You can install the optional dependencies for examples and documentation
|
|
79
80
|
data with:
|
|
80
81
|
|
|
81
82
|
``` bash
|
|
82
|
-
python -m pip install "theseusplot[examples]
|
|
83
|
+
python -m pip install "theseusplot[examples]"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
You can install the development version from
|
|
87
|
+
[GitHub](https://github.com/hoxo-m/TheseusPlot_py) with:
|
|
88
|
+
|
|
89
|
+
``` bash
|
|
90
|
+
python -m pip install "git+https://github.com/hoxo-m/TheseusPlot_py.git"
|
|
83
91
|
```
|
|
84
92
|
|
|
85
93
|
## 3. Details
|
|
@@ -168,7 +176,7 @@ fig, ax = ship.plot("origin")
|
|
|
168
176
|
fig.show()
|
|
169
177
|
```
|
|
170
178
|
|
|
171
|
-
<img src="README-figures/plot_origin-3.png" alt="" width="500" />
|
|
179
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/plot_origin-3.png" alt="" width="500" />
|
|
172
180
|
|
|
173
181
|
New York City has three major airports, and Newark Liberty International
|
|
174
182
|
Airport (EWR) accounted for the largest share of the decline in the
|
|
@@ -209,7 +217,7 @@ fig, ax = ship.plot_flip("carrier")
|
|
|
209
217
|
fig.show()
|
|
210
218
|
```
|
|
211
219
|
|
|
212
|
-
<img src="README-figures/plot_carrier-5.png" alt="" width="500" />
|
|
220
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/plot_carrier-5.png" alt="" width="500" />
|
|
213
221
|
|
|
214
222
|
When the number of subgroups is large, those with small contributions
|
|
215
223
|
are automatically grouped together. By default, this happens when there
|
|
@@ -221,7 +229,7 @@ fig, ax = ship.plot_flip("carrier", n=6)
|
|
|
221
229
|
fig.show()
|
|
222
230
|
```
|
|
223
231
|
|
|
224
|
-
<img src="README-figures/plot_carrier_n-7.png" alt="" width="500" />
|
|
232
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/plot_carrier_n-7.png" alt="" width="500" />
|
|
225
233
|
|
|
226
234
|
From this plot, JetBlue Airways and United Air Lines appear to have the
|
|
227
235
|
largest contributions to the decline in on-time arrival rate.
|
|
@@ -237,7 +245,7 @@ fig, ax = ship.plot_flip("dep_delay")
|
|
|
237
245
|
fig.show()
|
|
238
246
|
```
|
|
239
247
|
|
|
240
|
-
<img src="README-figures/plot_dep_delay-9.png" alt="" width="500" />
|
|
248
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/plot_dep_delay-9.png" alt="" width="500" />
|
|
241
249
|
|
|
242
250
|
By default, continuous variables are discretized so that each subgroup
|
|
243
251
|
has roughly equal sample sizes, with the number of bins set to 10. You
|
|
@@ -251,7 +259,7 @@ fig, ax = ship.plot_flip("dep_delay", continuous=continuous_config(n=3))
|
|
|
251
259
|
fig.show()
|
|
252
260
|
```
|
|
253
261
|
|
|
254
|
-
<img src="README-figures/plot_dep_delay_n-11.png" alt="" width="500" />
|
|
262
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/plot_dep_delay_n-11.png" alt="" width="500" />
|
|
255
263
|
|
|
256
264
|
This result shows that both a decrease in on-time departures and an
|
|
257
265
|
increase in delayed departures contributed to the decline in on-time
|
|
@@ -305,7 +313,7 @@ fig, ax = ship.plot("segment")
|
|
|
305
313
|
fig.show()
|
|
306
314
|
```
|
|
307
315
|
|
|
308
|
-
<img src="README-figures/factor_column-13.png" alt="" width="500" />
|
|
316
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/factor_column-13.png" alt="" width="500" />
|
|
309
317
|
|
|
310
318
|
Even if the contribution of `"High"` is larger than that of `"Low"` or
|
|
311
319
|
`"Medium"`, the rows and bars are shown in the order
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -18,12 +18,20 @@ knitr::opts_chunk$set(
|
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
library(reticulate)
|
|
21
|
+
py_install(envname = "theseusplot-env", packages = ".", pip = TRUE)
|
|
21
22
|
use_virtualenv("theseusplot-env", required = TRUE)
|
|
23
|
+
|
|
24
|
+
readme_base_url <- Sys.getenv("THESEUSPLOT_README_BASE_URL", unset = "")
|
|
25
|
+
if (nzchar(readme_base_url)) {
|
|
26
|
+
knitr::opts_knit$set(base.url = readme_base_url)
|
|
27
|
+
}
|
|
22
28
|
```
|
|
23
29
|
|
|
24
30
|
# TheseusPlot: Visualizing Decomposition of Differences in Rate Metrics
|
|
25
31
|
|
|
26
32
|
<!-- badges: start -->
|
|
33
|
+
[](https://pypi.org/project/theseusplot/)
|
|
34
|
+
[](https://pepy.tech/project/theseusplot)
|
|
27
35
|
[](https://github.com/hoxo-m/TheseusPlot_py/actions/workflows/ci.yml)
|
|
28
36
|
<!-- badges: end -->
|
|
29
37
|
|
|
@@ -87,18 +95,25 @@ The **TheseusPlot** package is designed to make it easy to generate Theseus Plot
|
|
|
87
95
|
|
|
88
96
|
## 2. Installation
|
|
89
97
|
|
|
90
|
-
You can install the
|
|
91
|
-
[
|
|
98
|
+
You can install the **theseusplot** package from
|
|
99
|
+
[PyPI](https://pypi.org/project/theseusplot/) with:
|
|
92
100
|
|
|
93
101
|
```bash
|
|
94
|
-
python -m pip install
|
|
102
|
+
python -m pip install theseusplot
|
|
95
103
|
```
|
|
96
104
|
|
|
97
105
|
You can install the optional dependencies for examples and documentation data
|
|
98
106
|
with:
|
|
99
107
|
|
|
100
108
|
```bash
|
|
101
|
-
python -m pip install "theseusplot[examples]
|
|
109
|
+
python -m pip install "theseusplot[examples]"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
You can install the development version from
|
|
113
|
+
[GitHub](https://github.com/hoxo-m/TheseusPlot_py) with:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
python -m pip install "git+https://github.com/hoxo-m/TheseusPlot_py.git"
|
|
102
117
|
```
|
|
103
118
|
|
|
104
119
|
## 3. Details
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
<!-- badges: start -->
|
|
7
7
|
|
|
8
|
+
[](https://pypi.org/project/theseusplot/)
|
|
9
|
+
[](https://pepy.tech/project/theseusplot)
|
|
8
10
|
[](https://github.com/hoxo-m/TheseusPlot_py/actions/workflows/ci.yml)
|
|
9
|
-
|
|
10
11
|
<!-- badges: end -->
|
|
11
12
|
|
|
12
13
|
## 1. Overview
|
|
@@ -40,7 +41,7 @@ Thus, the contribution of the female group is -0.2 percentage points.
|
|
|
40
41
|
|
|
41
42
|
When visualized, the results appear as follows:
|
|
42
43
|
|
|
43
|
-
<img src="README-figures/overview-1.png" alt="" width="500" />
|
|
44
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/overview-1.png" alt="" width="500" />
|
|
44
45
|
|
|
45
46
|
From this plot, we can see that the decline in the metric is primarily
|
|
46
47
|
driven by the male group. We call this visualization the “Theseus Plot.”
|
|
@@ -50,18 +51,25 @@ Theseus Plots for various attributes.
|
|
|
50
51
|
|
|
51
52
|
## 2. Installation
|
|
52
53
|
|
|
53
|
-
You can install the
|
|
54
|
-
[
|
|
54
|
+
You can install the **theseusplot** package from
|
|
55
|
+
[PyPI](https://pypi.org/project/theseusplot/) with:
|
|
55
56
|
|
|
56
57
|
``` bash
|
|
57
|
-
python -m pip install
|
|
58
|
+
python -m pip install theseusplot
|
|
58
59
|
```
|
|
59
60
|
|
|
60
61
|
You can install the optional dependencies for examples and documentation
|
|
61
62
|
data with:
|
|
62
63
|
|
|
63
64
|
``` bash
|
|
64
|
-
python -m pip install "theseusplot[examples]
|
|
65
|
+
python -m pip install "theseusplot[examples]"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
You can install the development version from
|
|
69
|
+
[GitHub](https://github.com/hoxo-m/TheseusPlot_py) with:
|
|
70
|
+
|
|
71
|
+
``` bash
|
|
72
|
+
python -m pip install "git+https://github.com/hoxo-m/TheseusPlot_py.git"
|
|
65
73
|
```
|
|
66
74
|
|
|
67
75
|
## 3. Details
|
|
@@ -150,7 +158,7 @@ fig, ax = ship.plot("origin")
|
|
|
150
158
|
fig.show()
|
|
151
159
|
```
|
|
152
160
|
|
|
153
|
-
<img src="README-figures/plot_origin-3.png" alt="" width="500" />
|
|
161
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/plot_origin-3.png" alt="" width="500" />
|
|
154
162
|
|
|
155
163
|
New York City has three major airports, and Newark Liberty International
|
|
156
164
|
Airport (EWR) accounted for the largest share of the decline in the
|
|
@@ -191,7 +199,7 @@ fig, ax = ship.plot_flip("carrier")
|
|
|
191
199
|
fig.show()
|
|
192
200
|
```
|
|
193
201
|
|
|
194
|
-
<img src="README-figures/plot_carrier-5.png" alt="" width="500" />
|
|
202
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/plot_carrier-5.png" alt="" width="500" />
|
|
195
203
|
|
|
196
204
|
When the number of subgroups is large, those with small contributions
|
|
197
205
|
are automatically grouped together. By default, this happens when there
|
|
@@ -203,7 +211,7 @@ fig, ax = ship.plot_flip("carrier", n=6)
|
|
|
203
211
|
fig.show()
|
|
204
212
|
```
|
|
205
213
|
|
|
206
|
-
<img src="README-figures/plot_carrier_n-7.png" alt="" width="500" />
|
|
214
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/plot_carrier_n-7.png" alt="" width="500" />
|
|
207
215
|
|
|
208
216
|
From this plot, JetBlue Airways and United Air Lines appear to have the
|
|
209
217
|
largest contributions to the decline in on-time arrival rate.
|
|
@@ -219,7 +227,7 @@ fig, ax = ship.plot_flip("dep_delay")
|
|
|
219
227
|
fig.show()
|
|
220
228
|
```
|
|
221
229
|
|
|
222
|
-
<img src="README-figures/plot_dep_delay-9.png" alt="" width="500" />
|
|
230
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/plot_dep_delay-9.png" alt="" width="500" />
|
|
223
231
|
|
|
224
232
|
By default, continuous variables are discretized so that each subgroup
|
|
225
233
|
has roughly equal sample sizes, with the number of bins set to 10. You
|
|
@@ -233,7 +241,7 @@ fig, ax = ship.plot_flip("dep_delay", continuous=continuous_config(n=3))
|
|
|
233
241
|
fig.show()
|
|
234
242
|
```
|
|
235
243
|
|
|
236
|
-
<img src="README-figures/plot_dep_delay_n-11.png" alt="" width="500" />
|
|
244
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/plot_dep_delay_n-11.png" alt="" width="500" />
|
|
237
245
|
|
|
238
246
|
This result shows that both a decrease in on-time departures and an
|
|
239
247
|
increase in delayed departures contributed to the decline in on-time
|
|
@@ -287,7 +295,7 @@ fig, ax = ship.plot("segment")
|
|
|
287
295
|
fig.show()
|
|
288
296
|
```
|
|
289
297
|
|
|
290
|
-
<img src="README-figures/factor_column-13.png" alt="" width="500" />
|
|
298
|
+
<img src="https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/README-figures/factor_column-13.png" alt="" width="500" />
|
|
291
299
|
|
|
292
300
|
Even if the contribution of `"High"` is larger than that of `"Low"` or
|
|
293
301
|
`"Medium"`, the rows and bars are shown in the order
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Prepare README image URLs for PyPI rendering."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
RAW_BASE_URL = "https://raw.githubusercontent.com/hoxo-m/TheseusPlot_py/main/"
|
|
8
|
+
README_PATH = Path("README.md")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main() -> None:
|
|
12
|
+
readme = README_PATH.read_text(encoding="utf-8")
|
|
13
|
+
readme = readme.replace(
|
|
14
|
+
'src="README-figures/',
|
|
15
|
+
f'src="{RAW_BASE_URL}README-figures/',
|
|
16
|
+
)
|
|
17
|
+
README_PATH.write_text(readme, encoding="utf-8")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
main()
|
|
@@ -20,8 +20,8 @@ _MIN_BREAK_COUNT = 2
|
|
|
20
20
|
_REFITTED_COLOR = "#00BFC4"
|
|
21
21
|
_ORIGINAL_SIZE_COLOR = "#7CAE00"
|
|
22
22
|
_REFITTED_SIZE_COLOR = "#C77CFF"
|
|
23
|
-
_POSITIVE_COLOR = "#
|
|
24
|
-
_NEGATIVE_COLOR = "#
|
|
23
|
+
_POSITIVE_COLOR = "#00BFC4"
|
|
24
|
+
_NEGATIVE_COLOR = "#F8766D"
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class ShipOfTheseus:
|
|
@@ -695,7 +695,6 @@ class ShipOfTheseus:
|
|
|
695
695
|
ax.axvline(0, color="#333333", linewidth=0.8)
|
|
696
696
|
ax.set_yticks(positions)
|
|
697
697
|
ax.set_yticklabels(waterfall["items"].astype(str))
|
|
698
|
-
ax.invert_yaxis()
|
|
699
698
|
ax.set_xlabel(self.y_label or "")
|
|
700
699
|
ax.set_ylabel("")
|
|
701
700
|
ax.set_title(column)
|
|
@@ -733,7 +732,6 @@ class ShipOfTheseus:
|
|
|
733
732
|
row["scaled_n"],
|
|
734
733
|
width=width,
|
|
735
734
|
color=colors[group],
|
|
736
|
-
alpha=0.35,
|
|
737
735
|
linewidth=0,
|
|
738
736
|
zorder=1,
|
|
739
737
|
)
|
|
@@ -760,7 +758,6 @@ class ShipOfTheseus:
|
|
|
760
758
|
row["scaled_n"],
|
|
761
759
|
height=height,
|
|
762
760
|
color=colors[group],
|
|
763
|
-
alpha=0.35,
|
|
764
761
|
linewidth=0,
|
|
765
762
|
zorder=1,
|
|
766
763
|
)
|
|
@@ -774,7 +771,7 @@ class ShipOfTheseus:
|
|
|
774
771
|
colors = [
|
|
775
772
|
_REFITTED_COLOR
|
|
776
773
|
if row["kind"] == "total"
|
|
777
|
-
else (
|
|
774
|
+
else self._contribution_color(float(row["amount"]))
|
|
778
775
|
for _, row in waterfall.iterrows()
|
|
779
776
|
]
|
|
780
777
|
ax.bar(
|
|
@@ -813,7 +810,7 @@ class ShipOfTheseus:
|
|
|
813
810
|
colors = [
|
|
814
811
|
_REFITTED_COLOR
|
|
815
812
|
if row["kind"] == "total"
|
|
816
|
-
else (
|
|
813
|
+
else self._contribution_color(-float(row["amount"]))
|
|
817
814
|
for _, row in waterfall.iterrows()
|
|
818
815
|
]
|
|
819
816
|
ax.barh(
|
|
@@ -827,10 +824,12 @@ class ShipOfTheseus:
|
|
|
827
824
|
zorder=3,
|
|
828
825
|
)
|
|
829
826
|
for position, (_, row) in zip(positions, waterfall.iterrows(), strict=True):
|
|
830
|
-
|
|
827
|
+
amount = float(row["amount"])
|
|
828
|
+
display_amount = -amount if row["kind"] == "contribution" else amount
|
|
829
|
+
value = self._format_plot_value(display_amount)
|
|
831
830
|
x = float(row["bottom"]) + float(row["height"])
|
|
832
831
|
ha = "left"
|
|
833
|
-
if
|
|
832
|
+
if amount < 0:
|
|
834
833
|
x = float(row["bottom"])
|
|
835
834
|
ha = "right"
|
|
836
835
|
ax.text(
|
|
@@ -843,6 +842,10 @@ class ShipOfTheseus:
|
|
|
843
842
|
zorder=4,
|
|
844
843
|
)
|
|
845
844
|
|
|
845
|
+
@staticmethod
|
|
846
|
+
def _contribution_color(amount: float) -> str:
|
|
847
|
+
return _NEGATIVE_COLOR if amount < 0 else _POSITIVE_COLOR
|
|
848
|
+
|
|
846
849
|
@staticmethod
|
|
847
850
|
def _draw_connectors(
|
|
848
851
|
ax: Any,
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from theseusplot import create_ship
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _require_pyplot():
|
|
8
|
+
matplotlib = pytest.importorskip("matplotlib")
|
|
9
|
+
matplotlib.use("Agg", force=True)
|
|
10
|
+
return pytest.importorskip("matplotlib.pyplot")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _waterfall_colors(ax) -> list[str]:
|
|
14
|
+
colors = pytest.importorskip("matplotlib.colors")
|
|
15
|
+
return [
|
|
16
|
+
colors.to_hex(patch.get_facecolor())
|
|
17
|
+
for patch in ax.patches
|
|
18
|
+
if patch.get_zorder() == 3
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _size_bar_styles(ax) -> list[tuple[str, float]]:
|
|
23
|
+
colors = pytest.importorskip("matplotlib.colors")
|
|
24
|
+
return [
|
|
25
|
+
(colors.to_hex(patch.get_facecolor()), patch.get_facecolor()[3])
|
|
26
|
+
for patch in ax.patches
|
|
27
|
+
if patch.get_zorder() == 1
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _visible_yticklabels(ax) -> list[str]:
|
|
32
|
+
labels = []
|
|
33
|
+
for tick in ax.get_yticklabels():
|
|
34
|
+
_, display_y = ax.transData.transform((0, tick.get_position()[1]))
|
|
35
|
+
labels.append((display_y, tick.get_text()))
|
|
36
|
+
return [label for _, label in sorted(labels, reverse=True)]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _plot_texts(ax) -> list[str]:
|
|
40
|
+
return [text.get_text() for text in ax.texts]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_plot_returns_matplotlib_figure_and_axes() -> None:
|
|
44
|
+
plt = _require_pyplot()
|
|
45
|
+
|
|
46
|
+
data1 = pd.DataFrame({"group": ["A", "A", "B", "B"], "y": [1, 1, 1, 1]})
|
|
47
|
+
data2 = pd.DataFrame({"group": ["A", "A", "B", "B"], "y": [0, 0, 1, 1]})
|
|
48
|
+
ship = create_ship(data1, data2, labels=("Before", "After"), y_label="Rate")
|
|
49
|
+
|
|
50
|
+
fig, ax = ship.plot("group")
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
assert ax.get_title() == "group"
|
|
54
|
+
assert ax.get_ylabel() == "Rate"
|
|
55
|
+
assert [tick.get_text() for tick in ax.get_xticklabels()] == [
|
|
56
|
+
"Before",
|
|
57
|
+
"A",
|
|
58
|
+
"B",
|
|
59
|
+
"After",
|
|
60
|
+
]
|
|
61
|
+
assert fig is ax.figure
|
|
62
|
+
finally:
|
|
63
|
+
plt.close(fig)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_plot_respects_explicit_levels() -> None:
|
|
67
|
+
plt = _require_pyplot()
|
|
68
|
+
|
|
69
|
+
data1 = pd.DataFrame({"group": ["A", "B"], "y": [1, 1]})
|
|
70
|
+
data2 = pd.DataFrame({"group": ["A", "B"], "y": [0, 1]})
|
|
71
|
+
ship = create_ship(data1, data2)
|
|
72
|
+
|
|
73
|
+
fig, ax = ship.plot("group", levels=["B", "A"])
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
assert [tick.get_text() for tick in ax.get_xticklabels()] == [
|
|
77
|
+
"Original",
|
|
78
|
+
"B",
|
|
79
|
+
"A",
|
|
80
|
+
"Refitted",
|
|
81
|
+
]
|
|
82
|
+
finally:
|
|
83
|
+
plt.close(fig)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_plot_validates_main_item() -> None:
|
|
87
|
+
_require_pyplot()
|
|
88
|
+
|
|
89
|
+
data1 = pd.DataFrame({"group": ["A", "B"], "y": [1, 1]})
|
|
90
|
+
data2 = pd.DataFrame({"group": ["A", "B"], "y": [0, 1]})
|
|
91
|
+
ship = create_ship(data1, data2)
|
|
92
|
+
|
|
93
|
+
with pytest.raises(ValueError, match="main_item"):
|
|
94
|
+
ship.plot("group", main_item="C")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_plot_colors_negative_contributions_red_and_positive_blue() -> None:
|
|
98
|
+
plt = _require_pyplot()
|
|
99
|
+
|
|
100
|
+
data1 = pd.DataFrame(
|
|
101
|
+
{"group": ["A"] * 10 + ["B"] * 10, "y": [0] * 10 + [1] * 10},
|
|
102
|
+
)
|
|
103
|
+
data2 = pd.DataFrame(
|
|
104
|
+
{"group": ["A"] * 10 + ["B"] * 30, "y": [1] * 10 + [0] * 30},
|
|
105
|
+
)
|
|
106
|
+
ship = create_ship(data1, data2)
|
|
107
|
+
|
|
108
|
+
fig, ax = ship.plot("group")
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
assert _waterfall_colors(ax) == [
|
|
112
|
+
"#00bfc4",
|
|
113
|
+
"#f8766d",
|
|
114
|
+
"#00bfc4",
|
|
115
|
+
"#00bfc4",
|
|
116
|
+
]
|
|
117
|
+
finally:
|
|
118
|
+
plt.close(fig)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_plot_size_bars_use_r_colors_without_alpha() -> None:
|
|
122
|
+
plt = _require_pyplot()
|
|
123
|
+
|
|
124
|
+
data1 = pd.DataFrame({"group": ["A", "B"], "y": [1, 1]})
|
|
125
|
+
data2 = pd.DataFrame({"group": ["A", "B"], "y": [0, 1]})
|
|
126
|
+
ship = create_ship(data1, data2)
|
|
127
|
+
|
|
128
|
+
fig, ax = ship.plot("group")
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
assert _size_bar_styles(ax) == [
|
|
132
|
+
("#7cae00", 1.0),
|
|
133
|
+
("#7cae00", 1.0),
|
|
134
|
+
("#c77cff", 1.0),
|
|
135
|
+
("#c77cff", 1.0),
|
|
136
|
+
]
|
|
137
|
+
finally:
|
|
138
|
+
plt.close(fig)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_plot_flip_returns_horizontal_matplotlib_plot() -> None:
|
|
142
|
+
plt = _require_pyplot()
|
|
143
|
+
|
|
144
|
+
data1 = pd.DataFrame({"group": ["A", "A", "B", "B"], "y": [1, 1, 1, 1]})
|
|
145
|
+
data2 = pd.DataFrame({"group": ["A", "A", "B", "B"], "y": [0, 0, 1, 1]})
|
|
146
|
+
ship = create_ship(data1, data2, labels=("Before", "After"), y_label="Rate")
|
|
147
|
+
|
|
148
|
+
fig, ax = ship.plot_flip("group")
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
assert ax.get_title() == "group"
|
|
152
|
+
assert ax.get_xlabel() == "Rate"
|
|
153
|
+
assert _visible_yticklabels(ax) == [
|
|
154
|
+
"Before",
|
|
155
|
+
"A",
|
|
156
|
+
"B",
|
|
157
|
+
"After",
|
|
158
|
+
]
|
|
159
|
+
assert fig is ax.figure
|
|
160
|
+
finally:
|
|
161
|
+
plt.close(fig)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_plot_flip_colors_original_negative_contributions_red() -> None:
|
|
165
|
+
plt = _require_pyplot()
|
|
166
|
+
|
|
167
|
+
data1 = pd.DataFrame(
|
|
168
|
+
{"group": ["A"] * 10 + ["B"] * 10, "y": [0] * 10 + [1] * 10},
|
|
169
|
+
)
|
|
170
|
+
data2 = pd.DataFrame(
|
|
171
|
+
{"group": ["A"] * 10 + ["B"] * 30, "y": [1] * 10 + [0] * 30},
|
|
172
|
+
)
|
|
173
|
+
ship = create_ship(data1, data2)
|
|
174
|
+
|
|
175
|
+
fig, ax = ship.plot_flip("group")
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
assert _waterfall_colors(ax) == [
|
|
179
|
+
"#00bfc4",
|
|
180
|
+
"#00bfc4",
|
|
181
|
+
"#f8766d",
|
|
182
|
+
"#00bfc4",
|
|
183
|
+
]
|
|
184
|
+
finally:
|
|
185
|
+
plt.close(fig)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_plot_flip_labels_use_original_contribution_signs() -> None:
|
|
189
|
+
plt = _require_pyplot()
|
|
190
|
+
|
|
191
|
+
data1 = pd.DataFrame(
|
|
192
|
+
{"group": ["A"] * 10 + ["B"] * 10, "y": [0] * 10 + [1] * 10},
|
|
193
|
+
)
|
|
194
|
+
data2 = pd.DataFrame(
|
|
195
|
+
{"group": ["A"] * 10 + ["B"] * 30, "y": [1] * 10 + [0] * 30},
|
|
196
|
+
)
|
|
197
|
+
ship = create_ship(data1, data2)
|
|
198
|
+
|
|
199
|
+
fig, ax = ship.plot_flip("group")
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
assert _plot_texts(ax) == ["25", "37.5", "-62.5", "50"]
|
|
203
|
+
finally:
|
|
204
|
+
plt.close(fig)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def test_plot_flip_size_bars_use_r_colors_without_alpha() -> None:
|
|
208
|
+
plt = _require_pyplot()
|
|
209
|
+
|
|
210
|
+
data1 = pd.DataFrame({"group": ["A", "B"], "y": [1, 1]})
|
|
211
|
+
data2 = pd.DataFrame({"group": ["A", "B"], "y": [0, 1]})
|
|
212
|
+
ship = create_ship(data1, data2)
|
|
213
|
+
|
|
214
|
+
fig, ax = ship.plot_flip("group")
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
assert _size_bar_styles(ax) == [
|
|
218
|
+
("#7cae00", 1.0),
|
|
219
|
+
("#7cae00", 1.0),
|
|
220
|
+
("#c77cff", 1.0),
|
|
221
|
+
("#c77cff", 1.0),
|
|
222
|
+
]
|
|
223
|
+
finally:
|
|
224
|
+
plt.close(fig)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_plot_flip_respects_reversed_explicit_levels() -> None:
|
|
228
|
+
plt = _require_pyplot()
|
|
229
|
+
|
|
230
|
+
data1 = pd.DataFrame({"group": ["A", "B"], "y": [1, 1]})
|
|
231
|
+
data2 = pd.DataFrame({"group": ["A", "B"], "y": [0, 1]})
|
|
232
|
+
ship = create_ship(data1, data2)
|
|
233
|
+
|
|
234
|
+
fig, ax = ship.plot_flip("group", levels=["B", "A"])
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
assert _visible_yticklabels(ax) == [
|
|
238
|
+
"Original",
|
|
239
|
+
"B",
|
|
240
|
+
"A",
|
|
241
|
+
"Refitted",
|
|
242
|
+
]
|
|
243
|
+
finally:
|
|
244
|
+
plt.close(fig)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_plot_flip_validates_main_item() -> None:
|
|
248
|
+
_require_pyplot()
|
|
249
|
+
|
|
250
|
+
data1 = pd.DataFrame({"group": ["A", "B"], "y": [1, 1]})
|
|
251
|
+
data2 = pd.DataFrame({"group": ["A", "B"], "y": [0, 1]})
|
|
252
|
+
ship = create_ship(data1, data2)
|
|
253
|
+
|
|
254
|
+
with pytest.raises(ValueError, match="main_item"):
|
|
255
|
+
ship.plot_flip("group", main_item="C")
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
|
-
import pytest
|
|
3
|
-
|
|
4
|
-
from theseusplot import create_ship
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def _require_pyplot():
|
|
8
|
-
matplotlib = pytest.importorskip("matplotlib")
|
|
9
|
-
matplotlib.use("Agg", force=True)
|
|
10
|
-
return pytest.importorskip("matplotlib.pyplot")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def test_plot_returns_matplotlib_figure_and_axes() -> None:
|
|
14
|
-
plt = _require_pyplot()
|
|
15
|
-
|
|
16
|
-
data1 = pd.DataFrame({"group": ["A", "A", "B", "B"], "y": [1, 1, 1, 1]})
|
|
17
|
-
data2 = pd.DataFrame({"group": ["A", "A", "B", "B"], "y": [0, 0, 1, 1]})
|
|
18
|
-
ship = create_ship(data1, data2, labels=("Before", "After"), y_label="Rate")
|
|
19
|
-
|
|
20
|
-
fig, ax = ship.plot("group")
|
|
21
|
-
|
|
22
|
-
try:
|
|
23
|
-
assert ax.get_title() == "group"
|
|
24
|
-
assert ax.get_ylabel() == "Rate"
|
|
25
|
-
assert [tick.get_text() for tick in ax.get_xticklabels()] == [
|
|
26
|
-
"Before",
|
|
27
|
-
"A",
|
|
28
|
-
"B",
|
|
29
|
-
"After",
|
|
30
|
-
]
|
|
31
|
-
assert fig is ax.figure
|
|
32
|
-
finally:
|
|
33
|
-
plt.close(fig)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def test_plot_respects_explicit_levels() -> None:
|
|
37
|
-
plt = _require_pyplot()
|
|
38
|
-
|
|
39
|
-
data1 = pd.DataFrame({"group": ["A", "B"], "y": [1, 1]})
|
|
40
|
-
data2 = pd.DataFrame({"group": ["A", "B"], "y": [0, 1]})
|
|
41
|
-
ship = create_ship(data1, data2)
|
|
42
|
-
|
|
43
|
-
fig, ax = ship.plot("group", levels=["B", "A"])
|
|
44
|
-
|
|
45
|
-
try:
|
|
46
|
-
assert [tick.get_text() for tick in ax.get_xticklabels()] == [
|
|
47
|
-
"Original",
|
|
48
|
-
"B",
|
|
49
|
-
"A",
|
|
50
|
-
"Refitted",
|
|
51
|
-
]
|
|
52
|
-
finally:
|
|
53
|
-
plt.close(fig)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def test_plot_validates_main_item() -> None:
|
|
57
|
-
_require_pyplot()
|
|
58
|
-
|
|
59
|
-
data1 = pd.DataFrame({"group": ["A", "B"], "y": [1, 1]})
|
|
60
|
-
data2 = pd.DataFrame({"group": ["A", "B"], "y": [0, 1]})
|
|
61
|
-
ship = create_ship(data1, data2)
|
|
62
|
-
|
|
63
|
-
with pytest.raises(ValueError, match="main_item"):
|
|
64
|
-
ship.plot("group", main_item="C")
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def test_plot_flip_returns_horizontal_matplotlib_plot() -> None:
|
|
68
|
-
plt = _require_pyplot()
|
|
69
|
-
|
|
70
|
-
data1 = pd.DataFrame({"group": ["A", "A", "B", "B"], "y": [1, 1, 1, 1]})
|
|
71
|
-
data2 = pd.DataFrame({"group": ["A", "A", "B", "B"], "y": [0, 0, 1, 1]})
|
|
72
|
-
ship = create_ship(data1, data2, labels=("Before", "After"), y_label="Rate")
|
|
73
|
-
|
|
74
|
-
fig, ax = ship.plot_flip("group")
|
|
75
|
-
|
|
76
|
-
try:
|
|
77
|
-
assert ax.get_title() == "group"
|
|
78
|
-
assert ax.get_xlabel() == "Rate"
|
|
79
|
-
assert [tick.get_text() for tick in ax.get_yticklabels()] == [
|
|
80
|
-
"After",
|
|
81
|
-
"B",
|
|
82
|
-
"A",
|
|
83
|
-
"Before",
|
|
84
|
-
]
|
|
85
|
-
assert fig is ax.figure
|
|
86
|
-
finally:
|
|
87
|
-
plt.close(fig)
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def test_plot_flip_respects_reversed_explicit_levels() -> None:
|
|
91
|
-
plt = _require_pyplot()
|
|
92
|
-
|
|
93
|
-
data1 = pd.DataFrame({"group": ["A", "B"], "y": [1, 1]})
|
|
94
|
-
data2 = pd.DataFrame({"group": ["A", "B"], "y": [0, 1]})
|
|
95
|
-
ship = create_ship(data1, data2)
|
|
96
|
-
|
|
97
|
-
fig, ax = ship.plot_flip("group", levels=["B", "A"])
|
|
98
|
-
|
|
99
|
-
try:
|
|
100
|
-
assert [tick.get_text() for tick in ax.get_yticklabels()] == [
|
|
101
|
-
"Refitted",
|
|
102
|
-
"A",
|
|
103
|
-
"B",
|
|
104
|
-
"Original",
|
|
105
|
-
]
|
|
106
|
-
finally:
|
|
107
|
-
plt.close(fig)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def test_plot_flip_validates_main_item() -> None:
|
|
111
|
-
_require_pyplot()
|
|
112
|
-
|
|
113
|
-
data1 = pd.DataFrame({"group": ["A", "B"], "y": [1, 1]})
|
|
114
|
-
data2 = pd.DataFrame({"group": ["A", "B"], "y": [0, 1]})
|
|
115
|
-
ship = create_ship(data1, data2)
|
|
116
|
-
|
|
117
|
-
with pytest.raises(ValueError, match="main_item"):
|
|
118
|
-
ship.plot_flip("group", main_item="C")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{theseusplot-0.1.0 → theseusplot-0.1.1}/tests/fixtures/table_missing_asymmetric_categories.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|