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组件中进行动态的绘制。如下面示例所示:
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程序几个运行效果图如下:
来源:CSDN
作者:聊聊侃侃
链接:https://blog.csdn.net/gaoy_kkk/article/details/104172925