Source code for pharmpy.modeling.trial_design

import math
from collections.abc import Sequence

from pharmpy.basic import Expr
from pharmpy.deps import numpy as np
from pharmpy.deps import pandas as pd
from pharmpy.model import (
    Administration,
    Arm,
    Bolus,
    DataVariable,
    Model,
    Observations,
    TrialDesign,
    get_and_check_dataset,
)

from .data import get_doses, get_ids, get_observations


[docs] def create_trial_design(idv_name: str = 'TIME') -> TrialDesign: """Create an empty trial design Parameters ---------- idv_name : str Optional name of the independent variable. Default is TIME Returns ------- TrialDesign An empty trial design Example ------- >>> from pharmpy.modeling import create_trial_design >>> td = create_trial_design() >>> td.is_empty True >>> td Empty trial design object """ idv = DataVariable.create(name=idv_name, type='idv', scale='ratio', count=False) new_td = TrialDesign.create(arms=(), independent_variable=idv) return new_td
[docs] def add_arm(td: TrialDesign, name: str, size: int) -> TrialDesign: """Add an empty arm to a trial design Parameters ---------- td : TrialDesign TrialDesign to add to name : str Name of the arm size : int Size of the arm (number of subjects) Returns ------- TrialDesign An updated TrialDesign Example ------- >>> from pharmpy.modeling import create_trial_design, add_arm >>> td = create_trial_design() >>> td = add_arm(td, name="Placebo", size=100) >>> td Empty trial design object """ arm = Arm.create(name=name, size=size, activities=()) new_td = td + arm return new_td
[docs] def add_observations( td: TrialDesign, arm: str, variable: str, time_points: Sequence[float], start_time: float = 0.0 ) -> TrialDesign: """Add observations to an arm in a trial design Parameters ---------- td : TrialDesign TrialDesign to add to arm : str Name of the arm variable : str Name of the variable to observe time_points : Sequence[float] List of the time points for the observations start_time : float Set a start_time to offset the time_points. Default 0.0 Returns ------- TrialDesign An updated TrialDesign Example ------- >>> from pharmpy.modeling import create_trial_design, add_arm, add_observations >>> td = create_trial_design() >>> td = add_arm(td, name="Placebo", size=100) >>> td = add_observations(td, arm="Placebo", variable="DV", time_points=[0.0, 1.0, 2.0, 4.0, 16.0]) >>> td ╭────── Observations ──────╮ Placebo │ 0.0, 1.0, 2.0, 4.0, 16.0 │ ╰─────────── DV ───────────╯ ├──────────────────────────┤ 0.0 16.0 <BLANKLINE> """ datavar = DataVariable.create(name=variable, type="dv") obs = Observations.create(variable=datavar, start_time=start_time, time_points=time_points) oldarm = td[arm] newarm = oldarm + obs newtd = td.replace_arm(newarm) return newtd
[docs] def add_administration( td: TrialDesign, arm: str, variable: str, amount: float, time_points: Sequence[float], start_time: float = 0.0, ) -> TrialDesign: """Add observations to an arm in a trial design Parameters ---------- td : TrialDesign TrialDesign to add to arm : str Name of the arm variable : str Name of the dose variable (e.g. AMT) amount : float The dose amount time_points : Sequence[float] List of the time points for the administration start_time : float Set a start_time to offset the time_points. Default 0.0 Returns ------- TrialDesign An updated TrialDesign Example ------- >>> from pharmpy.modeling import create_trial_design, add_arm, add_observations >>> from pharmpy.modeling import add_administration >>> td = create_trial_design() >>> td = add_arm(td, name="Drug", size=100) >>> td = add_observations(td, arm="Drug", variable="DV", time_points=[0.0, 1.0, 2.0, 4.0, 16.0]) >>> td = add_administration(td, arm="Drug", variable="AMT", amount=10.0, time_points=[0.0, 8.0]) >>> td ╭────── Observations ──────╮ Drug │ 0.0, 1.0, 2.0, 4.0, 16.0 │ ╰─────────── DV ───────────╯ ╭───── Administration ─────╮ │ 0.0, 8.0 │ ╰─────── 10.0 Bolus ───────╯ ├──────────────────────────┤ 0.0 16.0 <BLANKLINE> """ datavar = DataVariable.create(name=variable, type="dose") dose = Bolus(amount=Expr(amount), admid=1) adm = Administration.create( variable=datavar, dose=dose, start_time=start_time, time_points=time_points ) oldarm = td[arm] newarm = oldarm + adm newtd = td.replace_arm(newarm) return newtd
[docs] def create_dataset_from_design(td: TrialDesign) -> pd.DataFrame: """Create a dataset matching a design Parameters ---------- td : TrialDesign TrialDesign to add to Returns ------- pd.DataFrame A dataset Example ------- >>> from pharmpy.modeling import create_trial_design, add_arm, add_observations >>> from pharmpy.modeling import add_administration, create_dataset_from_design >>> td = create_trial_design() >>> td = add_arm(td, name="Drug", size=100) >>> td = add_observations(td, arm="Drug", variable="DV", time_points=[0.0, 1.0, 2.0, 4.0, 16.0]) >>> td = add_administration(td, arm="Drug", variable="AMT", amount=10.0, time_points=[0.0, 8.0]) >>> df = create_dataset_from_design(td) >>> df ID TIME EVID AMT DV 0 1 0.0 1 10.0 0.0 1 1 0.0 0 0.0 0.0 2 1 1.0 0 0.0 0.0 3 1 2.0 0 0.0 0.0 4 1 4.0 0 0.0 0.0 .. ... ... ... ... ... 695 100 1.0 0 0.0 0.0 696 100 2.0 0 0.0 0.0 697 100 4.0 0 0.0 0.0 698 100 8.0 1 10.0 0.0 699 100 16.0 0 0.0 0.0 <BLANKLINE> [700 rows x 5 columns] """ next_id = 1 full_df = pd.DataFrame() idv_name = td.independent_variable.name for armid, arm in enumerate(td, start=1): amount = [] evid = [] time = [] armcol = [] for act in arm: for t in act.time_points: curtime = t + act.start_time armcol.append(armid) time.append(curtime) if isinstance(act, Observations): evid.append(0) amount.append(0) else: evid.append(1) amount.append(float(act.dose.amount)) d = {idv_name: time, 'EVID': evid, 'AMT': amount} if len(td) > 1: d = {'ARM': armcol} | d one_id = pd.DataFrame(d) df = pd.concat([one_id] * arm.size, ignore_index=True) df.insert(0, 'ID', np.repeat(range(next_id, next_id + arm.size), len(one_id))) full_df = pd.concat([full_df, df], ignore_index=True) next_id += arm.size full_df = full_df.sort_values( by=["ID", idv_name, "EVID"], ascending=[True, True, False] ).reset_index(drop=True) full_df['DV'] = 0.0 return full_df
def infer_design_from_dataset(model: Model) -> TrialDesign: df = get_and_check_dataset(model) td = create_trial_design(idv_name=model.datainfo.idv_column.name) nids = len(get_ids(model)) td = add_arm(td, name="DRUG", size=nids) # Rule 1: observation time points present in over 50% of individuals moved to closest nice number # Rule 2: Currently: don't care about the remaining time points all_doses = get_doses(model) doses = all_doses.reset_index()[['ID', 'TIME']] frequent_dosing_times = _get_frequent_time_points(doses) # FIXME: This depends on times already being nice numbers for dt in frequent_dosing_times: amts = df.loc[(df['TIME'] == dt) & df['AMT'] != 0, 'AMT'] if len(amts.unique()) == 1: td = add_administration( td, arm="DRUG", variable="AMT", time_points=[dt], amount=amts.iloc[0] ) observations = get_observations(model).reset_index()[['ID', 'TIME']] frequent_observation_times = _get_frequent_time_points(observations) td = add_observations( td, arm="DRUG", variable="DV", time_points=list(frequent_observation_times) ) return td # Could move to internal NICE_NUMBERS = (0.0, 0.25, 0.5, 0.75, 1.0) def _move_to_closest_nice_number(x): current_nice = 0.0 current_delta = 2.0 for d in NICE_NUMBERS: nice_num = math.floor(x) + d delta = abs(x - nice_num) if delta < current_delta: current_delta = delta current_nice = nice_num return current_nice def _niceify(x): return pd.Series(x).apply(_move_to_closest_nice_number) def _get_frequent_time_points(df): # df has an ID and a TIME column nids = len(df['ID'].unique()) freq = df.groupby('TIME')['ID'].nunique() frequent_times = _niceify(freq[freq / nids > 0.5].index) return frequent_times