openstat-cli 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.
- openstat/__init__.py +3 -0
- openstat/__main__.py +4 -0
- openstat/backends/__init__.py +16 -0
- openstat/backends/duckdb_backend.py +70 -0
- openstat/backends/polars_backend.py +52 -0
- openstat/cli.py +92 -0
- openstat/commands/__init__.py +82 -0
- openstat/commands/adv_stat_cmds.py +1255 -0
- openstat/commands/advanced_ml_cmds.py +576 -0
- openstat/commands/advreg_cmds.py +207 -0
- openstat/commands/alias_cmds.py +135 -0
- openstat/commands/arch_cmds.py +82 -0
- openstat/commands/arules_cmds.py +111 -0
- openstat/commands/automodel_cmds.py +212 -0
- openstat/commands/backend_cmds.py +82 -0
- openstat/commands/base.py +170 -0
- openstat/commands/bayes_cmds.py +71 -0
- openstat/commands/causal_cmds.py +269 -0
- openstat/commands/cluster_cmds.py +152 -0
- openstat/commands/data_cmds.py +996 -0
- openstat/commands/datamanip_cmds.py +672 -0
- openstat/commands/dataquality_cmds.py +174 -0
- openstat/commands/datetime_cmds.py +176 -0
- openstat/commands/dimreduce_cmds.py +184 -0
- openstat/commands/discrete_cmds.py +149 -0
- openstat/commands/dsl_cmds.py +143 -0
- openstat/commands/epi_cmds.py +93 -0
- openstat/commands/equiv_tobit_cmds.py +94 -0
- openstat/commands/esttab_cmds.py +196 -0
- openstat/commands/export_beamer_cmds.py +142 -0
- openstat/commands/export_cmds.py +201 -0
- openstat/commands/export_extra_cmds.py +240 -0
- openstat/commands/factor_cmds.py +180 -0
- openstat/commands/groupby_cmds.py +155 -0
- openstat/commands/help_cmds.py +237 -0
- openstat/commands/i18n_cmds.py +43 -0
- openstat/commands/import_extra_cmds.py +561 -0
- openstat/commands/influence_cmds.py +134 -0
- openstat/commands/iv_cmds.py +106 -0
- openstat/commands/manova_cmds.py +105 -0
- openstat/commands/mediate_cmds.py +233 -0
- openstat/commands/meta_cmds.py +284 -0
- openstat/commands/mi_cmds.py +228 -0
- openstat/commands/mixed_cmds.py +79 -0
- openstat/commands/mixture_changepoint_cmds.py +166 -0
- openstat/commands/ml_adv_cmds.py +147 -0
- openstat/commands/ml_cmds.py +178 -0
- openstat/commands/model_eval_cmds.py +142 -0
- openstat/commands/network_cmds.py +288 -0
- openstat/commands/nlquery_cmds.py +161 -0
- openstat/commands/nonparam_cmds.py +149 -0
- openstat/commands/outreg_cmds.py +247 -0
- openstat/commands/panel_cmds.py +141 -0
- openstat/commands/pdf_cmds.py +226 -0
- openstat/commands/pipeline_cmds.py +319 -0
- openstat/commands/plot_cmds.py +189 -0
- openstat/commands/plugin_cmds.py +79 -0
- openstat/commands/posthoc_cmds.py +153 -0
- openstat/commands/power_cmds.py +172 -0
- openstat/commands/profile_cmds.py +246 -0
- openstat/commands/rbridge_cmds.py +81 -0
- openstat/commands/regex_cmds.py +104 -0
- openstat/commands/report_cmds.py +48 -0
- openstat/commands/repro_cmds.py +129 -0
- openstat/commands/resampling_cmds.py +109 -0
- openstat/commands/reshape_cmds.py +223 -0
- openstat/commands/sem_cmds.py +177 -0
- openstat/commands/stat_cmds.py +1040 -0
- openstat/commands/stata_import_cmds.py +215 -0
- openstat/commands/string_cmds.py +124 -0
- openstat/commands/surv_cmds.py +145 -0
- openstat/commands/survey_cmds.py +153 -0
- openstat/commands/textanalysis_cmds.py +192 -0
- openstat/commands/ts_adv_cmds.py +136 -0
- openstat/commands/ts_cmds.py +195 -0
- openstat/commands/tui_cmds.py +111 -0
- openstat/commands/ux_cmds.py +191 -0
- openstat/commands/validate_cmds.py +270 -0
- openstat/commands/viz_adv_cmds.py +312 -0
- openstat/commands/viz_extra_cmds.py +251 -0
- openstat/commands/watch_cmds.py +69 -0
- openstat/config.py +106 -0
- openstat/dsl/__init__.py +0 -0
- openstat/dsl/parser.py +332 -0
- openstat/dsl/tokenizer.py +105 -0
- openstat/i18n.py +120 -0
- openstat/io/__init__.py +0 -0
- openstat/io/loader.py +187 -0
- openstat/jupyter/__init__.py +18 -0
- openstat/jupyter/display.py +18 -0
- openstat/jupyter/magic.py +60 -0
- openstat/logging_config.py +59 -0
- openstat/plots/__init__.py +0 -0
- openstat/plots/plotter.py +437 -0
- openstat/plots/surv_plots.py +32 -0
- openstat/plots/ts_plots.py +59 -0
- openstat/plugins/__init__.py +5 -0
- openstat/plugins/manager.py +69 -0
- openstat/repl.py +457 -0
- openstat/reporting/__init__.py +0 -0
- openstat/reporting/eda.py +208 -0
- openstat/reporting/report.py +67 -0
- openstat/script_runner.py +319 -0
- openstat/session.py +133 -0
- openstat/stats/__init__.py +0 -0
- openstat/stats/advanced_regression.py +269 -0
- openstat/stats/arch_garch.py +84 -0
- openstat/stats/bayesian.py +103 -0
- openstat/stats/causal.py +258 -0
- openstat/stats/clustering.py +206 -0
- openstat/stats/discrete.py +311 -0
- openstat/stats/epidemiology.py +119 -0
- openstat/stats/equiv_tobit.py +163 -0
- openstat/stats/factor.py +174 -0
- openstat/stats/imputation.py +282 -0
- openstat/stats/influence.py +78 -0
- openstat/stats/iv.py +131 -0
- openstat/stats/manova.py +124 -0
- openstat/stats/mixed.py +128 -0
- openstat/stats/ml.py +275 -0
- openstat/stats/ml_advanced.py +117 -0
- openstat/stats/model_eval.py +183 -0
- openstat/stats/models.py +1342 -0
- openstat/stats/nonparametric.py +130 -0
- openstat/stats/panel.py +179 -0
- openstat/stats/power.py +295 -0
- openstat/stats/resampling.py +203 -0
- openstat/stats/survey.py +213 -0
- openstat/stats/survival.py +196 -0
- openstat/stats/timeseries.py +142 -0
- openstat/stats/ts_advanced.py +114 -0
- openstat/types.py +11 -0
- openstat/web/__init__.py +1 -0
- openstat/web/app.py +117 -0
- openstat/web/session_manager.py +73 -0
- openstat/web/static/app.js +117 -0
- openstat/web/static/index.html +38 -0
- openstat/web/static/style.css +103 -0
- openstat_cli-1.0.0.dist-info/METADATA +748 -0
- openstat_cli-1.0.0.dist-info/RECORD +143 -0
- openstat_cli-1.0.0.dist-info/WHEEL +4 -0
- openstat_cli-1.0.0.dist-info/entry_points.txt +2 -0
- openstat_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Structural Equation Modeling (SEM) commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from openstat.commands.base import command
|
|
6
|
+
from openstat.session import Session
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _fmt_sem(title: str, lines_body: list[str]) -> str:
|
|
10
|
+
sep = "=" * 60
|
|
11
|
+
return "\n".join([f"\n{title}", sep] + lines_body + [sep])
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@command("sem", usage="sem model_syntax [--method=ml|gls|wls]")
|
|
15
|
+
def cmd_sem(session: Session, args: str) -> str:
|
|
16
|
+
"""Structural Equation Model estimation via semopy.
|
|
17
|
+
|
|
18
|
+
Model syntax (lavaan/semopy style):
|
|
19
|
+
Latent variable: F1 =~ x1 + x2 + x3
|
|
20
|
+
Regression: y ~ x1 + x2
|
|
21
|
+
Covariance: x1 ~~ x2
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
sem 'F1 =~ age + income + score'
|
|
25
|
+
sem 'eta =~ x1 + x2 + x3 \\n y ~ eta + z'
|
|
26
|
+
sem 'F1 =~ x1 + x2 \\n y ~ F1' --method=ml
|
|
27
|
+
|
|
28
|
+
Requires: pip install semopy
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
import semopy
|
|
32
|
+
except ImportError:
|
|
33
|
+
return "SEM requires semopy. Install with: pip install semopy"
|
|
34
|
+
|
|
35
|
+
df = session.require_data()
|
|
36
|
+
|
|
37
|
+
import re
|
|
38
|
+
args = args.strip()
|
|
39
|
+
|
|
40
|
+
# Parse --method option
|
|
41
|
+
method = "ml"
|
|
42
|
+
m = re.search(r"--method[= ](\w+)", args)
|
|
43
|
+
if m:
|
|
44
|
+
method = m.group(1).lower()
|
|
45
|
+
args = re.sub(r"--method[= ]\w+", "", args).strip()
|
|
46
|
+
|
|
47
|
+
# Strip surrounding quotes
|
|
48
|
+
model_str = args.strip("\"'")
|
|
49
|
+
# Support \n in quoted strings as actual newlines
|
|
50
|
+
model_str = model_str.replace("\\n", "\n")
|
|
51
|
+
|
|
52
|
+
if not model_str:
|
|
53
|
+
return (
|
|
54
|
+
"Usage: sem '<model_syntax>' [--method=ml|gls|wls]\n"
|
|
55
|
+
"Example: sem 'F1 =~ x1 + x2 + x3'\n"
|
|
56
|
+
"Example: sem 'y ~ x1 + x2'"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Map user-friendly method names to semopy obj parameter
|
|
60
|
+
_method_map = {"ml": "MLW", "gls": "GLS", "wls": "WLS", "uls": "ULS", "fiml": "FIML"}
|
|
61
|
+
obj = _method_map.get(method.lower(), "MLW")
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
pandas_df = df.to_pandas()
|
|
65
|
+
model = semopy.Model(model_str)
|
|
66
|
+
res = model.fit(pandas_df, obj=obj)
|
|
67
|
+
|
|
68
|
+
# Model fit indices
|
|
69
|
+
stats = semopy.calc_stats(model)
|
|
70
|
+
|
|
71
|
+
lines = [
|
|
72
|
+
f"Model: {model_str.replace(chr(10), ' | ')}",
|
|
73
|
+
f"Method: {method.upper()} N = {len(pandas_df)}",
|
|
74
|
+
"",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# Parameter estimates
|
|
78
|
+
try:
|
|
79
|
+
params = model.inspect()
|
|
80
|
+
lines.append("Parameter Estimates:")
|
|
81
|
+
lines.append(f" {'lval':<12} {'op':<5} {'rval':<12} {'Estimate':>10} {'Std.Err':>9} {'z':>7} {'p-value':>9}")
|
|
82
|
+
lines.append(" " + "-" * 65)
|
|
83
|
+
for _, row in params.iterrows():
|
|
84
|
+
p_str = f"{row.get('p-value', float('nan')):.4f}" if row.get('p-value') is not None else " ."
|
|
85
|
+
z_str = f"{row.get('z-value', float('nan')):.3f}" if row.get('z-value') is not None else " ."
|
|
86
|
+
se_str = f"{row.get('Std. Err', float('nan')):.4f}" if row.get('Std. Err') is not None else " ."
|
|
87
|
+
lines.append(
|
|
88
|
+
f" {str(row.get('lval','')):<12} {str(row.get('op','')):<5} "
|
|
89
|
+
f"{str(row.get('rval','')):<12} "
|
|
90
|
+
f"{row.get('Estimate', float('nan')):>10.4f} "
|
|
91
|
+
f"{se_str:>9} {z_str:>7} {p_str:>9}"
|
|
92
|
+
)
|
|
93
|
+
except Exception:
|
|
94
|
+
lines.append("(Parameter table unavailable)")
|
|
95
|
+
|
|
96
|
+
# Fit indices
|
|
97
|
+
lines.append("")
|
|
98
|
+
lines.append("Model Fit Indices:")
|
|
99
|
+
fit_map = {
|
|
100
|
+
"CFI": "CFI", "GFI": "GFI", "AGFI": "AGFI",
|
|
101
|
+
"NFI": "NFI", "RMSEA": "RMSEA", "SRMR": "SRMR",
|
|
102
|
+
"chi2": "Chi-sq", "DoF": "df", "pvalue": "p(chi-sq)",
|
|
103
|
+
"AIC": "AIC", "BIC": "BIC",
|
|
104
|
+
}
|
|
105
|
+
for key, label in fit_map.items():
|
|
106
|
+
try:
|
|
107
|
+
val = stats[key].iloc[0] if hasattr(stats[key], 'iloc') else stats.get(key)
|
|
108
|
+
if val is not None:
|
|
109
|
+
lines.append(f" {label:<12} {float(val):.4f}")
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
# Interpretation hints
|
|
114
|
+
lines.append("")
|
|
115
|
+
lines.append("Fit guidelines: CFI/GFI > 0.95 (good), RMSEA < 0.05 (good), SRMR < 0.08 (good)")
|
|
116
|
+
|
|
117
|
+
return _fmt_sem("Structural Equation Model (SEM)", lines)
|
|
118
|
+
|
|
119
|
+
except Exception as exc:
|
|
120
|
+
return f"SEM error: {exc}"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@command("cfa", usage="cfa factor =~ x1 + x2 + x3 [+ factor2 =~ y1 + y2]")
|
|
124
|
+
def cmd_cfa(session: Session, args: str) -> str:
|
|
125
|
+
"""Confirmatory Factor Analysis — shortcut for SEM with =~ syntax only.
|
|
126
|
+
|
|
127
|
+
Examples:
|
|
128
|
+
cfa F1 =~ age + income + score
|
|
129
|
+
cfa 'F1 =~ x1 + x2 \\n F2 =~ y1 + y2'
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
import semopy
|
|
133
|
+
except ImportError:
|
|
134
|
+
return "CFA requires semopy. Install with: pip install semopy"
|
|
135
|
+
|
|
136
|
+
df = session.require_data()
|
|
137
|
+
model_str = args.strip().strip("\"'").replace("\\n", "\n")
|
|
138
|
+
if not model_str:
|
|
139
|
+
return "Usage: cfa 'F1 =~ x1 + x2 + x3'"
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
pandas_df = df.to_pandas()
|
|
143
|
+
model = semopy.Model(model_str)
|
|
144
|
+
model.fit(pandas_df)
|
|
145
|
+
|
|
146
|
+
params = model.inspect() # type: ignore[attr-defined]
|
|
147
|
+
stats = semopy.calc_stats(model)
|
|
148
|
+
|
|
149
|
+
lines = [f"CFA Model: {model_str.replace(chr(10), ' | ')}", f"N = {len(pandas_df)}", ""]
|
|
150
|
+
|
|
151
|
+
# Loadings only (=~ relationships)
|
|
152
|
+
loadings = params[params["op"] == "=~"] if "op" in params.columns else params
|
|
153
|
+
if not loadings.empty:
|
|
154
|
+
lines.append("Factor Loadings:")
|
|
155
|
+
lines.append(f" {'Factor':<12} {'Indicator':<12} {'Loading':>9} {'Std.Err':>9} {'p-value':>9}")
|
|
156
|
+
lines.append(" " + "-" * 55)
|
|
157
|
+
for _, row in loadings.iterrows():
|
|
158
|
+
p_str = f"{row.get('p-value', float('nan')):.4f}"
|
|
159
|
+
se_str = f"{row.get('Std. Err', float('nan')):.4f}"
|
|
160
|
+
lines.append(
|
|
161
|
+
f" {str(row.get('lval','')):<12} {str(row.get('rval','')):<12} "
|
|
162
|
+
f"{row.get('Estimate', float('nan')):>9.4f} {se_str:>9} {p_str:>9}"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
lines.append("")
|
|
166
|
+
lines.append("Fit Indices:")
|
|
167
|
+
for key, label in [("CFI", "CFI"), ("RMSEA", "RMSEA"), ("SRMR", "SRMR"), ("AIC", "AIC")]:
|
|
168
|
+
try:
|
|
169
|
+
val = stats[key].iloc[0]
|
|
170
|
+
lines.append(f" {label:<10} {float(val):.4f}")
|
|
171
|
+
except Exception:
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
return _fmt_sem("Confirmatory Factor Analysis (CFA)", lines)
|
|
175
|
+
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
return f"CFA error: {exc}"
|