turtles-glms 1.0.0__tar.gz

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,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Adam Motzel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: turtles-glms
3
+ Version: 1.0.0
4
+ Summary: GLMs in Python.
5
+ Author-email: Adam Motzel <motzel.adam@gmail.com>
6
+ Project-URL: Homepage, https://github.com/adammotzel/pyglms
7
+ Project-URL: Issues, https://github.com/adammotzel/pyglms/issues
8
+ Classifier: Programming Language :: Python :: 3.10
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: numpy>=2.0.2
16
+ Requires-Dist: pandas>=2.2.3
17
+ Requires-Dist: scipy>=1.13.1
18
+ Requires-Dist: matplotlib>=3.9.4
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=8.3.4; extra == "dev"
21
+ Requires-Dist: pytest-cov>=6.2.1; extra == "dev"
22
+ Requires-Dist: scikit-learn>=1.6.1; extra == "dev"
23
+ Requires-Dist: statsmodels>=0.14.4; extra == "dev"
24
+ Requires-Dist: ipykernel>=6.29.5; extra == "dev"
25
+ Dynamic: license-file
26
+
27
+ ![CI](https://github.com/adammotzel/pyglms/actions/workflows/ci.yaml/badge.svg)
28
+ ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
29
+ ![Python](https://img.shields.io/badge/python-3.10%20--%203.13-blue)
30
+ ![License](https://img.shields.io/github/license/adammotzel/pyglms)
31
+ ![PyPI](https://img.shields.io/pypi/v/turtles-glms.svg)
32
+
33
+
34
+ # PyGLMs (Turtles)
35
+
36
+ An implementation of various Generalized Linear Models (GLMs), written in Python.
37
+
38
+ I created this package as a refresher on GLMs and the underlying optimization techniques. It's intended as a learning tool and a reference for building and understanding these models from the ground up.
39
+
40
+
41
+ ## Overview
42
+
43
+ The code is packaged as a Python library named `turtles` ([I like turtles](https://www.youtube.com/watch?v=CMNry4PE93Y)), making the code easy to integrate into your own projects.
44
+
45
+ The package is written using `numpy` for linear algebra operations, `scipy` for (some) optimization, `pandas` for displaying tabular results, and `matplotlib` for plots.
46
+
47
+ The following models have been implemented:
48
+
49
+ 1. Multiple Linear Regression (`turtles.stats.glms.MLR` class)
50
+ 2. Logistic Regression (`turtles.stats.glms.LogReg` class, uses `GLM` parent class)
51
+ 3. Poisson Regression (`turtles.stats.glms.PoissonReg` class, uses `GLM` parent class)
52
+
53
+ The `GLM` parent class supports three optimization methods for parameter estimation: Momentum-based Gradient Descent for first-order optimization, Newton's Method for second-order optimization, and Limited-memory Broyden–Fletcher–Goldfarb–Shanno (L-BFGS). The user can specify the desired optimization `method` during class instantiation.
54
+
55
+ Momentum-based Gradient Descent and Newton's Method are implemented in Python as part of the `turtles` distribution. L-BFGS is implemented using `scipy.optimize`; it's a quasi-Newton method that approximates the Hessian (instead of fully computing it, like Newton's Method), so it's quite fast.
56
+
57
+ See `examples/{class name}_example.ipynb` for simple examples of using each model class and various supporting functions.
58
+
59
+
60
+ ## Usage
61
+
62
+ You can pip install the package from PyPI:
63
+
64
+ ```bash
65
+ pip install turtles-glms
66
+ ```
67
+
68
+
69
+ ## Contributing
70
+
71
+ To run (and edit) this project locally, clone the repo and create your virtual environment from project root using your global (or local) Python version. This project requires Python 3.10+.
72
+
73
+ ```bash
74
+ python -m venv
75
+ ```
76
+
77
+ Activate the env (`source .venv/Scripts/activate` for Windows OS, `source .venv/bin/activate` for Linux) and install dependencies:
78
+
79
+ ```bash
80
+ pip install -e .[dev]
81
+ ```
82
+
83
+ Optionally, you can execute `scripts/env.sh` to create and activate a virtual environment using `uv`. The `uv` package manager must be installed for this to work.
84
+
85
+
86
+ ### Adding GLMs
87
+
88
+ To add more GLM classes, use the `GLM` parent class for inheritence (see `PoissonReg` and `LogReg` as examples). The GLM parent class provides a solid framework for implementing new child classes and should be used whenever possible. Unimplemented GLMs include Negative Binomial, Gamma, and Tweedie.
89
+
90
+
91
+ ## Testing
92
+
93
+ All tests are contained within `tests` directories for each module. You can simply execute the `pytest` command from project root to run all unit tests.
94
+
95
+ ```bash
96
+ pytest
97
+ ```
98
+
99
+ **Notes on Test Coverage:**
100
+ - Plotting functions from `turtles.plotting` are tested, but plotting methods in GLM classes
101
+ (like `MLR`) are ignored. Those class methods are essentially just wrappers around `matplotlib`
102
+ and `turtles.plotting` functions.
103
+ - `GLM` class methods that are meant to be implemented by child classes are ignored.
@@ -0,0 +1,77 @@
1
+ ![CI](https://github.com/adammotzel/pyglms/actions/workflows/ci.yaml/badge.svg)
2
+ ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
3
+ ![Python](https://img.shields.io/badge/python-3.10%20--%203.13-blue)
4
+ ![License](https://img.shields.io/github/license/adammotzel/pyglms)
5
+ ![PyPI](https://img.shields.io/pypi/v/turtles-glms.svg)
6
+
7
+
8
+ # PyGLMs (Turtles)
9
+
10
+ An implementation of various Generalized Linear Models (GLMs), written in Python.
11
+
12
+ I created this package as a refresher on GLMs and the underlying optimization techniques. It's intended as a learning tool and a reference for building and understanding these models from the ground up.
13
+
14
+
15
+ ## Overview
16
+
17
+ The code is packaged as a Python library named `turtles` ([I like turtles](https://www.youtube.com/watch?v=CMNry4PE93Y)), making the code easy to integrate into your own projects.
18
+
19
+ The package is written using `numpy` for linear algebra operations, `scipy` for (some) optimization, `pandas` for displaying tabular results, and `matplotlib` for plots.
20
+
21
+ The following models have been implemented:
22
+
23
+ 1. Multiple Linear Regression (`turtles.stats.glms.MLR` class)
24
+ 2. Logistic Regression (`turtles.stats.glms.LogReg` class, uses `GLM` parent class)
25
+ 3. Poisson Regression (`turtles.stats.glms.PoissonReg` class, uses `GLM` parent class)
26
+
27
+ The `GLM` parent class supports three optimization methods for parameter estimation: Momentum-based Gradient Descent for first-order optimization, Newton's Method for second-order optimization, and Limited-memory Broyden–Fletcher–Goldfarb–Shanno (L-BFGS). The user can specify the desired optimization `method` during class instantiation.
28
+
29
+ Momentum-based Gradient Descent and Newton's Method are implemented in Python as part of the `turtles` distribution. L-BFGS is implemented using `scipy.optimize`; it's a quasi-Newton method that approximates the Hessian (instead of fully computing it, like Newton's Method), so it's quite fast.
30
+
31
+ See `examples/{class name}_example.ipynb` for simple examples of using each model class and various supporting functions.
32
+
33
+
34
+ ## Usage
35
+
36
+ You can pip install the package from PyPI:
37
+
38
+ ```bash
39
+ pip install turtles-glms
40
+ ```
41
+
42
+
43
+ ## Contributing
44
+
45
+ To run (and edit) this project locally, clone the repo and create your virtual environment from project root using your global (or local) Python version. This project requires Python 3.10+.
46
+
47
+ ```bash
48
+ python -m venv
49
+ ```
50
+
51
+ Activate the env (`source .venv/Scripts/activate` for Windows OS, `source .venv/bin/activate` for Linux) and install dependencies:
52
+
53
+ ```bash
54
+ pip install -e .[dev]
55
+ ```
56
+
57
+ Optionally, you can execute `scripts/env.sh` to create and activate a virtual environment using `uv`. The `uv` package manager must be installed for this to work.
58
+
59
+
60
+ ### Adding GLMs
61
+
62
+ To add more GLM classes, use the `GLM` parent class for inheritence (see `PoissonReg` and `LogReg` as examples). The GLM parent class provides a solid framework for implementing new child classes and should be used whenever possible. Unimplemented GLMs include Negative Binomial, Gamma, and Tweedie.
63
+
64
+
65
+ ## Testing
66
+
67
+ All tests are contained within `tests` directories for each module. You can simply execute the `pytest` command from project root to run all unit tests.
68
+
69
+ ```bash
70
+ pytest
71
+ ```
72
+
73
+ **Notes on Test Coverage:**
74
+ - Plotting functions from `turtles.plotting` are tested, but plotting methods in GLM classes
75
+ (like `MLR`) are ignored. Those class methods are essentially just wrappers around `matplotlib`
76
+ and `turtles.plotting` functions.
77
+ - `GLM` class methods that are meant to be implemented by child classes are ignored.
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "turtles-glms"
7
+ version = "1.0.0"
8
+ requires-python = ">= 3.10"
9
+ dependencies = [
10
+ "numpy>=2.0.2",
11
+ "pandas>=2.2.3",
12
+ "scipy>=1.13.1",
13
+ "matplotlib>=3.9.4",
14
+ ]
15
+ description = "GLMs in Python."
16
+ readme = "README.md"
17
+ license = {file = "LICENSE.txt"}
18
+ classifiers = [
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ ]
24
+ authors = [
25
+ {name = "Adam Motzel", email = "motzel.adam@gmail.com"}
26
+ ]
27
+
28
+ [project.optional-dependencies]
29
+ dev = [
30
+ "pytest>=8.3.4",
31
+ "pytest-cov>=6.2.1",
32
+ "scikit-learn>=1.6.1",
33
+ "statsmodels>=0.14.4",
34
+ "ipykernel>=6.29.5"
35
+ ]
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["."]
39
+ exclude = ["tests", "*tests*", "examples", "scripts"]
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/adammotzel/pyglms"
43
+ Issues = "https://github.com/adammotzel/pyglms/issues"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,17 @@
1
+ """
2
+ Configure global settings for package.
3
+ """
4
+
5
+ from importlib.metadata import version, PackageNotFoundError
6
+
7
+ try:
8
+ __version__ = version("turtles-glms")
9
+ except PackageNotFoundError:
10
+ __version__ = "0.0.0"
11
+
12
+ # modules
13
+ __all__ = [
14
+ "preprocess",
15
+ "stats",
16
+ "plotting"
17
+ ]
@@ -0,0 +1,110 @@
1
+ """
2
+ General utility functions.
3
+ """
4
+
5
+ from typing import (
6
+ Dict,
7
+ Tuple,
8
+ Any,
9
+ Union
10
+ )
11
+
12
+ import numpy as np
13
+
14
+
15
+ def _shape_check(x: np.ndarray, var_name: str, dim: int = 1):
16
+ """
17
+ Check if numpy array has at least `dim` dimensions.
18
+
19
+ x : np.ndarray
20
+ Numpy matrix.
21
+ var_name : str
22
+ The variable name to return in the error message.
23
+ dim : int, default 2
24
+ The min number of dimensions in the array.
25
+
26
+ Raises
27
+ ------
28
+ ValueError
29
+ If the array has less than `dim` dimensions.
30
+
31
+ Returns
32
+ -------
33
+ None
34
+ """
35
+
36
+ if np.ndim(x) == 1 or x.shape[1] < dim:
37
+ raise ValueError(
38
+ f"'{var_name}' must contain more than {dim} dimensions."
39
+ )
40
+
41
+
42
+ def _validate_args(
43
+ map: Dict[str, Tuple[Any, Union[type, Tuple[type, ...]]]]
44
+ ):
45
+ """
46
+ Simple type validation of a set of input arguments.
47
+
48
+ Parameters
49
+ ----------
50
+ map : Dict[str, Tuple[Any, Union[type, Tuple[type, ...]]]]
51
+ A dictionary where keys are the argument names and the values are
52
+ tuples containing:
53
+ - the object to check
54
+ - the expected type or a tuple of types
55
+
56
+ Raises
57
+ ------
58
+ TypeError
59
+ If the type of an argument does not match the expected type.
60
+
61
+ Example
62
+ -------
63
+ _validate_args(
64
+ {
65
+ 'X': (X, np.array),
66
+ 'Y': (Y, (int, float))
67
+ }
68
+ )
69
+ """
70
+ for key, (obj, expected_type) in map.items():
71
+ if isinstance(expected_type, tuple):
72
+ if not any(isinstance(obj, t) for t in expected_type):
73
+ raise TypeError(
74
+ f"Parameter '{key}' must be one of the types {expected_type}; "
75
+ f"received {type(obj)}"
76
+ )
77
+ else:
78
+ if not isinstance(obj, expected_type):
79
+ raise TypeError(
80
+ f"Parameter '{key}' must be of type {expected_type}; "
81
+ f"received {type(obj)}"
82
+ )
83
+
84
+
85
+ def _add_intercept(X: np.ndarray) -> np.ndarray:
86
+ """
87
+ Add intercept to the design matrix. The intercept is simply
88
+ a column of ones.
89
+
90
+ Parameters
91
+ ----------
92
+ X : np.ndarray, shape (M, N)
93
+ Design matrix with M rows (samples) and N columns (features).
94
+
95
+ Returns
96
+ -------
97
+ np.ndarray, shape (M, N+1)
98
+ Design matrix with the intercept as the first column.
99
+ """
100
+
101
+ _validate_args(
102
+ {
103
+ "X": (X, np.ndarray)
104
+ }
105
+ )
106
+
107
+ m = X.shape[0]
108
+ intercept = np.ones(m).reshape(m, 1)
109
+ X = np.concatenate((intercept, X), axis=1)
110
+ return X
@@ -0,0 +1,14 @@
1
+ """
2
+ Plotting module.
3
+
4
+ Contains various plotting functions.
5
+ """
6
+
7
+ from ._plotting import (
8
+ plot_y_vs_x
9
+ )
10
+
11
+
12
+ __all__ = [
13
+ "plot_y_vs_x"
14
+ ]
@@ -0,0 +1,61 @@
1
+ """
2
+ Plotting functions.
3
+ """
4
+
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+
8
+ from .._utils import _validate_args
9
+
10
+
11
+ def plot_y_vs_x(
12
+ x: np.ndarray,
13
+ y: np.ndarray,
14
+ title: str = "Dependent vs. Independent",
15
+ xlabel: str = "Independent",
16
+ ylabel: str = "Dependent"
17
+ ):
18
+ """
19
+ Scatter plot of a variable (y) against another variable (x).
20
+
21
+ Input arrays must be the same shape.
22
+
23
+ Parameters
24
+ ----------
25
+ x : np.ndarray
26
+ Input array for x-axis.
27
+ y : np.ndarray
28
+ Input aray for y-axis.
29
+ title : str
30
+ Plot title. Defaults to 'Dependent vs. Independent'.
31
+ xlabel : str
32
+ Plot label for x-axis. Defaults to 'Independent'.
33
+ ylabel : str
34
+ Plot label for y axis. Defaults to 'Dependent'.
35
+
36
+ Returns
37
+ -------
38
+ None
39
+ Displays scatter plot.
40
+ """
41
+
42
+ _validate_args(
43
+ {
44
+ "x": (x, np.ndarray),
45
+ "y": (y, np.ndarray),
46
+ "title": (title, str),
47
+ "xlabel": (xlabel, str),
48
+ "ylabel": (ylabel, str)
49
+ }
50
+ )
51
+
52
+ plt.scatter(
53
+ x=x,
54
+ y=y,
55
+ color="blue",
56
+ edgecolor="black"
57
+ )
58
+ plt.title(title)
59
+ plt.xlabel(xlabel)
60
+ plt.ylabel(ylabel)
61
+ plt.show()
@@ -0,0 +1,17 @@
1
+ """
2
+ Preprocess module.
3
+
4
+ Functions support common data transformation tasks to prep datasets
5
+ for GLMs.
6
+ """
7
+
8
+ from ._transform import (
9
+ one_hot_encode
10
+ )
11
+ from .._utils import _add_intercept as add_intercept
12
+
13
+
14
+ __all__ = [
15
+ "one_hot_encode",
16
+ "add_intercept"
17
+ ]
@@ -0,0 +1,81 @@
1
+ """
2
+ Functions to support preprocessing/transformations.
3
+ """
4
+
5
+ from typing import List, Tuple, Union
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ from .._utils import (
11
+ _validate_args,
12
+ )
13
+
14
+
15
+ def one_hot_encode(
16
+ df: pd.DataFrame,
17
+ columns: List[str],
18
+ drop_first: bool = True,
19
+ return_df: bool = True
20
+ ) -> Union[pd.DataFrame, Tuple[np.ndarray, List[str]]]:
21
+ """
22
+ One Hot Encode categorical variables in a Pandas DataFrame, also known
23
+ as dummy variables or indicator variables.
24
+
25
+ This is essentially just a wrapper around the Pandas `get_dummies()`
26
+ function, but it optioanlly returns a Numpy matrix, making the resulting
27
+ object immediately compatible with the `turtles` GLM classes.
28
+
29
+ https://pandas.pydata.org/docs/reference/api/pandas.get_dummies.html
30
+
31
+ Parameters
32
+ ----------
33
+ df : pd.DataFrame, shape (M, N)
34
+ Pandas DataFrame containing data to be encoded.
35
+ columns : List[str]
36
+ List of column names in `df` to be encoded. Column values must be
37
+ strings or categorical.
38
+ drop_first : bool, default True
39
+ Whether to drop the first category from the resulting table. Dropping
40
+ the first category is useful when building a GLM, because the dropped
41
+ category will then be treated as the 'base case' or 'reference category'
42
+ for model coefficient interpretation.
43
+ return_df : bool, default True
44
+ Optionally return a pandas DataFrame. If False, a Tuple of a numpy
45
+ matrix and resulting column names is returned.
46
+
47
+ Returns
48
+ -------
49
+ Union[pd.DataFrame, Tuple[np.ndarray, List[str]]]
50
+
51
+ If `return_df` = True, a pandas DataFrame is returned, containing the
52
+ encoded columns.
53
+
54
+ If `return_df` = False, a Tuple is returned, where the new matrix is
55
+ in position [0] and the list of new column names in position [1].
56
+ """
57
+
58
+ _validate_args(
59
+ {
60
+ "df": (df, pd.DataFrame),
61
+ "columns": (columns, list),
62
+ "drop_first": (drop_first, bool),
63
+ "return_df": (return_df, bool)
64
+ }
65
+ )
66
+
67
+ temp = df.copy()
68
+
69
+ one_hot_encoded_df = pd.get_dummies(
70
+ temp,
71
+ columns=columns,
72
+ drop_first=drop_first,
73
+ dtype=int
74
+ )
75
+
76
+ if return_df:
77
+ return one_hot_encoded_df
78
+
79
+ column_names = list(one_hot_encoded_df.columns)
80
+
81
+ return one_hot_encoded_df.to_numpy(), column_names
@@ -0,0 +1,23 @@
1
+ """
2
+ Stats module.
3
+
4
+ These functions support various statistical tasks.
5
+ """
6
+
7
+ from ._stats import (
8
+ calculate_errors,
9
+ variance_inflation_factor,
10
+ pearson_corr,
11
+ covariance_matrix,
12
+ pca
13
+ )
14
+
15
+
16
+ __all__ = [
17
+ "glms", # submodule
18
+ "calculate_errors",
19
+ "variance_inflation_factor",
20
+ "pearson_corr",
21
+ "covariance_matrix",
22
+ "pca"
23
+ ]