Package weka.classifiers.timeseries.gui

Source Code of weka.classifiers.timeseries.gui.ForecastingPanel$ForecastingThread

/*
* Copyright (c) 2010 Pentaho Corporation.  All rights reserved.
* This software was developed by Pentaho Corporation and is provided under the terms
* of the GNU Lesser General Public License, Version 2.1. You may not use
* this file except in compliance with the license. If you need a copy of the license,
* please go to http://www.gnu.org/licenses/lgpl-2.1.txt. The Original Code is Time Series
* Forecasting.  The Initial Developer is Pentaho Corporation.
*
* Software distributed under the GNU Lesser Public License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or  implied. Please refer to
* the license for the specific language governing your rights and limitations.
*/

/*
*    ForecastingPanel.java
*    Copyright (C) 2010 Pentaho Corporation
*/

package weka.classifiers.timeseries.gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;

import weka.classifiers.timeseries.AbstractForecaster;
import weka.classifiers.timeseries.TSForecaster;
import weka.classifiers.timeseries.WekaForecaster;
import weka.classifiers.timeseries.core.OverlayForecaster;
import weka.classifiers.timeseries.core.TSLagMaker;
import weka.classifiers.timeseries.core.TSLagUser;
import weka.classifiers.timeseries.eval.TSEvaluation;
import weka.classifiers.timeseries.eval.graph.GraphDriver;
import weka.core.Attribute;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Utils;
import weka.gui.BrowserHelper;
import weka.gui.LogPanel;
import weka.gui.Logger;
import weka.gui.ResultHistoryPanel;
import weka.gui.TaskLogger;
import weka.gui.WekaTaskMonitor;
import weka.gui.beans.KnowledgeFlowApp;
import weka.gui.beans.TimeSeriesForecasting;
import weka.gui.beans.TimeSeriesForecastingKFPerspective;

/**
* Main GUI panel for the forecasting environment.
*
* @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
* @version $Revision: 49983 $
*/
public class ForecastingPanel extends JPanel {
 
  /** For serialization */
  private static final long serialVersionUID = -8415151090793037265L;

  /** The training instances to operate on */
  protected Instances m_instances;
 
  /** Panel for logging */
  protected Logger m_log;// = new LogPanel(new WekaTaskMonitor());
 
  /** The simple configuration panel */
  protected SimpleConfigPanel m_simpleConfigPanel;
 
  /** The advanced configuration panel */
  protected AdvancedConfigPanel m_advancedConfigPanel;   
 
  /** The current forecaster */
  protected WekaForecaster m_forecaster = new WekaForecaster();
 
  /** Tabbed pane to hold the simple and advanced config panels */
  protected JTabbedPane m_configPane = new JTabbedPane();
 
  /** The main textual output area */
  protected JTextArea m_outText = new JTextArea(20, 50);
 
  /** A panel holding and controlling the results for viewing */
  protected ResultHistoryPanel m_history = new ResultHistoryPanel(m_outText);
 
  /** Button to launch the forecaster */
  protected JButton m_startBut = new JButton("Start");
 
  /** Button to stop processing */
  protected JButton m_stopBut = new JButton("Stop");
 
  /** Button to visit help docs in browser */
  protected JButton m_helpBut = new JButton("Help");
 
  /** The split panel to divided the history/results area from the configuration */
  protected JSplitPane m_splitP;
 
  protected Thread m_runThread;
 
  /** True if we are running in the KnowledgeFlow as a perspective */
  protected boolean m_isRunningAsPerspective = false;
 
  /** The file chooser for selecting model files. */
  protected JFileChooser m_fileChooser
    = new JFileChooser(new File(System.getProperty("user.dir")));
 
  /**
   *  For each dataset, perform a check (if a timestamp is specified)
   * just once to see if it is in ascending order
   */
  protected boolean m_sortedCheck;
 
  /**
   * Tabbed pane that holds the main text output plus tabs for
   * any generated graphs
   */
  JTabbedPane m_outputPane = new JTabbedPane();
 
  /**
   * Inner class extending Thread. Executes a forecasting task
   */
  protected class ForecastingThread extends Thread {
   
    protected boolean m_configAndBuild = true;
   
    protected WekaForecaster m_threadForecaster = null;
   
    protected String m_name;
   
    public ForecastingThread(WekaForecaster forecaster, String name) {
      m_threadForecaster = forecaster;
      m_name = name;
    }
   
    public void setConfigureAndBuild(boolean configAndBuild) {
      m_configAndBuild = configAndBuild;
    }

    public void run() {

      LogPrintStream logger = new LogPrintStream();         
      logger.println("Setting up...");

      String name = m_name;
      StringBuffer outBuff = null;
     
      if (name == null) {
        name = (new SimpleDateFormat("HH:mm:ss - ")).format(new Date());
        outBuff = new StringBuffer();
      }     
     
      String fname = "";
      try {           

        if (!m_sortedCheck) {
          sortCheck();
        }

        // copy the current state of things
        Instances inst = new Instances(m_instances);

        TSEvaluation eval = new TSEvaluation(inst,
            m_advancedConfigPanel.getHoldoutSetSize());               
       
        if (m_configAndBuild) {
          // configure the WekaForecaster with the base
          // learner
          m_threadForecaster.setBaseForecaster(m_advancedConfigPanel.getBaseClassifier());

          m_simpleConfigPanel.applyToForecaster(m_threadForecaster);
          m_advancedConfigPanel.applyToForecaster(m_threadForecaster);
        }
       
        m_simpleConfigPanel.applyToEvaluation(eval, m_threadForecaster);
        m_advancedConfigPanel.applyToEvaluation(eval, m_threadForecaster);

        eval.setForecastFuture(m_advancedConfigPanel.getOutputFuturePredictions() ||
            m_advancedConfigPanel.getGraphFuturePredictions());
       
        if (m_threadForecaster instanceof OverlayForecaster &&
            ((OverlayForecaster)m_threadForecaster).isUsingOverlayData()) {
          if (!eval.getEvaluateOnTestData() &&
              (m_advancedConfigPanel.m_outputFutureCheckBox.isSelected() ||
                  m_advancedConfigPanel.m_graphFutureCheckBox.isSelected())) {

            // warn the user that future forecast can't be produced for the training data
            dontShowMessageDialog("weka.classifiers.timeseries.gui.CantFutureForecastTraining",
                "Unable to generate a future forecast beyond the end of the training\n" +
                "data because there is no future overlay data available. Use a holdout\n" +
                "set for evaluation in order to simulate having \"future\" overlay\n" +
                "data available.\n\n",
            "ForecastingPanel");
          }
         
          if (eval.getEvaluateOnTestData() &&
              (m_advancedConfigPanel.m_outputFutureCheckBox.isSelected() ||
                  m_advancedConfigPanel.m_graphFutureCheckBox.isSelected())) {
           
            // warn the user that future forecast can't be produced for the test data
            dontShowMessageDialog("weka.classifiers.timeseries.gui.CantFutureForecastTesting",
                "Unable to generate a future forecast beyond the end of the test\n" +
                "data because there is no future overlay data available.\n\n",
            "ForecastingPanel");
          }
        }               


        fname = m_threadForecaster.getAlgorithmName();
       
        if (m_name == null) {
          String algoName = fname.substring(0, fname.indexOf(' '));
          if (algoName.startsWith("weka.classifiers.")) {
            name += algoName.substring("weka.classifiers.".length());
          } else {
            name += algoName;
          }
        }       
       
        String lagOptions = "";
        if (m_threadForecaster instanceof TSLagUser) {
          TSLagMaker lagMaker = ((TSLagUser)m_threadForecaster).getTSLagMaker();
          lagOptions = Utils.joinOptions(lagMaker.getOptions());
        }
       
        if (lagOptions.length() > 0 && m_name == null) {
          name += " [" + lagOptions + "]";
        }
       
        if (m_name == null) {
          outBuff.append("=== Run information ===\n\n");
        } else {
          outBuff = m_history.getNamedBuffer(name);
          outBuff.append("\n=== Model re-evaluation===\n\n");
        }
       
        outBuff.append("Scheme:\n\t" + fname).append("\n\n");
       
        if (lagOptions.length() > 0) {
          outBuff.append("Lagged and derived variable options:\n\t").
          append(lagOptions + "\n\n");
        }

        outBuff.append("Relation:     " + inst.relationName() + '\n');
        outBuff.append("Instances:    " + inst.numInstances() + '\n');
        outBuff.append("Attributes:   " + inst.numAttributes() + '\n');
        if (inst.numAttributes() < 100) {
          for (int i = 0; i < inst.numAttributes(); i++) {
            outBuff.append("              " + inst.attribute(i).name()
                + '\n');
          }
        } else {
          outBuff.append("              [list of attributes omitted]\n");
        }
       
        if (m_configAndBuild) {
          m_history.addResult(name, outBuff);
        }
        m_history.setSingle(name);

        if (m_log != null) {
          m_log.logMessage("Started " + fname);
          if (m_configAndBuild) {
            logger.println("Training forecaster...");
          }
          if (m_log instanceof TaskLogger) {
            ((TaskLogger)m_log).taskStarted();
          }
        }

        Instances trainInst = eval.getTrainingData();
        if (m_configAndBuild) {
          m_threadForecaster.buildForecaster(trainInst, logger);
          outBuff.append("\n" + m_threadForecaster.toString());
          m_history.updateResult(name);
        }

        if (eval.getEvaluateOnTrainingData() ||
            eval.getEvaluateOnTestData()) {
          logger.println("Evaluating...");
        }

        // evaluate the forecaster
        eval.evaluateForecaster(m_threadForecaster, false, logger);

        // output any predictions
        if (m_advancedConfigPanel.getOutputPredictionsAtStep() > 0) {
          int step = m_advancedConfigPanel.getOutputPredictionsAtStep();
          String targetName = m_advancedConfigPanel.getOutputPredictionsTarget();
          String fieldsToForecast = m_threadForecaster.getFieldsToForecast();
          if (!fieldsToForecast.contains(targetName)) {
            throw new Exception("Cannot output predictions for \""
                + targetName + "\" because that field is not being predicted.");
          }
          if (eval.getTrainingData() != null &&
              eval.getEvaluateOnTrainingData()) {
            String predString = eval.printPredictionsForTrainingData("=== Predictions " +
                "for training data: " + targetName + " ("
                + step + (step > 1 ? "-steps ahead)" : "-step ahead)") + " ===",
                targetName, step, eval.getPrimeWindowSize());
            outBuff.append("\n").append(predString);
          }             

          if (eval.getTestData() != null) {
            int instanceNumOffset =
              (eval.getTrainingData() != null &&
                  m_advancedConfigPanel.getHoldoutSetSize() > 0)
                  ? eval.getTrainingData().numInstances()
                      : 0;

                  String predString = eval.printPredictionsForTestData("=== Predictions " +
                      "for test data: " + targetName + " ("
                      + step + (step > 1 ? "-steps ahead)" : "-step ahead)") + " ===",
                      targetName, step, instanceNumOffset);
                  outBuff.append("\n").append(predString);
          }
          m_history.updateResult(name);
        }

        // output any future predictions
        if (m_advancedConfigPanel.getOutputFuturePredictions()) {
          if (eval.getTrainingData() != null /*&&
                eval.getEvaluateOnTrainingData()*/) {
            outBuff.append("\n=== Future predictions from end of training data ===\n");
            outBuff.append(eval.printFutureTrainingForecast(m_threadForecaster));
          }

          if (eval.getTestData() != null &&
              eval.getEvaluateOnTestData()) {
            outBuff.append("\n=== Future predictions from end of test data ===\n");
            outBuff.append(eval.printFutureTestForecast(m_threadForecaster));
          }
          m_history.updateResult(name);
        }

        // evaluation summary
        if (eval.getEvaluateOnTrainingData() || eval.getEvaluateOnTestData()) {
          outBuff.append("\n" + eval.toSummaryString());
          m_history.updateResult(name);
        }

        // result object list
        List<Object> resultList = (m_configAndBuild)
        ? new ArrayList<Object>()
            : (List<Object>)m_history.getNamedObject(name);
       
        if (!m_configAndBuild) {
          // go through and remove any JPanels
          List<Object> newResultList = new ArrayList<Object>();
          for (int z = 0; z < resultList.size(); z++) {
            if (resultList.get(z) instanceof TSForecaster ||
                resultList.get(z) instanceof Instances) {
              newResultList.add(resultList.get(z));
            }
          }
          resultList = newResultList;
        }

        // handle graphs
        List<JPanel> graphList = new ArrayList<JPanel>();
        // graph predictions for targets at specific step
        if (m_advancedConfigPanel.getGraphPredictionsAtStep() > 0) {
          int stepNum = m_advancedConfigPanel.getGraphPredictionsAtStep();
          List<String> targets =
            AbstractForecaster.stringToList(m_threadForecaster.getFieldsToForecast());
          if (eval.getTrainingData() != null && eval.getEvaluateOnTrainingData()) {               
            JPanel trainTargetsAtStep =
              eval.graphPredictionsForTargetsOnTraining(GraphDriver.getDefaultDriver(),
                  m_threadForecaster, targets, stepNum, eval.getPrimeWindowSize());
            trainTargetsAtStep.setToolTipText("Train pred. for targets");

            graphList.add(trainTargetsAtStep);
          }

          if (eval.getTestData() != null && eval.getEvaluateOnTestData()) {
            int instanceOffset = (eval.getPrimeForTestDataWithTestData())
            ? eval.getPrimeWindowSize()
                : 0;
            JPanel testTargetsAtStep =
              eval.graphPredictionsForTargetsOnTesting(GraphDriver.getDefaultDriver(),
                  m_threadForecaster, targets, stepNum, instanceOffset);
            testTargetsAtStep.setToolTipText("Test pred. for targets");

            graphList.add(testTargetsAtStep);
          }
        }

        // graph predictions for specific target at selected steps
        if (m_advancedConfigPanel.getGraphTargetForSteps()) {
          String fieldsToForecast = m_threadForecaster.getFieldsToForecast();
          String selectedTarget =
            m_advancedConfigPanel.getGraphTargetForStepsTarget();

          if (!fieldsToForecast.contains(selectedTarget)) {
            throw new Exception("Cannot graph predictions for \""
                + selectedTarget + "\" because that field is not being predicted.");
          }
          List<Integer> stepList =
            m_advancedConfigPanel.getGraphTargetForStepsStepList();

          if (eval.getTrainingData() != null && eval.getEvaluateOnTrainingData()) {
            JPanel trainStepsForTarget =
              eval.graphPredictionsForStepsOnTraining(GraphDriver.getDefaultDriver(),
                  m_threadForecaster, selectedTarget, stepList, eval.getPrimeWindowSize());
            trainStepsForTarget.setToolTipText("Train pred. at steps");

            graphList.add(trainStepsForTarget);
          }

          if (eval.getTestData() != null && eval.getEvaluateOnTestData()) {
            int instanceOffset = (eval.getPrimeForTestDataWithTestData())
            ? eval.getPrimeWindowSize()
                : 0;
            JPanel testStepsForTarget =
              eval.graphPredictionsForStepsOnTesting(GraphDriver.getDefaultDriver(),
                  m_threadForecaster, selectedTarget, stepList, instanceOffset);
            testStepsForTarget.setToolTipText("Test pred. at steps");

            graphList.add(testStepsForTarget);
          }
        }

        // graph future predictions
        if (m_advancedConfigPanel.getGraphFuturePredictions()) {
          if (eval.getTrainingData() != null /*&& eval.getEvaluateOnTrainingData()*/) {
            try {
              JPanel trainFuture = eval.
              graphFutureForecastOnTraining(GraphDriver.getDefaultDriver(),
                  m_threadForecaster, AbstractForecaster.
                  stringToList(m_threadForecaster.getFieldsToForecast()));
              trainFuture.setToolTipText("Train future pred.");

              graphList.add(trainFuture);
            } catch (Exception ex) {
              if (m_threadForecaster instanceof OverlayForecaster &&
                  ((OverlayForecaster)m_threadForecaster).isUsingOverlayData()) {
                if (m_log != null) {
                  m_log.logMessage("Unable to graph future forecast for training " +
                      "data because no future overlay data is available");
                }
              } else {
                if (m_log != null) {
                  m_log.logMessage("Unable to graph future forecast for " +
                      "training data: " + ex.getMessage());
                }
              }
            }
          }

          if (eval.getTestData() != null && eval.getEvaluateOnTestData()) {
            try {
              JPanel testFuture = eval.
              graphFutureForecastOnTesting(GraphDriver.getDefaultDriver(),
                  m_threadForecaster, AbstractForecaster.
                  stringToList(m_threadForecaster.getFieldsToForecast()));
              testFuture.setToolTipText("Test future pred.");

              graphList.add(testFuture);
            } catch (Exception ex) {
              if (m_threadForecaster instanceof OverlayForecaster &&
                  ((OverlayForecaster)m_threadForecaster).isUsingOverlayData()) {
                if (m_log != null) {
                  m_log.logMessage("Unable to graph future forecast for test " +
                        "data because no future overlay data is available");
                }
              } else {
                if (m_log != null) {
                  m_log.logMessage("Unable to graph future forecast for test " +
                      "data: " + ex.getMessage());
                }
              }
            }
          }
        }

        try {
          if (m_configAndBuild) {
            WekaForecaster copiedForecaster =
              (WekaForecaster)AbstractForecaster.makeCopy(m_threadForecaster);
            resultList.add(copiedForecaster);
            Instances trainHeader = new Instances(trainInst, 0);
            resultList.add(trainHeader);
          }
        } catch (Exception ex) {
          if (m_log != null) {
            logger.println("Problem copying model.");
            m_log.logMessage("Problem copying model: " + ex.getMessage());
          }
          ex.printStackTrace();
        }

        if (graphList.size() > 0) {
          resultList.add(graphList);             
        }

        m_history.addObject(name, resultList);
        if (graphList.size() > 0) {
          updateMainTabs(name);
        }

        if (m_log != null) {
          m_log.logMessage("Finished " + fname);
          logger.println("OK");
        }
      } catch (Exception ex) {
        ex.printStackTrace();
        if (m_log != null) {
          m_log.logMessage(ex.getMessage());
          logger.println("Problem evaluating forecaster");
        }
        JOptionPane.showMessageDialog(ForecastingPanel.this,
            "Problem evaluating forecaster:\n" + ex.getMessage(),
            "Evaluate forecaster", JOptionPane.ERROR_MESSAGE);

      } finally {

        if (isInterrupted()) {
          if (m_log != null) {
            m_log.logMessage("Interrupted " + fname);
            logger.println("Interrupted");
          }
        }

        synchronized (this) {
          m_startBut.setEnabled(true);
          m_stopBut.setEnabled(false);
          m_runThread = null;
        }

        if (m_log instanceof TaskLogger) {
          ((TaskLogger)m_log).taskFinished();
        }
      }
    }
  }
 
  /**
   * Constructor.
   *
   * @param log the log to use (may be null for no log)
   * @param displayWelcome true if the welcome message is to be
   * displayed as the first log entry
   */
  public ForecastingPanel(LogPanel log, boolean displayWelcome) {
    this(log, displayWelcome, true, false);
  }
 
  /**
   * Constructor
   *
   * @param log the log to use (may be null for no log)
   * @param displayLogWelcome true if the welcome message is to be
   * displayed as the first log entry
   * @param allowSeparateTestSet true if the separate test set button is
   * to be displayed
   */
  public ForecastingPanel(LogPanel log, boolean displayLogWelcome,
      boolean allowSeparateTestSet, boolean kfPerspective) {
    m_isRunningAsPerspective = kfPerspective;
   
    m_simpleConfigPanel = new SimpleConfigPanel(this);
   
    m_advancedConfigPanel =
      new AdvancedConfigPanel(m_simpleConfigPanel, allowSeparateTestSet);
   
    setLayout(new BorderLayout());
   
    m_log = log;
    if (m_log != null) {
      if (displayLogWelcome) {
        String date = (new SimpleDateFormat("EEEE, d MMMM yyyy")).format(new Date());
        m_log.logMessage("Weka Forecaster");
        /*m_logPanel.logMessage("(c) " + Copyright.getFromYear() + "-" + Copyright.getToYear()
        + " " + Copyright.getOwner() + ", " + Copyright.getAddress()); */
        // m_logPanel.logMessage("web: " + Copyright.getURL());
        m_log.logMessage("Started on " + date);
        m_log.statusMessage("Welcome to the Weka Forecaster");
      }
    }
   
    m_simpleConfigPanel.setAdvancedConfig(m_advancedConfigPanel);
    m_configPane.addTab(m_simpleConfigPanel.getTabTitle(), null,
        m_simpleConfigPanel, m_simpleConfigPanel.getTabTitleToolTip());
    m_configPane.addTab(m_advancedConfigPanel.getTabTitle(), null,
        m_advancedConfigPanel, m_advancedConfigPanel.getTabTitleToolTip());
   
    m_outText.setEditable(false);
    m_outText.setFont(new Font("Monospaced", Font.PLAIN, 12));
    m_outText.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
   
    final JScrollPane js = new JScrollPane(m_outText);
    js.getViewport().addChangeListener(new ChangeListener() {
      private int lastHeight;
      public void stateChanged(ChangeEvent e) {
        JViewport vp = (JViewport)e.getSource();
        int h = vp.getViewSize().height;
        if (h != lastHeight) { // i.e. an addition not just a user scrolling
          lastHeight = h;
          int x = h - vp.getExtentSize().height;
          vp.setViewPosition(new Point(0, x));
        }
      }
    });
   
    m_outputPane.setBorder(BorderFactory.createTitledBorder("Output/Visualization"));
   
    m_outputPane.addTab("Output", null, js, "Forecaster output");
   
    m_history.setBorder(BorderFactory.createTitledBorder("Result list"));
    m_history.getList().getSelectionModel().
      addListSelectionListener(new ListSelectionListener() {
        public void valueChanged(ListSelectionEvent e) {

          synchronized (ForecastingPanel.this) {
            if (!e.getValueIsAdjusting()) {
              ListSelectionModel lm = (ListSelectionModel)e.getSource();
              for (int j = e.getFirstIndex(); j <= e.getLastIndex(); j++) {
                if (lm.isSelectedIndex(j)) {
                  String name = m_history.getSelectedName();
                  updateMainTabs(name);
                }
              }
            }
          }
        }
      });
   
    m_history.getList().addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent e) {
        if (((e.getModifiers() & InputEvent.BUTTON1_MASK)
            != InputEvent.BUTTON1_MASK) || e.isAltDown()) {
         int index = m_history.getList().locationToIndex(e.getPoint());
         if (index != -1) {
           String name = m_history.getNameAtIndex(index);
           resultPopup(name, e.getX(), e.getY());
         } else {
           resultPopup(null, e.getX(), e.getY());
         }
       }
      }
    });
   
    m_history.setHandleRightClicks(false);
   
//    add(m_configPane, BorderLayout.NORTH);
   
    JPanel lowerPanel = new JPanel();
    lowerPanel.setLayout(new BorderLayout());
    JPanel butAndHistHolder = new JPanel();
    butAndHistHolder.setLayout(new BorderLayout());
    butAndHistHolder.add(m_history, BorderLayout.CENTER);
    JPanel butHolder = new JPanel();
    butHolder.setLayout(new GridLayout(1,3));
    butHolder.add(m_startBut);
    butHolder.add(m_stopBut);
    butHolder.add(m_helpBut);
   
    m_startBut.setToolTipText("Start the forecasting process");
    m_stopBut.setToolTipText("Stop the running forecasting process");
    butAndHistHolder.add(butHolder, BorderLayout.NORTH);
    m_startBut.setEnabled(false); m_stopBut.setEnabled(false);
   
    m_stopBut.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        stopForecaster();
      }
    });
   
    m_helpBut.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        BrowserHelper.openURL(ForecastingPanel.this,
            "http://wiki.pentaho.com/display/DATAMINING/" +
            "Time+Series+Analysis+and+Forecasting+with+Weka");
      }
    });
    m_helpBut.setToolTipText("Visit documentation for the time series " +
        "environment in your browser");
   
    lowerPanel.add(butAndHistHolder, BorderLayout.WEST);
    lowerPanel.add(m_outputPane, BorderLayout.CENTER);
   
    m_splitP = new JSplitPane(JSplitPane.VERTICAL_SPLIT, m_configPane, lowerPanel);
    m_splitP.setOneTouchExpandable(true);

//    add(lowerPanel, BorderLayout.CENTER);
    add(m_splitP, BorderLayout.CENTER);
   
    if (m_log != null && m_log instanceof JComponent) {
      add((JComponent)m_log, BorderLayout.SOUTH);
    }
   
    double width = m_history.getPreferredSize().width;
    int height = m_history.getPreferredSize().height;   
    width *= 0.75;
    m_history.setPreferredSize(new Dimension((int)width, height));
   
    m_startBut.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        startForecaster(m_forecaster);
      }
    });
 
 
  /**
   * Enable the start button
   *
   * @param enable true if the start button is to be enabled
   */
  protected void enableStartButton(boolean enable) {
    m_startBut.setEnabled(enable);
  }
 
  /** Map used to store the tabs containing graph output */
  protected HashMap<String, JTabbedPane> m_framedOutputMap =
    new HashMap<String, JTabbedPane>();
 
  /**
   * Opens the named result in a separate frame
   *
   * @param name the name of the result from the history list to use
   */
  protected void openResultFrame(String name) {
    StringBuffer buffer = m_history.getNamedBuffer(name);
    JTabbedPane tabbedPane = m_framedOutputMap.get(name);

    if (buffer != null && tabbedPane == null) {
      JTextArea textA = new JTextArea(20, 50);
      textA.setEditable(false);
      textA.setFont(new Font("Monospaced", Font.PLAIN, 12));
      textA.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
      textA.setText(buffer.toString());
      tabbedPane = new JTabbedPane();
      tabbedPane.addTab("Output", new JScrollPane(textA));
      updateComponentTabs(name, tabbedPane);
      m_framedOutputMap.put(name, tabbedPane);
     
      final JFrame jf = new JFrame(name);
      jf.addWindowListener(new WindowAdapter() {
        public void windowClosing(WindowEvent e) {
          m_framedOutputMap.remove(jf.getTitle());
          jf.dispose();
        }
      });
      jf.setLayout(new BorderLayout());
      jf.add(tabbedPane, BorderLayout.CENTER);
      jf.pack();
      jf.setSize(550, 400);
      jf.setVisible(true);
    }       
  }
 
  /**
   * Pops up a contextual menu in the result history list
   *
   * @param name the selected entry in the list
   * @param x the x position for the popup
   * @param y the y position for the popup
   */
  protected void resultPopup(final String name, int x, int y) {
    final String selectedName = name;
    JPopupMenu resultListMenu = new JPopupMenu();
   
    JMenuItem showMainBuff = new JMenuItem("View in main window");
    if (selectedName != null) {
      showMainBuff.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          m_history.setSingle(selectedName);
          updateMainTabs(selectedName);
        }
      });
    } else {
      showMainBuff.setEnabled(false);
    }
    resultListMenu.add(showMainBuff);
   
    JMenuItem showSepBuff = new JMenuItem("View in separate window");
    if (selectedName != null) {
      showSepBuff.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          openResultFrame(selectedName);
        }
      });
    } else {
      showSepBuff.setEnabled(false);
    }
    resultListMenu.add(showSepBuff);   
   
    JMenuItem deleteResultBuff = new JMenuItem("Delete result");
    if (selectedName != null && m_runThread == null) {
      deleteResultBuff.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          m_history.removeResult(selectedName);
        }
      });
    } else {
      deleteResultBuff.setEnabled(false);
    }

   
    resultListMenu.add(deleteResultBuff);
   
    resultListMenu.addSeparator();
    List<Object> resultList = null;
    if (selectedName != null) {
      resultList = (List<Object>)m_history.getNamedObject(name);
    }
   
    WekaForecaster saveForecaster = null;
    Instances saveForecasterStructure = null;
    if (resultList != null) {
      for (Object o : resultList) {
        if (o instanceof WekaForecaster){
          saveForecaster = (WekaForecaster)o;
        } else if (o instanceof Instances) {
          saveForecasterStructure = (Instances)o;
        }
      }
    }
   
    final WekaForecaster toSave = saveForecaster;
    final Instances structureToSave = saveForecasterStructure;
    JMenuItem saveForecasterMenuItem = new JMenuItem("Save forecasting model");
    if (saveForecaster != null) {
      saveForecasterMenuItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          saveForecaster(name, toSave, structureToSave);
        }
      });
    } else {
      saveForecasterMenuItem.setEnabled(false);
    }
    resultListMenu.add(saveForecasterMenuItem);
   
    JMenuItem loadForecasterMenuItem = new JMenuItem("Load forecasting model");
    resultListMenu.add(loadForecasterMenuItem);
    loadForecasterMenuItem.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        loadForecaster();
      }
    });
   
    if (m_isRunningAsPerspective) {
      JMenuItem copyToKFClipboardMenuItem =
        new JMenuItem("Copy model to Knowledge Flow clipboard");
      resultListMenu.add(copyToKFClipboardMenuItem);
      copyToKFClipboardMenuItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          try {
            KnowledgeFlowApp singleton = KnowledgeFlowApp.getSingleton();
            String encoded =
              TimeSeriesForecasting.encodeForecasterToBase64(toSave, structureToSave);
           
            TimeSeriesForecasting component = new TimeSeriesForecasting();
            component.setEncodedForecaster(encoded);                       

            TimeSeriesForecastingKFPerspective.setClipboard(component);
          } catch (Exception ex) {
            ex.printStackTrace();
          }
        }
      });
    }
   
   
    JMenuItem reevaluateModelItem = new JMenuItem("Re-evaluate model");
    if (selectedName != null && m_runThread == null) {
     
      reevaluateModelItem.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          reevaluateForecaster(selectedName, toSave, structureToSave);
        }
      });
     
      reevaluateModelItem.
        setEnabled((m_advancedConfigPanel.m_trainingCheckBox.isSelected() ||
            m_advancedConfigPanel.m_holdoutCheckBox.isSelected()) &&
            m_instances != null);
    } else {
      reevaluateModelItem.setEnabled(false);
    }           
   
    resultListMenu.add(reevaluateModelItem);
   
    resultListMenu.show(m_history.getList(), x, y);
  }
 
  /**
   * Load a forecaster and add it to the history list
   */
  protected void loadForecaster() {
    File sFile = null;
    int returnVal = m_fileChooser.showOpenDialog(this);
    if (returnVal == m_fileChooser.APPROVE_OPTION) {
      sFile = m_fileChooser.getSelectedFile();
     
      if (m_log != null) {
        m_log.statusMessage("Loading forecaster...");
      }
     
      Object f = null;
      Instances header = null;
      boolean loadOK = true;
      try {
        ObjectInputStream is = new ObjectInputStream(new FileInputStream(sFile));
        f = is.readObject();
        header = (Instances)is.readObject();
        is.close();
      } catch (Exception ex) {
        JOptionPane.showMessageDialog(null, ex, "Load failed",
            JOptionPane.ERROR_MESSAGE);
        loadOK = false;
      }
     
      if (!loadOK) {
        if (m_log != null) {
          m_log.logMessage("Loading from file " + sFile.getName() + "' failed.");
          m_log.statusMessage("OK");
        }
      } else if (!(f instanceof WekaForecaster)) {
        JOptionPane.showMessageDialog(this,
            "Loaded model is not a weka forecaster!", "Weka forecasting",
            JOptionPane.ERROR_MESSAGE);
        loadOK = false;
      }
     
      if (loadOK) {
        String name = (new SimpleDateFormat("HH:mm:ss - ")).format(new Date());
        StringBuffer outBuff = new StringBuffer();
        WekaForecaster wf = (WekaForecaster)f;       
       
        String lagOptions = "";
        if (wf instanceof TSLagUser) {
          TSLagMaker lagMaker = ((TSLagUser)wf).getTSLagMaker();
          lagOptions = Utils.joinOptions(lagMaker.getOptions());
        }
       
        String fname = wf.getAlgorithmName();
        String algoName = fname.substring(0, fname.indexOf(' '));
        if (algoName.startsWith("weka.classifiers.")) {
          name += algoName.substring("weka.classifiers.".length());
        } else {
          name += algoName;
        }
        name += " loaded from '" + sFile.getName() + "'";
       
        outBuff.append("Scheme:\n\t" + fname).append("\n");
        outBuff.append("loaded from '" + sFile.getName() + "'\n\n");
       
        if (lagOptions.length() > 0) {
          outBuff.append("Lagged and derived variable options:\n\t").
          append(lagOptions + "\n\n");
        }
       
        outBuff.append(wf.toString());
       
        m_history.addResult(name, outBuff);
        m_history.setSingle(name);
       
        List<Object> resultList = new ArrayList<Object>();
        resultList.add(wf); resultList.add(header);
        m_history.addObject(name, resultList);
        updateMainTabs(name);       
      }     
    }
  }
 
  /**
   * Serialize a forecaster out to a file
   *
   * @param name the name of forecaster to save
   * @param forecaster the actual forecaster to save
   * @param structure the structure of the instances used to train the forecaster
   */
  protected void saveForecaster(String name, TSForecaster forecaster,
      Instances structure) {
    File sFile = null;
    boolean saveOK = true;
   
    int returnVal = m_fileChooser.showSaveDialog(this);
    if (returnVal == JFileChooser.APPROVE_OPTION) {
      sFile = m_fileChooser.getSelectedFile();
      if (!sFile.getName().toLowerCase().endsWith(".model")) {
        sFile = new File(sFile.getParent(), sFile.getName()
                         + ".model");
      }
      if (m_log != null) {
        m_log.statusMessage("Saving forecaster to file...");
      }
     
      try {
        ObjectOutputStream oos =
          new ObjectOutputStream(new FileOutputStream(sFile));
        oos.writeObject(forecaster);
        if (structure != null) {
          oos.writeObject(new Instances(structure, 0));
        }
        oos.close();
      } catch (Exception ex) {
        JOptionPane.showMessageDialog(null, ex, "Save Failed",
            JOptionPane.ERROR_MESSAGE);
        saveOK = false;
      }
     
      if (saveOK) {
        if (m_log != null) {
          m_log.logMessage("Saved model (" + name
            + " ) to file '" + sFile.getName() + "'");
          m_log.statusMessage("OK");
        }       
      }
    }   
  }
 
  /**
   * Updates the display with the results from the currently selected
   * entry in the result history list
   *
   * @param name the name of the entry from which to display results
   * @param outputPane the output pane to update
   */
  protected synchronized void updateComponentTabs(String name,
      JTabbedPane outputPane) {
    // remove any tabs that are not the text output
    int numTabs = outputPane.getTabCount();
    if (numTabs > 1) {
      for (int i = numTabs - 1; i > 0; i--) {
        outputPane.removeTabAt(i);
      }
    }

    // see if there are any graphs associated with this name
    List<Object> storedResults = (List<Object>)m_history.getNamedObject(name);
    List<JPanel> graphList = null;
    if (storedResults != null) {
      for (Object o : storedResults) {
        if (o instanceof List<?>) {
          graphList = (List<JPanel>)o;
        }
      }
    }
   
    if (graphList != null && graphList.size() > 0) {                
      // add the graphs
      for (JPanel p : graphList) {
        outputPane.addTab(p.getToolTipText(), p);
      }
    }
  }
 
  /**
   * Updates the tabs in the main display.
   *
   * @param entryName the entry name of the currently displayed results. If
   * the selected result is the same as the current then no update is done.
   */
  protected synchronized void updateMainTabs(String entryName) {
    String name = m_history.getSelectedName();
    if (!name.equals(entryName)) {
      return;
    }
    updateComponentTabs(name, m_outputPane);
  }
 
  /**
   * Set the log to use
   *
   * @param log the log to use
   */
  public void setLog(Logger log) {
    if (log instanceof JComponent) {
      if (m_log != null) {
        remove((JComponent)m_log);
      }
      m_log = log;
      add((JComponent)m_log, BorderLayout.SOUTH);
    }
  }
 
  /**
   * Set the training instances to use
   *
   * @param insts the training instances to use
   * @throws Exception if a problem occurs
   */
  public void setInstances(Instances insts) throws Exception {
   
    // if this is the first set of instances seen then
    // install a listener on the simple config target panel's
    // table model so that we can enable/disable the start
    // button
    m_sortedCheck = false;
    boolean first =
      (m_simpleConfigPanel.m_targetPanel.getTableModel() == null);
   
    m_instances = new Instances(insts);
    m_simpleConfigPanel.setInstances(insts);
    m_advancedConfigPanel.setInstances(insts);
   
    if (first) {           
      TableModel model = m_simpleConfigPanel.m_targetPanel.getTableModel();
      model.addTableModelListener(new TableModelListener() {
        public void tableChanged(TableModelEvent e) {
          int[] selected = m_simpleConfigPanel.m_targetPanel.getSelectedAttributes();
          if (selected != null && selected.length > 0) {
            m_startBut.setEnabled(true);
          } else {
            m_startBut.setEnabled(false);
          }
          m_advancedConfigPanel.updatePanel();
        }
      });
    }
  }
 
  /**
   * Check to see if the data is sorted in order of the date time
   * stamp (if present)
   */
  protected void sortCheck() {
    if (m_instances == null) {
      return;
    }
   
    if (m_simpleConfigPanel.isUsingANativeTimeStamp()) {
      String timeStampF = m_simpleConfigPanel.getSelectedTimeStampField();
      Attribute timeStampAtt = m_instances.attribute(timeStampF);
      if (timeStampAtt != null) {
       
        double lastNonMissing = Utils.missingValue();
        boolean ok = true;
        boolean hasMissing = false;
        for (int i = 0; i < m_instances.numInstances(); i++) {
          Instance current = m_instances.instance(i);
         
          if (Utils.isMissingValue(lastNonMissing)) {
            if (!current.isMissing(timeStampAtt)) {
              lastNonMissing = current.value(timeStampAtt);
            } else {
              hasMissing = true;
            }
          } else {
            if (!current.isMissing(timeStampAtt)) {
              if (current.value(timeStampAtt) - lastNonMissing < 0) {
                ok = false;
                break;
              }
             
              lastNonMissing = current.value(timeStampAtt);
            } else {
              hasMissing = true;
            }
          }
        }
       
        if (!ok && !hasMissing) {
          // ask if we should sort
          int result = JOptionPane.showConfirmDialog(ForecastingPanel.this,
              "The data does not appear to be in sorted order of \""
              + timeStampF + "\". Do you want to sort the data?",
              "Forecasting", JOptionPane.YES_NO_OPTION);
         
          if (result == JOptionPane.YES_OPTION) {
            if (m_log != null) {
              m_log.statusMessage("Sorting data...");
            }
            m_instances.sort(timeStampAtt);
            m_sortedCheck = true;           
          }
        }
       
        if (!ok && hasMissing) {
          // we can't really proceed in this situation. We can't sort by the timestamp
          // because instances with missing values will be put at the end of the data.
          // The only thing we can do is to remove the instances with missing time
          // stamps but this is likely to screw up the periodicity and majorly impact
          // on results.
         
          int result = JOptionPane.showConfirmDialog(ForecastingPanel.this,
              "The data does not appear to be in sorted order of \""
              + timeStampF + "\". \nFurthermore, there are rows with\n" +
                  "missing timestamp values. We can remove these\n" +
                  "rows and then sort the data but this is likely to\n" +
                  "result in degraded performance. It is strongly\n" +
                  "recommended that you fix these issues in the data\n" +
                  "before continuing. Do you want the system to proceed\n" +
                  "anyway by removing rows with missing timestamps and\n" +
                  "then sorting the data?",
              "Forecasting", JOptionPane.YES_NO_OPTION);
         
          if (result == JOptionPane.YES_OPTION) {
            if (m_log != null) {
              m_log.statusMessage("Removing rows with missing time stamps and sorting data...");
            }
            m_instances.deleteWithMissing(timeStampAtt);
            m_instances.sort(timeStampAtt);
            m_sortedCheck = true;           
          }
        }
      }
    }
  }
 
  /**
   * Stop the currently running thread
   */
  protected void stopForecaster() {
    if (m_runThread != null) {
      m_runThread.interrupt();
      m_runThread.stop();
    }
  }
 
 
  /**
   * Inner class defining a log based on PrintStream. This enables command line,
   * gui and central Weka logging to be unified.
   *
   * @author Mark Hall (mhall{[at]}pentaho{[dot]}com)
   */
  class LogPrintStream extends PrintStream {
    public LogPrintStream() {
      super(System.out);
    }
   
    /**
     * Log to the status area. Logs to the log area if the string
     * begins with "WARNING" or "ERROR"
     *
     * @param string the string to display
     */
    private void logStatusMessage(String string) {
      if (m_log != null) {
        m_log.statusMessage(string);
        if (string.contains("WARNING") || string.contains("ERROR")) {
          m_log.logMessage(string);
        }
      }
    }
   
    /**
     * Log to the status area
     * @param string the string to log
     */
    public void println(String string) {
      // make sure that the global weka log picks it up
      System.out.println(string);   
      logStatusMessage(string);
    }
   
    /**
     * Log to the status area
     * @param obj the object to log
     */
    public void println(Object obj) {
      println(obj.toString());
    }
   
    /**
     * Log to the status area
     * @param string the string to log
     */
    public void print(String string){
      // make sure that the global weka log picks it up
      System.out.print(string);
      logStatusMessage(string);     
    }
   
    /**
     * Log to the status area
     * @param obj the object to log
     */
    public void print(Object obj) {
      print(obj.toString());
    }           
  }
 
  /**
   * Reevaluate the supplied forecaster on the current data
   *
   * @param name the name of the result from the history list associated
   * with the forecaster to be reevaluated
   *
   * @param forecaster the forecaster to reevaluate
   * @param trainHeader the header of the data used to train the forecaster
   */
  protected void reevaluateForecaster(final String name,
      final WekaForecaster forecaster, final Instances trainHeader) {
   
    if (!trainHeader.equalHeaders(m_instances)) {
      JOptionPane.showMessageDialog(null, "Data used to train this forecaster " +
                "is not compatible with the currently loaded data:\n\n"
          + trainHeader.equalHeadersMsg(m_instances), "Unable to reevaluate model",
          JOptionPane.ERROR_MESSAGE);
    } else {
      if (m_runThread == null) {
        synchronized (this) {
          m_startBut.setEnabled(false);
          m_stopBut.setEnabled(true);
        }
       
        m_runThread = new ForecastingThread(forecaster, name);
        ((ForecastingThread)m_runThread).setConfigureAndBuild(false);
       
        m_runThread.setPriority(Thread.MIN_PRIORITY);
        m_runThread.start();
      }
    }
  }
 
  /**
   * Start the forecasting process for the supplied configured forecaster
   *
   * @param forecaster the forecaster to run
   */
  protected void startForecaster(final WekaForecaster forecaster) {
    if (m_runThread == null) {
      synchronized (this) {
        m_startBut.setEnabled(false);
        m_stopBut.setEnabled(true);
      }
     
      m_runThread = new ForecastingThread(forecaster, null);     
     
      m_runThread.setPriority(Thread.MIN_PRIORITY);
      m_runThread.start();
    }
  }
 
  private void dontShowMessageDialog(String key, String message, String dialogTitle) {
    if (!Utils.getDontShowDialog(key)) {
      JCheckBox dontShow = new JCheckBox("Do not show this message again");
      Object[] stuff = new Object[2];
      stuff[0] = message + "\n";
      stuff[1] = dontShow;

      JOptionPane.showMessageDialog(this, stuff,
          dialogTitle, JOptionPane.OK_OPTION);

      if (dontShow.isSelected()) {
        try {
          Utils.setDontShowDialog(key);
        } catch (Exception ex) {
          // quietly ignore
        }
      }
    }
  }
     
  /**
   * Tests the Weka Forecasting panel from the command line.
   *
   * @param args must contain the name of an arff file to load.
   */
  public static void main(String[] args) {

    try {
      if (args.length == 0) {
        throw new Exception("supply the name of an arff file");
      }
      Instances i = new Instances(new java.io.BufferedReader(
                                  new java.io.FileReader(args[0])));
      ForecastingPanel scp =
        new ForecastingPanel(new LogPanel(new WekaTaskMonitor()), true, false, false);
      scp.setInstances(i);
      final javax.swing.JFrame jf =
        new javax.swing.JFrame("Weka Forecasting");
      jf.getContentPane().setLayout(new BorderLayout());
      jf.getContentPane().add(scp, BorderLayout.CENTER);
      jf.addWindowListener(new java.awt.event.WindowAdapter() {
        public void windowClosing(java.awt.event.WindowEvent e) {
          jf.dispose();
          System.exit(0);
        }
      });
      jf.pack();
      jf.setVisible(true);
    } catch (Exception ex) {
      ex.printStackTrace();
      System.err.println(ex.getMessage());
    }
  }
}
TOP

Related Classes of weka.classifiers.timeseries.gui.ForecastingPanel$ForecastingThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.