Skip to main content

An implementation of Wilkinson's formula language for statistical models à la lme4

Project description

formulae

formulae is a Python library that implements Wilkinson's formulas for statistical models à la lme4. The main difference with other implementations like Patsy or formulaic is that formulaic can work with formulas describing a model with both common and group specific effects (a.k.a. fixed and random effects, respectively).

This package has been written to make it easier to specify models with group effects in Bambi, a package that makes it easy to work with Bayesian GLMMs in Python, but it could be used independently as a backend for another library.

Note: While this package is working, there is a 100% chance of finding bugs within the code. You are encouraged to play with this library and give feedback about it, but it is not recommended to incorporate formulae in a larger project at this early stage of development.

Installation

formulae requires a working Python interpreter (3.7+) and the libraries numpy, scipy and pandas with versions specified in the requirements.txt file.

Assuming a standard Python environment is installed on your machine (including pip), the development version of formulae can be installed in one line using pip:

pip install git+https://github.com/bambinos/formulae.git

Example code

The main function you encounter in this library is design_matrices(). It returns an object of class DesignMatrices that contains information about the response, the common effects, and the group specific effects that can be accessed with the attributes .response, .common, and .group respectively.

import numpy as np 
import pandas as pd

from formulae import design_matrices
np.random.seed(1234)
df = pd.DataFrame({
    'y_num': np.random.normal(size=10),
    'y_cat': np.random.choice(['A', 'B'], size=10),
    'x': np.random.normal(size=10),
    'g': np.random.choice(['Group 1', 'Group 2', 'Group 3'], size=10)
})
df
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
y_num y_cat x g
0 0.471435 B -0.304260 Group 1
1 -1.190976 A 0.861661 Group 3
2 1.432707 B -0.689927 Group 3
3 -0.312652 B 0.187497 Group 1
4 -0.720589 A 0.604309 Group 2
5 0.887163 A -0.183014 Group 2
6 0.859588 B -1.126502 Group 1
7 -0.636524 A 1.658873 Group 1
8 0.015696 A -0.660441 Group 1
9 -2.242685 B 1.041086 Group 2

Example 1

A simple linear model with numeric response, numeric common effects and varying slope and intercept for each level of g.

design = design_matrices("y_num ~ x + (x|g)", df)
print(design.response)
print(design.response.design_vector)
ResponseVector(name=y_num, type=numeric, length=10)
[[ 0.47143516]
 [-1.19097569]
 [ 1.43270697]
 [-0.3126519 ]
 [-0.72058873]
 [ 0.88716294]
 [ 0.85958841]
 [-0.6365235 ]
 [ 0.01569637]
 [-2.24268495]]
print(design.common)
print(design.common.design_matrix) # this can be printed as a pandas.DataFrame with design.common.as_dataframe()
CommonEffectsMatrix(
  shape: (10, 2),
  terms: {
    'Intercept': {type=Intercept, cols=slice(0, 1, None), full_names=['Intercept']},
    'x': {type=numeric, cols=slice(1, 2, None), full_names=['x']}
  }
)
[[ 1.         -0.30426018]
 [ 1.          0.861661  ]
 [ 1.         -0.68992667]
 [ 1.          0.18749737]
 [ 1.          0.60430874]
 [ 1.         -0.18301422]
 [ 1.         -1.12650247]
 [ 1.          1.65887284]
 [ 1.         -0.66044141]
 [ 1.          1.04108597]]

Before exploring the group level effects we mention that formulae returns a sparse matrix in CSC format. If it is the case the matrix is not that big and you want to see it as a whole, you can call design.group.design_matrix.toarray()

print(design.group)
print(design.group.design_matrix.toarray())
GroupEffectsMatrix(
  shape: (20, 6),
  terms: {
    '1|g': {type=Intercept, groups=['Group 1', 'Group 3', 'Group 2'], idxs=(slice(0, 10, None), slice(0, 3, None)), full_names=['1|g[Group 1]', '1|g[Group 3]', '1|g[Group 2]']},
    'x|g': {type=numeric, groups=['Group 1', 'Group 3', 'Group 2'], idxs=(slice(10, 20, None), slice(3, 6, None)), full_names=['x|g[Group 1]', 'x|g[Group 3]', 'x|g[Group 2]']}
  }
)
[[ 1.          0.          0.          0.          0.          0.        ]
 [ 0.          1.          0.          0.          0.          0.        ]
 [ 0.          1.          0.          0.          0.          0.        ]
 [ 1.          0.          0.          0.          0.          0.        ]
 [ 0.          0.          1.          0.          0.          0.        ]
 [ 0.          0.          1.          0.          0.          0.        ]
 [ 1.          0.          0.          0.          0.          0.        ]
 [ 1.          0.          0.          0.          0.          0.        ]
 [ 1.          0.          0.          0.          0.          0.        ]
 [ 0.          0.          1.          0.          0.          0.        ]
 [ 0.          0.          0.         -0.30426018  0.          0.        ]
 [ 0.          0.          0.          0.          0.861661    0.        ]
 [ 0.          0.          0.          0.         -0.68992667  0.        ]
 [ 0.          0.          0.          0.18749737  0.          0.        ]
 [ 0.          0.          0.          0.          0.          0.60430874]
 [ 0.          0.          0.          0.          0.         -0.18301422]
 [ 0.          0.          0.         -1.12650247  0.          0.        ]
 [ 0.          0.          0.          1.65887284  0.          0.        ]
 [ 0.          0.          0.         -0.66044141  0.          0.        ]
 [ 0.          0.          0.          0.          0.          1.04108597]]

But if you are interested only in the sub-matrix corresponding to a given group specific effect, you can use design.group['level_name'] as follows

design.group['x|g']
array([[-0.30426018,  0.        ,  0.        ],
       [ 0.        ,  0.861661  ,  0.        ],
       [ 0.        , -0.68992667,  0.        ],
       [ 0.18749737,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.60430874],
       [ 0.        ,  0.        , -0.18301422],
       [-1.12650247,  0.        ,  0.        ],
       [ 1.65887284,  0.        ,  0.        ],
       [-0.66044141,  0.        ,  0.        ],
       [ 0.        ,  0.        ,  1.04108597]])

Example 2

A categorical response and a linear predictor that has an interaction between a categorical variable and a function call. In this example we use the variable[level] notation that is taken from Bambi that makes it easier to indicate which level represents a success in a categorical response.

design = design_matrices("y_cat[A] ~ np.exp(x) * g", df)
print(design.response)
print(design.response.design_vector)
ResponseVector(name=y_cat, type=categoric, length=10, refclass=A)
[[0]
 [1]
 [0]
 [0]
 [1]
 [1]
 [0]
 [1]
 [1]
 [0]]
design.common
CommonEffectsMatrix(
  shape: (10, 7),
  terms: {
    'Intercept': {type=Intercept, cols=slice(0, 1, None), full_names=['Intercept']},
    'np.exp(x)': {type=call, cols=slice(1, 2, None), full_names=['np.exp(x)']},
    'g': {type=categoric, levels=['Group 1', 'Group 3', 'Group 2'], reference=Group 1, encoding=reduced, cols=slice(2, 4, None), full_names=['g[Group 3]', 'g[Group 2]']},
    'np.exp(x):g': {type=interaction, vars={
      np.exp(x): {type=call},
      g: {type=categoric, levels=['Group 1', 'Group 3', 'Group 2'], reference=Group 1, encoding=full}
    }}
  }
)
design.common.as_dataframe()
<style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; }
.dataframe tbody tr th {
    vertical-align: top;
}

.dataframe thead th {
    text-align: right;
}
</style>
Intercept np.exp(x) g[Group 3] g[Group 2] np.exp(x):g[Group 1] np.exp(x):g[Group 3] np.exp(x):g[Group 2]
0 1.0 0.737669 0.0 0.0 0.737669 0.000000 0.000000
1 1.0 2.367089 1.0 0.0 0.000000 2.367089 0.000000
2 1.0 0.501613 1.0 0.0 0.000000 0.501613 0.000000
3 1.0 1.206227 0.0 0.0 1.206227 0.000000 0.000000
4 1.0 1.829987 0.0 1.0 0.000000 0.000000 1.829987
5 1.0 0.832756 0.0 1.0 0.000000 0.000000 0.832756
6 1.0 0.324165 0.0 0.0 0.324165 0.000000 0.000000
7 1.0 5.253386 0.0 0.0 5.253386 0.000000 0.000000
8 1.0 0.516623 0.0 0.0 0.516623 0.000000 0.000000
9 1.0 2.832291 0.0 1.0 0.000000 0.000000 2.832291

Notes

  • The data argument only accepts objects of class pandas.DataFrame.
  • y ~ . is not implemented and won't be implemented in a first version. However, it is planned to be included in the future.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

formulae-0.0.5.tar.gz (27.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

formulae-0.0.5-py3-none-any.whl (28.4 kB view details)

Uploaded Python 3

File details

Details for the file formulae-0.0.5.tar.gz.

File metadata

  • Download URL: formulae-0.0.5.tar.gz
  • Upload date:
  • Size: 27.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/50.3.0.post20201006 requests-toolbelt/0.9.1 tqdm/4.56.0 CPython/3.8.5

File hashes

Hashes for formulae-0.0.5.tar.gz
Algorithm Hash digest
SHA256 f13566d1685fee85f95b5efaee2c49d7d8b7f367bd3251151e398753135fdda1
MD5 5bc239c3a3844ccb991967e12501d758
BLAKE2b-256 bd29547267e9376c3118981ac7fdb98b23d2014349bf4aa0693b047f78f9f863

See more details on using hashes here.

File details

Details for the file formulae-0.0.5-py3-none-any.whl.

File metadata

  • Download URL: formulae-0.0.5-py3-none-any.whl
  • Upload date:
  • Size: 28.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.3.0 pkginfo/1.7.0 requests/2.25.1 setuptools/50.3.0.post20201006 requests-toolbelt/0.9.1 tqdm/4.56.0 CPython/3.8.5

File hashes

Hashes for formulae-0.0.5-py3-none-any.whl
Algorithm Hash digest
SHA256 59c88a235d8b1195f88e07d82edea924e72c703db6f5d063dd3cfd471468cfde
MD5 fad88717d875024a5e01b9393acba75a
BLAKE2b-256 2d44887fc408fbfb8e991075e8fa81ae91c9c770b11e81da1fb18e2fbb4e60df

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page