samplekit 1.0.0rc2__cp315-cp315-win_amd64.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.
- samplekit/__init__.py +405 -0
- samplekit/__init__.pyi +2218 -0
- samplekit/_figure.py +173 -0
- samplekit/_figures.py +2066 -0
- samplekit/_native.cp315-win_amd64.pyd +0 -0
- samplekit/_script.py +231 -0
- samplekit/_scripts.py +212 -0
- samplekit/_worker.py +1458 -0
- samplekit/py.typed +0 -0
- samplekit-1.0.0rc2.dist-info/METADATA +196 -0
- samplekit-1.0.0rc2.dist-info/RECORD +15 -0
- samplekit-1.0.0rc2.dist-info/WHEEL +4 -0
- samplekit-1.0.0rc2.dist-info/licenses/LICENSE +21 -0
- samplekit-1.0.0rc2.dist-info/licenses/THIRD-PARTY-LICENSES.md +2496 -0
- samplekit-1.0.0rc2.dist-info/sboms/samplekit.cyclonedx.json +10882 -0
samplekit/__init__.pyi
ADDED
|
@@ -0,0 +1,2218 @@
|
|
|
1
|
+
"""The machine-readable contract of samplekit.
|
|
2
|
+
|
|
3
|
+
Checked against the runtime surface in both directions by the package's tests:
|
|
4
|
+
every public runtime attribute is declared here, and every declaration exists.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections.abc import Callable, ItemsView, Iterable, Iterator, KeysView, Mapping, Sequence
|
|
8
|
+
from datetime import date, datetime
|
|
9
|
+
from os import PathLike
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Generic, Literal, Self, TypeAlias, TypeVar, overload
|
|
12
|
+
|
|
13
|
+
_T = TypeVar("_T")
|
|
14
|
+
|
|
15
|
+
# -------------------------------------------------------------- aliases
|
|
16
|
+
|
|
17
|
+
Scalar: TypeAlias = bool | int | float | str | date | datetime | None
|
|
18
|
+
AttributeScalar: TypeAlias = bool | int | float | str | date | datetime
|
|
19
|
+
AttributeList: TypeAlias = (
|
|
20
|
+
BoundList[bool] | BoundList[int] | BoundList[float]
|
|
21
|
+
| BoundList[str] | BoundList[date] | BoundList[datetime]
|
|
22
|
+
)
|
|
23
|
+
Attribute: TypeAlias = Scalar | AttributeList
|
|
24
|
+
IndexScalar: TypeAlias = bool | int | float | str | date | datetime
|
|
25
|
+
Index: TypeAlias = IndexScalar | tuple[IndexScalar, ...]
|
|
26
|
+
CellInput: TypeAlias = Scalar | Sequence[float] | Property
|
|
27
|
+
# What a table formula gives for one cell: a number, or (value, uncertainty),
|
|
28
|
+
# as a property's formula does; a Property carrying both is taken too.
|
|
29
|
+
CellOutput: TypeAlias = Scalar | tuple[Scalar, float | None] | Property
|
|
30
|
+
Pathish: TypeAlias = str | PathLike[str]
|
|
31
|
+
Precision: TypeAlias = str | tuple[str, str]
|
|
32
|
+
# A formula of a property, of no argument; on a table's column, of the row.
|
|
33
|
+
Compute: TypeAlias = Callable[[], Scalar | tuple[Scalar, float | None]] | Callable[[RowView], CellOutput]
|
|
34
|
+
ValueCompute: TypeAlias = Callable[[], Scalar] | Callable[[RowView], Scalar]
|
|
35
|
+
UncertaintyCompute: TypeAlias = Callable[[], float | None] | Callable[[RowView], float | None]
|
|
36
|
+
RowCompute: TypeAlias = Callable[[RowView], CellOutput | Mapping[str, CellOutput]]
|
|
37
|
+
ColumnCompute: TypeAlias = Callable[
|
|
38
|
+
[Mapping[str, ColumnView]],
|
|
39
|
+
Sequence[CellOutput] | Mapping[str, Sequence[CellOutput]],
|
|
40
|
+
]
|
|
41
|
+
FieldKey: TypeAlias = str | Field
|
|
42
|
+
# What a formula reads: names, or by channel — {"v": [...], "u": [...]}.
|
|
43
|
+
Declared: TypeAlias = Sequence[str] | Mapping[str, Sequence[str]]
|
|
44
|
+
# One end of an axis: a number, or free — None, or "auto" as .samplekitrc writes it.
|
|
45
|
+
AxisBound: TypeAlias = float | Literal["auto"] | None
|
|
46
|
+
# What a column's cells hold, read as a list: numbers, text or dates, as its
|
|
47
|
+
# rows hold them, which a checker cannot tell from the column's name. Typed
|
|
48
|
+
# as `Scalar`, a model's arithmetic over a column — a sum, a slope, a mean —
|
|
49
|
+
# is refused; the code reading the list knows what its column holds.
|
|
50
|
+
CellValue: TypeAlias = Any
|
|
51
|
+
|
|
52
|
+
# -------------------------------------------------------------- data
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
__version__: str
|
|
56
|
+
|
|
57
|
+
class BoundList(list[_T]):
|
|
58
|
+
"""A list bound to a sample: its changes are the sample's.
|
|
59
|
+
|
|
60
|
+
A sample's tags, a list attribute and a property's readings are read as
|
|
61
|
+
one. ``append``, ``remove``, an item assigned and every other change is
|
|
62
|
+
checked and made in the sample too, and a refused item leaves both
|
|
63
|
+
unchanged. ``list(...)`` or ``.copy()`` is a plain copy, bound to
|
|
64
|
+
nothing, and so is a list read before the attribute was assigned again.
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> steel.tags.append("reference")
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Property:
|
|
72
|
+
"""A property: a value, its uncertainty and its unit.
|
|
73
|
+
|
|
74
|
+
A property stands for its value in arithmetic and comparisons, so
|
|
75
|
+
``steel.area * 2`` and ``steel.area > 25`` work on the number, and it prints
|
|
76
|
+
as the whole property, ``25.056 ± 0.053 mm²``. A model declares its properties in its
|
|
77
|
+
``__init__``; a property a model does not declare is made the same way and
|
|
78
|
+
assigned to the sample.
|
|
79
|
+
|
|
80
|
+
A property is one of four kinds, by what is given: a value entered by hand;
|
|
81
|
+
readings, whose value and uncertainty a statistic gives; a value a formula
|
|
82
|
+
computes (``compute``, or ``compute_value`` and ``compute_uncertainty``);
|
|
83
|
+
or nothing yet.
|
|
84
|
+
|
|
85
|
+
A ``Table`` declares its columns with it too: there a property describes
|
|
86
|
+
every cell of its column, with the same keywords, and its formula is given
|
|
87
|
+
the row, ``f(row)``. A column takes no value nor uncertainty: each cell
|
|
88
|
+
holds its own, in the file.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
value: The value — a number, text, a boolean, a date, ``samplekit.NA``
|
|
92
|
+
— or a list of readings, or a statistic from ``samplekit.stats``
|
|
93
|
+
that gives the value from the readings.
|
|
94
|
+
uncertainty: The absolute standard uncertainty, in the value's unit, or
|
|
95
|
+
a spread from ``samplekit.stats``.
|
|
96
|
+
unit: The unit, as the file writes it: ``"g"``, ``"degC"``.
|
|
97
|
+
symbol: Not taken here: a symbol is declared in ``.samplekitrc``,
|
|
98
|
+
under ``[property.NAME]``.
|
|
99
|
+
precision: Not taken here: a precision is declared in ``.samplekitrc``,
|
|
100
|
+
under ``[property.NAME]``.
|
|
101
|
+
compute: A function of no argument returning the property: a number,
|
|
102
|
+
or ``(value, uncertainty)``.
|
|
103
|
+
compute_value: A function of no argument returning the value alone,
|
|
104
|
+
beside an uncertainty entered, given by readings or computed apart.
|
|
105
|
+
compute_uncertainty: A function of no argument returning the
|
|
106
|
+
uncertainty, beside a value entered or computed apart.
|
|
107
|
+
compute_column: For a table's column only: a function given the
|
|
108
|
+
columns it reads, by name, and returning a list, one cell per row,
|
|
109
|
+
each a number or ``(value, uncertainty)``.
|
|
110
|
+
depends_on: What the formula reads: a list of names — ``["width", "thickness"]``,
|
|
111
|
+
``"curve.force"`` for a column, ``"row.force"`` for a
|
|
112
|
+
cell of the same row — or, where the value and the uncertainty have
|
|
113
|
+
a formula each, ``{"v": [...], "u": [...]}``. ``[]`` says it reads
|
|
114
|
+
nothing of the sample. It can also be declared later with
|
|
115
|
+
``Sample.set_dependencies``.
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
ValueError: Two arguments give the same thing — ``value`` and a
|
|
119
|
+
formula, ``compute`` and ``compute_value``, ``compute_column`` and
|
|
120
|
+
another — or a value is not a number where a unit is given.
|
|
121
|
+
TypeError: A formula is not callable, or a symbol or a precision is
|
|
122
|
+
given.
|
|
123
|
+
|
|
124
|
+
Example:
|
|
125
|
+
>>> import samplekit as sk
|
|
126
|
+
>>> sk.Property(3.2, 0.1, unit="kg")
|
|
127
|
+
Property(value=3.2, uncertainty=0.1, unit='kg')
|
|
128
|
+
>>> width = sk.Property(unit="mm", value=sk.stats.mean,
|
|
129
|
+
... uncertainty=sk.stats.standard_error)
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def __init__(
|
|
133
|
+
self,
|
|
134
|
+
value: Scalar | Sequence[float] | Property | Statistic = None,
|
|
135
|
+
uncertainty: float | Statistic | None = None,
|
|
136
|
+
unit: str | None = None,
|
|
137
|
+
compute: Compute | None = None,
|
|
138
|
+
compute_value: ValueCompute | None = None,
|
|
139
|
+
compute_uncertainty: UncertaintyCompute | None = None,
|
|
140
|
+
compute_column: ColumnCompute | None = None,
|
|
141
|
+
# What this formula reads, so that a change to one of them makes it
|
|
142
|
+
# outdated. Optional here rather than required: a model may declare the
|
|
143
|
+
# same thing with `Sample.set_dependencies`, and a checker cannot see
|
|
144
|
+
# that from the call. A computed property that declares neither is refused once the
|
|
145
|
+
# model is built — `depends_on=[]` says it reads nothing.
|
|
146
|
+
depends_on: Declared | None = None,
|
|
147
|
+
) -> None: ...
|
|
148
|
+
# A property's value is read as a number, so that a model's arithmetic
|
|
149
|
+
# type-checks: a formula runs only once its inputs hold one, and waits
|
|
150
|
+
# otherwise. Outside a formula it may hold text, a date, NA or nothing,
|
|
151
|
+
# which the code reading it checks. Assigned, it takes all of them, and
|
|
152
|
+
# a list of readings.
|
|
153
|
+
@property
|
|
154
|
+
def value(self) -> float:
|
|
155
|
+
"""The value — a number, text, a boolean, a date, ``samplekit.NA``, or
|
|
156
|
+
``None`` when there is none.
|
|
157
|
+
|
|
158
|
+
Assigning a value to a computed property overrides its formula: the
|
|
159
|
+
property reads *edited* until ``compute(force=True)`` gives it back to the
|
|
160
|
+
formula. Assigning a list gives the property readings. A property with a
|
|
161
|
+
unit takes numbers only, and raises ``ValueError`` otherwise.
|
|
162
|
+
"""
|
|
163
|
+
@value.setter
|
|
164
|
+
def value(self, value: Scalar | NotApplicable | Sequence[float] | Property) -> None: ...
|
|
165
|
+
@property
|
|
166
|
+
def v(self) -> float:
|
|
167
|
+
"""The value — the same as ``value``, spelt as a field path spells it
|
|
168
|
+
(``area.v``).
|
|
169
|
+
"""
|
|
170
|
+
@v.setter
|
|
171
|
+
def v(self, value: Scalar | NotApplicable | Sequence[float] | Property) -> None: ...
|
|
172
|
+
uncertainty: float | None
|
|
173
|
+
"""The absolute standard uncertainty, in the value's unit, or ``None``."""
|
|
174
|
+
u: float | None
|
|
175
|
+
"""The uncertainty — the same as ``uncertainty``, spelt as a field path spells
|
|
176
|
+
it (``area.u``).
|
|
177
|
+
"""
|
|
178
|
+
readings: list[float] | None
|
|
179
|
+
"""The readings, or ``None`` for a value not measured several times.
|
|
180
|
+
|
|
181
|
+
The list is bound to the property: ``append``, ``remove`` or an item
|
|
182
|
+
assigned changes the property, as assigning a whole list does. Readings are
|
|
183
|
+
refused on a value a formula computes.
|
|
184
|
+
"""
|
|
185
|
+
@property
|
|
186
|
+
def stats(self) -> Summary | None:
|
|
187
|
+
"""The statistics of the readings, as a ``Summary``, or ``None`` without
|
|
188
|
+
readings.
|
|
189
|
+
"""
|
|
190
|
+
unit: str | None
|
|
191
|
+
"""The unit, as the file writes it — ``"g"``, ``"degC"`` — or ``None``."""
|
|
192
|
+
@property
|
|
193
|
+
def symbol(self) -> str | None:
|
|
194
|
+
"""The property's symbol — the one this sample's file writes, else the one
|
|
195
|
+
the project declares in ``.samplekitrc`` under ``[property.NAME]``, else
|
|
196
|
+
``None``. Read only: a symbol is the project's, declared there, never
|
|
197
|
+
set by a model or a script.
|
|
198
|
+
"""
|
|
199
|
+
@property
|
|
200
|
+
def is_computed(self) -> bool:
|
|
201
|
+
"""Whether a formula gives the value."""
|
|
202
|
+
@property
|
|
203
|
+
def is_outdated(self) -> bool:
|
|
204
|
+
"""Whether the value is outdated — an input changed since it was computed."""
|
|
205
|
+
@property
|
|
206
|
+
def failure(self) -> str | None:
|
|
207
|
+
"""What the formula raised when it last ran, or ``None`` if it did not fail."""
|
|
208
|
+
@property
|
|
209
|
+
def is_edited(self) -> bool:
|
|
210
|
+
"""Whether the value is edited — typed over its formula, or a computed table
|
|
211
|
+
cell whose record is missing.
|
|
212
|
+
"""
|
|
213
|
+
@property
|
|
214
|
+
def edited_upstream(self) -> list[str]:
|
|
215
|
+
"""The edited values this one was computed from, directly or further up."""
|
|
216
|
+
@property
|
|
217
|
+
def state(self) -> str:
|
|
218
|
+
"""Where the value stands, in one word.
|
|
219
|
+
|
|
220
|
+
One of ``"entered"`` (typed in, no formula), ``"absent"`` (no formula,
|
|
221
|
+
and nothing typed in), ``"current"``,
|
|
222
|
+
``"outdated"`` (an input changed since it was computed), ``"waiting"``
|
|
223
|
+
(an input it reads was emptied since: the number is the one computed
|
|
224
|
+
before), ``"never_computed"``, ``"edited"`` (a value typed over its
|
|
225
|
+
formula) or ``"failed"`` (its formula raised): the words a filter's
|
|
226
|
+
``state`` takes.
|
|
227
|
+
"""
|
|
228
|
+
def compute(self, rerun: bool = False, force: bool = False) -> None:
|
|
229
|
+
"""Compute the value, after the inputs it needs.
|
|
230
|
+
|
|
231
|
+
Only a value that is outdated or never computed runs, unless asked
|
|
232
|
+
otherwise. The file is not written: ``save()`` does that.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
rerun: Run it even when it is current.
|
|
236
|
+
force: Run it even when it is edited, giving it back to its formula.
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
ValueError: The value has no formula.
|
|
240
|
+
Exception: Whatever the formula raised, with a note naming the value.
|
|
241
|
+
"""
|
|
242
|
+
def format(self, unit: bool = True) -> str:
|
|
243
|
+
"""Format the property at its declared precision.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
unit: Include the unit.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
The property as text, such as ``"25.056 ± 0.053 mm²"``. For another
|
|
250
|
+
precision, use a format specifier: ``f"{steel.area:.2f}"``.
|
|
251
|
+
"""
|
|
252
|
+
def to_text(self, style: str | None = None) -> str:
|
|
253
|
+
r"""The property as a style writes it: its units, its symbols, its form
|
|
254
|
+
of a property — ``\SI{12.5 \pm 0.05}{\gram}`` in a style declaring
|
|
255
|
+
``property = '\SI{{value} \pm {uncertainty}}{{unit}}'``.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
style: A style's name: ``"plain"``, one SampleKit gives —
|
|
259
|
+
``"latex"``, ``"markdown"``, ``"html"`` — or one ``.samplekitrc``
|
|
260
|
+
declares. Without it, ``[render] style``'s, as ``str()`` gives.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
The text, ``n/a`` where the value does not apply, and the style's
|
|
264
|
+
``missing`` where there is none.
|
|
265
|
+
|
|
266
|
+
Raises:
|
|
267
|
+
ValueError: No style has that name.
|
|
268
|
+
|
|
269
|
+
Example:
|
|
270
|
+
>>> steel.area.to_text("latex")
|
|
271
|
+
'$25.056 \\pm 0.053$ mm²'
|
|
272
|
+
"""
|
|
273
|
+
def __str__(self) -> str: ...
|
|
274
|
+
def __format__(self, spec: str) -> str: ...
|
|
275
|
+
def __repr__(self) -> str: ...
|
|
276
|
+
def __float__(self) -> float: ...
|
|
277
|
+
def __int__(self) -> int: ...
|
|
278
|
+
def __bool__(self) -> bool: ...
|
|
279
|
+
def __add__(self, other: Any) -> Any: ...
|
|
280
|
+
def __radd__(self, other: Any) -> Any: ...
|
|
281
|
+
def __sub__(self, other: Any) -> Any: ...
|
|
282
|
+
def __rsub__(self, other: Any) -> Any: ...
|
|
283
|
+
def __mul__(self, other: Any) -> Any: ...
|
|
284
|
+
def __rmul__(self, other: Any) -> Any: ...
|
|
285
|
+
def __truediv__(self, other: Any) -> Any: ...
|
|
286
|
+
def __rtruediv__(self, other: Any) -> Any: ...
|
|
287
|
+
def __floordiv__(self, other: Any) -> Any: ...
|
|
288
|
+
def __rfloordiv__(self, other: Any) -> Any: ...
|
|
289
|
+
def __mod__(self, other: Any) -> Any: ...
|
|
290
|
+
def __rmod__(self, other: Any) -> Any: ...
|
|
291
|
+
def __pow__(self, other: Any, modulo: Any = None) -> Any: ...
|
|
292
|
+
def __rpow__(self, other: Any, modulo: Any = None) -> Any: ...
|
|
293
|
+
def __neg__(self) -> Any: ...
|
|
294
|
+
def __pos__(self) -> Any: ...
|
|
295
|
+
def __abs__(self) -> Any: ...
|
|
296
|
+
def __round__(self, ndigits: int | None = None) -> Any: ...
|
|
297
|
+
def __eq__(self, other: object) -> bool: ...
|
|
298
|
+
def __ne__(self, other: object) -> bool: ...
|
|
299
|
+
def __lt__(self, other: Any) -> bool: ...
|
|
300
|
+
def __le__(self, other: Any) -> bool: ...
|
|
301
|
+
def __gt__(self, other: Any) -> bool: ...
|
|
302
|
+
def __ge__(self, other: Any) -> bool: ...
|
|
303
|
+
__hash__: None # type: ignore[assignment]
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class RowView:
|
|
307
|
+
"""One row of a table, as a column's formula receives it.
|
|
308
|
+
|
|
309
|
+
A cell is read by its column's name, as an attribute or an item:
|
|
310
|
+
``row.force`` or ``row["force"]``, each a ``Property``.
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def position(self) -> int:
|
|
315
|
+
"""The row's position in the table, from 0."""
|
|
316
|
+
def __getitem__(self, column: str) -> Property: ...
|
|
317
|
+
def __getattr__(self, column: str) -> Property: ...
|
|
318
|
+
def __contains__(self, column: object) -> bool: ...
|
|
319
|
+
def __iter__(self) -> Iterator[str]: ...
|
|
320
|
+
def keys(self) -> KeysView[str]:
|
|
321
|
+
"""The names of the row's columns."""
|
|
322
|
+
def items(self) -> ItemsView[str, Property]:
|
|
323
|
+
"""Each column name with its cell."""
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class ColumnView:
|
|
327
|
+
"""One column of a table, as a ``compute_column`` formula receives it, and
|
|
328
|
+
as ``table.column_name`` reads it.
|
|
329
|
+
|
|
330
|
+
Iterating gives each cell as a ``Property``, in row order, and an item is
|
|
331
|
+
the cell of a row, by its index value: ``column[3]``.
|
|
332
|
+
"""
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def values(self) -> list[CellValue]:
|
|
336
|
+
"""The value of each cell, in row order; ``None`` where a cell has none."""
|
|
337
|
+
@property
|
|
338
|
+
def uncertainties(self) -> list[float | None]:
|
|
339
|
+
"""The uncertainty of each cell, in row order; ``None`` where a cell has none."""
|
|
340
|
+
def __getitem__(self, index: Index) -> Property: ...
|
|
341
|
+
def __iter__(self) -> Iterator[Property]: ...
|
|
342
|
+
def __len__(self) -> int: ...
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class ComputedColumns:
|
|
346
|
+
"""Several columns of a table filled by one formula: given each row, or the
|
|
347
|
+
columns it reads whole.
|
|
348
|
+
|
|
349
|
+
A column filled alone declares its formula on its ``Property``; this is for
|
|
350
|
+
one computation that gives several columns at once, a fit's two parameters.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
columns: The columns it fills: a name, or a list of names.
|
|
354
|
+
compute: A function given each row, a ``RowView``, and returning a dict:
|
|
355
|
+
one cell for each column it fills, a number or
|
|
356
|
+
``(value, uncertainty)``.
|
|
357
|
+
compute_column: A function given the columns it reads, by name, and
|
|
358
|
+
returning a dict: one list of cells for each column it fills, one
|
|
359
|
+
cell per row.
|
|
360
|
+
depends_on: What the formula reads: ``"row.force"`` for a cell of the
|
|
361
|
+
same row, ``"curve.force"`` for a whole column, ``"area"`` for a
|
|
362
|
+
property of the sample; ``[]`` for nothing.
|
|
363
|
+
|
|
364
|
+
Raises:
|
|
365
|
+
ValueError: Neither formula is given, or both, or ``depends_on`` is not.
|
|
366
|
+
TypeError: A formula is not callable.
|
|
367
|
+
|
|
368
|
+
Example:
|
|
369
|
+
>>> bounds = sk.ComputedColumns(
|
|
370
|
+
... ["low", "high"], compute=lambda row: {"low": 1.0, "high": 2.0},
|
|
371
|
+
... depends_on=[],
|
|
372
|
+
... )
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
def __init__(
|
|
376
|
+
self,
|
|
377
|
+
columns: str | Sequence[str],
|
|
378
|
+
*,
|
|
379
|
+
compute: RowCompute | None = None,
|
|
380
|
+
compute_column: ColumnCompute | None = None,
|
|
381
|
+
# What the formula reads: `"row.force"`, `"curve.force"`, `"area"`.
|
|
382
|
+
depends_on: Sequence[str] | None = None,
|
|
383
|
+
) -> None: ...
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class Table:
|
|
387
|
+
"""A table of a sample: named columns, and rows identified by their index.
|
|
388
|
+
|
|
389
|
+
The index is the column — or the columns — whose values identify a row: a
|
|
390
|
+
day, a temperature, a inspector. A cell is read by its index value, never by
|
|
391
|
+
its position: ``table.at(0.2, "force")``.
|
|
392
|
+
|
|
393
|
+
A column is declared with ``Property``: its unit, the statistics of its
|
|
394
|
+
cells' readings, or the formula that computes each cell, given the row
|
|
395
|
+
(``compute``, ``compute_value``, ``compute_uncertainty``), or the whole
|
|
396
|
+
column at once (``compute_column``). A formula filling several columns at
|
|
397
|
+
once is a ``ComputedColumns``, given in ``computed``.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
columns: A dict of ``Property`` by name, in the order they are shown.
|
|
401
|
+
index: The name of the index column, or a list of names; always named.
|
|
402
|
+
title: A title for the table.
|
|
403
|
+
rows: The first rows: each a dict of cells by column name.
|
|
404
|
+
computed: The ``ComputedColumns`` of the table, each filling several
|
|
405
|
+
columns.
|
|
406
|
+
|
|
407
|
+
Raises:
|
|
408
|
+
TypeError: A column is not a ``Property``, or is given a value; the
|
|
409
|
+
index is not named.
|
|
410
|
+
ValueError: The index names no column, a row does not fit the columns,
|
|
411
|
+
or a column is filled by two formulas.
|
|
412
|
+
|
|
413
|
+
Example:
|
|
414
|
+
>>> inspection = sk.Table(
|
|
415
|
+
... {"inspector": sk.Property(), "score": sk.Property()},
|
|
416
|
+
... index="inspector",
|
|
417
|
+
... rows=[{"inspector": "Ana", "score": 41}],
|
|
418
|
+
... )
|
|
419
|
+
>>> inspection.at("Ana", "score").value
|
|
420
|
+
41
|
|
421
|
+
"""
|
|
422
|
+
|
|
423
|
+
def __init__(
|
|
424
|
+
self,
|
|
425
|
+
columns: Mapping[str, Property],
|
|
426
|
+
*,
|
|
427
|
+
index: str | Sequence[str],
|
|
428
|
+
title: str | None = None,
|
|
429
|
+
rows: Iterable[Mapping[str, CellInput]] | None = None,
|
|
430
|
+
computed: Iterable[ComputedColumns] | None = None,
|
|
431
|
+
) -> None: ...
|
|
432
|
+
def add(self, **cells: CellInput) -> None:
|
|
433
|
+
"""Add a row at the end.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
**cells: The row's cells by column name: a value, a list of readings,
|
|
437
|
+
or a ``Property``. The index columns are required.
|
|
438
|
+
|
|
439
|
+
Raises:
|
|
440
|
+
KeyError: A column the table does not have.
|
|
441
|
+
ValueError: A row with that index exists already.
|
|
442
|
+
"""
|
|
443
|
+
def extend(self, rows: Iterable[Mapping[str, CellInput]]) -> None:
|
|
444
|
+
"""Add several rows at the end.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
rows: An iterable of dicts, each a row's cells by column name.
|
|
448
|
+
|
|
449
|
+
Raises:
|
|
450
|
+
KeyError: A column the table does not have.
|
|
451
|
+
ValueError: A row with that index exists already.
|
|
452
|
+
"""
|
|
453
|
+
def update(self, index: Index, **cells: CellInput) -> None:
|
|
454
|
+
"""Change cells of one row.
|
|
455
|
+
|
|
456
|
+
Values computed from those cells become outdated. A computed cell changed
|
|
457
|
+
here is edited: kept by ``compute()`` until ``compute(force=True)``.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
index: The row's index value; a tuple for an index of several columns.
|
|
461
|
+
**cells: The new cells, by column name.
|
|
462
|
+
|
|
463
|
+
Raises:
|
|
464
|
+
KeyError: No row has that index, or a column is unknown; the message
|
|
465
|
+
names the nearest.
|
|
466
|
+
"""
|
|
467
|
+
def at(self, index: Index, column: str) -> Property:
|
|
468
|
+
"""The cell of a row, by the row's index value.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
index: The row's index value; a tuple for an index of several columns.
|
|
472
|
+
column: The column's name.
|
|
473
|
+
|
|
474
|
+
Returns:
|
|
475
|
+
The cell, as a ``Property``.
|
|
476
|
+
|
|
477
|
+
Raises:
|
|
478
|
+
KeyError: No row has that index, or the column is unknown; the message
|
|
479
|
+
names the nearest.
|
|
480
|
+
|
|
481
|
+
Example:
|
|
482
|
+
>>> steel.curve.at(0.2, "force").value
|
|
483
|
+
9.516
|
|
484
|
+
"""
|
|
485
|
+
def values(self, column: str) -> list[CellValue]:
|
|
486
|
+
"""The values of a column, in row order.
|
|
487
|
+
|
|
488
|
+
Args:
|
|
489
|
+
column: The column's name.
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
A list of values, ``None`` where a cell has none.
|
|
493
|
+
|
|
494
|
+
Raises:
|
|
495
|
+
KeyError: The column is unknown; the message lists the columns.
|
|
496
|
+
|
|
497
|
+
Example:
|
|
498
|
+
>>> steel.curve.values("force")[:3]
|
|
499
|
+
[0.0, 1.281, 2.585]
|
|
500
|
+
"""
|
|
501
|
+
def uncertainties(self, column: str) -> list[float | None]:
|
|
502
|
+
"""The uncertainties of a column, in row order.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
column: The column's name.
|
|
506
|
+
|
|
507
|
+
Returns:
|
|
508
|
+
A list of uncertainties, ``None`` where a cell has none.
|
|
509
|
+
|
|
510
|
+
Raises:
|
|
511
|
+
KeyError: The column is unknown; the message lists the columns.
|
|
512
|
+
"""
|
|
513
|
+
@property
|
|
514
|
+
def column_names(self) -> list[str]:
|
|
515
|
+
"""The names of the columns, in the order they were declared."""
|
|
516
|
+
@property
|
|
517
|
+
def index_values(self) -> list[Index]:
|
|
518
|
+
"""The index value of each row, in row order; a tuple each for an index of
|
|
519
|
+
several columns.
|
|
520
|
+
"""
|
|
521
|
+
@property
|
|
522
|
+
def data_columns(self) -> list[str]:
|
|
523
|
+
"""The names of the columns that are not the index."""
|
|
524
|
+
@property
|
|
525
|
+
def index(self) -> str | list[str]:
|
|
526
|
+
"""The name of the index column, or a list of names for an index of several
|
|
527
|
+
columns.
|
|
528
|
+
"""
|
|
529
|
+
@property
|
|
530
|
+
def index_unit(self) -> str | list[str | None] | None:
|
|
531
|
+
"""The unit of the index column, or a list of units for an index of several
|
|
532
|
+
columns.
|
|
533
|
+
"""
|
|
534
|
+
@property
|
|
535
|
+
def title(self) -> str | None:
|
|
536
|
+
"""The table's title, or ``None``."""
|
|
537
|
+
def __getitem__(self, position: int) -> RowView: ...
|
|
538
|
+
def __getattr__(self, column: str) -> ColumnView: ...
|
|
539
|
+
def __contains__(self, index: object) -> bool: ...
|
|
540
|
+
def to_text(self, style: str | None = None) -> str:
|
|
541
|
+
"""The table as ``samplekit view`` unfolds it: its columns headed with
|
|
542
|
+
their units, its index first, each cell in a style's forms — those of
|
|
543
|
+
its sample's project, or of the project the current folder is in for a
|
|
544
|
+
table of no sample.
|
|
545
|
+
|
|
546
|
+
Args:
|
|
547
|
+
style: A style's name: ``"plain"``, one SampleKit gives, or one
|
|
548
|
+
``.samplekitrc`` declares. Without it, ``[render] style``'s.
|
|
549
|
+
|
|
550
|
+
Returns:
|
|
551
|
+
The text.
|
|
552
|
+
|
|
553
|
+
Raises:
|
|
554
|
+
ValueError: No style has that name.
|
|
555
|
+
|
|
556
|
+
Example:
|
|
557
|
+
>>> print(steel.curve.to_text())
|
|
558
|
+
"""
|
|
559
|
+
def to_csv(self) -> str:
|
|
560
|
+
"""The table as rows of CSV, as ``samplekit FILE view TABLE --csv``
|
|
561
|
+
prints it: its index first, each column a value and its uncertainty
|
|
562
|
+
headed with the unit, at the precision its columns declare.
|
|
563
|
+
|
|
564
|
+
Returns:
|
|
565
|
+
The text.
|
|
566
|
+
|
|
567
|
+
Raises:
|
|
568
|
+
ValueError: The table belongs to no sample yet.
|
|
569
|
+
|
|
570
|
+
Example:
|
|
571
|
+
>>> text = steel.curve.to_csv()
|
|
572
|
+
"""
|
|
573
|
+
def __iter__(self) -> Iterator[RowView]: ...
|
|
574
|
+
def __len__(self) -> int: ...
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
class Sample:
|
|
578
|
+
"""One sample, read from and saved to a Markdown file.
|
|
579
|
+
|
|
580
|
+
``Sample(path)`` reads the file with the model its project declares;
|
|
581
|
+
``Sample(name=...)`` makes a sample with no file, which ``save(path)``
|
|
582
|
+
gives one. A model is a subclass of ``Sample`` whose ``__init__``
|
|
583
|
+
declares its properties and tables, and passes ``path`` on to
|
|
584
|
+
``super().__init__``.
|
|
585
|
+
|
|
586
|
+
A property, a table or an attribute is read as an attribute or an item,
|
|
587
|
+
``steel.area`` or ``steel["area"]``, and any other field path as an item:
|
|
588
|
+
``steel["curve.force[0.2]"]`` is that cell's value. What is assigned
|
|
589
|
+
decides what a name becomes: a ``Property`` or a ``Table`` is one; a
|
|
590
|
+
``bool``, ``int``, ``float``, ``str``, ``date``, ``datetime``, or a list
|
|
591
|
+
of one of them, is an attribute; and a name with a leading ``_`` is
|
|
592
|
+
ordinary Python state, never saved. **Nothing assigned without a leading
|
|
593
|
+
``_`` is lost on save**: it is saved, or refused.
|
|
594
|
+
|
|
595
|
+
Args:
|
|
596
|
+
path: The sample's file.
|
|
597
|
+
name: The sample's name; a name the file holds wins over it.
|
|
598
|
+
model: The class to read the file with, or ``False`` to read the
|
|
599
|
+
data alone. By default, the class the project's ``[model]``
|
|
600
|
+
declares.
|
|
601
|
+
|
|
602
|
+
Raises:
|
|
603
|
+
FileNotFoundError: The file does not exist; the message names the
|
|
604
|
+
nearest. ``Sample.new`` makes a sample for a new file.
|
|
605
|
+
IsADirectoryError: The path is a folder: ``sk.load`` reads a folder.
|
|
606
|
+
ValueError: The file is not a sample.
|
|
607
|
+
AttributeError: An unknown field, read as an attribute; the message
|
|
608
|
+
names the nearest.
|
|
609
|
+
KeyError: An unknown field, read as an item.
|
|
610
|
+
TypeError: A value of a kind that cannot be saved; a leading ``_``
|
|
611
|
+
keeps it in Python only.
|
|
612
|
+
|
|
613
|
+
Example:
|
|
614
|
+
>>> steel = sk.Sample("samples/steel-01.md")
|
|
615
|
+
>>> steel.material
|
|
616
|
+
'steel'
|
|
617
|
+
>>> steel["curve.force[0.2]"]
|
|
618
|
+
9.516
|
|
619
|
+
"""
|
|
620
|
+
|
|
621
|
+
def __init__(
|
|
622
|
+
self,
|
|
623
|
+
path: Pathish | None = None,
|
|
624
|
+
name: str | None = None,
|
|
625
|
+
model: type[Sample] | Literal[False] | None = None,
|
|
626
|
+
) -> None: ...
|
|
627
|
+
@classmethod
|
|
628
|
+
def new(
|
|
629
|
+
cls,
|
|
630
|
+
path: Pathish,
|
|
631
|
+
name: str | None = None,
|
|
632
|
+
model: type[Sample] | Literal[False] | None = None,
|
|
633
|
+
) -> Self:
|
|
634
|
+
"""Make a new sample for a file that does not exist yet.
|
|
635
|
+
|
|
636
|
+
The sample is of the model the project of that folder declares — or of
|
|
637
|
+
this class, when called on a model — and is named by the file, which
|
|
638
|
+
writes no ``name:`` unless ``name`` is given.
|
|
639
|
+
``save()`` writes the file.
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
path: The file to create.
|
|
643
|
+
name: The sample's name, instead of the file's.
|
|
644
|
+
model: The class to make it with, or ``False`` for a plain
|
|
645
|
+
``Sample``.
|
|
646
|
+
|
|
647
|
+
Returns:
|
|
648
|
+
The new sample, not saved yet.
|
|
649
|
+
|
|
650
|
+
Raises:
|
|
651
|
+
FileExistsError: The file exists already: ``Sample(path)`` reads it.
|
|
652
|
+
FileNotFoundError: The folder does not exist.
|
|
653
|
+
|
|
654
|
+
Example:
|
|
655
|
+
>>> specimen = sk.Sample.new("samples/steel-02.md")
|
|
656
|
+
>>> specimen.material = "steel"
|
|
657
|
+
>>> specimen.save()
|
|
658
|
+
"""
|
|
659
|
+
@property
|
|
660
|
+
def path(self) -> Path | None:
|
|
661
|
+
"""The file the sample was read from or last saved to, or ``None`` for a
|
|
662
|
+
sample never saved. ``save(path)`` changes it.
|
|
663
|
+
"""
|
|
664
|
+
@property
|
|
665
|
+
def filename(self) -> str | None:
|
|
666
|
+
"""The name of the sample's file, without its folder, or ``None``."""
|
|
667
|
+
@property
|
|
668
|
+
def project(self) -> Project:
|
|
669
|
+
"""The project the sample's file belongs to — what its ``.samplekitrc``
|
|
670
|
+
declares.
|
|
671
|
+
"""
|
|
672
|
+
name: str | None
|
|
673
|
+
"""The sample's name, or ``None``."""
|
|
674
|
+
tags: BoundList[str]
|
|
675
|
+
"""The sample's tags, as a list whose changes are saved with the sample.
|
|
676
|
+
|
|
677
|
+
Each tag is an identifier, and none appears twice.
|
|
678
|
+
"""
|
|
679
|
+
note: str
|
|
680
|
+
"""The note — the Markdown text below the frontmatter, kept as written."""
|
|
681
|
+
def to_text(self, style: str | None = None) -> str:
|
|
682
|
+
"""The sample as ``samplekit view`` shows it — its values, then its
|
|
683
|
+
tables — each value in a style's forms.
|
|
684
|
+
|
|
685
|
+
Args:
|
|
686
|
+
style: A style's name: ``"plain"``, one SampleKit gives, or one
|
|
687
|
+
``.samplekitrc`` declares. Without it, ``[render] style``'s.
|
|
688
|
+
|
|
689
|
+
Returns:
|
|
690
|
+
The text.
|
|
691
|
+
|
|
692
|
+
Raises:
|
|
693
|
+
ValueError: No style has that name.
|
|
694
|
+
|
|
695
|
+
Example:
|
|
696
|
+
>>> print(steel.to_text())
|
|
697
|
+
"""
|
|
698
|
+
def save(self, path: Pathish | None = None, overwrite: bool = False) -> None:
|
|
699
|
+
"""Write the sample to its file.
|
|
700
|
+
|
|
701
|
+
Nothing is overwritten unless asked: a file changed on disk since it was
|
|
702
|
+
read, or an existing file at a new path, is refused. Saving runs no
|
|
703
|
+
formula: a value never computed is written without a value.
|
|
704
|
+
|
|
705
|
+
Args:
|
|
706
|
+
path: Where to write it instead. The sample then belongs to that file:
|
|
707
|
+
a later ``save()`` writes there.
|
|
708
|
+
overwrite: Write even over a file changed since it was read, or over an
|
|
709
|
+
existing file at ``path``.
|
|
710
|
+
|
|
711
|
+
Raises:
|
|
712
|
+
ValueError: The sample has no file yet and no ``path`` is given.
|
|
713
|
+
Also: another process is saving the same file at this moment.
|
|
714
|
+
FileExistsError: ``path`` exists already.
|
|
715
|
+
FileNotFoundError: The folder does not exist, or the sample's file was
|
|
716
|
+
removed or moved since it was read.
|
|
717
|
+
IsADirectoryError: ``path`` is a folder.
|
|
718
|
+
OSError: The file changed on disk since it was read.
|
|
719
|
+
PermissionError: The file is read-only.
|
|
720
|
+
|
|
721
|
+
Example:
|
|
722
|
+
>>> steel.grade = "S275"
|
|
723
|
+
>>> steel.save()
|
|
724
|
+
"""
|
|
725
|
+
def linked(self, field: str) -> Sample | Path | list[Sample | Path]:
|
|
726
|
+
"""The sample an attribute links to, read as ``sk.load`` reads it, or
|
|
727
|
+
the file it leads to.
|
|
728
|
+
|
|
729
|
+
A link is an attribute written ``"[[NAME]]"``, as Obsidian writes
|
|
730
|
+
one: the sample of the project whose file is NAME.md, else whose
|
|
731
|
+
``name`` is NAME; or a Markdown link to a file,
|
|
732
|
+
``"[the second](NAME.md)"``, its path read from this sample's folder.
|
|
733
|
+
A sample's file gives the sample; any other file its path. A list of
|
|
734
|
+
links gives a list, in order.
|
|
735
|
+
|
|
736
|
+
Args:
|
|
737
|
+
field: The attribute holding the link, or the links.
|
|
738
|
+
|
|
739
|
+
Returns:
|
|
740
|
+
The sample it names, or a ``pathlib.Path`` for a file that is no
|
|
741
|
+
sample; for a list, a list of them, in order.
|
|
742
|
+
|
|
743
|
+
Raises:
|
|
744
|
+
KeyError: The sample holds no such attribute.
|
|
745
|
+
ValueError: The attribute is no link: text, or a web address; or
|
|
746
|
+
several samples answer to it.
|
|
747
|
+
FileNotFoundError: No sample answers to it, or nothing is at its
|
|
748
|
+
path.
|
|
749
|
+
|
|
750
|
+
Example:
|
|
751
|
+
>>> steel.linked("reference").name
|
|
752
|
+
'aluminium-01'
|
|
753
|
+
"""
|
|
754
|
+
def rename(self, new: str) -> Path:
|
|
755
|
+
"""Rename the sample: its file, its own files, and every link of its
|
|
756
|
+
project leading to it.
|
|
757
|
+
|
|
758
|
+
The file becomes ``NEW.md`` beside the old one, its own files under
|
|
759
|
+
``[collection] files`` renamed with it, its ``name`` too where it was
|
|
760
|
+
the file's name; each link of the project that led to it, ``[[OLD]]``
|
|
761
|
+
or ``[text](OLD.md)``, is written to lead to it again. The history
|
|
762
|
+
keeps the samples; renamed back, the files and the links go back too.
|
|
763
|
+
|
|
764
|
+
Args:
|
|
765
|
+
new: The new name: the file's, without its folder or ``.md``.
|
|
766
|
+
|
|
767
|
+
Returns:
|
|
768
|
+
The sample's new path.
|
|
769
|
+
|
|
770
|
+
Raises:
|
|
771
|
+
ValueError: The sample has no file, the name is no file's name, or
|
|
772
|
+
the script runs under ``samplekit run``, whose preview cannot
|
|
773
|
+
hold a rename.
|
|
774
|
+
FileExistsError: Another sample, or one of its files, has the name.
|
|
775
|
+
OSError: The file changed on disk since it was read.
|
|
776
|
+
|
|
777
|
+
Example:
|
|
778
|
+
>>> sk.Sample("samples/steel-01.md").rename("steel-04")
|
|
779
|
+
PosixPath('samples/steel-04.md')
|
|
780
|
+
"""
|
|
781
|
+
...
|
|
782
|
+
|
|
783
|
+
def remove(self) -> None:
|
|
784
|
+
"""Delete the sample's file.
|
|
785
|
+
|
|
786
|
+
The project's history keeps it, and ``samplekit FILE restore --write``
|
|
787
|
+
gives it back; the sample's own files, under ``[collection] files``,
|
|
788
|
+
stay. The sample keeps its values: ``save()`` writes it again.
|
|
789
|
+
|
|
790
|
+
Raises:
|
|
791
|
+
ValueError: The sample has no file, or the script runs under
|
|
792
|
+
``samplekit run``, whose preview cannot hold a removal.
|
|
793
|
+
FileNotFoundError: Its file was removed or moved since it was read.
|
|
794
|
+
OSError: The file changed on disk since it was read.
|
|
795
|
+
|
|
796
|
+
Example:
|
|
797
|
+
>>> sk.Sample("samples/steel-01.md").remove()
|
|
798
|
+
"""
|
|
799
|
+
def files(self, pattern: str | None = None) -> list[Path]:
|
|
800
|
+
"""The sample's own files: images, reports, anything found by the sample's
|
|
801
|
+
name in the folders ``[collection] files`` declares.
|
|
802
|
+
|
|
803
|
+
Args:
|
|
804
|
+
pattern: A glob on the files' names, such as ``"*.png"``.
|
|
805
|
+
|
|
806
|
+
Returns:
|
|
807
|
+
The files' paths.
|
|
808
|
+
|
|
809
|
+
Raises:
|
|
810
|
+
ValueError: The sample has no file, or belongs to no project.
|
|
811
|
+
"""
|
|
812
|
+
def open(
|
|
813
|
+
self, pattern: str | None = None, navigate: bool = False, many: bool = False
|
|
814
|
+
) -> list[Path]:
|
|
815
|
+
"""Open the sample's own files with the system's application.
|
|
816
|
+
|
|
817
|
+
Args:
|
|
818
|
+
pattern: A glob on the files' names, such as ``"*.pdf"``.
|
|
819
|
+
navigate: Open the folders holding the files, in the file manager,
|
|
820
|
+
instead of the files.
|
|
821
|
+
many: Allow opening more than five.
|
|
822
|
+
|
|
823
|
+
Returns:
|
|
824
|
+
The paths opened.
|
|
825
|
+
|
|
826
|
+
Raises:
|
|
827
|
+
FileNotFoundError: No file of the sample matches.
|
|
828
|
+
ValueError: More than five would open and ``many`` is false.
|
|
829
|
+
RuntimeError: Over SSH without a display — ``DISPLAY`` and
|
|
830
|
+
``WAYLAND_DISPLAY`` unset — there is nothing to open them on.
|
|
831
|
+
Elsewhere the system's opener is started, and what it does
|
|
832
|
+
without a display is its own.
|
|
833
|
+
"""
|
|
834
|
+
def set_dependencies(self, output: str, *, depends_on: Declared) -> None:
|
|
835
|
+
"""Declare what a formula reads.
|
|
836
|
+
|
|
837
|
+
A change to one of the inputs makes the value outdated, and a computation
|
|
838
|
+
runs the inputs first. A later declaration replaces an earlier one.
|
|
839
|
+
|
|
840
|
+
Args:
|
|
841
|
+
output: The value the formula gives: a property's name, or
|
|
842
|
+
``table.column``.
|
|
843
|
+
depends_on: What it reads: a list of names, or ``{"v": [...],
|
|
844
|
+
"u": [...]}`` where the value and the uncertainty have a formula
|
|
845
|
+
each. ``[]`` says it reads nothing of the sample.
|
|
846
|
+
|
|
847
|
+
Raises:
|
|
848
|
+
KeyError: An unknown name, once the model is built.
|
|
849
|
+
ValueError: The declaration makes a cycle, or names a channel with no
|
|
850
|
+
formula.
|
|
851
|
+
|
|
852
|
+
Example:
|
|
853
|
+
>>> class Specimen(sk.Sample):
|
|
854
|
+
... def __init__(self, path=None, name=None):
|
|
855
|
+
... super().__init__(path, name=name)
|
|
856
|
+
... self.area = sk.Property(unit="mm²", compute_value=self._area)
|
|
857
|
+
... self.set_dependencies("area", depends_on=["width", "thickness"])
|
|
858
|
+
...
|
|
859
|
+
... def _area(self):
|
|
860
|
+
... return self.width.value * self.thickness.value
|
|
861
|
+
"""
|
|
862
|
+
def dependencies(self, output: str) -> list[str]:
|
|
863
|
+
"""What a value's formula is declared to read.
|
|
864
|
+
|
|
865
|
+
Args:
|
|
866
|
+
output: A property's name, or ``table.column``.
|
|
867
|
+
|
|
868
|
+
Returns:
|
|
869
|
+
The input names, sorted.
|
|
870
|
+
|
|
871
|
+
Raises:
|
|
872
|
+
KeyError: An unknown name; the message names the nearest.
|
|
873
|
+
|
|
874
|
+
Example:
|
|
875
|
+
>>> steel.dependencies("area")
|
|
876
|
+
['thickness', 'width']
|
|
877
|
+
"""
|
|
878
|
+
def dependents(self, input: str) -> list[str]:
|
|
879
|
+
"""The values whose formula reads this one directly.
|
|
880
|
+
|
|
881
|
+
Args:
|
|
882
|
+
input: A property's name, or ``table.column``.
|
|
883
|
+
|
|
884
|
+
Returns:
|
|
885
|
+
Their names, sorted.
|
|
886
|
+
|
|
887
|
+
Raises:
|
|
888
|
+
KeyError: An unknown name; the message names the nearest.
|
|
889
|
+
"""
|
|
890
|
+
def affected_by_change(self, input: str) -> list[str]:
|
|
891
|
+
"""Every value a change to this one would make outdated, directly or through
|
|
892
|
+
other values.
|
|
893
|
+
|
|
894
|
+
Args:
|
|
895
|
+
input: A property's name, or ``table.column``.
|
|
896
|
+
|
|
897
|
+
Returns:
|
|
898
|
+
Their names, sorted.
|
|
899
|
+
|
|
900
|
+
Raises:
|
|
901
|
+
KeyError: An unknown name; the message names the nearest.
|
|
902
|
+
|
|
903
|
+
Example:
|
|
904
|
+
>>> steel.affected_by_change("thickness")
|
|
905
|
+
['area', 'curve.stress', 'tensile_strength', 'yield_strength', 'young_modulus']
|
|
906
|
+
"""
|
|
907
|
+
def compute(self, *names: str, rerun: bool = False, force: bool = False) -> None:
|
|
908
|
+
"""Compute the sample's values that are not current, in dependency order.
|
|
909
|
+
|
|
910
|
+
Without names, every value a formula gives is considered; only those
|
|
911
|
+
outdated or never computed run. A value whose inputs nobody entered waits,
|
|
912
|
+
with a warning saying for what, and a formula that raises is recorded as
|
|
913
|
+
failed while the others run. The file is not written: ``save()`` does
|
|
914
|
+
that.
|
|
915
|
+
|
|
916
|
+
Args:
|
|
917
|
+
*names: The values to compute: properties, ``table.column``, or a
|
|
918
|
+
table's name for all its computed columns. Their inputs run first
|
|
919
|
+
when needed.
|
|
920
|
+
rerun: Run them even when they are current.
|
|
921
|
+
force: Run them even when they are edited, giving them back to their
|
|
922
|
+
formula.
|
|
923
|
+
|
|
924
|
+
Raises:
|
|
925
|
+
KeyError: An unknown name; the message names the nearest.
|
|
926
|
+
ValueError: A named value has no formula.
|
|
927
|
+
Exception: The first formula that raised, with a note naming the value
|
|
928
|
+
and the others that failed.
|
|
929
|
+
|
|
930
|
+
Example:
|
|
931
|
+
>>> steel.compute()
|
|
932
|
+
>>> steel.save()
|
|
933
|
+
"""
|
|
934
|
+
def outdated(self) -> list[str]:
|
|
935
|
+
"""The outdated values — those an input changed under since they were
|
|
936
|
+
computed.
|
|
937
|
+
|
|
938
|
+
Returns:
|
|
939
|
+
The names of the outdated properties, and ``table.column`` for a
|
|
940
|
+
column with an outdated cell.
|
|
941
|
+
"""
|
|
942
|
+
def edited(self) -> list[str]:
|
|
943
|
+
"""The edited values — those typed over their formula.
|
|
944
|
+
|
|
945
|
+
Returns:
|
|
946
|
+
The names of the edited properties, and ``table.column`` for a column
|
|
947
|
+
with an edited cell.
|
|
948
|
+
"""
|
|
949
|
+
def not_current(self) -> dict[str, str]:
|
|
950
|
+
"""Every value that is not current, and why: what ``samplekit status``
|
|
951
|
+
reports.
|
|
952
|
+
|
|
953
|
+
``outdated()`` and ``edited()`` answer one question each; this one also
|
|
954
|
+
sees values that failed, were never computed, or wait for an input.
|
|
955
|
+
|
|
956
|
+
Returns:
|
|
957
|
+
A dict from each name — a property, or ``table.column`` — to a reason:
|
|
958
|
+
``"outdated"``, ``"formula changed"``, ``"edited"``, ``"failed"``,
|
|
959
|
+
``"never computed"`` or ``"waits for notch"``.
|
|
960
|
+
|
|
961
|
+
Example:
|
|
962
|
+
>>> steel.not_current()
|
|
963
|
+
{}
|
|
964
|
+
"""
|
|
965
|
+
def keys(self) -> KeysView[str]:
|
|
966
|
+
"""The names of the sample's attributes, properties and tables, in the order
|
|
967
|
+
they were declared.
|
|
968
|
+
"""
|
|
969
|
+
def items(self) -> ItemsView[str, Property | Table | Attribute]:
|
|
970
|
+
"""Each name with its attribute, property or table, in the order they were
|
|
971
|
+
declared.
|
|
972
|
+
"""
|
|
973
|
+
def __getitem__(self, name: str) -> Property | Table | Attribute | list[Scalar]: ...
|
|
974
|
+
# A field's type is the data's, which no checker can know from the class.
|
|
975
|
+
def __getattr__(self, name: str) -> Any: ...
|
|
976
|
+
def __setattr__(self, name: str, value: Any) -> None: ...
|
|
977
|
+
def __delattr__(self, name: str) -> None: ...
|
|
978
|
+
def __setitem__(self, name: str, value: Property | Table | Attribute) -> None: ...
|
|
979
|
+
def __delitem__(self, name: str) -> None: ...
|
|
980
|
+
def __contains__(self, name: object) -> bool: ...
|
|
981
|
+
def __iter__(self) -> Iterator[str]: ...
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
class SampleList(Sequence[Sample]):
|
|
985
|
+
"""An ordered list of samples, read like a Python list.
|
|
986
|
+
|
|
987
|
+
A list is read from a folder, or built from samples already read. An item
|
|
988
|
+
is a sample, by position or by name — ``specimens["steel-01"]`` — and a slice
|
|
989
|
+
is a list. Lists join with ``+``. ``filter``, ``query``, ``sorted`` and
|
|
990
|
+
``group_by`` make new lists; the samples in them are the same objects.
|
|
991
|
+
|
|
992
|
+
Args:
|
|
993
|
+
source: A folder, whose samples are read as the project declares, or
|
|
994
|
+
an iterable of samples.
|
|
995
|
+
pattern: A glob choosing the folder's files, instead of what
|
|
996
|
+
``[collection]`` declares.
|
|
997
|
+
model: The model class to read the samples with, or ``False`` to read
|
|
998
|
+
the data alone. By default, the project's model.
|
|
999
|
+
|
|
1000
|
+
Raises:
|
|
1001
|
+
TypeError: The iterable holds something other than samples, such as
|
|
1002
|
+
paths: ``sk.load`` reads paths.
|
|
1003
|
+
ValueError: A sample appears twice, or ``pattern`` or ``model`` is
|
|
1004
|
+
given without a folder.
|
|
1005
|
+
FileNotFoundError: The folder does not exist.
|
|
1006
|
+
|
|
1007
|
+
Example:
|
|
1008
|
+
>>> specimens = sk.SampleList("samples")
|
|
1009
|
+
>>> len(specimens)
|
|
1010
|
+
3
|
|
1011
|
+
>>> specimens["steel-01"].material
|
|
1012
|
+
'steel'
|
|
1013
|
+
"""
|
|
1014
|
+
|
|
1015
|
+
def __init__(
|
|
1016
|
+
self,
|
|
1017
|
+
source: Pathish | Iterable[Sample] | None = None,
|
|
1018
|
+
pattern: str | None = None,
|
|
1019
|
+
model: type[Sample] | Literal[False] | None = None,
|
|
1020
|
+
) -> None: ...
|
|
1021
|
+
def __len__(self) -> int: ...
|
|
1022
|
+
@overload
|
|
1023
|
+
def __getitem__(self, key: int) -> Sample: ...
|
|
1024
|
+
@overload
|
|
1025
|
+
def __getitem__(self, key: slice) -> SampleList: ...
|
|
1026
|
+
@overload
|
|
1027
|
+
def __getitem__(self, key: str) -> Sample: ...
|
|
1028
|
+
def __contains__(self, item: object) -> bool: ...
|
|
1029
|
+
def __iter__(self) -> Iterator[Sample]: ...
|
|
1030
|
+
def __reversed__(self) -> Iterator[Sample]: ...
|
|
1031
|
+
def __add__(self, other: SampleList) -> SampleList: ...
|
|
1032
|
+
def index(self, value: object, start: int = 0, stop: int = ...) -> int:
|
|
1033
|
+
"""The position of a sample in the list.
|
|
1034
|
+
|
|
1035
|
+
Args:
|
|
1036
|
+
value: A sample, or a sample's name.
|
|
1037
|
+
start: Where to start looking.
|
|
1038
|
+
stop: Where to stop looking.
|
|
1039
|
+
|
|
1040
|
+
Raises:
|
|
1041
|
+
ValueError: It is not in the list.
|
|
1042
|
+
"""
|
|
1043
|
+
def count(self, value: object) -> int:
|
|
1044
|
+
"""How many times a sample is in the list: 0 or 1 for a sample, and, for a
|
|
1045
|
+
name, the number of samples of that name.
|
|
1046
|
+
"""
|
|
1047
|
+
def filter(self, predicate: str | Callable[[Sample], bool]) -> SampleList:
|
|
1048
|
+
"""The samples an expression selects, as ``-f`` selects them.
|
|
1049
|
+
|
|
1050
|
+
Args:
|
|
1051
|
+
predicate: A filter expression — ``"elongation > 20 && material == steel"`` — or a
|
|
1052
|
+
function taking a sample and returning whether to keep it.
|
|
1053
|
+
|
|
1054
|
+
Returns:
|
|
1055
|
+
A new list, in this list's order.
|
|
1056
|
+
|
|
1057
|
+
Raises:
|
|
1058
|
+
KeyError: The expression names an unknown field; the message names
|
|
1059
|
+
the nearest.
|
|
1060
|
+
ValueError: The expression does not parse, or names a tag no sample
|
|
1061
|
+
carries.
|
|
1062
|
+
TypeError: The expression compares a field in a way its values do not
|
|
1063
|
+
allow.
|
|
1064
|
+
|
|
1065
|
+
Example:
|
|
1066
|
+
>>> [specimen.name for specimen in specimens.filter("tensile_strength > 300")]
|
|
1067
|
+
['aluminium-01', 'steel-01']
|
|
1068
|
+
"""
|
|
1069
|
+
def sort(self, key: FieldKey | Sequence[FieldKey] | Callable[[Sample], Any]) -> None:
|
|
1070
|
+
"""Sort the list in place, and return ``None``.
|
|
1071
|
+
|
|
1072
|
+
Args:
|
|
1073
|
+
key: A field — ``"area"``, or ``"-area"`` for descending — several in a
|
|
1074
|
+
list or comma-separated, or a function of a sample as ``sorted``
|
|
1075
|
+
takes one.
|
|
1076
|
+
|
|
1077
|
+
Raises:
|
|
1078
|
+
KeyError: An unknown field; the message names the nearest.
|
|
1079
|
+
TypeError: A sort by ``state``, which a sample holds several of.
|
|
1080
|
+
"""
|
|
1081
|
+
def sorted(self, key: FieldKey | Sequence[FieldKey] | Callable[[Sample], Any]) -> SampleList:
|
|
1082
|
+
"""A new list in sorted order; this one is unchanged.
|
|
1083
|
+
|
|
1084
|
+
Args:
|
|
1085
|
+
key: A field — ``"area"``, or ``"-area"`` for descending — several in a
|
|
1086
|
+
list or comma-separated, or a function of a sample as ``sorted``
|
|
1087
|
+
takes one.
|
|
1088
|
+
|
|
1089
|
+
Returns:
|
|
1090
|
+
A new list.
|
|
1091
|
+
|
|
1092
|
+
Raises:
|
|
1093
|
+
KeyError: An unknown field; the message names the nearest.
|
|
1094
|
+
TypeError: A sort by ``state``, which a sample holds several of.
|
|
1095
|
+
|
|
1096
|
+
Example:
|
|
1097
|
+
>>> specimens.sorted("-tensile_strength")[0].name
|
|
1098
|
+
'steel-01'
|
|
1099
|
+
"""
|
|
1100
|
+
def query(self, name: str | Query) -> SampleList:
|
|
1101
|
+
"""The samples a saved query selects, as ``-Q`` selects them.
|
|
1102
|
+
|
|
1103
|
+
Args:
|
|
1104
|
+
name: The query's name in ``.samplekitrc``, or a ``Query``.
|
|
1105
|
+
|
|
1106
|
+
Returns:
|
|
1107
|
+
A new list, in this list's order.
|
|
1108
|
+
|
|
1109
|
+
Raises:
|
|
1110
|
+
KeyError: No query has that name; the message names the nearest.
|
|
1111
|
+
ValueError: The list spans several projects that declare it
|
|
1112
|
+
differently.
|
|
1113
|
+
|
|
1114
|
+
Example:
|
|
1115
|
+
>>> len(specimens.query("ductile"))
|
|
1116
|
+
2
|
|
1117
|
+
"""
|
|
1118
|
+
def stats(self, field: FieldKey) -> Summary:
|
|
1119
|
+
"""The statistics of one field over the list, as ``--summary`` gives them.
|
|
1120
|
+
|
|
1121
|
+
Args:
|
|
1122
|
+
field: A field: ``"area"``, ``"area.u"``, ``"curve.force[0.2]"``.
|
|
1123
|
+
|
|
1124
|
+
Returns:
|
|
1125
|
+
A ``Summary``. Its mean is weighted by 1/u² where every value has an
|
|
1126
|
+
uncertainty above zero.
|
|
1127
|
+
|
|
1128
|
+
Raises:
|
|
1129
|
+
KeyError: An unknown field; the message names the nearest.
|
|
1130
|
+
ValueError: No sample of the list holds a number there.
|
|
1131
|
+
|
|
1132
|
+
Example:
|
|
1133
|
+
>>> round(specimens.stats("tensile_strength").mean, 1)
|
|
1134
|
+
352.3
|
|
1135
|
+
"""
|
|
1136
|
+
def group_by(self, fields: FieldKey | Sequence[FieldKey]) -> dict[Any, SampleList]:
|
|
1137
|
+
"""Split the list into groups by the values of fields, as ``--group`` does.
|
|
1138
|
+
|
|
1139
|
+
Args:
|
|
1140
|
+
fields: A field, or several — a list, or comma-separated as
|
|
1141
|
+
``--group`` writes them.
|
|
1142
|
+
|
|
1143
|
+
Returns:
|
|
1144
|
+
A dict from each value — a tuple of values for several fields — to
|
|
1145
|
+
the list of samples holding it. The groups come in the order their
|
|
1146
|
+
values sort; for a sorted list, in the order of their first samples.
|
|
1147
|
+
|
|
1148
|
+
Raises:
|
|
1149
|
+
KeyError: An unknown field; the message names the nearest.
|
|
1150
|
+
ValueError: The field is a whole table.
|
|
1151
|
+
RuntimeError: The field is ``state``, which a sample holds several of.
|
|
1152
|
+
|
|
1153
|
+
Example:
|
|
1154
|
+
>>> list(specimens.group_by("material"))
|
|
1155
|
+
['aluminium', 'copper', 'steel']
|
|
1156
|
+
"""
|
|
1157
|
+
def save_all(self, directory: Pathish | None = None, overwrite: bool = False) -> list[Path]:
|
|
1158
|
+
"""Write every sample of the list: back where each was read, or into a folder.
|
|
1159
|
+
|
|
1160
|
+
Without a folder, each sample is written to its own file, as ``save()``
|
|
1161
|
+
writes it: a file changed since it was read — by another object, or an
|
|
1162
|
+
editor — is refused rather than written over. Into a folder, each is
|
|
1163
|
+
written under its file's name, or its name, and nothing is written if
|
|
1164
|
+
any file is refused.
|
|
1165
|
+
|
|
1166
|
+
Args:
|
|
1167
|
+
directory: The folder, which must exist; each sample's own file when
|
|
1168
|
+
left out.
|
|
1169
|
+
overwrite: Replace existing files in the folder.
|
|
1170
|
+
|
|
1171
|
+
Returns:
|
|
1172
|
+
The paths written.
|
|
1173
|
+
|
|
1174
|
+
Raises:
|
|
1175
|
+
FileNotFoundError: The folder does not exist.
|
|
1176
|
+
NotADirectoryError: It is a file.
|
|
1177
|
+
FileExistsError: A file exists and ``overwrite`` is false.
|
|
1178
|
+
ValueError: Two samples would be written to the same file, or,
|
|
1179
|
+
without a folder, a sample has no file of its own.
|
|
1180
|
+
OSError: Without a folder, a file changed on disk since it was read.
|
|
1181
|
+
"""
|
|
1182
|
+
def compute(self, *names: str, rerun: bool = False, force: bool = False) -> None:
|
|
1183
|
+
"""Compute every sample of the list, as ``Sample.compute`` does.
|
|
1184
|
+
|
|
1185
|
+
Every sample is computed even when one fails; the failures are raised at
|
|
1186
|
+
the end. A ``KeyboardInterrupt`` stops at once.
|
|
1187
|
+
|
|
1188
|
+
Args:
|
|
1189
|
+
*names: The values to compute; every value that is not current by
|
|
1190
|
+
default.
|
|
1191
|
+
rerun: Run them even when they are current.
|
|
1192
|
+
force: Run them even when they are edited.
|
|
1193
|
+
|
|
1194
|
+
Raises:
|
|
1195
|
+
Exception: The one failure, with a note naming the sample and the
|
|
1196
|
+
value.
|
|
1197
|
+
ExceptionGroup: Several failures, in the order they happened.
|
|
1198
|
+
"""
|
|
1199
|
+
@property
|
|
1200
|
+
def fields(self) -> Names[Field]:
|
|
1201
|
+
"""Every field the samples of the list can be addressed by, as
|
|
1202
|
+
``samplekit list fields`` lists them.
|
|
1203
|
+
"""
|
|
1204
|
+
@property
|
|
1205
|
+
def project(self) -> Project:
|
|
1206
|
+
"""The project of the list's samples — what their ``.samplekitrc`` declares.
|
|
1207
|
+
|
|
1208
|
+
Raises:
|
|
1209
|
+
ValueError: The samples belong to several projects.
|
|
1210
|
+
"""
|
|
1211
|
+
def to_dict(
|
|
1212
|
+
self,
|
|
1213
|
+
columns: str | Sequence[FieldKey] | None = None,
|
|
1214
|
+
profile: str | Profile | None = None,
|
|
1215
|
+
) -> dict[str, list[Scalar | list[AttributeScalar]]]:
|
|
1216
|
+
"""The list as a dict of columns, one list of values per column heading.
|
|
1217
|
+
|
|
1218
|
+
Values are the stored numbers, unrounded.
|
|
1219
|
+
|
|
1220
|
+
Args:
|
|
1221
|
+
columns: The fields to include: a list, or comma-separated as ``-c``
|
|
1222
|
+
writes them, each ``FIELD[:PRECISION][=LABEL]``.
|
|
1223
|
+
profile: A profile's name in ``.samplekitrc``, or a ``Profile``,
|
|
1224
|
+
instead of ``columns``.
|
|
1225
|
+
|
|
1226
|
+
Returns:
|
|
1227
|
+
A dict from each heading to its values, one per sample.
|
|
1228
|
+
|
|
1229
|
+
Raises:
|
|
1230
|
+
ValueError: Neither ``columns`` nor ``profile`` is given, or both.
|
|
1231
|
+
KeyError: An unknown field or profile; the message names the nearest.
|
|
1232
|
+
|
|
1233
|
+
Example:
|
|
1234
|
+
>>> columns = specimens.to_dict(columns="name,area")
|
|
1235
|
+
>>> list(columns)
|
|
1236
|
+
['name', 'area_value', 'area_uncertainty']
|
|
1237
|
+
"""
|
|
1238
|
+
# Without a path the text is returned; with one it is written, and
|
|
1239
|
+
# nothing is returned.
|
|
1240
|
+
@overload
|
|
1241
|
+
def to_csv(
|
|
1242
|
+
self,
|
|
1243
|
+
path: None = None,
|
|
1244
|
+
columns: str | Sequence[FieldKey] | None = None,
|
|
1245
|
+
profile: str | Profile | None = None,
|
|
1246
|
+
overwrite: bool = False,
|
|
1247
|
+
) -> str:
|
|
1248
|
+
"""The list as CSV, as the command line exports it.
|
|
1249
|
+
|
|
1250
|
+
Args:
|
|
1251
|
+
path: The file to write. Without it, the text is returned.
|
|
1252
|
+
columns: The fields to include: a list, or comma-separated as ``-c``
|
|
1253
|
+
writes them, each ``FIELD[:PRECISION][=LABEL]``.
|
|
1254
|
+
profile: A profile's name in ``.samplekitrc``, or a ``Profile``,
|
|
1255
|
+
instead of ``columns``.
|
|
1256
|
+
overwrite: Replace an existing file.
|
|
1257
|
+
|
|
1258
|
+
Returns:
|
|
1259
|
+
The text, when no ``path`` is given; otherwise ``None``.
|
|
1260
|
+
|
|
1261
|
+
Raises:
|
|
1262
|
+
ValueError: Neither ``columns`` nor ``profile`` is given, or both.
|
|
1263
|
+
KeyError: An unknown field or profile; the message names the nearest.
|
|
1264
|
+
FileExistsError: ``path`` exists and ``overwrite`` is false.
|
|
1265
|
+
|
|
1266
|
+
Example:
|
|
1267
|
+
>>> text = specimens.to_csv(columns="name,area")
|
|
1268
|
+
"""
|
|
1269
|
+
@overload
|
|
1270
|
+
def to_csv(
|
|
1271
|
+
self,
|
|
1272
|
+
path: Pathish,
|
|
1273
|
+
columns: str | Sequence[FieldKey] | None = None,
|
|
1274
|
+
profile: str | Profile | None = None,
|
|
1275
|
+
overwrite: bool = False,
|
|
1276
|
+
) -> None:
|
|
1277
|
+
"""The list as CSV, as the command line exports it.
|
|
1278
|
+
|
|
1279
|
+
Args:
|
|
1280
|
+
path: The file to write. Without it, the text is returned.
|
|
1281
|
+
columns: The fields to include: a list, or comma-separated as ``-c``
|
|
1282
|
+
writes them, each ``FIELD[:PRECISION][=LABEL]``.
|
|
1283
|
+
profile: A profile's name in ``.samplekitrc``, or a ``Profile``,
|
|
1284
|
+
instead of ``columns``.
|
|
1285
|
+
overwrite: Replace an existing file.
|
|
1286
|
+
|
|
1287
|
+
Returns:
|
|
1288
|
+
The text, when no ``path`` is given; otherwise ``None``.
|
|
1289
|
+
|
|
1290
|
+
Raises:
|
|
1291
|
+
ValueError: Neither ``columns`` nor ``profile`` is given, or both.
|
|
1292
|
+
KeyError: An unknown field or profile; the message names the nearest.
|
|
1293
|
+
FileExistsError: ``path`` exists and ``overwrite`` is false.
|
|
1294
|
+
|
|
1295
|
+
Example:
|
|
1296
|
+
>>> text = specimens.to_csv(columns="name,area")
|
|
1297
|
+
"""
|
|
1298
|
+
@overload
|
|
1299
|
+
def to_tsv(
|
|
1300
|
+
self,
|
|
1301
|
+
path: None = None,
|
|
1302
|
+
columns: str | Sequence[FieldKey] | None = None,
|
|
1303
|
+
profile: str | Profile | None = None,
|
|
1304
|
+
overwrite: bool = False,
|
|
1305
|
+
) -> str:
|
|
1306
|
+
"""The list as TSV, as the command line exports it.
|
|
1307
|
+
|
|
1308
|
+
Args:
|
|
1309
|
+
path: The file to write. Without it, the text is returned.
|
|
1310
|
+
columns: The fields to include: a list, or comma-separated as ``-c``
|
|
1311
|
+
writes them, each ``FIELD[:PRECISION][=LABEL]``.
|
|
1312
|
+
profile: A profile's name in ``.samplekitrc``, or a ``Profile``,
|
|
1313
|
+
instead of ``columns``.
|
|
1314
|
+
overwrite: Replace an existing file.
|
|
1315
|
+
|
|
1316
|
+
Returns:
|
|
1317
|
+
The text, when no ``path`` is given; otherwise ``None``.
|
|
1318
|
+
|
|
1319
|
+
Raises:
|
|
1320
|
+
ValueError: Neither ``columns`` nor ``profile`` is given, or both.
|
|
1321
|
+
KeyError: An unknown field or profile; the message names the nearest.
|
|
1322
|
+
FileExistsError: ``path`` exists and ``overwrite`` is false.
|
|
1323
|
+
|
|
1324
|
+
Example:
|
|
1325
|
+
>>> text = specimens.to_tsv(columns="name,area")
|
|
1326
|
+
"""
|
|
1327
|
+
@overload
|
|
1328
|
+
def to_tsv(
|
|
1329
|
+
self,
|
|
1330
|
+
path: Pathish,
|
|
1331
|
+
columns: str | Sequence[FieldKey] | None = None,
|
|
1332
|
+
profile: str | Profile | None = None,
|
|
1333
|
+
overwrite: bool = False,
|
|
1334
|
+
) -> None:
|
|
1335
|
+
"""The list as TSV, as the command line exports it.
|
|
1336
|
+
|
|
1337
|
+
Args:
|
|
1338
|
+
path: The file to write. Without it, the text is returned.
|
|
1339
|
+
columns: The fields to include: a list, or comma-separated as ``-c``
|
|
1340
|
+
writes them, each ``FIELD[:PRECISION][=LABEL]``.
|
|
1341
|
+
profile: A profile's name in ``.samplekitrc``, or a ``Profile``,
|
|
1342
|
+
instead of ``columns``.
|
|
1343
|
+
overwrite: Replace an existing file.
|
|
1344
|
+
|
|
1345
|
+
Returns:
|
|
1346
|
+
The text, when no ``path`` is given; otherwise ``None``.
|
|
1347
|
+
|
|
1348
|
+
Raises:
|
|
1349
|
+
ValueError: Neither ``columns`` nor ``profile`` is given, or both.
|
|
1350
|
+
KeyError: An unknown field or profile; the message names the nearest.
|
|
1351
|
+
FileExistsError: ``path`` exists and ``overwrite`` is false.
|
|
1352
|
+
|
|
1353
|
+
Example:
|
|
1354
|
+
>>> text = specimens.to_tsv(columns="name,area")
|
|
1355
|
+
"""
|
|
1356
|
+
@overload
|
|
1357
|
+
def to_text(
|
|
1358
|
+
self,
|
|
1359
|
+
style: str,
|
|
1360
|
+
path: None = None,
|
|
1361
|
+
columns: str | Sequence[FieldKey] | None = None,
|
|
1362
|
+
profile: str | Profile | None = None,
|
|
1363
|
+
overwrite: bool = False,
|
|
1364
|
+
) -> str:
|
|
1365
|
+
"""The list as a document in a style: ``"latex"``, ``"markdown"``,
|
|
1366
|
+
``"html"``, or one ``[style.NAME]`` declares, as ``--style NAME -o -``
|
|
1367
|
+
writes it.
|
|
1368
|
+
|
|
1369
|
+
Args:
|
|
1370
|
+
style: The style's name.
|
|
1371
|
+
path: The file to write. Without it, the text is returned.
|
|
1372
|
+
columns: The fields to include: a list, or comma-separated as ``-c``
|
|
1373
|
+
writes them, each ``FIELD[:PRECISION][=LABEL]``.
|
|
1374
|
+
profile: A profile's name in ``.samplekitrc``, or a ``Profile``,
|
|
1375
|
+
instead of ``columns``.
|
|
1376
|
+
overwrite: Replace an existing file.
|
|
1377
|
+
|
|
1378
|
+
Returns:
|
|
1379
|
+
The text, when no ``path`` is given; otherwise ``None``.
|
|
1380
|
+
|
|
1381
|
+
Raises:
|
|
1382
|
+
ValueError: No style writing documents has that name, or neither
|
|
1383
|
+
``columns`` nor ``profile`` is given, or both.
|
|
1384
|
+
KeyError: An unknown field or profile; the message names the nearest.
|
|
1385
|
+
FileExistsError: ``path`` exists and ``overwrite`` is false.
|
|
1386
|
+
|
|
1387
|
+
Example:
|
|
1388
|
+
>>> table = specimens.to_text("latex", columns="name,area")
|
|
1389
|
+
"""
|
|
1390
|
+
@overload
|
|
1391
|
+
def to_text(
|
|
1392
|
+
self,
|
|
1393
|
+
style: str,
|
|
1394
|
+
path: Pathish,
|
|
1395
|
+
columns: str | Sequence[FieldKey] | None = None,
|
|
1396
|
+
profile: str | Profile | None = None,
|
|
1397
|
+
overwrite: bool = False,
|
|
1398
|
+
) -> None:
|
|
1399
|
+
"""The list as a document in a style: ``"latex"``, ``"markdown"``,
|
|
1400
|
+
``"html"``, or one ``[style.NAME]`` declares, as ``--style NAME -o -``
|
|
1401
|
+
writes it.
|
|
1402
|
+
|
|
1403
|
+
Args:
|
|
1404
|
+
style: The style's name.
|
|
1405
|
+
path: The file to write. Without it, the text is returned.
|
|
1406
|
+
columns: The fields to include: a list, or comma-separated as ``-c``
|
|
1407
|
+
writes them, each ``FIELD[:PRECISION][=LABEL]``.
|
|
1408
|
+
profile: A profile's name in ``.samplekitrc``, or a ``Profile``,
|
|
1409
|
+
instead of ``columns``.
|
|
1410
|
+
overwrite: Replace an existing file.
|
|
1411
|
+
|
|
1412
|
+
Returns:
|
|
1413
|
+
The text, when no ``path`` is given; otherwise ``None``.
|
|
1414
|
+
|
|
1415
|
+
Raises:
|
|
1416
|
+
ValueError: No style writing documents has that name, or neither
|
|
1417
|
+
``columns`` nor ``profile`` is given, or both.
|
|
1418
|
+
KeyError: An unknown field or profile; the message names the nearest.
|
|
1419
|
+
FileExistsError: ``path`` exists and ``overwrite`` is false.
|
|
1420
|
+
|
|
1421
|
+
Example:
|
|
1422
|
+
>>> table = specimens.to_text("latex", columns="name,area")
|
|
1423
|
+
"""
|
|
1424
|
+
@overload
|
|
1425
|
+
def to_json(
|
|
1426
|
+
self,
|
|
1427
|
+
path: None = None,
|
|
1428
|
+
columns: str | Sequence[FieldKey] | None = None,
|
|
1429
|
+
profile: str | Profile | None = None,
|
|
1430
|
+
overwrite: bool = False,
|
|
1431
|
+
) -> str:
|
|
1432
|
+
"""The list as JSON, as the command line exports it.
|
|
1433
|
+
|
|
1434
|
+
Args:
|
|
1435
|
+
path: The file to write. Without it, the text is returned.
|
|
1436
|
+
columns: The fields to include: a list, or comma-separated as ``-c``
|
|
1437
|
+
writes them, each ``FIELD[:PRECISION][=LABEL]``.
|
|
1438
|
+
profile: A profile's name in ``.samplekitrc``, or a ``Profile``,
|
|
1439
|
+
instead of ``columns``.
|
|
1440
|
+
overwrite: Replace an existing file.
|
|
1441
|
+
|
|
1442
|
+
Returns:
|
|
1443
|
+
The text, when no ``path`` is given; otherwise ``None``.
|
|
1444
|
+
|
|
1445
|
+
Raises:
|
|
1446
|
+
ValueError: Neither ``columns`` nor ``profile`` is given, or both.
|
|
1447
|
+
KeyError: An unknown field or profile; the message names the nearest.
|
|
1448
|
+
FileExistsError: ``path`` exists and ``overwrite`` is false.
|
|
1449
|
+
|
|
1450
|
+
Example:
|
|
1451
|
+
>>> text = specimens.to_json(columns="name,area")
|
|
1452
|
+
"""
|
|
1453
|
+
@overload
|
|
1454
|
+
def to_json(
|
|
1455
|
+
self,
|
|
1456
|
+
path: Pathish,
|
|
1457
|
+
columns: str | Sequence[FieldKey] | None = None,
|
|
1458
|
+
profile: str | Profile | None = None,
|
|
1459
|
+
overwrite: bool = False,
|
|
1460
|
+
) -> None:
|
|
1461
|
+
"""The list as JSON, as the command line exports it.
|
|
1462
|
+
|
|
1463
|
+
Args:
|
|
1464
|
+
path: The file to write. Without it, the text is returned.
|
|
1465
|
+
columns: The fields to include: a list, or comma-separated as ``-c``
|
|
1466
|
+
writes them, each ``FIELD[:PRECISION][=LABEL]``.
|
|
1467
|
+
profile: A profile's name in ``.samplekitrc``, or a ``Profile``,
|
|
1468
|
+
instead of ``columns``.
|
|
1469
|
+
overwrite: Replace an existing file.
|
|
1470
|
+
|
|
1471
|
+
Returns:
|
|
1472
|
+
The text, when no ``path`` is given; otherwise ``None``.
|
|
1473
|
+
|
|
1474
|
+
Raises:
|
|
1475
|
+
ValueError: Neither ``columns`` nor ``profile`` is given, or both.
|
|
1476
|
+
KeyError: An unknown field or profile; the message names the nearest.
|
|
1477
|
+
FileExistsError: ``path`` exists and ``overwrite`` is false.
|
|
1478
|
+
|
|
1479
|
+
Example:
|
|
1480
|
+
>>> text = specimens.to_json(columns="name,area")
|
|
1481
|
+
"""
|
|
1482
|
+
def export(
|
|
1483
|
+
self, export: str | Export, output: Pathish | None = None, overwrite: bool = False
|
|
1484
|
+
) -> Path:
|
|
1485
|
+
"""Write an export declared in ``.samplekitrc``, as ``samplekit export
|
|
1486
|
+
--write`` does.
|
|
1487
|
+
|
|
1488
|
+
The export's own query, if it declares one, narrows the list first. An
|
|
1489
|
+
export of a figure draws it as ``sk.plot`` does, to its file.
|
|
1490
|
+
|
|
1491
|
+
Args:
|
|
1492
|
+
export: The export's name, or an ``Export``.
|
|
1493
|
+
output: Write here instead of the declared file.
|
|
1494
|
+
overwrite: Replace an existing file.
|
|
1495
|
+
|
|
1496
|
+
Returns:
|
|
1497
|
+
The path written, absolute.
|
|
1498
|
+
|
|
1499
|
+
Raises:
|
|
1500
|
+
KeyError: No export has that name, or its profile or figure is not
|
|
1501
|
+
declared.
|
|
1502
|
+
FileExistsError: The file exists and ``overwrite`` is false.
|
|
1503
|
+
ValueError: ``output``'s extension does not match the export's format.
|
|
1504
|
+
|
|
1505
|
+
Example:
|
|
1506
|
+
>>> specimens.export("paper", output="paper/results.tex").name
|
|
1507
|
+
'results.tex'
|
|
1508
|
+
"""
|
|
1509
|
+
|
|
1510
|
+
|
|
1511
|
+
class Summary:
|
|
1512
|
+
"""The statistics of a set of values: readings, or a field over a list.
|
|
1513
|
+
|
|
1514
|
+
Given by ``Property.stats`` and ``SampleList.stats``.
|
|
1515
|
+
"""
|
|
1516
|
+
|
|
1517
|
+
def to_text(self, style: str | None = None) -> str:
|
|
1518
|
+
"""The summary as ``--summary`` writes a row of it: the count, the
|
|
1519
|
+
mean, the deviation ``s``, its standard error ``sem``, the median and
|
|
1520
|
+
the extremes.
|
|
1521
|
+
|
|
1522
|
+
Args:
|
|
1523
|
+
style: Accepted for every ``to_text``; a summary's numbers are
|
|
1524
|
+
written alike in every style.
|
|
1525
|
+
|
|
1526
|
+
Returns:
|
|
1527
|
+
The text.
|
|
1528
|
+
"""
|
|
1529
|
+
@property
|
|
1530
|
+
def count(self) -> int:
|
|
1531
|
+
"""How many values were summarised."""
|
|
1532
|
+
@property
|
|
1533
|
+
def considered(self) -> int | None:
|
|
1534
|
+
"""How many samples the value applies to, those holding no number
|
|
1535
|
+
included and those saying *not applicable* left out: the denominator of
|
|
1536
|
+
``count``, as ``--summary`` writes ``3/4``. ``None`` for readings.
|
|
1537
|
+
"""
|
|
1538
|
+
@property
|
|
1539
|
+
def not_applicable(self) -> int:
|
|
1540
|
+
"""How many samples say the value is not applicable, counted apart —
|
|
1541
|
+
neither summarised nor missing.
|
|
1542
|
+
"""
|
|
1543
|
+
@property
|
|
1544
|
+
def minimum(self) -> float:
|
|
1545
|
+
"""The smallest value."""
|
|
1546
|
+
@property
|
|
1547
|
+
def maximum(self) -> float:
|
|
1548
|
+
"""The largest value."""
|
|
1549
|
+
@property
|
|
1550
|
+
def mean(self) -> float:
|
|
1551
|
+
"""The mean — weighted by 1/u² where every value has an uncertainty above zero,
|
|
1552
|
+
the arithmetic mean otherwise. ``weighted`` says which.
|
|
1553
|
+
"""
|
|
1554
|
+
@property
|
|
1555
|
+
def weighted(self) -> bool:
|
|
1556
|
+
"""Whether ``mean`` is weighted by 1/u², as the command line's summary heads
|
|
1557
|
+
it ``mean (1/u²)``.
|
|
1558
|
+
"""
|
|
1559
|
+
@property
|
|
1560
|
+
def median(self) -> float:
|
|
1561
|
+
"""The median."""
|
|
1562
|
+
@property
|
|
1563
|
+
def first_quartile(self) -> float:
|
|
1564
|
+
"""The first quartile."""
|
|
1565
|
+
@property
|
|
1566
|
+
def third_quartile(self) -> float:
|
|
1567
|
+
"""The third quartile."""
|
|
1568
|
+
@property
|
|
1569
|
+
def sample_stdev(self) -> float | None:
|
|
1570
|
+
"""The sample standard deviation, dividing by n − 1; ``None`` for one value."""
|
|
1571
|
+
@property
|
|
1572
|
+
def population_stdev(self) -> float:
|
|
1573
|
+
"""The population standard deviation, dividing by n."""
|
|
1574
|
+
@property
|
|
1575
|
+
def standard_error(self) -> float | None:
|
|
1576
|
+
"""The standard error of the mean — 1/√Σw beside a weighted mean, the
|
|
1577
|
+
sample standard deviation over √n beside an arithmetic one; ``None``
|
|
1578
|
+
for one value unweighted.
|
|
1579
|
+
"""
|
|
1580
|
+
|
|
1581
|
+
|
|
1582
|
+
class Field:
|
|
1583
|
+
"""A field of a list, as ``SampleList.fields`` gives it, and as ``-c`` and
|
|
1584
|
+
``-s`` take it.
|
|
1585
|
+
|
|
1586
|
+
``str(field)`` is its path, and a field is accepted wherever a field's name
|
|
1587
|
+
is: ``specimens.sorted(specimens.fields.area)``.
|
|
1588
|
+
"""
|
|
1589
|
+
|
|
1590
|
+
@property
|
|
1591
|
+
def path(self) -> str:
|
|
1592
|
+
"""The field's path — ``"area"``, ``"curve.force[0.2]"``."""
|
|
1593
|
+
def __str__(self) -> str: ...
|
|
1594
|
+
|
|
1595
|
+
|
|
1596
|
+
# ------------------------------------------------------------ project
|
|
1597
|
+
|
|
1598
|
+
|
|
1599
|
+
class Names(Generic[_T]):
|
|
1600
|
+
"""The entries a project declares, read-only, by name.
|
|
1601
|
+
|
|
1602
|
+
An entry is read as an attribute or an item — ``project.queries.ductile`` or
|
|
1603
|
+
``project.queries["ductile"]`` — and iterating gives the names. An unknown
|
|
1604
|
+
name raises ``AttributeError`` or ``KeyError`` naming the nearest.
|
|
1605
|
+
"""
|
|
1606
|
+
|
|
1607
|
+
def __getattr__(self, name: str) -> _T: ...
|
|
1608
|
+
def __getitem__(self, name: str) -> _T: ...
|
|
1609
|
+
def __iter__(self) -> Iterator[str]: ...
|
|
1610
|
+
def __len__(self) -> int: ...
|
|
1611
|
+
def __contains__(self, name: object) -> bool: ...
|
|
1612
|
+
|
|
1613
|
+
|
|
1614
|
+
class Project:
|
|
1615
|
+
"""A project: what its ``.samplekitrc`` declares, read-only.
|
|
1616
|
+
|
|
1617
|
+
Read from a sample or a list — ``steel.project``, ``specimens.project``. Outside
|
|
1618
|
+
any project every collection of entries is empty.
|
|
1619
|
+
|
|
1620
|
+
Example:
|
|
1621
|
+
>>> list(specimens.project.queries)
|
|
1622
|
+
['ductile']
|
|
1623
|
+
"""
|
|
1624
|
+
|
|
1625
|
+
@property
|
|
1626
|
+
def root(self) -> Path | None:
|
|
1627
|
+
"""The folder holding ``.samplekitrc``, or ``None`` outside a project."""
|
|
1628
|
+
@property
|
|
1629
|
+
def queries(self) -> Names[Query]:
|
|
1630
|
+
"""The saved queries, ``[query.*]``, by name, each a ``Query``."""
|
|
1631
|
+
@property
|
|
1632
|
+
def profiles(self) -> Names[Profile]:
|
|
1633
|
+
"""The profiles, ``[profile.*]``, by name, each a ``Profile``."""
|
|
1634
|
+
@property
|
|
1635
|
+
def exports(self) -> Names[Export]:
|
|
1636
|
+
"""The exports, ``[export.*]``, by name, each an ``Export``."""
|
|
1637
|
+
@property
|
|
1638
|
+
def figures(self) -> Names[FigureDeclaration]:
|
|
1639
|
+
"""The figures, ``[figure.*]``, by name, each a ``FigureDeclaration``."""
|
|
1640
|
+
@property
|
|
1641
|
+
def matplotlib(self) -> dict[str, object]:
|
|
1642
|
+
"""The matplotlib settings every figure is drawn with, ``[matplotlib]`` —
|
|
1643
|
+
a dict by matplotlib's own dotted names.
|
|
1644
|
+
"""
|
|
1645
|
+
@property
|
|
1646
|
+
def properties(self) -> Names[PropertyDeclaration]:
|
|
1647
|
+
"""The declared properties, ``[property.*]``, by name, each a
|
|
1648
|
+
``PropertyDeclaration``.
|
|
1649
|
+
"""
|
|
1650
|
+
@property
|
|
1651
|
+
def units(self) -> Names[Unit]:
|
|
1652
|
+
"""The declared units, ``[unit.*]``, by spelling, each a ``Unit``."""
|
|
1653
|
+
@property
|
|
1654
|
+
def render(self) -> Render:
|
|
1655
|
+
"""How tables and figures are rendered, ``[render]``, as a ``Render``."""
|
|
1656
|
+
@property
|
|
1657
|
+
def model(self) -> Model | None:
|
|
1658
|
+
"""The model, ``[model]``, or ``None`` when the project declares none."""
|
|
1659
|
+
@property
|
|
1660
|
+
def collection(self) -> Collection:
|
|
1661
|
+
"""Which files are samples, ``[collection]``, as a ``Collection``."""
|
|
1662
|
+
|
|
1663
|
+
|
|
1664
|
+
class Query:
|
|
1665
|
+
"""A saved query, ``[query.*]``: a filter under a name."""
|
|
1666
|
+
|
|
1667
|
+
@property
|
|
1668
|
+
def name(self) -> str:
|
|
1669
|
+
"""The query's name."""
|
|
1670
|
+
@property
|
|
1671
|
+
def filter(self) -> str:
|
|
1672
|
+
"""The filter expression, as ``-f`` takes it."""
|
|
1673
|
+
@property
|
|
1674
|
+
def directory(self) -> Path | None:
|
|
1675
|
+
"""The folder the query searches by default, or ``None`` for the project's."""
|
|
1676
|
+
|
|
1677
|
+
|
|
1678
|
+
class ColumnSpec:
|
|
1679
|
+
"""One column of a profile: its field, its headings and its precision."""
|
|
1680
|
+
|
|
1681
|
+
@property
|
|
1682
|
+
def field(self) -> str:
|
|
1683
|
+
"""The field the column shows."""
|
|
1684
|
+
@property
|
|
1685
|
+
def label(self) -> str | None:
|
|
1686
|
+
"""The heading of the column in a terminal table, or ``None`` for the field."""
|
|
1687
|
+
@property
|
|
1688
|
+
def header(self) -> str | None:
|
|
1689
|
+
"""The heading of the column in an exported file, or ``None``."""
|
|
1690
|
+
@property
|
|
1691
|
+
def precision(self) -> Precision | None:
|
|
1692
|
+
"""The column's precision — ``".3f"``, or a pair for the value and the
|
|
1693
|
+
uncertainty — or ``None``.
|
|
1694
|
+
"""
|
|
1695
|
+
@property
|
|
1696
|
+
def template(self) -> str | None:
|
|
1697
|
+
"""The text each cell is written as, such as ``"{value:.3f}"``, or ``None``."""
|
|
1698
|
+
|
|
1699
|
+
|
|
1700
|
+
class Profile:
|
|
1701
|
+
"""A profile, ``[profile.*]``: the columns of a table, and the order of its
|
|
1702
|
+
rows.
|
|
1703
|
+
"""
|
|
1704
|
+
|
|
1705
|
+
@property
|
|
1706
|
+
def name(self) -> str:
|
|
1707
|
+
"""The profile's name."""
|
|
1708
|
+
@property
|
|
1709
|
+
def columns(self) -> tuple[ColumnSpec, ...]:
|
|
1710
|
+
"""The columns, each a ``ColumnSpec``, in order."""
|
|
1711
|
+
@property
|
|
1712
|
+
def sort(self) -> tuple[str, ...]:
|
|
1713
|
+
"""The sort keys, in order of precedence; a leading ``-`` sorts descending."""
|
|
1714
|
+
|
|
1715
|
+
|
|
1716
|
+
class FigureDeclaration:
|
|
1717
|
+
"""A figure declared in ``.samplekitrc``, ``[figure.*]``: what it draws, and
|
|
1718
|
+
how its axes are set.
|
|
1719
|
+
"""
|
|
1720
|
+
|
|
1721
|
+
@property
|
|
1722
|
+
def name(self) -> str:
|
|
1723
|
+
"""The figure's name."""
|
|
1724
|
+
@property
|
|
1725
|
+
def kind(self) -> Literal["scatter", "line", "step", "bar", "box"]:
|
|
1726
|
+
"""``"scatter"``, ``"line"``, ``"step"``, ``"bar"`` or ``"box"``."""
|
|
1727
|
+
@property
|
|
1728
|
+
def x(self) -> str:
|
|
1729
|
+
"""The field on the x axis."""
|
|
1730
|
+
@property
|
|
1731
|
+
def y(self) -> str:
|
|
1732
|
+
"""The field on the y axis."""
|
|
1733
|
+
@property
|
|
1734
|
+
def group(self) -> str | None:
|
|
1735
|
+
"""The field, or comma-separated fields, whose values split the samples into
|
|
1736
|
+
series; ``None`` for one series.
|
|
1737
|
+
"""
|
|
1738
|
+
@property
|
|
1739
|
+
def query(self) -> str | None:
|
|
1740
|
+
"""The saved query that selects its samples, or ``None``."""
|
|
1741
|
+
@property
|
|
1742
|
+
def title(self) -> str | None:
|
|
1743
|
+
"""The title, or ``None``."""
|
|
1744
|
+
@property
|
|
1745
|
+
def x_label(self) -> str | None:
|
|
1746
|
+
"""The x axis label, or ``None`` for the field's symbol and unit."""
|
|
1747
|
+
@property
|
|
1748
|
+
def y_label(self) -> str | None:
|
|
1749
|
+
"""The y axis label, or ``None`` for the field's symbol and unit."""
|
|
1750
|
+
@property
|
|
1751
|
+
def style(self) -> str | None:
|
|
1752
|
+
"""The style whose symbols and units label the axes, or ``None``."""
|
|
1753
|
+
@property
|
|
1754
|
+
def x_limits(self) -> tuple[float | None, float | None] | None:
|
|
1755
|
+
"""The x axis bounds, ``None`` for a free one; ``None`` when not declared."""
|
|
1756
|
+
@property
|
|
1757
|
+
def y_limits(self) -> tuple[float | None, float | None] | None:
|
|
1758
|
+
"""The y axis bounds, ``None`` for a free one; ``None`` when not declared."""
|
|
1759
|
+
@property
|
|
1760
|
+
def x_scale(self) -> str | None:
|
|
1761
|
+
"""``"linear"``, ``"log"`` or ``"symlog"``, or ``None``."""
|
|
1762
|
+
@property
|
|
1763
|
+
def y_scale(self) -> str | None:
|
|
1764
|
+
"""``"linear"``, ``"log"`` or ``"symlog"``, or ``None``."""
|
|
1765
|
+
@property
|
|
1766
|
+
def aspect(self) -> str | None:
|
|
1767
|
+
"""``"equal"`` or ``"auto"``, or ``None``."""
|
|
1768
|
+
@property
|
|
1769
|
+
def legend(self) -> str | None:
|
|
1770
|
+
"""Where the legend goes — ``"best"``, ``"outside"``, ``"none"`` or one of
|
|
1771
|
+
matplotlib's places — or ``None``.
|
|
1772
|
+
"""
|
|
1773
|
+
@property
|
|
1774
|
+
def figsize(self) -> tuple[float, float] | None:
|
|
1775
|
+
"""The figure's width and height in centimetres, or ``None``."""
|
|
1776
|
+
@property
|
|
1777
|
+
def matplotlib(self) -> dict[str, Any]:
|
|
1778
|
+
"""Its own matplotlib settings, over the project's ``[matplotlib]``, by
|
|
1779
|
+
matplotlib's dotted names.
|
|
1780
|
+
"""
|
|
1781
|
+
|
|
1782
|
+
|
|
1783
|
+
class Export:
|
|
1784
|
+
"""An export, ``[export.*]``: a profile written to a file in a format, or
|
|
1785
|
+
a figure drawn to a file.
|
|
1786
|
+
"""
|
|
1787
|
+
|
|
1788
|
+
@property
|
|
1789
|
+
def name(self) -> str:
|
|
1790
|
+
"""The export's name."""
|
|
1791
|
+
@property
|
|
1792
|
+
def profile(self) -> str | None:
|
|
1793
|
+
"""The name of the profile it writes, or ``None`` for a figure's export."""
|
|
1794
|
+
@property
|
|
1795
|
+
def figure(self) -> str | None:
|
|
1796
|
+
"""The name of the figure it draws, or ``None`` for a table's export."""
|
|
1797
|
+
@property
|
|
1798
|
+
def format(self) -> Literal["csv", "tsv", "json"] | None:
|
|
1799
|
+
"""The data format — ``"csv"``, ``"tsv"``, ``"json"`` — or ``None`` for
|
|
1800
|
+
a document its style writes, or a figure.
|
|
1801
|
+
"""
|
|
1802
|
+
@property
|
|
1803
|
+
def output(self) -> Path:
|
|
1804
|
+
"""The file it writes."""
|
|
1805
|
+
@property
|
|
1806
|
+
def filename(self) -> bool:
|
|
1807
|
+
"""Whether the export adds a column with each sample's file name."""
|
|
1808
|
+
@property
|
|
1809
|
+
def path(self) -> bool:
|
|
1810
|
+
"""Whether the export adds a column with each sample's path."""
|
|
1811
|
+
@property
|
|
1812
|
+
def query(self) -> str | None:
|
|
1813
|
+
"""The saved query that selects its samples, or ``None``."""
|
|
1814
|
+
|
|
1815
|
+
|
|
1816
|
+
class Statistic:
|
|
1817
|
+
"""A statistic of a property's readings, which a model declares to give the
|
|
1818
|
+
value or the uncertainty.
|
|
1819
|
+
|
|
1820
|
+
The statistics are in ``samplekit.stats``.
|
|
1821
|
+
|
|
1822
|
+
Example:
|
|
1823
|
+
>>> sk.Property(value=sk.stats.median, uncertainty=sk.stats.standard_error)
|
|
1824
|
+
Property(value=None)
|
|
1825
|
+
"""
|
|
1826
|
+
|
|
1827
|
+
@property
|
|
1828
|
+
def name(self) -> str:
|
|
1829
|
+
"""Its name, as ``Summary`` spells it — ``"median"``."""
|
|
1830
|
+
|
|
1831
|
+
|
|
1832
|
+
class stats:
|
|
1833
|
+
"""The statistics a model can declare for readings.
|
|
1834
|
+
|
|
1835
|
+
Six give a value — ``mean``, ``median``, ``minimum``, ``maximum``,
|
|
1836
|
+
``first_quartile``, ``third_quartile`` — and three give an uncertainty —
|
|
1837
|
+
``standard_error``, ``sample_stdev``, ``population_stdev``.
|
|
1838
|
+
|
|
1839
|
+
Example:
|
|
1840
|
+
>>> width = sk.Property(unit="mm", value=sk.stats.mean,
|
|
1841
|
+
... uncertainty=sk.stats.standard_error)
|
|
1842
|
+
"""
|
|
1843
|
+
|
|
1844
|
+
mean: Statistic
|
|
1845
|
+
median: Statistic
|
|
1846
|
+
minimum: Statistic
|
|
1847
|
+
maximum: Statistic
|
|
1848
|
+
first_quartile: Statistic
|
|
1849
|
+
third_quartile: Statistic
|
|
1850
|
+
standard_error: Statistic
|
|
1851
|
+
sample_stdev: Statistic
|
|
1852
|
+
population_stdev: Statistic
|
|
1853
|
+
|
|
1854
|
+
|
|
1855
|
+
class PropertyDeclaration:
|
|
1856
|
+
"""A property declared in ``.samplekitrc``, ``[property.*]``: its unit, symbol
|
|
1857
|
+
and precision, for every sample of the project.
|
|
1858
|
+
"""
|
|
1859
|
+
|
|
1860
|
+
@property
|
|
1861
|
+
def unit(self) -> str | None:
|
|
1862
|
+
"""The unit, or ``None``."""
|
|
1863
|
+
@property
|
|
1864
|
+
def symbol(self) -> str | None:
|
|
1865
|
+
"""The symbol, or ``None``."""
|
|
1866
|
+
@property
|
|
1867
|
+
def symbol_variants(self) -> Mapping[str, str]:
|
|
1868
|
+
r"""The symbol in each style, by style name:
|
|
1869
|
+
``{"figure": "$\\sigma$"}``.
|
|
1870
|
+
"""
|
|
1871
|
+
@property
|
|
1872
|
+
def precision(self) -> Precision | None:
|
|
1873
|
+
"""The precision — ``".3f"``, or a pair for the value and the uncertainty —
|
|
1874
|
+
or ``None``.
|
|
1875
|
+
"""
|
|
1876
|
+
|
|
1877
|
+
|
|
1878
|
+
class Unit:
|
|
1879
|
+
"""A unit declared in ``.samplekitrc``, ``[unit.*]``: how it is written in
|
|
1880
|
+
each style.
|
|
1881
|
+
"""
|
|
1882
|
+
|
|
1883
|
+
@property
|
|
1884
|
+
def spelling(self) -> str:
|
|
1885
|
+
"""The unit as the files write it — ``"degC"``."""
|
|
1886
|
+
@property
|
|
1887
|
+
def variants(self) -> Mapping[str, str]:
|
|
1888
|
+
"""The unit in each style, by style name — ``{"plain": "°C"}``."""
|
|
1889
|
+
|
|
1890
|
+
|
|
1891
|
+
class Render:
|
|
1892
|
+
"""How the project renders tables and figures, ``[render]``."""
|
|
1893
|
+
|
|
1894
|
+
@property
|
|
1895
|
+
def style(self) -> str | None:
|
|
1896
|
+
"""The style tables are rendered in, or ``None``."""
|
|
1897
|
+
@property
|
|
1898
|
+
def precision(self) -> Precision | None:
|
|
1899
|
+
"""The precision of every number without its own, or ``None``."""
|
|
1900
|
+
@property
|
|
1901
|
+
def table(self) -> str | None:
|
|
1902
|
+
"""How a table is drawn — ``"plain"`` or ``"boxed"`` — or ``None``."""
|
|
1903
|
+
@property
|
|
1904
|
+
def figure_style(self) -> str | None:
|
|
1905
|
+
"""The style figures label their axes in when they name none, or ``None``."""
|
|
1906
|
+
|
|
1907
|
+
|
|
1908
|
+
class Model:
|
|
1909
|
+
"""The project's model, ``[model]``: the Python file and class its samples are
|
|
1910
|
+
read with.
|
|
1911
|
+
"""
|
|
1912
|
+
|
|
1913
|
+
@property
|
|
1914
|
+
def path(self) -> Path:
|
|
1915
|
+
"""The model's Python file."""
|
|
1916
|
+
@property
|
|
1917
|
+
def class_name(self) -> str | None:
|
|
1918
|
+
"""The class in that file, or ``None`` when the file declares only one."""
|
|
1919
|
+
|
|
1920
|
+
|
|
1921
|
+
class Collection:
|
|
1922
|
+
"""Which files of the project are samples, ``[collection]``."""
|
|
1923
|
+
|
|
1924
|
+
@property
|
|
1925
|
+
def recursive(self) -> bool:
|
|
1926
|
+
"""Whether subfolders are searched."""
|
|
1927
|
+
@property
|
|
1928
|
+
def include(self) -> tuple[str, ...]:
|
|
1929
|
+
"""The globs a sample's file must match."""
|
|
1930
|
+
@property
|
|
1931
|
+
def exclude(self) -> tuple[str, ...]:
|
|
1932
|
+
"""The globs of files that are not samples."""
|
|
1933
|
+
@property
|
|
1934
|
+
def files(self) -> tuple[str, ...]:
|
|
1935
|
+
"""Where a sample's own files are looked for, ``[collection] files`` —
|
|
1936
|
+
the folders searched by the sample's name, and the patterns naming it,
|
|
1937
|
+
as written.
|
|
1938
|
+
"""
|
|
1939
|
+
|
|
1940
|
+
|
|
1941
|
+
class NotApplicable:
|
|
1942
|
+
"""A value that does not apply to a sample — ``yield_strength: n/a`` in its file.
|
|
1943
|
+
|
|
1944
|
+
There is one, ``samplekit.NA``. It is false in a test and prints as
|
|
1945
|
+
``n/a``; a property set to it is saved as ``n/a``, and a formula reading
|
|
1946
|
+
it gives ``NA`` too, without running.
|
|
1947
|
+
|
|
1948
|
+
Example:
|
|
1949
|
+
>>> steel.yield_strength.value = sk.NA
|
|
1950
|
+
"""
|
|
1951
|
+
|
|
1952
|
+
def __bool__(self) -> bool: ...
|
|
1953
|
+
|
|
1954
|
+
NA: NotApplicable
|
|
1955
|
+
|
|
1956
|
+
# One path is a sample for a file and a list for a folder, which a type
|
|
1957
|
+
# cannot tell apart: the script knows which it named, and a union would
|
|
1958
|
+
# refuse `for specimen in sk.load("samples")` until narrowed. Several paths are
|
|
1959
|
+
# always one list.
|
|
1960
|
+
@overload
|
|
1961
|
+
def load(path: Pathish, /) -> Any:
|
|
1962
|
+
"""Read samples as the command line reads its targets.
|
|
1963
|
+
|
|
1964
|
+
A folder is read as a ``SampleList`` of its samples, as its project
|
|
1965
|
+
declares them, and a file as one ``Sample``; each is read with its
|
|
1966
|
+
project's model. Several paths are one ``SampleList``, in the order given,
|
|
1967
|
+
each sample once.
|
|
1968
|
+
|
|
1969
|
+
Args:
|
|
1970
|
+
*paths: Folders and sample files.
|
|
1971
|
+
|
|
1972
|
+
Returns:
|
|
1973
|
+
A ``Sample`` for one file, otherwise a ``SampleList``.
|
|
1974
|
+
|
|
1975
|
+
Raises:
|
|
1976
|
+
FileNotFoundError: A path does not exist; the message names the
|
|
1977
|
+
nearest.
|
|
1978
|
+
ValueError: A file is not a sample.
|
|
1979
|
+
|
|
1980
|
+
Example:
|
|
1981
|
+
>>> specimens = sk.load("samples")
|
|
1982
|
+
>>> steel = sk.load("samples/steel-01.md")
|
|
1983
|
+
>>> steel.area
|
|
1984
|
+
Property(value=25.05586444444445, uncertainty=0.05217296159913354, unit='mm²')
|
|
1985
|
+
"""
|
|
1986
|
+
@overload
|
|
1987
|
+
def load(first: Pathish, second: Pathish, /, *paths: Pathish) -> SampleList:
|
|
1988
|
+
"""Read samples as the command line reads its targets.
|
|
1989
|
+
|
|
1990
|
+
A folder is read as a ``SampleList`` of its samples, as its project
|
|
1991
|
+
declares them, and a file as one ``Sample``; each is read with its
|
|
1992
|
+
project's model. Several paths are one ``SampleList``, in the order given,
|
|
1993
|
+
each sample once.
|
|
1994
|
+
|
|
1995
|
+
Args:
|
|
1996
|
+
*paths: Folders and sample files.
|
|
1997
|
+
|
|
1998
|
+
Returns:
|
|
1999
|
+
A ``Sample`` for one file, otherwise a ``SampleList``.
|
|
2000
|
+
|
|
2001
|
+
Raises:
|
|
2002
|
+
FileNotFoundError: A path does not exist; the message names the
|
|
2003
|
+
nearest.
|
|
2004
|
+
ValueError: A file is not a sample.
|
|
2005
|
+
|
|
2006
|
+
Example:
|
|
2007
|
+
>>> specimens = sk.load("samples")
|
|
2008
|
+
>>> steel = sk.load("samples/steel-01.md")
|
|
2009
|
+
>>> steel.area
|
|
2010
|
+
Property(value=25.05586444444445, uncertainty=0.05217296159913354, unit='mm²')
|
|
2011
|
+
"""
|
|
2012
|
+
def keep(message: str | None = None) -> None:
|
|
2013
|
+
"""Keep what the script has saved so far as one snapshot of the history.
|
|
2014
|
+
|
|
2015
|
+
A script's saves are kept as one snapshot when it ends, with the
|
|
2016
|
+
script's source beside it; ``keep`` takes that snapshot now, so that the
|
|
2017
|
+
history shows the script's steps apart.
|
|
2018
|
+
|
|
2019
|
+
Args:
|
|
2020
|
+
message: What the snapshot says; the script's command line when not
|
|
2021
|
+
given. A blank message says nothing, and several lines are kept
|
|
2022
|
+
on one.
|
|
2023
|
+
|
|
2024
|
+
Warns:
|
|
2025
|
+
UserWarning: Nothing was saved in a project since the last snapshot,
|
|
2026
|
+
so nothing is kept.
|
|
2027
|
+
|
|
2028
|
+
Raises:
|
|
2029
|
+
TypeError: The message is not text.
|
|
2030
|
+
|
|
2031
|
+
Example:
|
|
2032
|
+
>>> steel.save()
|
|
2033
|
+
>>> sk.keep("thickness measured again")
|
|
2034
|
+
"""
|
|
2035
|
+
|
|
2036
|
+
|
|
2037
|
+
# A figure is matplotlib's. `figure` marks a model's method — an
|
|
2038
|
+
# instance method draws one sample, a class method a collection — and `plot`
|
|
2039
|
+
# draws it, a declared one, or the axes given.
|
|
2040
|
+
_Method = TypeVar("_Method")
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
@overload
|
|
2044
|
+
def figure(method: _Method) -> _Method:
|
|
2045
|
+
"""Mark a method of a model as a figure.
|
|
2046
|
+
|
|
2047
|
+
The method draws into the matplotlib axes it receives, and returns
|
|
2048
|
+
nothing; ``samplekit.plot`` and ``samplekit plot`` open it in a window or
|
|
2049
|
+
write it to a file. A method draws one sample; placed above
|
|
2050
|
+
``@classmethod``, it draws a list of them.
|
|
2051
|
+
|
|
2052
|
+
Args:
|
|
2053
|
+
method: The method, when used without arguments: ``@sk.figure``.
|
|
2054
|
+
subplots: ``(rows, columns)``: the method receives the array of axes
|
|
2055
|
+
``plt.subplots`` makes, instead of one.
|
|
2056
|
+
figsize: The figure's width and height, in centimetres.
|
|
2057
|
+
matplotlib: matplotlib's settings for this figure alone, by their
|
|
2058
|
+
names, over the project's ``[matplotlib]``:
|
|
2059
|
+
``{"lines.marker": "None"}``.
|
|
2060
|
+
|
|
2061
|
+
Returns:
|
|
2062
|
+
The method, marked.
|
|
2063
|
+
|
|
2064
|
+
Raises:
|
|
2065
|
+
ValueError: ``subplots`` is not two positive integers.
|
|
2066
|
+
|
|
2067
|
+
Example:
|
|
2068
|
+
>>> class Specimen(sk.Sample):
|
|
2069
|
+
... @sk.figure
|
|
2070
|
+
... def stress_strain(self, ax):
|
|
2071
|
+
... ax.plot(self.curve.values("strain"),
|
|
2072
|
+
... self.curve.values("stress"))
|
|
2073
|
+
"""
|
|
2074
|
+
@overload
|
|
2075
|
+
def figure(
|
|
2076
|
+
method: None = None,
|
|
2077
|
+
*,
|
|
2078
|
+
subplots: tuple[int, int] | None = None,
|
|
2079
|
+
figsize: tuple[float, float] | None = None,
|
|
2080
|
+
matplotlib: dict[str, Any] | None = None,
|
|
2081
|
+
) -> Callable[[_Method], _Method]:
|
|
2082
|
+
"""Mark a method of a model as a figure.
|
|
2083
|
+
|
|
2084
|
+
The method draws into the matplotlib axes it receives, and returns
|
|
2085
|
+
nothing; ``samplekit.plot`` and ``samplekit plot`` open it in a window or
|
|
2086
|
+
write it to a file. A method draws one sample; placed above
|
|
2087
|
+
``@classmethod``, it draws a list of them.
|
|
2088
|
+
|
|
2089
|
+
Args:
|
|
2090
|
+
method: The method, when used without arguments: ``@sk.figure``.
|
|
2091
|
+
subplots: ``(rows, columns)``: the method receives the array of axes
|
|
2092
|
+
``plt.subplots`` makes, instead of one.
|
|
2093
|
+
figsize: The figure's width and height, in centimetres.
|
|
2094
|
+
matplotlib: matplotlib's settings for this figure alone, by their
|
|
2095
|
+
names, over the project's ``[matplotlib]``:
|
|
2096
|
+
``{"lines.marker": "None"}``.
|
|
2097
|
+
|
|
2098
|
+
Returns:
|
|
2099
|
+
The method, marked.
|
|
2100
|
+
|
|
2101
|
+
Raises:
|
|
2102
|
+
ValueError: ``subplots`` is not two positive integers.
|
|
2103
|
+
|
|
2104
|
+
Example:
|
|
2105
|
+
>>> class Specimen(sk.Sample):
|
|
2106
|
+
... @sk.figure
|
|
2107
|
+
... def stress_strain(self, ax):
|
|
2108
|
+
... ax.plot(self.curve.values("strain"),
|
|
2109
|
+
... self.curve.values("stress"))
|
|
2110
|
+
"""
|
|
2111
|
+
# A script is the model's. `script` marks a method — an instance method acts
|
|
2112
|
+
# on one sample, a class method on the selection — and `samplekit run` runs it.
|
|
2113
|
+
def script(method: _Method) -> _Method:
|
|
2114
|
+
"""Mark a method of a model as a script.
|
|
2115
|
+
|
|
2116
|
+
``samplekit run NAME`` runs it, and ``r`` in the workbench: a method acts
|
|
2117
|
+
on one sample, each selected in turn; placed above ``@classmethod``, it
|
|
2118
|
+
receives the selection. Its keyword parameters are the command's options,
|
|
2119
|
+
``--set offset=0.002``, read as their annotation or their default says;
|
|
2120
|
+
its docstring is its description. What it changes in the samples is
|
|
2121
|
+
shown, and written once the change is accepted; what it returns is shown.
|
|
2122
|
+
|
|
2123
|
+
Args:
|
|
2124
|
+
method: The method.
|
|
2125
|
+
|
|
2126
|
+
Returns:
|
|
2127
|
+
The method, marked.
|
|
2128
|
+
|
|
2129
|
+
Raises:
|
|
2130
|
+
TypeError: The method is a static method, or takes ``*args`` or
|
|
2131
|
+
``**kwargs``.
|
|
2132
|
+
|
|
2133
|
+
Example:
|
|
2134
|
+
>>> class Specimen(sk.Sample):
|
|
2135
|
+
... @sk.script
|
|
2136
|
+
... def classify(self, limit: float = 20.0):
|
|
2137
|
+
... "Class the specimen's ductility by its elongation at break."
|
|
2138
|
+
... self.ductility = "high" if self.elongation.value >= limit else "low"
|
|
2139
|
+
"""
|
|
2140
|
+
def plot(
|
|
2141
|
+
target: Sample | SampleList | Iterable[Sample],
|
|
2142
|
+
figure: str | None = None,
|
|
2143
|
+
*,
|
|
2144
|
+
x: str | None = None,
|
|
2145
|
+
y: str | None = None,
|
|
2146
|
+
# A field, or several whose combinations are the groups.
|
|
2147
|
+
group: str | Sequence[str] | None = None,
|
|
2148
|
+
kind: Literal["scatter", "line", "step", "bar", "box"] | None = None,
|
|
2149
|
+
title: str | None = None,
|
|
2150
|
+
x_label: str | None = None,
|
|
2151
|
+
y_label: str | None = None,
|
|
2152
|
+
style: str | None = None,
|
|
2153
|
+
x_limits: tuple[AxisBound, AxisBound] | None = None,
|
|
2154
|
+
y_limits: tuple[AxisBound, AxisBound] | None = None,
|
|
2155
|
+
x_scale: Literal["linear", "log", "symlog"] | None = None,
|
|
2156
|
+
y_scale: Literal["linear", "log", "symlog"] | None = None,
|
|
2157
|
+
aspect: Literal["equal", "auto"] | None = None,
|
|
2158
|
+
legend: str | None = None,
|
|
2159
|
+
figsize: tuple[float, float] | None = None,
|
|
2160
|
+
output: Pathish | None = None,
|
|
2161
|
+
overwrite: bool = False,
|
|
2162
|
+
project_style: bool = True,
|
|
2163
|
+
) -> list[Any]:
|
|
2164
|
+
"""Draw a figure of a sample or a list of samples, with matplotlib.
|
|
2165
|
+
|
|
2166
|
+
The figure is a model's figure, one declared in ``.samplekitrc``, or
|
|
2167
|
+
axes given here: ``x`` and ``y``, drawn as a declared figure would be.
|
|
2168
|
+
It opens in a window, or is written to ``output``. The project's
|
|
2169
|
+
``[matplotlib]`` settings apply to every figure.
|
|
2170
|
+
|
|
2171
|
+
Args:
|
|
2172
|
+
target: A sample, a ``SampleList``, or an iterable of samples.
|
|
2173
|
+
figure: The name of a model's figure or of a declared one.
|
|
2174
|
+
x: The field on the x axis, for a figure not declared.
|
|
2175
|
+
y: The field on the y axis, for a figure not declared.
|
|
2176
|
+
group: A field, or several, whose values split the samples into
|
|
2177
|
+
series, each in its own colour.
|
|
2178
|
+
kind: ``"scatter"`` (the default), ``"line"``, ``"step"``, ``"bar"``
|
|
2179
|
+
or ``"box"``.
|
|
2180
|
+
title: The title; ``""`` draws none.
|
|
2181
|
+
x_label: The x axis label, instead of the field's symbol and unit.
|
|
2182
|
+
y_label: The y axis label, instead of the field's symbol and unit.
|
|
2183
|
+
style: The declared style whose symbols and units label the axes;
|
|
2184
|
+
``[render] figure_style`` by default.
|
|
2185
|
+
x_limits: The x axis bounds, ``(low, high)``; ``None`` or ``"auto"``
|
|
2186
|
+
leaves one free.
|
|
2187
|
+
y_limits: The y axis bounds, ``(low, high)``; ``None`` or ``"auto"``
|
|
2188
|
+
leaves one free.
|
|
2189
|
+
x_scale: ``"linear"``, ``"log"`` or ``"symlog"``.
|
|
2190
|
+
y_scale: ``"linear"``, ``"log"`` or ``"symlog"``.
|
|
2191
|
+
aspect: ``"equal"`` or ``"auto"``.
|
|
2192
|
+
legend: Where the legend goes: ``"best"``, ``"outside"``, ``"none"``
|
|
2193
|
+
or one of matplotlib's places.
|
|
2194
|
+
figsize: The figure's width and height, in centimetres.
|
|
2195
|
+
output: The file to write, in the format its extension names:
|
|
2196
|
+
``.png``, ``.pdf``, ``.svg``… ``{name}`` in it writes one file per
|
|
2197
|
+
sample, for a figure of one sample. A folder it names that does
|
|
2198
|
+
not exist is made.
|
|
2199
|
+
overwrite: Replace an existing file.
|
|
2200
|
+
project_style: Apply the project's ``[matplotlib]`` settings.
|
|
2201
|
+
|
|
2202
|
+
Returns:
|
|
2203
|
+
The matplotlib figures drawn.
|
|
2204
|
+
|
|
2205
|
+
Raises:
|
|
2206
|
+
TypeError: Neither ``figure`` nor ``x`` and ``y`` is given, or both
|
|
2207
|
+
are, or axes options are given to a model's figure.
|
|
2208
|
+
KeyError: An unknown figure or field; the message names the nearest.
|
|
2209
|
+
ValueError: No sample to draw, or an output format matplotlib
|
|
2210
|
+
cannot write.
|
|
2211
|
+
FileExistsError: ``output`` exists and ``overwrite`` is false.
|
|
2212
|
+
RuntimeError: No ``output`` is given and there is no display.
|
|
2213
|
+
|
|
2214
|
+
Example:
|
|
2215
|
+
>>> figures = sk.plot(specimens, "curves", output="paper/curves.pdf")
|
|
2216
|
+
>>> figures = sk.plot(specimens, x="yield_strength", y="tensile_strength",
|
|
2217
|
+
... group="material", output="strengths.png")
|
|
2218
|
+
"""
|