Study/머신러닝

머신러닝 Advanced_ 전처리 기법

김 도경 2024. 11. 5. 13:05

[2024.11.05] 필수 온라인 강의 Part16 Machine Learning Advanced CH02 전처리 기법

데이터 전처리의 이론

 

  • 데이터 전처리
     - 데이터의 특성을 이해하고 수정하여 품질을 향상시키는 과정
     - 머신러닝 모델의 성능을 향상시킴과 더불어, 모델의 해석 가능성을 높이는 역할
     - 필요한 부분은 더 수집을 하고, 불필요한 부분은 제거하기도 하고 수정하기도 함
     - 모델을 선택학습하는 관점에서 모델마다 요구하는 것이 다름 : 선형회귀- 정규분포/ kkn - 데이터 스케일을 맞춰야함 포멧을 수정

  • 데이터 전처리 프로세스
     - 데이터 분석(EDA)
     - 결측치 처리, 이상치 처리
     - 연속형 변수, 범주형 변수 처리
     - 파생변수 생성
     - 변수 선택

  • 데이터 전처리의 중요성
    - 편향을 제거하며, 이를 통해 신뢰성 높은 모델을 제작 가능
           - 선형회귀 모델 및 거리기반 알고리즘(KNN)은 결측치 및 이상치에 영향
           - 아래와 그림과 같이 이상치의 존재는 선형 회귀선을 왜곡시키는 원인
           - 데이터 전처리는 신뢰성과 안정성을 높이는 모델 제작에 도움
    - 알고리즘 모델 성능에 영향을 끼치는 중요 변수나 패턴을 찾기가 용이
           - 알고리즘 모델에 있어서 중요한 변수나 패턴을 찾는 일은 모델의 복잡도를 낮추고, 과적합을 방지하는 역할
           - 더 나아가 올바른 모델의 해석을 가능케 하여 모델의 안정화와 신뢰성을 높임
           - > 데이터 전처리가 선행되지 않으면 데이터에 편향과 같은 불균형을 만들어내고 모델의 해석을 방해
    - 데이터 시각화가 용이하여 데이터 및 모델 해석에 있어서 편리함 제공
           - 데이터 전처리 및 탐색(Exploration Data Analysis) 하는 과정에서 데이터 시각화는 필수적으로 선행
           - 분포 및 특성을 시각적으로 올바르게 표현하여 잠재적 정보 탐색이나 문제를 발견할 수 있음

  • 데이터의 종류
    - 정형(Tabular) 데이터 ***주로 사용되는 데이터***
           - 행과 열이 있는 테이블로 이루어짐
           - 관측 값을 나타내는 행(Row)과 속성(Attribute)을 나타내는 열(Column)로 구성
           - 관계형 데이터베이스, 스프레드시트(CSV, Excel)에서 2차원 구조로 활용
    - 비정형(Unstructured) 데이터
           - 구조화 되지 않은 데이터
           - 정형 데이터와는 반대로 숫자, 기호, 이모티콘 등 비구조적 형태로 존재하는 데이터
           - 비정형 데이터에는 자연어 텍스트, 음성, 영상, 이미지 등이 이에 해당

  • 변수
    - 측정하거나 계산할 수 있는 특성, 숫자, 수량을 의미
    - 시간이 지남에 따라 값들이 변경되기에 변수라고 명명 ( 코스피 주식에서 종목, 거래량, 시가, 종가, 고가, 저가 등이 변수)
    - 수치형 변수(Numerical & Quantitative Variable) : 숫자로 표현되는 변수
        - 연속형 변수(Continuous Variable) : 셀수 없는 변수 (소숫점을 가짐)
                 - 실수 범위 내에서 수치로 표현 가능한 연속적인 값을 가지는 변수
                 - 주식에서 시가, 종가와 같이 연속적인 실수로 표현되는 데이터
        - 이산형 변수(Discrete Variable) : 셀수 있는 변수
                 - 이산(셀 수 있는) 표현이 가능한 특징을 가지는 정수로 구성된 변수
                 - 수치적인 의미를 가지지만 소수의 형태로 표현되지 못하는 경우

    - 범주형 변수(Categorical Variable)  : 숫자로 표현되지 않는 변수
        - 명목형 변수(Nominal Variable) 
                 - 범주로 표현될 수 있는 변수
                 - 변수간 순서나 등급이 존재하지 않으며, 동등하고 독립적인 형태 (이름, 종목, 등등)
                 - 주식에서 순서가 존재하지 않는 종목과 같이 범주로 표현가능한 데이터
        - 서열(순위)형 변수(Ordinal Variable) : 순서가 있는 변수
                 - 순위 또는 등급을 가지는 변수 (등급, 순위, 등등)

  • 결측치
    - 데이터에서 누락(결측)된 관측치
    - 데이터의 손실과 더불어, 분포를 왜곡시켜 편향을 야기시키는 원인
    - N/A (Not Available) / NaN (Not a Number) / NULL / Empty / ? 등의 값으로 표현

  • 결측치(Missing Value) 
  • 결측치 매커니즘
    - 완전 무작위 결측(MCAR: Missing Completely At Random)
         - 가장 단순하면서도 가장 흔한 결측치 매커니즘
         - 결측치가 다른 변수와 상관 없이 무작위로 발생하는 경우
         - 관측치와 결측치 모두 결측 원인과 독립 : 완전한 무작위
         - 센서의 고장, 전산 오류, 사람의 실수 등의 이유로 발생 가능

    - 무작위 결측(MAR: Missing At Random)
         - 결측치가 해당 변수와는 무관하나, 다른 변수와 연관되어 있는 경우
         - 다른 변수의 관측치와 연관되어 있기 때문에 예측 모델을 통해 대체 가능 (다른 변수에 대한 의존 여부를 통해 검증 가능)
                  -  예시) 설문조사에서 특정 키에 따라 몸무게에 대한 질문에 무응답인 경우 (몸무게와 키와의 연관)

    - 비무작위 결측(MNAR: Missing Not At Random)
         - 결측의 원인이 해당 변수와 연관되어 발생
         - MCAR, MCR에 해당되지 않는 결측 유형
                 - 예) 신체 정보 관련 설문조사에서 몸무게가 비교적 높은 응답군에서 결측이 많이 발생

  • 결측치 패턴
    - 일변량 결측 패턴 (Univariate Pattern)
         - 가장 흔하게 발생하는 결측치
         - 하나의 변수에서만 결측치가 발생
         - 다른 변수와는 연관성이 없는 형태
                 - 예) 임상 실험 중 특정 하나의 집단에서만 부작용이 발생한 경우

    - 단조 결측 패턴 (Monotone Pattern)
         - 일정하고 단조적인 패턴으로 결측치가 발생
         - 임상 실험과 같은 종단(Longitudinal Study) 연구에서 많이 발생
                - 예) 나이에 따른 사망, 악화 등 일정 개체에서 중도 탈락(Drop-Out)이 발생

    - 일반 결측 패턴 (General & Non-Monotone Pattern)
         - 단조적이지 않은 형태의 결측치
         - 다른 변수와의 관계에 따라 나타날 수 있는 비선형 결측 패턴 형태
                - 예) 알 수 없는 이유로 특정 나이에 따라 질병이 드물게 발생하는 경우

    - 규칙 결측 패턴 (Planned Pattern)
         - 결측의 원인이 일정한 패턴 및 규칙에 따라 발생
         - 특정 상황에 의해 의도적이며 규칙적으로 결측치를 생성하는 경우


  • 결측치 처리

    - 삭제 (Deletion)
         - 장점
             - 결측치를 처리하기 편리
             - 결측치가 데이터 왜곡의 원인이라면, 삭제로 인한 왜곡 방지와 알고리즘 모델 성능 향상 기대
         - 단점
             - 결측치에도 데이터 특성이 유효한 경우가 존재
             - 삭제된 데이터로 인하여 남은 데이터에 편향이 만들어질 가능성
             - 관측치의 정보가 줄어들게 되므로 알고리즘 모델에 악영향

    - 목록 삭제 (Listwise Deletion)
         - 변수에 결측치가 존재하는 해당 행을(Row) 제거
         - 모든 변수가 결측치 없이 채워진 형태의 행만 분석 및 모델링에 활용하는 방식
         - 쉬운 방식이지만, 데이터 손실등의 문제로 꼭 고려를 해야한다.

    - 열 삭제 (Dropping Columns & Features)
         - 변수에 결측치가 존재하는 해당 열을(Column) 제거


    - 대체 (Imputation)
         - 결측치를 관측치의 통계값(평균값, 최빈값, 중앙값)으로 대체
         - 장점
             - 대체된 값은 데이터의 통계 특성을 반영하므로 정보를 안정적으로 보존 가능
             - 데이터의 샘플이 그대로 유지되기 때문에 알고리즘 모델에 원본 크기로 적용 가능
         - 단점
             - 통계값으로 인해 변수의 분산이 감소하여 변수간 상관관계가 낮아지는 문제 발생

    - 회귀 대체 (Regression)
      - 변수간의 관계를 파악 후, 예측하여 결측치를 대체하는 방식
         - 장점
             - 변수 간의 연관성을 활용해 결측치를 예측하기 때문에 많은 데이터 정보를 활용 가능
             - 삭제(Deletion) 및 대체(Imputation) 방법에 비해 실제 데이터와의 유사 가능성이 높음
         - 단점
             - 예측 모델을 생성해야 하는 어려움

  • 이상치(Outlier)
    - 변수의 분포상 비정상적으로 극단적이며, 일반적인 데이터 패턴을 벗어나는 관측치
    - 평균값과 같은 통계적 중요 결과를 왜곡 시키는 원인

    - 이상치가 나타나는 다양한 원인
          - 데이터의 전송 오류, 위변조 해킹, 수집 기기의 오작동 등으로 이상치가 발생
          - 수동으로 데이터를 입력하는 담당자의 실수로 인한 이상치 생성 (휴먼 에러)
          - 편법이나 조작으로 이상치 생성 (주식 작전세력, 부동산 업&다운 거래, 분양가 상한제)

    - 이상치 탐지 및 제거가 필요한 이유
          - 데이터 분석 및 탐색 시에 패턴과 인사이트를 도출하는 것이 가능
          - 데이터로부터 도출된 명확한 패턴과 인사이트는 올바른 의사결정에 도움
          - 데이터 전체를 왜곡시키는 이상치를 제거하여 모델의 안정성 향상을 기대
          - 대표적으로 회귀분석은 이상치에 민감 (아래 그림)

  • 이상치(Outlier)
  • 이상치(Outlier) 종류
    - 점 이상치 (Point, Global Outlier)
         - 대부분의 관측치들과 동떨어진 형태의 이상치
         - 변수의 분포상 비정상적인 패턴을 가졌기 때문에 탐지가 어렵지 않은 케이스

    - 상황적 이상치 (Contextual Outlier)
         - 정상적인 데이터 패턴이라도 상황(Context)에 따라 이상치로 변환되는 형태
         - 상황은 주로 시점에 따라 바뀌기 때문에 시계열 데이터에서 주로 나타나는 케이스

    - 집단적 이상치 (Collective Outlier)
         - 데이터 분포에서 집단적으로 편차가 이탈되어 이상치로 간주
         - 관측치 개별로 보았을 때는 이상치처럼 보이지 않는 것이 특징
                - 예) 스팸 메일은 일반 메일과 형태가 유사하지만, 정상적이지 못한 메일


  • 이상치(Outlier) 탐지
    - Z-Score
         - 평균으로부터의 표준편차 거리 단위를 측정(Z = x - mean / standard deviation) 하여 이상치를 탐지하는 방법
         - Z 값을 측정하여 이상치를 탐색
         - Z 값이 2.5~3정도 수치가 나오면 이상치로 판별
         - 데이터가 정규분포를 따른다고 가정
         - 장점
              - 데이터에서 이상치를 간단하게 탐지 가능
         - 단점
              - 표준화된 점수를 도출하는 과정이기 때문에 데이터가 정규분포를 따르지 않는 경우 효과적이지 않을 가능성
              - Z-Score를 구성하는 평균과 표준편차 자체가 이상치에 민감 (Modified Z-Score를 이용하는 방법도 존재)

    - IQR (Inter Quartile Range)
         - IQR은 상위 75%와 하위 25% 사이의 범위
         - IQR은 Q3(제 3사분위)에서 Q1(제 1사분위)를 뺀 위치
         - Q1 - 1.5 * IQR 및 Q3 + 1.5 * IQR 을 최극단의 이상치로 판단하여 처리
         - 장점
              - 데이터의 중앙값과 분포만을 통해 이상치를 식별하므로 직관적
              - 표준편차 대신 백분위수(25%, 75%)를 사용하므로 이상치에 강건한 특징
              - 데이터가 몰려 있는 경우라도 분포를 활용하기 때문에 효과적
         - 단점
              - 이상치의 식별 기준이 백분위수에 의존
              - 왜도가 심하거나 정규분포를 따르지 않는 경우 제대로 작동하지 않을 가능성


  • 이상치(Outlier) 처리
    - 결측치 처리와 마찬가지로 데이터를 삭제, 대체하는 방식으로 구분
    - 변환(Transformation)을 통해서 단위(스케일)를 변환 시키는 방식도 존재

    - 삭제(Deletion)
         - 이상치에 해당하는 데이터 포인트(값, 인스턴스)를 제거하는 방법
         - 다만, 이상치는 중요한 정보를 내포하고 있는 경우도 존재
         - 이상치는 도메인 지식에 기반하여 객관적인 상황에 맞게 제거하는 것이 필요

    - 대체(Imputation)
         - 통계치(평균, 중앙, 최빈)으로 이상치를 대체
         - 상한값(Upper boundary), 하한값(Lower boundary) 정해놓고 이상치가 경계를 넘어갈 때 대체
         - 회귀 및 KNN 등의 거리기반 알고리즘 등을 이용해 이상치를 예측 및 대체하는 방식도 존재

    - 변환(Transformation)
         - 변수 내에서 매우 큰 값을 가진 이상치를 완화시킬 수 있는 방법 (큰 값을 작은 값으로 변환시키는 원리)
         - 이외에도 제곱근 변환으로 대체하여 사용 가능

 

카테고리와 기타 변수 다루기의 이론
  • 연속형 변수 다루기
  • 함수 변환
    - 로그 변환 (Log Transform)
          - 큰 값은 많이, 작은 값은 작게 줄임
          - 비대칭(Right-Skewed)된 임의의 분포를 정규분포에 가깝게 전환시키는데 도움
          - 데이터의 정규성은 모델(회귀)의 성능을 향상시키는데 도움
          - 데이터의 스케일을 작게 만들어 데이터 간의 편차를 줄이는 효과
          - 로그변환을 적용했을 때 비대칭 분포가 제거되는 것을 확인
          - 데이터의 이상치 완화에도 효율적이지만 0이나 음수를 적용하는 것이 불가능

    - 제곱근 변환 (Square Root Transform)

          - 로그 변환과의 차이점은 변환 시 데이터 편차의 강도
          - 비대칭이 (Right-Skewed) 강한 경우 로그 변환, 약한 경우 제곱근 변환이 유리
          - 왼쪽으로 치우쳐진 데이터 분포에 활용 시 더 치우치는 부작용 발생

    - 거듭제곱 변환 (Power Transform)
          - 변수에 거듭제곱 함수를 적용하여 전환
          - 작은 값을 크게, 큰 값은 더 크게 바꿔주는 역할

    - Box-Cox 변환 (Box-Cox Transform)
          - 람다(λ) 파라미터 값을 통해 다양한 변환을 하는 방법
          - 목적에 맞게 최적 람다(λ) 파라미터 값을 찾는 것이 중요

  • 스케일링 (Scaling)
    - 스케일링을 통해서 동일한 수치 범위로 변경
    - 예시) 키는 170(cm), 몸무게는 56(kg)일 때, 스케일의 차이가 존재

    - 스케일링(Scaling) 이 필요한 이유
          - 수치형 독립 변수들의 수치 범위가 다르게 존재하면 종속 변수에 각기 다르게 영향을 미침
          - 수치 범위가 큰 변수일수록 다른 변수에 비해 더 중요하게 인식될 수 있음
          - 독립 변수들의 영향력을 동등하게 변환시켜 KNN과 같은 거리기반 알고리즘에 효과
          - KNN은 벡터간 거리(유클리드&맨하튼 거리법)를 측정하여 데이터를 분류하는 방식
          - 변수들이 동일한 범위로 스케일링이 되어 있지 않다면, 결과가 올바르지 않은 리스크 존재

    - Min-Max 스케일링 (정규화)
          - 연속형 변수의 수치 범위를 0-1 사이로 전환
          - 각기 다른 연속형 변수의 수치 범위를 통일할 때 사용
          - 최소, 최대값을 기준으로 데이터의 상대적 위치를 구성
          - Min-Max 스케일링은 모든 수치 범위가 같아지므로 이상치에 취약한 부분이 존재
                - 예) 1,000개의 관측치 중에 999개는 0~30 사이의 범위에 분포해 있지만, 나머지 1개의 값이 100인 경우

    - 표준화 (Standardization)
          - 변수의 수치 범위(스케일)를 평균이 0, 표준편차가 1이 되도록 변경(Z-score)
          - 평균과의 거리를 표준편차로 나누기(정규분포의 표준화 과정)
          - 즉, 평균에 가까워질수록 0으로, 평균에서 멀어질수록 큰 값으로 변환
          - Min-Max 스케일링과 마찬가지로 변수들의 수치 범위를 축소시키기 때문에 거리기반 알고리즘에서 장점

    - 로버스트 스케일링 (Robust Scaling)
          - IQR을 기준으로 변환 (중앙값과의 거리를 IQR로 나누기)
          - 즉, 중앙값에 가까워질수록 0으로, 중앙값에서 멀어질수록 큰 값으로 변환
          - 표준화와 다르게 평균 대신 중앙값을 활용하므로 이상치에 강건한 효과
          - 표준화는 데이터에 이상치가 존재하면 평균이나 표준편차에 큰 영향

    - 구간화 (Binning, Bucketing)
          - 수치형 변수를 범주형 변수로 전환시키는 방법
          - 데이터가 범주화 되기 때문에 학습 모델의 복잡도가 줄어드는 장점
          - 등간격(동일 길이), 등빈도(동일 개수)로 나누어 구간화를 진행
          - 수치 값들이 기준에 따라 범주로 통일 되기 때문에 이상치를 완화
          - 데이터의 ‘구분’이 가능해지기 때문에 데이터 및 모델해석에 용이

  • 범주형 변수 다루기
  • 범주형 변수 변환

    - 원-핫 인코딩(One-Hot Encoding)
          - 범주 변수를 0과 1로만 구성된 이진(binary) 벡터 형태로 변환하는 방법
          - 고유 범주 변수 크기와 동일한 이진 벡터를 생성
          - 범주에 해당하는 값만 1 설정, 나머지는 모두 0 변환
          - 장점
                - 변수의 이진화를 통해 컴퓨터가 인식하는 것에 적합
                - 알고리즘 모델이 변수의 의미를 정확하게 파악 가능
          - 단점
                - 고유 범주 변수의 크기가 늘어날 때마다 희소 벡터 차원이 늘어나는 문제점이 존재 : feature가 엄청 늘어남
                - 벡터의 차원이 늘어나면 메모리 및 연산에 악영향
                - 차원의 저주 발생

    - 레이블 인코딩(Label Encoding)
          - 이진 벡터로 표현하는 원-핫 인코딩과 다르게 각 범주를 정수로 표현 : 가장 잘 쓸만함
          - 하나의 변수(컬럼)으로 모든 범주 표현 가능
          - 순서(Ordinal)가 존재하는 변수들에 적용할 경우 효율적
          - 장점
                - 범주 당 정수로 간단하게 변환 가능
                - 하나의 변수로 표현 가능하기 때문에 메모리 관리 측면에서 효율적
          - 단점
                - 순서가 아닌 값을 순서로 인식할 수도 있는 문제가 발생

    - 빈도 인코딩(Frequency Encoding)
          - 고유 범주의 빈도 값을 인코딩
          - 빈도가 높을수록 높은 정숫값을, 빈도가 낮을수록 낮은 정숫값을 부여 받는 형태
          - 빈도 정보가 유지되어 학습에 적용시킬 수 있는 것이 특징
          - Count Encoding이라고도 불림
          - 장점
                - 빈도라는 수치적인 의미를 변수에 부여 가능
                - 하나의 변수(컬럼)로 표현 가능하기 때문에 메모리 관리 측면에서 효율적
          - 단점
                - 다른 특성의 의미를 지니고 있어도 빈도가 같으면, 다른 범주간 의미가 동일하게 인식될 가능성이 존재

    - 타겟 인코딩(Target Encoding)
          - 특정(타겟) 변수를 통계량(평균)으로 인코딩하는 방식
          - 범주형 변수가 연속(Continuous)적인 특성을 가진 값으로 변환
          - 범주형 변수가 특정 타겟 변수와 어떤 관련성이 있는지 파악하기 위한 목적도 존재
          - Mean Encoding이라고도 불림
          - 장점
                - 범주 간 수치적인 의미를 변수에 부여 가능
                - 타겟 변수라는 추가적인 정보를 가진 변수에 의존하므로 추가된 정보를 알고리즘에 입력 가능
                - 하나의 변수로 표현 가능하기 때문에 메모리 관리 측면에서 효율적
          - 단점
                - 타겟 변수에 이상치가 존재하거나, 타겟 변수의 범주 종류가 소수라면 과적합 가능성이 존재
                - 학습과 검증 데이터를 분할했을 때, 타겟 변수 특성이 학습 데이터셋에서 이미 노출되었기 때문에 Data-Leakage 발생
         - 타겟 인코딩(Target Encoding)의 과적합 방지
              - K-Fold (교차 검증)
                  - 데이터 샘플(학습 데이터) 내에서도 다시 여러 데이터 샘플(Fold)로 재구성하여 각각 샘플을 타겟 인코딩을 하는 방식
                  - 타겟 인코딩 값이 보다 다양하게 생성


  • 인코딩 패키지 소개
    - scikit-learn
          - 전처리, 알고리즘, 평가 등을 지원하는 Python 머신러닝 종합 패키지
          - 기본 인코딩 (One Hot Encoding, Label Encoding, Target Encoding) API 지원
    - category_encoders
          - 범주형 데이터를 처리하기 위한 Python 패키지
          - scikit-learn과 호환되는 인터페이스
          - 전처리를 위한 다양한 인코딩 API 지원

 

데이터 전처리 실습

 

- 정형 데이터에서 전처리
- 전처리에서 사용되는 변수의 변환 기법들을 살펴보고 적용
- 연속형 & 범주형 변수를 변환하여 알고리즘 모델에 적절히 활용하게 기반 만들기

  • 실습 데이터
    - Yahoo Finance OHLCV 데이터셋
       - Yahoo Finance는 주식, 환율과 같은 금융 정보를 제공하는 플랫폼

    - OHLCV 데이터셋
       - 양초와 비슷하게 생겨 캔들스틱(Candlestick) 차트
         - (O)pen (시가): 주식의 거래가 시작된 가격
         - (H)igh (고가): 특정 기간 동안 주식의 가격 중 최고점
         - (C)lose (종가): 주식의 거래가 마감된 가격
         - (L)ow (저가): 특정 기간 동안 주식의 가격 중 최저점
         - (V)Volume (거래량): 특정 기간 동안 거래된 주식의 총 수량

- 필요 패키지 로드

# 계산
import numpy as np

# 전처리
from scipy.special import boxcox1p
from sklearn.preprocessing import MinMaxScaler, StandardScaler, RobustScaler
from sklearn.preprocessing import OneHotEncoder, LabelEncoder

from category_encoders import CountEncoder

# 출력 및 시각화
import pprint
import matplotlib.pyplot as plt
import matplotlib.dates as mpdates
import seaborn as sns

from IPython.display import display
from mplfinance.original_flavor import candlestick_ohlc

# 데이터셋 로드 및 정제
import pandas as pd
import os

# 코드 셀 실행 후 경고를 무시
import warnings
warnings.filterwarnings(action='ignore')

# 구글드라이브 연동
from google.colab import drive
drive.mount('/content/drive')

 

- 데이터셋 불러오기

# 데이터셋 불러오기
OHCLV_file = "OHLCV.parquet"
OHLCV_data = pd.read_parquet(OHCLV_file)

# 샘플 데이터 출력 해보기
display(OHLCV_data)

 

- 데이터셋의 날짜 범위와 컬럼 확인

display(f"데이터셋 날짜 범위: {OHLCV_data.index.min()} ~ {OHLCV_data.index.max()}")

display(f"데이터셋 컬럼: {tuple(OHLCV_data.columns)}")

 

- 데이터를 캔들스틱 차트로 그려서 보기

# 삼성전자(005930) 종목을 선택해서 OHCL 캔들스틱 차트를 그려봄
# .copy()를 하지 않으면, 추후 원본 데이터프레임의 변화에 종속될 수 있으니 복사.
samsung_data = OHLCV_data[OHLCV_data["code"] == '005930'].copy()

# index가 날짜로 설정되어 있으니 하나의 컬럼으로 추가.
samsung_data['Date'] = samsung_data.index

# 데이터가 너무 방대하니 2020-01-01 ~ 2020-03-01
samsung_data = samsung_data.loc["2020-01-01":"2020-03-01"]

# 날짜 형식을 숫자로 변환해야 시각화를 하기 수월함
samsung_data['Date'] = samsung_data['Date'].map(mpdates.date2num)

# 캔들스틱 차트를 그리기 위해서 필요한 데이터만 뽑을 수 있음
samsung_data = samsung_data[['Date', 'Open', 'High', 'Low', 'Close']]

fig, ax = plt.subplots(figsize=(10, 5))

# 아래 함수를 이용해 캔들스틱 차트
candlestick_ohlc(ax, samsung_data.values, width=0.6, colorup='g', colordown='r')

# X 축에 주간 구분선을 추가
ax.xaxis.set_major_locator(mpdates.WeekdayLocator(mpdates.MONDAY))
ax.xaxis.set_minor_locator(mpdates.DayLocator())

# 아까 숫자로 변환한 날짜 데이터를 보기 좋게 형식 변환
ax.xaxis.set_major_formatter(mpdates.DateFormatter('%Y-%m-%d'))

# 필요한 범례들을 작성
plt.title('Samsung Electronics Co., Ltd.')
plt.xlabel('Date')
plt.ylabel('Price')
plt.xticks(rotation=45)

plt.show()

 

- 데이터 분석(시각화 및 통계치)

# 데이터셋에 존재하는 종목 수 확인
display(f"데이터셋 종목 수: {len(OHLCV_data['code'].unique())}")

# 기본 통계치 보기
display(OHLCV_data.describe())

# 컬럼에 대한 기본 정보 info 함수
display(OHLCV_data.info())

 

- 결측치 다루기

# isna() 함수를 이용해 결측치 확인
display(OHLCV_data[OHLCV_data["Change"].isna() == True])

 

-이상치 다루기

# 삼성전자 종목을 기준
samsung_data = OHLCV_data[OHLCV_data["code"] == '005930'].copy()

# 박스플롯을 그려서 이상치 확인
sns.set(style="whitegrid")
plt.figure(figsize=(10, 5))
sns.boxplot(data=samsung_data[["Open", "High", "Low", "Close"]], orient="v")
plt.title("Samsung Electronics Co., Ltd.")
plt.ylabel("Price")
plt.show()

 

- 선형 차트로 이상치 확인하기

# 삼성전자의 2018년에서 2023년까지의 시가 선형 차트
sns.set(style="whitegrid")
plt.figure(figsize=(10, 5))
sns.lineplot(data=samsung_data['Open'], label='Open Price')

plt.title('Samsung Electronics Co., Ltd.')
plt.xlabel('Date')
plt.ylabel('Open Price')
plt.xticks(rotation=45)

plt.show()

print("\n")

# 이상치의 존재
# 어떤 수치인지 표로 추출
display(samsung_data[samsung_data['Open'] <= 0])

 

- 이상치를 평균으로 대체

# 이상치를 평균으로 대체(Imputation) : 2018년 04월 25일 ~ 4월 27일까지의 시가 평균으로 대체
imputation = int(samsung_data.loc["2018-04-25":"2018-04-27"]["Open"].mean())
samsung_data.loc[samsung_data["Open"] <= 0, "Open"] = imputation
# 이상치가 대체된것을 확인
sns.set(style="whitegrid")
plt.figure(figsize=(10, 5))
sns.lineplot(data=samsung_data['Open'], label='Open Price')

plt.title('Samsung Electronics Co., Ltd.')
plt.xlabel('Date')
plt.ylabel('Open Price')
plt.xticks(rotation=45)

plt.show()

 

  • 연속형 변수 다루기
    연속형 변수 (Continous Variable)
       - 실수 범위 내에서 수치로 표현 가능한 연속적인 값을 가지는 변수
       - 키, 거리, 온도, 습도와 같이 수치적인 정보
    함수 변환 (Function Transformation)
       - 로그 & 제곱근, 제곱 등의 수학 수식을 통해서 비대칭된 임의의 분포를 변화
       - 로그 및 제곱근 변환은 데이터의 스케일을 작고 균일하게 만들며, 반대로 제곱 변환은 데이터의 스케일을 크게만듬

- 로그 변환 (Log Transformation)
   - 데이터의 스케일을 작고, 균일하게 만들어 데이터간 편차를 줄이는 효과를 줄여줌
   - 큰 값을 작게 변환시키기 때문에 이상치의 완화를 기대

# LG전자(066570) 종목을 선택해서 OHCL 함수 변환 실습
lg_data = OHLCV_data[OHLCV_data["code"] == "066570"].copy()

# 시가를 기준으로 히스토그램
sns.set(style="whitegrid")
plt.figure(figsize=(10, 5))
sns.histplot(lg_data["Open"], bins=10, kde=True)
plt.title('LG Electronics Inc.')
plt.xlabel('Open Price')
plt.ylabel('Frequency')
plt.show()
# np.log(x)를 사용해도 되지만, 0 및 음수 값에 대한 대응을 위해 log1p를 사용
lg_data["Log" + "Open"] = np.log1p(lg_data["Open"])

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(18, 6))
sns.set(style="whitegrid")

for i, col in enumerate(["Open", "LogOpen"]):
    sns.histplot(lg_data[col], bins=10, kde=True, ax=axes[i])
    axes[i].set_title(f"{col}")
    axes[i].set_xlabel(f"{col} Price")
    axes[i].set_ylabel("Frequency")

plt.tight_layout()
plt.show()

 

- 제곱근 변환 (Square Root Transformation)
      - 관측값의 제곱근으로 변환
      - 제곱근 변환은 비교적 큰 관측값에 영향을 덜 주기 때문에 로그 변환의 분포에 비하여 완화되는 경향

# 로그 변환과 비슷하지만 스케일이 조금 더 작게 변화시키는 효과
# np.sqrt(x)를 사용
lg_data["Sqrt" + "Open"] = np.sqrt(lg_data["Open"])

fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(18, 6))
sns.set(style="whitegrid")

for i, col in enumerate(["Open", "SqrtOpen"]):
    sns.histplot(lg_data[col], bins=10, kde=True, ax=axes[i])
    axes[i].set_title(f"{col}")
    axes[i].set_xlabel(f"{col} Price")
    axes[i].set_ylabel("Frequency")

plt.tight_layout()
plt.show()

 

- 로그변환과 제곱근 변환을 함께 하기 : 그래프를 함께 그려서 확인을 하기

# 로그&제곱근 변환을 비교
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(18, 6))
sns.set(style="whitegrid")

for i, col in enumerate(["Open", "LogOpen", "SqrtOpen"]):
    sns.histplot(lg_data[col], bins=10, kde=True, ax=axes[i])
    axes[i].set_title(f"{col}")
    axes[i].set_xlabel(f"{col} Price")
    axes[i].set_ylabel("Frequency")

plt.tight_layout()
plt.show()

 

-Box-Cox 변환 (Box-Cox Transformation)
      - 앞선 로그&제곱근 변환은 모두 왜도(Skewness)를 줄여 정규분포에 가깝게 변환하는 방법
      - 다만, 오른쪽으로 치우쳐진 데이터 분포에 활용시 더 심하게 오른쪽으로 치우치는 부작용이 발생
      - 당연히 큰 값을 작게 만들기 때문에 큰 값의 분포가 많아지기 때문
      - 분포가 치우친 방향에 무관하게 Box-Cox 변환을 이용하면 손쉽게 조절이 가능

samsung_data["Log" + "Open"] = np.log1p(samsung_data["Open"])
samsung_data["Sqrt" + "Open"] = np.sqrt(samsung_data["Open"])

# 로그&제곱근 변환을 히스토그램을 그려 비교
fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(18, 6))
sns.set(style="whitegrid")

for i, col in enumerate(["Open", "LogOpen", "SqrtOpen"]):
    sns.histplot(samsung_data[col], bins=10, kde=True, ax=axes[i])
    axes[i].set_title(f"{col}")
    axes[i].set_xlabel(f"{col} Price")
    axes[i].set_ylabel("Frequency")

plt.tight_layout()
plt.show()
# Box-Cox변환은 람다 파라미터를 조절해 가면서 유연하게 분포를 변화
# 사용자가 정하는 주관적인 파라미터이기 때문에 정해진 값은 없지만 저는 1.0에서 2.5까지 설정
lambdas = [[-3.0, -2.0, -1.0], [1.0, 2.0, 3.0]]

fig, axes = plt.subplots(nrows=2, ncols=3, figsize=(18, 6))
sns.set(style="whitegrid")

# 삼성전자의 Open(시가) 컬럼에 Box-Cox를 적용
for i in range(0, 2):
    for j in range(0, 3):
        col = "BoxCox " + "Open " + str(lambdas[i][j])
        samsung_data[col] = boxcox1p(samsung_data["Open"], lambdas[i][j])
        sns.histplot(samsung_data[col], bins=10, kde=True, ax=axes[i, j])
        axes[i, j].set_title(f"{col}")
        axes[i, j].set_xlabel(f"{col} Price")
        axes[i, j].set_ylabel("Frequency")

plt.tight_layout()
plt.show()

 

 

  • 스케일링 (Scaling)
    - 스케일링은 연속형 변수들의 수치 범위를 동등하게 변화시키는 과정

- Min-Max Scaling
      - 수치형 변수를 최소, 최댓값을 기준으로 데이터의 상대적 위치를 구성

      - 각기 다른 수치형 변수의 수치 범위를 통일할 때 사용

# 이번엔 포스코 홀딩스(005490) 종목을 선택
posco_data = OHLCV_data[OHLCV_data["code"] == "005490"].copy()

# 이번엔 시가(Open)와 거래량(Volume) 컬럼을 선택
display(posco_data[["Open", "Volume"]].describe())
# 위의 두 컬럼을 Min-Max 스케일링
scaler = MinMaxScaler()
posco_data[["MinMaxOpen", "MinMaxVolume"]] = scaler.fit_transform(posco_data[["Open", "Volume"]])

# 산점도
# X, Y축이 0-1사이로 구
sns.scatterplot(data=posco_data, x="MinMaxOpen", y="MinMaxVolume", color='indigo', marker='o', s=80)

# 통계치 확인 :  max 1, min 0로 변환
display(posco_data[["MinMaxOpen", "MinMaxVolume"]].describe())

 

- 표준화 (Standardization)
      - 변수의 수치 범위를 평균이 0, 표준편차가 1이 되도록 변경하는 방법
      - 평균과의 거리를 표준편차로 나눠줌
      - 평균에 가까워질수록 0, 평균에서 멀어질 수록 큰 값으로 변화

# 포스코 홀딩스(005490) 종목
posco_data = OHLCV_data[OHLCV_data["code"] == "005490"].copy()
# 두 컬럼을 표준화
scaler = StandardScaler()
posco_data[["StdzationOpen", "StdzationVolume"]] = scaler.fit_transform(posco_data[["Open", "Volume"]])

# 산점도
sns.scatterplot(data=posco_data, x="StdzationOpen", y="StdzationVolume", color='indigo', marker='o', s=80)

# 통계치 : 평균은 0, 표준편차는 1로 변환
display(posco_data[["StdzationOpen", "StdzationVolume"]].describe())

 

- 로버스트 스케일링 (Robust Scaling)
      - IQR을 기준으로 수치 범위를 변환
      - 중앙값에 가까워질수록 0, 멀어질수록 큰 값으로 변환

# 003535 종목을 선택
stock_data = OHLCV_data[OHLCV_data["code"] == "003535"].copy()
# 위의 두 컬럼을 로버스트 스케일링
scaler = RobustScaler()
stock_data[["RobustOpen", "RobustVolume"]] = scaler.fit_transform(stock_data[["Open", "Volume"]])

# 산점도
sns.scatterplot(data=stock_data, x="RobustOpen", y="RobustVolume", color='indigo', marker='o', s=80)

 

- 로버스트 스케일링과 표준화 비교

# 표준화와 로버스트 스케일링의 확률밀도 함수를 각각 그려 비교해 봅시다.
scaler = StandardScaler()
stock_data[["StdzationOpen", "StdzationVolume"]] = scaler.fit_transform(stock_data[["Open", "Volume"]])

plt.figure(figsize=(8, 6))

sns.kdeplot(data=stock_data["RobustOpen"], label="RobustOpen", color="red", shade=True)
sns.kdeplot(data=stock_data["StdzationOpen"], label="StdzationOpen", color="indigo", shade=True)

plt.title('RobustOpen vs StdzationOpen')
plt.xlabel('Scaled Value')
plt.ylabel('Density')
plt.legend()
plt.show()

 

  • 구간화 (Binning, Bucketing)
    - 연속형 변수를 범주형 변수로 전환시키는 방법
    -  데이터가 범주화 되기 때문에 학습 모델의 복잡도가 줄어드는 장점
# 이번에는 종가(Close)를 기준
# 주식 애널리스트가 종가를 보기 쉽게 Low, Medium, High로 3가지로 나눠서 본다고 가정
posco_data = OHLCV_data[OHLCV_data["code"] == "005490"].copy()

# 각 구간을 설정
bins = [0, 300000, 500000, 1000000]

# 구간마다 레이블
labels = ["Low", "Medium", "High"]

# Binning을 수행
posco_data["BinClose"] = pd.cut(posco_data["Close"], bins=bins, labels=labels)

display(posco_data)

posco_data["BinClose"].value_counts()

 

  • 범주형 변수 다루기
    범주형 변수 (Categorical Variable)
    - 데이터를 이산적 범주로 나타낼 수 있는 변수
    - 이중 순서가 존재하는 변수를 순서형 변수(Ordinal Variable)
    - 순서 없이 동등한 범주를 나타내는 변수는 명목형 변수(Nominal Variable)
    - 범주형 변수들을 바로 컴퓨터가 인식하기가 어려움 : 컴퓨터가 이해하기 쉬운 정수형태로 바꿔주는 작업이 필요
        -> 이런 작업을 인코딩

- 원-핫 인코딩 (One-hot Encoding)
  - 범주형 변수를 0과 1로만 구성된 이진(Binary) 형태로 변환하는 방법
  - 변수의 고유(Unique) 범주 크기와 동일한 이진 벡터가 생성
  - 범주에 해당하는 값은 1, 나머지는 0으로 변환

onehot_data = OHLCV_data.copy()

# scikit-learn에서 제공하는 OneHotEncoder 클래스를 활용해 종목(code)열의 One-Hot Encoding을 진행
# OneHotEncoder의 클래스 객체를 생성
encoder = OneHotEncoder()

# 원-핫 인코딩에서는 2차원 형태의 배열로 입력해야 하기 때문에 대괄호([])가 2개가 들어감
onehot = encoder.fit_transform(onehot_data[["code"]])

# 배열형태로 전환
onehot_array = onehot.toarray()

# 종목의 고유 범주수 999개인 것과 동일하게 999차원의 원-핫 인코딩 배열이 생성
# 이는 데이터셋의 규모가 커질 때 메모리에 영향을 미침
display(onehot_array)
display(f"종목 고유 범주 수 : {len(onehot_data['code'].unique())}")
display(f"원-핫 인코딩의 차원 수 : {onehot_array.shape}")
# 5개의 샘플만 확인
onehot_data_sample = onehot_data[:5]
onehot_data_sample["OnehotCode"] = onehot_array[:5].tolist()

display(onehot_data_sample)

display(f"삼성전자의 원-핫 코드: {onehot_data_sample[onehot_data_sample['code'] == '005930']['OnehotCode'][0]}")

 

- 레이블 인코딩 (Label Encoding)

     - 위의 이진 벡터로 표현하는 원-핫 인코딩과는 다르게 각 범주를 정수에 1:1로 매칭시키는 방법
     - 하나의 변수(컬럼)으로 모든 범주를 표현 가능한 장점

label_data = OHLCV_data.copy()

# scikit-learn의 LabelEncoder 클래스 객체를 생성합니다.
encoder = LabelEncoder()

# 레이블 인코딩 변환
label = encoder.fit_transform(label_data[["code"]])

# 결과를 기존 데이터셋에 추가해볼까요?
label_data["LabelCode"] = label

display(label_data)

display(f"레이블 인코딩의 범주 크기: {len(label_data['LabelCode'].unique())}")
display(f"레이블 인코딩의 범주 범위: {label_data['LabelCode'].min()} ~ {label_data['LabelCode'].max()}")

display(f"삼성전자의 인코딩 값: {label_data[label_data['code'] == '005930']['LabelCode'][0]}")
display(f"LG전자의 인코딩 값: {label_data[label_data['code'] == '066570']['LabelCode'][0]}")

 


- 빈도 인코딩 (Frequency Encoding = Count Encoding )

     - 특정 관측치의 빈도가 높을 수록 높은 정값을, 빈도가 낮을 수록 낮은 정수 값을 부여받게 되는 형태

frequency_data = OHLCV_data.copy()

# CountEncoder를 클래스 객체를 생성합니다.
# cols에는 적용할 열을 명시해줍니다.
encoder = CountEncoder(cols=['code'])

# 변환시켜줍시다.
frequency = encoder.fit_transform(frequency_data['code'])

frequency_data["FrequencyCode"] = frequency

display(frequency_data)
# 삼성전자 및 LG전자 모두 code 빈도가 동일하기 때문에 같은 값을 갖게 됨
# 어느정도 파악하셨겠지만 빈도 수라는 수치적인 의미를 반영하지 않는다면 효과적이지가 않음.
display(f"삼성전자의 인코딩 값: {frequency_data[frequency_data['code'] == '005930']['FrequencyCode'][0]}")
display(f"LG전자의 인코딩 값: {frequency_data[frequency_data['code'] == '066570']['FrequencyCode'][0]}")


- 타겟 인코딩 (Target Encoding = Mean Encoding )

     - 타겟 변수를 선정하고, 이를 집계하여 통계량(평균)으로 인코딩하는 방식

     - 범주형 변수가 연속적인 특성을 가진 값으로 변환

target_data = OHLCV_data.copy()

# 우선 pandas에서 제공하는 groupby를 통해 code열을 기준으로 Open열의 평균을 확인
target = target_data.groupby(OHLCV_data['code'])['Open'].mean()

# map을 이용해 변환
target_data['TargetCode'] = target_data['code'].map(target)

display(target_data)
display(f"삼성전자의 타겟 인코딩 값: {target_data[target_data['code'] == '005930']['TargetCode'][0]}")
display(f"LG전자의 타겟 인코딩 값: {target_data[target_data['code'] == '066570']['TargetCode'][0]}")