<

개인 프로젝트

[web] 성적 예상 등수 계산기 개발 회고

hanseongjun 2025. 5. 2. 23:49
728x90
반응형

성적 예상 등수 계산기

  • 시험을 보고 나서 평균, 표준편차, 총원, 내 점수를 입력하면 정규분포 식을 이용해 해당 점수가 몇등일지 예상 등수, 예상 학점을 알려 주는 프로그램

배운 점

  • 확률과 통계 수업에서 pdf, cdf에 대해 배웠는데, 해당 프로젝트에서 정규분포를 사용할 때 이 식이 사용될 줄은 몰랐다.
  • normalPDF, normalCDF, erf, normalInverseCDF 이렇게 4개의 함수가 대표적으로 사용되었으며, 해당 함수 각각의 기능은 다음과 같다.
  1. normalPDF : x, mean(평균), stddev(표준분포)를 이용해 현재 점수가 전체 구간 중 얼마정도를 차지하는지 알려준다.
  • 확률적으론 pdf에서 x가 나올 확률이 얼마인지를 나타낸다.
  • 해당 프로젝트에선 성적 정규분포에서 구간당 사람이 얼마나 있는지를 나타낼 때, 해당 구간의 밀도를 표현할 때 사용한다.

function normalPDF(x, mean, stddev) {
    const exponent = -0.5 * Math.pow((x - mean) / stddev, 2);
    return (1 / (stddev * Math.sqrt(2 * Math.PI))) * Math.exp(exponent);
}
  1. normalCDF : x, meal(평균), stddev(표준분포)를 이용해 특정 점수 이하의 비율을 계산한다.
  • 확률적으론 특정 값(x) 이하가 나올 확률을 계산할 때 사용된다.
  • 해당 프로젝트에선 해당 점수가 가지는 백분율 구간을 구할 때 사용되었다.
  • ((1 - normalCDF()) * 100 = 상위 n%)
  • 뒤에서 설명할 erf()를 이용해 이를 구할 수 있다.
  • function normalCDF(x, mean, stddev) { return 0.5 * (1 + erf((x - mean) / (stddev * Math.sqrt(2)))); }
  1. erf : 오류 함수, 정규분포의 누적 면적과 비슷한 역할 수행
  • 해당 프로젝트에선 normalCDF를 구할 때 사용되었다.
    function erf(x) {
      const sign = x < 0 ? -1 : 1;
      x = Math.abs(x);
      const a1 = 0.254829592, a2 = -0.284496736, a3 = 1.421413741;
      const a4 = -1.453152027, a5 = 1.061405429, p = 0.3275911;
      const t = 1 / (1 + p * x);
      const y = 1 - (((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t) * Math.exp(-x * x);
      return sign * y;
    }
  1. normalInverseCDF : 역정규분포 함수, 누적 확률값이 주어졌을 때 해당하는 x값을 알려준다.
  • 해당 프로젝트에선 상위 n% 백분율이 주어졌을 때, 해당하는 점수값을 역산할 때 사용됐다.

    function normalInverseCDF(p, mean = 0, stddev = 1) {
    if (p <= 0 || p >= 1) {
        console.error(`parameter out of bounds: ${p}`); // 오류 로깅
      throw new Error("p must be between 0 and 1 (exclusive)");
    }
    
    // Constants for approximation
    const a1 = -39.69683028665376, a2 = 220.9460984245205;
    const a3 = -275.9285104469687, a4 = 138.3577518672690;
    const a5 = -30.66479806614716, a6 = 2.506628277459239;
    
    const b1 = -54.47609879822406, b2 = 161.5858368580409;
    const b3 = -155.6989798598866, b4 = 66.80131188771972;
    const b5 = -13.28068155288572;
    
    const c1 = -0.007784894002430293, c2 = -0.3223964580411365;
    const c3 = -2.400758277161838, c4 = -2.549732539343734;
    const c5 = 4.374664141464968, c6 = 2.938163982698783;
    
    const d1 = 0.007784695709041462, d2 = 0.3224671290700398;
    const d3 = 2.445134137142996, d4 = 3.754408661907416;
    
    // Define break-points.
    const plow = 0.02425;
    const phigh = 1 - plow;
    
    let q, r;
    let result;
    
    if (p < plow) {
      q = Math.sqrt(-2 * Math.log(p));
      result = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) /
               ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
    } else if (p <= phigh) {
      q = p - 0.5;
      r = q * q;
      result = (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q /
               (((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1);
    } else {
      q = Math.sqrt(-2 * Math.log(1 - p));
      result = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) /
                ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
    }
    
    return mean + stddev * result;
    }
  1. 그 외에도 step을 정해서 몇 개의 막대가 구간에서 표시되게 정하는 부분이나, chart.js를 이용해 그래프를 그리는 법 등을 배웠다.

아쉬웠던 점

  1. 정규 분포 함수로는 실제 시험 등수를 구할 수 없다. 다만 평균과 표준 편차를 이용해 실제 데이터가 통계적으로 정규분포 식에 가깝다는 점을 이용해 예상 등수를 구한다. 실제 등수와는 다를 수 있다는 한계가 있다.
  2. 정규 분포에선 실제 시험 점수의 상한선과 하한선을 넘어갈 수 있다는 문제가 있다. 정규분포이기 때문에 말 그대로 평균 점수에서 표준분포를 이용해 주변 점수의 범위와 그 확률을 구한다. 예를 들어 실제 시험 점수의 상한이 100점이지만, 평균 80에 표준분포 10으로 계산하면 일부 구간에서 100점을 넘어가버리는 문제가 있다.

추가 개발 계획

  • 실제 시험 점수의 상한과 하한을 입력하면 해당 구간 내에서만 정규분포식을 적용하고 (넘어가는 부분 절삭), 그 안에서 예상 등수와 상위 n%인지를 다시 구하는 로직을 추가하고 싶다.
728x90
반응형
LIST