def linear(cls): """Linear continuous template (for continuous covariates).""" symbol = S('symbol') expression = 1 + S('theta') * (S('cov') - S('median')) template = Assignment(symbol, expression) return cls(template)
def parameter_translation(self, reverse=False, remove_idempotent=False, as_symbols=False): """Get a dict of NONMEM name to Pharmpy parameter name i.e. {'THETA(1)': 'TVCL', 'OMEGA(1,1)': 'IVCL'} """ self.parameters d = dict() for theta_record in self.control_stream.get_records('THETA'): for key, value in theta_record.name_map.items(): nonmem_name = f'THETA({value})' d[nonmem_name] = key for record in self.control_stream.get_records('OMEGA'): for key, value in record.name_map.items(): nonmem_name = f'OMEGA({value[0]},{value[1]})' d[nonmem_name] = key for record in self.control_stream.get_records('SIGMA'): for key, value in record.name_map.items(): nonmem_name = f'SIGMA({value[0]},{value[1]})' d[nonmem_name] = key if remove_idempotent: d = {key: val for key, val in d.items() if key != val} if reverse: d = {val: key for key, val in d.items()} if as_symbols: d = {S(key): S(val) for key, val in d.items()} return d
def exponential(cls): """Exponential template (for continuous covariates).""" symbol = S('symbol') expression = exp(S('theta') * (S('cov') - S('median'))) template = Assignment(symbol, expression) return cls(template)
def power(cls): """Power template (for continuous covariates).""" symbol = S('symbol') expression = (S('cov') / S('median'))**S('theta') template = Assignment(symbol, expression) return cls(template)
def test_apply(eta_trans, symbol, expression): etas = {'eta1': S('ETA(1)'), 'eta2': S('ETA(2)'), 'etab1': S('ETAB(1)'), 'etab2': S('ETAB(2)')} thetas = {'theta1': 'BOXCOX1', 'theta2': 'BOXCOX2'} eta_trans.apply(etas, thetas) assert eta_trans.assignments[1].symbol == symbol assert eta_trans.assignments[1].expression == expression
def boxcox(cls, no_of_etas): assignments = ModelStatements() for i in range(1, no_of_etas + 1): symbol = S(f'etab{i}') expression = (exp(S(f'eta{i}')) ** S(f'theta{i}') - 1) / (S(f'theta{i}')) assignment = Assignment(symbol, expression) assignments.append(assignment) return cls('boxcox', assignments, 'lambda')
def _abbr_translation(self, rv_trans): abbr_pharmpy = self.control_stream.abbreviated.translate_to_pharmpy_names( ) abbr_replace = self.control_stream.abbreviated.replace abbr_trans = update_abbr_record(self, rv_trans) abbr_recs = { S(abbr_pharmpy[value]): S(key) for key, value in abbr_replace.items() if value in abbr_pharmpy.keys() } abbr_trans.update(abbr_recs) return abbr_trans
def categorical(cls, counts): """Linear categorical template (for categorical covariates).""" symbol = S('symbol') most_common = counts.mode().pop(0) categories = counts.unique() values = [1] if np.isnan(most_common): most_common = S('NaN') conditions = [Eq(S('cov'), most_common)] for i, cat in enumerate(categories): if cat != most_common: if len(categories) == 2: values += [1 + S('theta')] else: values += [1 + S(f'theta{i}')] if np.isnan(cat): conditions += [Eq(S('cov'), S('NaN'))] else: conditions += [Eq(S('cov'), cat)] expression = Piecewise(*zip(values, conditions)) template = Assignment(symbol, expression) return cls(template)
def iiv_on_ruv(model, list_of_eps=None, same_eta=True, eta_names=None): """ Multiplies epsilons with exponential (new) etas. Initial estimates for new etas are 0.09. Parameters ---------- model : Model Pharmpy model to apply IIV on epsilons. list_of_eps : str, list Name/names of epsilons to multiply with exponential etas. If None, all epsilons will be chosen. None is default. same_eta : bool Boolean of whether all RUVs from input should use the same new ETA or if one ETA should be created for each RUV. True is default. eta_names: str, list Custom names of new etas. Must be equal to the number epsilons or 1 if same eta. """ list_of_eps = _format_input_list(list_of_eps) eps = _get_epsilons(model, list_of_eps) if eta_names and len(eta_names) != len(eps): raise ValueError( 'The number of provided eta names must be equal to the number of epsilons.' ) rvs, pset, sset = model.random_variables, model.parameters, model.statements if same_eta: eta = _create_eta(pset, 1, eta_names) rvs.append(eta) eta_dict = {e: eta for e in eps} else: etas = [_create_eta(pset, i + 1, eta_names) for i in range(len(eps))] for eta in etas: rvs.append(eta) eta_dict = {e: eta for e, eta in zip(eps, etas)} for e in eps: statement = sset.find_assignment(e.name, is_symbol=False) statement.expression = statement.expression.subs( S(e.name), S(e.name) * sympy.exp(S(eta_dict[e].name))) model.random_variables = rvs model.parameters = pset model.statements = sset model.modelfit_results = None return model
def john_draper(cls, no_of_etas): assignments = ModelStatements() for i in range(1, no_of_etas + 1): symbol = S(f'etad{i}') eta = S(f'eta{i}') theta = S(f'theta{i}') expression = sign(eta) * (((abs(eta) + 1) ** theta - 1) / theta) assignment = Assignment(symbol, expression) assignments.append(assignment) return cls('johndraper', assignments, 'lambda')
def statements(self): try: return self._statements except AttributeError: pass rec = self.get_pred_pk_record() statements = rec.statements des = self._get_des_record() error = self._get_error_record() if error: sub = self.control_stream.get_records('SUBROUTINES')[0] advan = sub.get_option_startswith('ADVAN') trans = sub.get_option_startswith('TRANS') if not trans: trans = 'TRANS1' comp = compartmental_model(self, advan, trans, des) if comp is not None: cm, link = comp statements += [cm, link] else: statements.append( ODESystem()) # FIXME: Placeholder for ODE-system # FIXME: Dummy link statement statements.append(Assignment('F', S('F'))) statements += error.statements if not hasattr(self, '_parameters'): self._read_parameters() trans_statements, trans_params = self._create_name_trans(statements) for key, value in trans_params.items(): try: self.parameters[key].name = value for theta in self.control_stream.get_records('THETA'): theta.update_name_map(trans_params) for omega in self.control_stream.get_records('OMEGA'): omega.update_name_map(trans_params) for sigma in self.control_stream.get_records('SIGMA'): sigma.update_name_map(trans_params) except KeyError: self.random_variables.subs({S(key): value}) statements.subs(trans_statements) self._statements = statements self._old_statements = statements.copy() return statements
def power_on_ruv(model, list_of_eps=None): """ Applies a power effect to provided epsilons. Initial estimates for new thetas are 1 if the error model is proportional, otherwise they are 0.1. Parameters ---------- model : Model Pharmpy model to create block effect on. list_of_eps : list List of epsilons to apply power effect. If None, all epsilons will be used. None is default. """ eps = _get_epsilons(model, list_of_eps) pset, sset = model.parameters, model.statements if model.error_model == 'PROP': theta_init = 1 else: theta_init = 0.1 for i, e in enumerate(eps): theta_name = str( model.create_symbol(stem='power', force_numbering=True)) theta = Parameter(theta_name, theta_init) pset.add(theta) sset.subs( {e.name: model.individual_prediction_symbol**S(theta.name) * e}) model.parameters = pset model.statements = sset return model
def power_on_ruv(model, list_of_eps=None): """ Applies a power effect to provided epsilons. Initial estimates for new thetas are 1 if the error model is proportional, otherwise they are 0.1. Parameters ---------- model : Model Pharmpy model to create block effect on. list_of_eps : str, list Name/names of epsilons to apply power effect. If None, all epsilons will be used. None is default. """ list_of_eps = _format_input_list(list_of_eps) eps = model.random_variables.epsilons if list_of_eps is not None: eps = eps[list_of_eps] pset, sset = model.parameters, model.statements if has_proportional_error(model): theta_init = 1 else: theta_init = 0.1 for i, e in enumerate(eps): theta_name = str(model.create_symbol(stem='power', force_numbering=True)) theta = Parameter(theta_name, theta_init) pset.append(theta) sset.subs({e.name: model.individual_prediction_symbol ** S(theta.name) * e.symbol}) model.parameters = pset model.statements = sset return model
def __init__(self, src, **kwargs): super().__init__() parser = NMTranParser() self.source = src if not self.source.filename_extension: self.source.filename_extension = '.ctl' self._name = self.source.path.stem self.control_stream = parser.parse(src.code) self._initial_individual_estimates_updated = False self._updated_etas_file = None self._dataset_updated = False self._modelfit_results = None self.dependent_variable = S('Y') self.individual_prediction_symbol = S('CIPREDI') self.observation_transformation = self.dependent_variable self._old_observation_transformation = self.dependent_variable
def remove_iov(model): """ Removes all IOV omegas. Parameters ---------- model : Model Pharmpy model to remove IOV from. """ rvs, sset = model.random_variables, model.statements etas = _get_etas(rvs) if not etas: warnings.warn('No IOVs present') return model for eta in etas: statement = sset.find_assignment(eta.name, is_symbol=False) statement.expression = statement.expression.subs(S(eta.name), 0) del rvs[eta] model.random_variables = rvs model.statements = sset model.modelfit_results = None return model
def test_choose_param_init(pheno_path, testdata): model = Model(pheno_path) params = (model.parameters['OMEGA(1,1)'], model.parameters['OMEGA(2,2)']) rvs = RandomVariables(model.random_variables.etas) init = _choose_param_init(model, rvs, params) assert init == 0.0118179 model = Model(pheno_path) model.source.path = testdata # Path where there is no .ext-file init = _choose_param_init(model, rvs, params) assert init == 0.0031045 model = Model(pheno_path) omega1 = S('OMEGA(3,3)') x = stats.Normal('ETA(3)', 0, sympy.sqrt(omega1)) x.variability_level = VariabilityLevel.IIV rvs.add(x) ie = model.modelfit_results.individual_estimates ie['ETA(3)'] = ie['ETA(1)'] model.modelfit_results = ModelfitResults(individual_estimates=ie) init = _choose_param_init(model, rvs, params) assert init == 0.0118179
def remove_iiv(model, list_to_remove=None): """ Removes all IIV omegas given a list with eta names and/or parameter names. Parameters ---------- model : Model Pharmpy model to create block effect on. list_to_remove : list List of etas and/or parameter name to remove. If None, all etas that are IIVs will be removed. None is default. """ rvs, sset = model.random_variables, model.statements etas = _get_etas(model, list_to_remove) for eta in etas: statement = sset.find_assignment(eta.name, is_symbol=False) statement.expression = statement.expression.subs(S(eta.name), 0) rvs.discard(eta) model.random_variables = rvs model.statements = sset model.modelfit_results = None return model
def _name_as_comments(self, statements): params_current = self.parameter_translation(remove_idempotent=True) for name_abbr, name_nonmem in self.control_stream.abbreviated.replace.items( ): if name_nonmem in params_current.keys(): params_current[name_abbr] = params_current.pop(name_nonmem) trans_params = { name_comment: name_comment for name_current, name_comment in params_current.items() if S(name_current) not in statements.free_symbols } trans_statements = { name_current: name_comment for name_current, name_comment in params_current.items() if S(name_current) in statements.free_symbols } return trans_statements, trans_params
def _create_eta(pset, number): omega = S(f'IIV_RUV{number}') pset.add(Parameter(str(omega), 0.09)) eta = stats.Normal(f'RV{number}', 0, sympy.sqrt(omega)) eta.variability_level = VariabilityLevel.IIV return eta
def iiv_on_ruv(model, list_of_eps=None, same_eta=True): """ Multiplies epsilons with exponential (new) etas. Initial estimates for new etas are 0.09. Parameters ---------- model : Model Pharmpy model to apply IIV on epsilons. list_of_eps : list List of epsilons to multiply with exponential etas. If None, all epsilons will be chosen. None is default. same_eta : bool Boolean of whether all RUVs from input should use the same new ETA or if one ETA should be created for each RUV. True is default. """ eps = _get_epsilons(model, list_of_eps) rvs, pset, sset = model.random_variables, model.parameters, model.statements if same_eta: eta = _create_eta(pset, 1) rvs.add(eta) eta_dict = {e: eta for e in eps} else: etas = [_create_eta(pset, i + 1) for i in range(len(eps))] for eta in etas: rvs.add(eta) eta_dict = {e: eta for e, eta in zip(eps, etas)} for e in eps: statement = sset.find_assignment(e.name, is_symbol=False) statement.expression = statement.expression.subs( S(e.name), S(e.name) * sympy.exp(S(eta_dict[e].name)) ) model.random_variables = rvs model.parameters = pset model.statements = sset model.modelfit_results = None return model
def _create_eta(pset, number, eta_names): omega = S(f'IIV_RUV{number}') pset.append(Parameter(str(omega), 0.09)) if eta_names: eta_name = eta_names[number - 1] else: eta_name = f'ETA_RV{number}' eta = RandomVariable.normal(eta_name, 'iiv', 0, omega) return eta
def rv_translation(self, reverse=False, remove_idempotent=False, as_symbols=False): self.random_variables d = dict() for record in self.control_stream.get_records('OMEGA'): for key, value in record.eta_map.items(): nonmem_name = f'ETA({value})' d[nonmem_name] = key for record in self.control_stream.get_records('SIGMA'): for key, value in record.eta_map.items(): nonmem_name = f'EPS({value})' d[nonmem_name] = key if remove_idempotent: d = {key: val for key, val in d.items() if key != val} if reverse: d = {val: key for key, val in d.items()} if as_symbols: d = {S(key): S(val) for key, val in d.items()} return d
def tdist(cls, no_of_etas): assignments = ModelStatements() for i in range(1, no_of_etas + 1): symbol = S(f'etat{i}') eta = S(f'eta{i}') theta = S(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')
def apply(self, parameter, covariate, thetas, statistics): effect_name = f'{parameter}{covariate}' self.template.symbol = S(effect_name) self.template.subs(thetas) self.template.subs({'cov': covariate}) template_str = [str(symbol) for symbol in self.template.free_symbols] if 'mean' in template_str: self.template.subs({'mean': f'{covariate}_MEAN'}) s = Assignment(S(f'{covariate}_MEAN'), Float(statistics['mean'], 6)) self.statistic_statements.append(s) if 'median' in template_str: self.template.subs({'median': f'{covariate}_MEDIAN'}) s = Assignment(S(f'{covariate}_MEDIAN'), Float(statistics['median'], 6)) self.statistic_statements.append(s) if 'std' in template_str: self.template.subs({'std': f'{covariate}_STD'}) s = Assignment(S(f'{covariate}_STD'), Float(statistics['std'], 6)) self.statistic_statements.append(s)
def _create_template(effect, model, covariate): """Creates Covariate class objects with effect template.""" if effect == 'lin': return CovariateEffect.linear() elif effect == 'cat': counts = _count_categorical(model, covariate) return CovariateEffect.categorical(counts) elif effect == 'piece_lin': return CovariateEffect.piecewise_linear() elif effect == 'exp': return CovariateEffect.exponential() elif effect == 'pow': return CovariateEffect.power() else: symbol = S('symbol') expression = sympy.sympify(effect) return CovariateEffect(Assignment(symbol, expression))
def add_etas(model, parameter, expression, operation='*'): """ Adds etas to :class:`pharmpy.model`. Effects that currently have templates are: - Additive (*add*) - Proportional (*prop*) - Exponential (*exp*) - Logit (*logit*) For all except exponential the operation input is not needed. Otherwise user specified input is supported. Initial estimates for new etas are 0.09. Parameters ---------- model : Model Pharmpy model to add new etas to. parameter : str Name of parameter to add new etas to. expression : str Effect on eta. Either abbreviated (see above) or custom. operation : str, optional Whether the new eta should be added or multiplied (default). """ rvs, pset, sset = model.random_variables, model.parameters, model.statements omega = S(f'IIV_{parameter}') eta = stats.Normal(f'ETA_{parameter}', 0, sympy.sqrt(omega)) eta.variability_level = VariabilityLevel.IIV rvs.add(eta) pset.add(Parameter(str(omega), init=0.09)) statement = sset.find_assignment(parameter) eta_addition = _create_template(expression, operation) eta_addition.apply(statement.expression, eta.name) statement.expression = eta_addition.template model.random_variables = rvs model.parameters = pset model.statements = sset return model
def _create_name_trans(self, statements): rvs = self.random_variables conf_functions = { 'comment': self._name_as_comments(statements), 'abbr': self._name_as_abbr(rvs), 'basic': self._name_as_basic(), } abbr = self.control_stream.abbreviated.replace pset_current = { **self.parameter_translation(reverse=True), **{rv: rv for rv in [rv.name for rv in rvs]}, } sset_current = { **abbr, **{ rv.name: rv.name for rv in rvs if rv.name not in abbr.keys() and rv.symbol in statements.free_symbols }, **{ p: p for p in pset_current.values() if p not in abbr.keys() and S(p) in statements.free_symbols }, } trans_sset, trans_pset = dict(), dict() names_sset_translated, names_pset_translated, names_basic = [], [], [] clashing_symbols = set() for setting in pharmpy.plugins.nonmem.conf.parameter_names: trans_sset_setting, trans_pset_setting = conf_functions[setting] if setting != 'basic': clashing_symbols.update( self._clashing_symbols(statements, { **trans_sset_setting, **trans_pset_setting })) for name_current, name_new in trans_sset_setting.items(): name_nonmem = sset_current[name_current] if S( name_new ) in clashing_symbols or name_nonmem in names_sset_translated: continue name_in_sset_current = {v: k for k, v in sset_current.items() }[name_nonmem] trans_sset[name_in_sset_current] = name_new names_sset_translated.append(name_nonmem) if name_nonmem in pset_current.values( ) and name_new in pset_current.keys(): names_pset_translated.append(name_nonmem) for name_current, name_new in trans_pset_setting.items(): name_nonmem = pset_current[name_current] if S( name_new ) in clashing_symbols or name_nonmem in names_pset_translated: continue trans_pset[name_current] = name_new names_pset_translated.append(name_nonmem) if setting == 'basic': params_left = [ k for k in pset_current.keys() if k not in names_pset_translated ] params_left += [ rv.name for rv in rvs if rv.name not in names_sset_translated ] names_basic = [ name for name in params_left if name not in names_sset_translated ] break if clashing_symbols: warnings.warn( f'The parameter names {clashing_symbols} are also names of variables ' f'in the model code. Falling back to the in naming scheme config ' f'names for these.') names_nonmem_all = [rv.name for rv in rvs] + [ key for key in self.parameter_translation().keys() ] if set(names_nonmem_all) - set(names_sset_translated + names_pset_translated + names_basic): raise ValueError( 'Mismatch in number of parameter names, all have not been accounted for. If basic ' 'NONMEM-names are desired as fallback, double-check that "basic" is included in ' 'config-settings for parameter_names.') return trans_sset, trans_pset
def piecewise_linear(cls): """Piecewise linear ("hockey-stick") template (for continuous covariates).""" symbol = S('symbol') values = [ 1 + S('theta1') * (S('cov') - S('median')), 1 + S('theta2') * (S('cov') - S('median')), ] conditions = [Le(S('cov'), S('median')), Gt(S('cov'), S('median'))] expression = Piecewise((values[0], conditions[0]), (values[1], conditions[1])) template = Assignment(symbol, expression) return cls(template)
def _clashing_symbols(statements, trans_statements): parameter_symbols = {S(symb) for _, symb in trans_statements.items()} clashing_symbols = parameter_symbols & statements.free_symbols return clashing_symbols
def add_covariate_effect(model, parameter, covariate, effect, operation='*'): """ Adds covariate effect to :class:`pharmpy.model`. The following effects have templates: - Linear function for continuous covariates (*lin*) - Initial estimate: 0.001 - Upper bound: 100,000 if the median of the covariate is equal to the minimum, otherwise :math:`1/(median - min)` - Lower bound: -100,000 if the median of the covariate is equal to the maximum, otherwise :math:`1/(median - max)` - Linear function for categorical covariates (*cat*) - Initial estimate: 0.001 - Upper bound: 100,000 - Lower bound: -100,000 - Piecewise linear function/"hockey-stick", continuous covariates only (*piece_lin*) - Initial estimate: 0.001 - Upper bound: for first state 1/(median - minimum), otherwise 100,000 - Lower bound: for first state -100,000, otherwise 1/(median - maximum) - Exponential function, continuous covariates only (*exp*) - Initial estimate: 0.001 unless lower bound > 0.001 or upper bound < 0.001. In that case :math:`init = (upper - lower)/2`, if init = 0: :math:`init = upper/2` - Upper bound: if :math:`min - median = 0` or :math:`max - median = 0`, upper bound is 100. Otherwise the upper bound is :math:`min(log(0.01)/(min - median), log(100)/(max - median))` - Lower bound: if :math:`min - median = 0` or :math:`max - median = 0`, lower bound is 0.01. Otherwise the lower bound is :math:`max(log(0.01)/(max - median), log(100)/(min - median))` - Power function, continuous covariates only (*pow*) - Initial estimate: 0.001 - Upper bound: 100,000 - Lower bound: -100 Parameters ---------- model : Model Pharmpy model to add covariate effect to. parameter : str Name of parameter to add covariate effect to. covariate : str Name of covariate. effect : str Type of covariate effect. May be abbreviated covariate effect (see above) or custom. operation : str, optional Whether the covariate effect should be added or multiplied (default). """ sset = model.statements if S(f'{parameter}{covariate}') in sset.free_symbols: warnings.warn('Covariate effect already exists') return model statistics = dict() statistics['mean'] = _calculate_mean(model.dataset, covariate) statistics['median'] = _calculate_median(model.dataset, covariate) statistics['std'] = _calculate_std(model.dataset, covariate) covariate_effect = _create_template(effect, model, covariate) thetas = _create_thetas(model, effect, covariate, covariate_effect.template) param_statement = sset.find_assignment(parameter) index = sset.index(param_statement) covariate_effect.apply(parameter, covariate, thetas, statistics) effect_statement = covariate_effect.create_effect_statement( operation, param_statement) statements = covariate_effect.statistic_statements statements.append(covariate_effect.template) statements.append(effect_statement) for i, statement in enumerate(statements, 1): sset.insert(index + i, statement) model.statements = sset return model