| 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 |