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.
@@ -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]