//NormalCurve.java //Gaussian distribution normal distribution import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.border.*; import java.util.*; import java.text.*; public class NormalCurve extends JApplet implements ActionListener { double u, s, z, x, area; int numNormals; double yScale; JPanel controlPanel; JLabel uLabel, sLabel, zLabel, xLabel, yScaleLabel, areaLabel, numNormalsLabel; JTextField uTextField, sTextField, zTextField, xTextField, yScaleTextField, areaTextField, numNormalsTextField; JButton yScaleUpButton, yScaleDownButton, showNormalsButton, inputButton; JSlider zSlider; JTextArea normaltextarea; JScrollPane normalscrollpane; DecimalFormat d4; public void init() { String input; Container container = getContentPane(); container.setLayout( new BorderLayout() ); controlPanel = new JPanel(); controlPanel.setLayout(new FlowLayout()); controlPanel.setBackground(Color.MAGENTA); controlPanel.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); container.add(controlPanel,BorderLayout.NORTH); uLabel = new JLabel( "µ" ); controlPanel.add( uLabel ); uTextField = new JTextField("0",4); u = Double.parseDouble(uTextField.getText()); uTextField.setBackground(Color.BLUE); uTextField.setForeground(Color.WHITE); uTextField.setHorizontalAlignment(JTextField.RIGHT); uTextField.addActionListener( this ); controlPanel.add( uTextField ); sLabel = new JLabel( "\u03C3" ); controlPanel.add( sLabel ); sTextField = new JTextField("1",3); s = Double.parseDouble(sTextField.getText()); sTextField.setHorizontalAlignment(JTextField.RIGHT); sTextField.addActionListener( this ); controlPanel.add( sTextField ); zLabel = new JLabel( "z" ); controlPanel.add( zLabel ); zTextField = new JTextField("0",4); z = Double.parseDouble(zTextField.getText()); zTextField.setBackground(Color.PINK); zTextField.setHorizontalAlignment(JTextField.RIGHT); zTextField.addActionListener( this ); controlPanel.add( zTextField ); zSlider = new JSlider( SwingConstants.HORIZONTAL, 0, getWidth()/2, 4 ); zSlider.addChangeListener( new ChangeListener() { // anonymous inner class // handle change in slider value public void stateChanged( ChangeEvent e ) { z = zSlider.getValue() / (getWidth()/8.0); zTextField.setText(""+d4.format(z)); x = u + z*s; xTextField.setText(""+d4.format(x)); area = calcArea(z); areaTextField.setText(""+d4.format(area)); repaint(); } } ); controlPanel.add(zSlider); xLabel = new JLabel( "x" ); controlPanel.add( xLabel ); xTextField = new JTextField(uTextField.getText(),6); x = Double.parseDouble(xTextField.getText()); xTextField.setBackground(Color.CYAN); xTextField.setHorizontalAlignment(JTextField.RIGHT); xTextField.addActionListener( this ); controlPanel.add( xTextField ); areaLabel = new JLabel( "area" ); controlPanel.add( areaLabel ); areaTextField = new JTextField("0",5); area = Double.parseDouble(areaTextField.getText()); areaTextField.setBackground(Color.BLACK); areaTextField.setForeground(Color.WHITE); areaTextField.setHorizontalAlignment(JTextField.RIGHT); areaTextField.addActionListener( this ); controlPanel.add( areaTextField ); yScaleLabel = new JLabel( "y scale" ); controlPanel.add( yScaleLabel ); yScaleTextField = new JTextField("1",3); yScale = Double.parseDouble(yScaleTextField.getText()); yScaleTextField.setHorizontalAlignment(JTextField.RIGHT); yScaleTextField.addActionListener( this ); controlPanel.add( yScaleTextField ); yScaleUpButton = new JButton( "+" ); controlPanel.add( yScaleUpButton ); yScaleUpButton.addActionListener( this ); yScaleDownButton = new JButton( "-" ); controlPanel.add( yScaleDownButton ); yScaleDownButton.addActionListener( this ); showNormalsButton = new JButton( "Sample" ); controlPanel.add( showNormalsButton ); showNormalsButton.addActionListener( this ); numNormalsLabel = new JLabel( "n" ); controlPanel.add( numNormalsLabel ); numNormalsTextField = new JTextField("100",4); numNormals = Integer.parseInt(numNormalsTextField.getText()); numNormalsTextField.setBackground(Color.BLUE); numNormalsTextField.setForeground(Color.WHITE); numNormalsTextField.setHorizontalAlignment(JTextField.RIGHT); numNormalsTextField.addActionListener( this ); controlPanel.add( numNormalsTextField ); inputButton = new JButton( "Input" ); controlPanel.add( inputButton ); inputButton.addActionListener( this ); normaltextarea = new JTextArea(20,100); normaltextarea.setLineWrap( true ); normaltextarea.setFont( new Font("Courier",Font.BOLD,14) ); normalscrollpane = new JScrollPane(normaltextarea); d4 = new DecimalFormat ("0.0000"); zTextField.setText("1.0"); //fires listener? no zTextField.postActionEvent(); //fires listener } public void actionPerformed( ActionEvent event ) { try { u = Double.parseDouble(uTextField.getText()); double sTry = Double.parseDouble(sTextField.getText()); double zTry = Double.parseDouble(zTextField.getText()); x = Double.parseDouble(xTextField.getText()); double areaTry = Double.parseDouble(areaTextField.getText()); double yScaleTry = Double.parseDouble(yScaleTextField.getText()); numNormals = Integer.parseInt(numNormalsTextField.getText()); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(null, "µ, \u03C3, z, x, area and y scale must be numbers", "NormalCurve: Invalid input", JOptionPane.ERROR_MESSAGE); return; } if ( event.getSource() == uTextField ) { u = Double.parseDouble(uTextField.getText()); x = u + z*s; xTextField.setText(""+d4.format(x)); repaint(); } else if ( event.getSource() == sTextField ) { double oldS = s; s = Double.parseDouble(sTextField.getText()); if (s <= 0) { JOptionPane.showMessageDialog(null, "\u03C3 must be > 0", "NormalCurve: Invalid input", JOptionPane.ERROR_MESSAGE); s = oldS; sTextField.setText(""+s); } else { x = u + z*s; xTextField.setText(""+d4.format(x)); repaint(); } } else if ( event.getSource() == zTextField ) { double oldZ = z; z = Double.parseDouble(zTextField.getText()); if (z < 0) { JOptionPane.showMessageDialog(null, "z must be > 0 for this demo please", "NormalCurve: Invalid input", JOptionPane.ERROR_MESSAGE); z = oldZ; zTextField.setText(""+z); } else { x = u + z*s; xTextField.setText(""+d4.format(x)); zSlider.setValue((int)(z/4*getWidth()/2)); area = calcArea(z); areaTextField.setText(""+d4.format(area)); repaint(); } } else if ( event.getSource() == xTextField ) { x = Double.parseDouble(xTextField.getText()); z = (x-u) / s; zTextField.setText(""+d4.format(z)); zSlider.setValue((int)(z/4*getWidth()/2)); area = calcArea(z); areaTextField.setText(""+d4.format(area)); repaint(); } else if ( event.getSource() == areaTextField ) { double oldarea = area; area = Double.parseDouble(areaTextField.getText()); if (area<0 || area>0.5) { JOptionPane.showMessageDialog(null, "0 <= area <= 0.5", "NormalCurve: Invalid input", JOptionPane.ERROR_MESSAGE); area = oldarea; areaTextField.setText(""+d4.format(area)); } else { z = calcZ(area); zTextField.setText(""+z); zSlider.setValue((int)(z/4*getWidth()/2)); x = u + z*s; xTextField.setText(""+d4.format(x)); repaint(); } } else if ( event.getSource() == yScaleTextField ) { double oldYscale = yScale; yScale = Double.parseDouble(yScaleTextField.getText()); if (yScale < 0) { JOptionPane.showMessageDialog(null, "y scale can not be negative", "NormalCurve: Invalid input", JOptionPane.ERROR_MESSAGE); yScale = oldYscale; yScaleTextField.setText(""+yScale); } else repaint(); } else if ( event.getSource() == yScaleUpButton ) { yScale++; yScaleTextField.setText(""+yScale); repaint(); } else if ( event.getSource() == yScaleDownButton ) { if (yScale > 1) { yScale--; yScaleTextField.setText(""+yScale); repaint(); } } else if ( event.getSource() == showNormalsButton ) { Random generator = new Random(); ArrayList dataList = new ArrayList(); double[] statsArray = new double[12]; double x, sum, xBar, sBar; //s already used for population sigma double min, max, q1, median, q3, iqr; int sd1, sd2, sd3; for (int i=1; i<=numNormals; i++) { x = u + generator.nextGaussian() * s; normaltextarea.append(" "+d4.format(x)); dataList.add(x); } statsArray = stats(dataList); sum = statsArray[0]; xBar = statsArray[1]; sBar = statsArray[2]; min = statsArray[3]; q1 = statsArray[4]; median = statsArray[5]; q3 = statsArray[6]; iqr = q3 - q1; max = statsArray[7]; sd1 = (int)statsArray[8]; sd2 = (int)statsArray[9]; sd3 = (int)statsArray[10]; //numData = (int)statsArray[11]; normaltextarea.append("\n\nx\u0304="+d4.format(xBar)+" (z="+d4.format((xBar-u)/s)+")"); //no Unicode for xBar!?! normaltextarea.append(" s="+d4.format(sBar)); normaltextarea.append("\n\nmax="+d4.format(max)+" (z="+d4.format((max-u)/s)+")"); normaltextarea.append("\n3Q="+d4.format(q3)+" (z="+d4.format((q3-u)/s)+")"); normaltextarea.append("\nmedian="+d4.format(median)+" (z="+d4.format((median-u)/s)+")"); normaltextarea.append(" IQR="+d4.format(q3-q1)); normaltextarea.append("\n1Q="+d4.format(q1)+" (z="+d4.format((q1-u)/s)+")"); normaltextarea.append("\nmin="+d4.format(min)+" (z="+d4.format((min-u)/s)+")"); normaltextarea.append("\n\n%data \u00B1 1\u03C3 from µ: "+d4.format((double)sd1/numNormals*100)); normaltextarea.append(" %data \u00B1 2\u03C3 from µ: "+d4.format((double)sd2/numNormals*100)); normaltextarea.append(" %data \u00B1 3\u03C3 from µ: "+d4.format((double)sd3/numNormals*100)); normaltextarea.append("\n\nSkewness="+skewness(dataList, xBar, sBar)); normaltextarea.append("\nKurtosis="+kurtosis(dataList, xBar, sBar)); JOptionPane.showMessageDialog(null, normalscrollpane, ""+numNormals+" Normally-distributed random numbers: µ="+u+" \u03C3="+s, JOptionPane.PLAIN_MESSAGE); normaltextarea.setText(""); } else if ( event.getSource() == inputButton ) { String input = JOptionPane.showInputDialog("Enter/paste your data separated by spaces");//, //"test your data", //JOptionPane.PLAIN_MESSAGE ); StringTokenizer st = new StringTokenizer(input); ArrayList dataList = new ArrayList(); double[] statsArray = new double[12]; double x, sum, xBar, sBar; double min, max, q1, median, q3, iqr; int numData, sd1, sd2, sd3; while (st.hasMoreTokens()) { x = Double.parseDouble(st.nextToken()); dataList.add(x); } statsArray = stats(dataList); sum = statsArray[0]; xBar = statsArray[1]; sBar = statsArray[2]; min = statsArray[3]; q1 = statsArray[4]; median = statsArray[5]; q3 = statsArray[6]; iqr = q3 - q1; max = statsArray[7]; sd1 = (int)statsArray[8]; sd2 = (int)statsArray[9]; sd3 = (int)statsArray[10]; numData = (int)statsArray[11]; JOptionPane.showMessageDialog(null,"\n\nx\u0304="+d4.format(xBar)+ " s="+d4.format(sBar)+ "\n\nmax="+d4.format(max)+ "\n3Q="+d4.format(q3)+ "\nmedian="+d4.format(median)+ " IQR="+d4.format(q3-q1)+ "\n1Q="+d4.format(q1)+ "\nmin="+d4.format(min)+ "\n\n%data \u00B1 1s from x\u0304: "+ d4.format((double)sd1/numData*100)+ " %data \u00B1 2s from x\u0304: "+ d4.format((double)sd2/numData*100)+ " %data \u00B1 3s from x\u0304: "+ d4.format((double)sd3/numData*100)+ "\n\nSkewness="+ skewness(dataList, xBar, sBar)+ "\nKurtosis="+ kurtosis(dataList, xBar, sBar) ); //needs some sophisticated analysis of normality. /*the D’Agostino-Pearson omnibus test is easy to understand. *It first analyzes your data to determine skewness (to quantify the asymmetry of the distribution) and kurtosis (to quantify the shape of the distribution). It then calculates how far each of these values differs from the value expected with a Gaussian distribution, and computes a single P value from the sum of the squares of these discrepancies. Unlike the Shapiro-Wilk test, this test is not affected if the data contains identical values. */ } } public void paint (Graphics g) { super.paint( g ); //erase int points=1000, xi, yi, sdWidth, bottomOffset=10; double halfWidth, xd, xp, y, coeff; int w = getWidth(); int h = getHeight(); g.setFont(new Font("Courier",Font.BOLD,14)); halfWidth = 4 * s; // +/-4 SD xd = halfWidth / points; coeff = 1 / (s*Math.sqrt(2*Math.PI)); for (int i=1; i<=points; i++) { xp = u + xd*i; //right of mean y = coeff * Math.pow(Math.E,-0.5*(((xp-u)/s)*((xp-u)/s))); //System.out.println(""+x+" "+y); xi = (int)((xp-u+4*s)/(8*s) * w); //scale to applet width yi = h - (int)(y*yScale*h) - bottomOffset; g.drawLine(xi,yi, xi,yi); if ((xp-u)/s <= z) //within z of mean g.drawLine(xi,h-bottomOffset, xi,yi); //vertical line xp = u - (xp - u); //symmetric value on the left of u xi = (int)((xp-u+4*s)/(8*s) * w); g.drawLine(xi,yi, xi,yi); } //cyan line at z(x) y = coeff * Math.pow(Math.E,-0.5*(z*z)); xi = (int)((x-u+4*s)/(8*s) * w); //scale to applet width yi = h - (int)(y*yScale*h) - bottomOffset; g.setColor(Color.CYAN); g.drawLine(xi,h-bottomOffset, xi,yi); //vertical line g.drawString(""+d4.format(x), xi,h); //x value on x axis g.drawLine(1,yi, 5,yi); //tick at y=P(x) on y axis g.drawString(""+d4.format(y), 6,yi); //y axis g.setColor(Color.BLACK); g.drawLine(1,50, 1,h-bottomOffset); //tick at y=P(u) y = coeff; yi = h - (int)(y*yScale*h) - bottomOffset; g.setColor(Color.BLUE); g.drawLine(1,yi, 5,yi); g.drawString(""+d4.format(y), 6,yi); g.setColor(Color.RED); //std dev ticks g.drawLine(1,h-bottomOffset,w,h-bottomOffset); sdWidth = (int)(w / 8.0); //4 SD each side of mean for (int i=0; i<=8; i++) g.drawLine(sdWidth*i,h-bottomOffset,sdWidth*i,h-bottomOffset-4); g.setColor(Color.BLUE); //mean tick g.drawLine(sdWidth*4,h-bottomOffset,sdWidth*4,h-bottomOffset-4); g.drawString("µ",sdWidth*4-3,h-1); } double calcArea( double z){ return 1 - Math.pow((1 + (z * (0.0498673470 + z * (0.0211410061 + z * (0.0032776263 + z * (0.0000380036 + z * (0.0000488906 + z * 0.0000053830))))))), -16) / 2 - 0.5; } double calcZ( double area){ double s, a, b, u; s = Math.sqrt(-2.0 * Math.log(0.5-area)); a = 2.515517 + (0.802853 * s) + (0.010328 * s * s); b = 1 + (1.432788 * s) + (0.189269 * s * s) + (0.001308 * s * s * s); u = s - (a / b); return u;// - 0.5; } double skewness (ArrayList dataList, double xbar, double sd) { double x, sumCubedDiffs=0; Iterator dataIt = dataList.iterator(); while (dataIt.hasNext()) { x = (Double)dataIt.next(); sumCubedDiffs += Math.pow(x-xbar, 3); } return (sumCubedDiffs/dataList.size()) / Math.pow(sd,3); } double kurtosis (ArrayList dataList, double xbar, double sd) { double x, sumFouredDiffs=0; Iterator dataIt = dataList.iterator(); while (dataIt.hasNext()) { x = (Double)dataIt.next(); sumFouredDiffs += Math.pow(x-xbar, 4); } return (sumFouredDiffs/dataList.size()) / Math.pow(sd,4) - 3; } double[] stats (ArrayList dataList) { /* 0 sum, 1 mean, 2 sd, 3 min, 4 1Q, 5 median, 6 3Q, 7 max, 8 % +/- 1sd, 9 % +/- 2sd, 10 % +/- 3sd, 11 numData */ double[] statArray = new double[12]; double x, sum=0, sumSqrd=0, xBar, sBar; double min, max, q1, median, q3, iqr; int numData, sd1=0, sd2=0, sd3=0; Collections.sort(dataList); Iterator dataIt = dataList.iterator(); while (dataIt.hasNext()) { x = (Double)dataIt.next(); sum += x; sumSqrd += x*x; } numData = dataList.size(); xBar = (double)sum/numData; sBar = Math.sqrt((sumSqrd - numData*(xBar*xBar)) / (numData-1) ); dataIt = dataList.iterator(); while (dataIt.hasNext()) { x = (Double)dataIt.next(); if (x>=xBar-sBar && x<=xBar+sBar) sd1++; if (x>=xBar-2*sBar && x<=xBar+2*sBar) sd2++; if (x>=xBar-2*sBar && x<=xBar+3*sBar) sd3++; } min = (Double)dataList.get(0); q1 = 0.5 * ((Double)dataList.get(numData/4) + (Double)dataList.get(numData/4-1)); //same odd even if (numData%2 == 0) { //even median = 0.5 * ((Double)dataList.get(numData/2) + (Double)dataList.get(numData/2-1)); q3 = 0.5 * ((Double)dataList.get(3*numData/4) + (Double)dataList.get(3*numData/4-1)); } else { //odd median = (Double)dataList.get(numData/2); q3 = 0.5 * ((Double)dataList.get(3*numData/4) + (Double)dataList.get(3*numData/4+1)); } iqr = q3 - q1; max = (Double)dataList.get(numData-1); statArray[0] = sum; statArray[1] = xBar; statArray[2] = sBar; statArray[3] = min; statArray[4] = q1; statArray[5] = median; statArray[6] = q3; statArray[7] = max; statArray[8] = sd1; statArray[9] = sd2; statArray[10] = sd3; statArray[11] = numData; return statArray; } }