Model Diagnostics
Comprehensive diagnostic tools for assessing model fit and validating assumptions.
Overview
from neopkpd.estimation import compute_diagnostics
result = estimate(data, model, config)
diagnostics = compute_diagnostics(result)
Residual Types
CWRES - Conditional Weighted Residuals
# Access CWRES
for ind in result.individual_estimates:
print(f"Subject {ind.subject_id}: CWRES = {ind.cwres}")
# All CWRES combined
import numpy as np
all_cwres = np.concatenate([ind.cwres for ind in result.individual_estimates])
print(f"CWRES mean: {np.mean(all_cwres):.3f}")
print(f"CWRES std: {np.std(all_cwres):.3f}")
IWRES - Individual Weighted Residuals
all_iwres = np.concatenate([ind.iwres for ind in result.individual_estimates])
Expected Values
| Residual |
Expected Mean |
Expected SD |
| CWRES |
0 |
1 |
| IWRES |
0 |
1 |
| NPDE |
0 |
1 |
Shrinkage
Eta Shrinkage
# Access eta shrinkage
print(f"Eta shrinkage: {result.eta_shrinkage}")
# Interpretation
for i, shrink in enumerate(result.eta_shrinkage):
pct = shrink * 100
status = "Good" if pct < 20 else ("Moderate" if pct < 30 else "High")
print(f" η{i+1}: {pct:.1f}% ({status})")
| Shrinkage |
Interpretation |
| < 20% |
Good |
| 20-30% |
Moderate |
| > 30% |
High - use caution |
Epsilon Shrinkage
print(f"Epsilon shrinkage: {result.epsilon_shrinkage*100:.1f}%")
Goodness-of-Fit Plots
import matplotlib.pyplot as plt
import numpy as np
# Collect data
obs = np.concatenate([ind.observed for ind in result.individual_estimates])
pred = np.concatenate([ind.pred for ind in result.individual_estimates])
ipred = np.concatenate([ind.ipred for ind in result.individual_estimates])
cwres = np.concatenate([ind.cwres for ind in result.individual_estimates])
time = np.concatenate([ind.times for ind in result.individual_estimates])
# Create 4-panel GOF plot
fig, axes = plt.subplots(2, 2, figsize=(10, 10))
# DV vs PRED
axes[0, 0].scatter(pred, obs, alpha=0.5)
axes[0, 0].plot([pred.min(), pred.max()], [pred.min(), pred.max()], 'r--')
axes[0, 0].set_xlabel('Population Prediction')
axes[0, 0].set_ylabel('Observed')
axes[0, 0].set_title('DV vs PRED')
# DV vs IPRED
axes[0, 1].scatter(ipred, obs, alpha=0.5)
axes[0, 1].plot([ipred.min(), ipred.max()], [ipred.min(), ipred.max()], 'r--')
axes[0, 1].set_xlabel('Individual Prediction')
axes[0, 1].set_ylabel('Observed')
axes[0, 1].set_title('DV vs IPRED')
# CWRES vs Time
axes[1, 0].scatter(time, cwres, alpha=0.5)
axes[1, 0].axhline(y=0, color='r', linestyle='--')
axes[1, 0].axhline(y=-2, color='gray', linestyle=':')
axes[1, 0].axhline(y=2, color='gray', linestyle=':')
axes[1, 0].set_xlabel('Time')
axes[1, 0].set_ylabel('CWRES')
axes[1, 0].set_title('CWRES vs Time')
# CWRES vs PRED
axes[1, 1].scatter(pred, cwres, alpha=0.5)
axes[1, 1].axhline(y=0, color='r', linestyle='--')
axes[1, 1].axhline(y=-2, color='gray', linestyle=':')
axes[1, 1].axhline(y=2, color='gray', linestyle=':')
axes[1, 1].set_xlabel('Population Prediction')
axes[1, 1].set_ylabel('CWRES')
axes[1, 1].set_title('CWRES vs PRED')
plt.tight_layout()
plt.show()
QQ Plot
import scipy.stats as stats
fig, ax = plt.subplots(figsize=(6, 6))
stats.probplot(cwres, dist="norm", plot=ax)
ax.set_title("QQ Plot of CWRES")
plt.show()
Individual Fits
# Plot first 9 subjects
fig, axes = plt.subplots(3, 3, figsize=(12, 12))
axes = axes.flatten()
for i, ind in enumerate(result.individual_estimates[:9]):
ax = axes[i]
ax.scatter(ind.times, ind.observed, label='Observed', s=50)
ax.plot(ind.times, ind.ipred, 'b-', label='IPRED', linewidth=2)
ax.plot(ind.times, ind.pred, 'r--', label='PRED', alpha=0.7)
ax.set_title(f"Subject {ind.subject_id}")
ax.set_xlabel('Time')
ax.set_ylabel('Concentration')
if i == 0:
ax.legend()
plt.tight_layout()
plt.show()
Covariance Diagnostics
# Condition number
print(f"Condition number: {result.condition_number:.1f}")
if result.condition_number > 1000:
print("WARNING: High condition number - potential identifiability issues")
# Eigenvalue ratio
print(f"Eigenvalue ratio: {result.eigenvalue_ratio:.1f}")
# Correlation matrix
if result.covariance_matrix is not None:
corr = result.correlation_matrix
print("\nParameter correlation matrix:")
print(corr)
Diagnostic Checklist
def check_diagnostics(result):
issues = []
# Convergence
if not result.converged:
issues.append("Model did not converge")
# Condition number
if result.condition_number > 1000:
issues.append(f"High condition number: {result.condition_number:.0f}")
# RSE
for i, rse in enumerate(result.theta_rse):
if rse > 50:
issues.append(f"High RSE for θ{i+1}: {rse:.1f}%")
# Shrinkage
for i, shrink in enumerate(result.eta_shrinkage):
if shrink > 0.3:
issues.append(f"High η{i+1} shrinkage: {shrink*100:.1f}%")
# CWRES
all_cwres = np.concatenate([ind.cwres for ind in result.individual_estimates])
cwres_mean = np.mean(all_cwres)
cwres_std = np.std(all_cwres)
if abs(cwres_mean) > 0.2:
issues.append(f"CWRES mean bias: {cwres_mean:.3f}")
if abs(cwres_std - 1) > 0.2:
issues.append(f"CWRES SD deviation: {cwres_std:.3f}")
return issues
# Run checks
issues = check_diagnostics(result)
if issues:
print("Diagnostic issues found:")
for issue in issues:
print(f" - {issue}")
else:
print("All diagnostics passed!")
See Also