# fiasto-py
[](https://badge.fury.io/py/fiasto-py)
[](https://pypi.org/project/fiasto-py/)
[](https://opensource.org/licenses/MIT)
<h1 align="center">fiasto-py</h1>
<p align="center">
<img src="img/mango_pixle2_py.png" alt="logo" width="240">
</p>
---
<p align="center">Pronouned like <strong>fiasco</strong>, but with a <strong>t</strong> instead of a <strong>c</strong></p>
---
<p align="center">(F)ormulas (I)n (AST) (O)ut</p>
Python bindings for [fiasto](https://github.com/alexhallam/fiasto) - A language-agnostic modern Wilkinson's formula parser and lexer.
## π― Features
- **Parse Wilkinson's Formulas**: Convert formula strings into structured JSON metadata
- **Tokenize Formulas**: Break down formulas into individual tokens with detailed information
- **Python Dictionaries**: Returns native Python dictionaries for easy integration
## π― Simple API
- `parse_formula()` - Takes a Wilkinsonβs formula string and returns a Python dictionary
- `lex_formula()` - Tokenizes a formula string and returns a Python dictionary
## π Quick Start
### Installation
**Install from PyPI** (recommended):
```bash
pip install fiasto-py
```
### Usage
#### Usage: Parse Formula
```python
import fiasto_py
from pprint import pprint
# Parse a formula into structured metadata
print("="*30)
print("Parse Formula")
print("="*30)
result = fiasto_py.parse_formula("y ~ x1 + x2 + (1|group)")
pprint(result, compact = True)
```
**Output:**
```bash
==============================
Parse Formula
==============================
{'all_generated_columns': ['y', 'x1', 'x2', 'group'],
'columns': {'group': {'generated_columns': ['group'],
'id': 4,
'interactions': [],
'random_effects': [{'correlated': True,
'grouping_variable': 'group',
'has_intercept': True,
'includes_interactions': [],
'kind': 'grouping',
'variables': []}],
'roles': ['GroupingVariable'],
'transformations': []},
'x1': {'generated_columns': ['x1'],
'id': 2,
'interactions': [],
'random_effects': [],
'roles': ['FixedEffect'],
'transformations': []},
'x2': {'generated_columns': ['x2'],
'id': 3,
'interactions': [],
'random_effects': [],
'roles': ['FixedEffect'],
'transformations': []},
'y': {'generated_columns': ['y'],
'id': 1,
'interactions': [],
'random_effects': [],
'roles': ['Response'],
'transformations': []}},
'formula': 'y ~ x1 + x2 + (1|group)',
'metadata': {'family': None,
'has_intercept': True,
'has_uncorrelated_slopes_and_intercepts': False,
'is_random_effects_model': True}}
```
#### Usage: Lex Formula
```python
import fiasto_py
from pprint import pprint
print("="*30)
print("Lex Formula")
print("="*30)
tokens = fiasto_py.lex_formula("y ~ x1 + x2 + (1|group)")
pprint(tokens, compact = True)
```
**Output:**
```bash
==============================
Lex Formula
==============================
[{'lexeme': 'y', 'token': 'ColumnName'},
{'lexeme': '~', 'token': 'Tilde'},
{'lexeme': 'x1', 'token': 'ColumnName'},
{'lexeme': '+', 'token': 'Plus'},
{'lexeme': 'x2', 'token': 'ColumnName'},
{'lexeme': '+', 'token': 'Plus'},
{'lexeme': '(', 'token': 'FunctionStart'},
{'lexeme': '1', 'token': 'One'},
{'lexeme': '|', 'token': 'Pipe'},
{'lexeme': 'group', 'token': 'ColumnName'},
{'lexeme': ')', 'token': 'FunctionEnd'}]
```
### Simple OLS Regression
```python
import fiasto_py
import polars as pl
import numpy as np
from pprint import pprint
# Load data
mtcars_path = "https://gist.githubusercontent.com/seankross/a412dfbd88b3db70b74b/raw/5f23f993cd87c283ce766e7ac6b329ee7cc2e1d1/mtcars.csv"
df = pl.read_csv(mtcars_path)
# Parse formula
formula = "mpg ~ wt + cyl"
result = fiasto_py.parse_formula(formula)
pprint(result)
# Find the response column(s)
response_cols = [
col for col, details in result["columns"].items()
if "Response" in details["roles"]
]
# Find non-response columns
preds = [
col for col, details in result["columns"].items()
if "Response" not in details["roles"]
]
# Has intercept
has_intercept = result["metadata"]["has_intercept"]
# Prepare data matrices
X = df.select(preds).to_numpy()
y = df.select(response_cols).to_numpy().ravel()
# Add intercept if metadata says so
if has_intercept:
X_with_intercept = np.column_stack([np.ones(X.shape[0]), X])
else:
X_with_intercept = X
# Solve normal equations: (X'X)^-1 X'y
XTX = X_with_intercept.T @ X_with_intercept
XTy = X_with_intercept.T @ y
coefficients = np.linalg.solve(XTX, XTy)
# Extract intercept and slopes
if has_intercept:
intercept = coefficients[0]
slopes = coefficients[1:]
else:
intercept = 0.0
slopes = coefficients
# Calculate R2
y_pred = X_with_intercept @ coefficients
ss_res = np.sum((y - y_pred) ** 2)
ss_tot = np.sum((y - np.mean(y)) ** 2)
r_squared = 1 - (ss_res / ss_tot)
# Prep Output
# Combine intercept and slopes into one dict
coef_dict = {"intercept": intercept} | dict(zip(preds, slopes))
# Create a tidy DataFrame
coef_df = pl.DataFrame(
{
"term": list(coef_dict.keys()),
"estimate": list(coef_dict.values())
}
)
# Print results
print(f"Formula: {formula}")
print(f"RΒ² Score: {r_squared:.3f}")
print(coef_df)
```
**Output:**
```json
{'all_generated_columns': ['mpg', 'intercept', 'wt', 'cyl'],
'all_generated_columns_formula_order': {'1': 'mpg',
'2': 'intercept',
'3': 'wt',
'4': 'cyl'},
'columns': {'cyl': {'generated_columns': ['cyl'],
'id': 3,
'interactions': [],
'random_effects': [],
'roles': ['Identity'],
'transformations': []},
'mpg': {'generated_columns': ['mpg'],
'id': 1,
'interactions': [],
'random_effects': [],
'roles': ['Response'],
'transformations': []},
'wt': {'generated_columns': ['wt'],
'id': 2,
'interactions': [],
'random_effects': [],
'roles': ['Identity'],
'transformations': []}},
'formula': 'mpg ~ wt + cyl',
'metadata': {'family': None,
'has_intercept': True,
'has_uncorrelated_slopes_and_intercepts': False,
'is_random_effects_model': False,
'response_variable_count': 1}}
Formula: mpg ~ wt + cyl
RΒ² Score: 0.830
shape: (3, 2)
βββββββββββββ¬ββββββββββββ
β term β estimate β
β --- β --- β
β str β f64 β
βββββββββββββͺββββββββββββ‘
β intercept β 39.686261 β
β cyl β -1.507795 β
β wt β -3.190972 β
βββββββββββββ΄ββββββββββββ
```
## π Supported Formula Syntax
`fiasto` supports comprehensive Wilkinson's notation including:
- **Basic formulas**: `y ~ x1 + x2`
- **Interactions**: `y ~ x1 * x2`
- **Smooth terms**: `y ~ s(z)`
- **Random effects**: `y ~ x + (1|group)`
- **Complex random effects**: `y ~ x + (1+x|group)`
### Supported Formulas (Coming Soon)
- **Multivariate models**: `mvbind(y1, y2) ~ x + (1|g)`
- **Non-linear models**: `y ~ a1 - a2^x, a1 ~ 1, a2 ~ x + (x|g), nl = TRUE`
For the complete reference, see the [fiasto documentation](https://docs.rs/fiasto/latest/fiasto/).
## π¦ PyPI Package
The package is available on PyPI and can be installed with:
```bash
pip install fiasto-py
```
- **PyPI Page**: [pypi.org/project/fiasto-py](https://pypi.org/project/fiasto-py/)
- **Source Code**: [github.com/alexhallam/fiasto-py](https://github.com/alexhallam/fiasto-py)
- **Documentation**: This README and inline docstrings
## π API Reference
### `parse_formula(formula: str) -> dict`
Parse a Wilkinson's formula string and return structured JSON metadata.
**Parameters:**
- `formula` (str): The formula string to parse
**Returns:**
- `dict`: Structured metadata describing the formula
**Raises:**
- `ValueError`: If the formula is invalid or parsing fails
### `lex_formula(formula: str) -> dict`
Tokenize a formula string and return JSON describing each token.
**Parameters:**
- `formula` (str): The formula string to tokenize
**Returns:**
- `dict`: Token information for each element in the formula
**Raises:**
- `ValueError`: If the formula is invalid or lexing fails
## π€ Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## π Acknowledgments
- [fiasto](https://github.com/alexhallam/fiasto) - The underlying Rust library
- [PyO3](https://pyo3.rs/) - Python-Rust bindings
- [maturin](https://maturin.rs/) - Build system for Python extensions
- [PyPI](https://pypi.org/) - Python Package Index for distribution
## π License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
Raw data
{
"_id": null,
"home_page": null,
"name": "fiasto-py",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "Alex Hallam <alex@example.com>",
"keywords": "formula, parser, statistics, mixed-effects, wilkinson, rust, pyo3",
"author": null,
"author_email": "Alex Hallam <alex@example.com>",
"download_url": null,
"platform": null,
"description": "# fiasto-py\n\n[](https://badge.fury.io/py/fiasto-py)\n[](https://pypi.org/project/fiasto-py/)\n[](https://opensource.org/licenses/MIT)\n\n<h1 align=\"center\">fiasto-py</h1>\n\n<p align=\"center\">\n <img src=\"img/mango_pixle2_py.png\" alt=\"logo\" width=\"240\">\n</p>\n\n---\n\n<p align=\"center\">Pronouned like <strong>fiasco</strong>, but with a <strong>t</strong> instead of a <strong>c</strong></p>\n\n---\n\n<p align=\"center\">(F)ormulas (I)n (AST) (O)ut</p>\n\nPython bindings for [fiasto](https://github.com/alexhallam/fiasto) - A language-agnostic modern Wilkinson's formula parser and lexer.\n\n## \ud83c\udfaf Features\n\n- **Parse Wilkinson's Formulas**: Convert formula strings into structured JSON metadata\n- **Tokenize Formulas**: Break down formulas into individual tokens with detailed information\n- **Python Dictionaries**: Returns native Python dictionaries for easy integration\n\n## \ud83c\udfaf Simple API\n\n- `parse_formula()` - Takes a Wilkinson\u2019s formula string and returns a Python dictionary\n- `lex_formula()` - Tokenizes a formula string and returns a Python dictionary\n\n## \ud83d\ude80 Quick Start\n\n### Installation\n\n**Install from PyPI** (recommended):\n```bash\npip install fiasto-py\n```\n\n### Usage\n\n#### Usage: Parse Formula\n\n```python\nimport fiasto_py\nfrom pprint import pprint\n# Parse a formula into structured metadata\nprint(\"=\"*30)\nprint(\"Parse Formula\")\nprint(\"=\"*30)\nresult = fiasto_py.parse_formula(\"y ~ x1 + x2 + (1|group)\")\npprint(result, compact = True)\n```\n\n**Output:**\n\n```bash\n==============================\nParse Formula\n==============================\n{'all_generated_columns': ['y', 'x1', 'x2', 'group'],\n 'columns': {'group': {'generated_columns': ['group'],\n 'id': 4,\n 'interactions': [],\n 'random_effects': [{'correlated': True,\n 'grouping_variable': 'group',\n 'has_intercept': True,\n 'includes_interactions': [],\n 'kind': 'grouping',\n 'variables': []}],\n 'roles': ['GroupingVariable'],\n 'transformations': []},\n 'x1': {'generated_columns': ['x1'],\n 'id': 2,\n 'interactions': [],\n 'random_effects': [],\n 'roles': ['FixedEffect'],\n 'transformations': []},\n 'x2': {'generated_columns': ['x2'],\n 'id': 3,\n 'interactions': [],\n 'random_effects': [],\n 'roles': ['FixedEffect'],\n 'transformations': []},\n 'y': {'generated_columns': ['y'],\n 'id': 1,\n 'interactions': [],\n 'random_effects': [],\n 'roles': ['Response'],\n 'transformations': []}},\n 'formula': 'y ~ x1 + x2 + (1|group)',\n 'metadata': {'family': None,\n 'has_intercept': True,\n 'has_uncorrelated_slopes_and_intercepts': False,\n 'is_random_effects_model': True}}\n```\n\n#### Usage: Lex Formula\n\n```python\nimport fiasto_py\nfrom pprint import pprint\nprint(\"=\"*30)\nprint(\"Lex Formula\")\nprint(\"=\"*30)\ntokens = fiasto_py.lex_formula(\"y ~ x1 + x2 + (1|group)\")\npprint(tokens, compact = True)\n```\n\n**Output:**\n\n```bash\n==============================\nLex Formula\n==============================\n[{'lexeme': 'y', 'token': 'ColumnName'},\n {'lexeme': '~', 'token': 'Tilde'},\n {'lexeme': 'x1', 'token': 'ColumnName'},\n {'lexeme': '+', 'token': 'Plus'},\n {'lexeme': 'x2', 'token': 'ColumnName'},\n {'lexeme': '+', 'token': 'Plus'},\n {'lexeme': '(', 'token': 'FunctionStart'},\n {'lexeme': '1', 'token': 'One'},\n {'lexeme': '|', 'token': 'Pipe'},\n {'lexeme': 'group', 'token': 'ColumnName'},\n {'lexeme': ')', 'token': 'FunctionEnd'}]\n```\n\n### Simple OLS Regression\n\n```python\nimport fiasto_py\nimport polars as pl\nimport numpy as np\nfrom pprint import pprint\n\n# Load data\nmtcars_path = \"https://gist.githubusercontent.com/seankross/a412dfbd88b3db70b74b/raw/5f23f993cd87c283ce766e7ac6b329ee7cc2e1d1/mtcars.csv\"\ndf = pl.read_csv(mtcars_path)\n\n# Parse formula\nformula = \"mpg ~ wt + cyl\"\nresult = fiasto_py.parse_formula(formula)\n\npprint(result)\n\n# Find the response column(s)\nresponse_cols = [\n col for col, details in result[\"columns\"].items()\n if \"Response\" in details[\"roles\"]\n]\n\n# Find non-response columns\npreds = [\n col for col, details in result[\"columns\"].items()\n if \"Response\" not in details[\"roles\"]\n]\n\n# Has intercept\nhas_intercept = result[\"metadata\"][\"has_intercept\"]\n\n# Prepare data matrices\nX = df.select(preds).to_numpy()\ny = df.select(response_cols).to_numpy().ravel()\n\n# Add intercept if metadata says so\nif has_intercept:\n X_with_intercept = np.column_stack([np.ones(X.shape[0]), X])\nelse:\n X_with_intercept = X\n\n# Solve normal equations: (X'X)^-1 X'y\nXTX = X_with_intercept.T @ X_with_intercept\nXTy = X_with_intercept.T @ y\ncoefficients = np.linalg.solve(XTX, XTy)\n\n# Extract intercept and slopes\nif has_intercept:\n intercept = coefficients[0]\n slopes = coefficients[1:]\nelse:\n intercept = 0.0\n slopes = coefficients\n\n# Calculate R2\ny_pred = X_with_intercept @ coefficients\nss_res = np.sum((y - y_pred) ** 2)\nss_tot = np.sum((y - np.mean(y)) ** 2)\nr_squared = 1 - (ss_res / ss_tot)\n\n# Prep Output\n# Combine intercept and slopes into one dict\ncoef_dict = {\"intercept\": intercept} | dict(zip(preds, slopes))\n\n# Create a tidy DataFrame\ncoef_df = pl.DataFrame(\n {\n \"term\": list(coef_dict.keys()),\n \"estimate\": list(coef_dict.values())\n }\n)\n\n# Print results\nprint(f\"Formula: {formula}\")\nprint(f\"R\u00b2 Score: {r_squared:.3f}\")\nprint(coef_df)\n```\n\n**Output:**\n\n```json\n{'all_generated_columns': ['mpg', 'intercept', 'wt', 'cyl'],\n 'all_generated_columns_formula_order': {'1': 'mpg',\n '2': 'intercept',\n '3': 'wt',\n '4': 'cyl'},\n 'columns': {'cyl': {'generated_columns': ['cyl'],\n 'id': 3,\n 'interactions': [],\n 'random_effects': [],\n 'roles': ['Identity'],\n 'transformations': []},\n 'mpg': {'generated_columns': ['mpg'],\n 'id': 1,\n 'interactions': [],\n 'random_effects': [],\n 'roles': ['Response'],\n 'transformations': []},\n 'wt': {'generated_columns': ['wt'],\n 'id': 2,\n 'interactions': [],\n 'random_effects': [],\n 'roles': ['Identity'],\n 'transformations': []}},\n 'formula': 'mpg ~ wt + cyl',\n 'metadata': {'family': None,\n 'has_intercept': True,\n 'has_uncorrelated_slopes_and_intercepts': False,\n 'is_random_effects_model': False,\n 'response_variable_count': 1}}\nFormula: mpg ~ wt + cyl\nR\u00b2 Score: 0.830\nshape: (3, 2)\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 term \u2506 estimate \u2502\n\u2502 --- \u2506 --- \u2502\n\u2502 str \u2506 f64 \u2502\n\u255e\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2561\n\u2502 intercept \u2506 39.686261 \u2502\n\u2502 cyl \u2506 -1.507795 \u2502\n\u2502 wt \u2506 -3.190972 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n\n## \ud83d\udccb Supported Formula Syntax\n\n`fiasto` supports comprehensive Wilkinson's notation including:\n\n- **Basic formulas**: `y ~ x1 + x2`\n- **Interactions**: `y ~ x1 * x2`\n- **Smooth terms**: `y ~ s(z)`\n- **Random effects**: `y ~ x + (1|group)`\n- **Complex random effects**: `y ~ x + (1+x|group)`\n\n### Supported Formulas (Coming Soon)\n\n- **Multivariate models**: `mvbind(y1, y2) ~ x + (1|g)`\n- **Non-linear models**: `y ~ a1 - a2^x, a1 ~ 1, a2 ~ x + (x|g), nl = TRUE`\n\nFor the complete reference, see the [fiasto documentation](https://docs.rs/fiasto/latest/fiasto/).\n\n## \ud83d\udce6 PyPI Package\n\nThe package is available on PyPI and can be installed with:\n\n```bash\npip install fiasto-py\n```\n\n- **PyPI Page**: [pypi.org/project/fiasto-py](https://pypi.org/project/fiasto-py/)\n- **Source Code**: [github.com/alexhallam/fiasto-py](https://github.com/alexhallam/fiasto-py)\n- **Documentation**: This README and inline docstrings\n\n\n## \ud83d\udcda API Reference\n\n### `parse_formula(formula: str) -> dict`\n\nParse a Wilkinson's formula string and return structured JSON metadata.\n\n**Parameters:**\n- `formula` (str): The formula string to parse\n\n**Returns:**\n- `dict`: Structured metadata describing the formula\n\n**Raises:**\n- `ValueError`: If the formula is invalid or parsing fails\n\n### `lex_formula(formula: str) -> dict`\n\nTokenize a formula string and return JSON describing each token.\n\n**Parameters:**\n- `formula` (str): The formula string to tokenize\n\n**Returns:**\n- `dict`: Token information for each element in the formula\n\n**Raises:**\n- `ValueError`: If the formula is invalid or lexing fails\n\n## \ud83e\udd1d Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## \ud83d\ude4f Acknowledgments\n\n- [fiasto](https://github.com/alexhallam/fiasto) - The underlying Rust library\n- [PyO3](https://pyo3.rs/) - Python-Rust bindings\n- [maturin](https://maturin.rs/) - Build system for Python extensions\n- [PyPI](https://pypi.org/) - Python Package Index for distribution\n\n## \ud83d\udcc4 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Python bindings for fiasto - A language-agnostic modern Wilkinson's formula parser and lexer",
"version": "0.1.4",
"project_urls": {
"Bug Tracker": "https://github.com/alexhallam/fiasto-py/issues",
"Changelog": "https://github.com/alexhallam/fiasto-py/blob/main/CHANGELOG.md",
"Documentation": "https://github.com/alexhallam/fiasto-py#readme",
"Homepage": "https://github.com/alexhallam/fiasto-py",
"Repository": "https://github.com/alexhallam/fiasto-py"
},
"split_keywords": [
"formula",
" parser",
" statistics",
" mixed-effects",
" wilkinson",
" rust",
" pyo3"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "43c09b6240ffdda6b12b1bbad7038a846a9a7a822a7d7de597f3237aae0abd5f",
"md5": "f0ca0e756770db5fc3dd96f69b0ef796",
"sha256": "079ee52330e7b3fc6f0dc8903bf0ed43e2f09e0cc042469bee20c82e62e8b294"
},
"downloads": -1,
"filename": "fiasto_py-0.1.4-cp313-cp313-macosx_11_0_arm64.whl",
"has_sig": false,
"md5_digest": "f0ca0e756770db5fc3dd96f69b0ef796",
"packagetype": "bdist_wheel",
"python_version": "cp313",
"requires_python": ">=3.8",
"size": 292069,
"upload_time": "2025-09-10T02:45:20",
"upload_time_iso_8601": "2025-09-10T02:45:20.433659Z",
"url": "https://files.pythonhosted.org/packages/43/c0/9b6240ffdda6b12b1bbad7038a846a9a7a822a7d7de597f3237aae0abd5f/fiasto_py-0.1.4-cp313-cp313-macosx_11_0_arm64.whl",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-09-10 02:45:20",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "alexhallam",
"github_project": "fiasto-py",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "fiasto-py"
}