scitex 2.15.1__py3-none-any.whl → 2.15.2__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/__init__.py +68 -61
- scitex/_mcp_tools/introspect.py +42 -23
- scitex/_mcp_tools/template.py +24 -0
- scitex/ai/classification/timeseries/_TimeSeriesSlidingWindowSplit.py +30 -1550
- scitex/ai/classification/timeseries/_sliding_window_core.py +467 -0
- scitex/ai/classification/timeseries/_sliding_window_plotting.py +369 -0
- scitex/audio/__init__.py +2 -2
- scitex/audio/_tts.py +18 -10
- scitex/audio/engines/base.py +17 -10
- scitex/audio/engines/elevenlabs_engine.py +1 -1
- scitex/canvas/editor/flask_editor/_core/__init__.py +27 -0
- scitex/canvas/editor/flask_editor/_core/_bbox_extraction.py +200 -0
- scitex/canvas/editor/flask_editor/_core/_editor.py +173 -0
- scitex/canvas/editor/flask_editor/_core/_export_helpers.py +353 -0
- scitex/canvas/editor/flask_editor/_core/_routes_basic.py +190 -0
- scitex/canvas/editor/flask_editor/_core/_routes_export.py +332 -0
- scitex/canvas/editor/flask_editor/_core/_routes_panels.py +252 -0
- scitex/canvas/editor/flask_editor/_core/_routes_save.py +218 -0
- scitex/canvas/editor/flask_editor/_core.py +25 -1684
- scitex/cli/introspect.py +112 -74
- scitex/cli/main.py +2 -0
- scitex/cli/plt.py +357 -0
- scitex/cli/repro.py +15 -8
- scitex/cli/resource.py +15 -8
- scitex/cli/scholar/__init__.py +15 -8
- scitex/cli/social.py +6 -6
- scitex/cli/stats.py +15 -8
- scitex/cli/template.py +129 -12
- scitex/cli/tex.py +15 -8
- scitex/cli/writer.py +15 -8
- scitex/cloud/__init__.py +41 -2
- scitex/config/_env_registry.py +84 -19
- scitex/context/__init__.py +22 -0
- scitex/dev/__init__.py +20 -1
- scitex/gen/__init__.py +50 -14
- scitex/gen/_list_packages.py +4 -4
- scitex/introspect/__init__.py +16 -9
- scitex/introspect/_core.py +7 -8
- scitex/{gen/_inspect_module.py → introspect/_list_api.py} +43 -54
- scitex/introspect/_mcp/__init__.py +10 -6
- scitex/introspect/_mcp/handlers.py +37 -12
- scitex/introspect/_members.py +7 -3
- scitex/introspect/_signature.py +3 -3
- scitex/introspect/_source.py +2 -2
- scitex/io/_save.py +1 -2
- scitex/logging/_formatters.py +19 -9
- scitex/mcp_server.py +1 -1
- scitex/os/__init__.py +4 -0
- scitex/{gen → os}/_check_host.py +4 -5
- scitex/plt/__init__.py +11 -14
- scitex/session/__init__.py +26 -7
- scitex/session/_decorator.py +1 -1
- scitex/sh/__init__.py +7 -4
- scitex/social/__init__.py +10 -8
- scitex/stats/_mcp/_handlers/__init__.py +31 -0
- scitex/stats/_mcp/_handlers/_corrections.py +113 -0
- scitex/stats/_mcp/_handlers/_descriptive.py +78 -0
- scitex/stats/_mcp/_handlers/_effect_size.py +106 -0
- scitex/stats/_mcp/_handlers/_format.py +94 -0
- scitex/stats/_mcp/_handlers/_normality.py +110 -0
- scitex/stats/_mcp/_handlers/_posthoc.py +224 -0
- scitex/stats/_mcp/_handlers/_power.py +247 -0
- scitex/stats/_mcp/_handlers/_recommend.py +102 -0
- scitex/stats/_mcp/_handlers/_run_test.py +279 -0
- scitex/stats/_mcp/_handlers/_stars.py +48 -0
- scitex/stats/_mcp/handlers.py +19 -1171
- scitex/stats/auto/_stat_style.py +175 -0
- scitex/stats/auto/_style_definitions.py +411 -0
- scitex/stats/auto/_styles.py +22 -620
- scitex/stats/descriptive/__init__.py +11 -8
- scitex/stats/descriptive/_ci.py +39 -0
- scitex/stats/power/_power.py +15 -4
- scitex/str/__init__.py +2 -1
- scitex/str/_title_case.py +63 -0
- scitex/template/__init__.py +25 -10
- scitex/template/_code_templates.py +147 -0
- scitex/template/_mcp/handlers.py +81 -0
- scitex/template/_mcp/tool_schemas.py +55 -0
- scitex/template/_templates/__init__.py +51 -0
- scitex/template/_templates/audio.py +233 -0
- scitex/template/_templates/canvas.py +312 -0
- scitex/template/_templates/capture.py +268 -0
- scitex/template/_templates/config.py +43 -0
- scitex/template/_templates/diagram.py +294 -0
- scitex/template/_templates/io.py +107 -0
- scitex/template/_templates/module.py +53 -0
- scitex/template/_templates/plt.py +202 -0
- scitex/template/_templates/scholar.py +267 -0
- scitex/template/_templates/session.py +130 -0
- scitex/template/_templates/session_minimal.py +43 -0
- scitex/template/_templates/session_plot.py +67 -0
- scitex/template/_templates/session_stats.py +77 -0
- scitex/template/_templates/stats.py +323 -0
- scitex/template/_templates/writer.py +296 -0
- scitex/ui/_backends/_email.py +10 -2
- scitex/ui/_backends/_webhook.py +5 -1
- scitex/web/_search_pubmed.py +10 -6
- {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/METADATA +1 -1
- {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/RECORD +105 -64
- scitex/gen/_ci.py +0 -12
- scitex/gen/_title_case.py +0 -89
- /scitex/{gen → context}/_detect_environment.py +0 -0
- /scitex/{gen → context}/_get_notebook_path.py +0 -0
- /scitex/{gen/_shell.py → sh/_shell_legacy.py} +0 -0
- {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/WHEEL +0 -0
- {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/entry_points.txt +0 -0
- {scitex-2.15.1.dist-info → scitex-2.15.2.dist-info}/licenses/LICENSE +0 -0
scitex/logging/_formatters.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
# Timestamp: "2025-10-11 00:17:43 (ywatanabe)"
|
|
4
3
|
# File: /home/ywatanabe/proj/scitex_repo/src/scitex/logging/_formatters.py
|
|
5
4
|
# ----------------------------------------
|
|
6
5
|
from __future__ import annotations
|
|
6
|
+
|
|
7
7
|
import os
|
|
8
8
|
|
|
9
9
|
__FILE__ = "./src/scitex/logging/_formatters.py"
|
|
@@ -18,12 +18,17 @@ import sys
|
|
|
18
18
|
|
|
19
19
|
# Global format configuration via environment variable
|
|
20
20
|
# Options: default, minimal, detailed, debug, full
|
|
21
|
-
#
|
|
22
|
-
LOG_FORMAT = os.getenv("
|
|
21
|
+
# SCITEX_LOGGING_FORMAT=debug python script.py
|
|
22
|
+
LOG_FORMAT = os.getenv("SCITEX_LOGGING_FORMAT") or os.getenv(
|
|
23
|
+
"SCITEX_LOG_FORMAT", "default"
|
|
24
|
+
)
|
|
23
25
|
|
|
24
26
|
# Force color output even when stdout is not a TTY (e.g., when piping through tee)
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
+
# SCITEX_LOGGING_FORCE_COLOR=1 python script.py | tee output.log
|
|
28
|
+
_force_color = os.getenv("SCITEX_LOGGING_FORCE_COLOR") or os.getenv(
|
|
29
|
+
"SCITEX_FORCE_COLOR", ""
|
|
30
|
+
)
|
|
31
|
+
FORCE_COLOR = _force_color.lower() in ("1", "true", "yes")
|
|
27
32
|
|
|
28
33
|
# Available format templates
|
|
29
34
|
FORMAT_TEMPLATES = {
|
|
@@ -107,14 +112,19 @@ class SciTeXConsoleFormatter(logging.Formatter):
|
|
|
107
112
|
# First line already has prefix from parent formatter
|
|
108
113
|
# Add prefix to each continuation line
|
|
109
114
|
prefix = f"{record.levelname}: "
|
|
110
|
-
formatted =
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
formatted = (
|
|
116
|
+
lines[0]
|
|
117
|
+
+ "\n"
|
|
118
|
+
+ "\n".join(
|
|
119
|
+
prefix + line if line.strip() else line for line in lines[1:]
|
|
120
|
+
)
|
|
113
121
|
)
|
|
114
122
|
|
|
115
123
|
# Check if we can use colors (stdout is a tty and not closed, or forced)
|
|
116
124
|
try:
|
|
117
|
-
use_colors = FORCE_COLOR or (
|
|
125
|
+
use_colors = FORCE_COLOR or (
|
|
126
|
+
hasattr(sys.stdout, "isatty") and sys.stdout.isatty()
|
|
127
|
+
)
|
|
118
128
|
except ValueError:
|
|
119
129
|
# stdout/stderr is closed
|
|
120
130
|
use_colors = FORCE_COLOR
|
scitex/mcp_server.py
CHANGED
|
@@ -106,7 +106,7 @@ result = stx.stats.test_anova(*groups, return_as="latex")
|
|
|
106
106
|
- [scholar] search_papers, enrich_bibtex, download_pdf, fetch_papers
|
|
107
107
|
- [diagram] create_diagram, compile_mermaid, compile_graphviz
|
|
108
108
|
- [canvas] create_canvas, add_panel, export_canvas
|
|
109
|
-
- [template] list_templates, clone_template
|
|
109
|
+
- [template] list_templates, clone_template, get_code_template, list_code_templates
|
|
110
110
|
- [ui] notify, list_notification_backends
|
|
111
111
|
- [writer] usage (LaTeX manuscript compilation)
|
|
112
112
|
- [introspect] signature, docstring, source, members (like IPython's ? and ??)
|
scitex/os/__init__.py
CHANGED
scitex/{gen → os}/_check_host.py
RENAMED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
# Time-stamp: "2024-11-02 13:43:36 (ywatanabe)"
|
|
4
|
-
# File:
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-python/src/scitex/os/_check_host.py
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
from scitex.sh import sh
|
|
5
|
+
import socket
|
|
8
6
|
import sys
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
def check_host(keyword):
|
|
12
|
-
|
|
10
|
+
"""Check if the current hostname contains the given keyword."""
|
|
11
|
+
return keyword in socket.gethostname()
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
is_host = check_host
|
scitex/plt/__init__.py
CHANGED
|
@@ -70,34 +70,31 @@ __DIR__ = os.path.dirname(__FILE__)
|
|
|
70
70
|
# Re-export figrecipe public API with scitex branding
|
|
71
71
|
# ============================================================================
|
|
72
72
|
if _FIGRECIPE_AVAILABLE:
|
|
73
|
-
# Core
|
|
73
|
+
# Core public API
|
|
74
|
+
from figrecipe import __version__ as _figrecipe_version
|
|
74
75
|
from figrecipe import (
|
|
75
|
-
STYLE,
|
|
76
|
-
align_panels,
|
|
77
|
-
apply_style,
|
|
78
76
|
compose,
|
|
79
77
|
crop,
|
|
80
|
-
distribute_panels,
|
|
81
78
|
edit,
|
|
82
|
-
enable_svg,
|
|
83
79
|
extract_data,
|
|
84
|
-
get_graph_preset,
|
|
85
80
|
info,
|
|
86
|
-
list_graph_presets,
|
|
87
81
|
list_presets,
|
|
88
82
|
load_style,
|
|
89
|
-
register_graph_preset,
|
|
90
83
|
reproduce,
|
|
91
84
|
save,
|
|
92
|
-
smart_align,
|
|
93
|
-
sns,
|
|
94
85
|
subplots,
|
|
95
86
|
unload_style,
|
|
96
87
|
validate,
|
|
97
88
|
)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
89
|
+
|
|
90
|
+
# Internal imports (not part of figrecipe public API)
|
|
91
|
+
from figrecipe._api._notebook import enable_svg
|
|
92
|
+
from figrecipe._api._seaborn_proxy import sns
|
|
93
|
+
from figrecipe._api._style_manager import STYLE, apply_style
|
|
94
|
+
from figrecipe._composition import align_panels, distribute_panels, smart_align
|
|
95
|
+
from figrecipe._graph_presets import get_preset as get_graph_preset
|
|
96
|
+
from figrecipe._graph_presets import list_presets as list_graph_presets
|
|
97
|
+
from figrecipe._graph_presets import register_preset as register_graph_preset
|
|
101
98
|
|
|
102
99
|
# Also export load as alias for reproduce
|
|
103
100
|
load = reproduce
|
scitex/session/__init__.py
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
# Timestamp: "2025-08-21 20:36:45 (ywatanabe)"
|
|
4
3
|
# File: /home/ywatanabe/proj/SciTeX-Code/src/scitex/session/__init__.py
|
|
5
4
|
# ----------------------------------------
|
|
6
5
|
from __future__ import annotations
|
|
6
|
+
|
|
7
7
|
import os
|
|
8
8
|
|
|
9
9
|
__FILE__ = __file__
|
|
@@ -20,27 +20,46 @@ Usage:
|
|
|
20
20
|
import sys
|
|
21
21
|
import matplotlib.pyplot as plt
|
|
22
22
|
from scitex import session
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
# Start a session
|
|
25
25
|
CONFIG, sys.stdout, sys.stderr, plt, COLORS, rng = session.start(sys, plt)
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
# Your experiment code here
|
|
28
|
-
|
|
29
|
-
# Close the session
|
|
28
|
+
|
|
29
|
+
# Close the session
|
|
30
30
|
session.close(CONFIG)
|
|
31
31
|
|
|
32
32
|
# Session manager for advanced use cases
|
|
33
33
|
manager = session.SessionManager()
|
|
34
34
|
active_sessions = manager.get_active_sessions()
|
|
35
|
+
|
|
36
|
+
# Using INJECTED sentinel for decorator parameters
|
|
37
|
+
@stx.session
|
|
38
|
+
def main(CONFIG=stx.session.INJECTED, plt=stx.session.INJECTED):
|
|
39
|
+
...
|
|
35
40
|
"""
|
|
36
41
|
|
|
42
|
+
|
|
43
|
+
# Sentinel object for decorator-injected parameters
|
|
44
|
+
class _InjectedSentinel:
|
|
45
|
+
"""Sentinel value indicating a parameter will be injected by a decorator."""
|
|
46
|
+
|
|
47
|
+
def __repr__(self):
|
|
48
|
+
return "<INJECTED>"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
INJECTED = _InjectedSentinel()
|
|
52
|
+
|
|
53
|
+
|
|
37
54
|
# Import session management functionality
|
|
55
|
+
from ._decorator import run, session
|
|
56
|
+
from ._lifecycle import close, running2finished, start
|
|
38
57
|
from ._manager import SessionManager
|
|
39
|
-
from ._lifecycle import start, close, running2finished
|
|
40
|
-
from ._decorator import session, run
|
|
41
58
|
|
|
42
59
|
# Export public API
|
|
43
60
|
__all__ = [
|
|
61
|
+
# Sentinel for injected parameters
|
|
62
|
+
"INJECTED",
|
|
44
63
|
# Session lifecycle (main functions)
|
|
45
64
|
"start",
|
|
46
65
|
"close",
|
scitex/session/_decorator.py
CHANGED
|
@@ -21,7 +21,7 @@ import sys as sys_module
|
|
|
21
21
|
|
|
22
22
|
from ._lifecycle import start, close
|
|
23
23
|
from scitex.logging import getLogger
|
|
24
|
-
from
|
|
24
|
+
from . import INJECTED # Use local INJECTED from session module
|
|
25
25
|
|
|
26
26
|
# Internal logger for the decorator itself
|
|
27
27
|
_decorator_logger = getLogger(__name__)
|
scitex/sh/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
2
|
from __future__ import annotations
|
|
3
|
+
|
|
4
4
|
import os
|
|
5
5
|
|
|
6
6
|
__FILE__ = __file__
|
|
@@ -8,9 +8,9 @@ __DIR__ = os.path.dirname(__FILE__)
|
|
|
8
8
|
|
|
9
9
|
from typing import Union
|
|
10
10
|
|
|
11
|
-
from ._types import CommandInput, ReturnFormat, ShellResult
|
|
12
|
-
from ._security import quote, validate_command
|
|
13
11
|
from ._execute import execute
|
|
12
|
+
from ._security import quote, validate_command
|
|
13
|
+
from ._types import CommandInput, ReturnFormat, ShellResult
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def sh(
|
|
@@ -88,6 +88,9 @@ def sh_run(command: CommandInput, verbose: bool = True) -> ShellResult:
|
|
|
88
88
|
return execute(command, verbose=verbose)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
# Legacy functions moved from gen module
|
|
92
|
+
from ._shell_legacy import run_shellcommand, run_shellscript
|
|
93
|
+
|
|
94
|
+
__all__ = ["sh", "sh_run", "quote", "run_shellcommand", "run_shellscript"]
|
|
92
95
|
|
|
93
96
|
# EOF
|
scitex/social/__init__.py
CHANGED
|
@@ -17,13 +17,15 @@ Features
|
|
|
17
17
|
|
|
18
18
|
Environment Variables
|
|
19
19
|
---------------------
|
|
20
|
-
Credentials use
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
20
|
+
Credentials use SCITEX_SOCIAL_ prefix (falls back to SOCIALIA_):
|
|
21
|
+
- SCITEX_SOCIAL_X_CONSUMER_KEY, SCITEX_SOCIAL_X_CONSUMER_KEY_SECRET
|
|
22
|
+
- SCITEX_SOCIAL_X_ACCESS_TOKEN, SCITEX_SOCIAL_X_ACCESS_TOKEN_SECRET
|
|
23
|
+
- SCITEX_SOCIAL_X_BEARER_TOKEN
|
|
24
|
+
- SCITEX_SOCIAL_LINKEDIN_CLIENT_ID, SCITEX_SOCIAL_LINKEDIN_CLIENT_SECRET
|
|
25
|
+
- SCITEX_SOCIAL_LINKEDIN_ACCESS_TOKEN
|
|
26
|
+
- SCITEX_SOCIAL_REDDIT_CLIENT_ID, SCITEX_SOCIAL_REDDIT_CLIENT_SECRET
|
|
27
|
+
- SCITEX_SOCIAL_YOUTUBE_API_KEY
|
|
28
|
+
- SCITEX_SOCIAL_GOOGLE_ANALYTICS_PROPERTY_ID
|
|
27
29
|
|
|
28
30
|
Usage
|
|
29
31
|
-----
|
|
@@ -55,7 +57,7 @@ import os as _os
|
|
|
55
57
|
|
|
56
58
|
# Set branding BEFORE importing socialia
|
|
57
59
|
_os.environ.setdefault("SOCIALIA_BRAND", "scitex.social")
|
|
58
|
-
_os.environ.setdefault("SOCIALIA_ENV_PREFIX", "
|
|
60
|
+
_os.environ.setdefault("SOCIALIA_ENV_PREFIX", "SCITEX_SOCIAL")
|
|
59
61
|
|
|
60
62
|
# Check socialia availability
|
|
61
63
|
try:
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-01-25
|
|
3
|
+
# File: src/scitex/stats/_mcp/_handlers/__init__.py
|
|
4
|
+
|
|
5
|
+
"""Stats MCP handler implementations split into modules."""
|
|
6
|
+
|
|
7
|
+
from ._corrections import correct_pvalues_handler
|
|
8
|
+
from ._descriptive import describe_handler
|
|
9
|
+
from ._effect_size import effect_size_handler
|
|
10
|
+
from ._format import format_results_handler
|
|
11
|
+
from ._normality import normality_test_handler
|
|
12
|
+
from ._posthoc import posthoc_test_handler
|
|
13
|
+
from ._power import power_analysis_handler
|
|
14
|
+
from ._recommend import recommend_tests_handler
|
|
15
|
+
from ._run_test import run_test_handler
|
|
16
|
+
from ._stars import p_to_stars_handler
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"recommend_tests_handler",
|
|
20
|
+
"run_test_handler",
|
|
21
|
+
"format_results_handler",
|
|
22
|
+
"power_analysis_handler",
|
|
23
|
+
"correct_pvalues_handler",
|
|
24
|
+
"describe_handler",
|
|
25
|
+
"effect_size_handler",
|
|
26
|
+
"normality_test_handler",
|
|
27
|
+
"posthoc_test_handler",
|
|
28
|
+
"p_to_stars_handler",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
# EOF
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-01-25
|
|
3
|
+
# File: src/scitex/stats/_mcp/_handlers/_corrections.py
|
|
4
|
+
|
|
5
|
+
"""P-value correction handler for multiple comparisons."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
__all__ = ["correct_pvalues_handler"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def correct_pvalues_handler(
|
|
18
|
+
pvalues: list[float],
|
|
19
|
+
method: str = "fdr_bh",
|
|
20
|
+
alpha: float = 0.05,
|
|
21
|
+
) -> dict:
|
|
22
|
+
"""Apply multiple comparison correction to p-values."""
|
|
23
|
+
try:
|
|
24
|
+
loop = asyncio.get_event_loop()
|
|
25
|
+
|
|
26
|
+
def do_correct():
|
|
27
|
+
from statsmodels.stats.multitest import multipletests
|
|
28
|
+
|
|
29
|
+
# Map method names
|
|
30
|
+
method_map = {
|
|
31
|
+
"bonferroni": "bonferroni",
|
|
32
|
+
"fdr_bh": "fdr_bh",
|
|
33
|
+
"fdr_by": "fdr_by",
|
|
34
|
+
"holm": "holm",
|
|
35
|
+
"sidak": "sidak",
|
|
36
|
+
}
|
|
37
|
+
sm_method = method_map.get(method, "fdr_bh")
|
|
38
|
+
|
|
39
|
+
pvals = np.array(pvalues)
|
|
40
|
+
reject, pvals_corrected, _, _ = multipletests(
|
|
41
|
+
pvals, alpha=alpha, method=sm_method
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
"original_pvalues": pvalues,
|
|
46
|
+
"corrected_pvalues": pvals_corrected.tolist(),
|
|
47
|
+
"reject_null": reject.tolist(),
|
|
48
|
+
"n_significant": int(reject.sum()),
|
|
49
|
+
"n_tests": len(pvalues),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
result = await loop.run_in_executor(None, do_correct)
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
"success": True,
|
|
56
|
+
"method": method,
|
|
57
|
+
"alpha": alpha,
|
|
58
|
+
**result,
|
|
59
|
+
"timestamp": datetime.now().isoformat(),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
except ImportError:
|
|
63
|
+
# Fallback implementation without statsmodels
|
|
64
|
+
try:
|
|
65
|
+
n = len(pvalues)
|
|
66
|
+
pvals = np.array(pvalues)
|
|
67
|
+
|
|
68
|
+
if method == "bonferroni":
|
|
69
|
+
corrected = np.minimum(pvals * n, 1.0)
|
|
70
|
+
elif method == "holm":
|
|
71
|
+
sorted_idx = np.argsort(pvals)
|
|
72
|
+
corrected = np.empty(n)
|
|
73
|
+
cummax = 0.0
|
|
74
|
+
for rank, idx in enumerate(sorted_idx, start=1):
|
|
75
|
+
adj = min((n - rank + 1) * pvals[idx], 1.0)
|
|
76
|
+
adj = max(adj, cummax)
|
|
77
|
+
corrected[idx] = adj
|
|
78
|
+
cummax = adj
|
|
79
|
+
elif method == "fdr_bh":
|
|
80
|
+
sorted_idx = np.argsort(pvals)
|
|
81
|
+
corrected = np.empty(n)
|
|
82
|
+
prev = 1.0
|
|
83
|
+
for rank in range(n, 0, -1):
|
|
84
|
+
idx = sorted_idx[rank - 1]
|
|
85
|
+
bh = pvals[idx] * n / rank
|
|
86
|
+
val = min(bh, prev, 1.0)
|
|
87
|
+
corrected[idx] = val
|
|
88
|
+
prev = val
|
|
89
|
+
elif method == "sidak":
|
|
90
|
+
corrected = 1 - (1 - pvals) ** n
|
|
91
|
+
else:
|
|
92
|
+
corrected = pvals
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
"success": True,
|
|
96
|
+
"method": method,
|
|
97
|
+
"alpha": alpha,
|
|
98
|
+
"original_pvalues": pvalues,
|
|
99
|
+
"corrected_pvalues": corrected.tolist(),
|
|
100
|
+
"reject_null": (corrected < alpha).tolist(),
|
|
101
|
+
"n_significant": int((corrected < alpha).sum()),
|
|
102
|
+
"n_tests": n,
|
|
103
|
+
"timestamp": datetime.now().isoformat(),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
except Exception as e:
|
|
107
|
+
return {"success": False, "error": str(e)}
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
return {"success": False, "error": str(e)}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# EOF
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-01-25
|
|
3
|
+
# File: src/scitex/stats/_mcp/_handlers/_descriptive.py
|
|
4
|
+
|
|
5
|
+
"""Descriptive statistics handler."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
__all__ = ["describe_handler"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def describe_handler(
|
|
18
|
+
data: list[float],
|
|
19
|
+
percentiles: list[float] | None = None,
|
|
20
|
+
) -> dict:
|
|
21
|
+
"""Calculate descriptive statistics for data."""
|
|
22
|
+
try:
|
|
23
|
+
loop = asyncio.get_event_loop()
|
|
24
|
+
|
|
25
|
+
def do_describe():
|
|
26
|
+
arr = np.array(data, dtype=float)
|
|
27
|
+
arr = arr[~np.isnan(arr)] # Remove NaN
|
|
28
|
+
|
|
29
|
+
if len(arr) == 0:
|
|
30
|
+
return {"error": "No valid data points"}
|
|
31
|
+
|
|
32
|
+
percs = percentiles or [25, 50, 75]
|
|
33
|
+
percentile_values = np.percentile(arr, percs)
|
|
34
|
+
|
|
35
|
+
result = {
|
|
36
|
+
"n": int(len(arr)),
|
|
37
|
+
"mean": float(np.mean(arr)),
|
|
38
|
+
"std": float(np.std(arr, ddof=1)) if len(arr) > 1 else 0.0,
|
|
39
|
+
"var": float(np.var(arr, ddof=1)) if len(arr) > 1 else 0.0,
|
|
40
|
+
"sem": (
|
|
41
|
+
float(np.std(arr, ddof=1) / np.sqrt(len(arr)))
|
|
42
|
+
if len(arr) > 1
|
|
43
|
+
else 0.0
|
|
44
|
+
),
|
|
45
|
+
"min": float(np.min(arr)),
|
|
46
|
+
"max": float(np.max(arr)),
|
|
47
|
+
"range": float(np.max(arr) - np.min(arr)),
|
|
48
|
+
"median": float(np.median(arr)),
|
|
49
|
+
"percentiles": {
|
|
50
|
+
str(int(p)): float(v) for p, v in zip(percs, percentile_values)
|
|
51
|
+
},
|
|
52
|
+
"iqr": float(np.percentile(arr, 75) - np.percentile(arr, 25)),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Add skewness and kurtosis if scipy available
|
|
56
|
+
try:
|
|
57
|
+
from scipy import stats as scipy_stats
|
|
58
|
+
|
|
59
|
+
result["skewness"] = float(scipy_stats.skew(arr))
|
|
60
|
+
result["kurtosis"] = float(scipy_stats.kurtosis(arr))
|
|
61
|
+
except ImportError:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
return result
|
|
65
|
+
|
|
66
|
+
result = await loop.run_in_executor(None, do_describe)
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
"success": True,
|
|
70
|
+
**result,
|
|
71
|
+
"timestamp": datetime.now().isoformat(),
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
except Exception as e:
|
|
75
|
+
return {"success": False, "error": str(e)}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# EOF
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: 2026-01-25
|
|
3
|
+
# File: src/scitex/stats/_mcp/_handlers/_effect_size.py
|
|
4
|
+
|
|
5
|
+
"""Effect size calculation handler."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
__all__ = ["effect_size_handler"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def effect_size_handler(
|
|
18
|
+
group1: list[float],
|
|
19
|
+
group2: list[float],
|
|
20
|
+
measure: str = "cohens_d",
|
|
21
|
+
pooled: bool = True,
|
|
22
|
+
) -> dict:
|
|
23
|
+
"""Calculate effect size between groups."""
|
|
24
|
+
try:
|
|
25
|
+
from scitex.stats.effect_sizes import (
|
|
26
|
+
cliffs_delta,
|
|
27
|
+
cohens_d,
|
|
28
|
+
interpret_cliffs_delta,
|
|
29
|
+
interpret_cohens_d,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
loop = asyncio.get_event_loop()
|
|
33
|
+
|
|
34
|
+
def do_effect_size():
|
|
35
|
+
g1 = np.array(group1, dtype=float)
|
|
36
|
+
g2 = np.array(group2, dtype=float)
|
|
37
|
+
|
|
38
|
+
result = {}
|
|
39
|
+
|
|
40
|
+
if measure == "cohens_d":
|
|
41
|
+
d = cohens_d(g1, g2)
|
|
42
|
+
result = {
|
|
43
|
+
"measure": "Cohen's d",
|
|
44
|
+
"value": float(d),
|
|
45
|
+
"interpretation": interpret_cohens_d(d),
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
elif measure == "hedges_g":
|
|
49
|
+
# Hedges' g is Cohen's d with bias correction
|
|
50
|
+
d = cohens_d(g1, g2)
|
|
51
|
+
n1, n2 = len(g1), len(g2)
|
|
52
|
+
correction = 1 - (3 / (4 * (n1 + n2) - 9))
|
|
53
|
+
g = d * correction
|
|
54
|
+
result = {
|
|
55
|
+
"measure": "Hedges' g",
|
|
56
|
+
"value": float(g),
|
|
57
|
+
"interpretation": interpret_cohens_d(g), # Same thresholds
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
elif measure == "glass_delta":
|
|
61
|
+
# Glass's delta uses only control group std
|
|
62
|
+
mean_diff = np.mean(g1) - np.mean(g2)
|
|
63
|
+
delta = mean_diff / np.std(g2, ddof=1)
|
|
64
|
+
result = {
|
|
65
|
+
"measure": "Glass's delta",
|
|
66
|
+
"value": float(delta),
|
|
67
|
+
"interpretation": interpret_cohens_d(delta),
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
elif measure == "cliffs_delta":
|
|
71
|
+
delta = cliffs_delta(g1, g2)
|
|
72
|
+
result = {
|
|
73
|
+
"measure": "Cliff's delta",
|
|
74
|
+
"value": float(delta),
|
|
75
|
+
"interpretation": interpret_cliffs_delta(delta),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
else:
|
|
79
|
+
raise ValueError(f"Unknown measure: {measure}")
|
|
80
|
+
|
|
81
|
+
# Add confidence interval approximation for Cohen's d
|
|
82
|
+
if measure in ["cohens_d", "hedges_g", "glass_delta"]:
|
|
83
|
+
n1, n2 = len(g1), len(g2)
|
|
84
|
+
se = np.sqrt(
|
|
85
|
+
(n1 + n2) / (n1 * n2) + result["value"] ** 2 / (2 * (n1 + n2))
|
|
86
|
+
)
|
|
87
|
+
result["ci_lower"] = float(result["value"] - 1.96 * se)
|
|
88
|
+
result["ci_upper"] = float(result["value"] + 1.96 * se)
|
|
89
|
+
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
result = await loop.run_in_executor(None, do_effect_size)
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
"success": True,
|
|
96
|
+
"group1_n": len(group1),
|
|
97
|
+
"group2_n": len(group2),
|
|
98
|
+
**result,
|
|
99
|
+
"timestamp": datetime.now().isoformat(),
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
except Exception as e:
|
|
103
|
+
return {"success": False, "error": str(e)}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# EOF
|