reader-workbench 1.0.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.
- reader_workbench/__init__.py +22 -0
- reader_workbench/__main__.py +4 -0
- reader_workbench/_version.py +17 -0
- reader_workbench/api/__init__.py +74 -0
- reader_workbench/api/_record_reads.py +75 -0
- reader_workbench/api/artifacts.py +79 -0
- reader_workbench/api/facade.py +538 -0
- reader_workbench/api/models.py +285 -0
- reader_workbench/api/notebooks.py +63 -0
- reader_workbench/contracts/__init__.py +18 -0
- reader_workbench/contracts/builtins/__init__.py +36 -0
- reader_workbench/contracts/builtins/cytometry.py +140 -0
- reader_workbench/contracts/builtins/four_state_event_window.py +214 -0
- reader_workbench/contracts/builtins/generic.py +21 -0
- reader_workbench/contracts/builtins/logic.py +149 -0
- reader_workbench/contracts/builtins/plate_reader.py +47 -0
- reader_workbench/contracts/catalog.py +257 -0
- reader_workbench/contracts/model.py +109 -0
- reader_workbench/domains/__init__.py +1 -0
- reader_workbench/domains/cytometry/__init__.py +3 -0
- reader_workbench/domains/cytometry/analysis/__init__.py +29 -0
- reader_workbench/domains/cytometry/analysis/events.py +182 -0
- reader_workbench/domains/cytometry/analysis/gating.py +175 -0
- reader_workbench/domains/cytometry/analysis/workflow.py +248 -0
- reader_workbench/domains/cytometry/io/__init__.py +3 -0
- reader_workbench/domains/cytometry/io/fcs.py +135 -0
- reader_workbench/domains/cytometry/plots/__init__.py +5 -0
- reader_workbench/domains/cytometry/plots/diagnostic.py +155 -0
- reader_workbench/domains/logic/__init__.py +3 -0
- reader_workbench/domains/logic/crosstalk/__init__.py +3 -0
- reader_workbench/domains/logic/crosstalk/pairs.py +661 -0
- reader_workbench/domains/logic/four_state_vector/__init__.py +7 -0
- reader_workbench/domains/logic/four_state_vector/builder.py +321 -0
- reader_workbench/domains/logic/four_state_vector/collection/__init__.py +20 -0
- reader_workbench/domains/logic/four_state_vector/collection/checks.py +49 -0
- reader_workbench/domains/logic/four_state_vector/collection/constants.py +29 -0
- reader_workbench/domains/logic/four_state_vector/collection/model.py +19 -0
- reader_workbench/domains/logic/four_state_vector/collection/render.py +383 -0
- reader_workbench/domains/logic/four_state_vector/collection/sources.py +185 -0
- reader_workbench/domains/logic/four_state_vector/config.py +214 -0
- reader_workbench/domains/logic/four_state_vector/diagnostic.py +361 -0
- reader_workbench/domains/logic/four_state_vector/heatmap.py +86 -0
- reader_workbench/domains/logic/four_state_vector/math.py +191 -0
- reader_workbench/domains/logic/four_state_vector/reference.py +85 -0
- reader_workbench/domains/logic/four_state_vector/selection.py +228 -0
- reader_workbench/domains/logic/four_state_vector/treatment_semantics.py +51 -0
- reader_workbench/domains/logic/four_state_vector/validation.py +19 -0
- reader_workbench/domains/logic/logic_symmetry/__init__.py +3 -0
- reader_workbench/domains/logic/logic_symmetry/encodings.py +93 -0
- reader_workbench/domains/logic/logic_symmetry/extract_corners.py +192 -0
- reader_workbench/domains/logic/logic_symmetry/main.py +236 -0
- reader_workbench/domains/logic/logic_symmetry/metrics.py +96 -0
- reader_workbench/domains/logic/logic_symmetry/overlay.py +129 -0
- reader_workbench/domains/logic/logic_symmetry/prep.py +138 -0
- reader_workbench/domains/logic/logic_symmetry/render.py +356 -0
- reader_workbench/domains/logic/treatment_columns.py +42 -0
- reader_workbench/domains/plate_reader/__init__.py +1 -0
- reader_workbench/domains/plate_reader/analysis/__init__.py +14 -0
- reader_workbench/domains/plate_reader/analysis/fold_change.py +474 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/__init__.py +21 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/aggregation.py +191 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/contract_fields.py +50 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/contracts.py +320 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/design_dispositions.py +54 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/disposition_records.py +140 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/event_sensitivity.py +27 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/materialize.py +274 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/observation_resampling.py +97 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/reduction.py +62 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/seeds.py +15 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/sources.py +308 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/well_exclusion_validation.py +94 -0
- reader_workbench/domains/plate_reader/analysis/four_state_event_window/well_exclusions.py +54 -0
- reader_workbench/domains/plate_reader/analysis/timepoints.py +76 -0
- reader_workbench/domains/plate_reader/io/__init__.py +6 -0
- reader_workbench/domains/plate_reader/io/sample_map.py +65 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/__init__.py +6 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_kinetic.py +141 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_parser.py +295 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_shared.py +195 -0
- reader_workbench/domains/plate_reader/io/synergy_h1/_snapshot.py +130 -0
- reader_workbench/domains/plate_reader/ordering.py +59 -0
- reader_workbench/domains/plate_reader/plots/__init__.py +15 -0
- reader_workbench/domains/plate_reader/plots/_data.py +29 -0
- reader_workbench/domains/plate_reader/plots/common.py +346 -0
- reader_workbench/domains/plate_reader/plots/distributions.py +324 -0
- reader_workbench/domains/plate_reader/plots/dual_reporter_triptych.py +525 -0
- reader_workbench/domains/plate_reader/plots/dual_reporter_triptych_render.py +195 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/__init__.py +26 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic.py +295 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_components.py +149 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_render.py +308 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/diagnostic_style.py +51 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/schema.py +8 -0
- reader_workbench/domains/plate_reader/plots/four_state_event_window/summary.py +140 -0
- reader_workbench/domains/plate_reader/plots/grouping.py +53 -0
- reader_workbench/domains/plate_reader/plots/panels/__init__.py +12 -0
- reader_workbench/domains/plate_reader/plots/panels/snapshot.py +161 -0
- reader_workbench/domains/plate_reader/plots/panels/snapshot_data.py +91 -0
- reader_workbench/domains/plate_reader/plots/panels/time_series.py +296 -0
- reader_workbench/domains/plate_reader/plots/single_reporter_diagnostic.py +435 -0
- reader_workbench/domains/plate_reader/plots/single_reporter_diagnostic_render.py +300 -0
- reader_workbench/domains/plate_reader/plots/snapshot_barplot/__init__.py +315 -0
- reader_workbench/domains/plate_reader/plots/snapshot_barplot/planning.py +168 -0
- reader_workbench/domains/plate_reader/plots/snapshot_heatmap/__init__.py +205 -0
- reader_workbench/domains/plate_reader/plots/snapshot_heatmap/inputs.py +103 -0
- reader_workbench/domains/plate_reader/plots/time_series.py +317 -0
- reader_workbench/domains/plate_reader/plots/ts_and_snap/__init__.py +479 -0
- reader_workbench/domains/plate_reader/plots/ts_and_snap/planning.py +283 -0
- reader_workbench/domains/time_series/__init__.py +29 -0
- reader_workbench/domains/time_series/aggregation.py +60 -0
- reader_workbench/domains/time_series/contracts.py +368 -0
- reader_workbench/domains/time_series/reduction.py +395 -0
- reader_workbench/errors.py +55 -0
- reader_workbench/maintenance/__init__.py +6 -0
- reader_workbench/maintenance/docs.py +335 -0
- reader_workbench/maintenance/model.py +28 -0
- reader_workbench/maintenance/release.py +39 -0
- reader_workbench/maintenance/skills.py +124 -0
- reader_workbench/plotting/__init__.py +20 -0
- reader_workbench/plotting/mpl.py +56 -0
- reader_workbench/plotting/sinks.py +69 -0
- reader_workbench/plotting/style.py +175 -0
- reader_workbench/plotting/utils.py +27 -0
- reader_workbench/plugins/__init__.py +1 -0
- reader_workbench/plugins/catalog.py +33 -0
- reader_workbench/plugins/export/__init__.py +0 -0
- reader_workbench/plugins/export/_paths.py +21 -0
- reader_workbench/plugins/export/csv.py +41 -0
- reader_workbench/plugins/export/xlsx.py +44 -0
- reader_workbench/plugins/ingest/__init__.py +0 -0
- reader_workbench/plugins/ingest/_discovery.py +58 -0
- reader_workbench/plugins/ingest/discovery_policy.py +66 -0
- reader_workbench/plugins/ingest/flow_cytometer.py +139 -0
- reader_workbench/plugins/ingest/synergy_h1.py +234 -0
- reader_workbench/plugins/manifests/__init__.py +1 -0
- reader_workbench/plugins/manifests/export.py +29 -0
- reader_workbench/plugins/manifests/ingest.py +29 -0
- reader_workbench/plugins/manifests/plot.py +161 -0
- reader_workbench/plugins/manifests/transform.py +172 -0
- reader_workbench/plugins/manifests/validator.py +18 -0
- reader_workbench/plugins/plot/__init__.py +0 -0
- reader_workbench/plugins/plot/_shared.py +55 -0
- reader_workbench/plugins/plot/cytometry_diagnostic.py +54 -0
- reader_workbench/plugins/plot/distributions.py +58 -0
- reader_workbench/plugins/plot/dual_reporter_triptych.py +224 -0
- reader_workbench/plugins/plot/four_state_event_window_diagnostic.py +133 -0
- reader_workbench/plugins/plot/four_state_event_window_summary.py +53 -0
- reader_workbench/plugins/plot/four_state_vector_collection.py +45 -0
- reader_workbench/plugins/plot/four_state_vector_diagnostic.py +105 -0
- reader_workbench/plugins/plot/four_state_vector_heatmap.py +63 -0
- reader_workbench/plugins/plot/logic_symmetry.py +56 -0
- reader_workbench/plugins/plot/single_reporter_diagnostic.py +279 -0
- reader_workbench/plugins/plot/snapshot_barplot.py +65 -0
- reader_workbench/plugins/plot/snapshot_heatmap.py +104 -0
- reader_workbench/plugins/plot/time_series.py +114 -0
- reader_workbench/plugins/plot/ts_and_snap.py +210 -0
- reader_workbench/plugins/transform/__init__.py +0 -0
- reader_workbench/plugins/transform/_four_state_vector.py +204 -0
- reader_workbench/plugins/transform/_labeling.py +109 -0
- reader_workbench/plugins/transform/alias.py +70 -0
- reader_workbench/plugins/transform/assay_labels.py +62 -0
- reader_workbench/plugins/transform/blank.py +79 -0
- reader_workbench/plugins/transform/crosstalk_pairs.py +180 -0
- reader_workbench/plugins/transform/cytometry_gating.py +120 -0
- reader_workbench/plugins/transform/fold_change.py +79 -0
- reader_workbench/plugins/transform/four_state_event_window.py +93 -0
- reader_workbench/plugins/transform/four_state_vector.py +62 -0
- reader_workbench/plugins/transform/four_state_vector_collection.py +41 -0
- reader_workbench/plugins/transform/logic_symmetry.py +67 -0
- reader_workbench/plugins/transform/outlier_filter.py +60 -0
- reader_workbench/plugins/transform/overflow.py +197 -0
- reader_workbench/plugins/transform/ratio.py +237 -0
- reader_workbench/plugins/transform/sample_map.py +170 -0
- reader_workbench/plugins/transform/sample_metadata.py +94 -0
- reader_workbench/plugins/validator/__init__.py +1 -0
- reader_workbench/plugins/validator/to_tidy_plus_map.py +155 -0
- reader_workbench/protocols/__init__.py +80 -0
- reader_workbench/protocols/_builtins_plate_reader_growth.py +179 -0
- reader_workbench/protocols/_builtins_plate_reader_variants.py +274 -0
- reader_workbench/protocols/builtins.py +1656 -0
- reader_workbench/protocols/compiler.py +22 -0
- reader_workbench/protocols/compilers/__init__.py +1 -0
- reader_workbench/protocols/compilers/common.py +100 -0
- reader_workbench/protocols/compilers/cytometry.py +87 -0
- reader_workbench/protocols/compilers/generic.py +14 -0
- reader_workbench/protocols/compilers/logic.py +245 -0
- reader_workbench/protocols/compilers/plate_reader.py +937 -0
- reader_workbench/protocols/compilers/plate_reader_pipeline.py +197 -0
- reader_workbench/protocols/model.py +1486 -0
- reader_workbench/protocols/semantic_coverage.py +234 -0
- reader_workbench/runtime/__init__.py +12 -0
- reader_workbench/runtime/builtin.py +23 -0
- reader_workbench/runtime/model.py +42 -0
- reader_workbench/workbench/__init__.py +60 -0
- reader_workbench/workbench/assets/__init__.py +22 -0
- reader_workbench/workbench/assets/types.py +118 -0
- reader_workbench/workbench/audit/__init__.py +5 -0
- reader_workbench/workbench/audit/experiments.py +307 -0
- reader_workbench/workbench/audit/staging.py +187 -0
- reader_workbench/workbench/cli/__init__.py +51 -0
- reader_workbench/workbench/cli/_lazy.py +9 -0
- reader_workbench/workbench/cli/_records_view.py +150 -0
- reader_workbench/workbench/cli/_surface_execution.py +443 -0
- reader_workbench/workbench/cli/audit.py +95 -0
- reader_workbench/workbench/cli/automation.py +229 -0
- reader_workbench/workbench/cli/demo.py +46 -0
- reader_workbench/workbench/cli/dop.py +91 -0
- reader_workbench/workbench/cli/experiments.py +635 -0
- reader_workbench/workbench/cli/helpers.py +232 -0
- reader_workbench/workbench/cli/main.py +59 -0
- reader_workbench/workbench/cli/maintenance.py +82 -0
- reader_workbench/workbench/cli/notebooks.py +260 -0
- reader_workbench/workbench/cli/pagination.py +117 -0
- reader_workbench/workbench/cli/protocols.py +336 -0
- reader_workbench/workbench/cli/shared.py +309 -0
- reader_workbench/workbench/cli/surfaces.py +534 -0
- reader_workbench/workbench/cli/verification.py +128 -0
- reader_workbench/workbench/commands.py +10 -0
- reader_workbench/workbench/config/__init__.py +47 -0
- reader_workbench/workbench/config/identity.py +13 -0
- reader_workbench/workbench/config/load.py +405 -0
- reader_workbench/workbench/config/model.py +274 -0
- reader_workbench/workbench/context.py +26 -0
- reader_workbench/workbench/decl/__init__.py +31 -0
- reader_workbench/workbench/decl/build.py +190 -0
- reader_workbench/workbench/decl/model.py +81 -0
- reader_workbench/workbench/dop/__init__.py +12 -0
- reader_workbench/workbench/dop/builtins.py +261 -0
- reader_workbench/workbench/dop/model.py +209 -0
- reader_workbench/workbench/engine/__init__.py +42 -0
- reader_workbench/workbench/engine/_shared.py +76 -0
- reader_workbench/workbench/engine/contracts.py +283 -0
- reader_workbench/workbench/engine/execution.py +326 -0
- reader_workbench/workbench/engine/file_outputs.py +260 -0
- reader_workbench/workbench/engine/inputs.py +161 -0
- reader_workbench/workbench/engine/invocations.py +507 -0
- reader_workbench/workbench/engine/planning.py +72 -0
- reader_workbench/workbench/engine/runtime.py +464 -0
- reader_workbench/workbench/engine/setup.py +149 -0
- reader_workbench/workbench/engine/validation.py +684 -0
- reader_workbench/workbench/experiment/__init__.py +47 -0
- reader_workbench/workbench/experiment/model.py +381 -0
- reader_workbench/workbench/experiments.py +133 -0
- reader_workbench/workbench/graph/__init__.py +47 -0
- reader_workbench/workbench/graph/nodes.py +102 -0
- reader_workbench/workbench/graph/normalize.py +177 -0
- reader_workbench/workbench/graph/refs.py +148 -0
- reader_workbench/workbench/input_discovery.py +19 -0
- reader_workbench/workbench/inspection/__init__.py +3 -0
- reader_workbench/workbench/inspection/catalogs.py +128 -0
- reader_workbench/workbench/inspection/common.py +92 -0
- reader_workbench/workbench/inspection/dop.py +64 -0
- reader_workbench/workbench/inspection/experiments.py +449 -0
- reader_workbench/workbench/inspection/inventory.py +68 -0
- reader_workbench/workbench/inspection/protocols.py +368 -0
- reader_workbench/workbench/inspection/readiness.py +333 -0
- reader_workbench/workbench/inspection/reports.py +367 -0
- reader_workbench/workbench/inspection/results.py +166 -0
- reader_workbench/workbench/inspection/runtime.py +287 -0
- reader_workbench/workbench/inspection/semantics.py +192 -0
- reader_workbench/workbench/inspection/validation.py +30 -0
- reader_workbench/workbench/notebooks/__init__.py +17 -0
- reader_workbench/workbench/notebooks/_launch_registry.py +112 -0
- reader_workbench/workbench/notebooks/_launch_runtime.py +104 -0
- reader_workbench/workbench/notebooks/components/__init__.py +21 -0
- reader_workbench/workbench/notebooks/components/deliverables.py +403 -0
- reader_workbench/workbench/notebooks/components/overview.py +119 -0
- reader_workbench/workbench/notebooks/eda.marimo.py.txt +153 -0
- reader_workbench/workbench/notebooks/launch.py +274 -0
- reader_workbench/workbench/notebooks/presentation.py +136 -0
- reader_workbench/workbench/notebooks/scaffold.py +60 -0
- reader_workbench/workbench/ontology.py +78 -0
- reader_workbench/workbench/paths.py +44 -0
- reader_workbench/workbench/ports/__init__.py +31 -0
- reader_workbench/workbench/ports/model.py +168 -0
- reader_workbench/workbench/records/__init__.py +44 -0
- reader_workbench/workbench/records/epoch.py +329 -0
- reader_workbench/workbench/records/evidence.py +247 -0
- reader_workbench/workbench/records/identity.py +87 -0
- reader_workbench/workbench/records/locking.py +185 -0
- reader_workbench/workbench/records/model.py +711 -0
- reader_workbench/workbench/records/sources.py +73 -0
- reader_workbench/workbench/records/store.py +1022 -0
- reader_workbench/workbench/records/verification.py +998 -0
- reader_workbench/workbench/registry.py +333 -0
- reader_workbench/workbench/spec_overrides.py +215 -0
- reader_workbench-1.0.0.dist-info/METADATA +91 -0
- reader_workbench-1.0.0.dist-info/RECORD +293 -0
- reader_workbench-1.0.0.dist-info/WHEEL +5 -0
- reader_workbench-1.0.0.dist-info/entry_points.txt +2 -0
- reader_workbench-1.0.0.dist-info/licenses/LICENSE +21 -0
- reader_workbench-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Materialize explicit well and design eligibility dispositions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
from .design_dispositions import DesignDisposition
|
|
10
|
+
from .well_exclusions import WellExclusion
|
|
11
|
+
|
|
12
|
+
_COLUMNS = (
|
|
13
|
+
"experiment_id",
|
|
14
|
+
"design_id",
|
|
15
|
+
"scope",
|
|
16
|
+
"state",
|
|
17
|
+
"position",
|
|
18
|
+
"reduction_id",
|
|
19
|
+
"reason",
|
|
20
|
+
"observed_count",
|
|
21
|
+
"required_count",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def partition_eligible_wells(
|
|
26
|
+
wells: pd.DataFrame,
|
|
27
|
+
*,
|
|
28
|
+
dispositions: Sequence[DesignDisposition],
|
|
29
|
+
experiment_id: str,
|
|
30
|
+
reduction_id: str,
|
|
31
|
+
reference_design_id: str,
|
|
32
|
+
available_design_ids: set[str],
|
|
33
|
+
required_count: int,
|
|
34
|
+
) -> tuple[pd.DataFrame, list[dict[str, object]]]:
|
|
35
|
+
selected = [
|
|
36
|
+
item for item in dispositions if item.experiment_id == experiment_id and item.reduction_id == reduction_id
|
|
37
|
+
]
|
|
38
|
+
disposed_ids: set[str] = set()
|
|
39
|
+
records: list[dict[str, object]] = []
|
|
40
|
+
for item in selected:
|
|
41
|
+
if item.design_id == reference_design_id:
|
|
42
|
+
raise ValueError(f"{experiment_id}:{reduction_id} cannot dispose the reference design.")
|
|
43
|
+
if item.design_id not in available_design_ids:
|
|
44
|
+
raise ValueError(f"{experiment_id}:{reduction_id} disposition names absent design {item.design_id!r}.")
|
|
45
|
+
observed_count = int(
|
|
46
|
+
wells.loc[
|
|
47
|
+
wells["design_id"].astype(str).eq(item.design_id) & wells["state"].astype(str).eq(item.state)
|
|
48
|
+
].shape[0]
|
|
49
|
+
)
|
|
50
|
+
if observed_count >= required_count:
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"{experiment_id}:{item.design_id}:{item.state}:{reduction_id} has {observed_count} observations; "
|
|
53
|
+
f"at least {required_count} are required, so the design disposition is not justified."
|
|
54
|
+
)
|
|
55
|
+
disposed_ids.add(item.design_id)
|
|
56
|
+
records.append(
|
|
57
|
+
_record(
|
|
58
|
+
experiment_id=item.experiment_id,
|
|
59
|
+
design_id=item.design_id,
|
|
60
|
+
scope="design",
|
|
61
|
+
state=item.state,
|
|
62
|
+
position=None,
|
|
63
|
+
reduction_id=item.reduction_id,
|
|
64
|
+
reason=item.reason,
|
|
65
|
+
observed_count=observed_count,
|
|
66
|
+
required_count=required_count,
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
eligible = wells.loc[~wells["design_id"].astype(str).isin(disposed_ids)].copy()
|
|
70
|
+
return eligible, records
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def partition_reduction_wells(
|
|
74
|
+
midpoint: pd.DataFrame,
|
|
75
|
+
lower: pd.DataFrame,
|
|
76
|
+
upper: pd.DataFrame,
|
|
77
|
+
*,
|
|
78
|
+
exclusions: Sequence[WellExclusion],
|
|
79
|
+
dispositions: Sequence[DesignDisposition],
|
|
80
|
+
experiment_id: str,
|
|
81
|
+
reduction_id: str,
|
|
82
|
+
reference_design_id: str,
|
|
83
|
+
available_design_ids: set[str],
|
|
84
|
+
required_count: int,
|
|
85
|
+
) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame, list[dict[str, object]]]:
|
|
86
|
+
eligible, records = partition_eligible_wells(
|
|
87
|
+
midpoint,
|
|
88
|
+
dispositions=dispositions,
|
|
89
|
+
experiment_id=experiment_id,
|
|
90
|
+
reduction_id=reduction_id,
|
|
91
|
+
reference_design_id=reference_design_id,
|
|
92
|
+
available_design_ids=available_design_ids,
|
|
93
|
+
required_count=required_count,
|
|
94
|
+
)
|
|
95
|
+
eligible_ids = set(eligible["design_id"].astype(str))
|
|
96
|
+
eligible_lower = lower.loc[lower["design_id"].astype(str).isin(eligible_ids)].copy()
|
|
97
|
+
eligible_upper = upper.loc[upper["design_id"].astype(str).isin(eligible_ids)].copy()
|
|
98
|
+
records[:0] = well_disposition_records(
|
|
99
|
+
exclusions,
|
|
100
|
+
experiment_id=experiment_id,
|
|
101
|
+
reduction_id=reduction_id,
|
|
102
|
+
)
|
|
103
|
+
return eligible, eligible_lower, eligible_upper, records
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def well_disposition_records(
|
|
107
|
+
exclusions: Sequence[WellExclusion],
|
|
108
|
+
*,
|
|
109
|
+
experiment_id: str,
|
|
110
|
+
reduction_id: str,
|
|
111
|
+
) -> list[dict[str, object]]:
|
|
112
|
+
return [
|
|
113
|
+
_record(
|
|
114
|
+
experiment_id=item.experiment_id,
|
|
115
|
+
design_id=item.design_id,
|
|
116
|
+
scope="well",
|
|
117
|
+
state=item.state,
|
|
118
|
+
position=item.position,
|
|
119
|
+
reduction_id=item.reduction_id,
|
|
120
|
+
reason=item.reason,
|
|
121
|
+
observed_count=None,
|
|
122
|
+
required_count=None,
|
|
123
|
+
)
|
|
124
|
+
for item in exclusions
|
|
125
|
+
if item.experiment_id == experiment_id and item.reduction_id == reduction_id
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def disposition_frame(records: Sequence[dict[str, object]]) -> pd.DataFrame:
|
|
130
|
+
frame = pd.DataFrame.from_records(records, columns=_COLUMNS)
|
|
131
|
+
frame["observed_count"] = frame["observed_count"].astype("Int64")
|
|
132
|
+
frame["required_count"] = frame["required_count"].astype("Int64")
|
|
133
|
+
return frame
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _record(**values: object) -> dict[str, object]:
|
|
137
|
+
return {column: values[column] for column in _COLUMNS}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
__all__ = ["disposition_frame", "partition_reduction_wells"]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Event-time sensitivity primitives for four-state event-window reductions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def symmetric_event_sensitivity_half_width(
|
|
9
|
+
midpoint: float,
|
|
10
|
+
lower_bound: float,
|
|
11
|
+
upper_bound: float,
|
|
12
|
+
) -> float:
|
|
13
|
+
"""Return a midpoint-centered envelope covering both event-bound reductions.
|
|
14
|
+
|
|
15
|
+
Four-state event-window reduction is not necessarily linear in event time. The
|
|
16
|
+
midpoint estimate therefore need not bisect the values obtained at the two
|
|
17
|
+
event bounds. A symmetric display interval uses the larger absolute
|
|
18
|
+
deviation from the midpoint.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
values = np.asarray([midpoint, lower_bound, upper_bound], dtype=float)
|
|
22
|
+
if not np.all(np.isfinite(values)):
|
|
23
|
+
raise ValueError("event-time sensitivity values must be finite.")
|
|
24
|
+
return float(max(abs(values[0] - values[1]), abs(values[2] - values[0])))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
__all__ = ["symmetric_event_sensitivity_half_width"]
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""Materialize well and design four-state event-window records from verified sources."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from .aggregation import build_design_records
|
|
8
|
+
from .contracts import FourStateEventWindowAnalysisSpec, ReductionSpec
|
|
9
|
+
from .disposition_records import disposition_frame, partition_reduction_wells
|
|
10
|
+
from .observation_resampling import descriptive_resampling_records
|
|
11
|
+
from .reduction import (
|
|
12
|
+
combine_bound_kinds,
|
|
13
|
+
four_state_event_window_temporal_spec,
|
|
14
|
+
invert_bound_kind,
|
|
15
|
+
reduce_temporal_trace,
|
|
16
|
+
)
|
|
17
|
+
from .sources import ExperimentSource, event_record
|
|
18
|
+
from .well_exclusion_validation import require_well_exclusion_support_failure
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def materialize_experiment(
|
|
22
|
+
source: ExperimentSource,
|
|
23
|
+
*,
|
|
24
|
+
request: FourStateEventWindowAnalysisSpec,
|
|
25
|
+
) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame, pd.DataFrame, pd.DataFrame, pd.DataFrame]:
|
|
26
|
+
"""Build midpoint well/design records plus event-bound sensitivity."""
|
|
27
|
+
|
|
28
|
+
require_well_exclusion_support_failure(
|
|
29
|
+
source.well_exclusions,
|
|
30
|
+
reductions={item.id: item for item in request.reductions},
|
|
31
|
+
quality=request.quality,
|
|
32
|
+
response=source.response,
|
|
33
|
+
magnitude=source.magnitude,
|
|
34
|
+
event_estimates_h=(
|
|
35
|
+
source.event.interval_start_assay_h,
|
|
36
|
+
source.event.estimate_assay_h,
|
|
37
|
+
source.event.interval_end_assay_h,
|
|
38
|
+
),
|
|
39
|
+
pre_window_end_h=source.event.interval_start_assay_h,
|
|
40
|
+
experiment_id=source.experiment_id,
|
|
41
|
+
)
|
|
42
|
+
well_frames: list[pd.DataFrame] = []
|
|
43
|
+
design_frames: list[pd.DataFrame] = []
|
|
44
|
+
draw_frames: list[pd.DataFrame] = []
|
|
45
|
+
disposition_records: list[dict[str, object]] = []
|
|
46
|
+
for reduction in request.reductions:
|
|
47
|
+
midpoint = _reduce_wells(
|
|
48
|
+
source,
|
|
49
|
+
request=request,
|
|
50
|
+
reduction=reduction,
|
|
51
|
+
event_estimate_h=source.event.estimate_assay_h,
|
|
52
|
+
)
|
|
53
|
+
lower = _reduce_wells(
|
|
54
|
+
source,
|
|
55
|
+
request=request,
|
|
56
|
+
reduction=reduction,
|
|
57
|
+
event_estimate_h=source.event.interval_start_assay_h,
|
|
58
|
+
)
|
|
59
|
+
upper = _reduce_wells(
|
|
60
|
+
source,
|
|
61
|
+
request=request,
|
|
62
|
+
reduction=reduction,
|
|
63
|
+
event_estimate_h=source.event.interval_end_assay_h,
|
|
64
|
+
)
|
|
65
|
+
eligible_midpoint, eligible_lower, eligible_upper, reduction_dispositions = partition_reduction_wells(
|
|
66
|
+
midpoint,
|
|
67
|
+
lower,
|
|
68
|
+
upper,
|
|
69
|
+
exclusions=request.source.well_exclusions,
|
|
70
|
+
dispositions=request.source.design_dispositions,
|
|
71
|
+
experiment_id=source.experiment_id,
|
|
72
|
+
reduction_id=reduction.id,
|
|
73
|
+
reference_design_id=request.source.reference_design_id,
|
|
74
|
+
available_design_ids=set(source.response["design_id"].astype(str)),
|
|
75
|
+
required_count=request.quality.min_observations_per_state,
|
|
76
|
+
)
|
|
77
|
+
well_frames.append(midpoint)
|
|
78
|
+
draw_frames.append(descriptive_resampling_records(eligible_midpoint, request=request))
|
|
79
|
+
design_frames.append(
|
|
80
|
+
build_design_records(
|
|
81
|
+
eligible_midpoint,
|
|
82
|
+
lower=eligible_lower,
|
|
83
|
+
upper=eligible_upper,
|
|
84
|
+
source=source,
|
|
85
|
+
request=request,
|
|
86
|
+
reduction=reduction,
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
disposition_records.extend(reduction_dispositions)
|
|
90
|
+
|
|
91
|
+
wells = pd.concat(well_frames, ignore_index=True)
|
|
92
|
+
designs = pd.concat(design_frames, ignore_index=True)
|
|
93
|
+
draws = pd.concat(draw_frames, ignore_index=True)
|
|
94
|
+
well_key = ["experiment_id", "design_id", "state", "position", "reduction_id"]
|
|
95
|
+
design_key = ["experiment_id", "design_id", "reduction_id"]
|
|
96
|
+
if wells.duplicated(subset=well_key).any():
|
|
97
|
+
raise ValueError(f"{source.experiment_id}: well record identity is not unique.")
|
|
98
|
+
if designs.duplicated(subset=design_key).any():
|
|
99
|
+
raise ValueError(f"{source.experiment_id}: design record identity is not unique.")
|
|
100
|
+
draw_key = ["experiment_id", "design_id", "reduction_id", "draw_index"]
|
|
101
|
+
if draws.duplicated(subset=draw_key).any():
|
|
102
|
+
raise ValueError(f"{source.experiment_id}: descriptive-resampling draw identity is not unique.")
|
|
103
|
+
traces = _trace_record(source, request=request)
|
|
104
|
+
return wells, designs, draws, traces, event_record(source.event), disposition_frame(disposition_records)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _reduce_wells(
|
|
108
|
+
source: ExperimentSource,
|
|
109
|
+
*,
|
|
110
|
+
request: FourStateEventWindowAnalysisSpec,
|
|
111
|
+
reduction: ReductionSpec,
|
|
112
|
+
event_estimate_h: float,
|
|
113
|
+
) -> pd.DataFrame:
|
|
114
|
+
response = _summaries_by_trace(
|
|
115
|
+
source.response,
|
|
116
|
+
request=request,
|
|
117
|
+
reduction=reduction,
|
|
118
|
+
event_estimate_h=event_estimate_h,
|
|
119
|
+
signal_kind="response",
|
|
120
|
+
pre_window_end_h=source.event.interval_start_assay_h,
|
|
121
|
+
)
|
|
122
|
+
magnitude = _summaries_by_trace(
|
|
123
|
+
source.magnitude,
|
|
124
|
+
request=request,
|
|
125
|
+
reduction=reduction,
|
|
126
|
+
event_estimate_h=event_estimate_h,
|
|
127
|
+
signal_kind="magnitude",
|
|
128
|
+
pre_window_end_h=None,
|
|
129
|
+
)
|
|
130
|
+
keys = ["design_id", "state", "position"]
|
|
131
|
+
merged = response.merge(
|
|
132
|
+
magnitude, on=keys, how="outer", validate="one_to_one", suffixes=("_response", "_magnitude")
|
|
133
|
+
)
|
|
134
|
+
if merged.isna().any().any():
|
|
135
|
+
missing = merged.columns[merged.isna().any()].tolist()
|
|
136
|
+
raise ValueError(f"{source.experiment_id}:{reduction.id}: response/magnitude wells do not align: {missing}.")
|
|
137
|
+
merged.insert(0, "experiment_id", source.experiment_id)
|
|
138
|
+
merged["reduction_id"] = reduction.id
|
|
139
|
+
merged["reduction_method"] = reduction.method
|
|
140
|
+
merged["response_basis"] = reduction.response_basis
|
|
141
|
+
merged["reduction_role"] = reduction.role
|
|
142
|
+
merged["event_time_estimate_assay_h"] = float(event_estimate_h)
|
|
143
|
+
merged["window_start_event_h"] = reduction.window_start_event_h
|
|
144
|
+
merged["window_end_event_h"] = reduction.window_end_event_h
|
|
145
|
+
merged["window_start_assay_h"] = event_estimate_h + reduction.window_start_event_h
|
|
146
|
+
merged["window_end_assay_h"] = event_estimate_h + reduction.window_end_event_h
|
|
147
|
+
merged["is_reference"] = merged["design_id"].astype(str).eq(request.source.reference_design_id)
|
|
148
|
+
return merged.sort_values(keys, kind="mergesort").reset_index(drop=True)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _summaries_by_trace(
|
|
152
|
+
frame: pd.DataFrame,
|
|
153
|
+
*,
|
|
154
|
+
request: FourStateEventWindowAnalysisSpec,
|
|
155
|
+
reduction: ReductionSpec,
|
|
156
|
+
event_estimate_h: float,
|
|
157
|
+
signal_kind: str,
|
|
158
|
+
pre_window_end_h: float | None,
|
|
159
|
+
) -> pd.DataFrame:
|
|
160
|
+
rows: list[dict[str, object]] = []
|
|
161
|
+
excluded = {
|
|
162
|
+
(item.design_id, item.state, item.position)
|
|
163
|
+
for item in request.source.well_exclusions
|
|
164
|
+
if item.experiment_id == source_experiment_id(frame) and item.reduction_id == reduction.id
|
|
165
|
+
}
|
|
166
|
+
for (design_id, state, position), trace in frame.groupby(
|
|
167
|
+
["design_id", "state", "position"], sort=True, dropna=False
|
|
168
|
+
):
|
|
169
|
+
if (str(design_id), str(state), str(position)) in excluded:
|
|
170
|
+
continue
|
|
171
|
+
trace_id = f"{frame['experiment_id'].iloc[0]}:{signal_kind}:{design_id}:{state}:{position}:{reduction.id}"
|
|
172
|
+
post = reduce_temporal_trace(
|
|
173
|
+
trace["time"].to_numpy(dtype=float),
|
|
174
|
+
trace["value"].to_numpy(dtype=float),
|
|
175
|
+
spec=four_state_event_window_temporal_spec(reduction, request.quality),
|
|
176
|
+
origin_h=event_estimate_h,
|
|
177
|
+
trace_id=trace_id,
|
|
178
|
+
policy_clipped=trace["value_policy_clipped"].to_numpy(dtype=bool),
|
|
179
|
+
instrument_overflow=trace["value_instrument_overflow"].to_numpy(dtype=bool),
|
|
180
|
+
bound_kinds=trace["value_bound_kind"].to_numpy(dtype=object),
|
|
181
|
+
)
|
|
182
|
+
value = post.value
|
|
183
|
+
policy_clipped_point_count = post.policy_clipped_point_count
|
|
184
|
+
instrument_overflow_point_count = post.instrument_overflow_point_count
|
|
185
|
+
bound_kind = post.bound_kind
|
|
186
|
+
pre_observed_point_count = 0
|
|
187
|
+
pre_integration_point_count = 0
|
|
188
|
+
pre_max_interior_gap_h = 0.0
|
|
189
|
+
if signal_kind == "response" and reduction.response_basis == "post_minus_pre":
|
|
190
|
+
if reduction.pre_window_duration_h is None or pre_window_end_h is None:
|
|
191
|
+
raise ValueError(f"{trace_id}: delta response lacks an explicit pre-event window.")
|
|
192
|
+
pre = reduce_temporal_trace(
|
|
193
|
+
trace["time"].to_numpy(dtype=float),
|
|
194
|
+
trace["value"].to_numpy(dtype=float),
|
|
195
|
+
spec=four_state_event_window_temporal_spec(
|
|
196
|
+
reduction,
|
|
197
|
+
request.quality,
|
|
198
|
+
absolute_window_h=(
|
|
199
|
+
pre_window_end_h - reduction.pre_window_duration_h,
|
|
200
|
+
pre_window_end_h,
|
|
201
|
+
),
|
|
202
|
+
),
|
|
203
|
+
trace_id=f"{trace_id}:pre",
|
|
204
|
+
policy_clipped=trace["value_policy_clipped"].to_numpy(dtype=bool),
|
|
205
|
+
instrument_overflow=trace["value_instrument_overflow"].to_numpy(dtype=bool),
|
|
206
|
+
bound_kinds=trace["value_bound_kind"].to_numpy(dtype=object),
|
|
207
|
+
)
|
|
208
|
+
value -= pre.value
|
|
209
|
+
policy_clipped_point_count += pre.policy_clipped_point_count
|
|
210
|
+
instrument_overflow_point_count += pre.instrument_overflow_point_count
|
|
211
|
+
bound_kind = combine_bound_kinds(bound_kind, invert_bound_kind(pre.bound_kind))
|
|
212
|
+
pre_observed_point_count = pre.observed_point_count
|
|
213
|
+
pre_integration_point_count = pre.evaluation_point_count
|
|
214
|
+
pre_max_interior_gap_h = pre.max_interior_gap_h
|
|
215
|
+
rows.append(
|
|
216
|
+
{
|
|
217
|
+
"design_id": str(design_id),
|
|
218
|
+
"state": str(state),
|
|
219
|
+
"position": str(position),
|
|
220
|
+
f"{signal_kind}_well": float(value),
|
|
221
|
+
f"{signal_kind}_observed_point_count": post.observed_point_count,
|
|
222
|
+
f"{signal_kind}_integration_point_count": post.evaluation_point_count,
|
|
223
|
+
f"{signal_kind}_max_interior_gap_h": post.max_interior_gap_h,
|
|
224
|
+
f"{signal_kind}_pre_observed_point_count": pre_observed_point_count,
|
|
225
|
+
f"{signal_kind}_pre_integration_point_count": pre_integration_point_count,
|
|
226
|
+
f"{signal_kind}_pre_max_interior_gap_h": pre_max_interior_gap_h,
|
|
227
|
+
f"{signal_kind}_policy_clipped_point_count": policy_clipped_point_count,
|
|
228
|
+
f"{signal_kind}_instrument_overflow_point_count": instrument_overflow_point_count,
|
|
229
|
+
f"{signal_kind}_bound_kind": bound_kind,
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
result = pd.DataFrame.from_records(rows)
|
|
233
|
+
if result.empty:
|
|
234
|
+
raise ValueError(f"{signal_kind} reduction produced no well records.")
|
|
235
|
+
return result
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def source_experiment_id(frame: pd.DataFrame) -> str:
|
|
239
|
+
return str(frame["experiment_id"].iloc[0])
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _trace_record(source: ExperimentSource, *, request: FourStateEventWindowAnalysisSpec) -> pd.DataFrame:
|
|
243
|
+
frames: list[pd.DataFrame] = []
|
|
244
|
+
for signal_kind, frame in (
|
|
245
|
+
("response", source.response),
|
|
246
|
+
("magnitude", source.magnitude),
|
|
247
|
+
("growth", source.trajectory),
|
|
248
|
+
):
|
|
249
|
+
selected = frame.loc[
|
|
250
|
+
:,
|
|
251
|
+
[
|
|
252
|
+
"experiment_id",
|
|
253
|
+
"design_id",
|
|
254
|
+
"position",
|
|
255
|
+
"state",
|
|
256
|
+
"time",
|
|
257
|
+
"time_from_event_h",
|
|
258
|
+
"value",
|
|
259
|
+
"value_policy_clipped",
|
|
260
|
+
"value_instrument_overflow",
|
|
261
|
+
"value_bound_kind",
|
|
262
|
+
],
|
|
263
|
+
].copy()
|
|
264
|
+
selected["signal_kind"] = signal_kind
|
|
265
|
+
selected["is_reference"] = selected["design_id"].astype(str).eq(request.source.reference_design_id)
|
|
266
|
+
frames.append(selected)
|
|
267
|
+
result = pd.concat(frames, ignore_index=True)
|
|
268
|
+
key = ["experiment_id", "design_id", "position", "state", "signal_kind", "time"]
|
|
269
|
+
if result.duplicated(subset=key).any():
|
|
270
|
+
raise ValueError(f"{source.experiment_id}: trace record identity is not unique.")
|
|
271
|
+
return result.sort_values(key, kind="mergesort").reset_index(drop=True)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
__all__ = ["materialize_experiment"]
|
reader_workbench/domains/plate_reader/analysis/four_state_event_window/observation_resampling.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Descriptive resampling primitives for within-experiment observations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from .contracts import FourStateEventWindowAnalysisSpec, ObservationStat
|
|
9
|
+
from .seeds import stable_seed
|
|
10
|
+
from .sources import STATE_ORDER
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def descriptive_resampling_records(wells: pd.DataFrame, *, request: FourStateEventWindowAnalysisSpec) -> pd.DataFrame:
|
|
14
|
+
"""Resample observed wells jointly without implying population inference."""
|
|
15
|
+
|
|
16
|
+
anchor_id = request.source.reference_design_id
|
|
17
|
+
anchor = wells.loc[wells["design_id"].astype(str).eq(anchor_id)]
|
|
18
|
+
records: list[pd.DataFrame] = []
|
|
19
|
+
for design_id, design_wells in wells.groupby("design_id", sort=True):
|
|
20
|
+
state_draws: dict[str, np.ndarray] = {}
|
|
21
|
+
for state in STATE_ORDER:
|
|
22
|
+
state_wells = design_wells.loc[design_wells["state"].astype(str).eq(state)]
|
|
23
|
+
anchor_wells = anchor.loc[anchor["state"].astype(str).eq(state)]
|
|
24
|
+
response_values = state_wells["response_well"].to_numpy(dtype=float)
|
|
25
|
+
magnitude_values = state_wells["magnitude_well"].to_numpy(dtype=float)
|
|
26
|
+
anchor_values = anchor_wells["magnitude_well"].to_numpy(dtype=float)
|
|
27
|
+
minimum = request.quality.min_observations_per_state
|
|
28
|
+
if min(len(response_values), len(magnitude_values), len(anchor_values)) < minimum:
|
|
29
|
+
raise ValueError(f"{design_id}:{state} lacks support for within-experiment descriptive resampling.")
|
|
30
|
+
reduction_id = str(state_wells["reduction_id"].iloc[0])
|
|
31
|
+
experiment_id = str(state_wells["experiment_id"].iloc[0])
|
|
32
|
+
rng = np.random.default_rng(
|
|
33
|
+
stable_seed(request.aggregation.random_seed, experiment_id, str(design_id), state, reduction_id)
|
|
34
|
+
)
|
|
35
|
+
response_draws, anchored_magnitude_draws = joint_state_descriptive_resampling_draws(
|
|
36
|
+
response_values,
|
|
37
|
+
magnitude_values,
|
|
38
|
+
anchor_values,
|
|
39
|
+
samples=request.aggregation.descriptive_resampling_draws,
|
|
40
|
+
stat=request.aggregation.observation_stat,
|
|
41
|
+
rng=rng,
|
|
42
|
+
paired_anchor=str(design_id) == anchor_id,
|
|
43
|
+
)
|
|
44
|
+
state_draws[f"r{state}"] = response_draws
|
|
45
|
+
state_draws[f"b{state}"] = anchored_magnitude_draws
|
|
46
|
+
frame = pd.DataFrame(state_draws)
|
|
47
|
+
frame.insert(0, "draw_index", np.arange(len(frame), dtype=int))
|
|
48
|
+
frame.insert(0, "reduction_id", str(design_wells["reduction_id"].iloc[0]))
|
|
49
|
+
frame.insert(0, "design_id", str(design_id))
|
|
50
|
+
frame.insert(0, "experiment_id", str(design_wells["experiment_id"].iloc[0]))
|
|
51
|
+
frame["is_reference"] = str(design_id) == anchor_id
|
|
52
|
+
records.append(frame)
|
|
53
|
+
return pd.concat(records, ignore_index=True)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def joint_state_descriptive_resampling_draws(
|
|
57
|
+
response_values: np.ndarray,
|
|
58
|
+
magnitude_values: np.ndarray,
|
|
59
|
+
anchor_values: np.ndarray,
|
|
60
|
+
*,
|
|
61
|
+
samples: int,
|
|
62
|
+
stat: ObservationStat,
|
|
63
|
+
rng: np.random.Generator,
|
|
64
|
+
paired_anchor: bool = False,
|
|
65
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
66
|
+
"""Resample paired observed wells and either paired or independent reference wells."""
|
|
67
|
+
|
|
68
|
+
response = np.asarray(response_values, dtype=float)
|
|
69
|
+
magnitude = np.asarray(magnitude_values, dtype=float)
|
|
70
|
+
anchor = np.asarray(anchor_values, dtype=float)
|
|
71
|
+
if stat not in {"mean", "median"}:
|
|
72
|
+
raise ValueError("joint descriptive resampling requires stat to be 'mean' or 'median'.")
|
|
73
|
+
if response.ndim != 1 or magnitude.ndim != 1 or anchor.ndim != 1:
|
|
74
|
+
raise ValueError("joint descriptive-resampling inputs must be one-dimensional arrays.")
|
|
75
|
+
if len(response) != len(magnitude) or len(response) == 0 or len(anchor) == 0:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
"joint descriptive resampling requires aligned design observations and non-empty reference observations."
|
|
78
|
+
)
|
|
79
|
+
if samples < 2:
|
|
80
|
+
raise ValueError("joint descriptive resampling requires at least two draws.")
|
|
81
|
+
if paired_anchor and not np.array_equal(magnitude, anchor):
|
|
82
|
+
raise ValueError(
|
|
83
|
+
"paired reference descriptive resampling requires identical ordered magnitude and anchor observations."
|
|
84
|
+
)
|
|
85
|
+
design_indexes = rng.integers(0, len(response), size=(samples, len(response)))
|
|
86
|
+
anchor_indexes = design_indexes if paired_anchor else rng.integers(0, len(anchor), size=(samples, len(anchor)))
|
|
87
|
+
aggregate = np.median if stat == "median" else np.mean
|
|
88
|
+
response_draws = aggregate(response[design_indexes], axis=1)
|
|
89
|
+
magnitude_draws = aggregate(magnitude[design_indexes], axis=1)
|
|
90
|
+
anchor_draws = aggregate(anchor[anchor_indexes], axis=1)
|
|
91
|
+
return response_draws, magnitude_draws - anchor_draws
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
__all__ = [
|
|
95
|
+
"descriptive_resampling_records",
|
|
96
|
+
"joint_state_descriptive_resampling_draws",
|
|
97
|
+
]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Four-state event-window adapter to the assay-neutral temporal reducer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from reader_workbench.domains.time_series import (
|
|
6
|
+
IntervalSelection,
|
|
7
|
+
TemporalReductionSpec,
|
|
8
|
+
TemporalSupportPolicy,
|
|
9
|
+
combine_bound_kinds,
|
|
10
|
+
invert_bound_kind,
|
|
11
|
+
reduce_temporal_trace,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
from .contracts import QualitySpec, ReductionSpec
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def four_state_event_window_temporal_spec(
|
|
18
|
+
reduction: ReductionSpec,
|
|
19
|
+
quality: QualitySpec,
|
|
20
|
+
*,
|
|
21
|
+
absolute_window_h: tuple[float, float] | None = None,
|
|
22
|
+
) -> TemporalReductionSpec:
|
|
23
|
+
"""Project four-state event-window policy into the neutral temporal contract.
|
|
24
|
+
|
|
25
|
+
Four-state event-window reductions remain event-relative unless an explicit
|
|
26
|
+
absolute pre-event interval is supplied. The projection preserves the
|
|
27
|
+
established interpolation, log2 output, support-gap, and censor behavior.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
if absolute_window_h is None:
|
|
31
|
+
selection = IntervalSelection(
|
|
32
|
+
time_basis="event_relative",
|
|
33
|
+
start_h=reduction.window_start_event_h,
|
|
34
|
+
end_h=reduction.window_end_event_h,
|
|
35
|
+
)
|
|
36
|
+
else:
|
|
37
|
+
selection = IntervalSelection(
|
|
38
|
+
time_basis="absolute",
|
|
39
|
+
start_h=float(absolute_window_h[0]),
|
|
40
|
+
end_h=float(absolute_window_h[1]),
|
|
41
|
+
)
|
|
42
|
+
return TemporalReductionSpec(
|
|
43
|
+
selection=selection,
|
|
44
|
+
method=reduction.method,
|
|
45
|
+
output_space="log2",
|
|
46
|
+
support=TemporalSupportPolicy(
|
|
47
|
+
boundary_support="covered",
|
|
48
|
+
minimum_observations=0,
|
|
49
|
+
maximum_interior_gap_h=quality.max_interior_gap_h,
|
|
50
|
+
positive_floor=quality.positive_floor,
|
|
51
|
+
positive_value_scope="entire_trace",
|
|
52
|
+
censored_values="allow",
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"combine_bound_kinds",
|
|
59
|
+
"invert_bound_kind",
|
|
60
|
+
"reduce_temporal_trace",
|
|
61
|
+
"four_state_event_window_temporal_spec",
|
|
62
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Deterministic seed derivation for four-state event-window resampling."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def stable_seed(base: int, *parts: str) -> int:
|
|
9
|
+
"""Derive a reproducible unsigned seed from an explicit identity tuple."""
|
|
10
|
+
|
|
11
|
+
digest = hashlib.sha256((str(base) + ":" + ":".join(parts)).encode()).digest()
|
|
12
|
+
return int.from_bytes(digest[:8], "little")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__all__ = ["stable_seed"]
|