/* * This file is part of ADDIS (Aggregate Data Drug Information System). * ADDIS is distributed from http://drugis.org/. * Copyright © 2009 Gert van Valkenhoef, Tommi Tervonen. * Copyright © 2010 Gert van Valkenhoef, Tommi Tervonen, Tijs Zwinkels, * Maarten Jacobs, Hanno Koeslag, Florin Schimbinschi, Ahmad Kamal, Daniel * Reid. * Copyright © 2011 Gert van Valkenhoef, Ahmad Kamal, Daniel Reid, Florin * Schimbinschi. * Copyright © 2012 Gert van Valkenhoef, Daniel Reid, Joël Kuiper, Wouter * Reckman. * Copyright © 2013 Gert van Valkenhoef, Joël Kuiper. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.drugis.addis.entities.analysis; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.SwingUtilities; import org.drugis.addis.entities.Arm; import org.drugis.addis.entities.ContinuousMeasurement; import org.drugis.addis.entities.Entity; import org.drugis.addis.entities.Indication; import org.drugis.addis.entities.Measurement; import org.drugis.addis.entities.OutcomeMeasure; import org.drugis.addis.entities.RateMeasurement; import org.drugis.addis.entities.RateVariableType; import org.drugis.addis.entities.Study; import org.drugis.addis.entities.relativeeffect.Distribution; import org.drugis.addis.entities.relativeeffect.Gaussian; import org.drugis.addis.entities.relativeeffect.GaussianBase; import org.drugis.addis.entities.relativeeffect.LogGaussian; import org.drugis.addis.entities.relativeeffect.LogitGaussian; import org.drugis.addis.entities.treatment.Category; import org.drugis.addis.entities.treatment.TreatmentDefinition; import org.drugis.addis.mcmcmodel.AbstractBaselineModel; import org.drugis.addis.mcmcmodel.BaselineMeanDifferenceModel; import org.drugis.addis.mcmcmodel.BaselineOddsModel; import org.drugis.addis.util.EntityUtil; import org.drugis.addis.util.comparator.AlphabeticalComparator; import org.drugis.common.beans.SortedSetModel; import org.drugis.common.threading.Task; import org.drugis.common.threading.ThreadHandler; import org.drugis.mtc.MCMCModel; import org.drugis.mtc.presentation.ConsistencyWrapper; import org.drugis.mtc.presentation.MCMCModelWrapper; import org.drugis.mtc.presentation.MCMCSimulationWrapper; import org.drugis.mtc.summary.MultivariateNormalSummary; import org.drugis.mtc.summary.NormalSummary; import org.drugis.mtc.summary.Summary; import org.drugis.mtc.summary.TransformedMultivariateNormalSummary; import com.jgoodies.binding.list.ObservableList; public class MetaBenefitRiskAnalysis extends BenefitRiskAnalysis<TreatmentDefinition> { private final class MetaMeasurementSource extends AbstractMeasurementSource<TreatmentDefinition> { public MetaMeasurementSource() { PropertyChangeListener l = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { SwingUtilities.invokeLater(new Runnable() { public void run() { notifyListeners(); } }); } }; for (Summary s : getEffectSummaries()) { s.addPropertyChangeListener(l); } } private List<Summary> getEffectSummaries() { List<Summary> summaryList = getAbsoluteEffectSummaries(); summaryList.addAll(d_relativeEffects.values()); return summaryList; } } private Indication d_indication; private List<MetaAnalysis> d_metaAnalyses; private ObservableList<TreatmentDefinition> d_alternatives; private TreatmentDefinition d_baseline; private Map<OutcomeMeasure, MCMCModelWrapper> d_baselineModelMap; private AnalysisType d_analysisType; private DecisionContext d_decisionContext; private Map<MetaAnalysis, TransformedMultivariateNormalSummary> d_relativeEffects = new HashMap<MetaAnalysis, TransformedMultivariateNormalSummary>(); public static String PROPERTY_ALTERNATIVES = "alternatives"; public static String PROPERTY_BASELINE = "baseline"; public static String PROPERTY_METAANALYSES = "metaAnalyses"; public MetaBenefitRiskAnalysis(String name, Indication indication, List<MetaAnalysis> metaAnalyses, TreatmentDefinition baseline, List<TreatmentDefinition> alternatives, AnalysisType analysisType) { this(name, indication, metaAnalyses, baseline, alternatives, analysisType, null); } public MetaBenefitRiskAnalysis(String name, Indication indication, List<MetaAnalysis> metaAnalyses, TreatmentDefinition baseline, List<TreatmentDefinition> alternatives, AnalysisType analysisType, DecisionContext context) { super(name); d_indication = indication; d_metaAnalyses = metaAnalyses; d_alternatives = new SortedSetModel<TreatmentDefinition>(alternatives); d_baseline = baseline; d_alternatives.add(baseline); d_baselineModelMap = new HashMap<OutcomeMeasure, MCMCModelWrapper>(); d_analysisType = analysisType; if(d_analysisType == AnalysisType.LyndOBrien && (d_metaAnalyses.size() != 2 || d_alternatives.size() != 2) ) { throw new IllegalArgumentException("Attempt to create Lynd & O'Brien analysis with not exactly 2 criteria and 2 alternatives"); } d_decisionContext = context; for (MetaAnalysis ma : d_metaAnalyses) { double[][] transformation = createTransform(ma); d_relativeEffects.put(ma, new TransformedMultivariateNormalSummary(ma.getRelativeEffectsSummary(), transformation)); } } /** * Fill in a transformation matrix to change the baseline of the relative effects. * For the algorithm see docs/transform.pdf in the repository. */ private double[][] createTransform(MetaAnalysis ma) { final List<TreatmentDefinition> rowAlternatives = getNonBaselineAlternatives(); final List<TreatmentDefinition> columnAlternatives = new ArrayList<TreatmentDefinition>(ma.getAlternatives()); final TreatmentDefinition rowBaseline = d_baseline; // the desired baseline final TreatmentDefinition columnBaseline = columnAlternatives.remove(0); // the meta-analysis baseline (first alternative by definition) final int nRows = rowAlternatives.size(); final int nCols = columnAlternatives.size(); double[][] transformation = new double[nRows][nCols]; // Change of baseline (from x to y): d_{y,z} = -d_{x,y} + d_{x,z} // fill the column where the rowBaseline occurs with -1 (if baselines differ) if (!rowBaseline.equals(columnBaseline)) { int minusColumn = columnAlternatives.indexOf(rowBaseline); for (int i = 0; i < nRows; ++i) { transformation[i][minusColumn] = -1; } } // +1 where row- and column-TreatmentDefinitions match for (int i = 0; i < nRows; ++i) { int oneColumn = columnAlternatives.indexOf(rowAlternatives.get(i)); if (oneColumn >= 0) { transformation[i][oneColumn] = 1; } } return transformation; } public Indication getIndication() { return d_indication; } public List<OutcomeMeasure> getCriteria() { List<OutcomeMeasure> sortedList = findOutcomeMeasures(); Collections.sort(sortedList); return sortedList; } private List<OutcomeMeasure> findOutcomeMeasures() { List<OutcomeMeasure> list = new ArrayList<OutcomeMeasure>(); for (MetaAnalysis a : d_metaAnalyses) { list.add(a.getOutcomeMeasure()); } return list; } public List<MetaAnalysis> getMetaAnalyses() { ArrayList<MetaAnalysis> analyses = new ArrayList<MetaAnalysis>(d_metaAnalyses); Collections.sort(analyses, new AlphabeticalComparator()); return Collections.unmodifiableList(analyses); } void setMetaAnalyses(List<MetaAnalysis> metaAnalysis) { d_metaAnalyses = metaAnalysis; } public ObservableList<TreatmentDefinition> getAlternatives() { return d_alternatives; } @Override public Set<? extends Entity> getDependencies() { HashSet<Entity> dependencies = new HashSet<Entity>(); dependencies.add(d_indication); for (Category category : EntityUtil.<Category>flatten(d_alternatives)) { dependencies.addAll(category.getDependencies()); } EntityUtil.addRecursiveDependencies(dependencies, d_metaAnalyses); return dependencies; } @Override public boolean deepEquals(Entity other) { if (!equals(other) || !(other instanceof MetaBenefitRiskAnalysis)) { return false; } MetaBenefitRiskAnalysis o = (MetaBenefitRiskAnalysis) other; return EntityUtil.deepEqual(getBaseline(), o.getBaseline()) && EntityUtil.deepEqual(getIndication(), o.getIndication()) && EntityUtil.deepEqual(getMetaAnalyses(), o.getMetaAnalyses()) && EntityUtil.deepEqual(getAlternatives(), o.getAlternatives()) && EntityUtil.deepEqual(getDecisionContext(), o.getDecisionContext()); } @Override public String toString() { return getName(); } public TreatmentDefinition getBaseline() { return d_baseline; } private MetaAnalysis findMetaAnalysis(OutcomeMeasure om) { for(MetaAnalysis ma : getMetaAnalyses()){ if(ma.getOutcomeMeasure().equals(om)) { return ma; } } return null; } public GaussianBase getRelativeEffectDistribution(OutcomeMeasure om, TreatmentDefinition subject) { if (subject.equals(d_baseline)) { return createDistribution(om, 0.0, 0.0); } MetaAnalysis ma = findMetaAnalysis(om); if (ma == null) { throw new IllegalArgumentException("No meta-analysis for outcome " + om); } MultivariateNormalSummary summary = d_relativeEffects.get(ma); if (summary.getDefined()) { int index = getNonBaselineAlternatives().indexOf(subject); return createDistribution(om, summary.getMeanVector()[index], Math.sqrt(summary.getCovarianceMatrix()[index][index])); } else { return null; } } /** * Get a summary of the effects on the given criterion of the non-baseline alternatives relative to the baseline. * @param om The criterion to get the summary for. * @return A MultivariateNormalSummary (mean and covariance) of the relative effects. * @see MetaBenefitRiskAnalysis#getNonBaselineAlternatives() The non-baseline alternatives. * @see MetaBenefitRiskAnalysis#getBaseline() The baseline. */ public MultivariateNormalSummary getRelativeEffectsSummary(OutcomeMeasure om) { return d_relativeEffects.get(findMetaAnalysis(om)); } private GaussianBase createDistribution(OutcomeMeasure om, double mu, double sigma) { return (om.getVariableType() instanceof RateVariableType) ? new LogGaussian(mu, sigma) : new Gaussian(mu, sigma); } /** * Get the measurement to be used in the BenefitRisk simulation. */ public Distribution getMeasurement(OutcomeMeasure om, TreatmentDefinition d) { if (om.getVariableType() instanceof RateVariableType) { GaussianBase logOdds = getAbsoluteEffectDistribution(d, om); return logOdds == null ? null : new LogitGaussian(logOdds.getMu(), logOdds.getSigma()); } return getAbsoluteEffectDistribution(d, om); } /** * Get the assumed distribution for the baseline odds. */ public GaussianBase getBaselineDistribution(OutcomeMeasure om) { AbstractBaselineModel<?> model = (AbstractBaselineModel<?>) getBaselineModel(om).getModel(); NormalSummary summary = model.getSummary(); if (summary == null || !summary.getDefined()) { return null; } return createDistribution(om, summary.getMean(), summary.getStandardDeviation()); } public MCMCModelWrapper getBaselineModel(OutcomeMeasure om) { MCMCModelWrapper model = d_baselineModelMap.get(om); if (model == null || model.isDestroyed()) { model = createBaselineModel(om); d_baselineModelMap.put(om, model); } return model; } private MCMCModelWrapper createBaselineModel(OutcomeMeasure om) { AbstractBaselineModel<?> model = (AbstractBaselineModel<?>) ((om.getVariableType() instanceof RateVariableType) ? new BaselineOddsModel(getBaselineMeasurements(om, RateMeasurement.class)) : new BaselineMeanDifferenceModel(getBaselineMeasurements(om, ContinuousMeasurement.class))); return new MCMCSimulationWrapper<MCMCModel>(model, "Baseline Model"); } @SuppressWarnings("unchecked") private <M extends Measurement> List<M> getBaselineMeasurements(OutcomeMeasure om, Class<M> cls) { List<M> result = new ArrayList<M>(); MetaAnalysis ma = findMetaAnalysis(om); for (Study s : ma.getIncludedStudies()) { Arm a = s.findMatchingArm(getBaseline()); if (a != null) { result.add((M)s.getMeasurement(om, a)); } } return result; } /** * The absolute effect of d on om given the assumed odds of the baseline treatment. */ private GaussianBase getAbsoluteEffectDistribution(TreatmentDefinition d, OutcomeMeasure om) { GaussianBase baseline = getBaselineDistribution(om); GaussianBase relative = getRelativeEffectDistribution(om, d); if (baseline == null || relative == null) return null; return baseline.plus(relative); } public void runAllConsistencyModels() { List<Task> tasks = getNetworkTasks(); ThreadHandler.getInstance().scheduleTasks(tasks); } public List<Task> getNetworkTasks() { List<Task> tasks = new ArrayList<Task>(); for (MetaAnalysis ma : getMetaAnalyses() ){ if (ma instanceof NetworkMetaAnalysis) { ConsistencyWrapper<TreatmentDefinition> wrapper = ((NetworkMetaAnalysis) ma).getConsistencyModel(); if (!wrapper.isSaved()) { tasks.add((Task) wrapper.getModel().getActivityTask()); } } } return tasks; } public AnalysisType getAnalysisType() { return d_analysisType; } public List<Summary> getAbsoluteEffectSummaries() { List<Summary> summaryList = new ArrayList<Summary>(); for (OutcomeMeasure om : getCriteria()) { summaryList.add(((AbstractBaselineModel<?>) getBaselineModel(om).getModel()).getSummary()); } return summaryList; } public List<TreatmentDefinition> getNonBaselineAlternatives() { List<TreatmentDefinition> alternatives = new ArrayList<TreatmentDefinition>(getAlternatives()); alternatives.remove(getBaseline()); return alternatives; } public MeasurementSource<TreatmentDefinition> getMeasurementSource() { return new MetaMeasurementSource(); } public DecisionContext getDecisionContext() { return d_decisionContext; } }