본문 바로가기
Study/Data전처리 및 통계

Pandas(6)-Scaler 및 Normalization

by 왕방개 2024. 2. 16.

**데이터 전처리
1.단위 확산

2.자료형 변환
-> astype('변환하고자 하는 자료형')

-> 데이터 가져오기
mpg = pd.read_csv('./data/auto-mpg.csv', header=None)
mpg.columns = ['mpg', 'cylinders', 'displacement', 'horsepower', 'weight',
              'acceleration', 'model year', 'origin', 'name']


#horsepower는 마력으로 숫자 데이터 인데 현재는 object
#origin은 현재 1,2,3으로 만들어진 숫자인데 실제 의미는 USA, EU, JPA을 나타냄
#model year는 제조년도를 나타내는데 현재는 숫자로 되어있음.

-> horsepower를 숫자 자료형으로 변경
#horsepower의 모든 종류의 값을 조회
print(mpg['horsepower'].unique())

#이 방법은 데이터가 아주 많거나 데이터의 종류가 연속형 형태면 쉽지 않습니다.

 

#데이터 많을 떄는 먼저 변경 - 예외가 발생하면 그 예외를 확인하면 이유를 알 수 있음

#?을 NaN치환

mpg['horsepower'].replace('?',np.nan,inplace=True)

#NaN제거

mpg.dropna(subset=['horsepower'],axis=0,inplace=True)

#자료형 변환

mpg['horsepower']=mpg['horsepower'].astype('float')

mpg.info()

=>origin 의 데이터를 1->USA , 2->EU, 3->JAPAN 으로 치환

#범주형 데이터를 의미를 갖는 문자열로 변환: 출력이 목적

#replace 에 dict을 대입하면 key를 찾아 value로 치환

mpg['origin'].replace({1:'USA',2:'EU',3:'JAPAN'},inplace=True)
print(mpg['origin'].head())

 

=>model year 컬럼의 값을 범주형(category- 특정 값만을 가져야 하는 자료형) 으로 치환 

#문자열 이나 숫자 자료형은 원핫 -인코딩이 안되는 경우가 발생할 수 있음
#원핫 인코딩을 문자열을 숫자형으로 바꿈
mpg['model year']=mpg['model year'].astype('category')
mpg.info()

 

3.Standardization(표준화)

1)개요

=>다변량 분석(2개 이상의 컬럼을 같이 사용해서 수행하는 분석)에서는 각 컬럼에 들어있는 숫자 데이터의 상대적 크기 차이 떄문에 분석 결과가 왜곡될 수 있음

=>상대적 크기 차이를 동일하게 맞추는 작업이 표준화 또는 Scailing

=>방법은 여러가지가 존재하는데 일반적으로0~1이나 -1~ 1로 만듭니다

=>방법을 달리하는 가장 큰 이유는 이상치 여부 때문

 

2)표준값과 편차 값

=>모든 값들의 표준 값을 정해서 그 값을 기준으로 차이를 구해서 비교하는 방법

=>표준 값:(데이터-평균)/표준 편차

=>편차 값:표준값*10 + 50

=>해석

표준 값들의 평균은 0 이고 표준 편차는 1(정규 분포와 비교하기 쉽도록 하기 위해서)

편차 값은 평균이 50이고 표준 편차는 10

 

3)scikit-learn의 표준화 클래스

=>표준화 클래tm

=>StandardScaler(데이터)

-평균이 0 표준편차가 1이 되도록 변환

-주성분 분석등에서 많이 사용

 

=>MinMaxScaler(데이터)

-최대값이 1 그리고 최소값이 0이 되도록 변환

-(데이터-최소값)/(최대값-최소값)

-신경망에서 주로 이용

 

=>RobustScaler(데이터)

Robustic:이상치에 덜 민감하다는 뜻

-중앙값(median)이 0 그리고 IQR(Interquartile Range)이 1이 되도록 변환

-데이터에 이상치가 많으면 평균과 표준편차 영향을 미쳐서 위의 방식이 부정적

-중간 값과 사분위 범위를 사용하여 스케일 조정

-(데이터-중간값)/(75%값-25%값)

 

=>MaxAbScaler(데이터)

-0을 기준으로 절대값이 가장 큰 수가 1 또는 -1이 되도록 변환

-절대값이 0~1 사이가 되도록 매핑

-각 열 별로 절대값이 가장 큰 값으로 나누는 것

-양수 데이터로만 구성된 경우에는 MinMax 와 유사하면 이상치에 민감

-음수 자체가 어떤 의미를 지닌 경우 사용

 

=>QuantileTransformer

-데이터를 1000개 분위로 나누어 0~1 사이에 고르게 분포시키는 방식

-이상치에 영향을 덜 받음

 

=>표준화 과정

-데이터를 입력으로 해서 fit 메서드를 호출해서 분포 모수를 객체 내에 저장하고 데이터를 입력으로 해서 transform 메서드를 호출하면 표준화된 결과를 리턴

2개의 과정을 합친 fit_transform 메서드를 이용하는 것도 가능

 

-훈련 세트와 테스트 세트로 데이터를 나눈 경우 동일한 방식으로 표준화를 수행해야 함

from sklearn import preprocessing

#사이킷런은 머신러닝을 위한 패키지라서 numpy 배열을 가지고 작업을 수행
x = mpg[['horsepower']].values
#print(x)
#표준화 전의 기술 통계
print("표준화 전 기술 통계")

print("평균:",np.mean(x))
print("표준편차:",np.std(x))
print("최대:",np.max(x))
print("최소:",np.min(x))


#평균이 0이고 표준편차가 1로 만드는 Scaler

scaler=preprocessing.StandardScaler()
scaler.fit(x)
x_scaled=scaler.transform(x)

print("평균:",np.mean(x_scaled))
print("표준편차:",np.std(x_scaled))
print("최대:",np.max(x_scaled))
print("최소:",np.min(x_scaled))
-

 

4)Normalization(정규화)

=>데이터의 범위를 0과 1로 변환해서 데이터의 분포를 조정하는 방법

=>scikit-learn의 Normalizer 클래스의 객체를 만든 후 transform 함수에 데이터를 대입하면 정규화한 결과를 리턴합니다.

이 때 norm 옵션에 l1이나 l2 을 설정할 수 있는데 l1은 맨해튼거리로 계산하고 l2 를 설정하면 유클리드 거리를 이용합니다

=>하나의 속성이 아니라 여러 개의 속성을 한꺼번에 스케일링

=>행 단위로 스케일링

=>여러 개의 컬럼을 가진 데이터(텍스트)에서 주로 이용

컬럼들이 독립적이지 않은 경우 사용

=>유클리드 거리:대각선 거리- 각 좌표를 뺸 다음 제곱해서 더한 후 제곱

from sklearn.preprocessing import Normalizer

features=np.array([[1,2],[2,3],[4,3]])

#정규화 객체를 생성
normalizer=Normalizer(norm="l2")

result=normalizer.transform(features)

print(result)

=>맨해튼 거리:각 좌표 거리의 합

from sklearn.preprocessing import Normalizer

features=np.array([[1,2],[2,3],[4,3]])

#정규화 객체를 생성
normalizer=Normalizer(norm="l1")

result=normalizer.transform(features)

print(result)

 

5)다항 특성 과 교차항 특성

=>컬럼들을 제곱하고 곱해서 데이터를 추가하는 것

=>특성과 타겟 사이에 비선형 관계가 존재하는 경우 다항 특성을 생성

=>scikit-learn의 PolynomialFeatures 클래스를 이용

옵션으로 degree를 설정할 수 있는데 몇 차항까지생성할 것인지를 설정

include_bias 옵션으로 첫번쨰 항으로 상수 1을 추가할지 여부를 설정

interaction_only 옵션에 True 를 설정하면 교차항 특성만 생성

생성된 객체의 get_feature_names_out 함수를 이용하면 변환식을 이름으로 반환

from sklearn.preprocessing import PolynomialFeatures

features=np.array([[2,3],[3,4],[1,5]])

#다항 ㅁ및 교차항을 생성해주는 객체 생성
polynomial_interaction = PolynomialFeatures(degree=2,include_bias=False)
result= polynomial_interaction.fit_transform(features)

print(result)

 

6)특성 변환

=>컬럼에 함수를 적용해서 데이터를 변경하는 작업

=>pandas 의 apply 함수를 이용해서 수행하는 것이 가능

=>scikit-learn에서는 FunctionTransformer 와 ColumnTransformer API를 제공

FunctionTransformer 는 모든 열에 동일한 함수를 적용하고자 할 때 사용-pandas 의 apply

ColumnTransformer는 각 열에 다른 함수를 적용하고자 할 때 사용하는데 함수와 변환기 그리고 컬럼 이름의 리스트로 이루어진 튜플의 리스트를 매개변수로 받습니다

=>pandas 는 데이터가 Series 나 DataFrame이고 scikit-learn 은 데이터가 numpy의 ndarray이고 특별한 경우가 아니면 2차원 배열

from sklearn.preprocessing import FunctionTransformer

features=np.array([[1,3],[3,6]])

def add_one(x:int) -> int:
    return x+1

one_transformer=FunctionTransformer(add_one)
result=one_transformer.transform(features)
print(result)

df=pd.DataFrame(features,columns=['f1','f2'])
print(df)
print(df.apply(add_one))

 

7)숫자 데이터의 이산화
=>데이터를 분석할 때 연속된 데이터를 그대로 사용하기 보다는 일정한 구간으로 나누어서 분석하는 것이 효율적인 경우가 있음
=>가격이나 비용 또는 효율, 나이 등 연속적인 값을 일정한 수준이나 정도를 나타내는 이산적인 값으로 나타내어 구간별 차이를 드러나게 함
=>연속형 데이터를 일정한 구간으로 나누고 각 구간을 범주형 이산 변수로 치환하는 과정을 binning(구간 분할)이라고 합니다.
=>pandas의 cut 함수를 이용하면 연속형 데이터를 여러 구간으로 분할해서 범주형 데이터로 변환하는 것이 가능
=>구간을 분할 할 때 반드시 일정한 간격을 만들 필요는 없습니다.
=>pandas의 cut 함수 이용

#연속형 데이터를 간격 단위로 분할해서 값을 할당하는 작업을 이산화

#구간 생성
#3개의 구간으로 분할
#최소값 부터 최대값 까지 3개로 분할하고 데이터의 개수 와 경계값을 리턴해 줍니다.
count, bin_dividers = np.histogram(mpg['horsepower'], bins = 3)
print(count)
print(bin_dividers)

#각 그룹에 할당할 데이터를 생성
bin_names = ["저출력", "보통출력", "고출력"]

#이산화
#x는 분할할 데이터, bins는 구간의 경계값 리스트, labels는 구간에 할당할 데이터 목록
#include_lowest는 첫 경계값 포함 여부
mpg['hp_bin'] = pd.cut(x = mpg['horsepower'], bins = bin_dividers,
                      labels=bin_names, include_lowest=True)
print(mpg[['horsepower', 'hp_bin']].head(20))



=>numpy의 digitize 함수를 이용해서 구간 분할이 가능
 첫번째 데이터는 이산화 할 데이터
 bins 매개변수에는 이산화 할 경계값 리스트
 right 매개변수에는 경계값을 포함할 지 여부를 설정

 - numpy의 digitize를 이용한 구간 분할

 age = np.array([[13], [30], [67], [36], [64], [24]])
#pandas의 메서드들은 DataFrame 이나 Series를 이용해서 작업
#machine learning 관련된 메서드들은 2차원 이상의 ndarray를 가지고 작업을 수행
#30이 안되면 0 30이상이면 1로 변환
#bins에는 여러 개의 데이터 설정이 가능
result = np.digitize(age, bins=[30])
print(result)



=>scikit-learn 의 Binarizer 와 KBinsDiscretizer
 - Binarizer: 생성할 때 하나의 경계값을 대입해서 생성하고 fit_transform을 호출하면 경계값보다 작은 경우는 0 그렇지 않은 경우는 1을 할당해서 리턴
 - KBinsDiscretizer
   첫번째 매개변수로 구간의 개수를 설정하고 encode 매개변수에 ordinal을 설정하면 각 구간을 하나의 컬럼에 일련번호 형태로 리턴하고 onehot으로 설정하면 희소 행렬을 리턴하고 onehot-dense를 설정하면 밀집 행렬의 형태로 리턴
   strategy 매개변수에 quantile을 설정하면 일정한 비율로 uniform을 설정하면 구간이 폭이 일정하도록 생성
  각 구간의 값을 알고자 하는 경우는 bin_edges_ 속성을 확인

 - Label Encoding
 - OneHot Encoding
 - Sparse Matrix(희소 행렬)
 - Dense Matrix(밀집 행렬)

=>Binarizer 를 이용한 구간 분할

from sklearn.preprocessing import Binarizer
#threshold(임계값)
#흑백 이미지 데이터에서 뚜렷한 구분하기 위해서 임계값을 설정해서
#임계값 아래 와 위를 구분합니다.
binarizer = Binarizer(threshold=30.0)
print(binarizer.transform(age))



=>KBinsDiscretizer를 이용한 구간 분할

from sklearn.preprocessing import KBinsDiscretizer

#균등한 분포로 4분할
#ordinal: 라벨 인코딩 - 일련번호
kb = KBinsDiscretizer(4, encode='ordinal', strategy='quantile')
print(kb.fit_transform(age))
print()
#원핫인코딩을 해서 희소 행렬로 표현
kb = KBinsDiscretizer(4, encode='onehot', strategy='quantile')
print(kb.fit_transform(age))
print()
#원핫인코딩을 해서 밀집 행렬로 표현
kb = KBinsDiscretizer(4, encode='onehot-dense', strategy='quantile')
print(kb.fit_transform(age))



=>2개 이상의 컬럼의 값으로 이산화: 군집(Clustering)
 - 데이터 전처리 과정에서 군집 분석을 수행해서 군집 분석한 결과를 기존 데이터의 컬럼으로 추가해서 사용하는 경우가 있음
 - 수집 -> 전처리 -> 분석의 과정은 절대적인 순서의 과정이 아니며 전처리 과정에서 분석을 해서 적용하기도 하고 분석이 끝나고 난 후 다시 전처리로 돌아가서 나선형으로 수행하기도 합니다.
 - 전처리의 일환으로 분석을 수행하는 경우는 높은 정확도를 요구하는 것이 아니므로 복잡한 모델(일반적으로 정확도는 높지만 시간이 오래 걸림)을 선택하는 것은 바람직하지 않음

=>KMeans 알고리즘을 이용한 군집의 결과를 컬럼으로 추가

from sklearn.cluster import KMeans

#샘플 데이터 - DataFrame에서 추출하는 것이 일반적
sample = np.array([[13, 30], [20, 30], [21, 99], [21, 33], [98, 22], [20, 87]])

dataframe = pd.DataFrame(sample, columns=['f1', 'f2'])

#print(dataframe)

clustering = KMeans(3, random_state=42)
clustering.fit(sample)
dataframe['group'] = clustering.predict(sample)
print(dataframe)


8)이상치 탐지
=>z-score 를 이용하는 방법
중앙값을 기준으로 표준 편차가 절대값 3 범위의 바깥쪽에 위치하는 데이터를 이상치로 간주
이 방식은 데이터가 12개 이하이면 이상치를 감지할 수 없음

=>z-score를 보정해서 사용하는 방법
편차의 절대값 범위를 3.5로 설정한 수 0.6745를 곱해서 사용

=>IQR(3사분위수 - 1사분위수)을 이용하는 방법
1사분위수보다 1.5IQR 이상 작은 값이나 3사분위수보다 1.5IQR 이상 큰 값을 이상치로 간주
boxplot 에서 수염바깥쪽 데이터를 이상치로 간주

=>3가지 방식을 모두 사용해서 2가지 경우 이상에서 이상치로 간주되는 데이터를 이상치로 판정

=>데이터의 특정 비율을 이상치로 간주(scikit-learn 의 covariance.EllipticEnvelope 클래스 이용)

=>수식을 이용한 이상치 탐지

#이상치 탐지를 위한 데이터 생성
features = np.array([[10000, 10, 7, 6, 4, 3, 3, 2], [20000, 10, 7, 6, 4, 3, 2, 8]])

#z-score를 이용해서 이상치를 탐지해주는 함수
def outliers_z_score(ys):
    #임계값 설정
    threshold=3
    
    #평균 구하기
    mean_y = np.mean(ys)
    #표준편차 구하기
    stdev_y = np.std(ys)
    z_scores = [(y-mean_y) / stdev_y for y in ys]
    print("z_score:", z_scores)
    return np.where(np.abs(z_scores) > threshold)

print(outliers_z_score(features))

#z_score를 보정
def outliers_mod_z_score(ys):
    #임계값 설정
    threshold=3.5
    
    #중앙값 구하기
    median_y = np.median(ys)

    #중위절대 편차를 구함
    median_absolute_deviation_y = np.median([np.abs(y-median_y) for y in ys])
    
    #중위 절대 편차를 가지고 계산한 후 0.6745를 곱하고 이 결과가 3.5 보다 큰 경우를 이상치로 감지
    #데이터의 개수에 상관없이 이상치를 감지   
    modified_z_scores = [0.6745 * (y - median_y) / median_absolute_deviation_y for y in ys ]

    return np.where(np.abs(modified_z_scores) > threshold)
print(outliers_mod_z_score(features))




#IQR을 이용하는 방법
#boxplot을 그렸을 때 수염의 바깥쪽에 있는 데이터를 이상치로 간주

def outliers_iqr(ys):
    #iqr 구하기
    quartile_1, quartile_3 = np.percentile(ys, [25, 75])
    iqr = quartile_3 - quartile_1
    
    #하한 과 상한 구하기
    lower_bound = quartile_1 - (iqr * 1.5)
    upper_bound = quartile_3 + (iqr * 1.5)
    
    return np.where((ys > upper_bound) | (ys < lower_bound))
    
features = np.array([[10, 10, 7, 6, 8], [20000, 2, 3, 4, 5]])   
print(outliers_iqr(features))

print(outliers_iqr(mpg[['horsepower']].values))



=>일정한 비율의 데이터를 이상치로 간주하는 방식
특별한 경우에는 값이 아니고 비율을 이용해서 이상치를 판정합니다.
대표적인 경우가 우리나라 학교에서 국가장학금 지급할 때 특정한 소득 기준은 없고 상위 10%를 제외한 학생에게 장학금을 지급하는 경우

#데이터를 생성해주는 API

from sklearn.datasets import make_blobs
#일정한 비율을 이상치로 간주하는 API
from sklearn.covariance import EllipticEnvelope

#2행 10행의 데이터를 랜덤하게 생성
#_는 데이터를 사용하지 않음
features, _ = make_blobs(n_samples=10, n_features=2, centers=1, random_state=42)
#이상치 생성 - 0번 데이터는 이상한 데이터
features[0,0] = 10000
features[0,1] = 1000

print(features)
#이상치 탐지 객체 - 10%는 이상치로 간주
outlier_detector = EllipticEnvelope(contamination=0.1)
outlier_detector.fit(features)
#-1 이면 이상치 1이면 정상적인 데이터
outlier_detector.predict(features)

'Study > Data전처리 및 통계' 카테고리의 다른 글

Pandas(8)-한글 NLP  (0) 2024.02.20
Pandas(7)-Outlier,Encoder,자연어처리  (0) 2024.02.19
Pandas(5)  (0) 2024.02.15
Pandas(4)  (0) 2024.02.14
Pandas(3)  (0) 2024.02.13