Java绘制正态分布统计图

房东的猫 提交于 2020-02-06 18:24:16

Java绘制正态分布统计图

1.正态分布

        正态分布(Normal distribution),又名高斯分布(Gaussian distribution),它在数学等工程领域中使用较为频繁的一种概率分布,尤其在统计学上有着重大的影响力。统计后的正态曲线呈钟型(两边低,中间高),如下图所示(图片来源百度百家号http://baijiahao.baidu.com/):
在这里插入图片描述

2.生成正态分布数据并统计

        在做开发的过程中,我们往往要模拟出一些正态分布的数据,统计后进行图表绘制。使用Java模拟正态分布是一件非常容易的事情,随机数类Random的nextGaussian方法提供。
        nextGaussian方法返回一个随机double类型数据,我们可以用一个数据容器存储该方法生成的若干个随机数。这些随机数会以0为基准随机生成正态分布的double数值,这些数值的均值接近0。在下面的程序示例中,我们利用nextGaussian生成若干个随机数,然后进行分组统计,最后在控制台上输出统计图表。

控制台输出正态分布统计图
01.	import java.util.ArrayList;
02.	import java.util.Comparator;
03.	import java.util.HashMap;
04.	import java.util.Map;
05.	import java.util.Random;
06.	import java.util.function.Supplier;
07.	import java.util.stream.Stream;
08.	
09.	public class Gauss {
10.	
11.		
12.		private int ySize;//统计的标记数目y轴(分组数)
13.		private int dataNumber;//生成的虚拟数据的个数
14.		private int xSize;//刻度数目x轴
15.	
16.		//保存生成的高斯数据
17.		private ArrayList<Double> list=new ArrayList<>();
18.		//根据分组统计数据个数
19.		private Map<Integer,Integer> map=new HashMap<>();
20.		
21.		public Gauss(int xSize,int ySize,int dataNumber) {
22.			this.ySize=ySize>3?ySize:3;
23.			this.xSize=xSize>3?xSize:3;
24.			this.dataNumber=dataNumber>1000?dataNumber:1000;
25.			init();
26.		}
27.		
28.		private void init() {
29.			//初始化高斯随机数
30.			Random ran=new Random();
31.			for(int i=0;i<dataNumber;i++) {
32.				list.add(ran.nextGaussian());
33.			}
34.			//初始化统计容器
35.			for(int i=1;i<=this.ySize;i++) {
36.				map.put(i, 0);
37.			}
38.		}
39.		//分析并统计高斯随机数
40.		public void analysis() {
41.			/*
42.			 * 利用Stream进行统计,由于Stream终极方法会关闭,当重复使用Stream时
43.			 * 我们需要用供应商不断的提供相同的stream。
44.			 */
45.			Supplier<Stream<Double>> supp=()->list.stream();//Lambda表达式给供应商
46.			//为Stream提供一个比较器
47.			Comparator<Double> comp=(e1,e2)->e1>e2?1:-1;
48.			//获取最大最小值
49.			double max=supp.get().max(comp).get();
50.			double min=supp.get().min(comp).get();
51.			double range=(max-min)/this.ySize;//计算统计区间的单位范围
52.			//将每一个标记区的数据统计后放入map中。
53.			for(int i=1;i<=this.ySize;i++) {
54.				double start=min+(i-1)*range;
55.				double end=min+i*range;
56.				Stream<Double> stream=supp.get()
57.							          .filter((e)->e>=start).filter((e)->e<end);
58.				map.put(i,(int)stream.count());
59.			}
60.		}	
61.		//绘制统计图
62.		public void grawValue() {
63.			int ScaleSize=14;//x轴刻度长度
64.			int avgScale=this.dataNumber/xSize;
65.			int printSize=ScaleSize-String.valueOf(avgScale).length();
66.			//打印X轴、刻度以及刻度值
67.			for(int i=0;i<=xSize;i++) {
68.				printChar(' ',printSize);
69.				System.out.print(i*avgScale);
70.			}
71.			System.out.println("");
72.			for(int i=0;i<=xSize;i++) {
73.				if(i==0) {
74.					printChar(' ',printSize);
75.				}else {
76.					printChar('-',ScaleSize);
77.				}
78.			}
79.			System.out.println();
80.			//绘制统计内容
81.			for(int i=1;i<=ySize;i++) {
82.				printChar(' ', printSize-1-String.valueOf(i).length());
83.				System.out.print(i+":");
84.				int scaleValue=map.get(i);
85.				double grawSize=scaleValue/(avgScale*1.0/ScaleSize);
86.				grawSize=(grawSize>0 && grawSize<1)?1:grawSize;
87.				printChar('█', (int)grawSize);
88.				System.out.println(" "+scaleValue+"\n");
89.			}
90.		}
91.		
92.		public Map<Integer,Integer> getAnalysisMap(){
93.			return this.map;
94.		}
95.		
96.		private void printChar(char c,int number) {
97.			for(int i=0;i<number;i++) {
98.				System.out.print(c);
99.			}
100.		}
101.		
102.		public static void main(String[] args) {
103.			Gauss g=new Gauss(8,12,10000);
104.			g.analysis();
105.			g.grawValue();
106.		}
107.	}

运行结果如下图:
在这里插入图片描述
        从运行的结果上来看,它形成的分布是符合正态分布的。如果熟悉Swing组件的话,我们也可以在Swing组件中进行动态的绘制。如下面示例所示:

SwingGUI模拟正态分布数据
01.	import java.awt.BorderLayout;
02.	import java.awt.Color;
03.	import java.awt.Dimension;
04.	import java.awt.Font;
05.	import java.awt.Graphics;
06.	import java.awt.Rectangle;
07.	import java.awt.font.FontRenderContext;
08.	import java.awt.geom.AffineTransform;
09.	import java.util.Map;
10.	
11.	import javax.swing.JButton;
12.	import javax.swing.JFrame;
13.	import javax.swing.JLabel;
14.	import javax.swing.JPanel;
15.	import javax.swing.JScrollPane;
16.	import javax.swing.JSlider;
17.	import javax.swing.SwingUtilities;
18.	import javax.swing.SwingWorker;
19.	import javax.swing.event.ChangeListener;
20.	
21.	public class GaussGUI extends JFrame{
22.	
23.		private static final long serialVersionUID = 1L;
24.		//绘图面板
25.		private JPanel grawPanel=new JPanel();
26.		//滑杆x-刻度个数,y-分组个数,dataNum-随机数个数
27.		private JSlider x,y,dataNum;
28.		//各组件说明标签
29.		private JLabel xlab,ylab,datalab;
30.		private JLabel grawLab;
31.		private JButton bt=new JButton("绘图");
32.		//绘制图像的基础宽度,高度
33.		private final int X_WIDTH=600;
34.		private final int Y_HEIGHT=350;
35.		//绘制图像的起始x,y坐标位置
36.		private final int X_START=50;
37.		private final int Y_START=50;
38.		
39.		public GaussGUI() {
40.			super("正态分布数据模拟");
41.			this.setBounds(300,300,800,600);
42.			this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
43.			this.setVisible(true); 
44.		}
45.		//加载界面内容和事件
46.		public void load() {
47.			init();
48.			eventInit();
49.		}
50.		
51.		//初始化界面
52.		public void init() {
53.			JScrollPane scrPan=new JScrollPane(grawPanel);
54.			grawLab=new JLabel("统计图");
55.			grawPanel.add(grawLab);
56.			JPanel topPanel=new JPanel(null);
57.			xlab=new JLabel("x轴刻度个数:5");		
58.			ylab=new JLabel("y轴刻度个数:5");
59.			datalab=new JLabel("随机数个数:10000");
60.			//数轴最小3,最大20,默认为5,刻度单位为3
61.			x=new JSlider(3,15,5);
62.			y=new JSlider(3,15,5);
63.			//设置随机数数量
64.			dataNum=new JSlider(1000,100000,10000);
65.			x.setMinorTickSpacing(3);
66.			y.setMinorTickSpacing(3);
67.			dataNum.setMinorTickSpacing(1000);
68.			
69.			xlab.setBounds(10, 10, 100, 30);
70.			x.setBounds(120, 10, 150, 30);
71.			ylab.setBounds(300, 10, 100, 30);
72.			y.setBounds(420, 10, 150, 30);
73.			bt.setBounds(600, 10, 100, 30);
74.			datalab.setBounds(10,50,130,30);
75.			dataNum.setBounds(150,50,600,30);
76.			topPanel.add(xlab);
77.			topPanel.add(x);
78.			topPanel.add(ylab);
79.			topPanel.add(y);
80.			topPanel.add(bt);
81.			topPanel.add(datalab);
82.			topPanel.add(dataNum);
83.			//显示刻度
84.			x.setPaintTicks(true);
85.			y.setPaintTicks(true);
86.			dataNum.setPaintTicks(true);
87.			dataNum.setSnapToTicks(true);//移动一个刻度
88.			topPanel.setPreferredSize(new Dimension(0,80));
89.			this.add(topPanel,BorderLayout.NORTH);
90.			this.add(scrPan,BorderLayout.CENTER);
91.		}
92.		//事件处理
93.		public void eventInit() {
94.			//处理滑杆
95.			ChangeListener listener=(e)->{
96.				if(e.getSource() instanceof JSlider){
97.					JSlider o=(JSlider)e.getSource();
98.					JLabel lab=null;
99.					if(o==x) lab=xlab;
100.					if(o==y) lab=ylab;
101.					if(o==dataNum) lab=datalab;
102.		
103.					if(lab!=null) {
104.						String newTxt=lab.getText()
105.								.replaceAll("\\d+",o.getValue()+"");
106.						lab.setText(newTxt);
107.					}
108.				}
109.			};
110.			x.addChangeListener(listener);
111.			y.addChangeListener(listener);
112.			dataNum.addChangeListener(listener);
113.			//处理按钮,关键绘图
114.			bt.addActionListener((e)->{
115.				//注意生成大量数据可能会造成阻塞,使用swing工作线程进行生成。
116.				getTask().execute();
117.			});
118.		}
119.		//工作线程实现
120.		private SwingWorker<String, Integer> getTask(){
121.			SwingWorker<String, Integer> task=new SwingWorker<String, Integer>(){
122.	
123.				@Override
124.				protected String doInBackground() throws Exception {
125.					//利用Console版的Gauss生成数据,并获取统计集合		
126.					Gauss gus=new Gauss(x.getValue(), 
127.									   y.getValue(),dataNum.getValue());
128.					gus.analysis();
129.					Graphics g=grawPanel.getGraphics();
130.					//图形绘制需要使用EDT线程完成,否则容易出现部分图形无法绘制的情况
131.					SwingUtilities.invokeLater(()->{
132.						g.clearRect(0, 0, grawPanel.getWidth(), 
133.										 grawPanel.getHeight());
134.						drowX(g);
135.						drowY(g,gus.getAnalysisMap());
136.					});
137.					return null;
138.				}
139.			};
140.			
141.			return task;
142.		}
143.		//绘制X坐标轴
144.		private void drowX(Graphics g) {
145.			//刻度值
146.			int avgScaleValue=dataNum.getValue()/x.getValue();
147.			g.setColor(Color.RED);
148.			//刻度间距
149.			int avgScale=X_WIDTH/x.getValue();
150.			//绘制坐标X横轴
151.			g.drawLine(X_START,Y_START,X_START+X_WIDTH+20,Y_START);
152.			g.drawLine(X_START+X_WIDTH+16,Y_START-3,X_START+X_WIDTH+20,Y_START);
153.			g.drawLine(X_START+X_WIDTH+16,Y_START+3,X_START+X_WIDTH+20,Y_START);
154.			Font font=this.getFont();
155.			//测量文本大小所需的信息容器
156.			FontRenderContext frc = new FontRenderContext(new AffineTransform(),
157.									                       true,true);
158.			//绘制坐标轴上的刻度标题
159.			for(int i=0;i<=x.getValue();i++) {
160.				String xTitle=String.valueOf(avgScaleValue*i);
161.				//获取标题文本像素宽度
162.				int titleWidthPix=font.getStringBounds(xTitle, frc)
163.									 .getBounds().width;
164.				//基础x加i倍刻度减一半文本宽度
165.				int titleX=X_START+avgScale*i-titleWidthPix/2;
166.				g.drawString(xTitle,titleX,Y_START-10);
167.				g.drawLine(X_START+avgScale*i, Y_START, 
168.						   X_START+avgScale*i, Y_START-2);
169.			}
170.		}
171.		
172.		//绘制Y坐标轴
173.		private void drowY(Graphics g,Map<Integer,Integer> map) {
174.			g.setColor(Color.RED);
175.			//刻度间距
176.			int avgScale=Y_HEIGHT/y.getValue();
177.			
178.			g.drawLine(X_START,Y_START, X_START, Y_START+Y_HEIGHT+50);
179.			Font font=this.getFont();
180.			//测量文本大小所需的信息容器
181.			FontRenderContext frc = new FontRenderContext(new AffineTransform(),
182.													    true,true);
183.			//绘制Y坐标轴上的刻度标题
184.			for(int i=1;i<=y.getValue();i++) {
185.				String xTitle=String.valueOf(i)+"Gr";
186.				//获取文本像素宽度
187.				Rectangle titleRec =font.getStringBounds(xTitle, frc).getBounds();
188.				//绘制标题
189.				//titleHeigth标题高度=基础距离+i倍的刻度间距+文本高度的一半再减5个像素
190.				int titleHeigth=Y_START+(avgScale*i)+titleRec.height/2-5;
191.				g.setColor(Color.RED);
192.				g.drawString(xTitle,X_START-(titleRec.width+5),titleHeigth);
193.				g.drawLine(X_START, Y_START+(avgScale*i), 
194.					       X_START-2, Y_START+(avgScale*i));
195.				//绘制统计图
196.				g.setColor(Color.blue);
197.				//计算柱图的宽度
198.				double gwidth=map.get(i)/(dataNum.getValue()*1.0/X_WIDTH);
199.				gwidth=gwidth>0 && gwidth<1?1:gwidth;
200.				
201.				g.fillRect(X_START+3, Y_START+avgScale*i-avgScale/4,
202.			 		       (int)gwidth,avgScale/2);
203.				g.drawString(map.get(i)+"", X_START+13+(int)gwidth,titleHeigth);
204.			}
205.		}
206.		
207.		public static void main(String[] args) {
208.			SwingUtilities.invokeLater(()->new GaussGUI().load());
209.		}
210.	
211.	}

        程序运行后,我们可以通过GUI界面上的组件动态的生成正态分布图,需要注意的是,两个小程序中都是用了Lambda表达式和Stream,我们必须在Java8以上的版本中才能运行。上述基于Swing的GUI程序几个运行效果图如下:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!