statsformula 0.1.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.
- statsformula/__init__.py +41 -0
- statsformula/_api.py +478 -0
- statsformula/_bases.py +548 -0
- statsformula/_eval.py +1842 -0
- statsformula/_formula.py +484 -0
- statsformula/_lapack.py +355 -0
- statsformula/_lapack_dc.py +482 -0
- statsformula/_linalg.py +110 -0
- statsformula/_mgcv.py +1447 -0
- statsformula/_model.py +642 -0
- statsformula/_parser.py +650 -0
- statsformula/_rrng.py +87 -0
- statsformula/_rtypes.py +540 -0
- statsformula/_terms.py +425 -0
- statsformula/_version.py +1 -0
- statsformula/py.typed +0 -0
- statsformula-0.1.0.dist-info/METADATA +208 -0
- statsformula-0.1.0.dist-info/RECORD +20 -0
- statsformula-0.1.0.dist-info/WHEEL +4 -0
- statsformula-0.1.0.dist-info/licenses/LICENSE +338 -0
statsformula/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""statsformula: R model formulas for Python.
|
|
2
|
+
|
|
3
|
+
A faithful re-implementation of R's formula machinery (``terms``,
|
|
4
|
+
``model.frame``, ``model.matrix``, ``update``, ...), the *Formula* package
|
|
5
|
+
(multi-part formulas) and the ``mgcv`` smooth terms (``s``, ``te``, ``ti``,
|
|
6
|
+
``t2``), validated against R.
|
|
7
|
+
|
|
8
|
+
>>> import pandas as pd, statsformula as sf
|
|
9
|
+
>>> df = pd.DataFrame({"y": [1., 2, 3, 4], "x": [1., 2, 3, 5], "g": list("abab")})
|
|
10
|
+
>>> X = sf.model_matrix("y ~ g * log(x)", df)
|
|
11
|
+
>>> list(X.columns)
|
|
12
|
+
['(Intercept)', 'gb', 'log(x)', 'gb:log(x)']
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from ._version import __version__
|
|
16
|
+
from ._rtypes import RError, set_collation, get_collation, Factor, Logical, Character, RMatrix
|
|
17
|
+
from ._parser import FormulaSyntaxError, parse
|
|
18
|
+
from ._terms import Terms
|
|
19
|
+
from ._bases import (ContrastMatrix, contr_helmert, contr_poly, contr_SAS, contr_sum,
|
|
20
|
+
contr_treatment, spline_design)
|
|
21
|
+
from ._eval import options, register_function
|
|
22
|
+
from ._formula import Formula, RFormula
|
|
23
|
+
from ._model import ModelFrame
|
|
24
|
+
from ._api import (ModelMatrix, ModelSpec, SparseModelMatrix, all_names, all_vars, as_formula,
|
|
25
|
+
as_Formula, delete_response, deparse, dmatrices, dmatrix, drop_terms, formula,
|
|
26
|
+
get_all_vars, model_extract, model_frame, model_matrix, model_offset, model_part,
|
|
27
|
+
model_response, model_weights, reformulate, sparse_model_matrix, terms, update)
|
|
28
|
+
from . import _mgcv # registers s(), te(), ti(), t2()
|
|
29
|
+
from ._mgcv import interpret_gam, smooth_construct, smooth_con, predict_mat, gam_model_matrix, GamSpec
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"__version__", "RError", "FormulaSyntaxError", "set_collation", "get_collation",
|
|
33
|
+
"Factor", "Logical", "Character", "RMatrix", "parse", "Terms", "ContrastMatrix",
|
|
34
|
+
"contr_helmert", "contr_poly", "contr_SAS", "contr_sum", "contr_treatment", "spline_design",
|
|
35
|
+
"options", "register_function", "Formula", "RFormula", "ModelFrame", "ModelMatrix", "ModelSpec",
|
|
36
|
+
"SparseModelMatrix", "all_names", "all_vars", "as_formula", "as_Formula", "delete_response",
|
|
37
|
+
"deparse", "dmatrices", "dmatrix", "drop_terms", "formula", "get_all_vars", "model_extract",
|
|
38
|
+
"model_frame", "model_matrix", "model_offset", "model_part", "model_response", "model_weights",
|
|
39
|
+
"reformulate", "sparse_model_matrix", "terms", "update", "interpret_gam", "smooth_construct",
|
|
40
|
+
"GamSpec", "smooth_con", "predict_mat", "gam_model_matrix",
|
|
41
|
+
]
|
statsformula/_api.py
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"""Public functions mirroring R's modelling API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
from typing import Any, Dict, List, Mapping, Optional, Sequence, Union
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from ._bases import ContrastMatrix, contrast_from_spec
|
|
13
|
+
from ._formula import (Formula, RFormula, _resolve_env, capture_env, coerce_formula,
|
|
14
|
+
delete_response as _delete_response, drop_terms as _drop_terms,
|
|
15
|
+
reformulate as _reformulate, update_formula)
|
|
16
|
+
from ._model import (ModelFrame, _data_names, build_model_matrix, model_extract as _model_extract,
|
|
17
|
+
model_frame_from_terms, model_offset as _model_offset,
|
|
18
|
+
model_response as _model_response)
|
|
19
|
+
from ._parser import deparse as _deparse, parse
|
|
20
|
+
from ._rtypes import Factor, Logical, RError
|
|
21
|
+
from ._terms import Terms
|
|
22
|
+
|
|
23
|
+
FormulaLike = Union[str, RFormula, Formula, Terms]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
# Result containers
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
class ModelMatrix(pd.DataFrame):
|
|
31
|
+
"""A design matrix (``pandas.DataFrame``) with R's attributes.
|
|
32
|
+
|
|
33
|
+
* ``assign``: for each column, the 0-based term index (0 = intercept)
|
|
34
|
+
* ``contrasts``: contrasts used for each factor variable
|
|
35
|
+
* ``model_spec``: a :class:`ModelSpec` to build the same design on new data
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
_metadata = ["assign", "contrasts", "model_spec"]
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def _constructor(self):
|
|
42
|
+
return ModelMatrix
|
|
43
|
+
|
|
44
|
+
def __finalize__(self, other, method=None, **kwargs):
|
|
45
|
+
for name in self._metadata:
|
|
46
|
+
object.__setattr__(self, name, getattr(other, name, None))
|
|
47
|
+
return self
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SparseModelMatrix:
|
|
51
|
+
"""Sparse design matrix (``scipy.sparse.csc_matrix``), like ``Matrix::sparse.model.matrix``."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, matrix, columns, index, assign, contrasts, model_spec=None):
|
|
54
|
+
self.matrix = matrix
|
|
55
|
+
self.columns = list(columns)
|
|
56
|
+
self.index = index
|
|
57
|
+
self.assign = list(assign)
|
|
58
|
+
self.contrasts = contrasts
|
|
59
|
+
self.model_spec = model_spec
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def shape(self):
|
|
63
|
+
return self.matrix.shape
|
|
64
|
+
|
|
65
|
+
def toarray(self) -> np.ndarray:
|
|
66
|
+
return self.matrix.toarray()
|
|
67
|
+
|
|
68
|
+
def to_pandas(self, sparse: bool = True) -> pd.DataFrame:
|
|
69
|
+
if sparse:
|
|
70
|
+
df = pd.DataFrame.sparse.from_spmatrix(self.matrix, index=self.index, columns=self.columns)
|
|
71
|
+
else:
|
|
72
|
+
df = pd.DataFrame(self.matrix.toarray(), index=self.index, columns=self.columns)
|
|
73
|
+
return df
|
|
74
|
+
|
|
75
|
+
def __repr__(self):
|
|
76
|
+
return "<SparseModelMatrix %dx%d, %d stored values; columns=%r>" % (
|
|
77
|
+
self.shape[0], self.shape[1], self.matrix.nnz, self.columns)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ---------------------------------------------------------------------------
|
|
81
|
+
# Model specification (stateful transforms for new data)
|
|
82
|
+
# ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
class ModelSpec:
|
|
85
|
+
"""Everything needed to rebuild a design matrix on new data.
|
|
86
|
+
|
|
87
|
+
It plays the role of R's ``terms`` object with ``predvars``, ``xlevels``
|
|
88
|
+
and ``contrasts`` (what ``predict()`` uses), or formulaic's ``ModelSpec``.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def __init__(self, terms: Terms, state: dict, xlevels: Dict[str, List[str]],
|
|
92
|
+
contrasts: Dict[str, Any], na_action: str, sparse: bool, env):
|
|
93
|
+
self.terms = terms
|
|
94
|
+
self.state = state
|
|
95
|
+
self.xlevels = xlevels
|
|
96
|
+
self.contrasts = contrasts
|
|
97
|
+
self.na_action = na_action
|
|
98
|
+
self.sparse = sparse
|
|
99
|
+
self.env = env
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def column_names(self):
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
def get_model_frame(self, data, na_action: Optional[str] = None) -> ModelFrame:
|
|
106
|
+
t = _delete_response(self.terms)
|
|
107
|
+
return model_frame_from_terms(t, data, env=self.env, na_action=na_action or self.na_action,
|
|
108
|
+
xlev=self.xlevels, state=copy.deepcopy(self.state), fitting=False)
|
|
109
|
+
|
|
110
|
+
def get_model_matrix(self, data, na_action: Optional[str] = None, output: str = "pandas"):
|
|
111
|
+
mf = self.get_model_frame(data, na_action)
|
|
112
|
+
t = mf.terms
|
|
113
|
+
X, cols, assign, contr = build_model_matrix(t, mf, sparse=self.sparse,
|
|
114
|
+
contrasts_override=self.contrasts)
|
|
115
|
+
return _wrap_matrix(X, cols, mf.index, assign, contr, self, self.sparse, output)
|
|
116
|
+
|
|
117
|
+
def __repr__(self):
|
|
118
|
+
return "<ModelSpec: %s>" % (self.terms.formula,)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _resolved_contrasts(mf: ModelFrame, contrasts_out) -> Dict[str, ContrastMatrix]:
|
|
122
|
+
res = {}
|
|
123
|
+
for nm, spec in contrasts_out.items():
|
|
124
|
+
v = mf.variables.get(nm)
|
|
125
|
+
if isinstance(v, Logical):
|
|
126
|
+
levels = ["FALSE", "TRUE"]
|
|
127
|
+
ordered = False
|
|
128
|
+
elif isinstance(v, Factor):
|
|
129
|
+
levels = v.levels
|
|
130
|
+
ordered = v.ordered
|
|
131
|
+
else:
|
|
132
|
+
from ._rtypes import as_factor
|
|
133
|
+
f = as_factor(v)
|
|
134
|
+
levels, ordered = f.levels, f.ordered
|
|
135
|
+
if spec is None:
|
|
136
|
+
from ._eval import OPTIONS
|
|
137
|
+
spec = OPTIONS["contrasts"][1 if ordered else 0]
|
|
138
|
+
if isinstance(spec, ContrastMatrix):
|
|
139
|
+
res[nm] = spec
|
|
140
|
+
else:
|
|
141
|
+
try:
|
|
142
|
+
res[nm] = contrast_from_spec(spec, levels)
|
|
143
|
+
except RError:
|
|
144
|
+
res[nm] = spec
|
|
145
|
+
return res
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def _wrap_matrix(X, cols, index, assign, contrasts, spec, sparse, output):
|
|
149
|
+
if sparse:
|
|
150
|
+
return SparseModelMatrix(X, cols, index, assign, contrasts, spec)
|
|
151
|
+
if output == "numpy":
|
|
152
|
+
return X
|
|
153
|
+
mm = ModelMatrix(X, index=index, columns=pd.Index(cols, dtype=object))
|
|
154
|
+
object.__setattr__(mm, "assign", list(assign))
|
|
155
|
+
object.__setattr__(mm, "contrasts", contrasts)
|
|
156
|
+
object.__setattr__(mm, "model_spec", spec)
|
|
157
|
+
return mm
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
# helpers
|
|
162
|
+
# ---------------------------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
def _get_env(env, formula, depth):
|
|
165
|
+
if env is not None:
|
|
166
|
+
return env if not isinstance(env, int) else _resolve_env(env, depth + 1)
|
|
167
|
+
fenv = getattr(formula, "env", None)
|
|
168
|
+
caller = capture_env(depth + 1)
|
|
169
|
+
if fenv:
|
|
170
|
+
from collections import ChainMap
|
|
171
|
+
return ChainMap(fenv, caller) if not isinstance(fenv, ChainMap) else ChainMap(*fenv.maps, *caller.maps)
|
|
172
|
+
return caller
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def formula(x, env=None) -> RFormula:
|
|
176
|
+
"""Create a base R formula (R's ``formula()`` / ``as.formula()``)."""
|
|
177
|
+
if isinstance(x, RFormula):
|
|
178
|
+
return x
|
|
179
|
+
if isinstance(x, Formula):
|
|
180
|
+
return x.formula()
|
|
181
|
+
return RFormula(x, env if env is not None else capture_env(1))
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
as_formula = formula
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def Formula_(x, *more, env=None) -> Formula:
|
|
188
|
+
return Formula(x, *more, env=env if env is not None else capture_env(1))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def as_Formula(x, *more, env=None) -> Formula:
|
|
192
|
+
"""R's ``as.Formula(...)``: combine formulas into one multi-part Formula."""
|
|
193
|
+
return Formula(x, *more, env=env if env is not None else capture_env(1))
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def terms(x: FormulaLike, data=None, keep_order: bool = False, specials=None, simplify: bool = False,
|
|
197
|
+
allow_dot_as_name: bool = False, lhs=None, rhs=None, dot: str = "separate") -> Terms:
|
|
198
|
+
"""R's ``terms()`` for formulas and Formulas."""
|
|
199
|
+
if isinstance(x, Terms):
|
|
200
|
+
return x
|
|
201
|
+
if isinstance(x, ModelFrame):
|
|
202
|
+
return x.terms
|
|
203
|
+
f = coerce_formula(x)
|
|
204
|
+
if isinstance(f, Formula):
|
|
205
|
+
return f.terms(lhs=lhs, rhs=rhs, data=data, dot=dot, keep_order=keep_order, specials=specials)
|
|
206
|
+
return f.terms(data=data, keep_order=keep_order, specials=specials, simplify=simplify,
|
|
207
|
+
allow_dot_as_name=allow_dot_as_name)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def model_frame(formula: FormulaLike, data=None, subset=None, na_action: Optional[str] = None,
|
|
211
|
+
drop_unused_levels: bool = False, xlev=None, weights=None, offset=None,
|
|
212
|
+
lhs=None, rhs=None, dot: str = "separate", env=None, **extra) -> ModelFrame:
|
|
213
|
+
"""R's ``model.frame()``.
|
|
214
|
+
|
|
215
|
+
``subset``, ``weights`` and ``offset`` may be expressions (strings
|
|
216
|
+
evaluated in ``data``) or arrays; extra keyword arguments become
|
|
217
|
+
``(name)`` columns, retrievable with :func:`model_extract`.
|
|
218
|
+
"""
|
|
219
|
+
env = _get_env(env, formula, 1)
|
|
220
|
+
if isinstance(formula, ModelFrame):
|
|
221
|
+
return formula
|
|
222
|
+
t = terms(formula, data=data, lhs=lhs, rhs=rhs, dot=dot) if not isinstance(formula, Terms) else formula
|
|
223
|
+
return model_frame_from_terms(t, data, env=env, subset=subset, na_action=na_action,
|
|
224
|
+
drop_unused_levels=drop_unused_levels, xlev=xlev, weights=weights,
|
|
225
|
+
offset=offset, extra_args=extra or None)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _model_matrix_impl(x, data, contrasts, xlev, lhs, rhs, sparse, output, na_action,
|
|
229
|
+
drop_unused_levels, env, dot):
|
|
230
|
+
if isinstance(x, (str, RFormula)) and not isinstance(data, ModelFrame):
|
|
231
|
+
from ._mgcv import gam_model_matrix, has_smooths
|
|
232
|
+
try:
|
|
233
|
+
smooth = has_smooths(x)
|
|
234
|
+
except Exception:
|
|
235
|
+
smooth = False
|
|
236
|
+
if smooth:
|
|
237
|
+
if sparse:
|
|
238
|
+
raise RError("sparse model matrices are not available for mgcv smooths")
|
|
239
|
+
return gam_model_matrix(x, data, na_action=na_action, env=env, output=output)
|
|
240
|
+
if isinstance(x, ModelFrame) and data is None:
|
|
241
|
+
mf = x
|
|
242
|
+
t = mf.terms
|
|
243
|
+
else:
|
|
244
|
+
f = x
|
|
245
|
+
if not isinstance(f, (Terms, ModelFrame)):
|
|
246
|
+
f = coerce_formula(f)
|
|
247
|
+
if isinstance(f, Formula):
|
|
248
|
+
if dot == "separate":
|
|
249
|
+
form = f.formula(lhs=lhs, rhs=rhs, collapse=(False, True))
|
|
250
|
+
t = _delete_response(form.terms(data=data))
|
|
251
|
+
else:
|
|
252
|
+
t = _delete_response(f.terms(lhs=lhs, rhs=rhs, data=data, dot=dot))
|
|
253
|
+
elif isinstance(f, Terms):
|
|
254
|
+
t = f
|
|
255
|
+
elif isinstance(f, ModelFrame):
|
|
256
|
+
t = f.terms
|
|
257
|
+
else:
|
|
258
|
+
t = f.terms(data=data if not isinstance(data, ModelFrame) else None)
|
|
259
|
+
if isinstance(data, ModelFrame):
|
|
260
|
+
missing = [v for v in t.variables if v not in data.variables]
|
|
261
|
+
if missing:
|
|
262
|
+
raise RError("model frame and formula mismatch in model.matrix()")
|
|
263
|
+
vars_ = OrderedDict((v, data.variables[v]) for v in t.variables)
|
|
264
|
+
mf = ModelFrame(vars_, t, data.index, data.na_action, data.extras, data.state)
|
|
265
|
+
else:
|
|
266
|
+
mf = model_frame_from_terms(t, data, env=env, na_action=na_action, xlev=xlev,
|
|
267
|
+
drop_unused_levels=drop_unused_levels)
|
|
268
|
+
t = mf.terms
|
|
269
|
+
X, cols, assign, contr = build_model_matrix(t, mf, contrasts_arg=contrasts, sparse=sparse)
|
|
270
|
+
spec = ModelSpec(t, copy.deepcopy(mf.state), mf.xlevels(), _resolved_contrasts(mf, contr),
|
|
271
|
+
na_action or "na.omit", sparse, env)
|
|
272
|
+
return _wrap_matrix(X, cols, mf.index, assign, contr, spec, sparse, output)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def model_matrix(formula: FormulaLike, data=None, contrasts: Optional[Mapping[str, Any]] = None,
|
|
276
|
+
xlev=None, lhs=None, rhs=1, output: str = "pandas", na_action: Optional[str] = None,
|
|
277
|
+
drop_unused_levels: bool = False, dot: str = "separate", env=None):
|
|
278
|
+
"""R's ``model.matrix()``.
|
|
279
|
+
|
|
280
|
+
``formula`` may be a string, :class:`RFormula`, :class:`Formula`,
|
|
281
|
+
:class:`Terms` or :class:`ModelFrame`. ``contrasts`` maps variable names
|
|
282
|
+
to a contrast name (``"contr.sum"``), a matrix or a callable, as R's
|
|
283
|
+
``contrasts.arg``. For multi-part formulas, ``rhs`` selects the part
|
|
284
|
+
(1-based, as in the Formula package).
|
|
285
|
+
"""
|
|
286
|
+
env = _get_env(env, formula, 1)
|
|
287
|
+
return _model_matrix_impl(formula, data, contrasts, xlev, lhs, rhs, False, output, na_action,
|
|
288
|
+
drop_unused_levels, env, dot)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def sparse_model_matrix(formula: FormulaLike, data=None, contrasts: Optional[Mapping[str, Any]] = None,
|
|
292
|
+
xlev=None, lhs=None, rhs=1, na_action: Optional[str] = None,
|
|
293
|
+
drop_unused_levels: bool = False, dot: str = "separate", env=None) -> SparseModelMatrix:
|
|
294
|
+
"""``Matrix::sparse.model.matrix()``: same columns as :func:`model_matrix`, stored sparsely."""
|
|
295
|
+
env = _get_env(env, formula, 1)
|
|
296
|
+
return _model_matrix_impl(formula, data, contrasts, xlev, lhs, rhs, True, "pandas", na_action,
|
|
297
|
+
drop_unused_levels, env, dot)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def model_response(mf: ModelFrame, type: str = "any"):
|
|
301
|
+
"""R's ``model.response``."""
|
|
302
|
+
return _model_response(mf, type)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def model_offset(mf: ModelFrame):
|
|
306
|
+
"""R's ``model.offset``."""
|
|
307
|
+
return _model_offset(mf)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def model_weights(mf: ModelFrame):
|
|
311
|
+
"""R's ``model.weights``."""
|
|
312
|
+
return _model_extract(mf, "weights")
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def model_extract(mf: ModelFrame, component: str):
|
|
316
|
+
"""R's ``model.extract(frame, component)`` (``"response"``, ``"offset"``, ``"weights"``, ...)."""
|
|
317
|
+
return _model_extract(mf, component)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def model_part(formula: Formula, data: ModelFrame, lhs=0, rhs=0, drop: bool = False, dot=None):
|
|
321
|
+
"""``Formula::model.part``: the columns of a model frame belonging to some parts."""
|
|
322
|
+
F = formula if isinstance(formula, Formula) else Formula(formula)
|
|
323
|
+
if lhs is None:
|
|
324
|
+
lhs = list(range(1, len(F.lhs_parts) + 1))
|
|
325
|
+
if rhs is None:
|
|
326
|
+
rhs = list(range(1, len(F.rhs_parts) + 1))
|
|
327
|
+
lz = all(i == 0 for i in (lhs if isinstance(lhs, (list, tuple)) else [lhs]))
|
|
328
|
+
rz = all(i == 0 for i in (rhs if isinstance(rhs, (list, tuple)) else [rhs]))
|
|
329
|
+
if lz and rz:
|
|
330
|
+
raise RError("Either some 'lhs' or 'rhs' has to be selected.")
|
|
331
|
+
if not isinstance(data, ModelFrame):
|
|
332
|
+
raise RError("'data' does not seem to be an appropriate 'model.frame'")
|
|
333
|
+
mt = F.terms(lhs=lhs, rhs=rhs)
|
|
334
|
+
ix = mt.variables
|
|
335
|
+
missing = [v for v in ix if v not in data]
|
|
336
|
+
if missing:
|
|
337
|
+
raise RError("'data' does not seem to be an appropriate 'model.frame': %s not found" %
|
|
338
|
+
", ".join("'%s'" % m for m in missing))
|
|
339
|
+
df = data.to_pandas()
|
|
340
|
+
cols = []
|
|
341
|
+
for v in ix:
|
|
342
|
+
val = data[v]
|
|
343
|
+
from ._rtypes import RMatrix
|
|
344
|
+
if isinstance(val, RMatrix):
|
|
345
|
+
cn = val.colnames or [str(j + 1) for j in range(val.ncol)]
|
|
346
|
+
cols.extend("%s.%s" % (v, c) for c in cn)
|
|
347
|
+
else:
|
|
348
|
+
cols.append(v)
|
|
349
|
+
out = df[cols]
|
|
350
|
+
if drop and out.shape[1] == 1:
|
|
351
|
+
return out.iloc[:, 0]
|
|
352
|
+
return out
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def update(old, new):
|
|
356
|
+
"""R's ``update()`` for formulas (``update.formula``) and Formulas (``update.Formula``)."""
|
|
357
|
+
if isinstance(old, Formula) or isinstance(new, Formula):
|
|
358
|
+
o = old if isinstance(old, Formula) else Formula(old)
|
|
359
|
+
return o.update(new)
|
|
360
|
+
o = coerce_formula(old)
|
|
361
|
+
if isinstance(o, Formula):
|
|
362
|
+
return o.update(new)
|
|
363
|
+
n = coerce_formula(new)
|
|
364
|
+
if isinstance(n, Formula):
|
|
365
|
+
return Formula(o).update(n)
|
|
366
|
+
return update_formula(o, n)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def all_vars(x, functions: bool = False) -> List[str]:
|
|
370
|
+
"""R's ``all.vars``."""
|
|
371
|
+
if isinstance(x, Terms):
|
|
372
|
+
x = x.formula
|
|
373
|
+
f = coerce_formula(x) if isinstance(x, str) else x
|
|
374
|
+
if isinstance(f, Formula):
|
|
375
|
+
return f.all_vars()
|
|
376
|
+
return f.all_vars()
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def all_names(x, functions: bool = True, unique: bool = False) -> List[str]:
|
|
380
|
+
"""R's ``all.names``."""
|
|
381
|
+
from ._parser import all_names as an
|
|
382
|
+
if isinstance(x, (RFormula,)):
|
|
383
|
+
return an(x.node, functions, unique)
|
|
384
|
+
if isinstance(x, Formula):
|
|
385
|
+
return an(x.formula().node, functions, unique)
|
|
386
|
+
return an(parse(x), functions, unique)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def reformulate(termlabels: Sequence[str], response=None, intercept: bool = True) -> RFormula:
|
|
390
|
+
"""R's ``reformulate``."""
|
|
391
|
+
return _reformulate(termlabels, response, intercept, env=capture_env(1))
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def drop_terms(termobj: Terms, dropx=None, keep_response: bool = False) -> Terms:
|
|
395
|
+
"""R's ``drop.terms`` (1-based ``dropx``)."""
|
|
396
|
+
return _drop_terms(termobj, dropx, keep_response)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def delete_response(termobj: Terms) -> Terms:
|
|
400
|
+
"""R's ``delete.response``."""
|
|
401
|
+
return _delete_response(termobj)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def get_all_vars(formula: FormulaLike, data) -> pd.DataFrame:
|
|
405
|
+
"""R's ``get_all_vars``: the raw data columns used by a formula."""
|
|
406
|
+
vars_ = all_vars(formula)
|
|
407
|
+
if not isinstance(data, pd.DataFrame):
|
|
408
|
+
data = pd.DataFrame(data)
|
|
409
|
+
cols = [v for v in vars_ if v in data.columns]
|
|
410
|
+
missing = [v for v in vars_ if v not in data.columns and v != "."]
|
|
411
|
+
if missing:
|
|
412
|
+
raise RError("object '%s' not found" % missing[0])
|
|
413
|
+
if "." in vars_:
|
|
414
|
+
cols = list(data.columns)
|
|
415
|
+
return data[cols]
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def deparse(x) -> str:
|
|
419
|
+
"""R's ``deparse`` for formulas and expressions."""
|
|
420
|
+
if isinstance(x, (RFormula, Formula)):
|
|
421
|
+
return str(x)
|
|
422
|
+
if isinstance(x, str):
|
|
423
|
+
return _deparse(parse(x))
|
|
424
|
+
return _deparse(x)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
# ---------------------------------------------------------------------------
|
|
428
|
+
# statsmodels / patsy style helpers
|
|
429
|
+
# ---------------------------------------------------------------------------
|
|
430
|
+
|
|
431
|
+
def dmatrix(formula: FormulaLike, data=None, return_type: str = "dataframe", contrasts=None,
|
|
432
|
+
na_action: Optional[str] = None, env=None, rhs=1):
|
|
433
|
+
"""Design matrix for the right hand side (patsy/statsmodels style)."""
|
|
434
|
+
env = _get_env(env, formula, 1)
|
|
435
|
+
X = _model_matrix_impl(formula, data, contrasts, None, None, rhs, False, "pandas", na_action, False, env,
|
|
436
|
+
"separate")
|
|
437
|
+
return X if return_type == "dataframe" else X.to_numpy()
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def dmatrices(formula: FormulaLike, data=None, return_type: str = "dataframe", contrasts=None,
|
|
441
|
+
na_action: Optional[str] = None, env=None, rhs=1):
|
|
442
|
+
"""``(y, X)`` as used by ``statsmodels`` (rows with NA in either are removed together)."""
|
|
443
|
+
env = _get_env(env, formula, 1)
|
|
444
|
+
f = coerce_formula(formula)
|
|
445
|
+
if isinstance(f, Formula):
|
|
446
|
+
mf = model_frame(f, data, na_action=na_action, env=env)
|
|
447
|
+
X = _model_matrix_impl(f, mf, contrasts, None, None, rhs, False, "pandas", na_action, False, env,
|
|
448
|
+
"separate")
|
|
449
|
+
y = model_part(f, mf, lhs=1)
|
|
450
|
+
else:
|
|
451
|
+
from ._mgcv import has_smooths
|
|
452
|
+
if has_smooths(f):
|
|
453
|
+
X = _model_matrix_impl(f, data, contrasts, None, None, rhs, False, "pandas", na_action, False, env,
|
|
454
|
+
"separate")
|
|
455
|
+
mf = model_frame(f.terms(data=data) if False else _gam_fake_terms(f, data), data, na_action=na_action, env=env)
|
|
456
|
+
y = model_response(mf).to_frame() if mf.terms.response else None
|
|
457
|
+
if y is None:
|
|
458
|
+
raise RError("model is missing a response (left hand side)")
|
|
459
|
+
y = y.loc[X.index]
|
|
460
|
+
if return_type == "dataframe":
|
|
461
|
+
return y, X
|
|
462
|
+
return y.to_numpy(), X.to_numpy()
|
|
463
|
+
mf = model_frame(f, data, na_action=na_action, env=env)
|
|
464
|
+
if not mf.terms.response:
|
|
465
|
+
raise RError("model is missing a response (left hand side)")
|
|
466
|
+
X = _model_matrix_impl(mf, None, contrasts, None, None, rhs, False, "pandas", na_action, False, env,
|
|
467
|
+
"separate")
|
|
468
|
+
y = model_response(mf)
|
|
469
|
+
if isinstance(y, pd.Series):
|
|
470
|
+
y = y.to_frame()
|
|
471
|
+
if return_type == "dataframe":
|
|
472
|
+
return y, X
|
|
473
|
+
return y.to_numpy(), X.to_numpy()
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _gam_fake_terms(f, data):
|
|
477
|
+
from ._mgcv import interpret_gam
|
|
478
|
+
return interpret_gam(f).fake_formula.terms(data=data)
|