/*
* 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 2 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, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/*
* CostBenefitAnalysis.java
* Copyright (C) 2009 University of Waikato, Hamilton, New Zealand
*
*/
package weka.gui.beans;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.EventSetDescriptor;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.beans.beancontext.BeanContext;
import java.beans.beancontext.BeanContextChild;
import java.beans.beancontext.BeanContextChildSupport;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.Vector;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import weka.classifiers.evaluation.ThresholdCurve;
import weka.core.Attribute;
import weka.core.FastVector;
import weka.core.Instance;
import weka.core.DenseInstance;
import weka.core.Instances;
import weka.core.Utils;
import weka.gui.Logger;
import weka.gui.visualize.VisualizePanel;
import weka.gui.visualize.Plot2D;
import weka.gui.visualize.PlotData2D;
/**
* Bean that aids in analyzing cost/benefit tradeoffs.
*
* @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
* @version $Revision: 6137 $
*/
public class CostBenefitAnalysis extends JPanel
implements BeanCommon, ThresholdDataListener, Visible, UserRequestAcceptor,
Serializable, BeanContextChild {
/** For serialization */
private static final long serialVersionUID = 8647471654613320469L;
protected BeanVisual m_visual;
protected transient JFrame m_popupFrame;
protected boolean m_framePoppedUp = false;
private transient AnalysisPanel m_analysisPanel;
/**
* True if this bean's appearance is the design mode appearance
*/
protected boolean m_design;
/**
* BeanContex that this bean might be contained within
*/
protected transient BeanContext m_beanContext = null;
/**
* BeanContextChild support
*/
protected BeanContextChildSupport m_bcSupport =
new BeanContextChildSupport(this);
/**
* The object sending us data (we allow only one connection at any one time)
*/
protected Object m_listenee;
/**
* Inner class for displaying the plots and all control widgets.
*
* @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
*/
protected static class AnalysisPanel extends JPanel {
/** For serialization */
private static final long serialVersionUID = 5364871945448769003L;
/** Displays the performance graphs(s) */
protected VisualizePanel m_performancePanel = new VisualizePanel();
/** Displays the cost/benefit (profit/loss) graph */
protected VisualizePanel m_costBenefitPanel = new VisualizePanel();
/**
* The class attribute from the data that was used to generate
* the threshold curve
*/
protected Attribute m_classAttribute;
/** Data for the threshold curve */
protected PlotData2D m_masterPlot;
/** Data for the cost/benefit curve */
protected PlotData2D m_costBenefit;
/** The size of the points being plotted */
protected int[] m_shapeSizes;
/** The index of the previous plotted point that was highlighted */
protected int m_previousShapeIndex = -1;
/** The slider for adjusting the threshold */
protected JSlider m_thresholdSlider = new JSlider(0,100,0);
protected JRadioButton m_percPop = new JRadioButton("% of Population");
protected JRadioButton m_percOfTarget = new JRadioButton("% of Target (recall)");
protected JRadioButton m_threshold = new JRadioButton("Score Threshold");
protected JLabel m_percPopLab = new JLabel();
protected JLabel m_percOfTargetLab = new JLabel();
protected JLabel m_thresholdLab = new JLabel();
// Confusion matrix stuff
protected JLabel m_conf_predictedA = new JLabel("Predicted (a)", SwingConstants.RIGHT);
protected JLabel m_conf_predictedB = new JLabel("Predicted (b)", SwingConstants.RIGHT);
protected JLabel m_conf_actualA = new JLabel(" Actual (a):");
protected JLabel m_conf_actualB = new JLabel(" Actual (b):");
protected ConfusionCell m_conf_aa = new ConfusionCell();
protected ConfusionCell m_conf_ab = new ConfusionCell();
protected ConfusionCell m_conf_ba = new ConfusionCell();
protected ConfusionCell m_conf_bb = new ConfusionCell();
// Cost matrix stuff
protected JLabel m_cost_predictedA = new JLabel("Predicted (a)", SwingConstants.RIGHT);
protected JLabel m_cost_predictedB = new JLabel("Predicted (b)", SwingConstants.RIGHT);
protected JLabel m_cost_actualA = new JLabel(" Actual (a)");
protected JLabel m_cost_actualB = new JLabel(" Actual (b)");
protected JTextField m_cost_aa = new JTextField("0.0", 5);
protected JTextField m_cost_ab = new JTextField("1.0", 5);
protected JTextField m_cost_ba = new JTextField("1.0", 5);
protected JTextField m_cost_bb = new JTextField("0.0" ,5);
protected JButton m_maximizeCB = new JButton("Maximize Cost/Benefit");
protected JButton m_minimizeCB = new JButton("Minimize Cost/Benefit");
protected JRadioButton m_costR = new JRadioButton("Cost");
protected JRadioButton m_benefitR = new JRadioButton("Benefit");
protected JLabel m_costBenefitL = new JLabel("Cost: ", SwingConstants.RIGHT);
protected JLabel m_costBenefitV = new JLabel("0");
protected JLabel m_randomV = new JLabel("0");
protected JLabel m_gainV = new JLabel("0");
protected int m_originalPopSize;
/** Population text field */
protected JTextField m_totalPopField = new JTextField(6);
protected int m_totalPopPrevious;
/** Classification accuracy */
protected JLabel m_classificationAccV = new JLabel("-");
// Only update curve & stats if values in cost matrix have changed
protected double m_tpPrevious;
protected double m_fpPrevious;
protected double m_tnPrevious;
protected double m_fnPrevious;
/**
* Inner class for handling a single cell in the confusion matrix.
* Displays the value, value as a percentage of total population and
* graphical depiction of percentage.
*
* @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
*/
protected static class ConfusionCell extends JPanel {
/** For serialization */
private static final long serialVersionUID = 6148640235434494767L;
private JLabel m_conf_cell = new JLabel("-", SwingConstants.RIGHT);
JLabel m_conf_perc = new JLabel("-", SwingConstants.RIGHT);
private JPanel m_percentageP;
protected double m_percentage = 0;
public ConfusionCell() {
setLayout(new BorderLayout());
setBorder(BorderFactory.createEtchedBorder());
add(m_conf_cell, BorderLayout.NORTH);
m_percentageP = new JPanel() {
public void paintComponent(Graphics gx) {
super.paintComponent(gx);
if (m_percentage > 0) {
gx.setColor(Color.BLUE);
int height = this.getHeight();
double width = this.getWidth();
int barWidth = (int)(m_percentage * width);
gx.fillRect(0, 0, barWidth, height);
}
}
};
Dimension d = new Dimension(30,5);
m_percentageP.setMinimumSize(d);
m_percentageP.setPreferredSize(d);
JPanel percHolder = new JPanel();
percHolder.setLayout(new BorderLayout());
percHolder.add(m_percentageP, BorderLayout.CENTER);
percHolder.add(m_conf_perc, BorderLayout.EAST);
add(percHolder, BorderLayout.SOUTH);
}
/**
* Set the value of a cell.
*
* @param cellValue the value of the cell
* @param max the max (for setting value as a percentage)
* @param scaleFactor scale the value by this amount
* @param precision precision for the percentage value
*/
public void setCellValue(double cellValue, double max, double scaleFactor, int precision) {
if (!Utils.isMissingValue(cellValue)) {
m_percentage = cellValue / max;
} else {
m_percentage = 0;
}
m_conf_cell.setText(Utils.doubleToString((cellValue * scaleFactor), 0));
m_conf_perc.setText(Utils.doubleToString(m_percentage * 100.0, precision) + "%");
// refresh the percentage bar
m_percentageP.repaint();
}
}
public AnalysisPanel() {
setLayout(new BorderLayout());
m_performancePanel.setShowAttBars(false);
m_performancePanel.setShowClassPanel(false);
m_costBenefitPanel.setShowAttBars(false);
m_costBenefitPanel.setShowClassPanel(false);
Dimension size = new Dimension(500, 400);
m_performancePanel.setPreferredSize(size);
m_performancePanel.setMinimumSize(size);
size = new Dimension(500, 400);
m_costBenefitPanel.setMinimumSize(size);
m_costBenefitPanel.setPreferredSize(size);
m_thresholdSlider.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
updateInfoForSliderValue((double)m_thresholdSlider.getValue() / 100.0);
}
});
JPanel plotHolder = new JPanel();
plotHolder.setLayout(new GridLayout(1,2));
plotHolder.add(m_performancePanel);
plotHolder.add(m_costBenefitPanel);
add(plotHolder, BorderLayout.CENTER);
JPanel lowerPanel = new JPanel();
lowerPanel.setLayout(new BorderLayout());
ButtonGroup bGroup = new ButtonGroup();
bGroup.add(m_percPop);
bGroup.add(m_percOfTarget);
bGroup.add(m_threshold);
ButtonGroup bGroup2 = new ButtonGroup();
bGroup2.add(m_costR);
bGroup2.add(m_benefitR);
ActionListener rl = new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (m_costR.isSelected()) {
m_costBenefitL.setText("Cost: ");
} else {
m_costBenefitL.setText("Benefit: ");
}
double gain = Double.parseDouble(m_gainV.getText());
gain = -gain;
m_gainV.setText(Utils.doubleToString(gain, 2));
}
};
m_costR.addActionListener(rl);
m_benefitR.addActionListener(rl);
m_costR.setSelected(true);
m_percPop.setSelected(true);
JPanel threshPanel = new JPanel();
threshPanel.setLayout(new BorderLayout());
JPanel radioHolder = new JPanel();
radioHolder.setLayout(new FlowLayout());
radioHolder.add(m_percPop);
radioHolder.add(m_percOfTarget);
radioHolder.add(m_threshold);
threshPanel.add(radioHolder, BorderLayout.NORTH);
threshPanel.add(m_thresholdSlider, BorderLayout.SOUTH);
JPanel threshInfoPanel = new JPanel();
threshInfoPanel.setLayout(new GridLayout(3,2));
threshInfoPanel.add(new JLabel("% of Population: ", SwingConstants.RIGHT));
threshInfoPanel.add(m_percPopLab);
threshInfoPanel.add(new JLabel("% of Target: ", SwingConstants.RIGHT));
threshInfoPanel.add(m_percOfTargetLab);
threshInfoPanel.add(new JLabel("Score Threshold: ", SwingConstants.RIGHT));
threshInfoPanel.add(m_thresholdLab);
JPanel threshHolder = new JPanel();
threshHolder.setBorder(BorderFactory.createTitledBorder("Threshold"));
threshHolder.setLayout(new BorderLayout());
threshHolder.add(threshPanel, BorderLayout.CENTER);
threshHolder.add(threshInfoPanel, BorderLayout.EAST);
lowerPanel.add(threshHolder, BorderLayout.NORTH);
// holder for the two matrixes
JPanel matrixHolder = new JPanel();
matrixHolder.setLayout(new GridLayout(1,2));
// confusion matrix
JPanel confusionPanel = new JPanel();
confusionPanel.setLayout(new GridLayout(3,3));
confusionPanel.add(m_conf_predictedA);
confusionPanel.add(m_conf_predictedB);
confusionPanel.add(new JLabel()); // dummy
confusionPanel.add(m_conf_aa);
confusionPanel.add(m_conf_ab);
confusionPanel.add(m_conf_actualA);
confusionPanel.add(m_conf_ba);
confusionPanel.add(m_conf_bb);
confusionPanel.add(m_conf_actualB);
JPanel tempHolderCA = new JPanel();
tempHolderCA.setLayout(new BorderLayout());
tempHolderCA.setBorder(BorderFactory.createTitledBorder("Confusion Matrix"));
tempHolderCA.add(confusionPanel, BorderLayout.CENTER);
JPanel accHolder = new JPanel();
accHolder.setLayout(new FlowLayout(FlowLayout.LEFT));
accHolder.add(new JLabel("Classification Accuracy: "));
accHolder.add(m_classificationAccV);
tempHolderCA.add(accHolder, BorderLayout.SOUTH);
matrixHolder.add(tempHolderCA);
// cost matrix
JPanel costPanel = new JPanel();
costPanel.setBorder(BorderFactory.createTitledBorder("Cost Matrix"));
costPanel.setLayout(new BorderLayout());
JPanel cmHolder = new JPanel();
cmHolder.setLayout(new GridLayout(3, 3));
cmHolder.add(m_cost_predictedA);
cmHolder.add(m_cost_predictedB);
cmHolder.add(new JLabel()); // dummy
cmHolder.add(m_cost_aa);
cmHolder.add(m_cost_ab);
cmHolder.add(m_cost_actualA);
cmHolder.add(m_cost_ba);
cmHolder.add(m_cost_bb);
cmHolder.add(m_cost_actualB);
costPanel.add(cmHolder, BorderLayout.CENTER);
FocusListener fl = new FocusListener() {
public void focusGained(FocusEvent e) {
}
public void focusLost(FocusEvent e) {
if (constructCostBenefitData()) {
try {
m_costBenefitPanel.setMasterPlot(m_costBenefit);
m_costBenefitPanel.validate(); m_costBenefitPanel.repaint();
} catch (Exception ex) {
ex.printStackTrace();
}
updateCostBenefit();
}
}
};
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (constructCostBenefitData()) {
try {
m_costBenefitPanel.setMasterPlot(m_costBenefit);
m_costBenefitPanel.validate(); m_costBenefitPanel.repaint();
} catch (Exception ex) {
ex.printStackTrace();
}
updateCostBenefit();
}
}
};
m_cost_aa.addFocusListener(fl);
m_cost_aa.addActionListener(al);
m_cost_ab.addFocusListener(fl);
m_cost_ab.addActionListener(al);
m_cost_ba.addFocusListener(fl);
m_cost_ba.addActionListener(al);
m_cost_bb.addFocusListener(fl);
m_cost_bb.addActionListener(al);
m_totalPopField.addFocusListener(fl);
m_totalPopField.addActionListener(al);
JPanel cbHolder = new JPanel();
cbHolder.setLayout(new BorderLayout());
JPanel tempP = new JPanel();
tempP.setLayout(new GridLayout(3, 2));
tempP.add(m_costBenefitL);
tempP.add(m_costBenefitV);
tempP.add(new JLabel("Random: ", SwingConstants.RIGHT));
tempP.add(m_randomV);
tempP.add(new JLabel("Gain: ", SwingConstants.RIGHT));
tempP.add(m_gainV);
cbHolder.add(tempP, BorderLayout.NORTH);
JPanel butHolder = new JPanel();
butHolder.setLayout(new GridLayout(2, 1));
butHolder.add(m_maximizeCB);
butHolder.add(m_minimizeCB);
m_maximizeCB.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
findMaxMinCB(true);
}
});
m_minimizeCB.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
findMaxMinCB(false);
}
});
cbHolder.add(butHolder, BorderLayout.SOUTH);
costPanel.add(cbHolder, BorderLayout.EAST);
JPanel popCBR = new JPanel();
popCBR.setLayout(new GridLayout(1, 2));
JPanel popHolder = new JPanel();
popHolder.setLayout(new FlowLayout(FlowLayout.LEFT));
popHolder.add(new JLabel("Total Population: "));
popHolder.add(m_totalPopField);
JPanel radioHolder2 = new JPanel();
radioHolder2.setLayout(new FlowLayout(FlowLayout.RIGHT));
radioHolder2.add(m_costR);
radioHolder2.add(m_benefitR);
popCBR.add(popHolder);
popCBR.add(radioHolder2);
costPanel.add(popCBR, BorderLayout.SOUTH);
matrixHolder.add(costPanel);
lowerPanel.add(matrixHolder, BorderLayout.SOUTH);
// popAccHolder.add(popHolder);
//popAccHolder.add(accHolder);
/*JPanel lowerPanel2 = new JPanel();
lowerPanel2.setLayout(new BorderLayout());
lowerPanel2.add(lowerPanel, BorderLayout.NORTH);
lowerPanel2.add(popAccHolder, BorderLayout.SOUTH); */
add(lowerPanel, BorderLayout.SOUTH);
}
private void findMaxMinCB(boolean max) {
double maxMin = (max)
? Double.NEGATIVE_INFINITY
: Double.POSITIVE_INFINITY;
Instances cBCurve = m_costBenefit.getPlotInstances();
int maxMinIndex = 0;
for (int i = 0; i < cBCurve.numInstances(); i++) {
Instance current = cBCurve.instance(i);
if (max) {
if (current.value(1) > maxMin) {
maxMin = current.value(1);
maxMinIndex = i;
}
} else {
if (current.value(1) < maxMin) {
maxMin = current.value(1);
maxMinIndex = i;
}
}
}
// set the slider to the correct position
int indexOfSampleSize =
m_masterPlot.getPlotInstances().attribute(ThresholdCurve.SAMPLE_SIZE_NAME).index();
int indexOfPercOfTarget =
m_masterPlot.getPlotInstances().attribute(ThresholdCurve.RECALL_NAME).index();
int indexOfThreshold =
m_masterPlot.getPlotInstances().attribute(ThresholdCurve.THRESHOLD_NAME).index();
int indexOfMetric;
if (m_percPop.isSelected()) {
indexOfMetric = indexOfSampleSize;
} else if (m_percOfTarget.isSelected()) {
indexOfMetric = indexOfPercOfTarget;
} else {
indexOfMetric = indexOfThreshold;
}
double valueOfMetric = m_masterPlot.getPlotInstances().instance(maxMinIndex).value(indexOfMetric);
valueOfMetric *= 100.0;
// set the approximate location of the slider
m_thresholdSlider.setValue((int)valueOfMetric);
// make sure the actual values relate to the true min/max rather
// than being off due to slider location error.
updateInfoGivenIndex(maxMinIndex);
}
private void updateCostBenefit() {
double value = (double)m_thresholdSlider.getValue() / 100.0;
Instances plotInstances = m_masterPlot.getPlotInstances();
int indexOfSampleSize =
m_masterPlot.getPlotInstances().attribute(ThresholdCurve.SAMPLE_SIZE_NAME).index();
int indexOfPercOfTarget =
m_masterPlot.getPlotInstances().attribute(ThresholdCurve.RECALL_NAME).index();
int indexOfThreshold =
m_masterPlot.getPlotInstances().attribute(ThresholdCurve.THRESHOLD_NAME).index();
int indexOfMetric;
if (m_percPop.isSelected()) {
indexOfMetric = indexOfSampleSize;
} else if (m_percOfTarget.isSelected()) {
indexOfMetric = indexOfPercOfTarget;
} else {
indexOfMetric = indexOfThreshold;
}
int index = findIndexForValue(value, plotInstances, indexOfMetric);
updateCBRandomGainInfo(index);
}
private void updateCBRandomGainInfo(int index) {
double requestedPopSize = m_originalPopSize;
try {
requestedPopSize = Double.parseDouble(m_totalPopField.getText());
} catch (NumberFormatException e) {}
double scaleFactor = requestedPopSize / m_originalPopSize;
double CB = m_costBenefit.
getPlotInstances().instance(index).value(1);
m_costBenefitV.setText(Utils.doubleToString(CB,2));
double totalRandomCB = 0.0;
Instance first = m_masterPlot.getPlotInstances().instance(0);
double totalPos = first.value(m_masterPlot.getPlotInstances().
attribute(ThresholdCurve.TRUE_POS_NAME).index()) * scaleFactor;
double totalNeg = first.value(m_masterPlot.getPlotInstances().
attribute(ThresholdCurve.FALSE_POS_NAME)) * scaleFactor;
double posInSample = (totalPos * (Double.parseDouble(m_percPopLab.getText()) / 100.0));
double negInSample = (totalNeg * (Double.parseDouble(m_percPopLab.getText()) / 100.0));
double posOutSample = totalPos - posInSample;
double negOutSample = totalNeg - negInSample;
double tpCost = 0.0;
try {
tpCost = Double.parseDouble(m_cost_aa.getText());
} catch (NumberFormatException n) {}
double fpCost = 0.0;
try {
fpCost = Double.parseDouble(m_cost_ba.getText());
} catch (NumberFormatException n) {}
double tnCost = 0.0;
try {
tnCost = Double.parseDouble(m_cost_bb.getText());
} catch (NumberFormatException n) {}
double fnCost = 0.0;
try {
fnCost = Double.parseDouble(m_cost_ab.getText());
} catch (NumberFormatException n) {}
totalRandomCB += posInSample * tpCost;
totalRandomCB += negInSample * fpCost;
totalRandomCB += posOutSample * fnCost;
totalRandomCB += negOutSample * tnCost;
m_randomV.setText(Utils.doubleToString(totalRandomCB, 2));
double gain = (m_costR.isSelected())
? totalRandomCB - CB
: CB - totalRandomCB;
m_gainV.setText(Utils.doubleToString(gain, 2));
// update classification rate
Instance currentInst = m_masterPlot.getPlotInstances().instance(index);
double tp = currentInst.value(m_masterPlot.getPlotInstances().
attribute(ThresholdCurve.TRUE_POS_NAME).index());
double tn = currentInst.value(m_masterPlot.getPlotInstances().
attribute(ThresholdCurve.TRUE_NEG_NAME).index());
m_classificationAccV.
setText(Utils.doubleToString((tp + tn) / (totalPos + totalNeg) * 100.0, 4) + "%");
}
private void updateInfoGivenIndex(int index) {
Instances plotInstances = m_masterPlot.getPlotInstances();
int indexOfSampleSize =
m_masterPlot.getPlotInstances().attribute(ThresholdCurve.SAMPLE_SIZE_NAME).index();
int indexOfPercOfTarget =
m_masterPlot.getPlotInstances().attribute(ThresholdCurve.RECALL_NAME).index();
int indexOfThreshold =
m_masterPlot.getPlotInstances().attribute(ThresholdCurve.THRESHOLD_NAME).index();
// update labels
m_percPopLab.setText(Utils.
doubleToString(100.0 * plotInstances.instance(index).value(indexOfSampleSize), 4));
m_percOfTargetLab.setText(Utils.doubleToString(
100.0 * plotInstances.instance(index).value(indexOfPercOfTarget), 4));
m_thresholdLab.setText(Utils.doubleToString(plotInstances.instance(index).value(indexOfThreshold), 4));
/*if (m_percPop.isSelected()) {
m_percPopLab.setText(Utils.doubleToString(100.0 * value, 4));
} else if (m_percOfTarget.isSelected()) {
m_percOfTargetLab.setText(Utils.doubleToString(100.0 * value, 4));
} else {
m_thresholdLab.setText(Utils.doubleToString(value, 4));
}*/
// Update the highlighted point on the graphs */
if (m_previousShapeIndex >= 0) {
m_shapeSizes[m_previousShapeIndex] = 1;
}
m_shapeSizes[index] = 10;
m_previousShapeIndex = index;
// Update the confusion matrix
// double totalInstances =
int tp = plotInstances.attribute(ThresholdCurve.TRUE_POS_NAME).index();
int fp = plotInstances.attribute(ThresholdCurve.FALSE_POS_NAME).index();
int tn = plotInstances.attribute(ThresholdCurve.TRUE_NEG_NAME).index();
int fn = plotInstances.attribute(ThresholdCurve.FALSE_NEG_NAME).index();
Instance temp = plotInstances.instance(index);
double totalInstances = temp.value(tp) + temp.value(fp) + temp.value(tn) + temp.value(fn);
// get the value out of the total pop field (if possible)
double requestedPopSize = totalInstances;
try {
requestedPopSize = Double.parseDouble(m_totalPopField.getText());
} catch (NumberFormatException e) {}
m_conf_aa.setCellValue(temp.value(tp), totalInstances,
requestedPopSize / totalInstances, 2);
m_conf_ab.setCellValue(temp.value(fn), totalInstances,
requestedPopSize / totalInstances, 2);
m_conf_ba.setCellValue(temp.value(fp), totalInstances,
requestedPopSize / totalInstances, 2);
m_conf_bb.setCellValue(temp.value(tn), totalInstances,
requestedPopSize / totalInstances, 2);
updateCBRandomGainInfo(index);
repaint();
}
private void updateInfoForSliderValue(double value) {
int indexOfSampleSize =
m_masterPlot.getPlotInstances().attribute(ThresholdCurve.SAMPLE_SIZE_NAME).index();
int indexOfPercOfTarget =
m_masterPlot.getPlotInstances().attribute(ThresholdCurve.RECALL_NAME).index();
int indexOfThreshold =
m_masterPlot.getPlotInstances().attribute(ThresholdCurve.THRESHOLD_NAME).index();
int indexOfMetric;
if (m_percPop.isSelected()) {
indexOfMetric = indexOfSampleSize;
} else if (m_percOfTarget.isSelected()) {
indexOfMetric = indexOfPercOfTarget;
} else {
indexOfMetric = indexOfThreshold;
}
Instances plotInstances = m_masterPlot.getPlotInstances();
int index = findIndexForValue(value, plotInstances, indexOfMetric);
updateInfoGivenIndex(index);
}
private int findIndexForValue(double value, Instances plotInstances, int indexOfMetric) {
// binary search
// threshold curve is sorted ascending in the threshold (thus
// descending for recall and pop size)
int index = -1;
int lower = 0;
int upper = plotInstances.numInstances() - 1;
int mid = (upper - lower) / 2;
boolean done = false;
while (!done) {
if (upper - lower <= 1) {
// choose the one closest to the value
double comp1 = plotInstances.instance(upper).value(indexOfMetric);
double comp2 = plotInstances.instance(lower).value(indexOfMetric);
if (Math.abs(comp1 - value) < Math.abs(comp2 - value)) {
index = upper;
} else {
index = lower;
}
break;
}
double comparisonVal = plotInstances.instance(mid).value(indexOfMetric);
if (value > comparisonVal) {
if (m_threshold.isSelected()) {
lower = mid;
mid += (upper - lower) / 2;
} else {
upper = mid;
mid -= (upper - lower) / 2;
}
} else if (value < comparisonVal) {
if (m_threshold.isSelected()) {
upper = mid;
mid -= (upper - lower) / 2;
} else {
lower = mid;
mid += (upper - lower) / 2;
}
} else {
index = mid;
done = true;
}
}
// now check for ties in the appropriate direction
if (!m_threshold.isSelected()) {
while (index + 1 < plotInstances.numInstances()) {
if (plotInstances.instance(index + 1).value(indexOfMetric) ==
plotInstances.instance(index).value(indexOfMetric)) {
index++;
} else {
break;
}
}
} else {
while (index - 1 >= 0) {
if (plotInstances.instance(index - 1).value(indexOfMetric) ==
plotInstances.instance(index).value(indexOfMetric)) {
index--;
} else {
break;
}
}
}
return index;
}
/**
* Set the threshold data for the panel to use.
*
* @param data PlotData2D object encapsulating the threshold data.
* @param classAtt the class attribute from the original data used to generate
* the threshold data.
* @throws Exception if something goes wrong.
*/
public synchronized void setDataSet(PlotData2D data, Attribute classAtt) throws Exception {
// make a copy of the PlotData2D object
m_masterPlot = new PlotData2D(data.getPlotInstances());
boolean[] connectPoints = new boolean[m_masterPlot.getPlotInstances().numInstances()];
for (int i = 1; i < connectPoints.length; i++) {
connectPoints[i] = true;
}
m_masterPlot.setConnectPoints(connectPoints);
m_masterPlot.m_alwaysDisplayPointsOfThisSize = 10;
setClassForConfusionMatrix(classAtt);
m_performancePanel.setMasterPlot(m_masterPlot);
m_performancePanel.validate(); m_performancePanel.repaint();
m_shapeSizes = new int[m_masterPlot.getPlotInstances().numInstances()];
for (int i = 0; i < m_shapeSizes.length; i++) {
m_shapeSizes[i] = 1;
}
m_masterPlot.setShapeSize(m_shapeSizes);
constructCostBenefitData();
m_costBenefitPanel.setMasterPlot(m_costBenefit);
m_costBenefitPanel.validate(); m_costBenefitPanel.repaint();
m_totalPopPrevious = 0;
m_fpPrevious = 0;
m_tpPrevious = 0;
m_tnPrevious = 0;
m_fnPrevious = 0;
m_previousShapeIndex = -1;
// set the total population size
Instance first = m_masterPlot.getPlotInstances().instance(0);
double totalPos = first.value(m_masterPlot.getPlotInstances().
attribute(ThresholdCurve.TRUE_POS_NAME).index());
double totalNeg = first.value(m_masterPlot.getPlotInstances().
attribute(ThresholdCurve.FALSE_POS_NAME));
m_originalPopSize = (int)(totalPos + totalNeg);
m_totalPopField.setText("" + m_originalPopSize);
m_performancePanel.setYIndex(5);
m_performancePanel.setXIndex(10);
m_costBenefitPanel.setXIndex(0);
m_costBenefitPanel.setYIndex(1);
// System.err.println(m_masterPlot.getPlotInstances());
updateInfoForSliderValue((double)m_thresholdSlider.getValue() / 100.0);
}
private void setClassForConfusionMatrix(Attribute classAtt) {
m_classAttribute = classAtt;
m_conf_actualA.setText(" Actual (a): " + classAtt.value(0));
m_conf_actualA.setToolTipText(classAtt.value(0));
String negClasses = "";
for (int i = 1; i < classAtt.numValues(); i++) {
negClasses += classAtt.value(i);
if (i < classAtt.numValues() - 1) {
negClasses += ",";
}
}
m_conf_actualB.setText(" Actual (b): " + negClasses);
m_conf_actualB.setToolTipText(negClasses);
}
private boolean constructCostBenefitData() {
double tpCost = 0.0;
try {
tpCost = Double.parseDouble(m_cost_aa.getText());
} catch (NumberFormatException n) {}
double fpCost = 0.0;
try {
fpCost = Double.parseDouble(m_cost_ba.getText());
} catch (NumberFormatException n) {}
double tnCost = 0.0;
try {
tnCost = Double.parseDouble(m_cost_bb.getText());
} catch (NumberFormatException n) {}
double fnCost = 0.0;
try {
fnCost = Double.parseDouble(m_cost_ab.getText());
} catch (NumberFormatException n) {}
double requestedPopSize = m_originalPopSize;
try {
requestedPopSize = Double.parseDouble(m_totalPopField.getText());
} catch (NumberFormatException e) {}
double scaleFactor = 1.0;
if (m_originalPopSize != 0) {
scaleFactor = requestedPopSize / m_originalPopSize;
}
if (tpCost == m_tpPrevious && fpCost == m_fpPrevious &&
tnCost == m_tnPrevious && fnCost == m_fnPrevious &&
requestedPopSize == m_totalPopPrevious) {
return false;
}
// First construct some Instances for the curve
FastVector fv = new FastVector();
fv.addElement(new Attribute("Sample Size"));
fv.addElement(new Attribute("Cost/Benefit"));
Instances costBenefitI = new Instances("Cost/Benefit Curve", fv, 100);
// process the performance data to make this curve
Instances performanceI = m_masterPlot.getPlotInstances();
for (int i = 0; i < performanceI.numInstances(); i++) {
Instance current = performanceI.instance(i);
double[] vals = new double[2];
vals[0] = current.value(10); // sample size
vals[1] = (current.value(0) * tpCost
+ current.value(1) * fnCost
+ current.value(2) * fpCost
+ current.value(3) * tnCost) * scaleFactor;
Instance newInst = new DenseInstance(1.0, vals);
costBenefitI.add(newInst);
}
costBenefitI.compactify();
// now set up the plot data
m_costBenefit = new PlotData2D(costBenefitI);
m_costBenefit.m_alwaysDisplayPointsOfThisSize = 10;
m_costBenefit.setPlotName("Cost/benefit curve");
boolean[] connectPoints = new boolean[costBenefitI.numInstances()];
for (int i = 0; i < connectPoints.length; i++) {
connectPoints[i] = true;
}
try {
m_costBenefit.setConnectPoints(connectPoints);
m_costBenefit.setShapeSize(m_shapeSizes);
} catch (Exception ex) {
// ignore
}
m_tpPrevious = tpCost;
m_fpPrevious = fpCost;
m_tnPrevious = tnCost;
m_fnPrevious = fnCost;
return true;
}
}
/**
* Constructor.
*/
public CostBenefitAnalysis() {
java.awt.GraphicsEnvironment ge =
java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
if (!ge.isHeadless()) {
appearanceFinal();
}
}
/**
* Global info for this bean
*
* @return a <code>String</code> value
*/
public String globalInfo() {
return "Visualize performance charts (such as ROC).";
}
/**
* Accept a threshold data event and set up the visualization.
* @param e a threshold data event
*/
public void acceptDataSet(ThresholdDataEvent e) {
try {
setCurveData(e.getDataSet(), e.getClassAttribute());
} catch (Exception ex) {
System.err.println("[CostBenefitAnalysis] Problem setting up visualization.");
ex.printStackTrace();
}
}
/**
* Set the threshold curve data to use.
*
* @param curveData a PlotData2D object set up with the curve data.
* @param origClassAtt the class attribute from the original data used to
* generate the curve.
* @throws Exception if somthing goes wrong during the setup process.
*/
public void setCurveData(PlotData2D curveData, Attribute origClassAtt)
throws Exception {
if (m_analysisPanel == null) {
m_analysisPanel = new AnalysisPanel();
}
m_analysisPanel.setDataSet(curveData, origClassAtt);
}
public BeanVisual getVisual() {
return m_visual;
}
public void setVisual(BeanVisual newVisual) {
m_visual = newVisual;
}
public void useDefaultVisual() {
m_visual.loadIcons(BeanVisual.ICON_PATH+"DefaultDataVisualizer.gif",
BeanVisual.ICON_PATH+"DefaultDataVisualizer_animated.gif");
}
public Enumeration enumerateRequests() {
Vector newVector = new Vector(0);
if (m_analysisPanel != null) {
if (m_analysisPanel.m_masterPlot != null) {
newVector.addElement("Show analysis");
}
}
return newVector.elements();
}
public void performRequest(String request) {
if (request.compareTo("Show analysis") == 0) {
try {
// popup visualize panel
if (!m_framePoppedUp) {
m_framePoppedUp = true;
final javax.swing.JFrame jf =
new javax.swing.JFrame("Cost/Benefit Analysis");
jf.setSize(1000,600);
jf.getContentPane().setLayout(new BorderLayout());
jf.getContentPane().add(m_analysisPanel, BorderLayout.CENTER);
jf.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent e) {
jf.dispose();
m_framePoppedUp = false;
}
});
jf.setVisible(true);
m_popupFrame = jf;
} else {
m_popupFrame.toFront();
}
} catch (Exception ex) {
ex.printStackTrace();
m_framePoppedUp = false;
}
} else {
throw new IllegalArgumentException(request
+ " not supported (Cost/Benefit Analysis");
}
}
public void addVetoableChangeListener(String name, VetoableChangeListener vcl) {
m_bcSupport.addVetoableChangeListener(name, vcl);
}
public BeanContext getBeanContext() {
return m_beanContext;
}
public void removeVetoableChangeListener(String name,
VetoableChangeListener vcl) {
m_bcSupport.removeVetoableChangeListener(name, vcl);
}
protected void appearanceFinal() {
removeAll();
setLayout(new BorderLayout());
setUpFinal();
}
protected void setUpFinal() {
if (m_analysisPanel == null) {
m_analysisPanel = new AnalysisPanel();
}
add(m_analysisPanel, BorderLayout.CENTER);
}
protected void appearanceDesign() {
removeAll();
m_visual = new BeanVisual("CostBenefitAnalysis",
BeanVisual.ICON_PATH+"ModelPerformanceChart.gif",
BeanVisual.ICON_PATH
+"ModelPerformanceChart_animated.gif");
setLayout(new BorderLayout());
add(m_visual, BorderLayout.CENTER);
}
public void setBeanContext(BeanContext bc) throws PropertyVetoException {
m_beanContext = bc;
m_design = m_beanContext.isDesignTime();
if (m_design) {
appearanceDesign();
} else {
java.awt.GraphicsEnvironment ge =
java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
if (!ge.isHeadless()) {
appearanceFinal();
}
}
}
/**
* Returns true if, at this time,
* the object will accept a connection via the named event
*
* @param eventName the name of the event in question
* @return true if the object will accept a connection
*/
public boolean connectionAllowed(String eventName) {
return (m_listenee == null);
}
/**
* Notify this object that it has been registered as a listener with
* a source for recieving events described by the named event
* This object is responsible for recording this fact.
*
* @param eventName the event
* @param source the source with which this object has been registered as
* a listener
*/
public void connectionNotification(String eventName, Object source) {
if (connectionAllowed(eventName)) {
m_listenee = source;
}
}
/**
* Returns true if, at this time,
* the object will accept a connection according to the supplied
* EventSetDescriptor
*
* @param esd the EventSetDescriptor
* @return true if the object will accept a connection
*/
public boolean connectionAllowed(EventSetDescriptor esd) {
return connectionAllowed(esd.getName());
}
/**
* Notify this object that it has been deregistered as a listener with
* a source for named event. This object is responsible
* for recording this fact.
*
* @param eventName the event
* @param source the source with which this object has been registered as
* a listener
*/
public void disconnectionNotification(String eventName, Object source) {
if (m_listenee == source) {
m_listenee = null;
}
}
/**
* Get the custom (descriptive) name for this bean (if one has been set)
*
* @return the custom name (or the default name)
*/
public String getCustomName() {
return m_visual.getText();
}
/**
* Returns true if. at this time, the bean is busy with some
* (i.e. perhaps a worker thread is performing some calculation).
*
* @return true if the bean is busy.
*/
public boolean isBusy() {
return false;
}
/**
* Set a custom (descriptive) name for this bean
*
* @param name the name to use
*/
public void setCustomName(String name) {
m_visual.setText(name);
}
/**
* Set a logger
*
* @param logger a <code>weka.gui.Logger</code> value
*/
public void setLog(Logger logger) {
// we don't need to do any logging
}
/**
* Stop any processing that the bean might be doing.
*/
public void stop() {
// nothing to do here
}
public static void main(String[] args) {
try {
Instances train = new Instances(new java.io.BufferedReader(new java.io.FileReader(args[0])));
train.setClassIndex(train.numAttributes() - 1);
weka.classifiers.evaluation.ThresholdCurve tc =
new weka.classifiers.evaluation.ThresholdCurve();
weka.classifiers.evaluation.EvaluationUtils eu =
new weka.classifiers.evaluation.EvaluationUtils();
//weka.classifiers.Classifier classifier = new weka.classifiers.functions.Logistic();
weka.classifiers.Classifier classifier = new weka.classifiers.bayes.NaiveBayes();
FastVector predictions = new FastVector();
eu.setSeed(1);
predictions.appendElements(eu.getCVPredictions(classifier, train, 10));
Instances result = tc.getCurve(predictions, 0);
PlotData2D pd = new PlotData2D(result);
pd.m_alwaysDisplayPointsOfThisSize = 10;
boolean[] connectPoints = new boolean[result.numInstances()];
for (int i = 1; i < connectPoints.length; i++) {
connectPoints[i] = true;
}
pd.setConnectPoints(connectPoints);
final javax.swing.JFrame jf =
new javax.swing.JFrame("CostBenefitTest");
jf.setSize(1000,600);
//jf.pack();
jf.getContentPane().setLayout(new BorderLayout());
final CostBenefitAnalysis.AnalysisPanel analysisPanel =
new CostBenefitAnalysis.AnalysisPanel();
jf.getContentPane().add(analysisPanel, BorderLayout.CENTER);
jf.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent e) {
jf.dispose();
System.exit(0);
}
});
jf.setVisible(true);
analysisPanel.setDataSet(pd, train.classAttribute());
} catch (Exception ex) {
ex.printStackTrace();
}
}
}