bs-python-utils 0.0.1__py3-none-any.whl

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.
@@ -0,0 +1,927 @@
1
+ """ a personal library of Altair plots
2
+ """
3
+
4
+ from typing import Callable
5
+
6
+ import altair as alt
7
+ import numpy as np
8
+ import pandas as pd
9
+ from altair_saver import save as alt_save
10
+
11
+ from bs_python_utils.bsnputils import check_matrix, check_vector
12
+ from bs_python_utils.bsutils import bs_error_abort
13
+
14
+
15
+ def _maybe_save(ch: alt.Chart, save: str | None = None):
16
+ if save is not None:
17
+ alt_save(ch, f"{save}.html")
18
+
19
+
20
+ def _add_title(ch: alt.Chart, title: str | None = None) -> alt.Chart:
21
+ if title is not None:
22
+ if isinstance(title, str):
23
+ ch = ch.properties(title=title)
24
+ else:
25
+ bs_error_abort(f"title must be a string, not {title}")
26
+ return ch
27
+
28
+
29
+ def alt_scatterplot(
30
+ df: pd.DataFrame,
31
+ str_x: str,
32
+ str_y: str,
33
+ time_series: bool = False,
34
+ save: str | None = None,
35
+ xlabel: str | None = None,
36
+ ylabel: str | None = None,
37
+ size: int | None = 30,
38
+ title: str | None = None,
39
+ color: str | None = None,
40
+ aggreg: str | None = None,
41
+ selection: bool = False,
42
+ ) -> alt.Chart:
43
+ """
44
+ scatterplot of `df[str_x]` vs `df[str_y]`
45
+
46
+ Args:
47
+ df: the data with columns for x, y
48
+ str_x: the name of a continuous x column
49
+ str_y: the name of a continuous y column
50
+ time_series: `True` if x is a time series
51
+ xlabel: label for the horizontal axis
52
+ ylabel: label for the vertical axis
53
+ title: title for the graph
54
+ size: radius of the circles
55
+ color: variable that determines the color of the circles
56
+ selection: if `True`, the user can select interactively from the `color` legend, if any
57
+ save: the name of a file to save to (HTML extension will be added)
58
+ aggreg: the name of an aggregating function for `y`
59
+
60
+ Returns:
61
+ the `alt.Chart` object
62
+ """
63
+ type_x = "T" if time_series else "Q"
64
+ var_x = alt.X(f"{str_x}:{type_x}")
65
+
66
+ if xlabel is not None:
67
+ if isinstance(xlabel, str):
68
+ var_x = alt.X(f"{str_x}:{type_x}", axis=alt.Axis(title=xlabel))
69
+ else:
70
+ bs_error_abort(f"xlabel must be a string, not {xlabel}")
71
+
72
+ var_y = f"{aggreg}({str_y}):Q" if aggreg is not None else str_y
73
+
74
+ if ylabel is not None:
75
+ if isinstance(ylabel, str):
76
+ var_y = alt.Y(var_y, axis=alt.Axis(title=ylabel))
77
+ else:
78
+ bs_error_abort(f"ylabel must be a string, not {ylabel}")
79
+
80
+ if isinstance(size, int):
81
+ circles_size = size
82
+ else:
83
+ bs_error_abort(f"size must be an integer, not {size}")
84
+
85
+ if color is not None:
86
+ if isinstance(color, str):
87
+ if selection:
88
+ selection_criterion = alt.selection_multi(fields=[color], bind="legend")
89
+ ch = (
90
+ alt.Chart(df)
91
+ .mark_circle(size=circles_size)
92
+ .encode(
93
+ x=var_x,
94
+ y=var_y,
95
+ color=color,
96
+ opacity=alt.condition(
97
+ selection_criterion, alt.value(1), alt.value(0.1)
98
+ ),
99
+ )
100
+ .add_selection(selection_criterion)
101
+ )
102
+ else:
103
+ ch = (
104
+ alt.Chart(df)
105
+ .mark_circle(size=circles_size)
106
+ .encode(x=var_x, y=var_y, color=color)
107
+ )
108
+ else:
109
+ bs_error_abort(f"color must be a string, not {color}")
110
+ else:
111
+ ch = alt.Chart(df).mark_circle(size=circles_size).encode(x=var_x, y=var_y)
112
+
113
+ ch = _add_title(ch, title)
114
+ _maybe_save(ch, save)
115
+ return ch
116
+
117
+
118
+ def alt_lineplot(
119
+ df: pd.DataFrame,
120
+ str_x: str,
121
+ str_y: str,
122
+ time_series: bool = False,
123
+ save: str | None = None,
124
+ aggreg: str | None = None,
125
+ **kwargs,
126
+ ) -> alt.Chart:
127
+ """
128
+ scatterplot of `df[str_x]` vs `df[str_y]`
129
+
130
+ Args:
131
+ df: the data with columns `str_x` and `str_y`
132
+ str_x: the name of a continuous column
133
+ str_y: the name of a continuous column
134
+ time_series: `True` if x is a time series
135
+ save: the name of a file to save to (HTML extension will be added)
136
+ aggreg: the name of an aggregating function for `y`
137
+
138
+ Returns:
139
+ the `alt.Chart` object
140
+ """
141
+ type_x = "T" if time_series else "Q"
142
+ var_y = f"{aggreg}({str_y}):Q" if aggreg is not None else str_y
143
+
144
+ ch = alt.Chart(df).mark_line().encode(x=f"{str_x}:{type_x}", y=var_y)
145
+ if "title" in kwargs:
146
+ ch = ch.properties(title=kwargs["title"])
147
+ _maybe_save(ch, save)
148
+ return ch
149
+
150
+
151
+ def alt_plot_fun(
152
+ f: Callable,
153
+ start: float,
154
+ end: float,
155
+ npoints: int = 100,
156
+ save: str | None = None,
157
+ ) -> alt.Chart:
158
+ """
159
+ plots the function `f` from `start` to `end`
160
+
161
+ Args:
162
+ f: returns a Numpy array from a Numpy array
163
+ start: first point on `x` axis
164
+ end: last point on `x` axis
165
+ npoints: number of points
166
+ save: the name of a file to save to (HTML extension will be added)
167
+
168
+ Returns:
169
+ the `alt.Chart` object
170
+ """
171
+ step = (end - start) / npoints
172
+ points = np.arange(start, end + step, step)
173
+ fun_data = pd.DataFrame({"x": points, "y": f(points)})
174
+
175
+ ch = (
176
+ alt.Chart(fun_data)
177
+ .mark_line()
178
+ .encode(
179
+ x="x:Q",
180
+ y="y:Q",
181
+ )
182
+ )
183
+
184
+ _maybe_save(ch, save)
185
+ return ch
186
+
187
+
188
+ def alt_density(df: pd.DataFrame, str_x: str, save: str | None = None) -> alt.Chart:
189
+ """plots the density of `df[str_x]`
190
+
191
+ Args:
192
+ df: the data with the `str_x` variable
193
+ str_x: the name of a continuous column
194
+ save: the name of a file to save to (HTML extension will be added)
195
+
196
+ Returns:
197
+ the `alt.Chart` object
198
+ """
199
+ ch = (
200
+ alt.Chart(df)
201
+ .transform_density(
202
+ str_x,
203
+ as_=[str_x, "Density"],
204
+ )
205
+ .mark_area(opacity=0.4)
206
+ .encode(
207
+ x=f"{str_x}:Q",
208
+ y="Density:Q",
209
+ )
210
+ )
211
+
212
+ _maybe_save(ch, save)
213
+ return ch
214
+
215
+
216
+ def alt_linked_scatterplots(
217
+ df: pd.DataFrame,
218
+ str_x1: str,
219
+ str_x2: str,
220
+ str_y: str,
221
+ str_f: str,
222
+ save: str | None = None,
223
+ ) -> alt.Chart:
224
+ """
225
+ two scatterplots: of `df[str_x1]` vs `df[str_y]` and of `df[str_x2]` vs `df[str_y]`,
226
+ both with color as per `df[str_f]`. Selecting an interval in one shows up in the other.
227
+
228
+ Args:
229
+ df:
230
+ str_x1: the name of a continuous column
231
+ str_x2: the name of a continuous column
232
+ str_y: the name of a continuous column
233
+ str_f: the name of a categorical column
234
+ save: the name of a file to save to (HTML extension will be added)
235
+
236
+ Returns:
237
+ the `alt.Chart` object
238
+ """
239
+ interval = alt.selection_interval()
240
+
241
+ base = (
242
+ alt.Chart(df)
243
+ .mark_point()
244
+ .encode(
245
+ y=f"{str_y}:Q", color=alt.condition(interval, str_f, alt.value("lightgray"))
246
+ )
247
+ .properties(selection=interval)
248
+ )
249
+
250
+ ch = base.encode(x=f"{str_x1}:Q") | base.encode(x=f"{str_x2}:Q")
251
+
252
+ _maybe_save(ch, save)
253
+ return ch
254
+
255
+
256
+ def alt_scatterplot_with_histo(
257
+ df: pd.DataFrame, str_x: str, str_y: str, str_f: str, save: str | None = None
258
+ ) -> alt.Chart:
259
+ """
260
+ scatterplots `df[str_x]` vs `df[str_y]` with colors as per `df[str_f]`
261
+ allows to select an interval and histograns the counts of `df[str_f]` in the interval
262
+
263
+ Args:
264
+ df: the data with the `str_x` and `str_f` variables
265
+ str_x: the name of a continuous column
266
+ str_y: the name of a continuous column
267
+ str_f: the name of a categorical column
268
+ save: the name of a file to save to (HTML extension will be added)
269
+
270
+ Returns:
271
+ the `alt.Chart` object
272
+ """
273
+ interval = alt.selection_interval()
274
+
275
+ points = (
276
+ alt.Chart(df)
277
+ .mark_point()
278
+ .encode(
279
+ x=f"{str_x}:Q",
280
+ y=f"{str_y}:Q",
281
+ color=alt.condition(interval, str_f, alt.value("lightgray")),
282
+ )
283
+ .properties(selection=interval)
284
+ )
285
+
286
+ histogram = (
287
+ alt.Chart(df)
288
+ .mark_bar()
289
+ .encode(
290
+ x="count()",
291
+ y=str_f,
292
+ color=str_f,
293
+ )
294
+ .transform_filter(interval)
295
+ )
296
+
297
+ ch = points & histogram
298
+
299
+ _maybe_save(ch, save)
300
+ return ch
301
+
302
+
303
+ def alt_faceted_densities(
304
+ df: pd.DataFrame,
305
+ str_x: str,
306
+ str_f: str,
307
+ legend_title: str | None = None,
308
+ save: str | None = None,
309
+ max_cols: int | None = 4,
310
+ ) -> alt.Chart:
311
+ """
312
+ plots the density of `df[str_x]` by `df[str_f]` in column facets
313
+
314
+ Args:
315
+ df: the data with the `str_x` and `str_f` variables
316
+ str_x: the name of a continuous column
317
+ str_f: the name of a categorical column
318
+ legend_title: a title for the legend
319
+ save: the name of a file to save to (HTML extension will be added)
320
+ max_cols: we wrap after that number of columns
321
+
322
+ Returns:
323
+ the `alt.Chart` object
324
+ """
325
+ our_legend_title = str_f if legend_title is None else legend_title
326
+ ch = (
327
+ alt.Chart(df)
328
+ .transform_density(
329
+ str_x,
330
+ groupby=[str_f],
331
+ as_=[str_x, "Density"],
332
+ )
333
+ .mark_area(opacity=0.4)
334
+ .encode(
335
+ x=f"{str_x}:Q",
336
+ y="Density:Q",
337
+ color=alt.Color(f"{str_f}:N", title=our_legend_title),
338
+ )
339
+ .facet(f"{str_f}:N", columns=max_cols)
340
+ )
341
+
342
+ _maybe_save(ch, save)
343
+ return ch
344
+
345
+
346
+ def alt_superposed_lineplot(
347
+ df: pd.DataFrame,
348
+ str_x: str,
349
+ str_y: str,
350
+ str_f: str,
351
+ time_series: bool = False,
352
+ legend_title: str | None = None,
353
+ save: str | None = None,
354
+ ) -> alt.Chart:
355
+ """
356
+ plots `df[str_x]` vs `df[str_y]` by `df[str_f]` on one plot
357
+
358
+ Args:
359
+ df: the data with the `str_x`, `str_y`, and `str_f` variables
360
+ str_x: the name of a continuous `x` column
361
+ str_y: the name of a continuous `y` column
362
+ str_f: the name of a categorical `f` column
363
+ time_series: `True` if `str_x` is a time series
364
+ legend_title: a title for the legend
365
+ save: the name of a file to save to (HTML extension will be added)
366
+
367
+ Returns:
368
+ the `alt.Chart` object
369
+ """
370
+ type_x = "T" if time_series else "Q"
371
+ our_legend_title = str_f if legend_title is None else legend_title
372
+ ch = (
373
+ alt.Chart(df)
374
+ .mark_line()
375
+ .encode(
376
+ x=f"{str_x}:{type_x}",
377
+ y=f"{str_y}:Q",
378
+ color=alt.Color(f"{str_f}:N", title=our_legend_title),
379
+ )
380
+ )
381
+ _maybe_save(ch, save)
382
+ return ch
383
+
384
+
385
+ def alt_superposed_faceted_lineplot(
386
+ df: pd.DataFrame,
387
+ str_x: str,
388
+ str_y: str,
389
+ str_f: str,
390
+ str_g: str,
391
+ time_series: bool = False,
392
+ legend_title: str | None = None,
393
+ max_cols: int | None = 5,
394
+ save: str | None = None,
395
+ ) -> alt.Chart:
396
+ """
397
+ plots `df[str_x]` vs `df[str_y]` superposed by `df[str_f]` and faceted by `df[str_g]`
398
+
399
+ Args:
400
+ df: the data with the `str_x`, `str_y`, and `str_f` variables
401
+ str_x: the name of a continuous column
402
+ str_y: the name of a continuous column
403
+ str_f: the name of a categorical column
404
+ str_g: the name of a categorical column
405
+ time_series: `True` if `str_x` is a time series
406
+ legend_title: a title for the legend
407
+ save: the name of a file to save to (HTML extension will be added)
408
+ max_cols: we wrap after that number of columns
409
+
410
+
411
+ Returns:
412
+ the `alt.Chart` object
413
+ """
414
+ type_x = "T" if time_series else "Q"
415
+ our_title = str_f if legend_title is None else legend_title
416
+ ch = (
417
+ alt.Chart(df)
418
+ .mark_line()
419
+ .encode(
420
+ x=f"{str_x}:{type_x}",
421
+ y=f"{str_y}:Q",
422
+ color=alt.Color(f"{str_f}:N", title=our_title),
423
+ facet=alt.Facet(f"{str_g}:N", columns=max_cols),
424
+ )
425
+ )
426
+ _maybe_save(ch, save)
427
+ return ch
428
+
429
+
430
+ def alt_histogram_by(
431
+ df: pd.DataFrame,
432
+ str_x: str,
433
+ str_y: str,
434
+ str_agg: str | None = "mean",
435
+ save: str | None = None,
436
+ ) -> alt.Chart:
437
+ """
438
+ plots a histogram of a statistic of `str_y` by `str_x`
439
+
440
+ Args:
441
+ df: a dataframe with columns `str_x` and `str_y`
442
+ str_x: a categorical variable
443
+ str_y: a continuous variable
444
+ str_agg: how we aggregate the values of `str_y` by `str_x`
445
+ save: the name of a file to save to (HTML extension will be added)
446
+
447
+ Returns:
448
+ the Altair chart
449
+ """
450
+ ch = (
451
+ alt.Chart(df)
452
+ .mark_bar()
453
+ .encode(x=str_x, y=f"{str_agg}({str_y}):Q")
454
+ .properties(height=300, width=400)
455
+ )
456
+ _maybe_save(ch, save)
457
+ return ch
458
+
459
+
460
+ def alt_histogram_continuous(
461
+ df: pd.DataFrame, str_x: str, save: str | None = None
462
+ ) -> alt.Chart:
463
+ """
464
+ histogram of a continuous variable `df[str_x]`
465
+
466
+ Args:
467
+ df: the data with the `str_x`, `str_y`, and `str_f` variables
468
+ str_x: the name of a continuous column
469
+ save: the name of a file to save to (HTML extension will be added)
470
+
471
+ Returns:
472
+ the `alt.Chart` object
473
+ """
474
+ ch = alt.Chart(df).mark_bar().encode(alt.X(str_x, bin=True), y="count()")
475
+ _maybe_save(ch, save)
476
+ return ch
477
+
478
+
479
+ def alt_stacked_area(
480
+ df: pd.DataFrame,
481
+ str_x: str,
482
+ str_y: str,
483
+ str_f: str,
484
+ time_series: bool = False,
485
+ title: str | None = None,
486
+ save: str | None = None,
487
+ ) -> alt.Chart:
488
+ """
489
+ normalized stacked lineplots of `df[str_x]` vs `df[str_y]` by `df[str_f]`
490
+
491
+ Args:
492
+ df: the data with columns for `str_x`, `str_y`, and `str_f`
493
+ str_x: the name of a continuous column
494
+ str_y: the name of a continuous column
495
+ str_f: the name of a categorical column
496
+ time_series: `True` if `str_x` is a time series
497
+ title: a title for the plot
498
+ save: the name of a file to save to (HTML extension will be added)
499
+
500
+ Returns:
501
+ the `alt.Chart` object
502
+ """
503
+ type_x = "T" if time_series else "Q"
504
+ ch = (
505
+ alt.Chart(df)
506
+ .mark_area()
507
+ .encode(
508
+ x=f"{str_x}:{type_x}",
509
+ y=alt.Y(f"{str_y}:Q", stack="normalize"),
510
+ color=f"{str_f}:N",
511
+ )
512
+ )
513
+ if title is not None:
514
+ ch = ch.properties(title=title)
515
+
516
+ _maybe_save(ch, save)
517
+ return ch
518
+
519
+
520
+ def alt_stacked_area_facets(
521
+ df: pd.DataFrame,
522
+ str_x: str,
523
+ str_y: str,
524
+ str_f: str,
525
+ str_g: str,
526
+ time_series: bool = False,
527
+ max_cols: int | None = 5,
528
+ title: str | None = None,
529
+ save: str | None = None,
530
+ ) -> alt.Chart:
531
+ """
532
+ normalized stacked lineplots of `df[str_x]` vs `df[str_y]` by `df[str_f]`, faceted by `df[str_g]`
533
+
534
+ Args:
535
+ df: the data with columns for `str_x`, `str_y`, and `str_f`
536
+ str_x: the name of a continuous column
537
+ str_y: the name of a continuous column
538
+ str_f: the name of a categorical column
539
+ str_g: the name of a categorical column
540
+ time_series: `True` if `str_x` is a time series
541
+ title: a title for the plot
542
+ save: the name of a file to save to (HTML extension will be added)
543
+
544
+ Returns:
545
+ the `alt.Chart` object
546
+ """
547
+ type_x = "T" if time_series else "Q"
548
+ ch = (
549
+ alt.Chart(df)
550
+ .mark_area()
551
+ .encode(
552
+ x=f"{str_x}:{type_x}",
553
+ y=alt.Y(f"{str_y}:Q", stack="normalize"),
554
+ color=f"{str_f}:N",
555
+ facet=alt.Facet(f"{str_g}:N", columns=max_cols),
556
+ )
557
+ )
558
+ _maybe_save(ch, save)
559
+ return ch
560
+
561
+
562
+ def _stack_estimates(
563
+ estimate_names: str | list[str], estimates: np.ndarray, df: pd.DataFrame
564
+ ) -> tuple[pd.DataFrame, list[str]]:
565
+ """
566
+ adds to a dataframe `df` columns with names `estimate_names` for various `estimates of one coefficient
567
+
568
+ Args:
569
+ estimate_names: names of the n estimate columns to be added
570
+ estimates: a matrix with n columns vectors
571
+ df: a receiving data frame
572
+
573
+ Returns:
574
+ the dataframe, updated; and the names+['True value']
575
+ """
576
+ df1 = df.copy()
577
+ n_estimates = 1 if isinstance(estimate_names, str) else len(estimate_names)
578
+ if n_estimates == 1:
579
+ size_est = check_vector(estimates, "_stack_estimates")
580
+ if size_est != n_estimates:
581
+ bs_error_abort(
582
+ f"_stack_estimates: we have {n_estimates} names of estimators and"
583
+ f" {size_est} estimators"
584
+ )
585
+ df1[estimate_names] = estimates
586
+ ordered_estimates = [estimate_names, "True value"]
587
+ else:
588
+ shape_est = check_matrix(estimates, "_stack_estimates")
589
+ if shape_est[1] != n_estimates:
590
+ bs_error_abort(
591
+ f"_stack_estimates: we have {n_estimates} names of estimators and"
592
+ f" {shape_est[1]} estimators"
593
+ )
594
+ for i_est, est_name in enumerate(estimate_names):
595
+ df1[est_name] = estimates[:, i_est]
596
+ ordered_estimates = [*estimate_names, "True value"]
597
+
598
+ return df1, ordered_estimates
599
+
600
+
601
+ def plot_parameterized_estimates(
602
+ parameter_name: str,
603
+ parameter_values: np.ndarray,
604
+ coeff_names: str | list[str],
605
+ true_values: np.ndarray,
606
+ estimate_names: str | list[str],
607
+ estimates: np.ndarray,
608
+ colors: list[str],
609
+ save: str | None = None,
610
+ ) -> alt.Chart:
611
+ """
612
+ plots estimates of coefficients, with the true values, as a function of a parameter; one facet per coefficient
613
+
614
+ Args:
615
+ parameter_name: the name of the parameter
616
+ parameter_values: a vector of `n_vals` values for the parameter
617
+ coeff_names: the names of the `n_coeffs` coefficients
618
+ true_values: their true values, depending on the parameter or not
619
+ estimate_names: names of the estimates
620
+ estimates: their values
621
+ colors: colors for the various estimates
622
+ save: the name of a file to save to (HTML extension will be added)
623
+
624
+ Returns:
625
+ the `alt.Chart` object
626
+ """
627
+ n_vals = check_vector(parameter_values)
628
+ n_coeffs = 1 if isinstance(coeff_names, str) else len(coeff_names)
629
+ if n_coeffs == 1:
630
+ n_true = check_vector(true_values, "plot_parameterized_estimates")
631
+ if n_true != n_vals:
632
+ bs_error_abort(
633
+ f"plot_parameterized_estimates: we have {n_true} values and"
634
+ f" {n_vals} parameter values."
635
+ )
636
+ df = pd.DataFrame({parameter_name: parameter_values, "True value": true_values})
637
+ df1, ordered_estimates = _stack_estimates(estimate_names, estimates, df)
638
+ df1m = pd.melt(df1, parameter_name, var_name="Estimate")
639
+ ch = (
640
+ alt.Chart(df1m)
641
+ .mark_line()
642
+ .encode(
643
+ x=f"{parameter_name}:Q",
644
+ y="value:Q",
645
+ strokeDash=alt.StrokeDash("Estimate:N", sort=ordered_estimates),
646
+ color=alt.Color(
647
+ "Estimate:N",
648
+ sort=estimate_names,
649
+ scale=alt.Scale(domain=ordered_estimates, range=colors),
650
+ ),
651
+ )
652
+ )
653
+ else:
654
+ n_true, n_c = check_matrix(true_values, "plot_parameterized_estimates")
655
+ if n_true != n_vals:
656
+ bs_error_abort(
657
+ f"plot_parameterized_estimates: we have {n_true} true values and"
658
+ f" {n_vals} parameter values."
659
+ )
660
+ if n_c != n_coeffs:
661
+ bs_error_abort(
662
+ f"plot_parameterized_estimates: we have {n_c} columns of true values"
663
+ f" and {n_coeffs} coefficients."
664
+ )
665
+ df1 = [None] * n_coeffs
666
+ for i_coeff, coeff in enumerate(coeff_names):
667
+ df_i = pd.DataFrame(
668
+ {
669
+ parameter_name: parameter_values,
670
+ "True value": true_values[:, i_coeff],
671
+ }
672
+ )
673
+ df1[i_coeff], ordered_estimates = _stack_estimates(
674
+ estimate_names, estimates[..., i_coeff], df_i
675
+ )
676
+ df1[i_coeff]["Coefficient"] = coeff
677
+
678
+ df2 = pd.concat(df1[i_coeff] for i_coeff in range(n_coeffs))
679
+ ordered_colors = colors
680
+ df2m = pd.melt(df2, [parameter_name, "Coefficient"], var_name="Estimate")
681
+ ch = (
682
+ alt.Chart(df2m)
683
+ .mark_line()
684
+ .encode(
685
+ x=f"{parameter_name}:Q",
686
+ y="value:Q",
687
+ strokeDash=alt.StrokeDash("Estimate:N", sort=ordered_estimates),
688
+ color=alt.Color(
689
+ "Estimate:N",
690
+ sort=ordered_estimates,
691
+ scale=alt.Scale(domain=ordered_estimates, range=ordered_colors),
692
+ ),
693
+ )
694
+ .facet(alt.Facet("Coefficient:N", sort=coeff_names))
695
+ .resolve_scale(y="independent")
696
+ )
697
+
698
+ _maybe_save(ch, save)
699
+
700
+ return ch
701
+
702
+
703
+ def plot_true_sim_facets(
704
+ parameter_name: str,
705
+ parameter_values: np.ndarray,
706
+ stat_names: list[str],
707
+ stat_true: np.ndarray,
708
+ stat_sim: np.ndarray,
709
+ colors: list[str],
710
+ stat_title: str | None = "Statistic",
711
+ subtitle: str | None = "True vs estimated",
712
+ ncols: int | None = 3,
713
+ save: str | None = None,
714
+ ) -> alt.Chart:
715
+ """
716
+ plots simulated and true values of statistics as a function of a parameter; one facet per coefficient
717
+
718
+ Args:
719
+ parameter_name: the name of the parameter
720
+ parameter_values: a vector of `n_vals` values for the parameter
721
+ stat_names: the names of the `n` statistics
722
+ stat_true: their true values, `(n_vals, n)`
723
+ stat_sim: their simulated values
724
+ colors: colors for the various estimates
725
+ stat_title: main title
726
+ subtitle: subtitle
727
+ ncols: wrap after `ncols` columns
728
+ save: the name of a file to save to (HTML extension will be added)
729
+
730
+ Returns:
731
+ the `alt.Chart` object
732
+ """
733
+ n_stats = len(stat_names)
734
+ nvals = check_vector(parameter_values, "plot_true_sim_facets")
735
+ nv_true, n_stat_true = check_matrix(stat_true, "plot_true_sim_facets")
736
+ if nv_true != nvals:
737
+ bs_error_abort(
738
+ f"plot_true_sim_facets: we have {nvals} parameter values and {nv_true} for"
739
+ " stat_true."
740
+ )
741
+ nv_est, n_stat_est = check_matrix(stat_sim, "plot_true_sim_facets")
742
+ if nv_est != nvals:
743
+ bs_error_abort(
744
+ f"plot_true_sim_facets: we have {nvals} parameter values and {nv_est} for"
745
+ " stat_sim."
746
+ )
747
+ if n_stat_true != n_stats:
748
+ bs_error_abort(
749
+ f"plot_true_sim_facets: we have {n_stats} names for {n_stat_true} true"
750
+ " statistics."
751
+ )
752
+ if n_stat_est != n_stats:
753
+ bs_error_abort(
754
+ f"plot_true_sim_facets: we have {n_stats} names for {n_stat_est} estimated"
755
+ " statistics."
756
+ )
757
+ df = pd.DataFrame(
758
+ {
759
+ parameter_name: parameter_values,
760
+ "True value": stat_true[:, 0],
761
+ "Estimated": stat_sim[:, 0],
762
+ stat_title: stat_names[0],
763
+ }
764
+ )
765
+ for i_stat in range(1, n_stats):
766
+ df_i = pd.DataFrame(
767
+ {
768
+ parameter_name: parameter_values,
769
+ "True value": stat_true[:, i_stat],
770
+ "Estimated": stat_sim[:, i_stat],
771
+ stat_title: stat_names[i_stat],
772
+ }
773
+ )
774
+ df = pd.concat((df, df_i))
775
+ sub_order = ["True value", "Estimated"]
776
+ dfm = pd.melt(df, [parameter_name, stat_title], var_name=subtitle)
777
+ ch = (
778
+ alt.Chart(dfm)
779
+ .mark_line()
780
+ .encode(
781
+ x=f"{parameter_name}:Q",
782
+ y="value:Q",
783
+ strokeDash=alt.StrokeDash(f"{subtitle}:N", sort=sub_order),
784
+ color=alt.Color(
785
+ f"{subtitle}:N",
786
+ sort=sub_order,
787
+ scale=alt.Scale(domain=sub_order, range=colors),
788
+ ),
789
+ facet=alt.Facet(f"{stat_title}:N", sort=stat_names, columns=ncols),
790
+ )
791
+ .resolve_scale(y="independent")
792
+ )
793
+
794
+ _maybe_save(ch, save)
795
+
796
+ return ch
797
+
798
+
799
+ def plot_true_sim2_facets(
800
+ parameter_name: str,
801
+ parameter_values: np.ndarray,
802
+ stat_names: list[str],
803
+ stat_true: np.ndarray,
804
+ stat_sim1: np.ndarray,
805
+ stat_sim2: np.ndarray,
806
+ colors: list[str],
807
+ stat_title: str | None = "Statistic",
808
+ subtitle: str | None = "True vs estimated",
809
+ ncols: int | None = 3,
810
+ save: str | None = None,
811
+ ) -> alt.Chart:
812
+ """
813
+ plots simulated values for two methods and true values of statistics as a function of a parameter;
814
+ one facet per coefficient
815
+
816
+ Args:
817
+ parameter_name: the name of the parameter
818
+ parameter_values: a vector of `n_vals` values for the parameter
819
+ stat_names: the names of the `n` statistics
820
+ stat_true: their true values, `(n_vals, n)`
821
+ stat_sim1: their simulated values, method 1
822
+ stat_sim2: their simulated values, method 2
823
+ colors: colors for the various estimates
824
+ stat_title: main title
825
+ subtitle: subtitle
826
+ ncols: wrap after `ncols` columns
827
+ save: the name of a file to save to (HTML extension will be added)
828
+
829
+ Returns:
830
+ the `alt.Chart` object
831
+ """
832
+ n_stats = len(stat_names)
833
+ nvals = check_vector(parameter_values, "plot_true_sim2_facets")
834
+ nv_true, n_stat_true = check_matrix(stat_true, "plot_true_sim2_facets")
835
+ if nv_true != nvals:
836
+ bs_error_abort(f"we have {nvals} parameter values and {nv_true} for stat_true.")
837
+ if n_stat_true != n_stats:
838
+ bs_error_abort(f"we have {n_stats} names for {n_stat_true} true statistics.")
839
+
840
+ nv_est1, n_stat_est1 = check_matrix(stat_sim1, "plot_true_sim2_facets")
841
+ if nv_est1 != nvals:
842
+ bs_error_abort(f"we have {nvals} parameter values and {nv_est1} for stat_sim1.")
843
+ if n_stat_est1 != n_stats:
844
+ bs_error_abort(
845
+ f"we have {n_stats} names for {n_stat_est1} estimated statistics."
846
+ )
847
+ nv_est2, n_stat_est2 = check_matrix(stat_sim2, "plot_true_sim2_facets")
848
+ if nv_est2 != nvals:
849
+ bs_error_abort(f"we have {nvals} parameter values and {nv_est2} for stat_sim2.")
850
+ if n_stat_est2 != n_stats:
851
+ bs_error_abort(
852
+ f"we have {n_stats} names for {n_stat_est2} estimated statistics."
853
+ )
854
+
855
+ df = pd.DataFrame(
856
+ {
857
+ parameter_name: parameter_values,
858
+ "True value": stat_true[:, 0],
859
+ "Estimated1": stat_sim1[:, 0],
860
+ "Estimated2": stat_sim2[:, 0],
861
+ stat_title: stat_names[0],
862
+ }
863
+ )
864
+ for i_stat in range(1, n_stats):
865
+ df_i = pd.DataFrame(
866
+ {
867
+ parameter_name: parameter_values,
868
+ "True value": stat_true[:, i_stat],
869
+ "Estimated1": stat_sim1[:, i_stat],
870
+ "Estimated2": stat_sim2[:, i_stat],
871
+ stat_title: stat_names[i_stat],
872
+ }
873
+ )
874
+ df = pd.concat((df, df_i))
875
+ sub_order = ["True value", "Estimated1", "Estimated2"]
876
+ dfm = pd.melt(df, [parameter_name, stat_title], var_name=subtitle)
877
+ ch = (
878
+ alt.Chart(dfm)
879
+ .mark_line()
880
+ .encode(
881
+ x=f"{parameter_name}:Q",
882
+ y="value:Q",
883
+ strokeDash=alt.StrokeDash(f"{subtitle}:N", sort=sub_order),
884
+ color=alt.Color(
885
+ f"{subtitle}:N",
886
+ sort=sub_order,
887
+ scale=alt.Scale(domain=sub_order, range=colors),
888
+ ),
889
+ facet=alt.Facet(f"{stat_title}:N", sort=stat_names, columns=ncols),
890
+ )
891
+ .resolve_scale(y="independent")
892
+ )
893
+
894
+ _maybe_save(ch, save)
895
+
896
+ return ch
897
+
898
+
899
+ def alt_tick_plots(
900
+ df: pd.DataFrame, list_vars: str | list[str], save: str | None = None
901
+ ) -> alt.Chart:
902
+ """
903
+ ticks plot the `df` variables in `list_vars`, arranged vertically
904
+
905
+ Args:
906
+ df: a dataframe with the variables in `list_vars`
907
+ list_vars: the name of a column of `df`, or a list of names
908
+ save: the name of a file to save to (HTML extension will be added)
909
+
910
+ Returns:
911
+ the `alt.Chart` object
912
+ """
913
+ if isinstance(list_vars, str):
914
+ varname = list_vars
915
+ ch = alt.Chart(df).encode(x=varname).mark_tick()
916
+ else:
917
+ ch = (
918
+ alt.Chart(df)
919
+ .encode(alt.X(alt.repeat("row"), type="quantitative"))
920
+ .mark_tick()
921
+ .repeat(row=list_vars)
922
+ .resolve_scale(y="independent")
923
+ )
924
+
925
+ _maybe_save(ch, save)
926
+
927
+ return ch