通過C#類庫繪制正態分布的統計圖(通用)


僅供學習交流,不足可以指出。

改代碼可以直接使用,對其中錯誤引起的損失不負責。

代碼注釋較清楚:

    /// <summary>
    /// 提供正態分布的數據和圖片
    /// </summary>
    public class StandardDistribution
    {

       
        /// <summary>
        /// 樣本數據
        /// </summary>
        public List<double> Xs { get; private set; }

        public StandardDistribution(List<double> Xs)
        {
            this.Xs = Xs;

            Average = Xs.Average();
            Variance = GetVariance(Xs);

            if (Variance == 0) throw new Exception("方差為0");//此時不需要統計 因為每個樣本數據都相同,可以在界面做相應提示

            StandardVariance = Math.Sqrt(Variance);
        }

        /// <summary>
        /// 方差/標准方差的平方
        /// </summary>
        public double Variance { get; private set; }

        /// <summary>
        /// 標准方差
        /// </summary>
        public double StandardVariance { get; private set; }

        /// <summary>
        /// 算數平均值/數學期望
        /// </summary>
        public double Average { get; private set; }

        /// <summary>
        /// 1/ (2π的平方根)的值
        /// </summary>
        public static double InverseSqrt2PI = 1 / Math.Sqrt(2 * Math.PI);

        /// <summary>
        /// 獲取指定X值的Y值  計算正太分布的公式
        /// </summary>
        /// <param name="x"></param>
        /// <returns></returns>
        public double GetGaussianDistributionY(double x)
        {
            double PowOfE = -(Math.Pow(Math.Abs(x - Average), 2) / (2 * Variance));

            double result = (StandardDistribution.InverseSqrt2PI / StandardVariance) * Math.Pow(Math.E, PowOfE);

            return result;
        }

        /// <summary>
        /// 獲取正太分布的坐標<x,y>
        /// </summary>
        /// <returns></returns>
        public List<Tuple<double, double>> GetGaussianDistributionYs()
        {
            List<Tuple<double, double>> XYs = new List<Tuple<double, double>>();

            Tuple<double, double> xy = null;

            foreach (double x in Xs)
            {
                xy = new Tuple<double, double>(x, GetGaussianDistributionY(x));
                XYs.Add(xy);
            }



            return XYs;
        }

        /// <summary>
        /// 獲取整型列表的方差
        /// </summary>
        /// <param name="src">要計算方差的數據列表</param>
        /// <returns></returns>
        public static double GetVariance(List<double> src)
        {
            double average = src.Average();
            double SumOfSquares = 0;
            src.ForEach(x => { SumOfSquares += Math.Pow(x - average, 2); });
            return SumOfSquares / src.Count;//方差
        }

        /// <summary>
        /// 獲取整型列表的方差
        /// </summary>
        /// <param name="src">要計算方差的數據列表</param>
        /// <returns></returns>
        public static float GetVariance(List<float> src)
        {
            float average = src.Average();
            double SumOfSquares = 0;
            src.ForEach(x => { SumOfSquares += Math.Pow(x - average, 2); });
            return (float)SumOfSquares / src.Count;//方差
        }

        /// <summary>
        /// 畫學生成績的正態分布
        /// </summary>
        /// <param name="Width"></param>
        /// <param name="Height"></param>
        /// <param name="Scores">分數,Y值</param>
        /// <param name="familyName"></param>
        /// <returns></returns>
        public  Bitmap GetGaussianDistributionGraph(int Width, int Height,int TotalScore, string familyName = "宋體")
        {
            //橫軸 分數;縱軸 正態分布的值


            Bitmap bitmap = new Bitmap(Width, Height);

            Graphics gdi = Graphics.FromImage(bitmap);

            gdi.Clear(Color.White);
            gdi.SmoothingMode = SmoothingMode.HighQuality;
            gdi.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
            gdi.PixelOffsetMode = PixelOffsetMode.HighQuality;



            List<Tuple<double, double>> Scores = GetGaussianDistributionYs().OrderBy(x => x.Item1).ToList();//排序 方便后面點與點之間的連線  保證 分數低的 在左邊

            float YHeight = 0.8F * Height;// 相對左下角 YHeight*0.9F 將表示 maxY
            float XWidth = 0.9F * Width;//相對左下角 XWidth*0.9F 將表示 maxX

            float marginX = (Width - XWidth) / 2F;//x軸相對左右圖片邊緣的像素
            float marginY = (Height - YHeight) / 2F;//y軸相對上下圖片邊緣的像素

            PointF leftTop = new PointF(marginX, marginY);

            PointF leftBottom = new PointF(marginX, marginY + YHeight);//坐標軸的左下角


            PointF rightBottom = new PointF(marginX + XWidth, marginY + YHeight);//坐標軸的右下角



            gdi.DrawLine(Pens.Gray, leftBottom, rightBottom);//x軸
            gdi.DrawLine(Pens.Gray, leftBottom, leftTop);//Y軸

            //兩個箭頭 四條線 6個坐標 另需4個坐標

            PointF YArrowLeft = new PointF(leftTop.X - 5, leftTop.Y + 5);
            PointF YArrowRight = new PointF(leftTop.X + 5, leftTop.Y + 5);
            PointF XArrowTop = new PointF(rightBottom.X - 5, rightBottom.Y - 5);
            PointF XArrowBottom = new PointF(rightBottom.X - 5, rightBottom.Y + 5);

            gdi.DrawLine(Pens.Gray, leftTop, YArrowLeft);
            gdi.DrawLine(Pens.Gray, leftTop, YArrowRight);
            gdi.DrawLine(Pens.Gray, rightBottom, XArrowTop);
            gdi.DrawLine(Pens.Gray, rightBottom, XArrowBottom);

            float unitX = 0.0F;//X軸轉換比率
            float unitY = 0.0F;//Y軸轉換比率

            List<PointF> pointFs = ConvertToPointF(Scores, XWidth * 0.9F, YHeight * 0.9F, leftTop, out unitX, out unitY);//將分數和概率 轉換成 坐標


            gdi.DrawCurve(Pens.Black, pointFs.ToArray(), 0.0F);//基數樣條

            //平均分 與 Y軸平行

            PointF avg_top = new PointF(leftTop.X + (float)Average * unitX, leftTop.Y);
            PointF avg_bottom = new PointF(leftTop.X + (float)Average * unitX, leftBottom.Y);
            gdi.DrawLine(Pens.Black, avg_top, avg_bottom);
            gdi.DrawString(string.Format("{0}", ((float)Average ).ToString("F2")), new Font("宋體", 11), Brushes.Black, avg_bottom.X, avg_bottom.Y-25);


            //將期望和方差寫在橫軸下方中間

            PointF variance_pf = new PointF(leftBottom.X+(XWidth/2)-120, avg_bottom.Y + 25);
            gdi.DrawString(string.Format("期望:{0};方差:{1}", ((float)Average).ToString("F2"), Variance.ToString("F2")), new Font("宋體", 11), Brushes.Black, variance_pf.X, variance_pf.Y);


            


            //將最小分數 和 最大分數 分成9段 標記在坐標軸橫軸上

            double minX = Scores.Min(x => x.Item1);
            double maxX = Scores.Max(x => x.Item1);

            double perSegment = TotalScore/10;// (maxX - minX) / 9F;//每一段表示的分數

            List<double> segs = new List<double>();//每一個分段分界線橫軸的值

            segs.Add(leftBottom.X + (float)minX * unitX);

            for (int i = 1; i < 11; i++)
            {
                segs.Add(leftBottom.X + (float)minX * unitX + perSegment * i * unitX);
            }
            for (int i = 0; i < 11; i++)
            {
                gdi.DrawPie(Pens.Black, (float)segs[i] - 1, leftBottom.Y - 1, 2, 2, 0, 360);

                gdi.DrawString(string.Format("{0}", ((minX + perSegment * (i))).ToString("F0")), new Font("宋體", 11), Brushes.Black, (float)segs[i] - 15, leftBottom.Y + 5);
            }




            return bitmap;
        }

        /// <summary>
        /// 將數據轉換為坐標
        /// </summary>
        /// <param name="Scores"></param>
        /// <param name="X">最長利用橫軸</param>
        /// <param name="Y">最長利用縱軸 </param>
        /// <param name="leftTop">左上角原點</param>
        /// <returns></returns>
        private static List<PointF> ConvertToPointF(List<Tuple<double, double>> Scores, float X, float Y, PointF leftTop, out  float unitX, out  float unitY)
        {
            double maxY = Scores.Max(x => x.Item2);
            double maxX = Scores.Max(x => x.Item1);

            List<PointF> result = new List<PointF>();

            float paddingY = Y * 0.01F;
            float paddingX = X * 0.01F;

            unitY = (float)((Y - paddingY) / maxY);//單位縱軸表示出來需要的高度 計算出來的縱坐標需要 leftTop.Y+(Y-item2*unitY)+paddingY
            unitX = (float)((X - paddingX) / maxX);//單位橫軸表示出來需要的寬度 計算出來的橫坐標需要 leftTop.X+item1*unitX

            PointF pf = new PointF();
            foreach (Tuple<double, double> item in Scores)
            {
                pf = new PointF(leftTop.X + (float)item.Item1 * unitX, leftTop.Y + (Y - (float)item.Item2 * unitY) + paddingY);
                result.Add(pf);
            }

            return result;
        }

    }

調用:

            StandardDistribution mathX = new StandardDistribution(scores);
            Bitmap bitmap = mathX.GetGaussianDistributionGraph(800, 480, totalScore);
            bitmap.Save("tt.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);

測試數據生成的正態分布圖:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM