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
|