Source code for pharmpy.modeling.eta_transformations
"""
:meta private:
"""
import re
from typing import List, Optional, Union
from pharmpy.deps import sympy
from pharmpy.model import Assignment, Model, Parameter, Parameters
from pharmpy.modeling.help_functions import _format_input_list, _get_etas
from .expressions import create_symbol
[docs]def transform_etas_boxcox(model: Model, list_of_etas: Optional[Union[List[str], str]] = None):
"""Applies a boxcox transformation to selected etas
Initial estimate for lambda is 0.1 with bounds (-3, 3).
Parameters
----------
model : Model
Pharmpy model to apply boxcox transformation to.
list_of_etas : str, list
Name/names of etas to transform. If None, all etas will be transformed (default).
Return
------
Model
Pharmpy model object
Examples
--------
>>> from pharmpy.modeling import *
>>> model = load_example_model("pheno")
>>> model = transform_etas_boxcox(model, ["ETA_1"])
>>> model.statements.before_odes.full_expression("CL")
PTVCL*WGT*exp((exp(ETA_1)**lambda1 - 1)/lambda1)
See also
--------
transform_etas_tdist
transform_etas_john_draper
"""
list_of_etas = _format_input_list(list_of_etas)
etas = _get_etas(model, list_of_etas)
eta_transformation = EtaTransformation.boxcox(len(etas))
model = _transform_etas(model, eta_transformation, etas)
return model.update_source()
[docs]def transform_etas_tdist(model: Model, list_of_etas: Optional[Union[List[str], str]] = None):
"""Applies a t-distribution transformation to selected etas
Initial estimate for degrees of freedom is 80 with bounds (3, 100).
Parameters
----------
model : Model
Pharmpy model to apply t distribution transformation to.
list_of_etas : str, list
Name/names of etas to transform. If None, all etas will be transformed (default).
Return
------
Model
Pharmpy model object
Examples
--------
>>> from pharmpy.modeling import *
>>> model = load_example_model("pheno")
>>> model = transform_etas_tdist(model, ["ETA_1"])
>>> model.statements.before_odes.full_expression("CL") # doctest: +ELLIPSIS
PTVCL*WGT*exp(ETA_1*(1 + (ETA_1**2 + 1)/(4*df1) + (5*ETA_1**4 + 16*ETA_1**2 + 3)/(96*...
See also
--------
transform_etas_boxcox
transform_etas_john_draper
"""
list_of_etas = _format_input_list(list_of_etas)
etas = _get_etas(model, list_of_etas)
eta_transformation = EtaTransformation.tdist(len(etas))
model = _transform_etas(model, eta_transformation, etas)
return model.update_source()
[docs]def transform_etas_john_draper(model: Model, list_of_etas: Optional[Union[List[str], str]] = None):
"""Applies a John Draper transformation [1]_ to spelected etas
Initial estimate for lambda is 0.1 with bounds (-3, 3).
.. [1] John, J., Draper, N. (1980). An Alternative Family of Transformations.
Journal of the Royal Statistical Society. Series C (Applied Statistics),
29(2), 190-197. doi:10.2307/2986305
Parameters
----------
model : Model
Pharmpy model to apply John Draper transformation to.
list_of_etas : str, list
Name/names of etas to transform. If None, all etas will be transformed (default).
Return
------
Model
Pharmpy model object
Examples
--------
>>> from pharmpy.modeling import *
>>> model = load_example_model("pheno")
>>> model = transform_etas_john_draper(model, ["ETA_1"])
>>> model.statements.before_odes.full_expression("CL")
PTVCL*WGT*exp(((Abs(ETA_1) + 1)**lambda1 - 1)*sign(ETA_1)/lambda1)
See also
--------
transform_etas_boxcox
transform_etas_tdist
"""
list_of_etas = _format_input_list(list_of_etas)
etas = _get_etas(model, list_of_etas)
eta_transformation = EtaTransformation.john_draper(len(etas))
model = _transform_etas(model, eta_transformation, etas)
return model.update_source()
def _transform_etas(model, transformation, etas):
etas_assignment, etas_subs = _create_new_etas(etas, transformation.name)
parameters, thetas = _create_new_thetas(model, transformation.theta_type, len(etas))
transformation.apply(etas_assignment, thetas)
statements_new = transformation.assignments
sset = model.statements.subs(etas_subs)
model = model.replace(parameters=parameters, statements=statements_new + sset)
return model
def _create_new_etas(etas_original, transformation):
etas_subs = {}
etas_assignment = {}
if transformation == 'boxcox':
eta_new = 'etab'
elif transformation == 'tdist':
eta_new = 'etat'
elif transformation == 'johndraper':
eta_new = 'etad'
else:
eta_new = 'etan'
for i, eta in enumerate(etas_original, 1):
etas_subs[sympy.Symbol(eta)] = sympy.Symbol(f'{eta_new.upper()}{i}')
etas_assignment[sympy.Symbol(f'{eta_new}{i}')] = sympy.Symbol(f'{eta_new.upper()}{i}')
etas_assignment[sympy.Symbol(f'eta{i}')] = sympy.Symbol(eta)
return etas_assignment, etas_subs
def _create_new_thetas(model, transformation, no_of_thetas):
pset = list(model.parameters)
thetas = {}
theta_name = str(create_symbol(model, stem=transformation, force_numbering=True))
param_settings = (0.01, -3, 3) if transformation == 'lambda' else (80, 3, 100)
if no_of_thetas == 1:
pset.append(Parameter(theta_name, *param_settings))
thetas['theta1'] = theta_name
else:
theta_no = int(re.findall(r'\d', theta_name)[0])
for i in range(1, no_of_thetas + 1):
pset.append(Parameter(theta_name, 0.01, -3, 3))
thetas[f'theta{i}'] = theta_name
theta_name = f'{transformation}{theta_no + i}'
return Parameters.create(pset), thetas
class EtaTransformation:
def __init__(self, name, assignments, theta_type):
self.name = name
self.assignments = assignments
self.theta_type = theta_type
def apply(self, etas, thetas):
for i, assignment in enumerate(self.assignments):
self.assignments[i] = assignment.subs(etas).subs(thetas)
@classmethod
def boxcox(cls, no_of_etas):
assignments = []
for i in range(1, no_of_etas + 1):
symbol = sympy.Symbol(f'etab{i}')
expression = (sympy.exp(sympy.Symbol(f'eta{i}')) ** sympy.Symbol(f'theta{i}') - 1) / (
sympy.Symbol(f'theta{i}')
)
assignment = Assignment(symbol, expression)
assignments.append(assignment)
return cls('boxcox', assignments, 'lambda')
@classmethod
def tdist(cls, no_of_etas):
assignments = []
for i in range(1, no_of_etas + 1):
symbol = sympy.Symbol(f'etat{i}')
eta = sympy.Symbol(f'eta{i}')
theta = sympy.Symbol(f'theta{i}')
num_1 = eta**2 + 1
denom_1 = 4 * theta
num_2 = (5 * eta**4) + (16 * eta**2 + 3)
denom_2 = 96 * theta**2
num_3 = (3 * eta**6) + (19 * eta**4) + (17 * eta**2) - 15
denom_3 = 384 * theta**3
expression = eta * (1 + (num_1 / denom_1) + (num_2 / denom_2) + (num_3 / denom_3))
assignment = Assignment(symbol, expression)
assignments.append(assignment)
return cls('tdist', assignments, 'df')
@classmethod
def john_draper(cls, no_of_etas):
assignments = []
for i in range(1, no_of_etas + 1):
symbol = sympy.Symbol(f'etad{i}')
eta = sympy.Symbol(f'eta{i}')
theta = sympy.Symbol(f'theta{i}')
expression = sympy.sign(eta) * (((abs(eta) + 1) ** theta - 1) / theta)
assignment = Assignment(symbol, expression)
assignments.append(assignment)
return cls('johndraper', assignments, 'lambda')
def __str__(self):
return str(self.assignments)