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.
@@ -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)