hiplot-plus 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hiplot/__init__.py +38 -0
- hiplot/__main__.py +10 -0
- hiplot/compress.py +70 -0
- hiplot/csvvalues.py +83 -0
- hiplot/experiment.py +637 -0
- hiplot/fetchers.py +322 -0
- hiplot/fetchers_demo.py +459 -0
- hiplot/ipython.py +217 -0
- hiplot/pkginfo.py +15 -0
- hiplot/py.typed +0 -0
- hiplot/render.py +141 -0
- hiplot/server.py +76 -0
- hiplot/static/built/hiplot.bundle.js +1224 -0
- hiplot/static/built/streamlit_component/hiplot_streamlit.bundle.js +1220 -0
- hiplot/static/built/streamlit_component/index.html +16 -0
- hiplot/static/icon.png +0 -0
- hiplot/streamlit_helpers.py +127 -0
- hiplot/templates/index.html +68 -0
- hiplot_plus-0.1.0.dist-info/METADATA +197 -0
- hiplot_plus-0.1.0.dist-info/RECORD +24 -0
- hiplot_plus-0.1.0.dist-info/WHEEL +4 -0
- hiplot_plus-0.1.0.dist-info/entry_points.txt +4 -0
- hiplot_plus-0.1.0.dist-info/licenses/LICENSE +21 -0
- hiplot_plus-0.1.0.dist-info/licenses/NOTICE +207 -0
hiplot/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
2
|
+
# This source code is licensed under the MIT license found in the
|
|
3
|
+
# LICENSE file in the root directory of this source tree.
|
|
4
|
+
|
|
5
|
+
from . import fetchers
|
|
6
|
+
from .experiment import (
|
|
7
|
+
Datapoint,
|
|
8
|
+
Displays,
|
|
9
|
+
Experiment,
|
|
10
|
+
ExperimentDisplayed,
|
|
11
|
+
ExperimentFetcherDoesntApply,
|
|
12
|
+
ExperimentValidationCircularRef,
|
|
13
|
+
ExperimentValidationError,
|
|
14
|
+
ExperimentValidationMissingParent,
|
|
15
|
+
ValueDef,
|
|
16
|
+
ValueType,
|
|
17
|
+
)
|
|
18
|
+
from .pkginfo import __version__, package_name
|
|
19
|
+
from .server import run_server, run_server_main
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Datapoint",
|
|
23
|
+
"Displays",
|
|
24
|
+
"Experiment",
|
|
25
|
+
"ExperimentDisplayed",
|
|
26
|
+
"ExperimentFetcherDoesntApply",
|
|
27
|
+
"ExperimentValidationCircularRef",
|
|
28
|
+
"ExperimentValidationError",
|
|
29
|
+
"ExperimentValidationMissingParent",
|
|
30
|
+
"ValueDef",
|
|
31
|
+
"ValueType",
|
|
32
|
+
"__package__",
|
|
33
|
+
"__version__",
|
|
34
|
+
"fetchers",
|
|
35
|
+
"package_name",
|
|
36
|
+
"run_server",
|
|
37
|
+
"run_server_main",
|
|
38
|
+
]
|
hiplot/__main__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
2
|
+
# This source code is licensed under the MIT license found in the
|
|
3
|
+
# LICENSE file in the root directory of this source tree.
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from .server import run_server_main
|
|
8
|
+
|
|
9
|
+
# Only ever executed by `python -m hiplot`, so it needs no __name__ guard.
|
|
10
|
+
sys.exit(run_server_main())
|
hiplot/compress.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
|
2
|
+
# This source code is licensed under the MIT license found in the
|
|
3
|
+
# LICENSE file in the root directory of this source tree.
|
|
4
|
+
"""Compact encodings of an experiment's datapoints for the frontend.
|
|
5
|
+
|
|
6
|
+
`src/lib/compress.ts` decodes both formats; the two sides change together.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import math
|
|
10
|
+
from collections.abc import Iterable, Sequence
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from .experiment import Datapoint
|
|
14
|
+
|
|
15
|
+
RESERVED_COLUMNS = frozenset(("uid", "from_uid"))
|
|
16
|
+
|
|
17
|
+
#: Key of the JSON object standing for a non-finite float, which JSON cannot represent.
|
|
18
|
+
NON_FINITE_KEY = "$f"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _build_columns_list(datapoints: Iterable[Datapoint]) -> list[str]:
|
|
22
|
+
"""Value columns in first-seen order, so the output is deterministic."""
|
|
23
|
+
columns: dict[str, None] = {}
|
|
24
|
+
for dp in datapoints:
|
|
25
|
+
columns.update(dict.fromkeys(dp.values))
|
|
26
|
+
return [column for column in columns if column not in RESERVED_COLUMNS]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def compress(datapoints: list[Datapoint]) -> dict[str, Any]:
|
|
30
|
+
"""Row-major encoding: column names once, then one array per row."""
|
|
31
|
+
columns = _build_columns_list(datapoints)
|
|
32
|
+
rows = [[dp.uid, dp.from_uid, *(dp.values.get(c) for c in columns)] for dp in datapoints]
|
|
33
|
+
return {"columns": columns, "rows": rows}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def encode_value(value: Any) -> Any:
|
|
37
|
+
"""A JSON-safe value: non-finite floats become {"$f": "inf" | "-inf" | "nan"}."""
|
|
38
|
+
if isinstance(value, float) and not math.isfinite(value):
|
|
39
|
+
return {NON_FINITE_KEY: "nan" if math.isnan(value) else ("inf" if value > 0 else "-inf")}
|
|
40
|
+
return value
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def compress_columnar(datapoints: Sequence[Datapoint]) -> dict[str, Any]:
|
|
44
|
+
"""Column-major encoding, valid strict JSON.
|
|
45
|
+
|
|
46
|
+
Each column is one array with a value per row, so column names are not
|
|
47
|
+
repeated per row. A datapoint without a value for a column is listed in
|
|
48
|
+
`missing` (its slot holds null), keeping "absent" distinct from None.
|
|
49
|
+
"""
|
|
50
|
+
columns = _build_columns_list(datapoints)
|
|
51
|
+
values: dict[str, list[Any]] = {}
|
|
52
|
+
missing: dict[str, list[int]] = {}
|
|
53
|
+
for column in columns:
|
|
54
|
+
column_values: list[Any] = []
|
|
55
|
+
column_missing: list[int] = []
|
|
56
|
+
for index, dp in enumerate(datapoints):
|
|
57
|
+
if column in dp.values:
|
|
58
|
+
column_values.append(encode_value(dp.values[column]))
|
|
59
|
+
else:
|
|
60
|
+
column_values.append(None)
|
|
61
|
+
column_missing.append(index)
|
|
62
|
+
values[column] = column_values
|
|
63
|
+
if column_missing:
|
|
64
|
+
missing[column] = column_missing
|
|
65
|
+
return {
|
|
66
|
+
"uid": [dp.uid for dp in datapoints],
|
|
67
|
+
"from_uid": [dp.from_uid for dp in datapoints],
|
|
68
|
+
"columns": values,
|
|
69
|
+
"missing": missing,
|
|
70
|
+
}
|
hiplot/csvvalues.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Reads the numbers out of CSV cells, which the csv module leaves as text.
|
|
2
|
+
|
|
3
|
+
A column is numeric when every one of its cells spells a number: decimal or
|
|
4
|
+
exponent notation, ``inf``/``infinity``/``nan`` in any case with an optional
|
|
5
|
+
sign, or a missing value: blank, or ``na``/``n/a``/``null``/``none`` in any
|
|
6
|
+
case. At least one cell must be finite. The
|
|
7
|
+
grammar is the one the frontend applies to text cells (``src/lib/d3_scales.ts``),
|
|
8
|
+
so both sides agree on which columns are numeric.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
import re
|
|
13
|
+
import typing as tp
|
|
14
|
+
from collections.abc import Iterable
|
|
15
|
+
|
|
16
|
+
_FINITE = re.compile(r"[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?")
|
|
17
|
+
_INTEGER = re.compile(r"[+-]?\d+")
|
|
18
|
+
_INFINITE = re.compile(r"([+-]?)(?:inf|infinity)", re.IGNORECASE)
|
|
19
|
+
_NAN = re.compile(r"[+-]?nan", re.IGNORECASE)
|
|
20
|
+
_MISSING = re.compile(r"na|n/a|null|none", re.IGNORECASE)
|
|
21
|
+
|
|
22
|
+
#: Columns that identify rows and are never read as numbers.
|
|
23
|
+
_IDENTIFIER_COLUMNS = frozenset({"uid", "from_uid"})
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def parse_number(cell: str | None) -> int | float | None:
|
|
27
|
+
"""The number a CSV cell spells: an int, a float (inf and nan included), or None when missing.
|
|
28
|
+
|
|
29
|
+
Blank cells, the missing-value spellings, and a short row's missing cells
|
|
30
|
+
(None from ``csv.DictReader``) are missing.
|
|
31
|
+
|
|
32
|
+
:raises ValueError: when the cell spells no number.
|
|
33
|
+
"""
|
|
34
|
+
text = "" if cell is None else cell.strip()
|
|
35
|
+
if _is_missing_spelling(text):
|
|
36
|
+
return None
|
|
37
|
+
if _INTEGER.fullmatch(text):
|
|
38
|
+
return int(text)
|
|
39
|
+
if _FINITE.fullmatch(text):
|
|
40
|
+
return float(text)
|
|
41
|
+
infinite = _INFINITE.fullmatch(text)
|
|
42
|
+
if infinite is not None:
|
|
43
|
+
return -math.inf if infinite.group(1) == "-" else math.inf
|
|
44
|
+
if _NAN.fullmatch(text):
|
|
45
|
+
return math.nan
|
|
46
|
+
raise ValueError(f"Not a number: {cell!r}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _is_missing_spelling(text: str) -> bool:
|
|
50
|
+
return text == "" or _MISSING.fullmatch(text) is not None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _is_number_spelling(cell: str | None) -> bool:
|
|
54
|
+
text = "" if cell is None else cell.strip()
|
|
55
|
+
return (
|
|
56
|
+
_is_missing_spelling(text)
|
|
57
|
+
or _FINITE.fullmatch(text) is not None
|
|
58
|
+
or _INFINITE.fullmatch(text) is not None
|
|
59
|
+
or _NAN.fullmatch(text) is not None
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _has_finite_number(cells: Iterable[str | None]) -> bool:
|
|
64
|
+
return any(cell is not None and _FINITE.fullmatch(cell.strip()) is not None for cell in cells)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def typed_rows(rows: Iterable[dict[str, str | None]]) -> list[dict[str, tp.Any]]:
|
|
68
|
+
"""The rows of a ``csv.DictReader`` with the cells of each numeric column read as numbers.
|
|
69
|
+
|
|
70
|
+
Columns holding any other text keep all of their cells as text, so a column
|
|
71
|
+
never mixes numbers and strings.
|
|
72
|
+
"""
|
|
73
|
+
table = list(rows)
|
|
74
|
+
columns = {column for row in table for column in row} - _IDENTIFIER_COLUMNS
|
|
75
|
+
numeric = {
|
|
76
|
+
column
|
|
77
|
+
for column in columns
|
|
78
|
+
if all(_is_number_spelling(row[column]) for row in table if column in row)
|
|
79
|
+
and _has_finite_number(row[column] for row in table if column in row)
|
|
80
|
+
}
|
|
81
|
+
return [
|
|
82
|
+
{column: parse_number(cell) if column in numeric else cell for column, cell in row.items()} for row in table
|
|
83
|
+
]
|