Programming Collective Intelligence by Toby Segaran. Copyright 2007 Toby Segaran, 978-0-596-52932-1.

이번에도 저자의 부탁을 들어주며 시작합니다. 이전에 다루었던 유클리디안 거리 점수와는 달리 이번에는 피어슨 상관점수를 사용합니다. 정규화되지 않은 데이터일지라도 훨씬 괜찮은 결과를 출력합니다. 기존에서 약간 더 개선된 방법론이죠.


피어슨 상관점수로 유사성 검증

https://www.statisticssolutions.com/bivariate-correlation/

수 많은 데이터 사이에 하나의 직선은 최적 맞춤선(best-fit line)이라고 합니다. 이 직선은 선형이라는 특성을 가지고 있습니다. 또한 동시에 그래프의 모든 항목을 고려한 모든 항목들과 가장 가까운 직선이기도 합니다. 각 축은 이전 유클리디안 거리 점수와 같이 하나의 차원으로 대해집니다. 항목에 대한 평가 척도죠. 만약 A와 B 항목이 같은 값을 가지고 있다면 이 선은 대각선이 됩니다. 또한 이 때의 피어슨 상관점수는 1이 될 것입니다. 즉, 최적 맞춤선이 오른쪽 위로 가로지르는 대각선에 가까울 수록 두 척도는 유사하다는 것이겠죠.

그리고 피어슨 상관점수의 경우에는 점수 부풀리기(Grade inflation)이라는 특이한 특성이 있습니다. 필자는 아직 이 특성에 대한 정확한 답변을 찾지 못했습니다. 그러나 대략 평균에서 무시당하는 소수의 입장을 생각해보면 어느정도 생각은 가능하다 생각합니다. (혹시 정확한 정의나 이 특성에 관한 성질을 알고 계신 분은 댓글이나 연락 가능한 수단으로 알려주시면 감사하겠습니다)

위의 경우에는 Reading Test와 Writing Test가 각각의 평가 척도가 되었습니다. 그리고 원으로 표시된 항목들이 산포도로 표현되었습니다. 비록 이미지의 출처는 아래 링크에 쓰여있습니다. 그래도 일단은 해당 테스트에 대한 임의의 데이터를 만들어 사용하겠습니다. 이 때 첫번째 항목 집합이 Reading Test라고 합시다. 그리고 두번째를 Writing Test라고 하겠습니다. 어떤 학생의 테스트 결과들이겠죠. 슬프겠지만요. 우리는 이제 그동안 본 Reading Test와 Writing Test 점수가 얼마나 유사한지 파악할 것입니다.

const createRandomNumbers = n => {
  return Array.from({length: n}, () => Math.random())
}

const data = new Array(2).fill(createRandomNumbers(100))

먼저 임의의 데이터를 생성합니다. 위에서 언급했듯이 0번에 Reading Test, 1번에는 Writing Test 데이터가 포함되어 있습니다.

const pearsonCorrelation = data => {
  let si = new Array()

  data[0].forEach(item => {
    if (data[1].indexOf(item) > -1) si.push(item)
  }

  if (si.length === 0) return 0
}

그리고 data 안의 두 배열에 서로 연관성(공통 요소)가 없으면 0을 반환합니다.

const pearsonCorrelation = data => {
  ...

  let sum = new Array(2).fill(0)
  let sumSq = new Array(2).fill(0)
  let sumMp = 0

  data.forEach((d, i) => {
    d.forEach(k => {
      sum[i] += k
      sumSq[i] += Math.pow(k, 2)
    })
  })
  data[0].forEach((k, i) => sumMp += i * data[1][k])
}

다음 단계에서는 각 요소의 모든 합과 제곱의 합, 곱의 합을 각각 계산합니다.

const pearsonCorrelation = data => {
  ...

  let num = sumMp - (sum[0] * [sum[1] / si.length)
  let den = ((sumSq[0] - Math.pow(sum[0], 2) / si.length) * (sumSq[1] - Math.pow(sum[1], 2) / si.length))

  if (den === 0) return 0

  return num / den
}

마지막으로 피어슨 상관계수를 계산하면 완성입니다. 결과적으로 아래와 같이 되겠습니다.

const pearsonCorrelation = data => {
  let si = new Array()

  data[0].forEach(item => {
    if (data[1].indexOf(item) > -1) si.push(item)
  }

  if (si.length === 0) return 0

  let sum = new Array(2).fill(0)
  let sumSq = new Array(2).fill(0)
  let sumMp = 0

  data.forEach((d, i) => {
    d.forEach(k => {
      sum[i] += k
      sumSq[i] += Math.pow(k, 2)
    })
  })
  data[0].forEach((k, i) => sumMp += i * data[1][k])

  let num = sumMp - (sum[0] * [sum[1] / si.length)
  let den = ((sumSq[0] - Math.pow(sum[0], 2) / si.length) * (sumSq[1] - Math.pow(sum[1], 2) / si.length))

  if (den === 0) return 0

  return num / den
}

틀린 점이 보이신다면 꼭 댓글로 알려주시기 바랍니다. 죄송합니다. 긴 글이었는데 이만 줄이겠습니다. 읽어주셔서 감사합니다.