Home : Course Map : Chapter 8 : Java : Tech :
Histogram Median
JavaTech
Course Map
Chapter 8

Introduction
Threads Overview
  Demo 1   Demo 2
Stopping Threads
Multi-Processing
Thread Tasks
Animations
 
 Demo 3   Demo 4  
  Demo 5

Non-interacting
  Demo 6

Task Splitting
  Demo 7

Exclusivity
  Demo 8

Communicating
  Demo 9

Priority/Scheduling
More Thread

Exercises

    Supplements
Java2D Animation
  Demo 1 
Processor View
More Concurrency
Cloning
  Demo 2  Demo 3 

     About JavaTech
     Codes List
     Exercises
     Feedback
     References
     Resources
     Tips
     Topic Index
     Course Guide
     What's New

The median of a distribution is the value at which half of the entries are above that value and half are below it. (For an even number of entries, use the average between the two central values.) For symmetric distributions the mean and median lie close together and the median provides little additional information. However, for distributions with far outliers skewed to one side of a central cluster of data, the mean may lie far from where most of the data values actually occur. A median in such a case will usually lie near or within the main body of data.

We discused in the previous section some tools such as in the Arrays class for sorting values in an numerical array. We can apply this sorting to median finding in the example below. If all the individual data entries are available in the data array, then the array is sorted and the middle entry used (or the average of the two middle values for an even number of data entries) as the median. If no data array available, then the median is estimated from the bin distribution.

MedianHistFillApplet.java - similar to AdaptHistFillApplet in Chapter 8: Tech: Histogram with Adaptable Range except that here the median value is added to the statistics popup frame and an instance of HistogramMedian, a subclass of HistogramAdapt is used.

+ New class:
HistogramMedian.java - this subclass of HistogramAdapt overrides the getStats() method so that it can calculate the median and add it to the statistics array returned from the method.

If the adapt flag is turned on, then the method will sort the data array with a java.util.Arrays sort() method and then use the value of the middle element for an odd number of entries and the average between the middle two entries for an even number of entries in the data array.

If the adapt flag is turned off, then the median is calculated from the histogram bins. The bins are summed until they reach half or more of the total number of entries. The bin that spans the median is scaled according to the number of entries to try to improve on the median value rather than simply use the middle of the bin as the median location.

Note that the array returned from getStats [] must be increased in size by one element to make room for the median.

+ Previous classes:
Chapter 8:Tech: HistogramAdapt.java
Chapter 8:Tech: MakeData.java, Updateable.java
Chapter 7:Tech:
HistogramStat.java,
Chapter 6:Tech: Histogram.java, HistPanel.java
Chapter 6:Tech: PlotPanel.java, PlotFormat.java

 

/**
  *  This class provides provides histograms that calculate the median
  *  of the distribution. It inherits HistogramAdapt that provides for
  *  adapting the range to keep all points within the bin limits. If
  *  adaptation turned on, then median is calculated from the entire
  *  input data. Otherwise, the median is calculated from the data bin
  *  values.
 **/
public class HistogramMedian extends HistogramAdapt
{
   // Add a pointer to a median value.
   public final static int I_MEDIAN = 5;

  /**
    * @param estmated number of data points.
    *   Negative to turn off adaptable binning.
    * @param number of bins for the histogram.
    * @param lowest value of bin.
    * @param highest value of bin.
    * @param size of data array. Must equal number of data points
    *        expected to be entered into the histogram.
   **/
  public HistogramMedian (int num_bins, double lo, double hi,
                          int num_data) {
    super (num_bins,lo,hi,num_data);
  } // ctor

  /**
    * Constructor with title and x axis label.
    *
    * @param title for histogram
    * @param label for x axis of histogram.
    * @param inital number of bins for the histogram.
    * @param lo lowest value of bin.
    * @param hi highest value of bin.
    * @param numData inital size of data array.
   **/
  public HistogramMedian (String title, String x_label, int num_bins,
                       double lo, double hi, int num_data){
    super (title, x_label, num_bins, lo, hi, num_data);
  } // ctor

  /**
    *  Get the statistical measures of the distribution calculated
    *  from the entry values.
    *  @return  values in double array correspond to
    *  mean,standard deviation, error on the mean, skewness, and
    *  kurtosis. If the number of entries is zero or the statistics
    *  accumulation is turned off  (see setStats () method), a null
    *  value will return
   **/
  public double [] getDataStats () {

    double [] tmp_stats = super.getDataStats ();
    // Need to add an element for the median so
    // create a bigger array
    double [] stats = new double[tmp_stats.length +1];
    // and copy the data into this one.
    System.arraycopy (tmp_stats,0, stats, 0, tmp_stats.length);

    // If adaptation turned on, then can calculate the median from
    // all of the data.
    if (fAdapt) {
        if (fDataIndex > 1) {
            java.util.Arrays.sort (fData, 0, fDataIndex);
            int mid = fDataIndex/2;
            // dataIndex points to element above last entry.
            if ((fDataIndex % 2) == 0) {
                // even number of entries
                stats[I_MEDIAN] =  (fData[mid] + fData[mid-1])/2.0;
            } else {
                // odd number of entries
                stats[I_MEDIAN] = fData[mid];
            }
        } else
          stats[I_MEDIAN] = fData[0];
    } else {
        // If no data array then estimate median from
        // the values in bins.
        int half_total_entries = getTotal ()/2;
        int sum_bin_entries = 0;
        int sum = 0;

        double bin_width = fRange/fNumBins;

        // Sum bins up to half total of entries
        for (int i=0; i < fNumBins; i++) {
            sum = sum_bin_entries + fBins[i];

            // Check if bin crosses halfTotal point
            if (sum >= half_total_entries) {
                 // Scale linearly across the bin
                int dif = half_total_entries - sum_bin_entries;
                double frac = 0.0;
                if (fBins[i] > 0) {
                    frac = ((double)dif)/(double)fBins[i];
                }
                stats[I_MEDIAN] =  (i+frac) * bin_width + fLo;

                // Finished
                break;
            }
            // Not reached median yet.
            sum_bin_entries = sum;
        }
    }
    return stats;
  } // getDataStats

} // class HistogramMedian
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

/**
  * Demonstrate the HistogramMedian class that provides the median
  * of the histogram data.
  *
  * This program will run as an applet inside an application frame.
  *
  * The applet uses the HistPanel to display contents of
  * an instance of Histogram. HistFormat used by HistPanel to
  * format the scale values.
  *
  * The java.util.Timer and java.util.TimerTask are used
  * to update the display of the histogram during the filling
  * of the histogram.
  *
  * Includes "Go" button to initiate the filling of the histogram.
  * To simulate data taking, a combination of a Gaussian and random
  * background values are generated.
  *
  * The number of values taken from
  * entry in a JTextField. "Clear"  button clears the histogram.
  * In standalone mode, the Exit button closes the program.
  *
 **/
public class MedianHistFillApplet extends JApplet
             implements ActionListener, Updateable
{
  // Use the HistPanel JPanel subclass here
  HistPanel fOutputPanel;

  HistogramMedian fHistogram;
  int fNumDataPoints = 100;

  boolean fMakingHist = false;
  boolean fUpdateDisplay = false;
  MakeData fMakeData;

  // Use the java.util Timer and TimerTask combo
  // for timing events.
  java.util.Timer fTimer;

  // A text field for input strings
  JTextField fTextField;

  // Flag for whether the applet is in a browser
  // or running via the main () below.
  boolean fInBrowser = true;

  //Buttons
  JButton fGoButton;
  JButton fStatsButton;
  JButton fClearButton;
  JButton fExitButton;

  /**
    * Create a User Interface with a histogram and a Go button
    * to initiate processing and a Clear button to clear the .
    * histogram. In application mode, the Exit button stops the
    * program. Add a stats button to open a frame window to show
    * statistical measures.
   **/
  public void init () {
    Container content_pane = getContentPane ();

    JPanel panel = new JPanel (new BorderLayout ());

    // Use a textfield for an input parameter.
    fTextField =
      new JTextField (Integer.toString (fNumDataPoints), 10);

    // If return hit after entering text, the
    // actionPerformed will be invoked.
    fTextField.addActionListener (this);

    fGoButton = new JButton ("Go");
    fGoButton.addActionListener (this);

    fStatsButton = new JButton ("Stats");
    fStatsButton.addActionListener (this);

    fClearButton = new JButton ("Clear");
    fClearButton.addActionListener (this);

    fExitButton = new JButton ("Exit");
    fExitButton.addActionListener (this);

    JPanel control_panel = new JPanel ();

    control_panel.add (fTextField);
    control_panel.add (fGoButton);
    control_panel.add (fStatsButton);
    control_panel.add (fClearButton);
    control_panel.add (fExitButton);

    // Create a histogram and start filling it.
    makeHist ();
    fGoButton.setText ("Stop");
    fClearButton.setEnabled (false);
    fStatsButton.setEnabled (false);
    if (fInBrowser) fExitButton.setEnabled (false);

    // JPanel subclass here.
    fOutputPanel = new HistPanel (fHistogram);

    panel.add (fOutputPanel,"Center");
    panel.add (control_panel,"South");

    // Add text area with scrolling to the contentPane.
    content_pane.add (panel);
  } // init

  /**  Stop the filling if the browser page unloaded. **/
  public void stop () {
    fGoButton.setText ("Go");
    fStatsButton.setEnabled (true);
    fClearButton.setEnabled (true);
    fMakingHist = false;

  } // stop

  public void actionPerformed (ActionEvent e) {
    Object source = e.getSource ();
    if (source == fGoButton || source == fTextField ) {
        String strNumDataPoints = fTextField.getText ();
        try {
          fNumDataPoints = Integer.parseInt (strNumDataPoints);
        }
        catch (NumberFormatException ex)
        {  // Could open an error dialog here but just
           // display a message on the browser status line.
           showStatus ("Bad input value");
           return;
        }
        if (!fMakingHist) {
            makeHist ();
            fGoButton.setText ("Stop");
            fStatsButton.setEnabled (false);
            fClearButton.setEnabled (false);
        } else {
            // Stop button has been pushed
            fGoButton.setText ("Go");
            fStatsButton.setEnabled (true);
            fClearButton.setEnabled (true);
            fMakingHist = false;
        }
    } else if (source == fStatsButton) {
        displayStats ();
    } else if (source == fClearButton) {
        // Use reset instead of clear so as to
        // set data array pointer to zero.
        fHistogram.reset ();
        repaint ();
    } else if (!fInBrowser)
        System.exit (0);
  } // actionPerformed

  /** Create a frame to display the distribution statistics. **/
  void displayStats () {
     JFrame frame =
        new JFrame ("Histogram Distributions Statistics");

    // Create a listener to close the frame
    WindowListener listener =
      new WindowAdapter ()
      {
        public void windowClosing (WindowEvent e)
        {
          JFrame f =  (JFrame) (e.getSource ());
          f.dispose ();
        }
      };
    frame.addWindowListener (listener);

    JTextArea area = new JTextArea ();

    double [] stats = fHistogram.getDataStats ();
    if (stats != null) {
        area.append ("Number entries = "+fHistogram.getTotal ()+"\n");

        String stat = PlotFormat.getFormatted (
                       stats[HistogramMedian.I_MEDIAN],
                       1000.0,0.001,3);
        area.append ("Median value = "+ stat +" "+"\n");

        stat = PlotFormat.getFormatted (
                      stats[HistogramStat.I_MEAN],
                      1000.0,0.001,3);
        area.append ("Mean value = "+ stat +" ");

        stat = PlotFormat.getFormatted (
                    stats[HistogramStat.I_MEAN_ERROR],
                      1000.0,0.001,3);
        area.append (" +/- "+stat+"\n");

        stat = PlotFormat.getFormatted (
                    stats[HistogramStat.I_STD_DEV],
                      1000.0,0.001,3);
        area.append ("Std. Dev. = "+stat+"\n");

        stat = PlotFormat.getFormatted (
                    stats[HistogramStat.I_SKEWNESS],
                      1000.0,0.001,3);
        area.append ("Skewness = "+stat+"\n");

        stat = PlotFormat.getFormatted (
                    stats[HistogramStat.I_KURTOSIS],
                      1000.0,0.001,3);
        area.append ("Kurtosis = "+stat+"\n");
    } else {
        area.setText ("No statistical information available");
    }

    frame.getContentPane ().add (area);
    frame.setSize (200,200);
    frame.setVisible (true);

  } // displayStats

  /**
    *  Create the histogram, create the MakeData instance and
    *  spin it off in a thread. Set up a java.util.Timer to
    *  run the PaintHistTask periodically.
   **/
  void makeHist () {
    if (fMakingHist) return; // only fill one hist at a time.
    fMakingHist = true;

    // Create an instance of the histogram class.
    // Make it wide enough enough to include the data.
    if (fHistogram == null)
        fHistogram = new HistogramMedian ("Gaussian + Random Background",
                                "Data",
                                20,-2.0,2.0,
                                fNumDataPoints);
    else
      fHistogram.setData (fNumDataPoints);

    // Create the runnable object to fill the histogram
    // Center signal at 3.0 and create background between
    // -10 and 10. The fraction of the data due to the
    // Gaussian will be 0.60. The maximum delay between
    // data poins will be 500msecs.
    fMakeData =
      new MakeData (this, fHistogram, fNumDataPoints,
                   3.0, 0.60, -10.0, 10.0, 500);
    Thread data_thread = new Thread (fMakeData);

    // Before starting the filling, create the timer task
    // that will cause the histogram display to update
    // during the filling.
    // Create a timer. TimerTask created in MakeHist ()
    fTimer = new java.util.Timer ();

    // Start the timer after 100ms and then repeat calls
    // to run in PaintHistTask object every 250ms.
    fTimer.schedule (new PaintHistTask (), 100, 250);

    // Now start the data filling.
    data_thread.start ();

  } // makeHist

  /**
    * Use the inner class technique to define the
    * TimerTask subclass for signalling that the display
    * should be updated.
   **/
  class PaintHistTask extends java.util.TimerTask
  {
    public void run () {
       fUpdateDisplay = true;
    }
  } // class PaintHistTask

  /**
    *  Repaint the histogram display. If called by
    *  the MakeData object, it will turn off the
    *  update display flag and return the fMakingHist flag
    *  to indicate if updating should continue.


   **/
  public boolean update (Object obj) {
    // Don't update the display until the timer
    // turns on the fUpdateDisplay flag.
    if (fUpdateDisplay) {
        // Possible this method called before fOutputPanel
        // created in the init (). So check if it exists
        // before attempting to repaint.
        if (fOutputPanel != null) {
            // Check if binning needs changing before
            // redrawing histogram.
            if (fHistogram.getRebinFlag () ) fHistogram.rebin ();
            // Now rescale and draw.
            fOutputPanel.getScaling ();
            fOutputPanel.repaint ();
        }
        fUpdateDisplay = false;
    }
    return fMakingHist;
  } // update

  /** Called when the histogram filling is finished. **/
  public void done () {
    fMakingHist = false;

    // Stop the histogram display updates.
    fTimer.cancel ();

    // Do a final check if binning needs changing and to
    // redraw histogram.
    if (fHistogram.getRebinFlag () ) fHistogram.rebin ();
    fOutputPanel.getScaling ();
    fOutputPanel.repaint ();

    // Reset the buttons.
    fGoButton.setText ("Go");

    fGoButton.setEnabled (true);
    fStatsButton.setEnabled (true);
    fClearButton.setEnabled (true);

  } // done

  public static void main (String[] args) {
    //
    int frame_width=450;
    int frame_height=300;

    MedianHistFillApplet applet = new MedianHistFillApplet ();
    applet.fInBrowser = false;
    applet.init ();

    // Following anonymous class used to close window & exit program
    JFrame f = new JFrame ("Demo");
    f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);

    // Add applet to the frame
    f.getContentPane ().add (applet);
    f.setSize (new Dimension (frame_width, frame_height));
    f.setVisible (true);
  } // main

} // class MedianHistFillApplet

 

References & Web Resources

Last update: Nov. 8, 2004

              Tech
Timers
  Demo 1
Hist. Adapt Range
  Demo 2
Sorting in Java
  Demo 3
Histogram Median
  Demo 4
Refactoring
  Demo 5
Error Bars
  Demo 6
Exercises

           Physics
Least Squares Fit
  Demo 1
Fit to Polynomial
  Demo 2
Fit Hist Errors
  Demo 3
Discretization
  Demo 4
Timing
  Demo 5
Exercises

  Part I Part II Part III
Java Core 1  2  3  4  5  6  7  8  9  10  11  12 13 14 15 16 17
18 19 20
21
22 23 24
Supplements

1  2  3  4  5  6  7  8  9  10  11  12

Tech 1  2  3  4  5  6  7  8  9  10  11  12
Physics 1  2  3  4  5  6  7  8  9  10  11  12

Java is a trademark of Sun Microsystems, Inc.