scistackplot 0.1.26__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.
- scistackplot/__init__.py +239 -0
- scistackplot/capability.py +656 -0
- scistackplot/codegen.py +886 -0
- scistackplot/groups.py +110 -0
- scistackplot/reduce.py +1475 -0
- scistackplot/render/__init__.py +26 -0
- scistackplot/render/base.py +262 -0
- scistackplot/render/mpl.py +458 -0
- scistackplot/render/plotly_.py +537 -0
- scistackplot/resolved.py +240 -0
- scistackplot/roles.py +413 -0
- scistackplot/shape.py +106 -0
- scistackplot/sources/__init__.py +7 -0
- scistackplot/sources/base.py +256 -0
- scistackplot/sources/csv.py +67 -0
- scistackplot/sources/frame.py +45 -0
- scistackplot/spec.py +698 -0
- scistackplot/table.py +328 -0
- scistackplot/variants.py +743 -0
- scistackplot/xaxis.py +183 -0
- scistackplot/ylimits.py +451 -0
- scistackplot-0.1.26.dist-info/METADATA +212 -0
- scistackplot-0.1.26.dist-info/RECORD +24 -0
- scistackplot-0.1.26.dist-info/WHEEL +4 -0
scistackplot/__init__.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""
|
|
2
|
+
scistackplot — spec-driven plotting for long-format scientific data.
|
|
3
|
+
|
|
4
|
+
Standalone: point it at a CSV or a DataFrame and it works, with no database
|
|
5
|
+
and no configuration. Compatible: the object it is built around, ``PlotSpec``,
|
|
6
|
+
is exactly what the body of a scidb ``plot_`` endpoint needs, so an interactive
|
|
7
|
+
exploration can be frozen into a lineage-tracked pipeline step.
|
|
8
|
+
|
|
9
|
+
Typical standalone use::
|
|
10
|
+
|
|
11
|
+
import pandas as pd
|
|
12
|
+
from scistackplot import DataFrameSource, PlotSpec, Role, PlotKind, render
|
|
13
|
+
|
|
14
|
+
source = DataFrameSource(pd.read_csv("gait.csv"))
|
|
15
|
+
spec = PlotSpec(
|
|
16
|
+
measures=["StepLength"],
|
|
17
|
+
roles={"session": Role.X, "limb": Role.COLOR, "subject": Role.FREE},
|
|
18
|
+
kind=PlotKind.BOX,
|
|
19
|
+
)
|
|
20
|
+
figure = render(source, spec)
|
|
21
|
+
|
|
22
|
+
Typical pipeline use (as a scidb endpoint body)::
|
|
23
|
+
|
|
24
|
+
def plot_step_length(df, filename):
|
|
25
|
+
return render(df, spec) # framework saves + closes the Figure
|
|
26
|
+
|
|
27
|
+
See ``docs/claude/plotting-library-design.md`` for the architecture.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
from typing import Any
|
|
33
|
+
|
|
34
|
+
from .capability import (
|
|
35
|
+
available_plots,
|
|
36
|
+
capabilities,
|
|
37
|
+
default_plot,
|
|
38
|
+
factors_menu,
|
|
39
|
+
grouping_summary,
|
|
40
|
+
role_hint,
|
|
41
|
+
role_label,
|
|
42
|
+
role_options,
|
|
43
|
+
variant_summary,
|
|
44
|
+
why_unavailable,
|
|
45
|
+
)
|
|
46
|
+
from .codegen import (
|
|
47
|
+
default_function_name,
|
|
48
|
+
extract_spec,
|
|
49
|
+
generate_plot_function,
|
|
50
|
+
generate_script,
|
|
51
|
+
)
|
|
52
|
+
from .reduce import MAX_TRANSPORT_POINTS, resolve, resolve_one
|
|
53
|
+
from .render import render_matplotlib, render_plotly
|
|
54
|
+
from .resolved import Encoding, Labels, Panel, ResolvedPlot
|
|
55
|
+
from .roles import (
|
|
56
|
+
RoleError,
|
|
57
|
+
complete_roles,
|
|
58
|
+
default_roles,
|
|
59
|
+
default_spec,
|
|
60
|
+
fanout_keys,
|
|
61
|
+
validate,
|
|
62
|
+
)
|
|
63
|
+
from .shape import Shape, classify_column, classify_value, is_plottable
|
|
64
|
+
from .sources import BaseSource, CsvSource, DataFrameSource, DataSource
|
|
65
|
+
from .spec import (
|
|
66
|
+
Aggregation,
|
|
67
|
+
ErrorBand,
|
|
68
|
+
FacetOptions,
|
|
69
|
+
Filter,
|
|
70
|
+
LevelGroup,
|
|
71
|
+
MatchOp,
|
|
72
|
+
Matcher,
|
|
73
|
+
PlotKind,
|
|
74
|
+
PlotSpec,
|
|
75
|
+
Role,
|
|
76
|
+
Statistic,
|
|
77
|
+
StyleOptions,
|
|
78
|
+
VariantSet,
|
|
79
|
+
YAxis,
|
|
80
|
+
grid_shape_for,
|
|
81
|
+
)
|
|
82
|
+
from .xaxis import XGroup, XPlan, leaf_key, plan_x_axis
|
|
83
|
+
from .ylimits import describe as describe_y_limits
|
|
84
|
+
from .ylimits import eligible_scope, limits_by_scope
|
|
85
|
+
from .table import (
|
|
86
|
+
CODE_FACTOR_PREFIX,
|
|
87
|
+
FactorInfo,
|
|
88
|
+
LongTable,
|
|
89
|
+
MeasureInfo,
|
|
90
|
+
natural_sort_key,
|
|
91
|
+
)
|
|
92
|
+
from .variants import (
|
|
93
|
+
LATEST,
|
|
94
|
+
VARIANT_FACTOR,
|
|
95
|
+
apply_variant_sets,
|
|
96
|
+
auto_label,
|
|
97
|
+
SPAN_LOCATION_LIMIT,
|
|
98
|
+
default_selection,
|
|
99
|
+
defined_sets,
|
|
100
|
+
describe_span,
|
|
101
|
+
spanned_code_axes,
|
|
102
|
+
strip_answered_roles,
|
|
103
|
+
variant_set_mask,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
__all__ = [
|
|
107
|
+
# spec
|
|
108
|
+
"PlotSpec",
|
|
109
|
+
"Role",
|
|
110
|
+
"PlotKind",
|
|
111
|
+
"Statistic",
|
|
112
|
+
"ErrorBand",
|
|
113
|
+
"Aggregation",
|
|
114
|
+
"FacetOptions",
|
|
115
|
+
"YAxis",
|
|
116
|
+
"eligible_scope",
|
|
117
|
+
"limits_by_scope",
|
|
118
|
+
"describe_y_limits",
|
|
119
|
+
"grid_shape_for",
|
|
120
|
+
"Matcher",
|
|
121
|
+
"MatchOp",
|
|
122
|
+
"StyleOptions",
|
|
123
|
+
"Filter",
|
|
124
|
+
"LevelGroup",
|
|
125
|
+
"VariantSet",
|
|
126
|
+
# data
|
|
127
|
+
"LongTable",
|
|
128
|
+
"FactorInfo",
|
|
129
|
+
"MeasureInfo",
|
|
130
|
+
"Shape",
|
|
131
|
+
"classify_value",
|
|
132
|
+
"classify_column",
|
|
133
|
+
"is_plottable",
|
|
134
|
+
"natural_sort_key",
|
|
135
|
+
# sources
|
|
136
|
+
"DataSource",
|
|
137
|
+
"BaseSource",
|
|
138
|
+
"CsvSource",
|
|
139
|
+
"DataFrameSource",
|
|
140
|
+
# policy
|
|
141
|
+
"available_plots",
|
|
142
|
+
"variant_summary",
|
|
143
|
+
"CODE_FACTOR_PREFIX",
|
|
144
|
+
# named variants
|
|
145
|
+
"VARIANT_FACTOR",
|
|
146
|
+
"LATEST",
|
|
147
|
+
"apply_variant_sets",
|
|
148
|
+
"default_selection",
|
|
149
|
+
"variant_set_mask",
|
|
150
|
+
"auto_label",
|
|
151
|
+
"defined_sets",
|
|
152
|
+
"spanned_code_axes",
|
|
153
|
+
"describe_span",
|
|
154
|
+
"SPAN_LOCATION_LIMIT",
|
|
155
|
+
"strip_answered_roles",
|
|
156
|
+
"default_plot",
|
|
157
|
+
"why_unavailable",
|
|
158
|
+
"role_options",
|
|
159
|
+
"role_label",
|
|
160
|
+
"role_hint",
|
|
161
|
+
"grouping_summary",
|
|
162
|
+
"factors_menu",
|
|
163
|
+
"capabilities",
|
|
164
|
+
"default_roles",
|
|
165
|
+
"default_spec",
|
|
166
|
+
"complete_roles",
|
|
167
|
+
"fanout_keys",
|
|
168
|
+
"validate",
|
|
169
|
+
"RoleError",
|
|
170
|
+
# resolution + rendering
|
|
171
|
+
"resolve",
|
|
172
|
+
"resolve_one",
|
|
173
|
+
"XPlan",
|
|
174
|
+
"XGroup",
|
|
175
|
+
"plan_x_axis",
|
|
176
|
+
"leaf_key",
|
|
177
|
+
"ResolvedPlot",
|
|
178
|
+
"Panel",
|
|
179
|
+
"Encoding",
|
|
180
|
+
"Labels",
|
|
181
|
+
"render",
|
|
182
|
+
"render_all",
|
|
183
|
+
"render_matplotlib",
|
|
184
|
+
"render_plotly",
|
|
185
|
+
"MAX_TRANSPORT_POINTS",
|
|
186
|
+
# export
|
|
187
|
+
"generate_plot_function",
|
|
188
|
+
"generate_script",
|
|
189
|
+
"default_function_name",
|
|
190
|
+
"extract_spec",
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
__version__ = "0.1.0"
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def as_table(data: Any) -> LongTable:
|
|
197
|
+
"""
|
|
198
|
+
Coerce whatever the caller has into a :class:`LongTable`.
|
|
199
|
+
|
|
200
|
+
Accepts a LongTable, a DataSource, or a plain DataFrame — the last being
|
|
201
|
+
what a scidb ``plot_`` endpoint receives, so ``render(df, spec)`` just works
|
|
202
|
+
inside a pipeline step.
|
|
203
|
+
"""
|
|
204
|
+
if isinstance(data, LongTable):
|
|
205
|
+
return data
|
|
206
|
+
if hasattr(data, "describe") and hasattr(data, "get_table"): # DataSource
|
|
207
|
+
# A scidb source needs to be told which variable to load; a flat source
|
|
208
|
+
# already holds one table. default_measure() answers both.
|
|
209
|
+
measure = getattr(data, "default_measure", lambda: None)()
|
|
210
|
+
return data.get_table([measure] if measure else [])
|
|
211
|
+
return LongTable.from_frame(data)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def render(data: Any, spec: PlotSpec, *, backend: str = "matplotlib"):
|
|
215
|
+
"""
|
|
216
|
+
Resolve and render a single figure.
|
|
217
|
+
|
|
218
|
+
Raises if the spec fans out into several figures — in a pipeline that
|
|
219
|
+
fan-out belongs to ``for_each``'s iteration keys, not to one endpoint call,
|
|
220
|
+
and silently returning only the first figure would hide the mistake.
|
|
221
|
+
Use :func:`render_all` when you deliberately want the whole set.
|
|
222
|
+
"""
|
|
223
|
+
figures = render_all(data, spec, backend=backend)
|
|
224
|
+
if len(figures) > 1:
|
|
225
|
+
raise ValueError(
|
|
226
|
+
f"This spec produces {len(figures)} figures (iterating over "
|
|
227
|
+
f"{', '.join(spec.iterate_factors)}). Inside a pipeline, pass those "
|
|
228
|
+
f"as for_each iteration keys instead of Role.ITERATE; outside one, "
|
|
229
|
+
f"call render_all()."
|
|
230
|
+
)
|
|
231
|
+
return figures[0]
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def render_all(data: Any, spec: PlotSpec, *, backend: str = "matplotlib") -> list:
|
|
235
|
+
"""Resolve and render every figure in the fan-out."""
|
|
236
|
+
table = as_table(data)
|
|
237
|
+
resolved = resolve(spec, table)
|
|
238
|
+
renderer = render_plotly if backend == "plotly" else render_matplotlib
|
|
239
|
+
return [renderer(item) for item in resolved]
|