scitex 2.3.0__py3-none-any.whl → 2.4.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.
- scitex/ai/classification/reporters/reporter_utils/_Plotter.py +1 -1
- scitex/ai/plt/__init__.py +2 -2
- scitex/ai/plt/{_plot_conf_mat.py → _stx_conf_mat.py} +3 -3
- scitex/config/PriorityConfig.py +195 -0
- scitex/config/__init__.py +24 -0
- scitex/io/_save.py +125 -34
- scitex/io/_save_modules/_image.py +37 -20
- scitex/plt/__init__.py +470 -17
- scitex/plt/_subplots/_AxisWrapper.py +98 -50
- scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +254 -124
- scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +49 -8
- scitex/plt/_subplots/_SubplotsWrapper.py +76 -91
- scitex/plt/_subplots/_export_as_csv.py +127 -58
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +25 -16
- scitex/plt/_subplots/_export_as_csv_formatters/_format_contourf.py +54 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_hexbin.py +41 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_hist2d.py +41 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow.py +59 -47
- scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +42 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pie.py +42 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +72 -35
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_box.py +1 -1
- scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +53 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stem.py +42 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_step.py +42 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +48 -0
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_conf_mat.py → _format_stx_conf_mat.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_ecdf.py → _format_stx_ecdf.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_fillv.py → _format_stx_fillv.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_heatmap.py → _format_stx_heatmap.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_image.py → _format_stx_image.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_joyplot.py → _format_stx_joyplot.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_line.py → _format_stx_line.py} +3 -3
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_mean_ci.py → _format_stx_mean_ci.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_mean_std.py → _format_stx_mean_std.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_median_iqr.py → _format_stx_median_iqr.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_raster.py → _format_stx_raster.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_rectangle.py → _format_stx_rectangle.py} +1 -1
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_scatter_hist.py → _format_stx_scatter_hist.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_shaded_line.py → _format_stx_shaded_line.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_violin.py → _format_stx_violin.py} +2 -2
- scitex/plt/_subplots/_export_as_csv_formatters/verify_formatters.py +23 -23
- scitex/plt/ax/__init__.py +16 -15
- scitex/plt/ax/_plot/__init__.py +30 -30
- scitex/plt/ax/_plot/_add_fitted_line.py +65 -11
- scitex/plt/ax/_plot/_plot_statistical_shaded_line.py +104 -76
- scitex/plt/ax/_plot/{_plot_conf_mat.py → _stx_conf_mat.py} +10 -10
- scitex/plt/ax/_plot/_stx_ecdf.py +109 -0
- scitex/plt/ax/_plot/{_plot_fillv.py → _stx_fillv.py} +7 -7
- scitex/plt/ax/_plot/_stx_heatmap.py +366 -0
- scitex/plt/ax/_plot/{_plot_image.py → _stx_image.py} +1 -1
- scitex/plt/ax/_plot/_stx_joyplot.py +113 -0
- scitex/plt/ax/_plot/{_plot_raster.py → _stx_raster.py} +37 -25
- scitex/plt/ax/_plot/{_plot_rectangle.py → _stx_rectangle.py} +10 -9
- scitex/plt/ax/_plot/{_plot_scatter_hist.py → _stx_scatter_hist.py} +1 -1
- scitex/plt/ax/_plot/_stx_shaded_line.py +215 -0
- scitex/plt/ax/_plot/{_plot_violin.py → _stx_violin.py} +13 -6
- scitex/plt/ax/_style/__init__.py +3 -0
- scitex/plt/ax/_style/_style_barplot.py +13 -2
- scitex/plt/ax/_style/_style_boxplot.py +78 -32
- scitex/plt/ax/_style/_style_errorbar.py +17 -3
- scitex/plt/ax/_style/_style_scatter.py +17 -3
- scitex/plt/ax/_style/_style_violinplot.py +109 -0
- scitex/plt/color/_vizualize_colors.py +3 -3
- scitex/plt/styles/SCITEX_STYLE.yaml +104 -0
- scitex/plt/styles/__init__.py +57 -0
- scitex/plt/styles/_plot_defaults.py +209 -0
- scitex/plt/styles/_plot_postprocess.py +518 -0
- scitex/plt/styles/_style_loader.py +268 -0
- scitex/plt/styles/presets.py +208 -0
- scitex/plt/utils/_collect_figure_metadata.py +160 -18
- scitex/plt/utils/_colorbar.py +72 -10
- scitex/plt/utils/_configure_mpl.py +108 -52
- scitex/plt/utils/_crop.py +21 -7
- scitex/plt/utils/_figure_mm.py +21 -7
- scitex/stats/__init__.py +13 -1
- scitex/stats/_schema.py +578 -0
- scitex/stats/tests/__init__.py +13 -0
- scitex/stats/tests/correlation/__init__.py +13 -0
- scitex/stats/tests/correlation/_test_pearson.py +262 -0
- scitex/vis/__init__.py +6 -0
- scitex/vis/editor/__init__.py +23 -0
- scitex/vis/editor/_defaults.py +205 -0
- scitex/vis/editor/_edit.py +342 -0
- scitex/vis/editor/_mpl_editor.py +231 -0
- scitex/vis/editor/_tkinter_editor.py +466 -0
- scitex/vis/editor/_web_editor.py +1440 -0
- scitex/vis/model/plot_types.py +15 -15
- {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/METADATA +2 -1
- {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/RECORD +94 -67
- {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/WHEEL +1 -1
- scitex/plt/ax/_plot/_plot_ecdf.py +0 -84
- scitex/plt/ax/_plot/_plot_heatmap.py +0 -277
- scitex/plt/ax/_plot/_plot_joyplot.py +0 -77
- scitex/plt/ax/_plot/_plot_shaded_line.py +0 -142
- scitex/plt/presets.py +0 -224
- {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/licenses/LICENSE +0 -0
scitex/plt/utils/_figure_mm.py
CHANGED
|
@@ -193,6 +193,7 @@ def apply_style_mm(ax: Axes, style: Dict) -> float:
|
|
|
193
193
|
- 'tick_length_mm' (float): Tick mark length in mm (default: 0.8)
|
|
194
194
|
- 'tick_thickness_mm' (float): Tick mark width in mm (default: 0.2)
|
|
195
195
|
- 'tick_cap_width_mm' (float): Tick cap width in mm (default: 0.8)
|
|
196
|
+
- 'marker_size_mm' (float): Default marker size in mm (default: 0.8)
|
|
196
197
|
- 'axis_font_size_pt' (float): Axis label font size in points (default: 8)
|
|
197
198
|
- 'tick_font_size_pt' (float): Tick label font size in points (default: 7)
|
|
198
199
|
- 'n_ticks' (int): Number of ticks on each axis (default: 4)
|
|
@@ -231,6 +232,14 @@ def apply_style_mm(ax: Axes, style: Dict) -> float:
|
|
|
231
232
|
# Convert trace thickness from mm to points
|
|
232
233
|
trace_lw_pt = mm_to_pt(style.get("trace_thickness_mm", 0.12))
|
|
233
234
|
|
|
235
|
+
# Convert marker size from mm to points and set as default
|
|
236
|
+
# Marker size in matplotlib is specified in points
|
|
237
|
+
marker_size_mm = style.get("marker_size_mm")
|
|
238
|
+
if marker_size_mm is not None:
|
|
239
|
+
marker_size_pt = mm_to_pt(marker_size_mm)
|
|
240
|
+
import matplotlib as mpl
|
|
241
|
+
mpl.rcParams["lines.markersize"] = marker_size_pt
|
|
242
|
+
|
|
234
243
|
# Configure tick parameters (all mm values converted to points)
|
|
235
244
|
# width = tick line thickness, length = tick line length
|
|
236
245
|
# pad = distance between ticks and tick labels (Nature-style: 1.5pt)
|
|
@@ -282,13 +291,18 @@ def apply_style_mm(ax: Axes, style: Dict) -> float:
|
|
|
282
291
|
# Disable grids by default
|
|
283
292
|
ax.grid(False)
|
|
284
293
|
|
|
285
|
-
#
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
294
|
+
# Ensure axes spines are in front of plot elements (e.g., histogram bars)
|
|
295
|
+
# Set high zorder on spines so they appear on top
|
|
296
|
+
for spine in ax.spines.values():
|
|
297
|
+
spine.set_zorder(1000)
|
|
298
|
+
# Also ensure ticks are on top
|
|
299
|
+
ax.tick_params(zorder=1000)
|
|
300
|
+
|
|
301
|
+
# Note: n_ticks is NOT applied here at figure creation time
|
|
302
|
+
# because we don't know yet if axes will be categorical or numerical.
|
|
303
|
+
# MaxNLocator will be applied in post-processing after plotting,
|
|
304
|
+
# only to numerical (non-categorical) axes.
|
|
305
|
+
# See _AxisWrapper.py for the implementation.
|
|
292
306
|
|
|
293
307
|
# Return trace linewidth for use in plotting
|
|
294
308
|
return trace_lw_pt
|
scitex/stats/__init__.py
CHANGED
|
@@ -8,7 +8,9 @@ Organized submodules:
|
|
|
8
8
|
- correct: 3 multiple comparison correction methods
|
|
9
9
|
- effect_sizes: Effect size computations and interpretations
|
|
10
10
|
- power: Statistical power analysis
|
|
11
|
+
- tests: Statistical tests (correlation, etc.)
|
|
11
12
|
- utils: Formatters and normalizers (for backward compatibility)
|
|
13
|
+
- _schema: Core StatResult schema for standardized test results
|
|
12
14
|
"""
|
|
13
15
|
|
|
14
16
|
# Import new organized submodules
|
|
@@ -18,9 +20,12 @@ from . import power
|
|
|
18
20
|
from . import utils
|
|
19
21
|
from . import posthoc
|
|
20
22
|
from . import descriptive
|
|
23
|
+
from . import tests
|
|
24
|
+
from . import _schema
|
|
21
25
|
|
|
22
|
-
# Export commonly used functions for convenience
|
|
26
|
+
# Export commonly used functions and classes for convenience
|
|
23
27
|
from .descriptive import describe
|
|
28
|
+
from ._schema import StatResult, Position, StatStyling, StatPositioning, create_stat_result
|
|
24
29
|
|
|
25
30
|
__all__ = [
|
|
26
31
|
# Main submodules
|
|
@@ -30,8 +35,15 @@ __all__ = [
|
|
|
30
35
|
"utils",
|
|
31
36
|
"posthoc",
|
|
32
37
|
"descriptive",
|
|
38
|
+
"tests",
|
|
39
|
+
"_schema",
|
|
33
40
|
# Convenience exports
|
|
34
41
|
"describe",
|
|
42
|
+
"StatResult",
|
|
43
|
+
"Position",
|
|
44
|
+
"StatStyling",
|
|
45
|
+
"StatPositioning",
|
|
46
|
+
"create_stat_result",
|
|
35
47
|
]
|
|
36
48
|
|
|
37
49
|
__version__ = "2.0.0"
|
scitex/stats/_schema.py
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
SciTeX Stats Result Schema - GUI-Ready Statistical Result Container
|
|
4
|
+
|
|
5
|
+
Standardized statistical test result format that integrates with:
|
|
6
|
+
- scitex.plt (TrackingMixin, metadata embedding)
|
|
7
|
+
- scitex.vis (JSON serialization, FigureModel)
|
|
8
|
+
- scitex-cloud GUI (Fabric.js canvas, properties panel, positioning)
|
|
9
|
+
|
|
10
|
+
This schema supports multiple coordinate systems, units, and position modes
|
|
11
|
+
to enable GUI-based adjustment while maintaining publication-ready output.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass, field, asdict
|
|
15
|
+
from typing import Optional, Dict, Any, List, Literal, Union
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
import json
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Type aliases for clarity
|
|
22
|
+
PositionMode = Literal["absolute", "relative_to_plot", "above_whisker", "auto"]
|
|
23
|
+
UnitType = Literal["mm", "px", "inch", "data"]
|
|
24
|
+
SymbolStyle = Literal["asterisk", "text", "bracket", "compact", "detailed", "publication"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class Position:
|
|
29
|
+
"""Position specification with unit support for GUI integration."""
|
|
30
|
+
|
|
31
|
+
x: float
|
|
32
|
+
y: float
|
|
33
|
+
unit: UnitType = "mm"
|
|
34
|
+
|
|
35
|
+
# For relative positioning (GUI anchoring)
|
|
36
|
+
relative_to: Optional[str] = None # Plot ID or "axes"
|
|
37
|
+
offset: Optional[Dict[str, float]] = None # {"dx": 0, "dy": 0}
|
|
38
|
+
|
|
39
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
40
|
+
"""Convert to dictionary."""
|
|
41
|
+
return {
|
|
42
|
+
"x": self.x,
|
|
43
|
+
"y": self.y,
|
|
44
|
+
"unit": self.unit,
|
|
45
|
+
"relative_to": self.relative_to,
|
|
46
|
+
"offset": self.offset,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'Position':
|
|
51
|
+
"""Create from dictionary."""
|
|
52
|
+
return cls(**data)
|
|
53
|
+
|
|
54
|
+
def to_mm(self, dpi: float = 300.0) -> 'Position':
|
|
55
|
+
"""Convert position to mm (for matplotlib)."""
|
|
56
|
+
if self.unit == "mm":
|
|
57
|
+
return self
|
|
58
|
+
elif self.unit == "px":
|
|
59
|
+
# Convert px to mm using DPI
|
|
60
|
+
mm_per_px = 25.4 / dpi
|
|
61
|
+
return Position(
|
|
62
|
+
x=self.x * mm_per_px,
|
|
63
|
+
y=self.y * mm_per_px,
|
|
64
|
+
unit="mm",
|
|
65
|
+
relative_to=self.relative_to,
|
|
66
|
+
offset=self.offset
|
|
67
|
+
)
|
|
68
|
+
elif self.unit == "inch":
|
|
69
|
+
# Convert inch to mm
|
|
70
|
+
return Position(
|
|
71
|
+
x=self.x * 25.4,
|
|
72
|
+
y=self.y * 25.4,
|
|
73
|
+
unit="mm",
|
|
74
|
+
relative_to=self.relative_to,
|
|
75
|
+
offset=self.offset
|
|
76
|
+
)
|
|
77
|
+
return self
|
|
78
|
+
|
|
79
|
+
def to_px(self, dpi: float = 300.0) -> 'Position':
|
|
80
|
+
"""Convert position to px (for Fabric.js canvas)."""
|
|
81
|
+
mm_pos = self.to_mm(dpi)
|
|
82
|
+
px_per_mm = dpi / 25.4
|
|
83
|
+
return Position(
|
|
84
|
+
x=mm_pos.x * px_per_mm,
|
|
85
|
+
y=mm_pos.y * px_per_mm,
|
|
86
|
+
unit="px",
|
|
87
|
+
relative_to=self.relative_to,
|
|
88
|
+
offset=self.offset
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class StatStyling:
|
|
94
|
+
"""Styling configuration for statistical annotation display."""
|
|
95
|
+
|
|
96
|
+
# Typography
|
|
97
|
+
font_size_pt: float = 7.0
|
|
98
|
+
font_family: str = "Arial"
|
|
99
|
+
color: str = "#000000" # Supports theme colors
|
|
100
|
+
|
|
101
|
+
# Symbol representation
|
|
102
|
+
symbol_style: SymbolStyle = "asterisk"
|
|
103
|
+
|
|
104
|
+
# For bracket-style comparisons
|
|
105
|
+
line_width_mm: Optional[float] = None
|
|
106
|
+
bracket_height_mm: Optional[float] = None
|
|
107
|
+
|
|
108
|
+
# Theme support
|
|
109
|
+
theme: Literal["light", "dark", "auto"] = "auto"
|
|
110
|
+
|
|
111
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
112
|
+
"""Convert to dictionary."""
|
|
113
|
+
return asdict(self)
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'StatStyling':
|
|
117
|
+
"""Create from dictionary."""
|
|
118
|
+
return cls(**data)
|
|
119
|
+
|
|
120
|
+
def get_theme_color(self, is_dark: bool = False) -> str:
|
|
121
|
+
"""Get appropriate color for theme."""
|
|
122
|
+
if self.theme == "auto":
|
|
123
|
+
return "#ffffff" if is_dark else "#000000"
|
|
124
|
+
elif self.theme == "dark":
|
|
125
|
+
return "#ffffff"
|
|
126
|
+
elif self.theme == "light":
|
|
127
|
+
return "#000000"
|
|
128
|
+
return self.color
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass
|
|
132
|
+
class StatPositioning:
|
|
133
|
+
"""Position configuration for GUI-ready annotation placement."""
|
|
134
|
+
|
|
135
|
+
mode: PositionMode = "auto"
|
|
136
|
+
position: Optional[Position] = None
|
|
137
|
+
|
|
138
|
+
# GUI hints for smart positioning
|
|
139
|
+
preferred_corner: Optional[str] = None # "top-right", "bottom-left", etc.
|
|
140
|
+
avoid_overlap: bool = True
|
|
141
|
+
min_distance_mm: float = 2.0 # Minimum distance from plot elements
|
|
142
|
+
|
|
143
|
+
# Anchoring (for relative positioning)
|
|
144
|
+
anchor_to: Optional[str] = None # "plot_center", "whisker_top", etc.
|
|
145
|
+
|
|
146
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
147
|
+
"""Convert to dictionary."""
|
|
148
|
+
return {
|
|
149
|
+
"mode": self.mode,
|
|
150
|
+
"position": self.position.to_dict() if self.position else None,
|
|
151
|
+
"preferred_corner": self.preferred_corner,
|
|
152
|
+
"avoid_overlap": self.avoid_overlap,
|
|
153
|
+
"min_distance_mm": self.min_distance_mm,
|
|
154
|
+
"anchor_to": self.anchor_to,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'StatPositioning':
|
|
159
|
+
"""Create from dictionary."""
|
|
160
|
+
data_copy = data.copy()
|
|
161
|
+
if data_copy.get("position"):
|
|
162
|
+
data_copy["position"] = Position.from_dict(data_copy["position"])
|
|
163
|
+
return cls(**data_copy)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@dataclass
|
|
167
|
+
class StatResult:
|
|
168
|
+
"""
|
|
169
|
+
Standardized statistical test result with GUI-ready metadata.
|
|
170
|
+
|
|
171
|
+
This schema integrates with:
|
|
172
|
+
- scitex.plt: Automatic annotation via TrackingMixin
|
|
173
|
+
- scitex.vis: JSON serialization for FigureModel
|
|
174
|
+
- scitex-cloud GUI: Fabric.js canvas positioning and properties panel
|
|
175
|
+
|
|
176
|
+
Examples
|
|
177
|
+
--------
|
|
178
|
+
>>> # Create from test result
|
|
179
|
+
>>> result = StatResult(
|
|
180
|
+
... test_type="pearson",
|
|
181
|
+
... test_category="correlation",
|
|
182
|
+
... statistic={"name": "r", "value": 0.85},
|
|
183
|
+
... p_value=0.001,
|
|
184
|
+
... stars="***"
|
|
185
|
+
... )
|
|
186
|
+
|
|
187
|
+
>>> # Add positioning for GUI
|
|
188
|
+
>>> result.positioning = StatPositioning(
|
|
189
|
+
... mode="relative_to_plot",
|
|
190
|
+
... position=Position(x=5, y=10, unit="mm", relative_to="plot_0")
|
|
191
|
+
... )
|
|
192
|
+
|
|
193
|
+
>>> # Export to JSON
|
|
194
|
+
>>> json_str = result.to_json()
|
|
195
|
+
|
|
196
|
+
>>> # Format for display
|
|
197
|
+
>>> text = result.format_text(style="compact")
|
|
198
|
+
>>> # "r = 0.850***"
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
# Core test information
|
|
202
|
+
test_type: str # "t-test", "pearson", "anova", etc.
|
|
203
|
+
test_category: str # "parametric", "non-parametric", "correlation"
|
|
204
|
+
|
|
205
|
+
# Primary results
|
|
206
|
+
statistic: Dict[str, Union[float, str]] # {"name": "t", "value": 3.45}
|
|
207
|
+
p_value: float
|
|
208
|
+
stars: str # "***", "**", "*", "ns", ""
|
|
209
|
+
|
|
210
|
+
# Effect size (optional)
|
|
211
|
+
effect_size: Optional[Dict[str, Any]] = None
|
|
212
|
+
# Example: {"name": "cohens_d", "value": 0.85,
|
|
213
|
+
# "interpretation": "large", "ci_95": [0.42, 1.28]}
|
|
214
|
+
|
|
215
|
+
# Multiple comparison correction (optional)
|
|
216
|
+
correction: Optional[Dict[str, Any]] = None
|
|
217
|
+
# Example: {"method": "bonferroni", "n_comparisons": 10,
|
|
218
|
+
# "corrected_p": 0.010, "alpha": 0.05}
|
|
219
|
+
|
|
220
|
+
# Sample information
|
|
221
|
+
samples: Optional[Dict[str, Any]] = None
|
|
222
|
+
# Example: {"group1": {"n": 30, "mean": 5.2, "std": 1.1},
|
|
223
|
+
# "group2": {"n": 32, "mean": 6.8, "std": 1.3}}
|
|
224
|
+
|
|
225
|
+
# Statistical assumptions testing
|
|
226
|
+
assumptions: Optional[Dict[str, Dict]] = None
|
|
227
|
+
# Example: {"normality": {"test": "shapiro", "passed": True, "p": 0.23},
|
|
228
|
+
# "homogeneity": {"test": "levene", "passed": True, "p": 0.45}}
|
|
229
|
+
|
|
230
|
+
# Confidence intervals
|
|
231
|
+
ci_95: Optional[List[float]] = None
|
|
232
|
+
# Example: [0.42, 1.28] for effect size or correlation
|
|
233
|
+
|
|
234
|
+
# GUI-ready positioning
|
|
235
|
+
positioning: Optional[StatPositioning] = None
|
|
236
|
+
|
|
237
|
+
# Display styling
|
|
238
|
+
styling: Optional[StatStyling] = None
|
|
239
|
+
|
|
240
|
+
# Additional test-specific data
|
|
241
|
+
extra: Optional[Dict[str, Any]] = None
|
|
242
|
+
# For test-specific outputs (degrees of freedom, test alternatives, etc.)
|
|
243
|
+
|
|
244
|
+
# Metadata
|
|
245
|
+
created_at: Optional[str] = None
|
|
246
|
+
software_version: Optional[str] = None
|
|
247
|
+
plot_id: Optional[str] = None # Associated plot in TrackingMixin
|
|
248
|
+
|
|
249
|
+
def __post_init__(self):
|
|
250
|
+
"""Initialize default values."""
|
|
251
|
+
if self.created_at is None:
|
|
252
|
+
self.created_at = datetime.now().isoformat()
|
|
253
|
+
|
|
254
|
+
if self.styling is None:
|
|
255
|
+
self.styling = StatStyling()
|
|
256
|
+
|
|
257
|
+
if self.positioning is None:
|
|
258
|
+
self.positioning = StatPositioning()
|
|
259
|
+
|
|
260
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
261
|
+
"""
|
|
262
|
+
Convert to dictionary for JSON serialization.
|
|
263
|
+
|
|
264
|
+
Returns
|
|
265
|
+
-------
|
|
266
|
+
dict
|
|
267
|
+
Dictionary representation with nested objects converted
|
|
268
|
+
"""
|
|
269
|
+
data = asdict(self)
|
|
270
|
+
|
|
271
|
+
# Convert numpy types to native Python types
|
|
272
|
+
def convert_numpy(obj):
|
|
273
|
+
if isinstance(obj, np.integer):
|
|
274
|
+
return int(obj)
|
|
275
|
+
elif isinstance(obj, np.floating):
|
|
276
|
+
return float(obj)
|
|
277
|
+
elif isinstance(obj, np.ndarray):
|
|
278
|
+
return obj.tolist()
|
|
279
|
+
elif isinstance(obj, dict):
|
|
280
|
+
return {k: convert_numpy(v) for k, v in obj.items()}
|
|
281
|
+
elif isinstance(obj, list):
|
|
282
|
+
return [convert_numpy(item) for item in obj]
|
|
283
|
+
return obj
|
|
284
|
+
|
|
285
|
+
return convert_numpy(data)
|
|
286
|
+
|
|
287
|
+
def to_json(self, indent: int = 2) -> str:
|
|
288
|
+
"""
|
|
289
|
+
Convert to JSON string.
|
|
290
|
+
|
|
291
|
+
Parameters
|
|
292
|
+
----------
|
|
293
|
+
indent : int
|
|
294
|
+
Number of spaces for indentation (default: 2)
|
|
295
|
+
|
|
296
|
+
Returns
|
|
297
|
+
-------
|
|
298
|
+
str
|
|
299
|
+
JSON string representation
|
|
300
|
+
"""
|
|
301
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
302
|
+
|
|
303
|
+
@classmethod
|
|
304
|
+
def from_dict(cls, data: Dict[str, Any]) -> 'StatResult':
|
|
305
|
+
"""
|
|
306
|
+
Create from dictionary.
|
|
307
|
+
|
|
308
|
+
Parameters
|
|
309
|
+
----------
|
|
310
|
+
data : dict
|
|
311
|
+
Dictionary representation
|
|
312
|
+
|
|
313
|
+
Returns
|
|
314
|
+
-------
|
|
315
|
+
StatResult
|
|
316
|
+
StatResult instance
|
|
317
|
+
"""
|
|
318
|
+
data_copy = data.copy()
|
|
319
|
+
|
|
320
|
+
# Convert nested objects
|
|
321
|
+
if "positioning" in data_copy and data_copy["positioning"]:
|
|
322
|
+
data_copy["positioning"] = StatPositioning.from_dict(
|
|
323
|
+
data_copy["positioning"]
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
if "styling" in data_copy and data_copy["styling"]:
|
|
327
|
+
data_copy["styling"] = StatStyling.from_dict(data_copy["styling"])
|
|
328
|
+
|
|
329
|
+
return cls(**data_copy)
|
|
330
|
+
|
|
331
|
+
@classmethod
|
|
332
|
+
def from_json(cls, json_str: str) -> 'StatResult':
|
|
333
|
+
"""
|
|
334
|
+
Create from JSON string.
|
|
335
|
+
|
|
336
|
+
Parameters
|
|
337
|
+
----------
|
|
338
|
+
json_str : str
|
|
339
|
+
JSON string representation
|
|
340
|
+
|
|
341
|
+
Returns
|
|
342
|
+
-------
|
|
343
|
+
StatResult
|
|
344
|
+
StatResult instance
|
|
345
|
+
"""
|
|
346
|
+
return cls.from_dict(json.loads(json_str))
|
|
347
|
+
|
|
348
|
+
def format_text(self, style: str = "compact") -> str:
|
|
349
|
+
"""
|
|
350
|
+
Format statistical result as text for display.
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
style : str
|
|
355
|
+
Formatting style. Options:
|
|
356
|
+
- "compact": "r = 0.850***"
|
|
357
|
+
- "asterisk": "***" (stars only)
|
|
358
|
+
- "text": "p = 0.001"
|
|
359
|
+
- "detailed": "r = 0.850, p = 1.000e-03, d = 1.23"
|
|
360
|
+
- "publication": "(r = 0.85, p < 0.001)"
|
|
361
|
+
- "bracket": For bracket-style display (returns empty string)
|
|
362
|
+
|
|
363
|
+
Returns
|
|
364
|
+
-------
|
|
365
|
+
str
|
|
366
|
+
Formatted text
|
|
367
|
+
|
|
368
|
+
Examples
|
|
369
|
+
--------
|
|
370
|
+
>>> result.format_text("compact")
|
|
371
|
+
'r = 0.850***'
|
|
372
|
+
|
|
373
|
+
>>> result.format_text("publication")
|
|
374
|
+
'(r = 0.85, p < 0.001)'
|
|
375
|
+
"""
|
|
376
|
+
stat_name = self.statistic.get("name", "stat")
|
|
377
|
+
stat_value = self.statistic.get("value", 0.0)
|
|
378
|
+
|
|
379
|
+
if style == "compact":
|
|
380
|
+
return f"{stat_name} = {stat_value:.3f}{self.stars}"
|
|
381
|
+
|
|
382
|
+
elif style == "asterisk":
|
|
383
|
+
return self.stars if self.stars != "ns" else "ns"
|
|
384
|
+
|
|
385
|
+
elif style == "text":
|
|
386
|
+
return f"p = {self.p_value:.3f}"
|
|
387
|
+
|
|
388
|
+
elif style == "detailed":
|
|
389
|
+
parts = [f"{stat_name} = {stat_value:.3f}"]
|
|
390
|
+
parts.append(f"p = {self.p_value:.3e}")
|
|
391
|
+
|
|
392
|
+
if self.effect_size:
|
|
393
|
+
es_name = self.effect_size.get("name", "d")
|
|
394
|
+
es_value = self.effect_size.get("value", 0.0)
|
|
395
|
+
parts.append(f"{es_name} = {es_value:.2f}")
|
|
396
|
+
|
|
397
|
+
return ", ".join(parts)
|
|
398
|
+
|
|
399
|
+
elif style == "publication":
|
|
400
|
+
p_text = self._format_p_publication()
|
|
401
|
+
return f"({stat_name} = {stat_value:.2f}, {p_text})"
|
|
402
|
+
|
|
403
|
+
elif style == "bracket":
|
|
404
|
+
# For bracket display, return stars only
|
|
405
|
+
return self.stars if self.stars != "ns" else ""
|
|
406
|
+
|
|
407
|
+
return f"{stat_name} = {stat_value:.3f}{self.stars}"
|
|
408
|
+
|
|
409
|
+
def _format_p_publication(self) -> str:
|
|
410
|
+
"""
|
|
411
|
+
Format p-value for publication style.
|
|
412
|
+
|
|
413
|
+
Returns
|
|
414
|
+
-------
|
|
415
|
+
str
|
|
416
|
+
Formatted p-value like "p < 0.001" or "p = 0.023"
|
|
417
|
+
"""
|
|
418
|
+
if self.p_value < 0.001:
|
|
419
|
+
return "p < 0.001"
|
|
420
|
+
elif self.p_value < 0.01:
|
|
421
|
+
return "p < 0.01"
|
|
422
|
+
elif self.p_value < 0.05:
|
|
423
|
+
return "p < 0.05"
|
|
424
|
+
else:
|
|
425
|
+
return f"p = {self.p_value:.3f}"
|
|
426
|
+
|
|
427
|
+
def get_interpretation(self) -> str:
|
|
428
|
+
"""
|
|
429
|
+
Get human-readable interpretation of results.
|
|
430
|
+
|
|
431
|
+
Returns
|
|
432
|
+
-------
|
|
433
|
+
str
|
|
434
|
+
Interpretation text
|
|
435
|
+
|
|
436
|
+
Examples
|
|
437
|
+
--------
|
|
438
|
+
>>> result.get_interpretation()
|
|
439
|
+
'Strong positive correlation (r=0.85, p<0.001)'
|
|
440
|
+
"""
|
|
441
|
+
if self.test_category == "correlation":
|
|
442
|
+
r = self.statistic.get("value", 0)
|
|
443
|
+
direction = "positive" if r > 0 else "negative"
|
|
444
|
+
|
|
445
|
+
if abs(r) > 0.7:
|
|
446
|
+
strength = "Strong"
|
|
447
|
+
elif abs(r) > 0.5:
|
|
448
|
+
strength = "Moderate"
|
|
449
|
+
elif abs(r) > 0.3:
|
|
450
|
+
strength = "Weak"
|
|
451
|
+
else:
|
|
452
|
+
strength = "Very weak"
|
|
453
|
+
|
|
454
|
+
sig = "significant" if self.stars and self.stars != "ns" else "non-significant"
|
|
455
|
+
|
|
456
|
+
return (
|
|
457
|
+
f"{strength} {direction} correlation "
|
|
458
|
+
f"({self.statistic['name']}={r:.2f}, {self._format_p_publication()}, {sig})"
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
elif "test" in self.test_type.lower():
|
|
462
|
+
sig = "Significant" if self.stars and self.stars != "ns" else "Non-significant"
|
|
463
|
+
return (
|
|
464
|
+
f"{sig} difference "
|
|
465
|
+
f"({self.statistic['name']}={self.statistic['value']:.2f}, "
|
|
466
|
+
f"{self._format_p_publication()})"
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
return f"Test result: {self.format_text('publication')}"
|
|
470
|
+
|
|
471
|
+
def to_annotation_dict(self) -> Dict[str, Any]:
|
|
472
|
+
"""
|
|
473
|
+
Convert to annotation dictionary for GUI integration.
|
|
474
|
+
|
|
475
|
+
This format matches the scitex-cloud GUI Annotation interface.
|
|
476
|
+
|
|
477
|
+
Returns
|
|
478
|
+
-------
|
|
479
|
+
dict
|
|
480
|
+
Annotation dictionary compatible with TypeScript Annotation interface
|
|
481
|
+
|
|
482
|
+
Examples
|
|
483
|
+
--------
|
|
484
|
+
>>> ann = result.to_annotation_dict()
|
|
485
|
+
>>> ann["type"]
|
|
486
|
+
'stat'
|
|
487
|
+
>>> ann["statResult"]["p_value"]
|
|
488
|
+
0.001
|
|
489
|
+
"""
|
|
490
|
+
return {
|
|
491
|
+
"id": self.plot_id or f"stat_{id(self)}",
|
|
492
|
+
"type": "stat",
|
|
493
|
+
"label": f"{self.test_type} result",
|
|
494
|
+
"content": self.format_text(style=self.styling.symbol_style if self.styling else "compact"),
|
|
495
|
+
"position": self.positioning.position.to_dict() if self.positioning and self.positioning.position else None,
|
|
496
|
+
"statResult": {
|
|
497
|
+
"id": self.plot_id or f"stat_{id(self)}",
|
|
498
|
+
"test_name": self.test_type,
|
|
499
|
+
"p_value": self.p_value,
|
|
500
|
+
"effect_size": self.effect_size.get("value") if self.effect_size else None,
|
|
501
|
+
"group1": self.samples.get("group1", {}).get("name") if self.samples else None,
|
|
502
|
+
"group2": self.samples.get("group2", {}).get("name") if self.samples else None,
|
|
503
|
+
"statistic": self.statistic.get("value"),
|
|
504
|
+
"method": self.test_type,
|
|
505
|
+
"formatted_output": self.format_text(style=self.styling.symbol_style if self.styling else "compact"),
|
|
506
|
+
},
|
|
507
|
+
"positioning": self.positioning.to_dict() if self.positioning else None,
|
|
508
|
+
"styling": self.styling.to_dict() if self.styling else None,
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
# Convenience function for backward compatibility
|
|
513
|
+
def create_stat_result(
|
|
514
|
+
test_type: str,
|
|
515
|
+
statistic_name: str,
|
|
516
|
+
statistic_value: float,
|
|
517
|
+
p_value: float,
|
|
518
|
+
**kwargs
|
|
519
|
+
) -> StatResult:
|
|
520
|
+
"""
|
|
521
|
+
Create a StatResult with minimal required fields.
|
|
522
|
+
|
|
523
|
+
Parameters
|
|
524
|
+
----------
|
|
525
|
+
test_type : str
|
|
526
|
+
Type of statistical test
|
|
527
|
+
statistic_name : str
|
|
528
|
+
Name of the test statistic (e.g., "t", "r", "F")
|
|
529
|
+
statistic_value : float
|
|
530
|
+
Value of the test statistic
|
|
531
|
+
p_value : float
|
|
532
|
+
P-value from the test
|
|
533
|
+
**kwargs
|
|
534
|
+
Additional fields for StatResult
|
|
535
|
+
|
|
536
|
+
Returns
|
|
537
|
+
-------
|
|
538
|
+
StatResult
|
|
539
|
+
Configured StatResult instance
|
|
540
|
+
|
|
541
|
+
Examples
|
|
542
|
+
--------
|
|
543
|
+
>>> result = create_stat_result(
|
|
544
|
+
... test_type="pearson",
|
|
545
|
+
... statistic_name="r",
|
|
546
|
+
... statistic_value=0.85,
|
|
547
|
+
... p_value=0.001
|
|
548
|
+
... )
|
|
549
|
+
"""
|
|
550
|
+
from scitex.stats.utils import p2stars
|
|
551
|
+
|
|
552
|
+
# Determine category from test type
|
|
553
|
+
category_map = {
|
|
554
|
+
"pearson": "correlation",
|
|
555
|
+
"spearman": "correlation",
|
|
556
|
+
"kendall": "correlation",
|
|
557
|
+
"t-test": "parametric",
|
|
558
|
+
"anova": "parametric",
|
|
559
|
+
"mannwhitney": "non-parametric",
|
|
560
|
+
"kruskal": "non-parametric",
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
test_category = category_map.get(test_type.lower(), "other")
|
|
564
|
+
|
|
565
|
+
# Get stars
|
|
566
|
+
stars = p2stars(p_value, ns_symbol=False)
|
|
567
|
+
|
|
568
|
+
return StatResult(
|
|
569
|
+
test_type=test_type,
|
|
570
|
+
test_category=test_category,
|
|
571
|
+
statistic={"name": statistic_name, "value": statistic_value},
|
|
572
|
+
p_value=p_value,
|
|
573
|
+
stars=stars,
|
|
574
|
+
**kwargs
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
# EOF
|