[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]}")
'Study > 머신러닝' 카테고리의 다른 글
머신러닝 Advanced_ 변수 선택 방법 (1) | 2024.11.06 |
---|---|
머신러닝 Advanced_ 파생 변수 만들기 (12) | 2024.11.05 |
머신러닝 BASIC _ 모델 평가와 개선 (3) | 2024.11.04 |
머신러닝 BASIC _ 다양한 데이터 처리 (13) | 2024.11.01 |
머신러닝 BASIC _ 비지도학습 방법론 (2) | 2024.10.31 |