728x90
반응형

머신러닝 사용 시 주의해야 할 사항들

 머신러닝은 데이터를 기반으로 머신러닝 알고리즘을 학습시키기 때문에 데이터나 알고리즘에 다음과 같은 문제가 있다면, 머신러닝을 사용했을 때, 제대로 된 결과를 기대하기 어렵다.

 

 

 

 

1. 충분하지 않은 양의 훈련 데이터

  • 머신러닝은 말 그대로, 데이터를 이용해서 그 안에 숨어 있는 패턴을 찾아내는 것이다.
  • 예를 들어, 고양이 사진을 이용해서, 고양이를 찾을 수 있는 모델을 만든다고 가정해보자.
  • 고양이 사진이 매우 적다면, 시스템은 단순하게 꼬리, 다리 4개를 가진 모든 대상을 고양이라고 판단할 수도 있다.
  • 고양이의 종류는 매우 다양하고, 다른 동물과 달리 고양이만 가진 특징을 컴퓨터가 찾아내기 위해서는 매우 많은 양의 데이터가 필요하다.

 

 

 

 

 

2. 대표성이 없는 훈련 데이터

  • 머신러닝은 훈련 데이터를 이용해서 시스템을 학습시키고, 그 학습된 시스템에 새로운 데이터가 들어왔을 때, 학습된 내용을 바탕으로 새로운 데이터를 분류하게 된다.
  • 만약, 훈련에 사용한 데이터가 대표성이 없다면, 새로운 데이터를 처음 보는 종류의 데이터로 보고, 잘못된 분류를 할 가능성이 높다.
  • 예를 들어, 우리가 길거리에서 흔히 볼 수 있는 길고양이 즉, 코리안 숏헤어만 고양이로 학습시킨다면, 고양이는 털이 짧고, 얼굴이 동그랗고, 귀가 뾰족하며, 주황색, 흰색, 회색, 검은색 털을 갖는다라고 패턴을 익혀버릴 수 있다. 이러한 경우, 털이 길거나, 귀가 둥글거나, 접혀있는 다른 종류의 고양이들을 다른 동물로 인식할 수 있다.

2.1. 데이터의 양과 대표성

  • 샘플링 잡음(Sampling noise): 데이터의 양이 매우 적다면, 대표성이 매우 적은 데이터만 뽑힐 확률이 높아진다. 고양이 사진 10,000장보다 고양이 사진 100장에 털이 짧은 고양이 사진만 뽑힐 확률이 매우 높기 때문이다.
  • 샘플링 편향(Sampling bias): 데이터의 양이 많다고해서, 반드시 대표성이 높은 것은 아니다. 10,000장의 고양이 사진에 털이 짧은 고양이 사진이 9,500장, 500장에만 털이 긴 고양이 사진이 들어갈 가능성도 있기 때문이다.
  • 물론, 추출한 데이터의 양이 매우 많고, 데이터를 생성하는 방법에도 이상이 없는 경우, 위와 같은 문제가 발생할 가능성은 크게 낮아진다.
  • 예를 들어, 어떤 TV 프로를 즐겨보는지 설문 조사를 할 때, 유치원생들만을 대상으로 한다면, 한국인들은 뽀로로와 핑크퐁을 굉장히 좋아하는 특이한 사람들이라고 판단할 위험이 있으며, 면접을 보는 사람들을 대상으로 학업 성적에 대해 설문 조사를 하면, 자신에게 불이익이 갈까 봐 응답을 하지 않거나, 거짓 대답을 할 가능성이 있다.

 

 

 

 

 

3. 품질이 낮은 훈련 데이터

  • 훈련 데이터에 결측값(Missing value)이나 이상 값(Outlier) 등이 굉장히 많이 존재한다면, 머신러닝을 통해 제대로 된 패턴을 찾아내지 못할 수 있다.
  • 예를 들어, 수질 검사기의 센서에 수중 식물이나 오염 물질이 붙어 있어, 실제 수치와 전혀 다른 결과가 생성될 수 있다.
  • 혹은, 키를 적는 문항에 몸무게를 적거나, 손으로 쓴 설문 문항을 전산화하는 과정에서 180을 1800이라고 잘못 입력할 수도 있다.
  • 때문에 데이터 분석가는 분석을 하기 전에, 결측값이나 이상 값들이 발생한 이유를 파악하고, 측정 도구의 신뢰도 검사를 통해, 제대로 된 데이터가 수집되고 있는지 파악하고, 그 성격에 맞는 조치를 취해야 한다.
  • 예를 들어, 결측값이 발생한 이유가 어떤 의도에 의해 발생하여, 결측 값에 숨겨진 패턴이 존재할 가능성이 높다면, 설문조사를 재실시해 누락된 부분을 채워 넣거나, 그 양이 매우 적으며 단순 실수에 의해 발생한 결측 값이라면, 일괄 제거 혹은 다중 대체법(Multiple Imputation) 등으로, 누락된 결측 값을 추론하여 계산할 수도 있다.

 

 

 

 

 

4. 관련 없는 특성이 들어가 있는 경우


Garbarge in, garbage out - "쓰레기가 들어가면 쓰레기가 나온다."


  • 정보통신분야에서 유명한 위 문구는 머신러닝 분야에서도 그대로 통용된다.
  • 고양이 사진을 이용해서 고양이를 학습시키려고 하는데, 쓸 데 없이 모든 고양이 사진에 화분이 들어가 있다면, 화분을 중요한 패턴으로 인식할 수 있다.
  • 때문에 훈련 데이터에는 관련 없는 특성을 최소화시키고, 관련 있는 특성을 최대화할 필요가 있다.

4.1. 특성 공학(Feature engineering)

  • 머신러닝을 포함한 모든 데이터 분석에서 가장 중요한 부분은 전처리로, 훈련에 사용할 좋은 특성을 찾는 과정을 특성 공학이라고 한다.
  • 특성 선택(Feature selection): 전체 특성 중 훈련에 가장 유용한 특성을 선택한다.
  • 특성 추출(Feature extraction): 특성을 결합하여 더 유용한 특성을 만들어낸다. 특성 간 성격을 이용하여, 특성의 정보를 축소시킬 수도 있고, 차원축소(PCA) 알고리즘을 사용하여 특성을 추출할 수도 있다.

 

 

 

 

 

5. 훈련 데이터 과대적합(Overfitting)

  • 과대적합(Overfitting)은 훈련 데이터에 대해 지나치게 최적화되어, 훈련 데이터는 잘 분류하지만, 새로운 데이터는 제대로 분류하지 못하는 현상을 말한다.
  • 과대적합은 데이터의 양이 적거나, 편향되어 있는 경우, 필요 이상으로 학습을 시키는 경우(Epochs가 필요치 보다 큰 경우), 주로 발생한다.
  • 예를 들어, 훈련 데이터의 모든 고양이 사진의 고양이 목에 리본이 달려 있다면, 목에 리본이 없는 고양이들을 고양이가 아니라고 분류할 가능성이 올라간다.

5.1. 과대적합을 피하는 방법

  • 과대적합은 훈련 데이터에 있는 잡음의 양에 비해 모델이 너무 복잡할 때 발생한다. 이를 해결하는 방법은 다음과 같다.
  • 규제(Regularization)를 사용하여 모델을 단순화시키거나, 모델 파라미터의 수가 적은 보다 단순한 모델을 사용한다.
  • 훈련 데이터에 있는 특성의 수를 차원축소(PCA)와 같은 방법으로 줄인다.
  • 훈련 데이터의 잡음을 줄인다(오류 데이터 수정 및 이상치 제거).
  • 훈련 데이터를 더 많이 모은다.

 

 

 

 

 

6. 훈련 데이터 과소적합(Underfitting)

  • 과소적합은 과대적합의 반대로 모델이 너무 단순해서 학습 데이터에 숨겨진 패턴을 제대로 찾아내진 못한 경우이다.

6.1. 과소적합을 피하는 방법

  • 모델 파라미터의 수가 더 많은 강력한 모델을 사용한다.
  • 보다 더 좋은 특성을 제공한다.
  • 규제의 양을 줄인다.

 

 

[참고 서적]

 

 

 

 지금까지 머신러닝 시 주의 사항에 대해 알아보았다. 머신러닝은 기본적으로 데이터를 이용해서, 데이터 속에 숨어 있는 패턴을 찾아내는 것이므로, 데이터의 품질이 떨어지거나, 데이터의 양이 지나치게 적은 경우, 그 역할을 제대로 해낼 수 없다. 

 데이터 분석에서 제일 중요한 것은 데이터의 품질 관리이므로, 데이터에 대한 파악과 사용하고자 하는 머신러닝 알고리즘에 대한 지식이 선행되어야만, 머신러닝을 사용한 데이터 분석을 제대로 수행할 수 있다.

728x90
반응형
728x90
반응형

사례 기반 학습과 모델 기반 학습

 지난 포스트에서는 데이터의 양, 분석 환경 등에 따라 학습 방법을 달리하는 배치 학습과 온라인 학습에 대해 알아보았다.

 이번 포스트에서는 머신러닝 모델이 새로운 데이터가 들어왔을 때, 학습된 내용을 기반으로 어떤 방식으로 일반화(Generalize)하는지에 따라 분류되는 "사례 기반 학습"과 "모델 기반 학습"에 대해 알아보도록 하겠다.

 

 

 

 

1. 사례 기반 학습(Instance-based learning)

  • 사례 기반 학습은 말 그대로 학습된 사례들을 기억하는 것으로, 시스템이 훈련 데이터를 기억함으로써 학습한다.
  • 새로운 데이터가 들어오는 경우, 학습된 데이터(일부 또는 전체)와 새로운 데이터의 유사도를 측정하여, 입력된 새로운 데이터를 가장 유사도가 높은 기존 학습 데이터의 클래스로 분류한다.
  • 유사도 측정(Similarty measurement)은 데이터에 따라 약간 다르게 진행되며, 텍스트 데이터가 대상인 경우, 공통으로 포함된 단어의 수를 기반으로 분류한다.
  • 예를 들어, 스팸 메일과 공통으로 포함된 단어가 많은 경우, 스팸 메일로 분류한다.

 

 

 

 

 

2. 모델 기반 학습(Model-based learning)

  • 모델 기반 학습은 데이터 셋에 적합한 모델을 사용하여 모델을 만들고, 모델을 이용하여 새로운 데이터를 어떻게 분류할지 예측(Prediction)한다.
  • 모델 기반 학습은 데이터 셋에 따라 사용되는 모델이 다르므로, 데이터 셋에 적합한 모델을 찾는 모델 선택(Model selection) 과정이 필요하다.
  • 모델에는 다양한 모델 파라미터(Model parameter)가 존재하며, 데이터에 최적화되는 모델 파라미터를 찾아내는 것이 모델 훈련(Training)이다.
  • 학습 데이터 셋을 훈련시키는 과정에서 선택된 모델의 성능이 얼마나 좋은지를 "적합도 함수(Fitness function, 효용 함수 - Utility function)"로 평가할 수 있으며, 얼마나 나쁜지에 대해서는 "비용 함수(Cost function, 손실 함수 - loss function)"로 평가할 수 있다.
  • 모델 파라미터는 일반적으로 손실 함수를 최소화시키는 방향으로 진행되며, 손실 함수를 최소화시키는 파라미터를 찾는 과정을 훈련(Training)이라 한다.
  • 모델 파라미터는 머신러닝 모델이 학습 과정에서 찾아가는 파라미터이며, 사용자가 직접 설정해주는 학습률(Learning rate) 같은 파라미터는 모델 파라미터와 구분하기 위해, 하이퍼 파라미터(Hyper parameter)라고 한다.
  • 모델 기반 학습은 다음과 같이 진행된다.
  1. 데이터 분석
  2. 데이터에 적합한 모델 선택
  3. 훈련 데이터(Training data)로 모델 훈련 - 비용 함수를 최소화하는 모델 파라미터 탐색
  4. 학습이 끝난 모델에 새로운 데이터를 적용해 예측(Predict) - 학습 데이터로 학습된 모델을 이용해서 학습 데이터와 분리된 시험 데이터(Test data)가 얼마나 잘 예측되는지를 본다.
  • 모델 학습 기반은 모델에 종속되어 새로운 데이터를 추론하므로, 모델이 적합하지 않거나, 데이터가 부족한 경우 더 좋은 모델을 탐색해야 하고, 그 모델에 맞는 데이터를 더 수집해야 한다.
  • 모델의 코드 작성 난이도는 낮지만, 그 모델이 구동되는 정확한 원리를 이해하지 못한다면, 제대로 된 결과를 도출하지 못할 가능성이 높다.

 

 

[참고 서적]

 

 

 

 지금까지 사례 기반 학습과 모델 기반 학습에 대해 알아보았다. 사례 기반 학습과 모델 기반 학습 모두 데이터를 기반으로 하지만, 사례 기반 학습은 데이터에서 찾아낸 특정 패턴이 얼마나 유사한지(동일 패턴이 얼마나 많이 등장하는지)에 따라 분류를 하고, 모델 기반 학습은 수많은 머신러닝 모델의 모델 파라미터를 데이터에 맞게 만들어 예측을 한다.

 추후 학습하게 될 머신러닝 모델들은 모두 모델 기반 학습이므로, 사례 기반 학습보다는 모델 기반 학습을 눈여겨보도록 하자.

728x90
반응형
728x90
반응형

배치 학습과 온라인 학습

 지난 포스트에서는 학습 데이터를 어떻게 입력하는지에 따라 분류되는 지도 학습, 비지도 학습, 준지도 학습, 강화 학습에 대해 알아보았다.

 이번 포스트에서는 실시간으로 학습이 가능한지 여부에 따라 나뉘는 배치 학습과 온라인 학습에 대해 알아보도록 하겠다.

 

 

 

 

1. 배치 학습(Batch learning)

  • 배치 학습은 한번에 모든 훈련 데이터를 학습시키는 방법으로, 시간과 자원을 많이 소모하므로, 일반적으로 오프라인 환경에서 수행되므로, 오프라인 학습(Offline learning)이라고도 한다.
  • 학습은 런칭 전에 일어나고, 제품에 학습된 내용을 적용하면, 더 이상의 학습 없이 사용만 된다.
  • 새로운 데이터가 등장하여, 머신을 재학습 하고자 하는 경우, 이전 데이터에 새로운 데이터를 포함한 전체 데이터를 학습시키고, 학습된 새로운 모델을 사용해야 한다.

  • 이전 포스트("머신러닝-1.0. 전통적인 기법과 머신러닝의 차이")에서 말한 것처럼 새로운 패턴에 대하여 자동화 하는 경우, 주기적으로 자동적으로 재학습을 할 수 있다.
  • 그러나, 빅데이터에 대하여, 머신러닝 알고리즘을 사용하는 경우, 그 데이터의 양이 지나치게 많기 때문에 학습 시간이 작게는 몇 시간에서 길게는 몇 주, 한 달 이상의 시간이 소모될 수도 있기 때문에 쉬운 작업은 아니다.
  • 데이터의 양이 매우 많아 학습 시간이 지나치게 길거나, 탐사 로봇 같이 자원이 지나치게 한정된 상황, 주식 가격표 같은 실시간 반영이 필요한 상황에서는 배치학습이 아닌 능동적인 학습이 필요하다.

 

 

 

 

 

2. 온라인 학습(Online learning)

  • 온라인 학습은 일반적으로, 학습이 끝나 제품화가 된 모델에 대하여, 미니배치(Mini-batch)라 부르는 작은 묶음 단위의 데이터를 주입하여 모델을 학습시키는 방법이다.
  • 미니 배치의 크기가 작기 때문에 학습 단계가 빠르고 비용이 적게 들기 때문에 모델은 데이터가 도착하는 대로 즉시 학습을 할 수 있다.
  • 점진적으로 학습이 일어나기 때문에 점진적 학습(Incremental learning)이라고도 하며, 온라인 학습은 오프라인으로도 시행되기도 하므로(외부 메모리 학습), 온라인 학습이라는 용어보다 점진적 학습이 정확한 명칭이라 할 수 있다.
  • 온라인 학습은 연속적으로 데이터를 받고 빠른 변화에 스스로 적응해야 하거나, 자원이 매우 한정된 환경에 적합하다.
  • 온라인 학습 모델은 새로운 데이터 샘플을 학습하면, 학습이 끝난 데이터는 더 이상 필요하지 않기 떄문에 보관하지 않아도 되므로, 저장 공간을 많이 아낄 수 있다.

 

2.1. 외부 메모리 학습(Out-of-core learning)

  • 빅 데이터 분석 시, 데이터의 양이 지나치게 커서, 컴퓨터의 메모리로 감당되지 않는 경우가 종종 있으며, 이때에도 온라인 학습 알고리즘이 사용된다.
  • 데이터 일부를 읽어 들여 머신러닝 알고리즘이 학습하며, 전체 데이터가 모두 적용될 때까지 일부를 학습하는 과정을 반복한다.
  • 온라인 학습 모델에서는 학습률(Learning Rate)이 가장 중요한 하이퍼 파라미터로 작동하며, 이는 모델이 변화하는 데이터에 얼마나 빠르게 적응할지를 이야기한다.
  • 학습률이 높은 경우, 시스템이 데이터에 빠르게 적응하나, 과거의 데이터를 금방 잊는다.
  • 학습률이 낮은 경우, 시스템의 관성이 커져 더 느리게 학습되지만, 노이즈나 대표성이 없는 데이터 포인트에 덜 민감해진다.

 

2.2. 온라인 학습의 문제점

  • 나쁜 데이터가 주입되었을 때, 시스템의 성능이 점진적으로 감소하게 된다.
  • 예를 들어, 실시간 추천 알고리즘에서 누군가가 자신이 만든 콘텐츠나 제품을 상위에 노출시키고자, 자신의 의도를 담은 데이터를 다량 생성할 수 있다. 이 경우, 모델은 이 데이터를 학습하여, 실제 알고리즘에서 노출시키고자 대상이 아닌, 누군가가 의도적으로 생성한 데이터를 노출시킬 수 있다.
  • 때문에, 온라인 학습에서는 시스템을 지속적으로 모니터링하고, 성능 감소가 감지되면, 즉시 학습을 중지시키고, 성능 감소가 이루어지기 전 상태로 되돌려야 한다.
  • 또는 이상 감지 머신러닝 알고리즘을 데이터 수집 앞에 넣어, 비정상 데이터가 온라인 학습 모델에 들어가지 않게 막을 수도 있다.

 

 

[참고 서적]

 

 

 지금까지 배치 학습과 온라인 학습에 대해 알아보았다. 배치 학습은 일반적인 머신러닝 알고리즘 학습 방법이나, 수많은 환경(특히 빅데이터를 사용하는 환경)에서 배치 학습을 사용하지 못하는 경우가 많다. 이런 경우에는 온라인 학습 방법을 사용하지 않는다면, 제대로 된 학습을 하지 못할 수 있다.

728x90
반응형
728x90
반응형

지도 학습 & 비지도 학습 & 준지도 학습 & 강화 학습

 지난 포스트에서는 전통적인 프로그래밍 기법과 머신러닝 기법을 직접 비교해보았다. 이번 포스트에서는 머신러닝 시스템에서 사람이 감독을 하는지 하지 않는지에 따라 나뉘는 지도 학습, 비지도 학습, 준지도 학습, 강화 학습이 무엇인지 알아보도록 하겠다.

 지도, 비지도, 준지도, 강화 학습은 사람의 감독 형태가 어떠한지와 정보량에 따라 구분된다.

 

 

 

1. 지도 학습(Supervised Learning)

  • 지도 학습에는 답지인 레이블(Label) 데이터가 필요하다.
  • 지도 학습의 대표적인 예는 분류(Classification)로, 숫자 손글씨 인식 모델을 만드는 경우, 손으로 쓴 숫자에 해당하는 클래스(Class) 0, 1, 2, 3,..., 9를 레이블로 붙이고, 머신은 각 레이블에 대한 패턴을 학습한다. 새로 들어온 데이터가 특정 클래스에 속하는 패턴을 가지고 있는 경우, 각 클래스에 속할 확률을 계산한다.
  • 또는, 사진을 바탕으로, 고양이를 인식하는 모델을 만들기 위해, 고양이 사진에는 고양이라는 의미로 0으로 범주화(Class) 하고, 나머지는 고양이가 아니다는 의미로 1로 범주화한다.
  • 통계분석의 꽃인 회귀분석(Regression Analysis)은 대표적인 지도 학습 방법 중 하나이다. 독립변수(Independent variable)인 성별, 하루 평균 운동 시간, 식사량, 간식 섭취량, 음료수 섭취량 등이 종속변수(Dependent variable)인 체질량지수(BMI)에 영향을 주는 정도를 학습하여, 체질량지수 모델을 만들어낼 수도 있다.
  • 대표적인 지도 학습 알고리즘은 다음과 같다.
  1. K-최근접 이웃(K-nearest neighbors)
  2. 선형 회귀(Linear ragression)
  3. 로지스틱 회귀(Logistic regression)
  4. 서포트 벡터 머신(Support vector machine, SVM)
  5. 의사 결정 나무(Decision tree)와 랜덤 포레스트(Random forest)
  6. 신경망(Neural networks)

 

 

 

 

 

2. 비지도 학습(Unsupervised learning)

  • 훈련 데이터에 레이블 데이터가 없이 학습을 진행하므로, 머신은 어떤 도움 없이 학습을 해야 한다.
  • 비지도 학습은 크게 3개 알고리즘에 특화되어 있다.
  1. 군집화(Clustering)
  2. 시각화와 차원 축소(Visualization & Dimensionality reduction) 
  3. 연관 규칙 학습(Association rule learning)

 

2.1. 군집화(Clustering)

  • 군집화는 데이터에 있는 패턴을 찾고, 그 패턴을 기반으로 n개의 집단으로 데이터를 나눈다.
  • 예를 들어, 특정 온라인 쇼핑몰에 방문하는 사람의 메타 데이터를 이용하여, 군집화를 실시한다면, 해당 온라인 쇼핑몰의 시간대별 방문자의 특징을 이용해, 각 집단이 선호하는 것에 대해 마케팅을 할 수 있다.
  • 대표적인 알고리즘은 다음과 같다.
  1. k-평균(k-means)
  2. DBSCAN
  3. 계층 군집 분석(Hierarchical cluster analysis, HCA)
  4. 원-클래스 서포트 벡터 머신(One-class SVM)
  5. 아이솔레이션 포레스트(Isolation forest)

 

2.1.1 이상치 탐지와 특이값 탐지(Outlier detection & Novelty detection)

  • 이상치 탐지(Outlier detection): 학습 시, 대부분의 데이터를 정상 데이터로 입력하여(모든 데이터를 정상 데이터로 사용하는 것보다. 소량의 이상치 데이터를 섞어 넣는 것이 보다 좋은 결과를 낸다는 이슈가 있다), 새로 입력된 데이터가 정상 데이터로부터 벗어나는 경우, 이상치로 탐지한다.
  • 특이값 탐지(Novelty detection): 모든 데이터를 특이값이 아닌 데이터로 학습시켜, 새로운 데이터가 들어갔을 때, 학습되지 않은 데이터가 들어간다면, 이를 특이값으로 탐지한다. 예를 들어, 시내에서 돌아다니는 사람들에 대해 학습을 하는 경우, 인형탈을 쓰고 돌아다니는 사람을 제외하고 학습을 시킨다면, 인형탈을 쓰고 돌아다니는 사람을 인식했을 때, 다른 사람들과 크게 다른 특이값으로 탐지하게 된다.
  • 이상치 탐지와 특이값 탐지는 학습 데이터에서 벗어나는 대상을 탐지하는 것이므로, 이 역시 전체 군집과 그 밖의 군집으로 나누는 군집화라고 할 수 있다.

 

2.2. 시각화와 차원 축소(Visualization & Dimensionality reduction) 

  • 시각화(Visualization): 레이블이 존재하지 않는 대규모의 고차원 데이터를 입력하면, 도식화가 가능한 2D나 3D 표현을 만들어내며, 해당 알고리즘은 가능한 구조를 그대로 유지(입력 공간에서 떨어져 있던 클러스터는 시각화된 그래프에서 겹쳐지지 않게 유지된다.)하려고 하기 때문에 데이터가 어떻게 조직되어 있고, 예상하지 못했던 패턴의 존재 등을 탐색할 수 있다.
  • 차원 축소(Dimensionality reduction): 정보의 손실을 최소화시키면서, 데이터를 간소화시키는 것으로, 관련이 깊은 특성을 하나로 합치는 작업이다.
  • 주어진 특성을 조합해 새로운 특성을 생성하므로 특성 추출(Feature extraction)이라고도 한다.
  • 시각화와 차원 축소 모두, 전체 데이터에서 정보 손실을 최소화하면서, 데이터를 간소화시키는 과정이 존재하기 때문에 상당히 유사하다.
  • 대표적인 알고리즘은 다음과 같다.
  1. 주성분 분석(Principal component analysis, PCA)
  2. 커널 주성분 분석(Kernel PCA)
  3. 지역적 선형 임베딩(Locally-Linear embedding, LLE)
  4. t-SNE(t-distributed stochastic neighnor embedding)

 

2.3. 연관 규칙 학습(Assiciation rule learning)

  • 대량의 학습 데이터에서 특성(Feature) 간 관계를 찾아내는 것이다.
  • 예를 들어, 마트에서 치킨이나 감자튀김 같은 기름진 음식을 구매한 사람은 맥주를 구매하는 경향이 있으므로, 고객의 동선에 맥주와 맥주 안주거리를 순차적으로 배치할 수 있다.
  • 대표적인 알고리즘은 다음과 같다.
  1. 어프라이어리(Apriori)
  2. 이클렛(Eclat)

 

 

 

 

 

3. 준지도 학습(Semisuplervised learning)

  • 데이터에 레이블을 다는 것은 시간과 비용이 매우 많이 드는 작업이기 때문에 레이블이 있는 샘플을 찾는 것은 쉽지 않다. 준지도 학습은 지도 학습과 비지도 학습의 사이에 있는 학습 방법으로, 레이블이 존재하는 데이터와 존재하지 않는 데이터 모두를 훈련에 사용하는 것이다.
  • 레이블이 없는 다량의 데이터에 적은 양의 레이블이 존재하는 데이터를 포함시키는 경우, 학습 정확도가 개선되는 경향이 있다고 한다.
  • 사용자가 올린 사진을 자동으로 정리해주는 구글 포토(Google Photo)가 대표적인 예로, 사람들이 다량의 사진을 올리는 경우, 학습된 패턴을 이용해 사진들을 군집화하고, 사진에 등장한 사람들에 대해 Labeling을 해주면, 특정 인물이 있는 사진들만 찾을 수도 있다.
  • 대부분의 준지도 학습 알고리즘은 지도 학습과 비지도 학습 알고리즘의 조합으로 이루어져 있으며, 대표적인 준지도 학습인 심층 신뢰 신경망(Deep belief networks, DBN)제한된 볼츠만 머신(Restricted Boltzmann machine, RBM)을 기반으로 순차적으로 학습한 후, 지도 학습 방식으로 세밀하게 조정된다.

 

 

 

 

 

4. 강화 학습(Reinforcement learning)

  • 강화 학습은 지도 학습, 비지도 학습과 상당히 다른 알고리즘으로, 학습하는 머신을 에이전트(Agent)라 부르고, 환경(Environment)을 관찰해 행동(Action)을 실행하고, 그 결과로 보상(Reward)이나 벌점(Penalty)을 받는다.
  • 강화 학습은 시간이 지나면서 가장 큰 보상을 얻기 위해, 정책(Policy)이라 부르는 최상의 전략을 스스로 학습한다.
  • 정책은 주어진 상황에서 에이전트가 어떤 행동을 선택해야 할지 정의한다.
  • 강화 학습은 딥러닝이라는 단어를 전 세계에 알린 딥마인드(DeepMind)의 알파고(AlphaGo)에서 사용되었다.

 

 

[참고 서적]

 

 

 지금까지 머신러닝의 학습 방식에 따른 차이인 지도 학습, 비지도 학습, 준지도 학습, 강화 학습에 대해 가볍게 알아보았다. 다음 포스트에서는 모델이 점진적으로 학습을 할 수 있는지 여부에 대하여, 머신러닝을 분류하는 배치 학습과 온라인 학습에 대해 알아보도록 하겠다.

728x90
반응형
728x90
반응형

머신러닝이란?

 앞서 인공지능, 머신러닝, 딥러닝에 대해 간략하게 언급해보았다. 해당 내용만으로는 앞으로 우리가 학습해나갈 머신러닝에 대해 구체적으로 알기 어려우므로, 이번에는 전통적인 알고리즘과 머신러닝 알고리즘을 직접 비교해보도록 하겠다.


명시적인 프로그래밍 없이 컴퓨터가 학습하는 능력을 갖추게 하는 연구 분야
- 아서 새뮤얼(Arthur Samuel), 1959 - 

어떤 작업 T에 대한 컴퓨터 프로그램의 성능을 P로 측정하였을 때, 경험 E로 인해 성능이 향상됐다면, 이 컴퓨터 프로그램은 작업 T와 성능 측정 P에 대해 경험 E로 학습한 것이다.
- 톰 미첼(Tom Mitchell) 1998 - 


 머신러닝(Machine Learning)이라는 용어를 만든 아서 새뮤얼은 머신러닝에 대해 컴퓨터가 학습하는 능력을 갖추게 하는 연구 분야라고 하였다. AI 분야의 권위자인 톰 미첼은 경험 E에 의해 어떤 작업 T의 성능을 측정한 P가 향상되게 하는 것이 머신러닝이라 했다.

 머신러닝은 전통적인 프로그래밍 기법과 달리 데이터만을 가지고 그 안에서 패턴을 찾아낸다는 학습이라는 개념을 사용하여, 사용자가 원하는 목적을 이루어내는 것으로, 이번 포스트에서는 전통적인 프로그래밍 기법과 머신러닝 기법을 두고, 어째서 머신러닝 기법이 그토록 강조되고 있는지에 대해 알아보도록 하겠다.

 

 

 

 

 

0. 예시 및 용어 정리

  • 예시 1. 우편에 쓰인 손글씨 인식 모델
  • 예시 2. 이메일 이용자가 스팸 처리한 스팸 메일과 정상 메일 분류 모델

0.1. 용어 정리

  • 훈련 데이터(Training data): 시스템을 학습시키는 데 사용하는 데이터
  • 훈련 사례(Training instance): 각각의 훈련 데이터셋
  • 정확도(Accuracy): 모델의 성능을 측정하는 도구

0.2. 톰 미첼의 정의와 연결

  • 어떤 작업 T: 어떤 목적을 위해 만들어진 알고리즘
  • 경험 E: 훈련 데이터
  • 성능 측정 P: 알고리즘 작성자가 정의한 모델 성능 평가 도구 - 정확도(Accuracy)

 

 

 

 

 

1. 전통적인 프로그래밍 기법

A. 문제 연구

  • 훈련 데이터(training data)에 대하여 통계 분석 방법, 특정 집단에서 공통적으로 발생하는 패턴을 파악한다.
  • "예시 1. 손글씨"의 경우, 손 글씨를 텐서화하고, 그 텐서에서 각 글자마다 각 Row에서 수치들이 어디서 등장하는지. 수치가 흩어진 정도를 이용하여, 각 글자의 경향성을 찾아냈다.
  • "예시 2. 스팸 메일"의 경우, 사용자가 스팸 메일 처리한 메일들에서 "무료", "기회", "굉장한", "대단한"과 같은 특정 단어가 메일의 제목이나 본문에서 자주 등장하는 경향이 있다.

B. 규칙 작성

  • 문제 연구에서 찾아낸 패턴을 바탕으로, 알고리즘을 만들어낸다.
  • "예시 1. 손글씨"의 경우, 앞에서 찾아낸 각 글자의 경향성을 기반으로, 그 패턴에 속하는 경우 해당 단어로, 속하지 않는 경우, 가장 가까운 패턴에 해당하는 단어를 찾아낸다.
  • "예시 2. 스팸 메일" 스팸 메일에서 주로 발생하는 단어들이 제목이나, 본문에서 등장하는 경우, 이를 스팸 메일로 분류한다.

C. 평가

  • 훈련 데이터를 기반으로 만든 알고리즘에 시험 데이터(Test data)를 넣었을 때, 실제 분류와 어느정도 일치하는 분류가 나오는지 확인한다.

 

1.1. 전통적인 프로그래밍 기법의 한계점

  • 연구자가 찾아낸 패턴은 표면적인 패턴일 가능성이 매우 높으므로, 반례가 존재할 가능성이 매우 높으며, 그 반례의 양도 많아, 이들을 하나하나 해결하다 보면 코드도 길어지고, 복잡해진다.
  • 새로운 패턴을 갖는 데이터 셋이 추가 되면, 기존에 해결했던 반례가 되려 문제를 일으키거나, 새로운 반례가 생기는 등 알고리즘에 단순하게 반영하기가 매우 어렵다.
  • 즉, 유지보수 난이도가 매우 높다(산출물 관리를 아주 아주 잘해야 한다.)

 

 

 

 

 

2. 머신러닝 접근 방법

  • 머신러닝은 데이터를 기반으로 하여, 머신러닝 알고리즘 스스로 패턴을 찾아낸다.
  • 물론, 알고리즘이 그 패턴을 제대로 찾아내게 하기 위해서는 연구자가 패턴이 잘 드러나는 방향으로 전처리를 해줘야 한다.
    (아무리 머신러닝 알고리즘의 성능이 뛰어나다 할지라도, 연구자가 그 알고리즘에 맞게 데이터를 세팅해주지 않는다면, 머신러닝 알고리즘의 성능을 전부 이끌어낼 수 없다.)
  • 머신러닝 알고리즘 자체가 패턴을 찾아내기 때문에 사용자가 패턴을 찾아내는 과정이 따로 필요하지 않으며, 머신러닝 알고리즘은 데이터에서 연구자가 인식하지 못하는 패턴마저도 뽑아내므로, 정확도가 상당히 높다.
  • 그러나, 머신러닝 알고리즘이 분류한 것에 대하여 연구자가 인식하는 것은 불가능에 가깝기 때문에 분류 과정에 대해 설명할 수 없다.

 

2.1. 새로운 패턴이 등장하는 경우

  • "예시 2. 스팸 메일"의 경우, "무료", "기회", "굉장한", "대단한"과 같은 특정 단어가 들어간 메일이 자동 스펨 처리되는 것을 눈치챈 발송자가 해당 단어를 사용하지 않고 "Free", "흔치않은", "오늘만"과 같은 기존 사전에 없던 단어를 이용할 수 있다.
  • 또는, "F.R.E.E", "!기!회!", "!@!기!@! 오.늘!! !@!무.료!@! !@!회!@!" 같이 정규표현식이나, 비교적 단순한 알고리즘으로 잡기 어렵게 패턴에 노이즈를 주어 스펨 메일을 보낼 수도 있다.
  • 머신러닝 알고리즘 기반으로 이를 해결하는 경우엔, 머신러닝 알고리즘 스스로 스펨 메일에 자주 등장하는 패턴을 찾아내 해당 패턴이 등장하는 경우를 스펨 처리하므로, 새로운 규칙을 추가하지 않고 동일한 모델로 패턴을 찾아낼 수 있다.
    (물론, 머신이 노이즈를 잘 인지할 수 있도록, 전처리를 해줘야 한다.)

 

2.2. 전통적인 방식에는 없는 알고리즘이 필요한 경우

  • "예시 1. 손글씨 인식"의 경우, 텐서의 Row 별로 수치가 변하는 위치와 편차를 이용해서 구해보겠다고 하였다. 그러나 해당 방법은 사람마다 손글씨의 차이가 매우 크기 때문에 제대로 작동하기 힘들 가능성이 높다.
  • 머신러닝, 그중에서도 딥러닝의 CNN을 이용한다면, 이미지 데이터의 특징 자체를 찾아낼 수 있다.
  • 즉, 전통적인 알고리즘은 특정 상황에 맞는 특정 알고리즘을 써야 하지만, 머신러닝을 사용하는 경우, 데이터 성격에 맞는 머신러닝 알고리즘을 사용하면, 수치로 표현하기 힘든 특징도 찾아낼 수 있다.

 

2.3. 데이터 마이닝(Data Mining)

  • 연구자는 머신러닝을 통해 그동안 알지 못했던 새로운 사실을 알 수도 있다.
  • 빅데이터를 사용하여 머신러닝 알고리즘을 훈련하는 경우, 기존에 노이즈인지 알고 제거했던 데이터에서 패턴이 존재한다는 것을 알 수 있다.
  • 또는, 어떤 패턴이 분류에서 가장 결정적인 역할을 했는지 찾아낼 수도 있다.

 

2.4. 머신러닝의 장점 정리

  1. 전통적인 프로그래밍 기법보다 머신러닝 모델의 코드가 간단하며, 유지보수가 쉽다.
  2. 전통적인 알고리즘의 반례로 인한 알고리즘 복잡도 증가가 없다.
  3. 전통적인 방식으로 해결할 수 없는 문제도 해결할 수 있다.
  4. 데이터의 변화에 쉽게 적응할 수 있다.
  5. 복잡한 문제와 대량의 데이터를 통해 그간 알지 못했던 아이디어를 얻을 수 있다.

 

 

[참고 서적]

 

 

 지금까지 전통적인 프로그래밍 기법과 머신러닝의 차이점에 대해 알아보았다. 다음 포스트에서는 지도 학습과 비지도 학습, 준지도 학습, 강화 학습에 대해 알아보도록 하겠다.

728x90
반응형
728x90
반응형

대푯값(Representative value)과 비대칭도(Skewness)

 앞서 학습였던, 중앙값(Median), 최빈값(Mode), 평균(Mean) 등과 같은 대푯값과 표준편차(Standard Deviation)를 사용하면, 데이터가 대략 어떻게 생겼는지를 파악할 수 있다.

 이번 포스트에서는 대푯값이 무엇인지, 대푯값을 사용하여 어떻게 데이터의 형태를 파악할 수 있는지에 대해 알아보도록 하자.

 

 

 

 

1. 대푯값(Representative value)

  • 대푯값은 말 그대로 어떠한 데이터를 단 하나의 값으로 표현할 수 있는 값을 말한다.
  • 데이터 어디에 모여있는지(중심경향치): 평균(Mean), 중앙값(Median), 최빈값(Mode), 절사 평균(Trimmed mean)
  • 데이터를 정렬하고 일정 크기로 나눴을 때의 간격 점: 사분위수(Quartile), 백분위수(Percentile)
  • 정상 범위에서 벗어난 값: 이상값(Outlier)
  • 확률상 기대되는 대푯값: 기댓값(Expected value)

 

1.1. 대푯값을 이용하여, 데이터의 대략적인 분포를 알 수 있다.

  • 산포도는 대푯값이 아니지만, 대푯값을 이용해서 산포도를 알 수도 있다.
  • 사분위수를 이용하면, 사분위 범위를 알 수 있고, 사분위 범위를 알면, 상자 수염 그림(Box-and-Whisker plot)을 그릴 수 있다.
  • 평균과 중앙값, 최빈값을 이용하여, 데이터의 대략적인 개형을 알 수 있다.

 

 

 

 

 

2. 대푯값을 이용한 데이터의 대략적인 개형 알아보기

  • 집중화 경향치인 평균, 중앙값, 최빈값은 데이터 분포에 따라 아래와 같은 경향을 갖는다.

  • 위 그림은 조금 과장을 한 것이긴 하지만, 데이터는 위와 같은 경향을 띄며, 그 이유는 다음과 같다.
  1. 최빈값: 가장 많이 등장한 관찰 값이 최빈값이 된다.
  2. 중앙값: 관찰 값의 빈도에 상관없이 무조건 가운데에 위치한다.
  3. 평균: 이상치의 영향을 크게 받는다.
  • 간단한 도수분포표를 만들어서, 실제로는 어떻게 돌아가는지 확인해보자.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 부드러운 그래프를 그린다.
from scipy.interpolate import make_interp_spline

plt.rc('font', family='NanumGothic')
# 최빈값, 중앙값, 평균 계산
def Rep_V(data, column):

    raw_list = []

    for i in range(len(data)):

        X_list = [data.loc[i, "X"]] * data.loc[i, column]
        raw_list = raw_list + X_list

    median = np.median(raw_list)
    mean = np.mean(raw_list)
    std = np.std(raw_list)
    mode = np.bincount(raw_list).argmax()

    return mode, median, mean, std



# 그래프 그리기
def draw_Ske(data, column, title):
    """
    도수분포다각형을 부드럽게 그리고, 최빈값, 중앙값, 평균을 점으로 찍고, 선을 그린다.
    """
    x = data["X"].to_numpy()
    y = data[column].to_numpy()

    fig = plt.figure(figsize=(10, 6))

    # 부드러운 그래프를 그린다.
    model=make_interp_spline(x,y)
    xs=np.linspace(np.min(index_list),np.max(index_list),100)
    ys=model(xs)

    plt.plot(xs, ys)

    # 최빈값, 중앙값, 평균 그리기
    mode, median, mean, std = Rep_V(data, column)
    plt.scatter(mode, 1, c = "r", s = 100)
    plt.axvline(x=mode, c = "r", linewidth = 1, alpha = 0.5)
    plt.annotate('최빈값', xy=(mode+1, 1), xytext=(mode + 10, 0.5), fontsize = 15,
                 arrowprops=dict(facecolor='black', width=1, headwidth=8))

    plt.scatter(median, 3, c = "g", s = 100)
    plt.axvline(x=median, c = "g", linewidth = 1, alpha = 0.5)
    plt.annotate('중앙값', xy=(median+1, 3), xytext=(median + 10, 3.2), fontsize = 15,
                 arrowprops=dict(facecolor='black', width=1, headwidth=8))

    plt.scatter(mean, 5, c = "y", s = 100)
    plt.axvline(x=mean, c = "y", linewidth = 1, alpha = 0.5)
    plt.annotate('평균', xy=(mean+1, 5), xytext=(mean + 10, 6), fontsize = 15,
                 arrowprops=dict(facecolor='black', width=1, headwidth=8))



    plt.title(title, fontsize = 20, pad = 15)
    plt.xlabel("$X$", fontsize = 15, labelpad = 10)
    plt.ylabel("$f$", rotation = 0, fontsize = 15, labelpad = 10)
    plt.show()
  • 함수 Rep_V(data, column)는 도수분포표를 본래 데이터 셋으로 되돌리고, 최빈값, 중앙값, 평균과 같은 대푯값을 계산하는 함수다.
  • 데이터의 양이 지나치게 많아, 컴퓨터가 이를 감당하지 못하는 경우에는 의도적으로 도수분포표를 만들어 대푯값을 계산할 수도 있으나, 위와 같이 데이터의 양이 적은 경우, 도수분포표를 본래 데이터 셋으로 되돌리는 것이 더 쉽다.
index_list = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
freq1_list = [1, 3, 6, 11, 17, 24, 17, 11, 6, 3, 1]
freq2_list = [1, 2, 3, 6, 10, 14, 20, 25, 15, 3, 1]
freq3_list = [1, 3, 15, 25, 20, 14, 10, 6, 3, 2, 1]

DF = pd.DataFrame({"X":index_list, "f1":freq1_list, "f2":freq2_list, "f3":freq3_list})
DF

2.1. 대칭 분포

draw_Ske(DF, "f1", "1. 대칭분포")

  • 대칭 분포인 경우, "평균 = 중앙값 = 최빈값"인 것을 볼 수 있다.

2.2. 왼쪽 꼬리 분포

draw_Ske(DF, "f2", "2. 왼쪽꼬리분포")

  • 왼쪽 꼬리 분포인 경우, "평균 < 중앙값 < 최빈값"이 되는 것을 볼 수 있다.

2.3. 오른쪽 꼬리 분포

  • 오른 꼬리 분포인 경우, "평균 > 중앙값 > 최빈값"이 되는 것을 볼 수 있다.

 

 

 

 

 

3. 피어슨의 비대칭 계수(Pearson's skewness coefficients)

 칼 피어슨(Karl Pearson)이 만든, 피어슨의 비대칭 계수는, 위에서 본 분포가 좌, 우로 치우치는 경우 평균, 중앙값, 최빈값이 일정한 크기로 순서를 갖는 성질을 이용해서 만든 것이다.

  • 피어슨의 비대칭 계수는 평균에 대하여, 최빈값이나 중앙값을 뺀 값을 표준 편차로 나눠서 구한다.
  • 피어슨의 비대칭 계수를 구하는 방법은 크게 3가지가 있다.

◎ 피어슨의 제1 비대칭도 계수: $SK_1 = \frac{\bar{X} - mode}{S}$

◎ 피어슨의 제2 비대칭도 계수: $SK_2 = \frac{3(\bar{X} - mode)}{S}$

◎ 피어슨의 제3 비대칭도 계수: $SK_3 = \frac{3(\bar{X} - median)}{S}$


  • 위 비대칭도 계수들은 평균과 다른 대푯값의 거리를 얼마나 민감하게 볼 것인가에 대하여 다르게 만들어진 것임을 볼 수 있다.
  • 일반적으로 사용하는 것은 피어슨의 제3 비대칭도 계수다.
  • 단순한 해석 방법은 3가지 모두 동일하다.

 

3.1. 피어슨의 비대칭 계수 보는 방법

  • 비대칭도 계수가 0이 나오는 경우, 평균과 중앙값(최빈값)이 같으므로, 분포가 치우쳐지지 않음을 알 수 있다.
  • 비대칭도 계수가 양수인 경우, 평균보다 중앙값이 작으므로, 오른쪽 꼬리 분포이다.
  • 비대칭도 계수가 음수인 경우, 평균보다 중앙값이 크므로, 왼쪽 꼬리 분포이다.
  • 비대칭도 계수가 클수록, 한쪽으로 치우친 정도가 크다는 소리다.
  • 비대칭도 계수가 0~1 사이인 경우, 1 표준편차보다 분자(평균과 대푯값의 거리에 3을 곱한 값)가 작다는 의미이므로, 치우친 정도가 작다는 소리다.
  • 이를 이용하여, 서로 다른 분포에 대하여, 치우친 정도를 비교할 수도 있다(표준편차로 나누었으므로, 단위도 사라진다.).

 

3.2. 파이썬으로 피어슨의 비대칭 계수를 구현해보자.

  • 기존 도수분포표에 보다 치우친 새로운 도수를 추가하여 비교해보자.
index_list = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
freq1_list = [1, 3, 6, 11, 17, 24, 17, 11, 6, 3, 1]
freq2_list = [1, 2, 3, 6, 10, 14, 20, 25, 15, 3, 1]
freq3_list = [1, 3, 15, 25, 20, 14, 10, 6, 3, 2, 1]
freq4_list = [1, 1, 2, 3, 5, 7, 10, 16, 23, 26, 6]

DF = pd.DataFrame({"X":index_list, "f1":freq1_list, "f2":freq2_list, "f3":freq3_list, "f4":freq4_list})
DF

  • 피어슨의 비대칭도 계수를 구하는 함수를 만들고, 각 변수의 도수들을 비교해보자.
  • 각 칼럼의 최빈값, 중앙값, 평균, 표준편차를 출력해보자.
>>> for column in ["f1", "f2", "f3", "f4"]:

>>>     mode, median, mean, std = Rep_V(DF, column)
>>>     print(f"column: {column} \n 최빈값:{mode}, 중앙값: {median}, 평균: {mean}, 표준편차: {np.round(std,2)}")
>>>     print("----"*20)
    
    
column: f1 
 최빈값:50, 중앙값: 50.0, 평균: 50.0, 표준편차: 19.39
--------------------------------------------------------------------------------
column: f2 
 최빈값:70, 중앙값: 60.0, 평균: 58.8, 표준편차: 19.2
--------------------------------------------------------------------------------
column: f3 
 최빈값:30, 중앙값: 40.0, 평균: 41.2, 표준편차: 19.2
--------------------------------------------------------------------------------
column: f4 
 최빈값:90, 중앙값: 80.0, 평균: 71.9, 표준편차: 21.06
--------------------------------------------------------------------------------

 

  • 1번부터 3번까지 피어슨의 비대칭 계수를 계산하는 함수를 만들고, 계수를 계산해보자.
def P_SK(data, column, key):
    
    mode, median, mean, std = Rep_V(DF, column)
    
    if key == 1:
        result = (mean - mode)/std
        
    elif key == 2:
        result = 3*(mean - mode)/std
        
    elif key == 3:
        result = 3*(mean - median)/std
        
    return result
>>> for column in ["f1", "f2", "f3", "f4"]:
    
>>>     print(f"column: {column}")
>>>     for key in range(1,4):
        
>>>         P_SK_value = P_SK(DF, column, key)
>>>         print(f"피어슨의 제 {key} 비대칭도 계수 = {np.round(P_SK_value, 4)}")
        
>>>     print("----"*20)


column: f1
피어슨의 제 1 비대칭도 계수 = 0.0
피어슨의 제 2 비대칭도 계수 = 0.0
피어슨의 제 3 비대칭도 계수 = 0.0
--------------------------------------------------------------------------------
column: f2
피어슨의 제 1 비대칭도 계수 = -0.5834
피어슨의 제 2 비대칭도 계수 = -1.7502
피어슨의 제 3 비대칭도 계수 = -0.1875
--------------------------------------------------------------------------------
column: f3
피어슨의 제 1 비대칭도 계수 = 0.5834
피어슨의 제 2 비대칭도 계수 = 1.7502
피어슨의 제 3 비대칭도 계수 = 0.1875
--------------------------------------------------------------------------------
column: f4
피어슨의 제 1 비대칭도 계수 = -0.8596
피어슨의 제 2 비대칭도 계수 = -2.5787
피어슨의 제 3 비대칭도 계수 = -1.154
--------------------------------------------------------------------------------
  • 대칭 분포인 f1은 비대칭도 계수가 0이 나왔다.
  • 왼쪽 꼬리 분포인 f2는 비대칭도 계수가 모두 음수가 나왔다.
  • 오른쪽 꼬리 분포인 f3는 비대칭도 계수가 모두 양수가 나왔다.
  • 새로 만든 f2의 비대칭도 계수는 모두 음수가 나왔다.
  • 수치가 제법 다르므로, f2와 f4를 시각화하여, 직접 비교해보자.

 

3.3. 시각화하여 비대칭도 비교

  • 위에서 만든 도수분포와 최빈값, 중앙값, 평균을 그려주는 함수인 draw_Ske를 약간 변형하여, 4개 칼럼에 대해 subplot으로 그려보도록 하자.
data = DF
x = data["X"].to_numpy()
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(16,12))
ax = axes.ravel()
column_list = ["f1", "f2", "f3", "f4"]

for i, column in enumerate(column_list):
    

    y = data[column].to_numpy()
    
    # 부드러운 그래프를 그린다.
    model=make_interp_spline(x,y)
    xs=np.linspace(np.min(index_list),np.max(index_list),100)
    ys=model(xs)
    
    ax[i].plot(xs, ys)
    title = column_list[i] + "의 분포"
    ax[i].set_title(title, fontsize=20, fontweight="bold")
    
    
    # 최빈값, 중앙값, 평균 그리기
    mode, median, mean, std = Rep_V(data, column)
    ax[i].scatter(mode, 1, c = "r", s = 100)
    ax[i].axvline(x=mode, c = "r", linewidth = 1, alpha = 0.5)
    ax[i].annotate('최빈값', xy=(mode+1, 1), xytext=(mode + 10, 0.5), fontsize = 15,
                 arrowprops=dict(facecolor='black', width=1, headwidth=8))

    ax[i].scatter(median, 3, c = "g", s = 100)
    ax[i].axvline(x=median, c = "g", linewidth = 1, alpha = 0.5)
    ax[i].annotate('중앙값', xy=(median+1, 3), xytext=(median + 10, 3.2), fontsize = 15,
                 arrowprops=dict(facecolor='black', width=1, headwidth=8))

    ax[i].scatter(mean, 5, c = "y", s = 100)
    ax[i].axvline(x=mean, c = "y", linewidth = 1, alpha = 0.5)
    ax[i].annotate('평균', xy=(mean+1, 5), xytext=(mean + 10, 6), fontsize = 15,
                 arrowprops=dict(facecolor='black', width=1, headwidth=8))
    
plt.tight_layout()

  • 위에서 만들었던, 피어슨의 3 비대칭도 계수로 f2와 f4를 비교해보자.
# 치우침이 비교적 작은 f2의 피어슨 비대칭도 계수
>>> P_SK(DF, "f2", 3)
-0.18752034836405185

# 치우침이 비교적 큰 f4의 피어슨 비대칭도 계수
>>> P_SK(DF, "f4", 3)
-1.154019976254971
  • f2와 f4의 피어슨 비대칭도 계수를 절댓값으로 비교해보면, f4가 더 큰 것을 알 수 있다.
  • 위 결과는 평균과 중앙값의 거리가 f2보다 f4가 더 멀다는 것을 의미하며, 이는 f4가 f2보다 한쪽에 더 치우쳐져 있음을 보여준다.
  • 위 그래프를 보면 알 수 있듯, f2의 분포보다 f4의 분포가 오른쪽으로 더 데이터가 몰려있음을 알 수 있다.

 

 

 

 

 

4. 왜도(Skewness)

  • 왜도는 위에서 본 피어슨의 비대칭 계수(Pearson's skewness coefficients)와 같은 분포가 어디에 치우쳐져 있는 지를 보는 보다 더 대표적인 방법이다.
  • 사실 왜도를 보다 간략화시킨 것이 피어슨의 비대칭 계수로, 왜도를 보는 방법은 피어슨의 비대칭 계수와 동일하다.
  • 음의 왜도(Negative skewness): 왜도에서는 왼쪽에 긴 꼬리를 갖는 왼쪽 꼬리 분포를, 음의 왜도나 좌비대칭형(Skewed left)이라 부른다.
  • 양의 왜도(positive skewness): 왜도에서는 오른쪽에 긴 꼬리를 갖는 오른쪽 꼬리 분포를, 양의 왜도나 우비대칭형(Skewed right)이라 부른다.
  • 왜도는 각 관찰 값의 편차에 표준편차를 나눈 값에 3 제곱을 하여 합친 값이다.
  • 왜도는 모집단의 왜도와 표본 왜도를 구하는 공식이 약간 다르다.

◎ 표본 왜도

$$b_1 = \frac{m_3}{S^3} = \frac{\frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^3}{[\frac{1}{n-1}\sum_{i=1}^{n}(x_i - \bar{x})^2]^{\frac{3}{2}}} = \frac{\sum_{i=1}^{n}(x_i - \bar{x})^3}{nS^3}$$

◎ 모집단의 왜도

$$G_1 = \frac{n^2}{(n-1)(n-2)}b_1 = \frac{n}{(n-1)(n-2)}\sum_{i=1}^{n}(\frac{x_i - \bar{x}}{S})^3$$


  • 모집단의 평균, 표준편차 등을 알 수 없기 때문에 표본집단을 이용하여, 모집단의 왜도를 추정한다.

 

4.1. 파이썬을 사용하여 왜도를 구해보자.

  • 위에서 만든 DF에 대하여 왜도를 구해보도록 하자.
  • 모집단의 왜도를 구하도록 해보자.
# 도수분포표의 도수를 list로 변환한다.
def freqT2list(data, column):
    
    result = []

    for i in range(len(data)):

        X_list = [data.loc[i, "X"]] * data.loc[i, column]
        result = result + X_list
        
    return result
    
    
# 왜도 계산
def skewness(data, column):
    
    target = np.array(freqT2list(data, column))

    n = len(target)
    mean = np.mean(target)
    std = np.std(target, ddof=1)

    return n/((n-1)*(n-2))*np.sum(((target - mean)/std)**3)
>>> for column in ["f1", "f2", "f3", "f4"]:
    
>>>     skew = skewness(DF, column)
>>>     print(f"column:{column}, 왜도: {skew}")


column:f1, 왜도: 1.0985509210885902e-16
column:f2, 왜도: -0.704757133477093
column:f3, 왜도: 0.7047571334770931
column:f4, 왜도: -1.1553356711140326
  • 대칭 분포인 f1의 왜도는 1.0985509210885902e-16로 나왔다.
  • 이는 0.0000000000000001098으로, 소수점 아래 16번째 자리에서 처음으로 e 앞에있는 숫자가 나온다는 의미로, 매우 작은 수로, 파이썬의 계산 방식 등으로 인해 이런 문제가 발생할 수 있으니, 깔끔하게 소숫점 아래 6자리에서 반올림을 해서 다시 보자.
>>> for column in ["f1", "f2", "f3", "f4"]:
    
>>>     skew = np.round(skewness(DF, column), 6)
>>>     print(f"column:{column}, 왜도: {skew}")


column:f1, 왜도: 0.0
column:f2, 왜도: -0.704757
column:f3, 왜도: 0.704757
column:f4, 왜도: -1.155336
  • 왜도 역시, 피어슨의 비대칭 계수처럼 음과 양으로 데이터가 어디에 모여있는지 알 수 있다. 간단하게 음수면 긴 꼬리가 음수의 방향, 양수면 긴 꼬리가 양수의 방향에 있다고 생각하자.
  • 왜도가 커질수록 분포가 크게 치우친 것이다.

 

4.2. Scipy로 왜도를 구해보자

  • scipy의 stats 모듈에 있는 skew 함수를 사용해서 더 쉽게 왜도를 구할 수 있다.
from scipy import stats
>>> for column in ["f1", "f2", "f3", "f4"]:
    
>>>     target = np.array(freqT2list(DF, column))
>>>     skew = stats.skew(target)
>>>     print(f"column:{column}, 왜도: {skew}")

column:f1, 왜도: 0.0
column:f2, 왜도: -0.6941414183237987
column:f3, 왜도: 0.694141418323799
column:f4, 왜도: -1.137932918011734
  • scipy.stats.skew(array): 왜도를 구한다.
  • scipy로 구한 왜도와 직접 만든 공식과 그 값이 꽤 다른 것을 알 수 있다.
  • 이는, scipy의 왜도는 표본 왜도를 구하며, 사용하는 공식이 약간 다르므로, 표준편차를 모 표준편차로 구했기 때문이다.
  • 이에 맞게 공식을 고쳐서 출력해보자.
# 왜도 계산
def skewness(data, column):
    
    target = np.array(freqT2list(data, column))

    n = len(target)
    mean = np.mean(target)
    std = np.std(target, ddof=0)

    return 1/n*np.sum(((target - mean)/std)**3)
>>> for column in ["f1", "f2", "f3", "f4"]:
    
>>>     skew = np.round(skewness(DF, column), 6)
>>>     print(f"column:{column}, 왜도: {skew}")


column:f1, 왜도: 0.0
column:f2, 왜도: -0.694141
column:f3, 왜도: 0.694141
column:f4, 왜도: -1.137933
  • 왜도가 돌아가는 방식에 대해 알 필요는 있으나, 개인적으로는 이 부분을 심화시키는 것은 시간낭비라고 판단된다.
  • 왜도는 분포의 형태를 대략적으로 아는 도구이므로, 보다 정확한 도구를 사용하는 것보단, 동일한 도구를 이용해서 평가하는 것이 중요하다고 판단되므로, scipy.stats의 skew함수를 사용하도록 하자.

 

 

 

 

 

5. 첨도(Kurtosis)

  • 왜도가 데이터가 어느 쪽에 쏠려 있는지를 보는 용도라면, 첨도는 데이터가 얼마나 모여있는지를 가리킨다.
  • 첨도가 클수록 데이터는 한 곳에 매우 많이 모여 있고, 첨도가 작을수록 데이터가 흩어져 있다.
  • 첨도 = 0: 표준 정규분포다.
  • 첨도 > 0: 표준 정규분포보다 평균에 데이터가 더 많이 모여 있는 뾰족한 형태다.
  • 첨도 < 0: 표준 정규분포보다 평균에서 데이터가 더 흩어진 완만한 형태다.
  • 첨도가 크다할지라도, 분산이 더 큰 경우, 완만한 그래프가 나타날 수 있으므로, 분산이 동일한 분포끼리 비교해야 한다.
  • 첨도는 각 관찰 값과 표본 평균의 편차에 표본 표준편차를 나눈 값에 4 제곱을 해서 구한다.
  • 첨도의 기준은 정규분포가 되며, 정규분포의 첨도는 3이지만, 이를 보기 쉽도록, 3을 빼서, 0으로 보정해준다.

◎ 표본 첨도

$$g_2 = \frac{m_4}{m_{2}^2} - 3 = \frac{\frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^4}{[\frac{1}{n}\sum_{i=1}^{n}(x_i - \bar{x})^2]^2} - 3 = \frac{n}{(n-1)^2}\sum_{i=1}^{n}(\frac{x_i-\bar{x}}{S})^4 - 3$$

◎ 모집단의 첨도(불편추정량)

$$G_2 = \frac{n-1}{(n-2)(n-3)}[(n+1)g_2 + 6]) = \frac{n(n+1)}{(n-1)(n-2)(n-3)}\sum_{i=1}^{n}(\frac{x_i - \bar{x}}{S})^4 - 3\frac{(n-1)^2}{(n-2)(n-3)}$$


 

5.1. 파이썬으로 첨도를 구현해보자.

  • 넘파이를 이용하여, 평균은 0이고, 표준편차 1인 표준 정규분포를 만들어보자.
np.random.seed(1234)
Normal_Dist = np.random.normal(loc = 0, scale = 1, size = 10000)
plt.rc('font', family='monospace')
  • 위에서 한글을 사용하기 위해 폰트를 나눔 고딕으로 설정하는 경우 음수의 "-" 부호가 깨지게 되므로, 폰트를 바꿔주었다.
fig = plt.figure(figsize=(10, 6))

plt.hist(Normal_Dist, bins = 60, density=True)
plt.show()

 

5.2. 첨도 함수를 만들어보자.

5.2.1. Numpy 라이브러리의 함수를 사용해서 만들어보자.

# 첨도
def Kurt(array):
    
    mean = np.mean(array)
    std = np.std(array, ddof = 1)
    n = len(array)

    g2 = n/(n-1)**2*np.sum(((array - mean)/std)**4) - 3

    G2 = ((n-1)/((n-2)*(n-3)))*((n+1)*g2 + 6)
    
    return G2
>>> Kurt(Normal_Dist)
-0.06547331601746852

 

5.2.2. scipy.stats 모듈의 kurtosis() 함수를 사용해서 만들어보자.

from scipy import stats
>>> stats.kurtosis(Normal_Dist)
-0.06604052394826398
  • scipy와 numpy로 직접 만든 첨도의 결과가 약간 다른 것을 알 수 있다.
  • 이는, scipy에서는 Kurt함수의 G2가 아닌 g2로 계산되기 때문으로, 즉 모집단의 첨도가 아닌 표본 첨도가 계산된다는 소리다.
def Kurt_g2(array):
    
    mean = np.mean(array)
    std = np.std(array, ddof = 1)
    n = len(array)

    g2 = n/(n-1)**2*np.sum(((array - mean)/std)**4) - 3
    
    return g2
>>> Kurt_g2(Normal_Dist)
-0.06604052394826398

>>> stats.kurtosis(Normal_Dist) == Kurt_g2(Normal_Dist)
True

 

5.3. 무작위로 생성한 표준정규분포의 난수를 더 표준정규분포에 가깝게 해 보자.

  • 위에서 만든 표준정규분포의 난수는 크기가 10,000으로, 실제 표준정규분포와 거리가 있다.
  • 이번에는 난수의 크기를 1,000,000으로 해서 실제 표준정규분포에 더 가깝게 만들어보자.
    (데이터의 양이 엄청나기 때문에 컴퓨터에 부담이 가는 경우 수를 조금 줄이자)
np.random.seed(1234)
Normal_Dist = np.random.normal(loc = 0, scale = 1, size = 1000000)
fig = plt.figure(figsize=(10, 6))

plt.hist(Normal_Dist, bins = 60, density=True)
plt.show()

>>> Kurt_g2(Normal_Dist)
0.0006377812832605301
  • 난수의 크기가 10,000일 때보다 표준정규분포에 근사하게 되니 첨도가 0에 더 가까워지는 것을 볼 수 있다.
  • 그러나, 난수를 이용해서인지 크기를 늘린다고 해서 선형으로 0에 더 가깝게 가지는 않는다.

 

5.4. 표준 편차가 1로 동일하지만, 크기가 매우 작아 표준 정규분포와 거리가 먼 난수를 생성해보자.

np.random.seed(1234)
Normal_Dist = np.random.normal(loc = 0, scale = 1, size = 10)
fig = plt.figure(figsize=(10, 6))

plt.hist(Normal_Dist, density=True)
plt.show()

>>> Kurt_g2(Normal_Dist)
-0.6112182077094723
  • 이번에 생성한 데이터는 평균이 1, 표준편차가 1로 이전의 난수와 동일하지만, 크기가 10으로 매우 작아, 분산만 동일하지 정규분포로 보기 힘든 데이터를 생성해보았다.
  • 이 경우, 첨도는 -0.611로 기존에 생성한 분포보다 더 완만하다는 것을 알 수 있다.

 

5.5. 표준편차가 다른 분포들을 만들고, 첨도를 비교해보자.

np.random.seed(1234)
Normal_Dist0 = np.random.normal(loc = 0, scale = 0.4, size = 100000)
Normal_Dist1 = np.random.normal(loc = 0, scale = 1, size = 100000)
Normal_Dist2 = np.random.normal(loc = 0, scale = 2, size = 100000)
fig = plt.figure(figsize=(10, 6))

plt.hist(Normal_Dist0, density=True, bins=60, alpha=0.5, label="N(0, 0.5)")
plt.hist(Normal_Dist1, density=True, bins=60, alpha=0.5, label="N(0, 1)")
plt.hist(Normal_Dist2, density=True, bins=60, alpha=0.5, label="N(0, 2)")
plt.legend(loc="upper right")
plt.show()

>>> Ku0 = Kurt_g2(Normal_Dist0)
>>> Ku1 = Kurt_g2(Normal_Dist1)
>>> Ku2 = Kurt_g2(Normal_Dist2)

>>> print(f"표준편차가 0.5인 분포의 첨도:{Ku0}")
>>> print(f"표준편차가 1인 분포의 첨도:{Ku1}")
>>> print(f"표준편차가 2인 분포의 첨도:{Ku2}")

표준편차가 0.5인 분포의 첨도:-0.030292593794574607
표준편차가 1인 분포의 첨도:-0.00581814290437066
표준편차가 2인 분포의 첨도:0.01995196078021788
  • 위 분포만 본다면, 표준 정규분포에 가장 가까운 노란색보다 더 뾰족하게 솟은 파란색 분포가 값이 더 커야 할 것으로 보이지만, 보다 음수에 가까운 것을 알 수 있다.
  • 이는 첨도와 표준편차는 별개로 움직이기 때문으로, 위와 같이 표준편차가 다른 경우, 그래프의 개형이 보다 더 뾰족하거나 완만하다 할지라도, 첨도가 별개로 작동하여, 잘못된 판단을 할 위험이 있다.
  • 즉, 첨도는 분산이 동일한 상태에서 사용해야, 데이터가 평균에 모인 정도에 대해 제대로 된 비교를 할 수 있다.
728x90
반응형
728x90
반응형

편차(Deviation)

 이전 포스트에서는 관측값과 평균의 차이인 편차를 이용해서 구하는 대표적인 산포도 "분산(Varience)"와 "표준편차(Standard Deviation)에 대해 학습해보았다.

 이번 포스트에서는 편차를 이용하는 다른 산포도인 절대 편차(Absolute deviation)와 변동 계수(Coefficient of variation)에 대해 알아보자.

 

 

 

 

 

1. 절대 편차(Absolute deviation)

  • 절대 편차는 관측값에서 평균(Mean) 또는 중앙값(Median)을 빼고, 그 편차에 절댓값을 취하고, 평균을 구한 것으로, 분산에서 제곱 대신 절댓값을 취한 것이다.

$$ AAD = \frac{1}{N}\sum_{i=1}^{N}\left | x_i - \mu \right | = \frac{\left | x_1 - \mu \right | + \left | x_2 - \mu \right | + \cdots + \left | x_N - \mu \right |}{N} $$


  • 평균 절대 편차(Average Absolute Deviation, AAD): 각 관측값에 평균을 빼고, 편차 절댓값의 평균을 구하는 경우로, 평균 편차(Mean Deviation), 절대 편차(Absolute Deviation)라고도 한다.
  • 중앙값 절대 편차(Median Absolute Deviation, MAD): 각 관측값에 중앙값을 빼고, 편차 절댓값의 평균을 구하는 방식이다.
  • 최소 절대 편차(Least Absolute Deviation, LAD): 회귀분석에서 최소제곱법(LSA)에 대응되는 기법으로, 절대 오차의 합이 최소가 되는 해를 찾는 기법이다.
  • 분산은 이상치가 존재하는 경우, 편차를 제곱시키므로 편차가 증폭될 수 있다. 절대 편차를 하는 경우, 제곱을 하지 않으므로, 이상치 존재로 인한 편차 증폭 문제를 보완할 수 있다.
  • 예) 평균 $100$, 이상치가 $10000$인 경우, $9,900^2 = 9,8010,000$이 되어 값이 매우 커지게 된다.

 

1.1. 파이썬과 절대 편차

Data_for_study.csv
3.39MB

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

 

1.1.1. numpy를 사용하여 절대 편차(AD) 만들기

  • 데이터 분석에 주로 사용되는 라이브러리인 numpy나 pandas는 따로 해당 함수를 제공하지 않는다.
  • 그러나, numpy를 사용해서 만들면, 난이도도 쉽고, numpy 기본 함수의 속도가 매우 빨라, 성능도 나쁘지 않다.
def AD(data, column, way, dof = 1):
    
    target_array = data[column].to_numpy()
    
    if way == "mean":
        key_value = np.mean(target_array)
        
    elif way == "median":
        key_value = np.median(target_array)
        
    return np.sum(np.abs(target_array - key_value))/(len(target_array) - dof)
  • way로 "mean"이 들어오는지, "median"이 들어오는지에 따라 평균 절대 편차나 중앙값 절대 편차가 수행된다.
  • np.abs(array): array에 있는 모든 원소를 양수로 만든다.
  • 표본 집단에 대한 분석을 가정하여, 자유도로 나눈다.
>>> AD(Rawdata, "키", "mean")
7.0767022252239355

>>> AD(Rawdata, "키", "median")
7.046750497784634

>>> AD(Rawdata, "몸무게", "mean")
10.159191145859118

>>> AD(Rawdata, "몸무게", "median")
10.00470697974779

 

 

 

 

 

2. 절대편차(AAD)가 아닌 표준편차(Standard Deviation)를 사용하는 이유

  • 절대편차를 사용하지 않는 이유는 총 4가지가 있다.
  1. 편차란 유클리디안 거리(Euclidean distance)에서, 각 관측값이 특정값(평균)으로부터 떨어져 있는 거리를 의미하기 때문이다.
  2. 절대편차의 최솟값은 평균이 아닌 중앙값이다.
  3. 절대편차는 미분되지 않는다.
  4. 절댓값을 연산하기 위해선 공식이 복잡해진다.

 

2.1. 편차와 유클리디안 거리(Euclidian distance)

  • 절대편차를 사용하지 않는 이유를 알기 위해선, 편차의 의미에 대해 알 필요가 있다.
  • "편차는, 관측값이 특정값(평균)으로부터 떨어진 거리"인데, 이를 구체적으로 보기 위해 고등학교 때 배웠던, 두점 사이의 거리 공식을 보도록 하자.

  • 위 두 점 사이의 거리는 2차원에서의 거리다. 이번에는 n차원의 공간에서 두 점간의 거리를 알아내는 공식인 유클리디안 거리(Euclidan distance)에 대해 알아보자. 위 2차원에서의 거리 공식과 개념이 매우 유사하다.

  • 위 유클리디안 거리에서 점 $B_i$를 평균인 $\mu$로 만든다면, 우리가 아는 분산(Variance) 공식이 나온다.
  • 그러나, 위 유클리디안 거리는 n차원에서의 두 점 사이의 거리를 의미한다. 때문에 1차원에서라면, 절대 평균 편차 공식을 사용해도 되지 않는가 하는 의문이 들 수 있다.

 

2.2. 절대 편차의 최솟값은 평균이 아닌 중앙값이다.

  • "편차는 관측값들이 특정값으로부터 떨어진 정도(거리)"라고 하였다.
  • 이 특정값을 우리는 대푯값(Representative value)라고 하며, 대푯값은 어떤 데이터를 대표하는 값이다.
  • 이번에는 관측값의 집합$X = (1, 2, 5)$가 있다고 가정하고, 이것을 분산으로 구할 때와, 절대편차로 구할 때에 따라, 임의의 대푯값 $R$이 분산($\sigma^2$)과 절대편차에 대하여 어떠한 함수의 형태를 그리는지 보도록 하자.
# 분산인 경우
def f(x):
    
    return((1-x)**2 + (2-x)**2 + (5-x)**2)/3
    
 
 
# 절대 편차인 경우
def h(x):
    
    return(np.abs(1-x) + np.abs(2-x) + np.abs(5-x))/3
def draw_Function(f, key):

    # 대푯값에 대한 시각화를 해보자.
    R = np.arange(0, 10, 0.001)
    y = f(R)

    # 최솟값을 구한다.
    min_y = y.min()
    min_x = R[np.where(y == min_y)][0]
    
    # 그래프 그리기
    plt.rc('font', family='NanumGothic')
    fig = plt.figure(figsize=(8, 6))

    # 함수 f의 시각화
    plt.plot(R, y)

    # y 값에서의 최솟값
    plt.scatter(min_x, min_y, color="r",s = 100)

    # 그래프 꾸며주기
    plt.title(f"{key}에 대한 대푯값", fontsize = 20, pad = 30)
    plt.xlabel("대푯값", fontsize = 15, labelpad = 10)
    plt.ylabel(f"{key}", rotation = 0, fontsize = 15, labelpad = 20)


    plt.show()
    
    
    print(f"({np.round(min_x, 3)}, {np.round(min_y, 3)})에서 함수 f(x)는 최솟값을 갖는다.")

 

2.2.1. 분산에 대한 대푯값의 그래프

>>> draw_Function(f, "분산")
(2.667, 2.889)에서 함수 f(x)는 최솟값을 갖는다.

  • 대푯값 R에 대한 분산($\sigma^2$)의 그래프를 그렸을 때, 이차 함수 형태를 그리는 것을 볼 수 있다.
  • 분산의 최솟값은 2.889이며, 이는 대푯값(R) 2.667이다.
  • 관측값 X = [1, 2, 5]의 평균은 다음과 같다.
>>> np.mean([1,2,5])
2.6666666666666665

 

  • 관측값의 평균과 분산이 최솟값을 갖는 대푯값은 일치한다.
    (약간의 차이가 있는 이유는, 그래프 생성 시, np.arange(start, end, by)에 대하여, by의 간격이 0.001로 이산된 부분이 생겼기 때문이다. by를 작게 할 수도록 평균과 근사해진다.)
  • 즉, 분산에 있어 대푯값은 평균이다.

 

2.2.2. 절대편차에 대한 대푯값의 그래프

>>> draw_Function(h, "절대편차")
(2.0, 1.333)에서 함수 f(x)는 최솟값을 갖는다.

  • 절대편차의 개형은 꺾은선 그래프와 같은 형태를 그린다.
  • 절대편차에서의 최솟값은 대푯값 2.0에서 존재한다.
  • 대푯값 2.0은 관측값의 집합 X=[1,2,5]에서 중앙값(Median)인 2이다.

 

2.2.3. 절대편차에 대한 대푯값의 그래프에서 집합의 원소가 짝수일 때

  • X = [1, 2, 5, 7]이 관측값의 집합이라 가정해보자.
def h1(x):
    
    return(np.abs(1-x) + np.abs(2-x) + np.abs(5-x) + np.abs(7-x))/4
>>> draw_Function(h1, "절대편차")
(2.0, 2.25)에서 함수 f(x)는 최솟값을 갖는다.

  • 집합의 원소가 짝수인 경우, 그래프의 개형에서 아래쪽이 평평해진다.
  • 대푯값 R이 한 점이 아닌 선이 되었으므로, 선에 대한 대푯값으로 중앙값을 구해보자.
>>> R = np.arange(0, 10, 0.001)
>>> y = h1(R)

>>> min_y = y.min()
>>> min_x = R[np.where(y == min_y)]

>>> print("절대편차에 대한 대푯값의 중앙값:", str(np.median(min_x)))
절대편차에 대한 대푯값의 중앙값: 3.5

>>> print("집합 X의 중앙값:", str(np.median([1,2,5,7])))
집합 X의 중앙값: 3.5
  • 집합의 개수가 짝수일 때도, 대푯값이 중앙값인 것을 알 수 있다.

 

2.2.4. 정리

  • 위 결과를 볼 때, 절대 편차는 관측값으로부터 중앙값까지의 거리의 평균인 것임을 알 수 있다.
  • 즉, 차원이 1개라고 가정한다 할지라도, 절대 편차의 대푯값은 중앙값이므로, 분산처럼 평균에서 각 원소까지의 거리 제곱의 평균을 구할 수 있는 것이 아니라, 중앙값에서 각 원소까지의 거리의 절댓값의 평균이 되게 되어, 의미가 달라지게 된다.
  • 또한, 절대 편차를 사용할 때, 중앙값 절대 편차(MAD)를 사용하게 되는 이유가 바로 위와 같다.

 

2.3. 절대편차는 미분되지 않는다.

  • 위에서 만든 절대편차의 대푯값 그래프를 가지고 와보자.

  • 미분을 위한 기본 조건은 좌 미분과 우 미분이 같아야 한다는 것인데, 보시다시피 절대편차는 매끈한 곡선이 아니므로, 좌 미분과 우 미분이 동일하지 않은 점이 반드시 존재한다.
  • 미분이 되지 않는다는 것은 산포도의 관점에서는 그다지 중요한 이유가 아니지만, 손실함수의 미분을 통한 최적해를 찾아가는 과정인 딥러닝의 학습 부분에서는 상당히 중요한 이유가 된다.

 

2.4. 절댓값 연산을 위해선 연산이 복잡해진다.

  • 절댓값 연산을 위해선, 각 편차의 부호를 확인하는 과정이 필요하다.
  • 반면에 분산은 편차를 일괄적으로 제곱하여 모든 부호가 양으로 만들기 때문에 연산이 아주 간단해진다.

 

2.5. 정리

  • 위 내용을 줄여보면, 산포도라는 대상을 위해 절대 편차를 사용하는 것에는 문제가 없으나, 그 기준을 평균이 아닌 중앙값(Median)으로 잡는 것이 좋으며, 중간값 절대 편차(MAD)를 사용한다 할지라도, 태생적인 한계점이 많으므로, 중간값 절대 편차보다는 분산을 사용하는 것이 좋다.

 

 

 

 

 

3. 변동 계수(Coefficient of Variation, CV)

  • 변동 계수는 표준 편차를 표본 평균이나 모 평균과 같은 산술 평균으로 나눈 값이다.

$$ CV = \frac{S}{\bar{X}} $$


  • 변동 계수는 상대 표준 편차(Relaative standard deviation, RSD)라고도 한다.
  • 변동 계수는 측정 단위가 서로 다른 데이터의 산포도를 비교할 때 사용한다.
  • 표준편차와 산술 평균 모두 단위가 있으나, 이 둘을 나눔으로써 단위가 사라지게 된다.
  • 평균과 표준편차는 단위가 존재하며, 서로 다른 데이터끼리 그 흩어져있음을 비교하기 위해선 변동 계수를 만들어, 단위를 없애야만 서로 비교해볼 수 있다.
  • 변동 계수가 클수록 상대적인 산포도가 크다고 할 수 있다.

 

3.1. 예시 및 파이썬 구현

  • A반의 키는 평균 164cm, 표준편차는 12cm가 나왔다.
  • A반의 몸무게는 평균 57 kg, 표준편차 10kg이 나왔다.
  • 키와 몸무게는 서로 다른 변수이므로, 단위 역시 다르기 때문에 이들의 표준편차를 단순하게 비교해서는 안된다.
def CV(mean, std):
    
    return std/mean
# 키의 변동계수
>>> CV(164, 12)
0.07317073170731707

# 몸무게의 변동계수
>>> CV(57, 10)
0.17543859649122806
  • 위 결과를 보면, 몸무게의 변동 계수는 0.1754, 키의 변동 계수는 0.731로 몸무게의 변동 계수가 더 크므로, 몸무게가 키보다 산포도가 크다는 것을 알 수 있다.
  • 변동 계수는 그 공식이 매우 단순하므로, 위처럼 평균과 표준편차를 출력하여 바로 구할 수도 있다.
def CV(data, column):
    
    target_array = data[column].to_numpy()
    
    return np.std(target_array)/np.mean(target_array)
>>> CV(Rawdata, "키")
0.05189104256446505

>>> CV(Rawdata, "몸무게")
0.2196611158371381
  • 또는 위 함수처럼 numpy로 평균과 표준편차를 직접 생성하여, 구할 수도 있다.
728x90
반응형
728x90
반응형

편차(Deviation)


◎ 편차(Deviation): 관측값들이 특정값(평균)으로부터 떨어진 정도(거리)이다.

$$ Dev = x_i - \mu $$


  • 이전 포스트에서 학습하였던, 범위, 사분위간 범위는 관측값 간의 간격을 사용해 산포도를 나타냈다. 이번에는 평균과 관측값의 차이인 편차(Deviation)를 이용해 산포도를 나타내는 방법에 대해 알아보겠다.
  • 편차를 이용해 산포도를 나타내는 방법은 분산(Varience), 표준 편차(Stadard deviation), 절대 편차(Absolute deviation), 변동 계수(Coefficient of variation) 등이 있다.
  • 편차는 양수, 음수 모두 가능하며, 평균보다 크면 양수, 작으면 음수가 된다.
  • 편차의 크기는 관측값이 평균으로부터 떨어진 거리를 말한다.
  • 모집단 평균에서의 편차는 오류(Error)라고 하며, 표본 집단 평균에서의 편차는 잔차(Observed value)라고 한다.

 

 

 

 

 

1. 분산(Varience)과 표준편차(Standard Deviation)


◎ 분산(Varience): 편차 제곱의 평균으로, 평균으로부터 관찰값들이 떨어진 거리의 제곱 평균이다.

◎ 표준편차(Standard Deviation): 분산의 양의 제곱근이다.


  • 중심경향치에서 평균이 제일 많이 쓰이듯, 산포도에서 제일 많이 쓰이는 분산과 표준편차가 나오게 된 개념은 편차의 평균을 구하려는 시도에서 시작되었다.
  • 편차는 각 관측값이 평균으로부터 떨어진 거리이므로, 그 평균을 알 수 있다면, 관측값들이 평균으로부터 떨어진 정도를 한 값으로 알 수 있다.
  • 편차 평균의 공식은 다음과 같다.

$$\frac{1}{N}\sum (x_i - \mu)$$

  • 그러나, 위 공식은 무조건 결괏값이 0이 나온다.

  • 모든 편차의 합이 0이 되는 것을 막기 위해, 편차에 제곱을 해줘서 모든 편차를 양수로 만들고, 이의 평균을 구한 것이 바로 분산(Varience)이다.
  • 본래 우리가 구하고자 했던 값은 편차의 평균이었다. 그러나, 편차의 합은 0이 나오기 때문에 제곱을 해주었고, 그로 인해 편차의 증폭과 단위의 제곱이 일어났다.
  • 위 문제를 해결하고자, 분산에 양의 제곱근을 씌워, 제곱으로 인한 편차의 증폭과 단위를 원상 복귀하고자, 분산에 양의 제곱근을 씌운 것이 표준편차(Standard deviation)다.

 

 

 

 

 

2. 모집단과 표본집단의 분산과 표준편차

  • 분산의 모수와 통계량은 계산 방법과 표기 방법이 달라진다.
  • 모집단의 분산과 표준편차

모집단의 분산: $\sigma ^2 = \frac{\sum(X_i - \mu)^2}{N}$

모집단의 표준편차: $\sigma = \sqrt{\frac{\sum(X_i - \mu)^2}{N}}$


  • 표본집단의 분산과 표준편차

표본집단의 분산: $S ^2 = \frac{\sum(X_i - \bar{X})^2}{n-1}$

표본집단의 표준편차: $S = \sqrt{\frac{\sum(X_i - \bar{X})^2}{n-1}}$


  • 단순 기술통계량을 구할 땐, 모집단의 분산과 표준편차를 구하는 방식으로 하면 된다.
  • 관찰 값들의 차이가 클수록 편차가 커지므로, 분산 $\sigma^2$은 커진다.
  • 분산은 편차의 제곱이므로, 평균으로부터 멀어질수록 그 차이가 증폭되게 된다.
  • 분산은 편차의 제곱이므로, 단위 역시 제곱된다.
  • 위 문제를 해결하기 위해 양의 제곱근을 씌워서 표준편차를 만든다.

 

 

 

 

 

3. 표본 분산에 $(n-1)$을 나눠주는 이유.

 표본 분산에 $(n-1)$을 나눠주는 이유는 꽤 복잡하기 때문에 기초통계학에서는 이를 다루지 않는다. 그러나, 이 부분을 그냥 넘어가게 된다면, 앞으로 이와 비슷한 경우가 등장할 때마다, 수식을 이해하는 것이 아닌, 수식을 암기만 하고 넘어갈 위험이 있다.

 표본 분산에 $(n-1)$을 나눠준 이유를 알기 위해서는 먼저 "자유도(Degree of freedom)"라는 개념에 대해 알아야 한다. 자유도의 개념은 꽤나 모호하고, 국내에서는 이를 명확히 설명하는 글을 찾기 어렵다. 위키피디아 영문판에서는 자유도를 무엇인지 대략적인 개념을 이해해보자.

 

3.1. 자유도(Degree of freedom)


◎ 자유도(Degree of freedom):

  • In statistics, the number of degrees of freedom is the number of values in the final calculation of a statistic that are free to vary.
    ("Degrees of Freedom". Glossary of Statistical Terms. Animated Software. Retrieved 2008-08-21.)
  • Estimates of statistical parameters can be based upon different amounts of information or data. The number of independent pieces of information that go into the estimate of a parameter are called the degrees of freedom. In general, the degrees of freedom of an estimate of a parameter are equal to the number of independent scores that go into the estimate minus the number of parameters used as intermediate steps in the estimation of the parameter itself 
    (Lane, David M. "Degrees of Freedom". HyperStat Online. Statistics Solutions. Retrieved 2008-08-21.)
  • 출처: en.wikipedia.org/wiki/Degrees_of_freedom_(statistics)

 위 자유도의 정의를 해석해보면, 자유도는 다음과 같은 성질을 갖는다.

  1. 통계의 최종 계산에서 자유롭게 변경 가능한 값의 개수
  2. "통계의 매개 변수의 추정치(Estimates of statistical parameters)"는 정보나 데이터에서 다른 값의 개수를 기반으로 하며, 매개 변수 추정치에 들어가는 독립적인 정보의 수를 자유도라 한다.
    일반적으로, 모수 추정의 자유도는 추정에 들어가는 독립된 값의 개수에 모수 추정의 중간 단계로 사용되는 모수의 수를 뺀 값과 같다.

 

 위 내용을 보다 쉽게 풀어써보면 다음과 같다.

  • 자유도는 독립된 값의 개수와 이를 통해 모수를 추정할 때, 사용되는 결정된 정보의 수를 뺀 것으로, 더 쉽게 말하면, 독립적인 미지수의 개수에 그 독립적인 미지수에 의해 자동으로 결정되어 버리는 값의 수를 빼는 것이다.
  • 표본집단의 각 원소는 완전 무작위 표본 추출 방법으로 복원 추출하여 실시한다면, 각 원소는 독립적지만, 그 독립된 원소들이 결정되면, 평균, 표준편차 등의 통계량들은 자동으로 결정되므로, 이들 통계량은 독립적이지 않다고 할 수 있다.
  • 그러므로, 자유도는 표본 집합의 원소의 수에 모수 추정 과정에서 표본 집합이 결정되면, 자동으로 결정되는 평균, 표준편차와 같은 파라미터의 개수를 빼서 구하게 된다.

 

3.2. 자유도를 사용해서 표본 분산을 계산하는 이유

  • 표본집단은 모집단에 비해, 표본의 수가 매우 적으므로, 데이터가 편향(Bias)되어 있다.
  • 편향되어 있기 때문에 표본집단의 크기는 모집단의 크기보다 작으며, 이를 보정하여, 통계량의 값을 크게 만들어줘서 불편추정량(Unbiased estimate)으로 만들어줘야 한다.
  • 이때, 독립적인 정보의 수인 n이 아니라, 자유도로 나눠준다면, 표본 집단의 통계량의 기댓값이 모집단의 통계량의 기댓값과 같아져 불편추정량이 된다.
  • 다른 값이 아니라 자유도를 사용하는 이유는, 자유도로 나눴을 때, 불편추정량이 만들어지기 때문이며, 이는 최소분산불편추정량(Uniformaly Minimum Variance Unbiased Estimator, UMVUE)를 통해 증명할 수 있다.
  • 모 분산과 표본 분산의 기댓값에 대하여, 표본 분산을 자유도로 나눴을 때, 어떤 결과가 나오는지 보도록 하자.

  • 위 결과를 보듯, 표본 분산의 기댓값은 $n$이 아니라, 자유도인 $n-1$로 나눴을 때, 모분산과 같게 된다.
  • 그러므로, 표본 분산에는 $n$이 아닌 자유도 $n-1$로 나눠줘야 한다.

 

3.3. 자유도를 무시하는 경우

  • 위 자유도의 개념을 알고 나니, 표본 집단을 이용하여, 모집단을 추정할 때는 불편추정량을 만들어주기 위해, 자유도의 개념이 필요하다는 것을 알 수 있다.
  • 그러나, 위 개념을 모르는 상태에서 현장에서 데이터 분석을 해본 사람들은 지금까지 큰 문제가 없었을 것인데, 이는 앞서 말했던, 불편추정량이 생기는 원인 때문이다.
  • 불편추정량은 표본 집단의 양이 작기 때문에 생기는 현상인데, 만약 표본 집단의 양이 매우 많다면 어떻겠는가
  • 자 표본 분산을 구하는데, 표본 집단의 크기가 10개라고 생각해보자, 자유도 9와 원래 값인 10은 꽤 큰 차이가 있다. 그러나 표본 집단의 크기가 10,000개라고 해보자, 10,000개와 9,999개는 거의 차이가 없다.
  • 즉, 표본의 크기가 매우 크다면, 자유도를 무시하고 n으로 계산해도 아무 문제가 없다.

 

 

 

 

 

4. 파이썬으로 표준편차를 구해보자.

 위에서 봤던, 표준편차와 분산의 개념과 달리 파이썬으로 표준편차를 구하는 것은 매우 쉽다. 아래 데이터를 이용해서, 양적 데이터인 "키", "몸무게"의 표준편차를 뽑아보도록 하자.

Data_for_study.csv
3.39MB

import numpy as np
import pandas as pd
Rawdata = pd.read_csv("Data_for_study.csv")

 

4.1. 위 식대로 함수를 만들어보자

# 모 표준편차
def Pop_standard_deviation(data, column):
    
    target_array = data[column].to_numpy()
    result = np.sqrt(np.sum(((target_array - np.mean(target_array)) ** 2))/len(target_array))
    
    return result


# 표본 표준편차
def Sample_standard_deviation(data, column):
    
    target_array = data[column].to_numpy()
    result = np.sqrt(np.sum((target_array - np.mean(target_array)) ** 2)/(len(target_array)-1))
    
    return result
# 키의 모표준편차
>>> Pop_standard_deviation(Rawdata, "키")
8.589382382344954

# 키의 표본표준편차
>>> Sample_standard_deviation(Rawdata, "키")
8.589459420964639

# 데이터의 크기
>>> len(Rawdata)
55748
  • 데이터의 크기가 55,784로 꽤 크다 보니, 모표준편차와 표본표준편차가 거의 유사하다는 것을 알 수 있다.

 

4.2 Pandas와 Numpy를 사용하여 표준편차를 구하자.

# pandas 함수로 구하기
>>> Rawdata.키.std()
8.589459420964502

>>> Rawdata.몸무게.std()
12.935373134509032


# numpy 함수로 구하기
>>> np.std(Rawdata.키.to_numpy())
8.589382382344954

>>> np.std(Rawdata.몸무게.to_numpy())
12.9352571175121
  • pd.Series.std(): 시리즈의 value를 대상으로 표준편차를 구한다. - 표본표준편차
  • np.std(array): 배열을 대상으로 표준편차를 구한다. - 모표준편차
  • 몸무게의 표준편차가 키의 표준편차보다 크므로, 몸무게의 데이터가 키의 데이터보다 평균에서 멀리 떨어진 것을 알 수 있다.
  • 위 결과를 보면, Pandas와 Numpy의 표준편차 결과가 약간이긴 하지만, 차이가 나는 것을 알 수 있다.
  • Pandas와 Numpy의 표준편차 계산 방식의 차이를 보기 위해, 표본을 아주 조금만 뽑아서 표본의 크기가 커져서, 자유도의 보정 영향력이 줄어드는 걸 줄여서 봐보자.
# 무작위로 데이터를 30개만 sampling 하자.
>>> sample_data = Rawdata.sample(30)


# 공식대로 만든 모표준편차
>>> Pop_standard_deviation(sample_data, "키")
7.172806672116262

# 공식대로 만든 표본표준편차
>>> Sample_standard_deviation(sample_data, "키")
7.295427634334816

# numpy의 std 함수
>>> np.std(sample_data.키.to_numpy())
7.172806672116262

# pandas의 std 함수
>>> sample_data.키.std()
7.295427634334816
  • 위 결과를 보면, numpy의 표준편차는 기본적으로 모표준편차를 구하는 공식으로 돌아가는 것을 알 수 있다.
  • pandas의 표준편차는 기본적으로 표본표준편차를 구하는 공식으로 돌아간다.
  • 기본적으로 numpy의 성능이 더 좋기 때문에, numpy를 사용하여 표본표준편차를 구하고자 하는 경우도 많을 텐데, numpy로 표본표준편차를 구하고자 한다면, 다음 파라미터를 추가해줘야 한다.
# numpy를 사용해서 표본표준편차 구하기
>>> np.std(sample_data.키.to_numpy(), ddof = 1)
7.295427634334816
  • ddof 파라미터는 자유도에서 빼 줄 독립적이지 않은 파라미터의 크기를 말한다.
  • 표본 데이터를 대상으로 numpy를 연산한다면, 자유도의 존재를 잊지 말고 꼭 반영해주자.
  • 물론, 데이터의 양이 굉장히 크다면, 굳이 불편추정량을 만들기 위해, 자유도로 나눠주지 않아도 괜찮다.

 

 

 

 

 

5. 도수분포표를 이용한 표준편차 계산

  • 앞서 도수분포표를 사용해서 평균, 중앙값 등을 구했듯, 도수분포표를 사용해서 표준편차를 계산할 수도 있다.
  • 앞서 말했듯, 원시자료를 가지고 있다면, 굳이 도수분포표로 표준편차를 구하지 않아도 되지만, 도수분포표밖에 없다면, 도수분포표를 사용해서 표준편차를 유추해야 한다.
  • 이전에 만들었던, 연속형 데이터를 범주형 데이터로 만드는 함수를 사용해서 도수분포표를 만들고, 해당 도수분포표를 이용해서 표준편차를 추론해보자.
def make_freq_table(data, column):
    """
    -------------------------------------------------------------------------------
    지정된 변수의 관찰값이 20개보다 많은 경우, 10개의 등급을 가진 데이터를 반환한다.
    10개의 등급은 동일한 간격으로 자른 것이다.
    -------------------------------------------------------------------------------
    Input: DataFrame, str
    Output: DataFrame
    """
    # array 생성
    target_array = data[column].to_numpy()

    # class의 수가 20개보다 많은 경우 10개로 줄인다.
    class_array = np.unique(target_array)

    if len(class_array) > 20:

        min_key = class_array.min()
        max_key = class_array.max()
        split_key = np.linspace(min_key, max_key, 10)

        a0 = str(round(split_key[0], 2)) + " 이하"
        a1 = str(round(split_key[0], 2)) + " ~ " + str(round(split_key[1], 2))
        a2 = str(round(split_key[1], 2)) + " ~ " + str(round(split_key[2], 2))
        a3 = str(round(split_key[2], 2)) + " ~ " + str(round(split_key[3], 2))
        a4 = str(round(split_key[3], 2)) + " ~ " + str(round(split_key[4], 2))
        a5 = str(round(split_key[4], 2)) + " ~ " + str(round(split_key[5], 2))
        a6 = str(round(split_key[5], 2)) + " ~ " + str(round(split_key[6], 2))
        a7 = str(round(split_key[6], 2)) + " ~ " + str(round(split_key[7], 2))
        a8 = str(round(split_key[7], 2)) + " ~ " + str(round(split_key[8], 2))
        a9 = str(round(split_key[8], 2)) + " 이상"
        new_index = [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]


        target_array= np.where(target_array <= split_key[0], 0,
                               np.where((target_array > split_key[0]) & (target_array <= split_key[1]), 1,
                                        np.where((target_array > split_key[1]) & (target_array <= split_key[2]), 2,
                                                 np.where((target_array > split_key[2]) & (target_array <= split_key[3]), 3,
                                                          np.where((target_array > split_key[3]) & (target_array <= split_key[4]), 4,
                                                                   np.where((target_array > split_key[4]) & (target_array <= split_key[5]), 5,
                                                                            np.where((target_array > split_key[5]) & (target_array <= split_key[6]), 6,
                                                                                     np.where((target_array > split_key[6]) & (target_array <= split_key[7]), 7,
                                                                                              np.where((target_array > split_key[7]) & (target_array <= split_key[8]), 8, 9)))))))))


    # 도수분포표 생성
    freq_table = pd.DataFrame(pd.Series(target_array).value_counts(), columns = ["freq"])
    freq_table.index.name = column

    freq_table.sort_index(inplace = True)
    freq_table["ratio"] = freq_table.freq / sum(freq_table.freq)
    freq_table["cum_freq"] = np.cumsum(freq_table.freq)
    freq_table["cum_ratio"] = np.round(np.cumsum(freq_table.ratio), 2)
    freq_table["ratio"] = np.round(freq_table["ratio"], 2)

    if "new_index" in locals():
        freq_table.index = new_index
        freq_table.index.name = column

    return freq_table
Rawdata = pd.read_csv("Data_for_study.csv")
make_freq_table(Rawdata, "키")

  • 위 도수분포표에서 사용해야 할 것은 범주화된 $X_i$인 키의 중앙값과 freg다.
  • 표준편차의 식을 다음과 같이 유도해보자.

$$\sqrt{\frac{\sum(X_i - \bar{X})^2}{n}} = \sqrt{\frac{\sum fX_i^2}{n} - \left ( \frac{\sum fX_i}{n} \right )^2}$$


# 중앙값 생성
키_도수분포표 = make_freq_table(Rawdata, "키")
키_도수분포표.reset_index(drop=False, inplace=True)

# 1 부터 8번 행까지 처리
키1_8_array = 키_도수분포표[1:9].키.str.split(" ~ ", expand=True).values.astype("float")
키1_8_array_mid = (키1_8_array[:,0] + 키1_8_array[:,1])/2

# 0, 9 행 처리
키0_9_array = 키_도수분포표.loc[[0,9]].키.str.partition(" ")[0].to_numpy().astype("float")

# 중앙값 생성
키1_8_array_mid = np.insert(키1_8_array_mid, 0, 키0_9_array[0])
키_도수분포표["중앙값"] = np.insert(키1_8_array_mid, len(키1_8_array_mid), 키0_9_array[1])
키_도수분포표

키_도수분포표["$fX$"] = 키_도수분포표["freq"] * 키_도수분포표["중앙값"]
키_도수분포표["$fX^2$"] = 키_도수분포표["freq"] * 키_도수분포표["중앙값"]**2
키_도수분포표

# 도수분포표로 추론한 표준편차
>>> 앞부분 = 키_도수분포표["$fX^2$"].sum()/키_도수분포표["freq"].sum()
>>> 뒷부분 = (키_도수분포표["$fX$"].sum()/키_도수분포표["freq"].sum())**2
>>> np.sqrt(앞부분 - 뒷부분)
8.771635569514544
# 실제 표준편차
>>> np.std(Rawdata.키)
8.589382382344816
  • 자유도가 아닌 총 빈도 수인 $n$으로 나눠 모 표준편차를 구하였다.
  • 표본 표준편차를 구하고자 하는 경우, 위 공식에서도 $n$이 아니라 $n-1$로 나눠주면 된다.
  • 실제 표준편차는 8.5893이 나왔으며, 도수분포표로 추론한 표준편차는 8.7716이 나왔다.
  • 앞서 말했듯, 이 방법은 도수분포표만 사용 가능한 경우에 쓸 수 있는 방법이다.
728x90
반응형
728x90
반응형

산포도(Dispersion)


◎ 산포도: 분산도(Degree of dispersion), 변산성(Variability)이라고도 하며, 관찰된 데이터가 흩어져 있는 정도를 말한다. 


  • 통계학은 기본적으로 데이터가 어디에 모여있고, 얼마나 흩어져 있는지를 통해 데이터를 파악한다고 하였다.
  • 지금까지 데이터가 어디에 모이는지에 대한 중심경향치(Center Tendency)를 알아보았으므로, 이번에는 데이터가 얼마나 흩어져있는지를 알 수 있는 산포도(Dispersion)에 대해 알아보겠다.
  • 산포도에는 범위(Range), 사분위간 범위(Interquartile range), 분산(Varience), 표준 편차(Standard deviation), 절대 편차(Absolute deviation), 변동 계수(Coefficient of variation) 등이 있다.
  • 학습 데이터(참고 포스트: "통계 분석을 위한 데이터 준비")

Data_for_study.csv
3.39MB

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

Rawdata = pd.read_csv("Data_for_study.csv")

 

 

 

 

 

1. 범위(Range)


◎ 범위(Range): 관찰 값에서 최댓값과 최솟값의 차이다.

$$ range = max - min $$


  • 데이터가 흩어진 정도를 보는 가장 간단한 방법으로, 관찰 값이 시작되는 최솟값에서 관찰값이 끝나는 최댓값의 차이다.
  • 범위는 구하기 쉽고, 해석하기도 쉬우나, 간단한만큼 많은 단점을 가지고 있다.
  • 범위는 이상치(다른 데이터에 비해 지나치게 크거나, 작은 값)의 영향을 너무 심하게 받는다.
  • 최댓값에서 최솟값의 차이만 구하므로, 같은 범위를 갖는다고 할지라도 데이터의 분포가 완전히 다를 수 있다.
  • 학습 데이터에서 "학업성적", "건강인지"의 범위를 구해보자.
def cal_range(data, column):
    
    target_series = data[column]
    
    return target_series.max() - target_series.min()
  • pd.Series.min(): 시리즈의 최솟값을 구한다.
  • pd.Series.max(): 시리즈의 최댓값을 구한다.
>>> cal_range(Rawdata, "학업성적")
4.0

>>> cal_range(Rawdata, "건강인지")
4.0
  • "학업성적", "건강인지"의 도수분포표를 이용해, 도수분포다각형(Frequency distribution polygon)을 그려보자.
def FDPolygon(data, column):
    
    freq_table = data[column].value_counts().sort_index()
    ratio_x = freq_table.values/sum(freq_table.values)

    # 도수분포다각형 그리기
    plt.plot(freq_table.index, ratio_x, label = column)

    plt.xticks(np.arange(1, len(freq_table.index) + 1))
plt.rc('font', family='NanumGothic')
fig = plt.figure(figsize=(8, 6))

FDPolygon(Rawdata, "건강인지")
FDPolygon(Rawdata, "학업성적")

# 그래프 꾸미기
plt.legend(loc = "upper right")
plt.title("건강인지 & 학업성적 도수분포다각형", fontsize = 20, pad = 30)
plt.xlabel("관측값", fontsize = 15, labelpad = 10)
plt.ylabel("비율", rotation = 0, fontsize = 15, labelpad = 20)
plt.show()

  • "건강인지", "학업성적" 변수의 범위는 동일하지만, 데이터의 분포는 상당히 다른 것을 알 수 있다.
  • 즉, 범위는 한 변수 안의 데이터의 관측값의 시작 값(최솟값)에서 끝 값(최댓값)까지의 간격을 알 수 있지만, 데이터가 어떻게 분포해 있는지 알 수는 없다.

 

 

 

 

 

2. 사분위간 범위(Interquartile range, IQR)


◎ 사분위간 범위:  데이터를 25% 단위로 4개의 구간으로 나누는 관측값을 사분위수(Quartile)라 하며, 이 사분위수에서 제1사분위수(Q1)와 제3사분위수(Q3)의 범위가 사분위간 범위다.

$$ IQR = Q3 - Q1 $$


  • 사분위수(Quartile):
    전체 데이터를 25% 단위로 나눌 수 있는, Q1(1사분위수), Q2(2사분위수), Q3(3사분위수)를 말한다.
  • 각 사분위수는 각 값의 하위 범위에 대하여, Q1은 25% 이하의 데이터가 존재하는 관측값, Q2는 50% 이하의 데이터가 존재하는 관측값(중위수), Q3는 75% 이하의 데이터가 존재하는 관측값을 말한다.
  • 즉, Q1은 제 25 백분위수, Q2는 제 50 백분위수, Q3는 제 75 백분위수이다.
  • Q4는 제4사분위로 제 100 백분위수다.
  • 사분위간 범위는 산포도이며, 사분위수는 대푯값에 해당한다.
  • 중위수, 사분위간 범위는 빈도를 이용해 계산되며, 이상치(지나치게 크거나 작은 값)의 영향을 받는 양 극단의 값 역시, 단순히 빈도로 보므로 이상치의 영향을 받지 않는다.

 

2.1. 파이썬을 이용해서 사분위수 구하기

2.1.1. pandas 라이브러리 활용

  • 키에 대하여 사분위수를 구해보자.
>>> Rawdata.키.quantile(0)
136.0

>>> Rawdata.키.quantile(0.25)
159.1

>>> Rawdata.키.quantile(0.5)
165.0

>>> Rawdata.키.quantile(0.75)
172.0

>>> Rawdata.키.quantile(1)
196.0
  • pd.Series.quantile(float): 0에서 1 사이의 비율에 대하여, 그 비율의 위치에 해당하는 관찰 값을 반환한다.
  • 하위 빈도의 비율에 대해 구하므로, 사분위수가 아닌 하위 60, 80%와 같은 값도 쉽게 구할 수 있다.
>>> Rawdata.키.quantile(0.6)
167.8

>>> Rawdata.키.quantile(0.8)
173.2

 

2.1.2. Pandas의 기본적인 기술 통계량을 반환하는 함수로도 구할 수 있다.

>>> Rawdata.키.describe()

count    55748.000000
mean       165.527266
std          8.589459
min        136.000000
25%        159.100000
50%        165.000000
75%        172.000000
max        196.000000
Name: 키, dtype: float64
  • pd.DataFrame.describe(): 데이터 요약을 위한 메서드로, 빈도, 평균, 표준편차, 최솟값, 사분위수, 최댓값을 시리즈로 출력한다.
>>> 키_기술통계량 = Rawdata.키.describe()
>>> 키_기술통계량.loc["25%"]
159.1

>>> 키_기술통계량.loc["75%"]
172.0

>>> 키_기술통계량.loc["mean"]
165.5272655521264

>>> 키_기술통계량.loc["std"]
8.589459420964502
  • 시리즈로 출력되므로, index를 이용해, 그 인데스에 해당하는 값을 반환하는 loc를 사용하면, 내가 원하는 값만 가지고 올 수 있다.

 

2.2.2. Numpy를 이용하여 사분위수 구하기

>>> target_array = Rawdata.키.to_numpy()
>>> np.percentile(target_array, 25)
159.1

>>> np.percentile(target_array, 50)
165.0

>>> np.percentile(target_array, 75)
172.0
  • np.percentile(array, q): q번째 백분위 수를 계산하여, 배열의 q번째 백분위 수를 반환한다.

 

2.3. 사분위간 범위 구하기

def cal_Interquartile_range(data, column):
    
    target_array = data[column].to_numpy()
    
    return np.percentile(target_array, 75) - np.percentile(target_array, 25)
>>> cal_Interquartile_range(Rawdata, "키")
12.900000000000006

 

 

 

 

 

3. 사분위수와 상자 수염 그림


◎ 상자 수염 그림(Box-and-whisker plot): 상자 그림(Box plot)이라고도 하며, 다섯 숫자 요약(최솟값, 제1사분위수, 중앙값, 제3사분위수, 최댓값)으로 데이터의 특성을 요약하는 그래프다.


  • 다섯 숫자 요약(Five-number summary): 최솟값(min), 제1사분위수(Q1), 중앙값(median), 제3사분위수(Q3), 최댓값(max)으로 전체 데이터를 요약한 것이다.
  • 사분위간 범위(IQR)로 몸통을 구성하고, 근접 값들로 꼬리를 구성한다.
  • 단위 척도(Step): 1.5 * IQR
  • 안 울타리(Inner fence)는 Q1에서 최솟값의 방향으로 1 step만큼 이동한 것이고, Q3에서 최댓값의 방향으로 1 step만큼 이동한 곳에 그린다.
  • 바깥 울타리(Outer fence)는 Q1에서 최솟값의 방향으로 2 step만큼 이동한 것이고, Q3에서 최댓값의 방향으로 2 step만큼 이동한 곳에 그린다.

 

3.1. 상자 수염 그림과 이상값(Outlier)


◎ 이상값(Outlier): 정상 범주에서 벗어난 값으로, 정상 관측값에서 벗어난 값이거나, 지나치게 크거나 작아 정상범위에서 벗어난 값을 의미한다.


  • 상자 수염 그림을 이용하면, 이상값을 찾아낼 수 있다.
  • 근접값(Adjacent value): 안 울타리 안쪽 값 중 안 울타리에 가장 가까운 값
  • 보통 이상값(Mild Outlier): 한쪽 방향에 대하여, 바깥 울타리, 안 울타리 사이의 값
  • 극단 이상값(Extreme Outlier): 바깥 울타리 밖에 있는 값
  • 근접값은 이상값에 가장 가까운 정상 범위 내 최대·최솟값이며, 이상값은 정상 범위(사분위간 범위)에서 크게 벗어난 값이므로, 연구자의 관점에 따라 데이터에서 제거, 혹은 집단의 재구성 등을 통한 이상값 처리를 해주는 것을 추천한다.

 

3.2. 상자 수염 그림 그리기

  • 주어진 데이터 "키" 변수에 대하여, 상자 수염 그림을 그려보자.
fig = plt.figure(figsize=(10, 8))

plt.boxplot(Rawdata.키.to_numpy())

plt.title("키의 상자 수염 그림", fontsize = 20, pad = 30)
plt.ylabel("키", rotation = 0, fontsize = 15, labelpad = 20)
plt.show()

  • 위 상자 수염 그림에서 상자(Box)는 사분위간 범위이다.
  • 상자(Box) 안의 주황색 선은 중앙값이다.
  • 상자 위아래에 달려 있는 'ㅜ', 'ㅗ' 모양의 선은 수염(Whisker)이다.
  • 양쪽 수염의 끝은 정상 범위의 최대·최솟값이다.
  • 수염 바깥의 동그란 점은 보통 이상값(mild outlier)이다.
  • 위 데이터에는 극단 이상값(Extream outlier)이 존재하지 않으므로, 바깥 울타리는 보이지 않는다.

 

3.3. 상자 수염 그림에서의 정상범위의 최솟값과 최댓값 찾기

  • 상자 수염 그림에서 정상범위의 최솟값과 최댓값을 뽑으면, 이상값을 구분할 수 있는 영역을 찾을 수 있다.
  • 상자 수염 그림에서 정삼 범위 최솟값은 $Q1 - 1.5*IQR$이며, 최댓값은 $Q3 + 1.5*IQR$이다.
def Outlier_checker(data, column):
    
    target_array = data[column].to_numpy()
    Q1 = np.percentile(target_array, 25)
    Q3 = np.percentile(target_array, 75)
    IQR =  Q3 - Q1
    
    norm_min = Q1 - 1.5*IQR
    norm_max = Q3 + 1.5*IQR
    
    return norm_min, norm_max
>>> Outlier_checker(data, "키")
(139.75, 191.35000000000002)
  • 위 결과를 볼 때, 키에서 139.75 cm 미만, 191.35cm 초과는 이상값임을 알 수 있다.
728x90
반응형
728x90
반응형

중심경향치(Center Tendency)

 이전 포스트에서는 최빈값, 중앙값을 구하는 방법에 대해 알아보았다. 이번 포스트에서는 가장 자주 사용되는 중심경향치인 산술 평균과 기하 평균, 조화 평균에 대해서 알아보도록 하자.

 

 

 

1. 산술 평균(Arithmetic mean)

  • 주어진 수의 합을 수의 개수로 나눈 값인 산술 평균은, 통계학에서 가장 많이 사용되는 중심경향치로, 단순하게 평균(Mean)이라고 부르기도 한다.
  • 중앙값처럼 산술 평균 역시, 양적 변수를 대상으로만 사용할 수 있다.

 

1.1. 산술평균의 정의

  • $N$ 개로 구성된 모집단의 관찰값 $X_1, X_2, X_3, ..., X_N$이 있다고 할 때, 모집단의 평균 $\mu$는 다음과 같이 구한다.

$$\mu = \frac{X_1 + X_2 + X_3 + \cdots X_N}{N} = \frac{1}{N}\sum_{i=1}^{N}X_i$$


  • 통계학에서 같은 값을 구한다고 할지라도 모집단을 대상(모수)으로 하는지, 표본집단을 대상(통계량)으로 하는지에 따라, 공식이 약간 달라지게 된다.
  • 평균은 모수와 통계량 모두 공식이 동일하지만, 사용하는 기호가 $\mu$에서 $\bar{x}$로 달라진다.

 

1.2 모평균과 표본평균이 동일한 이유.

 앞서 산술평균에서 단순하게 모수와 통계량의 공식은 동일하지만, 모수는 $\mu$로 통계량은 $\bar{x}$로 사용하는 기호가 다르다고 했다. 이는, 모집단의 평균과 표본 집단의 평균값이 같다는 소리인데, 어떻게 이 것이 가능한 것일까?

  • 먼저, 표본 집단을 완전 무작위로 추출한다고 가정해보자.
  • 이 무작위 추출은 복원 추출(뽑은 값을 또 뽑을 수 있음)을 가정한다.
  • 복원 추출을 가정한 완전 무작위 표본 추출이므로, 원소들이 뽑히는 사건은 서로 독립이다.

  • 위 증명을 보면, 아주 쉽게 표본평균의 기댓값이 모평균과 같다는 것을 찾아내었다.
  • 중간에 나온 원소가 1개인 표본 집합의 기댓값이 모평균과 같다는 이유는 무엇일까?

  • 위와 같은 이유로, 원소가 1개인 표본평균의 분산이 모집단과 동일하다.
  • 즉, 표본을 무수히 많이 뽑게 된다면, 표본 평균의 기댓값은 모평균과 동일하게 된다.

 

 

 

 

 

2. 파이썬으로 산술 평균 구하기.

2.1. 도수분포표로 평균 구하기

  • 중앙값과 마찬가지로, 도수분포표로 평균을 추정할 수는 있으나, 실제 평균과 일치하지는 않는다.
  • 원시 자료를 가지고 있는 상황이라면, 굳이 도수분포표를 구하고, 부정확한 평균을 추론할 필요는 없으나, 혹시나 구할 수 있는 데이터가 도수분포표밖에 없는 상황이 있을 수도 있으므로, 이를 가정하여, 도수분포표로 평균을 구하는 방법에 대해 알아보도록 하겠다.
  • 사용할 데이터와 양적 변수의 범주화된 도수분포표 변환 함수는 다음과 같다.

Data_for_study.csv
3.39MB

import pandas as pd
import numpy as np
def make_freq_table(data, column):
    """
    -------------------------------------------------------------------------------
    지정된 변수의 관찰값이 20개보다 많은 경우, 10개의 등급을 가진 데이터를 반환한다.
    10개의 등급은 동일한 간격으로 자른 것이다.
    -------------------------------------------------------------------------------
    Input: DataFrame, str
    Output: DataFrame
    """
    # array 생성
    target_array = data[column].to_numpy()

    # class의 수가 20개보다 많은 경우 10개로 줄인다.
    class_array = np.unique(target_array)

    if len(class_array) > 20:

        min_key = class_array.min()
        max_key = class_array.max()
        split_key = np.linspace(min_key, max_key, 10)

        a0 = str(round(split_key[0], 2)) + " 이하"
        a1 = str(round(split_key[0], 2)) + " ~ " + str(round(split_key[1], 2))
        a2 = str(round(split_key[1], 2)) + " ~ " + str(round(split_key[2], 2))
        a3 = str(round(split_key[2], 2)) + " ~ " + str(round(split_key[3], 2))
        a4 = str(round(split_key[3], 2)) + " ~ " + str(round(split_key[4], 2))
        a5 = str(round(split_key[4], 2)) + " ~ " + str(round(split_key[5], 2))
        a6 = str(round(split_key[5], 2)) + " ~ " + str(round(split_key[6], 2))
        a7 = str(round(split_key[6], 2)) + " ~ " + str(round(split_key[7], 2))
        a8 = str(round(split_key[7], 2)) + " ~ " + str(round(split_key[8], 2))
        a9 = str(round(split_key[8], 2)) + " 이상"
        new_index = [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9]


        target_array= np.where(target_array <= split_key[0], 0,
                               np.where((target_array > split_key[0]) & (target_array <= split_key[1]), 1,
                                        np.where((target_array > split_key[1]) & (target_array <= split_key[2]), 2,
                                                 np.where((target_array > split_key[2]) & (target_array <= split_key[3]), 3,
                                                          np.where((target_array > split_key[3]) & (target_array <= split_key[4]), 4,
                                                                   np.where((target_array > split_key[4]) & (target_array <= split_key[5]), 5,
                                                                            np.where((target_array > split_key[5]) & (target_array <= split_key[6]), 6,
                                                                                     np.where((target_array > split_key[6]) & (target_array <= split_key[7]), 7,
                                                                                              np.where((target_array > split_key[7]) & (target_array <= split_key[8]), 8, 9)))))))))


    # 도수분포표 생성
    freq_table = pd.DataFrame(pd.Series(target_array).value_counts(), columns = ["freq"])
    freq_table.index.name = column

    freq_table.sort_index(inplace = True)
    freq_table["ratio"] = freq_table.freq / sum(freq_table.freq)
    freq_table["cum_freq"] = np.cumsum(freq_table.freq)
    freq_table["cum_ratio"] = np.round(np.cumsum(freq_table.ratio), 2)
    freq_table["ratio"] = np.round(freq_table["ratio"], 2)

    if "new_index" in locals():
        freq_table.index = new_index
        freq_table.index.name = column

    return freq_table
  • 키에 대하여 범주화된 도수분포표를 뽑아보자.
Rawdata = pd.read_csv("Data_for_study.csv")
make_freq_table(Rawdata, "키")

  • 범주화된 도수분포표를 이용한 평균 계산은 각 등급(Class)의 중앙값에 각 빈도를 곱해 평균을 계산한다.
  • 위와 같이 136 이하, 189.33 이상이 존재하는 경우, 두 등급에 속하는 빈도가 전체에서 차지하는 비중이 매우 작으므로, 단순하게 우리가 알고 있는 136, 189.33에 대하여 빈도를 곱하거나, 이상치로 보고 제거해도 된다.
  • 애초에 도수분포표를 이용해 중심경향치를 계산하는 것은 정확한 값을 얻기 위해서가 아닌, 대략적인 값을 얻기 위해서이므로, 이번에는 저 두 값에 그냥 빈도를 곱하도록 하겠다.
>>> ((136.0*1)+((142.67+136.0)/2*70)+((149.33+142.67)/2*869)+((156.0+149.33)/2*7079)+
    ((162.67+156.0)/2*14131)+((169.33+162.67)/2*14640)+((176.0+169.33)/2*12492)+
    ((182.67+176.0)/2*5118)+((189.33+182.67)/2*1241)+(189.33*107))/55748
    
165.47919010547466
  • 위 코드처럼 값을 하나하나 쓰기 싫은 사람을 위해, pandas의 str 모듈을 사용해 계산하는 방법도 보도록 하겠다.
>>> 키_도수분포표 = make_freq_table(Rawdata, "키")
>>> 키_도수분포표.reset_index(drop=False, inplace=True)

# 1 부터 8번 행까지 처리
>>> 키1_8_array = 키_도수분포표[1:9].키.str.split(" ~ ", expand=True).values.astype("float")
>>> 키1_8_sum = np.sum(((키1_8_array[:,0] + 키1_8_array[:,1])/2) * 키_도수분포표[1:9].freq.to_numpy())

# 0, 9 행 처리
>>> 키0_9_array = 키_도수분포표.loc[[0,9]].키.str.partition(" ")[0].to_numpy().astype("float")
>>> 키0_9_sum = np.sum(키0_9_array * 키_도수분포표.loc[[0,9]].freq.to_numpy())

>>> 평균키 = (키1_8_sum + 키0_9_sum)/키_도수분포표.freq.sum()
>>> 평균키
165.4791901054746
  • 0번행과 9번행은 1~8번행까지와 구간의 패턴이 다르므로, 이를 분리하여, 원하는 값만 추출하여 평균을 구했다.
  • 위에서 숫자를 일일이 복사 붙여넣기한 것과 같은 결과가 나왔다.

 

2.2. 파이썬으로 산술 평균 구하기

  • 원시자료를 가지고 있는 경우, 위 같이 번거로운 과정 없이 아주 편하게 평균을 구할 수 있다.
>>> Rawdata.키.mean()
165.5272655521264

>>> np.mean(Rawdata.키.to_numpy())
165.52726555212743
  • Series.mean()은 pandas 함수로 평균을 구하는 것이다.
  • np.mean(array)는 numpy 함수로 평균을 구하는 것이다.
  • 실제 평균과 위에서 도수분포표를 이용해서 추론한 평균이 꽤 유사한 것을 알 수 있다.

 

 

 

 

 

3. 기타 산술 평균

3.1. 가중 평균(Weighted mean)

  • 서로 다른 집단에 대하여, 다른 가중치를 부여하여 평균을 내는 방법
  • 집단의 크기가 서로 다르거나, 다른 점수 배점을 가질 때, 사용하는 방법이다.
  • 예를 들어, A반은 10명, B반은 40명일 때, A반과 B반의 평균을 동일하게 생각하고 $\bar{X_A}$와 $\bar{X_B}$를 더하고 평균을 내면 안 된다.
  • 이 경우, A반은 전체에서 차지하는 비중인 $\frac{10}{50}$만큼 평균 $\bar{X_A}$ 곱해주고, B반은 전체에서 차지하는 비중인 $\frac{40}{50}$만큼 평균 $\bar{X_B}$에 곱해주고 이 둘을 더해줘야 한다.
  • 즉, A반의 평균이 $\bar{X_A} = 60$이고, B반의 평균이 $\bar{X_B} = 70$이라면, 두 반의 평균은 $\frac{60+70}{2}=65$이 아닌, $0.2\bar{X_A} + 0.8\bar{X_B}= 0.2 * 60 + 0.8 * 70 = 68$점이 된다.

3.2. 절삭 평균(Trimmed mean)

  • 이상치인 극단적으로 크거나 작은 값을 제거하고 평균을 내는 방법이다.
  • 예를 들어, A라는 마을의 실제 평균 월급은 250만 원인데, 그 동네에 빌 게이츠가 살아서 평균 월급이 1,000만 원이 나와버린다면, 그 평균은 실제 데이터가 모여 있는 곳을 보여준다고 할 수 없다.
  • 물론, 이상치를 제거한다면, 이상치가 제거된 것을 집단의 크기에도 반영해야 한다.

 

 

 

 

4. 기하 평균(Geometric mean)

  • 기하 평균은 $n$개의 양수 값을 모두 곱하고, $n$ 제곱근을 해준 것이다.
  • 기하 평균은 지금까지 우리가 다뤘던 변수들과 달리 곱셈으로 계산하는 값에서 사용된다.
  • 변수가 비율로 구성된 경우, 기하 평균을 사용하면 된다.
  • $n$ 개로 구성된 모집단의 관찰 값 $a_1, a_2, a_3, ..., a_n$이 있다고 할 때, 기하 평균은 다음과 같이 구한다.

$$(\prod_{i=1}^{n}a_i)^{\frac{1}{n}} = (a_1\cdot a_2 \cdot a_3\cdots a_n)^{\frac{1}{n}}=\sqrt[n]{a_1\cdot a_2 \cdot a_3\cdots a_n}$$


  • 앞서 보았던 산술평균은 합의 평균이고, 기하 평균은 곱의 평균이다.
  • 기하평균의 대상인 변수의 모든 관찰 값은 양수여야 한다(제곱근을 사용하므로).
  • 로그 항등식을 사용하여 기하평균 공식을 변환시키면, $f(n) = ln x$일 때의 일반화된 f-평균이 만들어진다.

$$ln(a_1\cdot a_2 \cdot a_3\cdots a_n)^{\frac{1}{n}} = \frac{lna_1 + lna_2 + lna3 + \cdots + lna_n}{n}$$


 

4.1 파이썬으로 기하평균 구하기

  • 아래와 같은 데이터가 있다고 가정해보자.
salary = [0.85, 1.1, 1.5, 0.95, 2.0, 1.05]
  • 해당 데이터는 월급 200만 원을 1로 놓고, 6명의 월급의 비율을 본 것이다.
  • 즉, $a_1$은 $200*0.85=170$만원을 월급으로 받고, $a_2$는 $200*1.1=220$만원을 월급으로 받는다는 의미다.
  • 위 데이터의 기하 평균은 다음과 같다.
>>> (0.85*1.1*1.5*0.95*2.0*1.05)**(1/6)
1.187064439031504

 

4.2. Numpy로 구하기

  • 반복문을 사용하는 방법 말고 기하평균을 Numpy로 구하는 방법은 크게 2가지가 있다.
  1. array의 모든 원소를 곱하고, 출력된 스칼라에 n 제곱근 씌우기
  2. array를 모두 로그 치환하고, 그에 대한 산술 평균을 구한 후, 지수로 원 상태로 만들어주기

4.2.1. 방법 1.

# 방법 1.
>>> salary_A = np.array(salary)
>>> np.prod(salary_A)**(1/len(salary_A))
1.187064439031504
  • np.prod(array): 배열의 모든 인자들을 곱한다.

4.2.2. 방법 2.

  • 자연로그의 평균을 구하고, 아래 로그의 성질을 사용하여, 기하 평균을 구하는 방법도 있다.

$$\log_xy = A \rightarrow x^A = y$$

# 방법 2
>>> np.exp(1) ** (np.mean(np.log(salary_A)))
1.1870644390315037
  • np.exp(1): 자연로그의 밑인 2.718281...이다.
  • np.log(array): array의 모든 원소를 자연로그로 변환시킨다.

 

4.3. statistics 라이브러리 사용하기

>>> import statistics as stat
>>> stat.geometric_mean(salary_A)
1.187064439031504
  • statistics 라이브러리는 굉장히 많은 수학적 통계 계산 함수를 제공한다.
  • 개인적으로는 statistics 함수를 사용하기보다는 numpy가 성능이 더 좋으므로, numpy를 사용하길 추천한다.

 

 

 

 

 

5. 조화 평균(Harmonic mean)

  • 조화 평균은 주어진 값들의 역수의 산술 평균의 역수다.
  • 평균 변화율을 구할 때 주로 사용한다.
  • $n$ 개로 구성된 모집단의 관찰 값 $a_1, a_2, a_3, ..., a_n$이 있다고 할 때, 조화 평균은 다음과 같이 구한다.

$$H = \frac{n}{\frac{1}{a_1} + \frac{1}{a_2} + \frac{1}{a_3} + \cdots + \frac{1}{a_n}}$$


  • 조화평균은 속도와 같은 변화율의 평균을 구할 때, 매우 유용하다.
  • 예를 들어, 전체 거리의 절반은 40km/h로 이동하고, 나머지 절반을 60km/h로 이동하였다면, 평균 속력은 산술 평균인 50km/h가 아닌 조화 평균인 48km/h가 된다.
  • 동일한 거리에 대하여 소모된 시간이 다르므로, 단순하게 산술 평균을 구한다면, 평균 속력을 구할 수 없다.
  • 위와 같은 경우엔, 시간의 차원에서 평균을 구해야하므로, 시간에 대하여 값을 바꿔주고, 평균을 구한 후, 다시 속력으로 바꿔보자.
  • 거리를 $S$라 가정하고, 속도를 $V$라고 가정 하자.

 

5.1. 변화율(Rate of change)

  • 두 변수의 변화 정도를 비율로 나타낸 것이다.
  • 미분에서 $\frac{dy}{dx}$라는 단어가 나오는데, 이는 변수 $x$에 대한 변수 $y$의 변화율을 의미한다.
  • 평균변화율(Average rate of change):
    어떤 함수 $f(x)$가 있다고 할 때, 두 점 $A(a, f(a)), B(b, f(b))$에 대하여(단, $a<b$이다.), 두 점을 이어 만든 직선의 기울기를 말한다.
  • 함수 $y=f(x)$에서 $x$의 값이 $a$부터 $a + \bigtriangleup x $까지 변한다고 하면, 아래 식을 $[a, a+\bigtriangleup x]$에서의 $y$의 평균변화율이라 한다.

$$\frac{\bigtriangleup y}{\bigtriangleup x} = \frac{f(a+\bigtriangleup x) - f(a)}{\bigtriangleup x}$$

 

5.2. 파이썬으로 조화 평균을 구해보자

V_array = np.array([20, 30, 50, 30, 60, 40, 80]) 
  • 다음과 같은 속도 데이터가 있다고 가정해보자.
  • 조화 평균을 사용하여, 평균 속력을 구해보자.

5.2.1.  Numpy로 구해보자.

  • 조화 평균은 역수 평균의 역수이므로, numpy의 Broadcast를 사용하면 쉽게 구할 수 있다.
>>> 1/(np.mean(1/V_array))
36.68122270742358

5.2.2. statistics 라이브러리 사용하기

>>> stat.harmonic_mean(V_array)
36.68122270742358

 

 

 

 

 

6. 기하 평균과 조화 평균의 주의사항

  • 0인 표본 값이 존재하는 경우, 사용할 수가 없다.
    • 기하평균은 곱의 평균이므로, 원소에 0이 있는 경우 곱하여 0이 된다.
    • 조화 평균은 역수의 평균의 역수인데, 0의 역수는 무한대로 발산한다.
  • 표본 값이 모두 양수여야 한다.
    • 기하평균은 비율, 배수에 대한 것인데, 음수가 나올 수가 없다.
    • 조화수열의 대상인 변화율의 음수는 벡터로써 방향이 반대를 의미한다.
  • 변수가 비율 혹은 배수이지만, 각 표본 값이 독립적일 때 사용할 수 있다.
    • 표본 값끼리 곱했을 때, 어떠한 의미도 갖지 않아야 서로 독립적이라 할 수 있다.

 

728x90
반응형

+ Recent posts