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.
Files changed (99) hide show
  1. scitex/ai/classification/reporters/reporter_utils/_Plotter.py +1 -1
  2. scitex/ai/plt/__init__.py +2 -2
  3. scitex/ai/plt/{_plot_conf_mat.py → _stx_conf_mat.py} +3 -3
  4. scitex/config/PriorityConfig.py +195 -0
  5. scitex/config/__init__.py +24 -0
  6. scitex/io/_save.py +125 -34
  7. scitex/io/_save_modules/_image.py +37 -20
  8. scitex/plt/__init__.py +470 -17
  9. scitex/plt/_subplots/_AxisWrapper.py +98 -50
  10. scitex/plt/_subplots/_AxisWrapperMixins/_MatplotlibPlotMixin.py +254 -124
  11. scitex/plt/_subplots/_AxisWrapperMixins/_SeabornMixin.py +49 -8
  12. scitex/plt/_subplots/_SubplotsWrapper.py +76 -91
  13. scitex/plt/_subplots/_export_as_csv.py +127 -58
  14. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +25 -16
  15. scitex/plt/_subplots/_export_as_csv_formatters/_format_contourf.py +54 -0
  16. scitex/plt/_subplots/_export_as_csv_formatters/_format_hexbin.py +41 -0
  17. scitex/plt/_subplots/_export_as_csv_formatters/_format_hist2d.py +41 -0
  18. scitex/plt/_subplots/_export_as_csv_formatters/_format_imshow.py +59 -47
  19. scitex/plt/_subplots/_export_as_csv_formatters/_format_matshow.py +42 -0
  20. scitex/plt/_subplots/_export_as_csv_formatters/_format_pie.py +42 -0
  21. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +72 -35
  22. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_box.py +1 -1
  23. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot_kde.py +2 -2
  24. scitex/plt/_subplots/_export_as_csv_formatters/_format_quiver.py +53 -0
  25. scitex/plt/_subplots/_export_as_csv_formatters/_format_stem.py +42 -0
  26. scitex/plt/_subplots/_export_as_csv_formatters/_format_step.py +42 -0
  27. scitex/plt/_subplots/_export_as_csv_formatters/_format_streamplot.py +48 -0
  28. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_conf_mat.py → _format_stx_conf_mat.py} +2 -2
  29. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_ecdf.py → _format_stx_ecdf.py} +2 -2
  30. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_fillv.py → _format_stx_fillv.py} +2 -2
  31. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_heatmap.py → _format_stx_heatmap.py} +2 -2
  32. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_image.py → _format_stx_image.py} +2 -2
  33. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_joyplot.py → _format_stx_joyplot.py} +2 -2
  34. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_line.py → _format_stx_line.py} +3 -3
  35. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_mean_ci.py → _format_stx_mean_ci.py} +2 -2
  36. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_mean_std.py → _format_stx_mean_std.py} +2 -2
  37. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_median_iqr.py → _format_stx_median_iqr.py} +2 -2
  38. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_raster.py → _format_stx_raster.py} +2 -2
  39. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_rectangle.py → _format_stx_rectangle.py} +1 -1
  40. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_scatter_hist.py → _format_stx_scatter_hist.py} +2 -2
  41. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_shaded_line.py → _format_stx_shaded_line.py} +2 -2
  42. scitex/plt/_subplots/_export_as_csv_formatters/{_format_plot_violin.py → _format_stx_violin.py} +2 -2
  43. scitex/plt/_subplots/_export_as_csv_formatters/verify_formatters.py +23 -23
  44. scitex/plt/ax/__init__.py +16 -15
  45. scitex/plt/ax/_plot/__init__.py +30 -30
  46. scitex/plt/ax/_plot/_add_fitted_line.py +65 -11
  47. scitex/plt/ax/_plot/_plot_statistical_shaded_line.py +104 -76
  48. scitex/plt/ax/_plot/{_plot_conf_mat.py → _stx_conf_mat.py} +10 -10
  49. scitex/plt/ax/_plot/_stx_ecdf.py +109 -0
  50. scitex/plt/ax/_plot/{_plot_fillv.py → _stx_fillv.py} +7 -7
  51. scitex/plt/ax/_plot/_stx_heatmap.py +366 -0
  52. scitex/plt/ax/_plot/{_plot_image.py → _stx_image.py} +1 -1
  53. scitex/plt/ax/_plot/_stx_joyplot.py +113 -0
  54. scitex/plt/ax/_plot/{_plot_raster.py → _stx_raster.py} +37 -25
  55. scitex/plt/ax/_plot/{_plot_rectangle.py → _stx_rectangle.py} +10 -9
  56. scitex/plt/ax/_plot/{_plot_scatter_hist.py → _stx_scatter_hist.py} +1 -1
  57. scitex/plt/ax/_plot/_stx_shaded_line.py +215 -0
  58. scitex/plt/ax/_plot/{_plot_violin.py → _stx_violin.py} +13 -6
  59. scitex/plt/ax/_style/__init__.py +3 -0
  60. scitex/plt/ax/_style/_style_barplot.py +13 -2
  61. scitex/plt/ax/_style/_style_boxplot.py +78 -32
  62. scitex/plt/ax/_style/_style_errorbar.py +17 -3
  63. scitex/plt/ax/_style/_style_scatter.py +17 -3
  64. scitex/plt/ax/_style/_style_violinplot.py +109 -0
  65. scitex/plt/color/_vizualize_colors.py +3 -3
  66. scitex/plt/styles/SCITEX_STYLE.yaml +104 -0
  67. scitex/plt/styles/__init__.py +57 -0
  68. scitex/plt/styles/_plot_defaults.py +209 -0
  69. scitex/plt/styles/_plot_postprocess.py +518 -0
  70. scitex/plt/styles/_style_loader.py +268 -0
  71. scitex/plt/styles/presets.py +208 -0
  72. scitex/plt/utils/_collect_figure_metadata.py +160 -18
  73. scitex/plt/utils/_colorbar.py +72 -10
  74. scitex/plt/utils/_configure_mpl.py +108 -52
  75. scitex/plt/utils/_crop.py +21 -7
  76. scitex/plt/utils/_figure_mm.py +21 -7
  77. scitex/stats/__init__.py +13 -1
  78. scitex/stats/_schema.py +578 -0
  79. scitex/stats/tests/__init__.py +13 -0
  80. scitex/stats/tests/correlation/__init__.py +13 -0
  81. scitex/stats/tests/correlation/_test_pearson.py +262 -0
  82. scitex/vis/__init__.py +6 -0
  83. scitex/vis/editor/__init__.py +23 -0
  84. scitex/vis/editor/_defaults.py +205 -0
  85. scitex/vis/editor/_edit.py +342 -0
  86. scitex/vis/editor/_mpl_editor.py +231 -0
  87. scitex/vis/editor/_tkinter_editor.py +466 -0
  88. scitex/vis/editor/_web_editor.py +1440 -0
  89. scitex/vis/model/plot_types.py +15 -15
  90. {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/METADATA +2 -1
  91. {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/RECORD +94 -67
  92. {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/WHEEL +1 -1
  93. scitex/plt/ax/_plot/_plot_ecdf.py +0 -84
  94. scitex/plt/ax/_plot/_plot_heatmap.py +0 -277
  95. scitex/plt/ax/_plot/_plot_joyplot.py +0 -77
  96. scitex/plt/ax/_plot/_plot_shaded_line.py +0 -142
  97. scitex/plt/presets.py +0 -224
  98. {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/entry_points.txt +0 -0
  99. {scitex-2.3.0.dist-info → scitex-2.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -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
- # Set number of ticks (3-4 ticks enforced)
286
- n_ticks = style.get("n_ticks", 4)
287
- if n_ticks is not None:
288
- from matplotlib.ticker import MaxNLocator
289
- # nbins=4 with min_n_ticks=3 will give 3-4 ticks
290
- ax.xaxis.set_major_locator(MaxNLocator(nbins=n_ticks, min_n_ticks=3))
291
- ax.yaxis.set_major_locator(MaxNLocator(nbins=n_ticks, min_n_ticks=3))
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"
@@ -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
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Statistical tests module.
4
+
5
+ Submodules:
6
+ - correlation: Correlation tests (Pearson, Spearman, Kendall)
7
+ """
8
+
9
+ from . import correlation
10
+
11
+ __all__ = [
12
+ 'correlation',
13
+ ]
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Correlation tests module.
4
+
5
+ Available tests:
6
+ - test_pearson: Pearson correlation (linear relationship)
7
+ """
8
+
9
+ from ._test_pearson import test_pearson
10
+
11
+ __all__ = [
12
+ 'test_pearson',
13
+ ]