Skip to content

Commit

Permalink
Add PyDocStyle Support (#951)
Browse files Browse the repository at this point in the history
  • Loading branch information
juanitorduz authored Aug 22, 2024
1 parent f6c6825 commit 39d38b7
Show file tree
Hide file tree
Showing 47 changed files with 828 additions and 292 deletions.
1 change: 1 addition & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
"""Sphinx configuration for PyMC Marketing Docs."""

import os

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4550,8 +4550,7 @@
"def plot_posterior(\n",
" posterior, figsize=(15, 8), path_color=\"blue\", hist_color=\"blue\", **kwargs\n",
"):\n",
" \"\"\"\n",
" Plot the posterior distribution of a stochastic process.\n",
" \"\"\"Plot the posterior distribution of a stochastic process.\n",
"\n",
" Parameters\n",
" ----------\n",
Expand All @@ -4565,8 +4564,8 @@
" Color of the histogram.\n",
" **kwargs\n",
" Additional keyword arguments to pass to the plotting functions.\n",
" \"\"\"\n",
"\n",
" \"\"\"\n",
" # Calculate the expected value (mean) across all draws and chains for each date\n",
" expected_value = posterior.mean(dim=(\"draw\", \"chain\"))\n",
"\n",
Expand Down
Binary file modified docs/source/uml/classes_clv.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/source/uml/classes_mmm.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions pymc_marketing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""PyMC Marketing."""

from pymc_marketing import clv, mmm
from pymc_marketing.version import __version__

Expand Down
2 changes: 2 additions & 0 deletions pymc_marketing/clv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""CLV models and utilities."""

from pymc_marketing.clv.models import (
BetaGeoModel,
GammaGammaModel,
Expand Down
38 changes: 25 additions & 13 deletions pymc_marketing/clv/distributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Distributions for the CLV module."""

import numpy as np
import pymc as pm
import pytensor.tensor as pt
Expand Down Expand Up @@ -80,9 +82,9 @@ def _supp_shape_from_params(*args, **kwargs):


class ContNonContract(PositiveContinuous):
r"""
Individual-level model for the customer lifetime value. See equation (3)
from Fader et al. (2005) [1]_.
r"""Individual-level model for the customer lifetime value.
See equation (3) from Fader et al. (2005) [1]_.
.. math::
Expand All @@ -100,15 +102,18 @@ class ContNonContract(PositiveContinuous):
.. [1] Fader, Peter S., Bruce GS Hardie, and Ka Lok Lee. "“Counting your customers”
the easy way: An alternative to the Pareto/NBD model." Marketing science
24.2 (2005): 275-284.
"""

rv_op = continuous_non_contractual

@classmethod
def dist(cls, lam, p, T, **kwargs):
"""Get the distribution from the parameters."""
return super().dist([lam, p, T], **kwargs)

def logp(value, lam, p, T):
"""Log-likelihood of the distribution."""
t_x = value[..., 0]
x = value[..., 1]

Expand Down Expand Up @@ -206,10 +211,10 @@ def _supp_shape_from_params(*args, **kwargs):


class ContContract(PositiveContinuous):
r"""
Distribution class of a continuous contractual data-generating process,
that is where purchases can occur at any time point (continuous) and
churning/dropping out is explicit (contractual).
r"""Distribution class of a continuous contractual data-generating process.
That is where purchases can occur at any time point (continuous) and churning/dropping
out is explicit (contractual).
.. math::
Expand All @@ -228,9 +233,11 @@ class ContContract(PositiveContinuous):

@classmethod
def dist(cls, lam, p, T, **kwargs):
"""Get the distribution from the parameters."""
return super().dist([lam, p, T], **kwargs)

def logp(value, lam, p, T):
"""Log-likelihood of the distribution."""
t_x = value[..., 0]
x = value[..., 1]
churn = value[..., 2]
Expand Down Expand Up @@ -358,9 +365,9 @@ def _supp_shape_from_params(*args, **kwargs):


class ParetoNBD(PositiveContinuous):
r"""
Population-level distribution class for a continuous, non-contractual, Pareto/NBD process,
based on Schmittlein, et al. in [2]_.
r"""Population-level distribution class for a continuous, non-contractual, Pareto/NBD process.
It is based on Schmittlein, et al. in [2]_.
The likelihood function is derived from equations (22) and (23) of [3]_, with terms
rearranged for numerical stability.
Expand Down Expand Up @@ -403,15 +410,18 @@ class ParetoNBD(PositiveContinuous):
.. [3] Fader, Peter & G. S. Hardie, Bruce (2005).
"A Note on Deriving the Pareto/NBD Model and Related Expressions."
http://brucehardie.com/notes/009/pareto_nbd_derivations_2005-11-05.pdf
""" # noqa: E501

rv_op = pareto_nbd

@classmethod
def dist(cls, r, alpha, s, beta, T, **kwargs):
"""Get the distribution from the parameters."""
return super().dist([r, alpha, s, beta, T], **kwargs)

def logp(value, r, alpha, s, beta, T):
"""Log-likelihood of the distribution."""
t_x = value[..., 0]
x = value[..., 1]

Expand Down Expand Up @@ -555,9 +565,9 @@ def _supp_shape_from_params(*args, **kwargs):


class BetaGeoBetaBinom(Discrete):
r"""
Population-level distribution class for a discrete, non-contractual, Beta-Geometric/Beta-Binomial process,
based on equation(5) from Fader, et al. in [1]_.
r"""Population-level distribution class for a discrete, non-contractual, Beta-Geometric/Beta-Binomial process.
It is based on equation(5) from Fader, et al. in [1]_.
.. math::
Expand All @@ -584,9 +594,11 @@ class BetaGeoBetaBinom(Discrete):

@classmethod
def dist(cls, alpha, beta, gamma, delta, T, **kwargs):
"""Get the distribution from the parameters."""
return super().dist([alpha, beta, gamma, delta, T], **kwargs)

def logp(value, alpha, beta, gamma, delta, T):
"""Log-likelihood of the distribution."""
t_x = pt.atleast_1d(value[..., 0])
x = pt.atleast_1d(value[..., 1])
scalar_case = t_x.type.broadcastable == (True,)
Expand Down
3 changes: 3 additions & 0 deletions pymc_marketing/clv/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""CLV models."""

from pymc_marketing.clv.models.basic import CLVModel
from pymc_marketing.clv.models.beta_geo import BetaGeoModel
from pymc_marketing.clv.models.gamma_gamma import (
Expand Down
28 changes: 21 additions & 7 deletions pymc_marketing/clv/models/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""CLV Model base class."""

import json
import warnings
from collections.abc import Sequence
Expand All @@ -32,6 +34,8 @@


class CLVModel(ModelBuilder):
"""CLV Model base class."""

_model_type = "CLVModel"

@validate_call(config=ConfigDict(arbitrary_types_allowed=True))
Expand Down Expand Up @@ -69,6 +73,7 @@ def _validate_cols(
raise ValueError(f"Column {required_col} has duplicate entries")

def __repr__(self) -> str:
"""Representation of the model."""
if not hasattr(self, "model"):
return self._model_type
else:
Expand All @@ -89,7 +94,7 @@ def fit( # type: ignore
fit_method: str = "mcmc",
**kwargs,
) -> az.InferenceData:
"""Infer model posterior
"""Infer model posterior.
Parameters
----------
Expand All @@ -99,8 +104,8 @@ def fit( # type: ignore
- "map": Finds maximum a posteriori via `pymc.find_MAP`
kwargs:
Other keyword arguments passed to the underlying PyMC routines
"""
"""
self.build_model() # type: ignore

if fit_method == "mcmc":
Expand All @@ -120,8 +125,8 @@ def fit( # type: ignore
return self.idata

def _fit_mcmc(self, **kwargs) -> az.InferenceData:
"""
Fit a model using the data passed as a parameter.
"""Fit a model using the data passed as a parameter.
Sets attrs to inference data of the model.
Expand All @@ -138,6 +143,7 @@ def _fit_mcmc(self, **kwargs) -> az.InferenceData:
-------
self : az.InferenceData
returns inference data of the fitted model.
"""
sampler_config = {}
if self.sampler_config is not None:
Expand All @@ -146,7 +152,7 @@ def _fit_mcmc(self, **kwargs) -> az.InferenceData:
return pm.sample(**sampler_config, model=self.model)

def _fit_MAP(self, **kwargs) -> az.InferenceData:
"""Find model maximum a posteriori using scipy optimizer"""
"""Find model maximum a posteriori using scipy optimizer."""
model = self.model
map_res = pm.find_MAP(model=model, **kwargs)
# Filter non-value variables
Expand All @@ -162,8 +168,8 @@ def _fit_MAP(self, **kwargs) -> az.InferenceData:

@classmethod
def load(cls, fname: str):
"""
Creates a ModelBuilder instance from a file,
"""Create a ModelBuilder instance from a file.
Loads inference data for the model.
Parameters
Expand All @@ -179,12 +185,14 @@ def load(cls, fname: str):
------
ValueError
If the inference data that is loaded doesn't match with the model.
Examples
--------
>>> class MyModel(ModelBuilder):
>>> ...
>>> name = './mymodel.nc'
>>> imported_model = MyModel.load(name)
"""
filepath = Path(str(fname))
idata = from_netcdf(filepath)
Expand Down Expand Up @@ -242,6 +250,7 @@ def thin_fit_result(self, keep_every: int):

@property
def default_sampler_config(self) -> dict:
"""Default sampler configuration."""
return {}

@property
Expand All @@ -250,6 +259,7 @@ def _serializable_model_config(self) -> dict:

@property
def fit_result(self) -> Dataset:
"""Get the fit result."""
if self.idata is None or "posterior" not in self.idata:
raise RuntimeError("The model hasn't been fit yet, call .fit() first")
return self.idata["posterior"]
Expand All @@ -265,6 +275,7 @@ def fit_result(self, res: az.InferenceData) -> None:
self.idata.posterior = res

def fit_summary(self, **kwargs):
"""Compute the summary of the fit result."""
res = self.fit_result
# Map fitting only gives one value, so we return it. We use arviz
# just to get it nicely into a DataFrame
Expand All @@ -278,10 +289,13 @@ def fit_summary(self, **kwargs):

@property
def output_var(self):
"""Output variable of the model."""
pass

def _generate_and_preprocess_model_data(self, *args, **kwargs):
"""Generate and preprocess model data."""
pass

def _data_setter(self):
"""Set the data for the model."""
pass
Loading

0 comments on commit 39d38b7

Please sign in to comment.