Skip to main content

pmcore/estimation/nonparametric/
predictions.rs

1use std::path::Path;
2
3use anyhow::{bail, Result};
4use pharmsol::{prelude::simulator::Prediction, Censor, Data, Predictions as PredTrait};
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    estimation::nonparametric::{theta::Theta, weights::Weights},
9    estimation::nonparametric::{weighted_median, Posterior},
10};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct NPPredictionRow {
14    id: String,
15    time: f64,
16    outeq: usize,
17    block: usize,
18    obs: Option<f64>,
19    cens: Censor,
20    pop_mean: f64,
21    pop_median: f64,
22    post_mean: f64,
23    post_median: f64,
24}
25
26impl NPPredictionRow {
27    pub fn id(&self) -> &str {
28        &self.id
29    }
30    pub fn time(&self) -> f64 {
31        self.time
32    }
33    pub fn outeq(&self) -> usize {
34        self.outeq
35    }
36    pub fn block(&self) -> usize {
37        self.block
38    }
39    pub fn obs(&self) -> Option<f64> {
40        self.obs
41    }
42    pub fn pop_mean(&self) -> f64 {
43        self.pop_mean
44    }
45    pub fn pop_median(&self) -> f64 {
46        self.pop_median
47    }
48    pub fn post_mean(&self) -> f64 {
49        self.post_mean
50    }
51    pub fn post_median(&self) -> f64 {
52        self.post_median
53    }
54
55    pub fn censoring(&self) -> Censor {
56        self.cens
57    }
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct NPPredictions {
62    predictions: Vec<NPPredictionRow>,
63}
64
65impl IntoIterator for NPPredictions {
66    type Item = NPPredictionRow;
67    type IntoIter = std::vec::IntoIter<NPPredictionRow>;
68
69    fn into_iter(self) -> Self::IntoIter {
70        self.predictions.into_iter()
71    }
72}
73
74impl Default for NPPredictions {
75    fn default() -> Self {
76        NPPredictions::new()
77    }
78}
79
80impl NPPredictions {
81    pub fn new() -> Self {
82        NPPredictions {
83            predictions: Vec::new(),
84        }
85    }
86
87    pub fn add(&mut self, row: NPPredictionRow) {
88        self.predictions.push(row);
89    }
90
91    pub fn predictions(&self) -> &[NPPredictionRow] {
92        &self.predictions
93    }
94
95    /// Write the predictions to a CSV file readable by Pmetrics.
96    ///
97    /// The parent directory is created if it does not already exist. The header
98    /// matches the fields of [`NPPredictionRow`]:
99    /// `id,time,outeq,block,obs,cens,pop_mean,pop_median,post_mean,post_median`.
100    pub fn write(&self, path: &Path) -> Result<()> {
101        tracing::debug!("Writing predictions...");
102
103        super::create_parent_dir(path)?;
104
105        let mut writer = csv::WriterBuilder::new()
106            .has_headers(true)
107            .from_path(path)?;
108
109        for row in &self.predictions {
110            writer.serialize(row)?;
111        }
112
113        writer.flush()?;
114        Ok(())
115    }
116
117    pub fn calculate(
118        equation: &impl pharmsol::prelude::simulator::Equation,
119        data: &Data,
120        theta: &Theta,
121        w: &Weights,
122        posterior: &Posterior,
123        idelta: f64,
124        tad: f64,
125    ) -> Result<Self> {
126        let mut container = NPPredictions::new();
127
128        let data = data.clone().expand(idelta, tad);
129        let subjects = data.subjects();
130
131        if subjects.len() != posterior.matrix().nrows() {
132            bail!("Number of subjects and number of posterior means do not match");
133        };
134
135        for (subject_index, subject) in subjects.iter().enumerate() {
136            let mut predictions: Vec<Vec<Prediction>> = Vec::new();
137
138            for spp in theta.matrix().row_iter() {
139                let spp_values = spp.iter().cloned().collect::<Vec<f64>>();
140                let pred = equation
141                    .simulate_subject_dense(subject, &spp_values, None)?
142                    .0
143                    .get_predictions();
144                predictions.push(pred);
145            }
146
147            if predictions.is_empty() {
148                continue;
149            }
150
151            let mut pop_mean: Vec<f64> = vec![0.0; predictions.first().unwrap().len()];
152            for (i, outer_pred) in predictions.iter().enumerate() {
153                for (j, pred) in outer_pred.iter().enumerate() {
154                    pop_mean[j] += pred.prediction() * w[i];
155                }
156            }
157
158            let mut pop_median: Vec<f64> = Vec::new();
159            for j in 0..predictions.first().unwrap().len() {
160                let mut values: Vec<f64> = Vec::new();
161                let mut weights: Vec<f64> = Vec::new();
162
163                for (i, outer_pred) in predictions.iter().enumerate() {
164                    values.push(outer_pred[j].prediction());
165                    weights.push(w[i]);
166                }
167
168                let median_val = weighted_median(&values, &weights);
169                pop_median.push(median_val);
170            }
171
172            let mut posterior_mean: Vec<f64> = vec![0.0; predictions.first().unwrap().len()];
173            for (i, outer_pred) in predictions.iter().enumerate() {
174                for (j, pred) in outer_pred.iter().enumerate() {
175                    posterior_mean[j] += pred.prediction() * posterior.matrix()[(subject_index, i)];
176                }
177            }
178
179            let mut posterior_median: Vec<f64> = Vec::new();
180            for j in 0..predictions.first().unwrap().len() {
181                let mut values: Vec<f64> = Vec::new();
182                let mut weights: Vec<f64> = Vec::new();
183
184                for (i, outer_pred) in predictions.iter().enumerate() {
185                    values.push(outer_pred[j].prediction());
186                    weights.push(posterior.matrix()[(subject_index, i)]);
187                }
188
189                let median_val = weighted_median(&values, &weights);
190                posterior_median.push(median_val);
191            }
192
193            if let Some(first_spp_preds) = predictions.first() {
194                for (j, p) in first_spp_preds.iter().enumerate() {
195                    let row = NPPredictionRow {
196                        id: subject.id().clone(),
197                        time: p.time(),
198                        outeq: p.outeq(),
199                        block: p.occasion(),
200                        obs: p.observation(),
201                        cens: p.censoring(),
202                        pop_mean: pop_mean[j],
203                        pop_median: pop_median[j],
204                        post_mean: posterior_mean[j],
205                        post_median: posterior_median[j],
206                    };
207                    container.add(row);
208                }
209            }
210        }
211
212        Ok(container)
213    }
214}