no image
[Python] 지도학습 연습 _ 자전거 대여 수요 예측
1. Kaggle 자전거 대여 수요 예측 - 예측 모델 및 분석 대회 플랫폼 - 현재는 구글이 운영 - url - https://www.kaggle.com/c/bike-sharing-demand Bike Sharing Demand | Kaggle www.kaggle.com - 미션: 날짜, 계절, 근무일 여부, 온도, 체감온도, 풍속 등의 데이터를 이용해서 자전거 대여 수요 예측 - 유형: 회귀 - 평가 지표: RMSLE sampleSubmission.csv 참고 실제 데이터 확인 * test 데이터에 없는 train 데이터의 피처는 삭제 해야함 2. 데이터 탐색 # 데이터 가져오기 import pandas as pd train = pd.read_csv('./bike/train.csv') test = p..
2024.03.06
no image
[Python] 벡터 연산에서 기억할 부분
1. 데이터 유형 1) Scala - 하나의 값: 정수와 실수 그리고 하나의 글자나 단어 (자연어 처리) 2) Vector - 0개 이상의 값 - Vector 분류 Vector: 1차원 배열 (Record) Matrix: 벡터가 모여서 만들어진 이차원 배열 - Table 머신러닝의 기본 단위 Tensor: Matrix의 배열로 삼차원 배열 딥러닝의 기본 단위 from sklearn.datasets import load_iris iris = load_iris() #이차원 배열이라 iris.data는 Matrix print(iris.data.shape) #첫번째 행의 붓꽃 데이터 - 배열이므로 Vector print(iris.data[0, :]) (150, 4) [5.1 3.5 1.4 0.2] - 하나의 행..
2024.02.27
[Python] 샘플링 _ 표본 추출
1. 전수조사와 표본조사 - 전수조사 모집단 내에 있는 모든 대상을 조사하는 방법 모집단의 특성을 정확히 반영하지만 시간과 비용이 많이 소모됨 - 표본조사 모집단으로부터 추출된 표본을 대상으로 분석 실시 전수조사의 단점을 보완하지만 모집단의 특성을 반영하는 표본이 제대로 추출되지 못하면 수집된 자료가 무용지물 2. 용어 - Sample: 큰 데이터 집합으로부터 얻은 부분집합 - Population: 데이터 집합을 구성하는 전체 - N(n): 모집단의 크기 - 랜덤 표본추출: 무작위로 표본을 추출하는 것 층화 랜덤 표본추출: 모집단을 여러개의 층으로 나누고 각 층에서 무작위로 표본 추출 단순 랜덤 표본추출: 층화 없이 단순하게 무작위로 표본 추출 - 표본 편항 (Sample Bias): 모집단을 잘못 대표..
2024.02.26
no image
[Python] 확률 분포 모형
1. 확률적 데이터와 확률 변수 1-1) 확률적 데이터 - 결정론적 데이터: 실험, 측정, 조사 등을 통해 어떤 데이터 값을 반복적으로 얻을 때 누가 언제 얻더라도 항상 동일한 값이 나오는 것 - 확률적 데이터: 정확히 예측할 수 없는 값 (ex. 혈압) - 대다수 데이터는 확률적 데이터다. 여러 조건이나 상황에 따라 데이터 값이 변할 수 있고 측정 시에 발생하는 오차 때문! 1-2) 분포 - 확률적 데이터를 살펴보면 어떤 값은 자주 등장하고 어떤 값은 드물게 등장하는데 어떤 값이 자주 나오고 어떤 값이 드물게 나오는지를 나타내는 정보를 의미 - 범주형 데이터 - 교차분할표, plot으로 확인 - 연속형 데이터 - binning 후 히스토그램 등으로 확인 1-3) 분포 추정을 위한 기술 통계값 - 평균,..
2024.02.26
no image
[Python] 기술통계
데이터: https://github.com/itggangpae/python_statistics 1. 통계 - 논리적 사고와 객관적인 사실에 따르며 일반적이고 확률적 결정론에 따라서 인과 관계를 규명 - 연구 목적에 의해 설정된 가설들에 대하여 분석 결과가 어떤 결과를 뒷받침하고 있는지를 통계적 방법으로 검정 - 분류 기술통계: 수집된 데이터의 특성을 쉽게 파악하기 위해서 데이터를 표나 그래프 또는 대푯값으로 정리 요약하는 통계 추론통계: 기술 통계량과 모집단에 추출한 정보를 이용해서 모집단의 특성을 과학적으로 추론 2. 변수(feature)의 종류 2-1) 질적변수와 양적변수 - 질적 변수: 구분을 위한 변수 범주형 명목척도, 순서척도 - 양적 변수: 양을 표현하는 변수 대부분 연속형 수치라고 표현해서는..
2024.02.22
no image
[Python] 이미지 데이터 다루기
1. Computer Vision 1-1) 영상 처리와 컴퓨터 비전 - 영상 처리 컴퓨터를 사용해서 입력 영상 데이터를 처리하는 분야 - 컴퓨터 비전 인간의 시각을 흉내내는 컴퓨터 프로그램 입력 영상에서 의미있는 정보를 추출하는 분야 제품의 결함을 검사하는 분야나 얼굴 인식이나 지문 인식이나 물체 검출 등이 컴퓨터 비전의 대표적인 분야 1-2) 이미지 처리 - 머신 러닝에서는 원본 이미지를 학습 알고리즘이 사용할 수 있도록 변환 수행 1-3) 컴퓨터 비전 분야 - Object Classification: 객체 분류, 이미지 분류 - Object Detection & Localization: 객체 식별 - Object Segmentation: 객체 분할 - Image Captioning: 이미지의 상황을 ..
2024.02.21
no image
[Python] 확률
1.주피터 노트북에서 수학 기호 사용 - TeX 라는 조판 언어를 이용해서 수식을 표현할 수 있음 - Markdown Cell을 생성한 후 셀 안에서 $ 기호를 이용해서 수기을 표현 - 그리스 문자는 \그리스문자이름 2. 공통 코드 import pandas as pd import numpy as np #dist 플롯이나 histogram import seaborn as sns #시각화에서 한글을 사용하기 위한 설정 import platform from matplotlib import font_manager, rc font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name() rc('font', family=fo..
2024.02.21
no image
[Python] 데이터 스케일링
1. Standardization (표준화) 1-1) 개요 - 다변량 분석에서는 각 컬럼의 숫자 데이터의 크기가 상대적으로 달라서 분석 결과가 왜곡되는 현상 발생 다변량 분석: 2개 이상의 컬럼을 같이 사용해서 수행하는 분석 - 상대적 크기 차이를 동일하게 맞추는 작업이 표준화 또는 스케일링 일반적으로 0 ~ 1이나 -1 ~ 1로 생성 방법을 달리하는 가장 큰 이유는 이상치 여부 때문 1-2) 표준값과 편차값 - 모든 값들의 표준 값 기준으로 차이를 구해서 비교 표준 값: (데이터 평균) /표준편차 표준 값 평균은 0, 표준 편차는 1 정규분포와 비교하기 쉽다. 편차 값: 표준값 * 10 + 50 양수로 바꿔주기. 음수는 오타나 오류를 많이 발생하고 직관적이지 않기 때문. 편차 값의 평균은 50, 표준 ..
2024.02.16

1. Kaggle 자전거 대여 수요 예측 

- 예측 모델 및 분석 대회 플랫폼

- 현재는 구글이 운영

- url - https://www.kaggle.com/c/bike-sharing-demand 

 

Bike Sharing Demand | Kaggle

 

www.kaggle.com

- 미션: 날짜, 계절, 근무일 여부, 온도, 체감온도, 풍속 등의 데이터를 이용해서 자전거 대여 수요 예측

- 유형: 회귀

- 평가 지표: RMSLE

 

  • sampleSubmission.csv 참고

 

실제 데이터 확인

* test 데이터에 없는 train 데이터의 피처는 삭제 해야함

 

 

 

2. 데이터 탐색

# 데이터 가져오기

import pandas as pd

train = pd.read_csv('./bike/train.csv')
test = pd.read_csv('./bike/test.csv')
submission = pd.read_csv('./bike/sampleSubmission.csv')

 

 

# 훈련 데이터 구조 확인

train.info()

  • 훈련 데이터에는 결측치가 없다.
  • datetime이 진짜 객체 타입인지 확인 (dtype)

# 테스트 데이터 구조 확인

test.info()
  • train 데이터에는 존재하지만 test 데이터에는 존재하지 않는 컬럼을 발견
    • casual, registered, count
    • count는 타겟이라서 없는 것
    • 훈련을 할 때 casual과 registered는 제외

 

- 피처에 대한 설명

  • datetime - hourly date + timestamp  
  • season -  1 = spring, 2 = summer, 3 = fall, 4 = winter
  • holiday - whether the day is considered a holiday   *0: 공휴일 아닌 날 / 1: 공휴일
  • workingday - whether the day is neither a weekend nor holiday   *0: 주말 및 국경일 / 1: 근무날
  • weather
    1: Clear, Few clouds, Partly cloudy, Partly cloudy
    2: Mist + Cloudy, Mist + Broken clouds, Mist + Few clouds, Mist
    3: Light Snow, Light Rain + Thunderstorm + Scattered clouds, Light Rain + Scattered clouds
    4: Heavy Rain + Ice Pallets + Thunderstorm + Mist, Snow + Fog
  • temp - temperature in Celsius
  • atemp - "feels like" temperature in Celsius 
  • humidity - relative humidity
  • windspeed - wind speed
  • casual - number of non-registered user rentals initiated   *예약하지 않은 사용자의 대여 횟수
  • registered - number of registered user rentals initiated   *예약 사용자의 대여 횟수
  • count - number of total rentals

- 날짜의 경우는 DateTime으로 변환해서 분할해보는 것이 좋다.

train['datetime'] = train.datetime.apply(pd.to_datetime)
0   datetime    10886 non-null  datetime64[ns]
train ['year'] = train['datetime'].apply(lambda x:x.year)
train ['month'] = train['datetime'].apply(lambda x:x.month)
train ['day'] = train['datetime'].apply(lambda x:x.day)
train ['hour'] = train['datetime'].apply(lambda x:x.hour)
train ['minute'] = train['datetime'].apply(lambda x:x.minute)
train ['second'] = train['datetime'].apply(lambda x:x.second)
train ['weekday'] = train['datetime'].apply(lambda x:x.weekday())

 

- 범주형은 원래 이름으로 변경해 두는 것이 좋다. 

  • 탐색을 할 때는 시각화를 많이 하게 되는데 시각화할 때 0이나 1같은 숫자보다는 spring이나 fall 같은 명시적인 의미를 가진 데이터가 보기 좋기 때문이다.
  • season, weather 변환
train['season'] = train['season'].map({
    1:'봄', 2:'여름', 3:'가을', 4:'겨울'
})

train['weather'] = train['weather'].map({
    1:'맑음', 2:'흐림', 3:'약한 눈/비', 4:'강한 눈/비'
})

 

#시각화를 위한 설정

import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

 

#한글을 위한 설정

#시각화에서 한글을 사용하기 위한 설정
import platform
from matplotlib import font_manager, rc

font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)

 

#경진대회에서만 사용

import warnings
warnings.filterwarnings(action='ignore')

 

- 타겟 확인

  • 타겟이 범주형인 경우 확인해서 특정 범주의 데이터가 아주 많거나 작으면 층화 추출이나 오버샘플링이나 언더샘플링 고려
  • 타겟이 연속형인 경우 정규 분포에 가까운지 확인해서 한쪽으로 몰려있으면 로그변환으로 분포 변경
sns.displot(train['count'])
  • 데이터의 분포가 왼쪽으로 치우쳐져 있다.

# 타겟을 로그 변환해서 시각화

import numpy as np
sns.distplot(np.log(train['count']))

 

# 년월일 시분초에 따른 타겟의 변환

figure, axes = plt.subplots(nrows=3, ncols=2)

sns.barplot(x='year', y='count', data=train, ax=axes[0, 0])
sns.barplot(x='month', y='count', data=train, ax=axes[0, 1])
sns.barplot(x='day', y='count', data=train, ax=axes[1, 0])
sns.barplot(x='hour', y='count', data=train, ax=axes[1, 1])
sns.barplot(x='minute', y='count', data=train, ax=axes[2, 0])
sns.barplot(x='second', y='count', data=train, ax=axes[2, 1])
  • 각 데이터가 유의미한 변화를 가져오면 훈련에 사용을 해야 하고 거의 변화가 없다면 제거
  • 분산이 아주 작다면 유의미한 변화를 가져올 수 없기 때문에 제거
    • 일은 별로 상관이 없음 - 제거
    • 시간은 유의미한 변화가 있음
    • 분하고 초는 0인걸 보니 빌릴 때 시간까지만 기록해놨음 - 제거

# 계절, 날씨, 공휴일 여부, 근무일 별 대여 수량 시각화

figure, axes = plt.subplots(nrows=2, ncols=2)

sns.barplot(x='season', y='count', data=train, ax=axes[0, 0])
sns.barplot(x='weather', y='count', data=train, ax=axes[0, 1])
sns.barplot(x='holiday', y='count', data=train, ax=axes[1, 0])
sns.barplot(x='workingday', y='count', data=train, ax=axes[1, 1])
  • holiday, workingday 여부는 상관이 없는듯함
  • 직선이 분산이기도 하다.  

# 박스플롯 - 이상치 분포 여부 확인 가능

figure, axes = plt.subplots(nrows=2, ncols=2)

sns.boxplot(x='season', y='count', data=train, ax=axes[0, 0])
sns.boxplot(x='weather', y='count', data=train, ax=axes[0, 1])
sns.boxplot(x='holiday', y='count', data=train, ax=axes[1, 0])
sns.boxplot(x='workingday', y='count', data=train, ax=axes[1, 1])

 

# 근무일, 공휴일, 요일, 계절, 날씨에 따른 시간대별 대여수량 확인

figure, axes = plt.subplots(nrows=5)

sns.pointplot(x='hour', y='count', hue='workingday', data=train, ax=axes[0])
sns.pointplot(x='hour', y='count', hue='holiday', data=train, ax=axes[1])
sns.pointplot(x='hour', y='count', hue='weekday', data=train, ax=axes[2])
sns.pointplot(x='hour', y='count', hue='season', data=train, ax=axes[3])
sns.pointplot(x='hour', y='count', hue='weather', data=train, ax=axes[4])
  • workingday는 출퇴근 시간에, 휴일은 낮에 많다.

 

- 연속형 데이터는 피처와 상관관계를 파악할 필요가 있다.

  • 상관관계가 거의 없다면 제거 가능
  • 상관관계 파악 방법
    • 상관계수 확인
    • 산포도(regplot: 선형회귀 직선을 같이 출력)
    • 상관계수 확인 이후 히트맵 같은 도구로 시각화 함

# 온도, 체감온도, 풍속, 습도별 대여수량 확인

figure, axes = plt.subplots(nrows=2, ncols=2)

sns.regplot(x='temp', y='count', scatter_kws={'alpha':0.2},
              line_kws={'color':'red'}, data=train, ax=axes[0,0])
sns.regplot(x='atemp', y='count', scatter_kws={'alpha':0.2},
              line_kws={'color':'red'}, data=train, ax=axes[0,1])
sns.regplot(x='windspeed', y='count', scatter_kws={'alpha':0.2},
              line_kws={'color':'red'}, data=train, ax=axes[1,0])
sns.regplot(x='humidity', y='count', scatter_kws={'alpha':0.2},
              line_kws={'color':'red'}, data=train, ax=axes[1,1])

 

 

 

 

 

3. 규제가 없는 선형회귀 적용

  • 이전에 데이터 탐색을 할 때 변경된 내용이 있을 수 있기 때문에 다시 가져오기

# 데이터 가져오기

import pandas as pd

train = pd.read_csv('./bike/train.csv')
test = pd.read_csv('./bike/test.csv')

 

# 데이터 전처리

  • 이상치 제거
    • 범주형에서 기본값 이외의 데이터나 수치형 데이터에서 범위에 맞지 않는 데이터 혹은 극단치
    • 도메인에 따라 정상적인 입력이라도 이상치로 판단할 수 있다. 
      ex. 심한 눈이나 비가 내리는데 자전거를 대여하는 상황 등
train = train[train['weather'] != 4]

 

 

# train 데이터와 test 데이터 합치기 (동일한 구조 만들기)

  • 일반적인 경우 train 데이터와 test 데이터 구조가 같다. 
  • 경진대회의 경우 train.csv와 test.csv 구조가 다르기도 하다. test.csv에는 타겟이 없다.
    • 경진대회는 train 데이터를 가지고 모델을 만들어서 test 데이터의 타겟을 예측해서 제출하고 채점하기 때문
all_data_temp = pd.concat([train, test])

 

- 여러개의 데이터를 행 방향으로 결합할 때 주의할 점!

  • 별도의 인덱스를 설정하지 않으면 인덱스는 0부터 시작하는 일련 번호라서
    인덱스를 설정하지 않은 상태로 만든 DataFrame을 행 방향으로 결합하면 인덱스가 중첩될 수 있다.
  • 행은 분명 17383개인데, 번호는 6492번까지밖에 없는 상황
    인덱스가 중복돼서 나오기 때문에 데이터 구별이 안될 수 있으므로 기존 인덱스는 무시하고 결합한다.

# 기존 인덱스 무시하고 결합

all_data_temp = pd.concat([train, test], ignore_index=True)

 

 

# 파생 피처 생성

  • datetime을 이용해서 새로운 열 (년 월 일 시 분 초 요일) 생성
  • 탐색했을 때 필요 없었던 일, 분, 초 제거
  • 년월일, 년, 시, 요일 생성해보기
all_data['datetime']
  1. 공백단위로 쪼개기 _ split 사용 - 정규표현식
  2. 문자 개수만큼 읽어오기 _ substring(위치, 개수)
from datetime import datetime

#datetime 필드에서 앞의 날짜 부분만 잘라서 date 필드 생성
all_data['date'] = all_data['datetime'].apply(lambda x : x.split()[0])

#datetime 필드에서 앞의 년도 부분만 잘라서 year 필드 생성
all_data['year'] = all_data['datetime'].apply(lambda x : x.split()[0].split('-')[0])

#datetime 필드에서 앞의 시간 부분만 잘라서 hour 필드 생성
all_data['hour'] = all_data['datetime'].apply(lambda x : x.split()[1].split(':')[0])

#datetime 필드에서 요일로 weekday 필드 생성
#날짜 컬럼을 datetime으로 변환하고 weekday 메소드를 호출해서 요일 리턴
all_data['weekday'] = all_data['date'].apply(
    lambda x : datetime.strptime(x, '%Y-%m-%d').weekday())

 

 

# 불필요한 피처 제거

  • casual, registered, datetime, date, windspeed

<초보자의 코딩>

all_data = all_data.drop(['casual', 'registered', 'datetime', 'date', 'windspeed'], axis=1)
  • literal을 많이 쓴다. 그냥 지워버린다.
  • 어떤 컬럼을 지웠는지 찾고 싶을 때 나중에 찾아봐야 한다.

<숙련자의 코딩>

all_data = all_data.drop(drop_features, axis=1)
  • 삭제 작업을 할 때는 내가 무엇을 지웠는지를 변수에 저장해두는 것이 좋다.
  • 리터럴을 직접 이용하는 것보다 변수 이용

 

# 모델 생성 및 훈련

#훈련 데이터와 테스트 데이터 분리
X_train = all_data[~pd.isnull(all_data['count'])]
X_test = all_data[pd.isnull(all_data['count'])]

#피처와 타겟 분리
X_train = X_train.drop(['count'], axis=1)
X_test = X_test.drop(['count'], axis=1)

#원천 데이터에서 count 추출 후 타겟으로 만들기
y = train['count']

 

 

#평가 지표 함수 생성

  • 평가지표: RMSLE
  • 타겟이 치우쳐 있어서 로그 변환을 수행하는 것이 좋은 모델을 만들 수 있다.
import numpy as np

#y_true는 실제 값, y_pred는 예측 값, convertExp는 로그 변환 여부
def rmsle(y_true, y_pred, convertExp=True):
    #로그 변환을 한 경우에는 원래 값으로 복원
    if convertExp:
        y_true = np.exp(y_true)
        y_pred = np.exp(y_pred)
    #로그 변환을 할 때 1을 더해주지 않으면 0이 될 수 있고 에러 발생
    #로그 변환을 할 때는 1을 더해서  이를 방지해야 한다.
    log_true = np.nan_to_num(np.log(y_true + 1))
    log_pred = np.nan_to_num(np.log(y_pred + 1))
    
    #RMSLE 계산
    output = np.sqrt((np.mean(log_true-log_pred)**2))

 

#모델 생성 및 훈련

#모델 생성 및 훈련
from sklearn.linear_model import LinearRegression

linear_reg_model = LinearRegression()

#타겟의 로그 변환
log_y = np.log(y)

#훈련
linear_reg_model.fit(X_train, log_y)

#예측
preds = linear_reg_model.predict(X_train)

print("일반 선형 회귀의 RMSLE:", rmsle(log_y, preds, True))
일반 선형 회귀의 RMSLE: 1.0204980189305026

 

답안 생성 후 제출

linearreg_preds = linear_reg_model.predict(X_test)

#로그변환을 원래 데이터로 복원
submission['count'] = np.exp(linearreg_preds)
submission.to_csv('submission.csv', index=False)

 

 

 

 

 

4. 규제가 있는 모델 

  • Ridge(가중치 감소)
  • Lasso(제거 가능)
  • ElasticNet(절충형) : alpha라는 규제 강도가 있음

# 모델 생성 및 하이퍼 파라미터 튜닝

from sklearn.linear_model import Ridge
from sklearn.model_selection import GridSearchCV
from sklearn import metrics

#기본 모델
ridge_model = Ridge()

#파라미터 생성
ridge_params = {
    'max_iter' : [3000], 
    'alpha' : [0.1, 1, 2, 3, 4, 10, 30, 100, 200, 300, 400, 800, 900, 1000]
}

#사용자 정의 함수를 평가 지표로 사용
rmsle_scorer = metrics.make_scorer(rmsle, greater_is_better=False)
gridsearch_ridge_model = GridSearchCV(estimator = ridge_model,
                                     param_grid = ridge_params, 
                                     scoring = rmsle_scorer,
                                     cv=5)

log_y = np.log(y)
gridsearch_ridge_model.fit(X_train, log_y)

 

# Ridge

#최적의 모델로 예측
preds = gridsearch_ridge_model.best_estimator_.predict(X_train)
print("릿지 적용한 RMSLE:", rmsle(log_y, preds, True))
릿지 적용한 RMSLE: 1.020497980747181
  • 이전 선형 회귀와 별 차이가 없네.. 규제를 줬는데도?
  • 그럼 얘는 선형이 아니구먼
  • 비선형으로 예측허자

 

결론

규제를 추가해도 별 다른 성능 현상이 없으면 대부분의 경우 비선형이기 때문에 규제로 성능 향상을 꾀하기는 어려운 상황

 

 

 

 

5. 부스팅 모델

RandomForest, AdaBoosting, GradientBoosting, HistGradientBoosting, XGBM, LightGBM, CatBoost

pip install xgboost
pip install lightGBM
pip install catboost
from sklearn.ensemble import RandomForestRegressor
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.ensemble import HistGradientBoostingRegressor

from xgboost import XGBRegressor
from lightgbm import LGBMRegressor
from catboost import CatBoostRegressor

 

#인스턴스 생성

rf_reg = RandomForestRegressor()
gbm_reg = GradientBoostingRegressor(n_estimators=500)
hgbm_reg = HistGradientBoostingRegressor(max_iter=500)

xgb_reg = XGBRegressor(n_estimators=500)
lgbm_reg = LGBMRegressor(n_estimators=500)
catgbm_reg = CatBoostRegressor(iterations=500)

 

#파라미터 값 생성

params = {'random_state':[42], 'n_estimators': [100, 300, 500, 700]}
hgbm_params = {'random_state':[42], 'max_iter': [100, 300, 500, 700]}
catgbm_params = {'random_state':[42], 'iterations': [100, 300, 500, 700]}

 

#타겟 값 생성

log_y = np.log(y)

 

#랜덤 포레스트

gridsearch_random_forest_model = GridSearchCV(estimator=rf_reg, 
                                             param_grid=params, 
                                             scoring=rmsle_scorer,
                                             cv=5)
gridsearch_random_forest_model.fit(X_train, log_y)
preds = gridsearch_random_forest_model.best_estimators_.predict(X_train)
print("랜덤 포레스트의 RMSLE 값:", rmsle(log_y, preds, True))
랜덤 포레스트의 RMSLE 값: 0.11124607292494956

 

#XGB

gridsearch_xgb_model = GridSearchCV(estimator=xgb_reg, 
                                             param_grid=params, 
                                             scoring=rmsle_scorer,
                                             cv=5)
gridsearch_xgb_model.fit(X_train.values, log_y.values)
preds = gridsearch_xgb_model.best_estimator_.predict(X_train.values)
print("XGB의 RMSLE 값:", rmsle(log_y, preds, True))
  • XBG는 데이터프레임을 사용할 수 없다. numpy의 ndarray만 가능
XGB의 RMSLE 값: 0.19626309218676358

 

#GBM

gridsearch_gbm_model = GridSearchCV(estimator=gbm_reg, 
                                             param_grid=params, 
                                             scoring=rmsle_scorer,
                                             cv=5)
gridsearch_gbm_model.fit(X_train, log_y)
preds = gridsearch_gbm_model.best_estimator_.predict(X_train)
print("GBM의 RMSLE 값:", rmsle(log_y, preds, True))
GBM의 RMSLE 값: 0.27007893677151384

 

#LGBM

gridsearch_lgbm_model = GridSearchCV(estimator=lgbm_reg, 
                                             param_grid=params, 
                                             scoring=rmsle_scorer,
                                             cv=5)
gridsearch_lgbm_model.fit(X_train.values, log_y.values)
preds = gridsearch_lgbm_model.best_estimator_.predict(X_train.values)
print("LGBM의 RMSLE 값:", rmsle(log_y, preds, True))
LGBM의 RMSLE 값: 0.25772337517896476

 

# HistGradientBoosting

  • 피처를 255개 구간으로 나누어 학습
gridsearch_hgbm_model = GridSearchCV(estimator=hgbm_reg, 
                                             param_grid=hgbm_params, 
                                             scoring=rmsle_scorer,
                                             cv=5)
gridsearch_hgbm_model.fit(X_train.values, log_y.values)
preds = gridsearch_hgbm_model.best_estimator_.predict(X_train.values)
print("Hist의 RMSLE 값:", rmsle(log_y, preds, True))
Hist의 RMSLE 값: 0.23245216702971983

 

#CatBoost

gridsearch_catgbm_model = GridSearchCV(estimator=catgbm_reg, 
                                             param_grid=catgbm_params, 
                                             scoring=rmsle_scorer,
                                             cv=5)
gridsearch_catgbm_model.fit(X_train.values, log_y.values)
preds = gridsearch_catgbm_model.best_estimator_.predict(X_train.values)
print("Catboost의 RMSLE 값:", rmsle(log_y, preds, True))

...
Catboost의 RMSLE 값: 0.23012371614685823

 

 

- 부스팅 사용 결과

  • RF: 0.111
  • GBM: 0.27
  • XGBM: 0.196
  • LGBM: 0.257
  • Hist: 0.232 
  • Cat: 0.230

* 시간 때문에 n_estimators의 값만 하이퍼 파라미터 튜닝을 했는데 학습률이나 max_depth 등의 매개변수도 파라미터 튜닝을 하게 되면 더 좋은 성능을 기대할 수 있다.

* 필요하다면 피처 엔지니어링도 수행해보는 것이 좋다.

 

result = gridsearch_random_forest_model.best_estimator_.predict(X_test)
#로그변환을 원래 데이터로 복원
submission['count'] = np.exp(result)
submission.to_csv('submission.csv', index=False)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Python' 카테고리의 다른 글

[Python] 분류  (0) 2024.03.07
[Python] 머신러닝  (0) 2024.03.07
[Python] 벡터 연산에서 기억할 부분  (0) 2024.02.27
[Python] 샘플링 _ 표본 추출  (1) 2024.02.26
[Python] 확률 분포 모형  (1) 2024.02.26

1. 데이터 유형

1) Scala

- 하나의 값: 정수와 실수 그리고 하나의 글자나 단어 (자연어 처리)

 

2) Vector

- 0개 이상의 값

- Vector 분류

  • Vector: 1차원 배열 (Record)
  • Matrix: 벡터가 모여서 만들어진 이차원 배열 - Table
    • 머신러닝의 기본 단위
  • Tensor: Matrix의 배열로 삼차원 배열
    • 딥러닝의 기본 단위
from sklearn.datasets import load_iris

iris = load_iris()

#이차원 배열이라 iris.data는 Matrix
print(iris.data.shape)
#첫번째 행의 붓꽃 데이터 - 배열이므로 Vector
print(iris.data[0, :])
(150, 4)
[5.1 3.5 1.4 0.2]

 

- 하나의 행을 추출했을 때 하나의 행이 입력 데이터로 사용되면 Feature Vector라고 함. (Feature)

# 컬러 이미지 배열이 Tensor
from scipy import misc
img_rgb= misc.face()
print(img_rgb.shape)
(768, 1024, 3)
  • 차원이 3개면 Tensor

# Matrix를 3차원으로 만들기

x = iris.data.copy()
# 3번째 값으로 1을 설정해도 되고 -1을 설정해도 됨
# -1은 남은 것은 모두 다 배정
y = x.reshape((x.shape[0], x.shape[1], -1))
print(y.shape)
(150, 4, 1)

 

#1차원으로 만들기

#1차원으로 만들 때는 flatten이라는 메소드를 이용해도 되고 reshape를 이용해도 가능
print(y.flatten().shape)
(600,)

 

 

 

 

2. Vector의 연산

1) 행과 열 전치

- T 속성이나 transpose 메소드 이용

  • 메소드를 사용할 때는 변경된 순서 직접 설정
  • 2차원까지는 T 사용
  • 3차원 배열 이상에서 transpose 사용

- VT(윗첨자)

 

 

2) 대칭 행렬

- 자신을 전치한 행렬과 동일한 행렬

ar = np.array([[1, 2, 3], [4, 5, 6]])
print(ar)
print(ar.T)

ar = np.array([[], [], [], []])
[[1 2 3]
 [4 5 6]]
[[1 4]
 [2 5]
 [3 6]]
ar = np.array([[1, 5, 6, 7], [5, 2, 8, 9], [6, 8, 3, 10], [7, 9, 10, 4]])
print(ar)
print(ar.T)
[[ 1  5  6  7]
 [ 5  2  8  9]
 [ 6  8  3 10]
 [ 7  9 10  4]]
[[ 1  5  6  7]
 [ 5  2  8  9]
 [ 6  8  3 10]
 [ 7  9 10  4]]
  • 대칭행렬 - 전치를 해도 동일한 모양이 나오는 행렬
  • 정방행렬 - 행과 열의 개수가 같은 행렬
  • 대칭행렬은 정방행렬에서만 가능

 

3) 벡터와 벡터 연산

-   동일한 차원의 벡터끼리는 동일한 위치의 데이터와 연산을 수행
-   차원이 다른 스칼라와 벡터 또는 벡터와 벡터 사이의 연산은 작은 차원의 데이터를 큰 차원의 데이터로 크기를 변경해서 연산을 수행

  • 이를 브로드캐스트 연산이라고 하고 컴퓨터 프로그래밍에서만 존재

-   차원이 같더라도 원소의 개수가 다르면 연산이 수행되지 않음

ar = np.array([1,2,3])
br = np.array([4,5,6])
cr = np.array([7,8,9,10])
print(ar + br)
#벡터의 차원이 1차원으로 동일하지만 원소의 개수가 3개와 4개로 달라서 에러
# print(ar + cr)
[5 7 9]

# ValueError: operands could not be broadcast together with shapes (3,) (4,) 

 

 

4) 선형 가중 결합

- 각 벡터에 스칼라 가중치를 적용해서 혼합하는 방법

l1 = 1
l2 = 2
l3 = -3

v1 = np.array([4.5, 1])
v2 = np.array([-4.5, -1])
v3 = np.array([1.5, 2])

print(l1 * v1 + l2 * v2 + l3 * v3)
[-9. -7.]

 

- 활용

  • 선형회귀 분석에서 회귀 변수와 계수의 관계를 선형 가중 결합으로 표현
  • 주성분분석 같은 차원 축소 과정에서 각 성분의 분산을 최대화하는 가중치와 계수의 관계 표현

- 선형독립성

  • 각 열의 독립적인 정도
  • 하나의 벡터를 다른 벡터들의 선형 가중 결합으로 나타낼 수 있는지 확인
    상관계수는 2개의 벡터 사이의 독립성을 판단

 

5) 벡터의 내적

- 벡터를 기하학적으로 표현했을 때 2개의 벡터를 확장해서 만든 사각형의 너비

- 내적을 구하려면 한쪽 벡터를 전치했을 때 차원과 동일해야 함

- 계산은 dot 함수 @ 연산자 이용

  • 1차원의 경우 전치를 하지 않아도 데이터의 개수만 같으면 전치를 해서 수행
  • 2차원 이상의 경우는 전치를 했을 때의 차원이 같아야 함
#일차원 벡터의 내적
x = np.array([1, 2, 3])
y = np.array([4, 5, 6])

#내적 구하기 - 일차원 벡터의 경우는 데이터의 개수만 같으면 알아서 수행
print(np.dot(x, y))
print(x @ y)

# 이 경우는 이차원 배열이라서 첫번째 데이터의 열의 개수와 두번째 데이터의 행의 개수가 맞아야 수행
x = np.array([[1], [2], [3]])
y = np.array([[4], [5], [6]])
print(np.dot(x.T, y))
print(x.T @ y)
32
32
[32]
[32]

 

- 가중합이나 가중평균에 사용

- 벡터의 내적은 두 벡터 간의 유사도를 계산하는데도 이용

  • 두 벡터가 닮은 정도 : 유사도
  • 내적을 이용해서 구한 유사도 : 코사인 유사도
  • 정규화를 한 후 내적 : 피어슨 상관계수
#이미지 가져오기
from sklearn.datasets import load_digits
import matplotlib.gridspec as gridspec

digits = load_digits()
digits.images[0]
array([[ 0.,  0.,  5., 13.,  9.,  1.,  0.,  0.],
       [ 0.,  0., 13., 15., 10., 15.,  5.,  0.],
       [ 0.,  3., 15.,  2.,  0., 11.,  8.,  0.],
       [ 0.,  4., 12.,  0.,  0.,  8.,  8.,  0.],
       [ 0.,  5.,  8.,  0.,  0.,  9.,  8.,  0.],
       [ 0.,  4., 11.,  0.,  1., 12.,  7.,  0.],
       [ 0.,  2., 14.,  5., 10., 12.,  0.,  0.],
       [ 0.,  0.,  6., 13., 10.,  0.,  0.,  0.]])
#0번 이미지 가져오기
d1 = digits.images[0]
d2 = digits.images[10]

#1번 이미지 가져오기
d3 = digits.images[1]
d4 = digits.images[11]

#유사도 계산을 위해 이미지의 shape 조정
v1 = d1.reshape(64, 1)
v2 = d2.reshape(64, 1)
v3 = d3.reshape(64, 1)
v4 = d4.reshape(64, 1)
#이미지 출력해보기
plt.figure(figsize=(9,9))

#이미지를 동일한 크기로 출력하기 위해서 영역 설정
gs = gridspec.GridSpec(1, 4, height_ratios=[1], width_ratios=[9, 9, 9, 9])

#eval: 문자열로 대입하면 문자열에 해당하는 인스턴스를 찾아오는 함수
for i in range(4):
    plt.subplot(gs[i])
    plt.imshow(eval("d" + str(i+1)))
    plt.grid(False)
    plt.title("image{}".format(i+1))

 

# 코사인 유사도 계산

#동일한 이미지
print(v1.T @ v2)
print(v3.T @ v4)

#다른 이미지
print(v1.T @ v3)
print(v2.T @ v4)
[[3064.  ]]
[[3661.  ]]
[[1866.  ]]
[[2479.  ]]

 

 

 

 

 

 

 

 

 

 

'Python' 카테고리의 다른 글

[Python] 머신러닝  (0) 2024.03.07
[Python] 지도학습 연습 _ 자전거 대여 수요 예측  (0) 2024.03.06
[Python] 샘플링 _ 표본 추출  (1) 2024.02.26
[Python] 확률 분포 모형  (1) 2024.02.26
[Python] 기술통계  (0) 2024.02.22

1. 전수조사와 표본조사

- 전수조사

  • 모집단 내에 있는 모든 대상을 조사하는 방법
  • 모집단의 특성을 정확히 반영하지만 시간과 비용이 많이 소모됨

- 표본조사

  • 모집단으로부터 추출된 표본을 대상으로 분석 실시
  • 전수조사의 단점을 보완하지만 모집단의 특성을 반영하는 표본이 제대로 추출되지 못하면 수집된 자료가 무용지물

 

2. 용어

- Sample: 큰 데이터 집합으로부터 얻은 부분집합

- Population: 데이터 집합을 구성하는 전체

- N(n): 모집단의 크기

 

- 랜덤 표본추출: 무작위로 표본을 추출하는 것

  • 층화 랜덤 표본추출: 모집단을 여러개의 층으로 나누고 각 층에서 무작위로 표본 추출
  • 단순 랜덤 표본추출: 층화 없이 단순하게 무작위로 표본 추출

- 표본 편항 (Sample Bias): 모집단을 잘못 대표하는 표본 추출

 

- 표준 오차

  • 통계에 대한 표본 분포의 변동성
  • 표본 값들의 표준편차(s)와 표본 크기(n)를 기반으로 추정
  • 표준오차와 표본 크기 사이의 관계는 n제곱근의 법칙이라고 하는데 표준오차를 2배 줄이려면 표본 크기를 4배로 증가시켜야 함

 

3. 표본 추출

- 전체 데이터(모집단) 중 일부를 표본(샘플)로 추출하는 작업이 데이터 분석에서 필수

- 훈련 데이터(80%), 테스트 데이터(20%)로 분리하여 데이터에 대한 모델링은 훈련 데이터로만 수행하고 모델의 성능은 테스트 데이터로 평가하면 모델의 성능을 가장 적절히 평가

- 데이터의 분포가 일정하지 않다면 가중치를 이용해서 데이터 추출하는 부분도 고려

- random.sample

 

3-1) 머신러닝에서 표본 추출

- train data(모델 생성) 와 test data(모델 테스트)로 나누는 방법

- train data 와 test data 와 validation data(모델 검증)로 나누는 방법

 

3-2) 복원추출과 비복원추출

- 복원 추출: 추출된 데이터를 모집단에 포함시켜서 추출

  • python에서는 random.random, randint, randrange 등의 함수가 있음

- 비복원 추출: 한 번 추출된 데이터는 모집단에 포함시키지 않고 추출

  • python에서는 random.sample 함수가 제공됨
import random

li = ["SES", "소녀시대", "f(x)", "레드벨벳", "에스파"]

#복원 추출
for i in range(5):
    print(li[random.randint(0, len(li)-1)], end='\t')

print()
#비복원 추출
print(random.sample(li, k=5))
f(x) f(x) 레드벨벳 SES f(x)
['f(x)', '레드벨벳', '소녀시대', 'SES', '에스파']

 

 

3-3) 가중치를 적용한 표본추출

- 표본의 비율이 달라져야 하는 경우 표본을 추출할 때 비율을 설정해서 추출하는 것

- numpy.random.choice(a, size=None, replace=True, p=None)

  • a: 배열,   size: 개수,   replace: 복원여부,   p: 확률
  • 추출하고자 하는 배열, 개수, 확률을 제시하면 가중치를 적용해서 표본 추출
  • 현실 세계의 문제에서는 간혹 가중치를 적용한 표본 추출이 어려우면 표본 추출을 해서 분석을 한 후 가중치 적용
ar = ["라투", "오미크론", "다크스펙터", "나이즈", "슬리피"]

print(np.random.choice(ar, 10, [0.05, 0.15, 0.2, 0.3, 0.3]))
['다크스펙터' '라투' '나이즈' '슬리피' '다크스펙터' '라투' '라투' '슬리피' '오미크론' '슬리피']

 

 

3-4) pandas의 표본추출

- Series나 DataFrame에서 sample 함수 제공

Series.sample(n=None, frac=None, replace=False, weights=None, random_state=None, axis=None)

  • n: 추출한 샘플의 수 (frac과 중복 사용 불가)
  • frac: 전체 개수의 비율만큼 샘플을 반환하려 할 경우 사용
  • replace: 복원추출 여부, 기본값음 False(중복불가)
  • weights: 샘플 추출 시 샘플마다 뽑힐 확률 조정
  • random_state: 랜덤 샘플 추출 시 시드를 입력받음
  • axis: 샘플을 추출할 방향. 기본=0(행)
ex_df = pd.DataFrame(np.arange(0,12).reshape(4, 3))
print(ex_df)
print()

#2개의 행을 추출
print(ex_df.sample(n=2))
print()

#1개의 열을 랜덤하게 추출
print(ex_df.sample(n=1, axis=1))
print()
   0   1   2
0  0   1   2
1  3   4   5
2  6   7   8
3  9  10  11

   0  1  2
2  6  7  8
0  0  1  2

   0
0  0
1  3
2  6
3  9

 

 

3-5) sklearn을 이용한 샘플 추출

- scikit-learn.model_selection 의 train_test_split()

  • 훈련 데이터 와 테스트 데이터를 분할하기 위한 API
  • shuffle 옵션: 기본값은 True 로 되어 있는데 시계열 데이터와 같은 데이터를 추출할 때처럼 랜덤한 추출이 아닌 순차적 분할을 해야 하는 경우, False 를 설정해서 추출
  • test_size: 테스트 데이터의 비율을 설정
    • 일반적으로 8:2 또는 7:3을 선호하지만 데이터가 아주 많은 경우에는 5:5 설정 가능
    • 이렇게 데이터가 많으면 테스트 데이터를 다시 테스트 데이터 와 검증을 위한 데이터로 분할하기도 함
  • 리턴되는 데이터는 4개의 데이터 그룹에 대한 tuple
    • 피처의 훈련데이터, 피처의 테스트 데이터, 타겟의 훈련 데이터, 타겟의 테스트 데이터
#데이터 생성
#특별한 경우가 아니면 피처는 대문자 X로 나타냅니다.
#타겟은 소문자 y로 나타냅니다.
#numpy 의 1차원 ndarray는 출력을 하면 옆으로 펼쳐지지만 하나의 열로 간주합니다.
X = np.arange(20).reshape(10, 2)
print(X)  
y = np.arange(10)
print(y)
[[ 0  1]
 [ 2  3]
 [ 4  5]
 [ 6  7]
 [ 8  9]
 [10 11]
 [12 13]
 [14 15]
 [16 17]
 [18 19]]
[0 1 2 3 4 5 6 7 8 9]

 

#순차적 분할 7:3

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, shuffle=False)
print('X_train shape:', X_train.shape)
print('X_test shape:', X_test.shape)
print('y_train shape:', y_train.shape)
print('y_test shape:', y_test.shape)
print()
print(X_train)
X_train shape: (7, 2)
X_test shape: (3, 2)
y_train shape: (7,)
y_test shape: (3,)

[[ 0  1]
 [ 2  3]
 [ 4  5]
 [ 6  7]
 [ 8  9]
 [10 11]
 [12 13]]

 

- 무작위 추출

  • shuffle 옵션 제거하고 동일한 데이터 추출을 위해 random_state에 seed값 고정
#순차적 분할 - 7:3
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
print(X_train)
print()
print(y_train)
[[ 0  1]
 [14 15]
 [ 4  5]
 [18 19]
 [ 8  9]
 [ 6  7]
 [12 13]]

[0 7 2 9 4 3 6]
l1 = np.array(range(0, 10))
l2 = np.array(range(10, 20))

print(np.vstack((l1, l2)).T)
[[ 0 10]
 [ 1 11]
 [ 2 12]
 [ 3 13]
 [ 4 14]
 [ 5 15]
 [ 6 16]
 [ 7 17]
 [ 8 18]
 [ 9 19]]

 

- 층화 추출

  • 데이터를 일정한 비율로 무작위 추출
  • 머신러닝에서 타겟의 비율의 일정하지 않은 경우 사용
    • 타켓의 비율이 편차가 심한 경우 랜덤하게 추출했을 때 비율이 낮은 데이터가 추출이 안될 수 도 있고 너무 많이 추출될 수 도 있다.
    • 이렇게 머신러닝의 결과가 엉뚱한 결과가 추출될 수 있습니다.
  • 층화 추출을 할 때는 stratify 옵션에 리스트를 대입하면 비율 계산을 해서 추출
X = np.arange(30).reshape(15, 2)
y = np.arange(15)
grep = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

#단순한 방식으로 추출을 하게 되면 한쪽으로 샘플이 쏠리는 현상이 발생하고
#이렇게 되면 샘플 데이터에서는 잘 맞지만 실제 서비스 환경에서는 제대로 맞추지 못하는
#현상이 발생할 수 있습니다.
'''
X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                   test_size=0.2,
                                                   shuffle=True,
                                                   random_state = 1004)
print(y_train)
print(y_test)
'''

#stratify 에 대입한 리스트의 비율을 확인해서 샘플링
X_train, X_test, y_train, y_test = train_test_split(X, y, 
                                                   test_size=0.2,
                                                   shuffle=True,
                                                   stratify=grep,
                                                   random_state = 32)
print(y_train)
print(y_test)
[14 12  7  9 10  4  6  2 11  1  0  5]
[ 8  3 13]

 

 

3-6) 층화 추출 API

- scikit-learn.model_selection 의 StratifiedShuffleSplit()

- n_splits: 데이터를 몇 개의 그룹으로 분할할지를 설정

- 데이터를 리턴하지 않고 데이터의 인덱스를 리턴

- k-folds cross-validation을 수행하기 위해서 만든 API

  • 모델을 만들고 이를 검증을 할 때 전체 데이터를 N 등분해서 N-1 개의 그룹으로 모델을 만들고 1개의 그룹으로 테스트를 수행하는데 N 번 수행
#교차 검증을 위해서 데이터를 n 등분 층화 추출을 해주는 API
#직접 사용하지 않고 머신러닝 모델이 내부적으로 사용
from sklearn.model_selection import StratifiedShuffleSplit

split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=1002)

#인덱스를 리턴하므로 인덱스를 이용해서 데이터를 추출해서 확인
for train_idx, test_idx in split.split(X, grep):
    X_train = X[train_idx]
    X_test = X[test_idx]
    y_train = y[train_idx]
    y_test = y[test_idx]    

print(y_train)
print(y_test)
[ 8  4  7  0 10 11 12  2 14  3  5  6]
[13  9  1]

 

 

3-7) 재표본 추출

- 랜덤한 변동성을 알아보자는 일반적인 목표를 가지고 관찰된 데이터의 값에서 표본을 반복적으로 추출하는 것

- 종류

순열 검정

- 두 개 이상의 표본을 함께 결합해서 관측값을 무작위로 재표본으로 추출하는 과정

- 두 개 이상의 표본을 사용하여 A/B 검정(하나의 UI 와 새로운 UI를 도입했을 때 결과 비교 등에 사용하는 방식)  등에 이용

  1. 여러 그룹의 결과를 하나의 데이터 집합으로 합침
  2. 결합된 데이터를 잘 섞은 다음 A 그룹과 동일한 크기의 표본을 비복원 무작위로 추출
  3. 나머지 데이터에서 B 그룹과 동일한 크기의 샘플을 비복원 무작위로 추출
  4. 그룹이 더 존재하면 동일한 방식으로 추출
  5. 원래의 표본에 대해 구한 통계량 또는 추정값이 무엇이었든지 간에 추출한 재표본에 대해서 다시 계산하고 기록을 한 다음 원래의 표본에 대해서 구한 값과 비교
  6. 이 작업을 여러 번 반복

부트스트래핑

- 모수의 분포를 추정하는 방법으로 현재있는 표본에서 추가적으로 표본을 복원 추출하고 각 표본에 대한 통계량을 다시 계산하는 것

- 개념적으로만 보면 원래 표본을 수천 수백만 번 복제하는 것

  1. 1억개의 모집단에서 200개의 표본을 추출
  2. 200개의 표본 중에서 하나를 추출해서 기록하고 복원
  3. 이 작업을 n번 반복
  4. n번 반복한 데이터를 가지고 통계량을 계산
  5. 이전 과정을 여러 번 반복해서 통계량을 구하고 이 통계량을 이용해서 신뢰구간을 구하는 방식

- 시간이 오래 걸린다.

  • coffee_dataset.csv 파일의 데이터
    21살 미만 여부
    커피를 마시는지 여부
    키에 대한 데이터
diffHeightListOver21 = []
for _ in range(iterationNum):
    bootSample = df_sample.sample(200, replace=True) # 복원 추출
    nonCoffeeHeightMeanOver21 = bootSample.query("age != '<21' and drinks_coffee == False").height.mean() # 21살 이상이며 커피를 마시지 않는 사람 평균 키
    coffeeHeightMeanOver21 = bootSample.query("age != '<21' and drinks_coffee == True").height.mean() # 21살 이상이며 커피를 마시는 사람 평균 키

    diff = nonCoffeeHeightMeanOver21 - coffeeHeightMeanOver21
    diffHeightListOver21.append(diff)

np.percentile(diffHeightListOver21, 0.5), np.percentile(diffHeightListOver21, 99.5)
0.37151963778610253 3.266041922112605
# 1. 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
print(df[df['drinks_coffee'] == False].height.mean() - df[df['drinks_coffee'] == True].height.mean())
# 2. 21살 이상과 21살 미만인 사람들의 평균 키 차이
print(df[df['age'] == '>=21'].height.mean() - df[df['age'] == '<21'].height.mean())
# 3. 21살 미만인 사람들 중 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
print(df.query("age == '<21' and drinks_coffee == False").height.mean() - df.query("age == '<21' and drinks_coffee == True").height.mean())
# 4. 21살 이상인 사람들 중 커피를 마시지 않는 사람과 커피를 마시는 사람의 평균 키 차이
print(df.query("age != '<21' and drinks_coffee == False").height.mean() - df.query("age != '<21' and drinks_coffee == True").height.mean())
-1.9568024933369799
3.88229124992111
1.6993900935511732
1.9509354889786579

 

[Python] 확률 분포 모형

0ㅑ채
|2024. 2. 26. 19:50

1. 확률적 데이터와 확률 변수

1-1) 확률적 데이터

- 결정론적 데이터: 실험, 측정, 조사 등을 통해 어떤 데이터 값을 반복적으로 얻을 때 누가 언제 얻더라도 항상 동일한 값이 나오는 것

- 확률적 데이터: 정확히 예측할 수 없는 값 (ex. 혈압)

- 대다수 데이터는 확률적 데이터다. 여러 조건이나 상황에 따라 데이터 값이 변할 수 있고 측정 시에 발생하는 오차 때문!

 

1-2) 분포

- 확률적 데이터를 살펴보면 어떤 값은 자주 등장하고 어떤 값은 드물게 등장하는데

  어떤 값이 자주 나오고 어떤 값이 드물게 나오는지를 나타내는 정보를 의미

- 범주형 데이터 - 교차분할표, plot으로 확인

- 연속형 데이터 - binning 후 히스토그램 등으로 확인

 

1-3) 분포 추정을 위한 기술 통계값

- 평균, 중앙값, 최빈값

- 편차, 분산, 표준편차, 평균절대편차, 중위값의 중위절대편차

- 범위(Range), 순서통계량(Order Statistics), 백분위수(Percentile), 사분위수(Interquartile Range)

 

1-4) 대칭 분포

- 좌우가 동일한 모양의 분포

- 분포 모양에 따른 평균, 중앙값, 최빈값의 특성

  • 평균을 기준으로 대칭이면, 평균 = 중앙값
  • 대칭 분포이면서 하나의 최대값만을 가지는 단봉분포이면, 최빈값 = 평균
  • 대칭 분포를 비대칭으로 만드는 데이터가 추가되면,   평균 -> 중앙 값 -> 최빈값   순으로 영향

 

1-5) 표본 비대칭도 - 왜도(Skewness)

- 평균과의 거리를 세제곱해서 구한 특징 값

- 표본 비대칭도가 0이면, 분포가 대칭

- 표본 비대칭도가 음수면, 표본 평균에서 왼쪽 값의 표본이 나올 가능성이 더 높음

 

1-6) 표본 첨도(kurtosis)

- 정규 분포보다 중앙에 값이 몰려있는 정도

- 평균과의 거리를 네제곱 해서 구한 특징 값

- 데이터가 중앙에 몰려있는 정도를 정밀하게 비교하는 데 사용

- 정규 분포보다 첨도가 높으면 양수가 되고 정규 분포포다 첨도가 낮으면 음수로 정의

  • 값이 3이면 정규 분포와 동일한 분포
  • 값이 3보다 작으면 정규 분포보다 꼬리가 얇은(넓게 펼쳐짐) 분포
  • 값이 3보다 크면 정규 분포보다 꼬리가 두꺼운(좁고 뾰족) 분포

 

 

1-7) 표본 모멘트

- k 제곱을 한 모멘트를 k차 표본 모멘트

- 평균은 1차 모멘트, 분산이 2차 모멘트, 비대칭도가 3차 모멘트, 첨도가 4차 모멘트

- scipy.stats 패키지의 skew 함수(왜도) kurtosis 함수(첨도),  moment 함수(데이터와 차수) 설정해서 구함

import numpy as np
import scipy as sp

# 샘플 데이터 생성
x = np.random.normal(size = 1000)
print(x)
[ 7.09400466e-01  2.90488628e-02  1.57024913e+00 -2.54960161e+00
  3.28136877e-01  1.22922309e-01  2.48529952e+00 -8.75967514e-01
 -1.67285266e+00  1.24635639e-01 -5.71475047e-01  5.97966093e-02
  1.05283163e+00  1.39524451e-01 -1.60198487e+00  1.34187712e+00
...
print("표본 평균 :", np.mean(x))
print("1차 모멘트 :", sp.stats.moment(x,1))

print("표본 분산 :", np.var(x))
print("2차 모멘트 :", sp.stats.moment(x,2))

print("표본 왜도 :", sp.stats.skew(x))
print("3차 모멘트 :", sp.stats.moment(x,3))

print("표본 첨도 :", sp.stats.kurtosis(x))
print("4차 모멘트 :", sp.stats.moment(x,4))
표본 평균 : 0.013852167418918475
1차 모멘트 : 0.0

표본 분산 : 0.9187724279137195
2차 모멘트 : 0.9187724279137195

표본 왜도 : 0.031172968025046505
3차 모멘트 : 0.02745301735258748

표본 첨도 : -0.11861329022879596
4차 모멘트 : 2.4323017710014816
  • excess kurtosis : 정규 분포의 첨도는 3인데 해석을 편하게 하기 위해서 3을 빼서 0을 만들기도 함
  • 2개의 값이 다른이유는 kurtosis는 4차 모멘트에서 3을 뺸 값이기 때문

 

 

2. 확률 분포 함수

2-1) 확률 질량 함수

- 어떤 사건의 확률 값을 이용해서 다른 사건의 확률 값 계산 가능

- 유한개의 사건이 존재하는 경우 각 단순 사건에 대한 확률만 정의하는 함수

- 단순 사건은 서로 교집합을 가지지 않는 사건 (사건이 이산적인 데이터)

  • 주사위에서 1, 2, 3, 4, 5, 6은 다른사건과 교집합을 가지지 않으므로 단순 사건, 이러한 단순 사건의 확률 합은 1
  • 확률 질량 함수는 각 사건이 발생할 수 있는 확률을 계산해주는 함수
    • 범주형이면 각 사건의 확률을 명확하게 나타낼 수 있지만 연속형은 구간이 필요하다. 
    • 연속형 데이터에서는 2개의 데이터 사이에 무한대의 데이터가 존재할 수 있기 때문에 하나의 값에 해당하는 확률은 0임
    • 둥그런 원반에 화살을 쏴서 특정 각도에 해당하는 확률을 계산
      원반에서 0 ~ 180 도 사이에 위치할 확률은? 1/2

- 소문자 p로 표시

 

# 확률 질량 함수 출력

#주사위의 각 눈을 나타내는 수 - 사건
x = np.arange(1, 7)

# 조작된 주사위의 확률질량함수
y = np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.5])

plt.stem(x, y)

 

 

2-2) 확률 밀도(분포) 함수 (Probability Density Function, pdf)

- 표본 수가 무한대(ex.연속형 데이터)인 경우 확률 질량 함수로 정의할 수 없음

    • 회전하는 원반에 화살을 쏘고 화살이 박힌 위치의 각도를 결정하는 문제에서 정확하게 0도가 될 확률은 ?
    • 이런 경우 구간을 이용해서 확률 정의
      각도가 0도 보다 크거나 같고 30보다 작은 경우의 확률은 정의가 가능 -> 동일한 가능성을 가진 사건이 12개(유한개)로 정의 가능 

- 확률 밀도 함수는 구간에 대한 확률을 정의한 함수. pdf

 

2-3) 누적 밀도(분포) 함수( Cumulative Distribution Function - cdf )

- 구간을 설정하기 위해 2개의 값이 필요한 확률 밀도 함수와 달리 하나의 값만으로 사건을 정의하는 방법이다.
- 시작점의 위치를 음의 무한대로 설정하고 하나의 값을 무조건 종료점으로 판정해서 확률을 정의한 함수

  • 음의 무한대에 대한 누적분포함수 값 = 0
  • 양의 무한대에 대한 누적분포함수 값 = 1
  • 입력이 크면 누적분포함수의 값은 같거나 커짐

- 0에서 시작해서 천천히 증가하면서 1로 다가가는 형태

- 단조 증가 설질에 의해 절대로 내려가지는 않음

 

 

 

3. scipy

- 수치 해석 기능을 제공하는 패키지

- stats 서브 패키지에서 확률 분포 분석을 위한 다양한 기능 제공

- 확률 분포 클래스 

  •  

 

3-1) 사용법

- 정규 분포 객체 생성

  • 모수 2개 (loc - 기대값·평균, scale - 표준편차)를 가지고 생성
  • rv = scipy.stats.norm( loc=1, scale=2 )  #평균이 1이고 표준편차가 2인 정규분포객체 생성

 

3-2) 확률 분포 객체의 함수

- pmf: probability mass function 확률 질량 함수 (이 사건이 발생할 확률)

- pdf: probability density function 확률 밀도 함수 (구간에 대한 확률값)

- cdf: cumulative distribution function 누적 분포 함수 (0에서부터 x까지의 확률)

----------------------어떤 특정한 사건의 확률

- ppf: percent point function 누적 분포 함수의 역함수 

- sf: survival function = 생존 함수 (1-누적분포함수)   (x부터 끝까지의 확률)

- isf: inverse survival function 생존 함수의 역함수

- rvs: random variable sampling 랜덤 표본 생성

----------------------- 확률을 통해 사건을 발견

 

# 확률 밀도 함수

xx = np.linspace(-8, 8, 100)

rv = sp.stats.norm(loc=1, scale=2)

pdf = rv.pdf(xx)
plt.plot(xx, pdf)

 

 

3-5) 정규 분포 객체의 누적 분포 함수

xx = np.linspace(-8, 8, 100)

rv = sp.stats.norm(loc=1, scale=2)

pdf = rv.cdf(xx)
plt.plot(xx, cdf)

 

 

 

 

 

4. 확률 분포 모형

4-1) 베르누이 분포

- 베르누이 시행

  • 결과가 두가지 중 하나로만 나오는 실험이나 시행
  • ex. 동전을 던져서 앞면이나 뒷면이 나오는 경우

- 베르누이 확률 변수

  • 베르누이 시행 결과를 0 또는 1로 바꾼 것
    • -1이나 1로 표기하기도 함
  • 이산 확률 변수다. 두가지 중에 하나의 경우만 나올 수 있기 때문.

- 베르누이 분포는 1이 나올 확률을 의미하는 모수를 가지게 됨

  • 0이 나올 확률은   1 - (1이 나올 확률)  을 하면 나오기 때문에 별도로 정의하지 않는다.

- scipy.stats.bernoulli 클래스로 구현

  • p 인수로 분포 모수 설정
  • 기술 통계값들은 discribe() 함수 이용, 결과는 DescribeResult 타입으로 리턴.
    • 이 안에 데이터 개수, 최대, 최소, 평균, 분산, 왜도, 첨도를 순차적으로 저장
  • 이산 확률 변수를 이용하기 때문에 확률질량함수 pmf 함수를 사용 가능
# 확률변수 0.6으로 설정 
rv = sp.stats.bernoulli(0.6)
rv
#나올 수 있는 경우
xx = [0, 1]

#확률 질량 함수
plt.bar(xx, rv.pmf(xx))
#X축 수정
plt.xticks([0,1], ['x=0', 'x=1'])

 

 

- 시뮬레이션 (샘플링)

  • random_state는 seed 설정
  • seed를 고정시키면 동일한 데이터가 샘플링되고
  • seed를 고정하지 않으면 현재 시간 값을 seed로 설정하기 때문에 무작위로 샘플링
    • 머신러닝에서는 일반적으로 seed 고정
    • 동일한 데이터를 샘플링해서 비교해야 모델 또는 알고리즘 간 비교가 가능하기 때문
#샘플 데이터 생성
x = rv.rvs(1000, random_state = 42)
sns.countplot(x=x)

 

 

- 이론적인 모델과 시뮬레이션 한 결과 비교

  • 정확히는 주장과 비교
  • 여러차례 시뮬레이션을 한 결과가 비교를 해서 주장이 어느정도 타당성을 갖는지 확인
y = np.bincount(x, minlength=2) / float(len(x))
print(y)
[0.387   0.613]

 

#결과 비교

df = pd.DataFrame({'이론':rv.pmf(xx), '시뮬레이션':y})
df.index = [0, 1]
df

 

# 시뮬레이션 결과

result = sp.stats.describe(x)
print(result)
DescribeResult(
nobs=1000, 
minmax=(0, 1), 
mean=0.613, 
variance=0.23746846846846847, 
skewness=-0.46400506298458166, 
kurtosis=-1.7846993015246748)

 

 

 

4-2) 이항 분포

- 베르누이 시행을 N번 반복할 때, 어떤 경우에는 N번 모두 1이 나오고, 어떤 경우에는 1이 한번도 안나올 수 있는데

이때 1이 나온 횟수를 X라고 하면 X는 0부터 N까지를 가지는 확률 변수다. 이를 이항분포를 따르는 확률변수라고 한다.

- 표본 데이터가 1개이면 베르누이 분포, 표본 데이터가 여러 개면 이항 분포

- scipy.stats 서브 패키지의 binom 클래스로 구현

- rv = sp.stats.binom( 전체 시행 횟수, 1이 나올 확률 )

rv = sp.stats.binom(10 ,0.5) #1이 나올 확률은 0.5이고 10번 수행

xx = np.arange(11)
plt.bar(xx, rv.pmf(xx), align = "center")

 

# 시뮬레이션

x = rv.rvs(1000) # 1000번 시뮬레이션
sns.countplot(x=x)
print(x[x > 7])
[8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 9 8 8 8 8 8 8 8 8 8 8
 8 8 8 8 8 8 9 8 9 8]

 

# 앞 뒷면 확률이 동일한 동전을 100번 던졌을 때 앞면이 60회 이상 나올 확률

  • 실험횟수: n
  • 확률: p
  • 발생횟수: k
#pmf 이용 : 0 ~ 59까지 나올 확률을 모두 더한 후 1에서 확률 빼기

#cdf 이용 : 0번부터 나올 확률의 누적 확률
p = sp.stats.binom.cdf(n=100, p=0.5, k=59)
print(1-p)

#sf 이용 : 생존 함수는 1-cdf의 값을 가지는 함수
p = sp.stats.binom.sf(n=100, p=0.5, k=59)
print(p)
0.02844396682049044
0.028443966820490392

 

# 앞면이 20~60번 나올 확률

#cdf 이용 : 0번부터 나올 확률의 누적 확률
p1 = sp.stats.binom.cdf(n=100, p=0.5, k=19)
p2 = sp.stats.binom.cdf(n=100, p=0.5, k=60)
print(p2 - p1)
0.9823998997560095

 

 

- 퍼센트 포인트 함수 ppf 활용: 모수로 구하고자 하는 확률(p) 대입

  • 원하는 지점의 확률 q
  • 비율을 설정해서 횟수를 구해주는 함수
  • 동전을 던졌을 때 n회 이상 나올 확률이 80% 넘는 지점은?
p = sp.stats.binom.ppf(n=100, p=0.5, q=0.8)
print(p)

p1 = sp.stats.binom.cdf(n=100, p=0.5, k=p)
p2 = sp.stats.binom.cdf(n=100, p=0.5, k=p+1)

print(p1)
print(p2)
54.0
7.888609052210118e-31
7.967495142732219e-29

 

 

- 베르누이 분포와 이항분포 활용 - 스팸 메일 필터

  • 현재까지 모든 메일을 확인해서 스팸으로 가정할 수 있는 필터의 확률을 구함
  • 베르누이 분포를 이용!
  • 각 메일을 단어 단위로 원핫 인코딩 수행
  • 각 단어가 메일에 포함된 개수와 스팸 메일에 포함된 개수의 비율 계산
  • 현재 메일이 온 경우 메일에 포함된 내용들을 원핫 인코딩 해서 기존에 만들어진 단어들의 스팸 비율 확인해서 판정 
감성분석, 욕설 방지 등에 사용
utf8mb4 : 글자 + 이모티콘까지

 

 

4-3) 카테고리 분포

- 이항 분포가 성공인지 실패인지를 구별하는 거라면

  카테고리 분포는 2개 이상의 경우 중 하나가 나오는 경우

  대표적인 경우가 주사위 (k=6)

- 스칼라 값으로 표현하는 경우가 많지만 확률 변수에서는 카테고리를 원핫인코딩해서 표현

 

각 열로부터 각 베르누이 확률 분포의 모수 추정값



순서가 명확한 의미를 가지면 라벨인코딩, 순서가 의미없이 독립적이면 원핫인코딩

원핫인코딩을 하는 이유:
서로 독립적인 데이터들의 거리를 동일하게 맞추기 위해!
그렇지 않으면 편향이 될 가능성이 높다.

 

- scipy에서는 카테고리 분포 클래스 제공 X

  • 다항분포를 위한 multinomial 클래스에서 시행횟수를 1로 설정하면 카테고리 분포가 됨

- 베르누이 분포가 이진 분류 문제에 사용된 것처럼 카테고리 분포는 다중 분류 클래스에 이용

  • 모수는 원래 카테고리 개수와 카테고리별 확률이 되어야 하는데 scipy를 이용할 때는 1과 각 카테고리 확률의 vector가 됨
  • 시뮬레이션에 사용되는 데이터는 원핫인코딩 된 데이터
  • 원핫 인코딩은 pandas의 get_dummies()를 이용하거나 sklearn.preprocessing 패키지의 Encoder 클래스를 이용해서 수행 가능
#카테고리 별 확률
mu = [0.1, 0.1, 0.1, 0.1, 0.1, 0.5]

#카테고리 분포 인스턴스
rv = sp.stats.multinomial(1, mu)

#데이터 생성
xx = np.arange(1, 7)

#원핫 인코딩
xx_ohe = pd.get_dummies(xx)
#확률 질량 함수 출력
plt.bar(xx, rv.pmf(xx_ohe.values))
  • 통계는 scikit-learn을 이용하는 전처리
  • 머신러닝에는 numpy.ndarray가 기본 자료형
# 시뮬레이션
X = rv.rvs(100)
print(X)
[[0 0 0 0 0 1]
 [0 1 0 0 0 0]
 [0 0 0 0 0 1]
 [0 0 0 0 0 1]
 [0 0 0 0 0 1]
 [1 0 0 0 0 0]
#확인해보면 6이 굉장히 많다.

 

 

 

4-4) 다항 분포

- 베르누이 시행을 여러번 한 결과가 이항분포, 카테고리 시행을 여러번 한 결과가 다항분포

- scipy.stats.multinomial(시행횟수, 확률) 이용해서 인스턴스 생성

- 시뮬레이션을 하게 되면 각 카테고리의 횟수가 리턴됨

## 다항분포 - 카테고리 분포를 여러번 수행한 분포
rv = sp.stats.multinomial(100, [1/6, 1/6, 1/6, 1/6, 1/6, 1/6])

#1000번 시뮬레이션
X = rv.rvs(1000)
print(X[:5])
[[23 16 14 11 18 18]
 [24 15 17 18 13 13]
 [21 18 15 16 16 14]
 [13 12 24 12 19 20]
 [17 16 14 11 19 23]]

 

# 시각화

df = pd.DataFrame(X).stack().reset_index()
df.columns =['시도', '클래스', '데이터개수']
df.head(12)
  • 시행 횟수가 많으면 swarmplot 보다는 violinplot이 낫다.
sns.swarmplot(x='클래스', y='데이터개수', data=df)
sns.violinplot(x='클래스', y='데이터개수', data=df)

 

 

4-5) 균일 분포

- 모든 확률 변수에 대해 균일한 분포를 갖는 모형

- scipy.stats.uniform 

  • 버스의 배차 간격이 일정한 경우
  • 시간표를 모르고 버스 정류장에 나갔을 때 평균 대기 시간은?

- Rvs(loc=0, scale =1, size=1, random_state=None)

  • Loc : 기댓값, 평균
  • Scale : 범위라고도 하는데 표준 편차
  • Size : 개수
  • Random_state : seed값
  • seed값은 여기서 설정하지 않고 numpy를 이용해서 미리 설정한 후 사용해도 됩니다.

- 균일 분포는 동일한 크기의 구간에 대한 확률이 거의 비슷하지만, 2개의 균일분포 합은 그렇지 않다.

 

# 마을에 다니는 버스의 주기는 1시간일 때, 버스 시간표를 모르는 사람이 정류장에 갔을 때 대기 시간이 7분 이내일 확률

  • 균일분포
  • 편차(scale): 60
result = sp.stats.uniform.cdf(scale=60, x=7)
print(result)
0.11666666666666667

 

# 대기 시간이 5~20분 이내일 확률

result1 = sp.stats.uniform.cdf(scale=60, x=20)
result2 = sp.stats.uniform.cdf(scale=60, x=5)
print(result1 - result2)
0.25

 

# 50분 이상일 확률

result1 = sp.stats.uniform.cdf(scale=60, x=50)
result2 = sp.stats.uniform.sf(scale=60, x=50)
print(1- result1)
print(result2)
0.16666666666666663
0.16666666666666663
  • 연속형 분포이기 때문에 50을 0으로 잡고 설정!

 

# 시간이 m분 이내일 확률이 73%가 되는 지점은?

result = sp.stats.uniform.ppf(scale=60, q=0.73)
result
43.8

 

 

4-6) 정규분포

- 가우시안 정규분포 (Gaussian normal distribution) 또는 정규 분포

- 자연 현상에서 나타나는 숫자를 확률 모형으로 나타낼 때 가장 많이 사용되는 모형

- 데이터가 평균을 기준으로 좌우 대칭

- 평균을 기준으로 표준 편차 좌우 1배 내에 약 68% 그리고 좌우 2배 안에 약 95% 정도의 데이터가 분포된 경우

- 평균이 0이고 표준편차가 1인 경우를 특별히 표준 정규 분포라고 함

- scipy.stats.norm 

mu = 0
std = 1

#평균이 0이고 표준편차가 1인 정규분포 객체 생성
rv = sp.stats.norm(mu, std)

#시각화 할 구간 설정
xx = np.linspace(-10, 10, 200)

plt.plot(xx, rv.pdf(xx))
plt.ylabel('확률')
plt.title('정규 분포 곡선')
  • -2 ~ 2 사이가 표준편차의 약 4배

# 붓꽃 데이터에서 꽃잎 길이에 대한 히스토그램 - 정규분포와 유사한 모양

from sklearn.datasets import load_iris
setosa_sepal_length = load_iris().data[:50, 2]
array([1.4, 1.4, 1.3, 1.5, 1.4, 1.7, 1.4, 1.5, 1.4, 1.5, 1.5, 1.6, 1.4,
       1.1, 1.2, 1.5, 1.3, 1.4, 1.7, 1.5, 1.7, 1.5, 1. , 1.7, 1.9, 1.6,
       1.6, 1.5, 1.4, 1.6, 1.6, 1.5, 1.5, 1.4, 1.5, 1.2, 1.3, 1.4, 1.3,
       1.5, 1.3, 1.3, 1.3, 1.6, 1.9, 1.4, 1.6, 1.4, 1.5, 1.4])
sns.histplot(setosa_sepal_length, kde=True)
plt.tight_layout()
  • kde: 분포 곡선을 그려준다.

 

- finance-datareader 패키지

  • 사용이 간편하고 다양한 시계열 데이터 수집해서 DaraFrame 생성
pip install finance-datareader

 

#한국 거래소 상장 종목 전체 가져오기

import FinanceDataReader as fdr

df_krx = fdr.StockListing('KRX')
df_krx.head()

 

#한국 코스피 지수 가져오기

import FinanceDataReader as fdr

#한국 코스피는 KS11, 시작날짜, 종료날짜(안쓰면 오늘날짜)
kospi = fdr.DataReader('KS11', '2011-01-01')
print(kospi.head())
                          Open     High      Low       Close     Volume   Change  UpDown  \
Date                                                                        
2011-01-03  2063.69  2070.09  2054.83  2070.08  354083359   0.009       1   
2011-01-04  2074.56  2085.14  2069.12  2085.14  415906517   0.007       1   
2011-01-05  2083.10  2087.14  2076.92  2082.55  386064920  -0.001       2   
2011-01-06  2094.35  2096.65  2066.10  2077.61  407829146  -0.002       2   
2011-01-07  2073.68  2086.20  2068.66  2086.20  335558949   0.004       1   
     
                     Comp         Amount            MarCap  
Date                                                
2011-01-03  19.08  5941470649203  1153204185044950  
2011-01-04  15.06  7799740341295  1163384106084700  
2011-01-05  -2.59  8609366845145  1162008605708700  
2011-01-06  -4.94  8804441828186  1159300878918060  
2011-01-07   8.59  7675864506306  1164242711087020  

 

# 현대 자동차 주가 지수 가져오기

#현대 자동차 주가 데이터 가져오기 (종복번호, 날짜)
df = fdr.DataReader('005380', '2011')
df.head(10)

 

# 나스닥 지수의 일자별 차이 확인

data = pd.DataFrame()

data['IXIC'] = fdr.DataReader('IXIC', '2011-01-01')['Close']

#결측치 제거
data = data.dropna()

#시각화
data.plot(legend=False)
  • 시간이 지나면서 증가
#일차별 차이값 구하기
daily_returns = data.pct_change().dropna()

#평균과 표준 편차 구하기 
mean = daily_returns.mean().values[0]
std = daily_returns.std().values[0]
print("평균 일간수익률: {:3.2f}%".format(mean * 100))
print("평균 일간변동성: {:3.2f}%".format(std * 100))
평균 일간수익률: 0.06%
평균 일간변동성: 1.29%
sns.histplot(daily_returns, kde=False)
ymin, ymax = plt.ylim()

plt.vlines(x=mean, ymin=0, ymax=ymax, ls="--") #가운데에 수직선
plt.ylim(0, ymax)
plt.title("나스닥 지수의 일간수익률 분포")
plt.xlabel("일간수익률")
kde = False
kde = True

 

 

 

- 로그 정규 분포

  • 데이터에 로그를 한 값 또는 변화율이 정규 분포가 되는 분포
  • 주가의 수익률이 정규 분포라면 주가 자체는 로그 정규 분포(log-normal distribution)
  • 로그 정규 분포를 가지는 데이터는 기본적으로 항상 양수라서 로그 변환을 수행한 다음 사용하는 것이 일반적
    • 머신러닝이나 딥러닝에 사용하는 경우 로그 변환 후 사용하기도 함
np.random.seed(0)
mu = 1
rv = sp.stats.norm(loc=mu)
x1 = rv.rvs(1000)

#정규 분포 데이터를 이용한 로그 정규 분포 데이터 생성
#시작하는 부분에 데이터가 치우침
#타겟 데이터가 한쪽으로 몰려있는 경우 로그 변환 고려
s = 0.5
x2 = np.exp(s * x1)

fig, ax = plt.subplots(1, 2)
sns.histplot(x1, kde=False, ax=ax[0])
ax[0].set_title("정규분포")
sns.histplot(x2, kde=False, ax=ax[1])
ax[1].set_title("로그정규분포")
plt.tight_layout()
plt.show()

 

- Q-Q (Quntile-Quantile)  Plot

  • 정규분포는 연속확률분포 중 가장 널리 사용되는 확률분포 => 정규분포인지 확인하는 것이 중요
  • 분석할 표본 데이터의 분포와 정규 분포의 분포 형태를 비교해서, 표본 데이터가 정규 분포를 따르는지 검사하는 간단한 시각적 도구
  • 정규 분포를 따르는 데이터를 Q-Q plot으로 그리면 대각선 방향의 직선 모양으로 만들어짐
  • scipy.stats.probplot(x, plot=plt)
#평균이 0이고 표준편차가 1인 정규 분포 객체로 데이터 샘플링
rv = sp.stats.norm(0, 1)
x = rv.rvs(20)

#랜덤하게 데이터 추출
# x = np.random.rand(100)

sp.stats.probplot(x, plot=plt)
plt.show()
정규분포
랜덤추출

 

- 중심 극한 정리

  • 모집단이 정규 분포가 아니더라도 표본 크기가 충분하고 데이터가 정규성을 크게 이탈하지 않는다면 여러 표본에서 추출한 평균은 종 모양의 정규 곡선을 따른다.
  • N 개의 임의의 분포로부터 얻은 표본의 평균은  N 이 증가할수록 기댓값이  μ , 분산이  σ2/N 인 정규 분포로 수렴
#여러개의 표본에서 추출한 데이터를 합치면 정규 분포와 유사해짐
xx = np.linspace(-2, 2, 100)

for i, N in enumerate([1, 2, 10]):
    X = np.random.rand(5000, N)

    Xbar = (X.mean(axis=1) - 0.5) * np.sqrt(12 * N)
    sp.stats.probplot(Xbar, plot=plt)
  • enumerate 함수: iterable 객체를 받아서 순회하고 (인덱스, 데이터)로 리턴 - 몇번째 데이터인지 알 수 있음
enumerate([1, 2, 10])
enumerate([1, 2, 5, 10, 20])
  • 점점 가까워진다.

중심극한정리

 

 

4-7) 스튜던트 T분포

- 현실의 데이터는 정규 분포와 거의 유사하지만, 양 끝단의 데이터가 정규 분포에 비해 극단적 현상이 더 자주 발생

- 분포의 모양을 볼 때 양 끝이 정규 분포보다 두껍기 때문에 fat tail 현상이라 한다.

  • 금융 시장에서는 black swan이라 한다.
#S&P 500, 나스닥(Nasdaq), 다우존스(Dow-Jones), 니케이255(Nikkei255) 
symbols=[
    'S&P500',
    'IXIC',
    'DJI',
    'N225', 
    'KS11'
]

#주가 가져오기
data = pd.DataFrame()
for sym in symbols:
    data[sym] = fdr.DataReader(sym, '2011-01-01')["Close"]
data = data.dropna()
data
# 단위 축소시켜주기
(data / data.iloc[0] * 100).plot()

plt.ylabel("날짜")
plt.ylabel("주가 수익률")
plt.show()

 

#t 분포 - 수익률

#로그화
log_returns = np.log(data / data.shift(1)) #shift: 오른쪽으로 한칸 밀기
log_returns.hist(bins=50)
  • 정규분포와 다른 점은 Q-Q 플롯을 통해 확인

#Q-Q 플롯

#두ㅕ
for i, sym in enumerate(symbols):
    ax = plt.subplot(2, 3, i+1)
    sp.stats.probplot(log_returns[sym].dropna(), plot=ax)
plt.tight_layout()

 

 

- 확률 밀도 함수나 누적 분포 함수를 사용하기 위해서는 t 클래스 이용

  • 매개변수는 df, loc(기대값), scale(표준편차)
    • df는 자유도, ddof는 제약조건
    • 자유도는 통계적 추정을 할 때 표본 자료 중 모집단에 대한 정보를 주는 독립적인 자료의 수(추정해야 할 미지수)의 개수를 내가 가진 정보의 수에서 뺀 값
    • 표준편차나 분산을 구할 때 데이터의 개수가 아닌 n-1이 되는 이유는 표준 편차나 분산을 구하기 위해서는 평균이 전제가 되어야 하기 때문. 하지만 실제 모수의 평균은 미지수이므로 평균을 가정해야 한다. 그럼 이 가정은 제약조건이 된다.
    • 실제로는 데이터의 개수가 많아지면 자유도는 의미가 없음

자유도가 커질수록 정규 분포에 가깝다

 

- t 통계량

  • 정규 분포의 표본을 표준편차로 나눠 정규화한 z 통계량은 항상 정규 분포
  • z 통계량을 구하려면 표준편차를 알아야 하지만, 현실적으로 모수의 표준편차를 정확히 알 수 없기 때문에 표본에서 측정한 표본 표준편차를 이용해서 정규화 수행
  • 정규 분포로부터 얻은 N개의 표본에서 계산한 표본 평균을 표본 평균 편차로 정규화한 값이 t 통계량
  • 분포를 알 수 없는 샘플 데이터만 주어진 경우에 확률이나 값을 추정할 때 사용
    • 기온을 측정했는데 특정 기온이 확률적으로 몇%에 해당하는지 알고자 하는 경우
  • scipy.stats.t.분포함수(x=score, loc=mean, scale=std, df=df)
scailing - 값을 줄이는 것
표준화(standardiztion) - 열의 값을 어떤 특정 범위(0~1, -1~1)로 변경하는 것
정규화(normalization) - 행의 값을 어떤 특정 범위로 변경하는 것 -1로 맞추는 직업

원핫인코딩이 정규화
data = np.array([23, 30,18, 33, 28, 33, 34, 38, 29, 31])

#자유도
df = len(data)-1

#평균
d_mean = data.mean()

#표준 편차
d_std = data.std(ddof=1) #모집단의 표준편차가 아니라 샘플 데이터의 표준편차이므로 평균을 가정해야 함 - 1 설정
#섬씨 25도일 때 상위 몇 %에 속하는 온도일까?
score = 25

p = sp.stats.t.cdf(x=score,loc=d_mean,scale=d_std,df=df)
print(f"{score}는 상위 {(1-p)*100:.2f}%로 예측한다.")
p2 = sp.stats.t.sf(x=score,loc=d_mean,scale=d_std,df=df)
print(f"{score}는 상위 {p2*100:.2f}%로 예측한다.")
25는 상위 78.31%로 예측한다.
25는 상위 78.31%로 예측한다.

 

 

4-8) 카이 제곱 분포

- 정규 분포를 따르는 확률 변수 X의 N개 개의 표본 x1,⋯,xN 의 합(또는 평균) 표본 분산으로 정규화하면 스튜던트 t분포를 따른다. N개의 표본을 제곱해서 더하면 양수만을 갖는 분포 생성 - 카이제곱분포

- scipy.stats.chi2 

- 제곱 합을 구하는 표본의 수가 2보다 커지면 0 근처의 값이 가장 많이 발생할 것 같지만, 실제로는 0보다 큰 어떤 수가 흔하게 발생

 

4-9) F 분포

- 카이 제곱 분포를 따르는 독립적인 두개의 확률변수의 확률 변수 표본을 각각 x1, x2라고 할 때 이를 각각 자유도  N1, N2로 나눈 뒤 비율을 구하면 F분포

- t 분포의 표본 값을 제곱한 값이 F 분포

  • N1과 N2의 값이 같을 경우:  1 근처 값이 가장 많이 발생할 것 같지만 실제는 1이 아닌 다른 수가 조금 더 흔하게 발생
  • N1과 N2의 값이 커지면:  1 근처의 값이 많이 발생

- scipy.stats.f

 

 

------------------------------------------------------------------------------------------------여기까지 정규분포 통계량 분포

  • 스튜던트 t 분포: 추정된 가중치에 대한 확률분포
  • 카이제곱 분포: 오차 제곱 합에 대한 확률분포
  • F분포: 비교 대상이 되는 선형 모형의 오차 제곱 합에 대한 비율의 확률 분포

 

 

4-10) 푸아송 분포

- 단위 시간 안에 어떤 사건이 몇번 일어날 것인지를 표현하는 이산확률분포

- 푸아송이 민사사건과 형사사건 재판에서 확률에 관한 연구 및 일반적인 확률계산 법칙에 대한 서문에서 최초로 사용

- scipy.stats.poisson.rvs()

  • μ: 모양 매개 변수로 사용
  • 방정식의 λ(람다): 단위 시간당 발생하는 사건의 개수 설정
  • loc: 분포 이동
  • zmrl: 분포에서 임의의 변량 수 결정
  • random_state 인수 포함: 재현성 유지

- 예시

  • 어떤 식당에 주말 오후 동안 시간당 평균 20명의 손님이 방문한다고 할 때, 다음주 주말 오후에 30분 동안 5명의 손님이 방문할 확률은?
    • 시간당 손님은 20명이므로 30분당 기대값은 10명

 

# 단위 시간당 사건이 10개가 발생하는 경우

data_poisson = sp.stats.poisson.rvs(mu=10, size=1000)

sns.histplot(data_poisson, bins=30, kde=True)

 

# 2022년 기준으로 한 시간 평균 신생아 수는 682 명인 경우에 한 시간에 650명 이하의 신생아를 낳을 확률은?

# p = sp.stats.poisson.cdf(mu=682, k=650)

x=range(500,800)
y = sp.stats.poisson.pmf(mu=682,k=x)
plt.plot(x,y,alpha=0.7,label='pmf')

x2 = range(500,630)
y2 = sp.stats.poisson.pmf(mu=682,k=x2)
plt.fill_between(x2,y2,color='r',label='x<630') #630명 미만일 때 확률(면적)

#630명 미만
x3 = range(630,651)
y3 = sp.stats.poisson.pmf(mu=682,k=x3)
plt.fill_between(x3,y3,color='c',label='630<x<=651') #630명 이상 650명 이하일 때 확률(면적)
plt.xticks([500,630,650,682,800])
plt.title("")
plt.show()
# 0.11328673711427531

 

#시간당 500M 정도의 트래픽을 소모하는데 99% 정도를 처리하고자 할 때 필요한 트래픽은?

#help(sp.stats.poisson.ppf)
p = sp.stats.poisson.ppf(mu=500, q=0.99)
print(p)
630.0

 

 

4-11) 지수분포

- 푸아송 분포: 사건이 독립적일 때 일정 시간 동안 발생하는 사건의 횟수

- 지수 분포: 다음 사건이 일어날 때까지 대기 시간

- scipy.stats.expon

  • scale: 시간 설정
  • loc: 사건의 발생 횟수
  • 사건의 대기 시간이 일정한 형태로 줄어들거나 늘어나는 경우 weibull distribution 이용

# 스마트폰의 배터리 수명은 평균 24시간인 경우, 20시간 이내에 소진할 확률은?

#  sp.stats.expon.pdf(scale=24,x=x)

x = range(0,100)
y = sp.stats.expon.pdf(scale=24,x=x)
for i in x:
  print(f'{i:02d}시간 {y[i]:.2f}', end=' ')
  if i%10==9:
    print()
    
# 시각화
plt.plot(x,y)
plt.xlabel("time")
plt.ylabel("probability")
# 0.5654017914929218

 

 

 

 

 

 

 

 

 

 

 

 

 

'Python' 카테고리의 다른 글

[Python] 벡터 연산에서 기억할 부분  (0) 2024.02.27
[Python] 샘플링 _ 표본 추출  (1) 2024.02.26
[Python] 기술통계  (0) 2024.02.22
[Python] 이미지 데이터 다루기  (0) 2024.02.21
[Python] 확률  (0) 2024.02.21

[Python] 기술통계

0ㅑ채
|2024. 2. 22. 12:22

데이터: https://github.com/itggangpae/python_statistics

 

1. 통계

- 논리적 사고와 객관적인 사실에 따르며 일반적이고 확률적 결정론에 따라서 인과 관계를 규명

- 연구 목적에 의해 설정된 가설들에 대하여 분석 결과가 어떤 결과를 뒷받침하고 있는지를 통계적 방법으로 검정

- 분류

  • 기술통계: 수집된 데이터의 특성을 쉽게 파악하기 위해서 데이터를 표나 그래프 또는 대푯값으로 정리 요약하는 통계
  • 추론통계: 기술 통계량과 모집단에 추출한 정보를 이용해서 모집단의 특성을 과학적으로 추론

 

 

2. 변수(feature)의 종류

2-1) 질적변수와 양적변수

- 질적 변수: 구분을 위한 변수

  • 범주형
  • 명목척도, 순서척도

- 양적 변수: 양을 표현하는 변수

  • 대부분 연속형
  • 수치라고 표현해서는 안됨. 범주형의 데이터를 머신러닝에 사용하기 위해서 원핫인코딩을 하면 수치 데이터로 변경이 되는데 양적 변수는 아니기 때문이다.  
    ex. 성별을 0과 1로 표현
  • 등간척도, 비율척도

 

2-2) 척도(scale) 수준

- 명목(nominal) 척도: 남자, 여자처럼 순서에 아무런 의미가 없는 척도

 

- 순서(ordinal) 척도: 상중하, 셋 사이에 얼마가 될지는 몰라도 분명히 크기의 차이가 있다.

 

- 등간(interval) 척도: 구분할 수 있는 범위가 있다. 

 

- 비율(ratio) 척도: 절대 영도가 있는지에 따라 등간척도와 구분한다.

  • 온도는 등간척도다. 끝이 없고 명확한 절대점이 없기 때문이다.

 

2-3) 데이터 가져오기

descriptive.csv
0.01MB

 

- 부모의 학력 수준에 따르나녀의 대학 진학 합격 여부를 조사한 데이터

- 300개 행, 8개 열

  • uresident: 거주지역(1,2,3 – 특별시, 광역시, 시군)
  • ugender: 성별(1,2 – 남, 여)
  • uage: 나이
  • ulevel: 학력 수준(1,2,3 – 고졸, 대졸, 대학원졸 이상)
  • ucost: 생활비
  • utype: 학교 유형(1,2)
  • usurvey: 만족도(1-5)
  • upass: 합격여부(1,2)
print('------데이터 읽기 ------')
print(university.head())
print('------데이터 크기 ------')
print(university.shape)
print('------데이터 요약 ------')
print(university.describe())
------데이터 읽기 ------
   resident  gender  age  level  cost  type  survey  pass
0       1.0       1   50    1.0   5.1   1.0     1.0   2.0
1       2.0       1   54    2.0   4.2   1.0     2.0   2.0
2       NaN     1   62    2.0   4.7   1.0     1.0   1.0
3       4.0       2   50  NaN   3.5   1.0     4.0   1.0
4       5.0       1   51    1.0   5.0   1.0     3.0   1.0
------데이터 크기 ------
(300, 8)
------데이터 요약 ------
            resident   gender      age          level     cost      type          survey     pass
count   279.000  300.000  300.000  287.000  271.000  274.000  187.000  279.000
mean      2.233    1.420   53.880    1.836    8.723       1.281         2.594    1.434
std       1.484    0.546      6.813        0.792   68.971    0.474         0.976    0.496
min       1.000    0.000   40.000      1.000 -457.200(?)  1.000       1.000    1.000
25%       1.000    1.000   48.000    1.000    4.400       1.000          2.000     1.000
50%       2.000    1.000   53.000    2.000    5.400       1.000           3.000     1.000
75%       3.000    2.000   60.000    2.000    6.300       2.000           3.000     2.000
max       5.000    5.000   69.000    3.000  675.000(?)  4.000         5.000    2.000

 

 

2-4) 명목 척도

- 구별만을 위해서 의미 없는 수치로 구성한 데이터

- 거주지역, 성별 등

- 요약 통계량 의미 없음

- 데이터 개수 정도만 확인 - 이상치 확인 가능

print(university['gender'].value_counts())
gender
1    173
2    124
0      2
5      1
Name: count, dtype: int64
  • 0과 5는 이상한 데이터. 지우자.
  • 방법은 두가지. drop을 사용해서 직접 제거하거나 올바른 데이터만 필터링 하거나.
#데이터 정제
university_gender = university[(university['gender'] == 1) | (university['gender'] ==2)]
print(university_gender['gender'].value_counts())
gender
1    173
2    124
Name: count, dtype: int64

 

#범주형 데이터 시각화
university_gender['gender'].value_counts().plot.bar(color='k', alpha=0.7)

 

 

2-5) 순서 척도

- 순서를 정하기 위해 만든 수치데이터

- 계급순위를 수치로 표현한 직급, 학력 수준 등

- 기초 통계량 중에서 빈도 수 정도만 의미

print(university_gender['level'].value_counts())
level
1.0    115
2.0     99
3.0     70
Name: count, dtype: int64

 

 

2-6) 등간 척도

- 속성의 간격이 일정한 값을 갖는 척도

- 만족도처럼 각 데이터끼리 비교가 가능하고 가중치 적용 등이 가능

- 기술통계량은 의미를 갖지만 산술 연산은 하지 않음

  • 평균을 구하기 위해서 합계를 구하기는 하지만 합계 자체는 의미가 없음

- 절대 원점없음

#등간 척도 데이터 시각화
university_gender['survey'].value_counts().plot.pie()
plt.show()

 

 

2-7) 비율 척도

- 등간 척도의 특성에 절대 원점이 존재

- 특정 값을 기준으로 한 수치 데이터라서 사칙 연산이 의미를 갖는 척도

  • 빈도를 직접 구하지는 않는 경우가 많다. 대부분 일정한 범위로 편집을 해서 빈도를 구한다.
  • 직접 입력받는 경우가 많아서 이상치 탐지를 수행해야 한다. 

- 점수, 나이, 무게, cost 등

 

#cost 값이 2~10인 데이터만 추출

cost = university_gender[(university_gender['cost']>=2) & (university_gender['cost']<=10)]
cost['cost'].describe()
count    248.000
mean       5.354
std        1.139
min        2.100
25%        4.600
50%        5.400
75%        6.200
max        7.900
Name: cost, dtype: float64

 

#범주화

cost = university_gender['cost']
cost = cost[(cost>=2) & (cost<=10)]

#범주화 2~3: 1    3~6: 2     6~: 3
cost[(cost>=2)&(cost<=3)] = 1
cost[(cost>3)&(cost<=6)] = 2
cost[(cost>6)] = 3

#정수로 변환
cost = cost.astype(int)

print(cost.value_counts())
cost
2    157
3     82
1      9
Name: count, dtype: int64

 

# 시각화

label = ["하", "중", "상"]

plt.pie(cost.value_counts(), labels=label, autopct='%1.1f%%')
plt.title('생활비 분포')
plt.legend()
plt.show()

 

 

2-8) 이산형 데이터와 연속형 데이터

- 이산형 데이터: 하나하나의 값을 취하고 서로 인접한 숫자 사이에 값이 존재하지 않는 변수

  • ex. 주사위의 눈

- 연속형 데이터: 연속적인 값을 취할 수 있는 데이터. 어떤 두 숫자 사이에 반드시 숫자가 존재하는 형태로 길이나 무게 등

  • 연속형 데이터라고 하더라도 측정 정밀도에 한계가 있어서 실제로는 띄엄띄엄 값을 취할 수밖에 없음
  • 키를 소수점 한자리까지의 정밀도로 측정하면 170.3과 170.4 사이에 다른숫자가 없는 것으로 간주
  • 이런 경우 이산형 데이터지만 연속형 데이터로 취급

 

 

 

3. 대표값

- 평균이나 분산 등의 수치 데이터를 요약해서 파악

- 그래프를 그려서 시각적으로 데이터 파악

 

- 대표값: 데이터를 하나의 값으로 요약한 지표

3-1) 평균 (mean)

- 모든 값의 총합을 데이터 개수로 나눈 값 - 산술 평균

 

- 기하 평균: 비율의 평균을 구할 때 사용. 각 데이터의 비율을 곱한 후 제곱근 구하는 것. 

  • 매출액이 100인 회사에서 다음 해의 매출이 110을 기록하고 그 다음해 매출이 107.8을 기록했을 때 연평균 성장률은?
  • 산술평균으로 구하면 첫 해에 10%, 다음 해에 2% 증가했으니 8 / 2 = 평균 4%가 된다.
  • 1.1 * 0.98의 제곱근으로 구해야 함!
from pandas import Series, DataFrame
import pandas as pd
import numpy as np
import math

s = Series([10, 11, 10.78])
print("평균 성장률이라고 생각하는 답:", s.pct_change().mean())
print("평균 성장률(기하평균):", math.sqrt((11/10)*(10.78/11)))
평균 성장률(산술평균): 0.040000000000000036 
평균 성장률(기하평균): 1.0382677881933928

 

- 조화 평균: 속도의 평균을 구할 때 사용.

  • 데이터 전부 곱함 x 2 / 데이터의 합
  • 동일한 거리를 한 번은 시속 100km로 달리고 한 번은 60km로 달렸을 때 평균 속도는?
  • 300km를 달렸다고 치면, 3시간 + 5시간 총 8시간이 나와야 한다.
print("평균 속도라고 생각하는 답" , (600 / 80))
print("평균 속도(조화평균):", (600 / ((2*100*60) / (100+60))))
평균 속도라고 생각하는 답: 7.5
평균 속도(조화평균): 8.0

 

- 가중평균(Weighted Mean)

  • 가중치를 곱한 값의 총합을 가중치의 총합으로 나눈 값
  • 데이터를 수집할 때 모든 사용자 그룹에 대해서 정확히 같은 비율의 데이터를 수집하기는 어렵기 때문에 가중치 부여해서 평균 구함
  • 여론 조사에서 많이 사용
  • 어느 정당을 지지하는지 알고 싶어서 샘플링을 수행해야 하는데 샘플링을 실제 투표권자의 비율대로 수집하는 것은 거의 불가능
  • 샘플링한 후 샘플링된 데이터에 투표권자의 비율을 가중치로 곱해서 가중치의 합으로 나누는 방식 채택
  • numpy.average 함수 이용해서 weights에 가중치 설정

- 이상치 (Outlier)

  • 대부분의 값과 매우 다른데이터 값(극단값)

- Robust

  • 극단값이나 이상치에 민감하지 않은 것
  • resistant, 저항성이 있다고도 함

- Trimmed Mean(절사 평균)

  • 정해진 개수의 극단 값을 제외한 나머지 값들의 평균
  • 이상치를 제거하고 구한 평균은 실제로는 절사 평균

- 이동평균 (moving average)

  • 데이터가 방향성을 가지고 움직일 때 (시계열 데이터가 대표적) 이동하면서 구해지는 평균
  • 단순이동평균(SMA - Simple Moving Average)
    • n번째 데이터를 포함한 왼쪽 m개(window) 데이터의 평균
    • 실제 함수는 매번 단순 이동 평균을 직접 계산하지 않고 (이전의 이동 평균) + 새로운 데이터 - 가장 오래된 데이터 를 계산하고 윈도우 크기로 나눠서 계산
  • 누적이동평균(CMA - Cumulative Moving Average)
    • 단순 이동 평균과 계산법은 같은데 윈도우 크기를 설정하지 않고 새로운 값이 들어올 때마다 전체 평균을 다시 계산
  • 선형가중이동평균(WMA - Weighted Moving Average)
    • 단순이동평균처럼 윈도우 개수(m)을 설정해서 구하는데 가중치가 선형으로 변경
    • dot product 연산 수행: ([m, m-1, m-2, ..., 1] * [n번째 데이터, n-1번째 데이터, ... , n-m+1번째 데이터])
      - 행렬의 곱 (열이 하나면 행도 하나, 길이가 같아야 한다.)
    • 오래된 데이터일수록 가중치가 선형적으로 감소한다!
    • 최신의 데이터에 큰 가중치를 제공해서 최신의 데이터가 결과에 더 큰 영향력을 발휘하게 함
    • 목적에 맞추어 조금 더 유연하게 비선형적인 방식으로 곱해나가기도 함. 

  • 지수가중이동평균(EWMA - Exponentially Weighted Moving Average)
    • span(알파값): 가중치 (0~1사이의 값)
    • 첫번째 지수가중이동평균: 1번째 데이터
    • n번째 지수가중이동평균: (1 - 알파) * (n-1번째 지수가중이동평균) + 알파 * 현재 데이터 값
    • 알파 값은 일반적으로 2/(windows 개수 +1)로 설정

 

3-2) 중앙값(median)

  • 데이터를 크기 순서대로 나열할 때 정확하게 중앙에 위치한 값
  • 중앙값은 평균에 비해서 이상치에 강하다는 특성이 있음
    • 데이터가 1, 2, 3, 4, 5, 6, 1000 이 있을 경우
      평균: 145.85
      중앙값: 4
    • 위의 데이터를 더 잘 설명한 값은 중앙값
  • 데이터가 짝수개라면 중앙값은 중앙에 위치한 2개 데이터의 평균
### 중앙값
tdata = pd.read_csv('./python_statistics-main/data/tdata.csv', encoding='cp949')
tdata.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10 entries, 0 to 9
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   번호      10 non-null     int64
 1   성적      10 non-null     int64
dtypes: int64(2)
memory usage: 292.0 bytes
from scipy import stats

print('평균:', tdata['성적'].mean())
print('중간값:', tdata['성적'].median())
#상위와 하위 10% 잘라내고 평균 구하기
print('절사평균:', stats.trim_mean(tdata['성적'], 0.1))
평균: 77.1
중간값: 77.5
절사평균: 77.0
  • 3개의 값이 별 차이가 없으니 크게 이상한 데이터가 없는 것
state = pd.read_csv('./python_statistics-main/data/state.csv')
state.head()


Population 데이터의 편차가 큼
print('단순 평균:', state['Population'].mean())
print('중앙값:',  state['Population'].median())
print('절사평균:', stats.trim_mean(state['Population'], 0.1))
단순 평균: 6162876.3
중앙값: 4436369.5
절사평균: 4783697.125
  • 단순 평균과 중앙값이 차이가 많이 난다. 
  • 중앙값은 실제 데이터다보니 절사평균 사용을 더 선호

- 가중 중앙값

  • state데이터에는 Murder.Rate가 살인 사건 발생 비율인데 살인 사건의 평균 비율을 알고자 할 때 단순하게 Murder.Rate의 평균을 구하는 것은 바람직하지 않다. 인구가 많은 곳에 가중치를 높게 설정하고, 인구가 작은 지역은 가중치를 적게 설정해서 평균을 구해야 한다.
!pip install wquantiles

import wquantiles
#가중 평균을 구하기 위해서는 numpy 의 average 함수를 이용
print(state['Murder.Rate'].mean())
print(np.average(state['Murder.Rate'], weights=state['Population']))

#가중 중앙값은 wquantiles 패키지의 median을 이용 - 설치 해야 함
print(wquantiles.median(state['Murder.Rate'], weights=state['Population']))
4.066  #평균
4.445833981123393  #가중평균
4.4  #가중 중앙값

 

 

3-3) 최빈값(mode)

- 데이터베이스 가장 많이 나타나는 값

- Series나 DataFrame에서 mode 함수를 호출하여 구해준다.

- 질적 데이터의 대표값을 구할 때 이용

print(state['Murder.Rate'].mode())
print(university['age'].mode())
0    1.6
1    2.0
2    5.7
Name: Murder.Rate, dtype: float64

0    48
Name: age, dtype: int64

 

 

 

3-4) 변이 추정

- 개요

  • 데이터 값이 얼마나 밀집해 있는지 혹은 퍼져있는지를 나타내는 것으로 분산(dispersion)이라고도 함
  • 변이를 측정해서 실제 변이와 랜덤을 구분하고 실제 변이의 다양한 요인들을 알아보고 변이가 있는 상황에서 결정을 내리는 등의 작업을 위해 추정

- 지표

  • 편차(Deviation)
    • 관츨된 데이터와 위치 추정을 위한 값(평균) 사이의 차이로 오차 또는 잔차(residual)
    • 편차는 음과 양의 부호로 존재해서 평균을 구하려 하면  서로 상쇄하여 0에 가까운 값이 되어버림
  • 분산(Variance)
    • 평균과의 편차의 제곱한 값들의 합을 n이나 n-1로 나눈 값인데 평균제곱오차라고도 함
    • 제곱을 해서 더한 값이기 때문에 원본 데이터와 스케일이 다를 가능성이 높아 바로 사용하기는 어렵다. 
  • 표준편차(Standard Deviation)
    • 분산의 제곱근, L2 norm 또는 유클리드 norm
  • 평균절대오차(Mean Absolute Deviation)
    • 평균에 대한 편차의 절대값의 평균으로 L1 norm 또는 맨허튼 norm
  • 중앙값의 중위절대편차(Median Absolute Deviation from the Median)
    • 중앙값과의 편차의 절대값의 중앙값
  •  
  • 범위(range)
    • 최대값과 최소값의 차이
    • 데이터를 scailing 할 때 0~1 사이로 만드는 방법은 (데이터-최소값) / (최대값-최소값)
  • 순서 통계량(Order Statistics)
    • 최소에서 최대까지 정렬된 데이터 값에 따른 계량형 순서
  • 백분위수(Percentile)
    • 분위 수, P 퍼센트 값.
  • 사분위 범위(Interquartile Range)
    • 75번째 백분위 수와 25번째 백분위수 사이의 차이로 IQR 이라고도 함
  • 자유도(degrees of freedom , df)
    • 과학 분야에서는 독립적으로 달라질 수 있는 시스템의 매개변수 개수
    • 평면에서의 한 점은 평행 이동을 할 때 X와 Y좌표 2개를 자유롭게 가질 수 있다. (자유도=2)
    • 통계학에서는 통계적 추정을 할 때 표본 자료 중 모집단에 대한 정보를 주는 독립적인 자료의 수
    • 표준편차나 분산은 표본의 평균에 따른다는 제약조건을 가지기 때문에 1개의 데이터는 독립적이지 못해서
      n-1로 자유도를 설정
    • ANOVA(분산분석)처럼 다수의 집단을 고려할 때는 자유도가 n - 집단의개수

- 분산, 표준편차, 평균절대편차 등은 특이값과 극단값에 로버스트 하지 않기 때문에 중앙값(중위절대편차) 이용

  •  MAD = median( |데이터 - 그룹의중앙값| )
  • MAD를 구해주는 함수는 statsmodel.robust.scale.mad 함수
state = pd.read_csv('./python_statistics-main/data/state.csv')

#표준 편차
print(state['Population'].std())

#IQR - 사분위 범위
print(state['Population'].quantile(0.75) - state['Population'].quantile(0.25))

#중위 절대 편차(MAD)
from statsmodels import robust
print(robust.scale.mad(state['Population']))
print(abs(state['Population'] - state['Population'].median()).median() / 0.6744897501960817)
6848235.347401142

4847308.0

3849876.1459979336
  • IQR은 이상치 검출할 때도 이용
    IQR * 1.5를 한 값에 구한 후 25% 숫자에서 뺀 값보다 작은 값이나 75% 숫자에 더한 값보다 큰 값을 이상치로 판정
    but 데이터 개수가 12개보다 적으면 검출 불가
    이런 경우 중위값을 가지고 보정해서 구하기도 함

 

 

 

4. 데이터의 분포 탐색

분포 탐색을 위한 시각화

- Box Plot(상자 그림)

  • 사각형과 수염을 이용해서 중앙값과 4분위수 그리고 극단치 등을 표시해주는 그림
  • 극단치가 있는 경우 (1) 데이터를 스케일링(단위 조정)해서 극단치의 영향력을 감소시키거나 (2) 극단치를 제거하고 사용하기도 하고 (3) 극단치를 이용해서 별도의 변수를 생성해서 데이터분석에 활용하기도 함
    • 비의 양을 데이터화 시킬 때 우리나라의 경우 하루에 보통 0 ~ 100mm 정도 온다고 가정했을 때 비가 1000mm 온 날이 있다면 비가 500mm이상 온 변수를 생성해서 True와 False로 설정
  • 상자 그림은 중앙값과 4분위수, 극단치를 파악하는 데는 좋지만 실제 데이터의 분포를 알아보는 것은 불가
  • 데이터의 분포를 확인하고자 하는 경우 scatter(산포도)나 바이올린 차트, swart 차트 이용

- 도수 분포표(Frequency Table)

  • 어떤 구간에 해당하는 수치 데이터 값들의 빈도를 나타내는 기록

- 히스토그램

  • 도수 분포표의 내용을 그래프로 표현

- 밀도 그림(Density Plot)

  • 히스토그램을 부드러운 곡선으로 나타낸 그림으로 KDE(Kernel Density Estimation - 커널 밀도 추정)를 주로 이용
  • 밀도 그림을 그려서 어떤 확률 분포를 따르는지 파악

# 상자 그림

ax = (state['Population']/1_000_000).plot.box(figsize=(3, 4)) #천단위 구분기호
#현재는 ax = (state['Population']).plot.box(figsize=(3, 4))라고 자동으로 추정

ax.set_ylabel('Population (millions)')
plt.tight_layout()
plt.show()

# 도수 분포표

  • 인구 수를 10개의 구간으로 분할
binnedPopulation = pd.cut(state['Population'], bins=10)
binnedPopulation.value_counts()
Population
(526935.67, 4232659.0]      24
(4232659.0, 7901692.0]      14
(7901692.0, 11570725.0]      6
(11570725.0, 15239758.0]     2
(15239758.0, 18908791.0]     1
(18908791.0, 22577824.0]     1
(22577824.0, 26246857.0]     1
(33584923.0, 37253956.0]     1
(26246857.0, 29915890.0]     0
(29915890.0, 33584923.0]     0
Name: count, dtype: int64

 

#히스토그램

ax = (state['Population']).plot.hist(figsize=(4, 4))
  • 밀도 추정 - 데이터의 분포를 부드러운 곡선으로 표현
ax = (state['Population']).plot.hist(density=True, xlim=[0, 12], 
                                    bins=range(1, 12), figsize=(4, 4))
state['Murder.Rate'].plot.density(ax=ax)

 

 

 

 

5. 다변량 탐색

5-1) 개요

-  일변량 분석 :평균이나 분산같은 추정값들은 한번에 하나의 변수를 다루는 것

- 이변량 분석(bivariate analysis) : 두개의 변수 간 관계를 파악하는 것

- 다변량 분석(multivariate analysis) : 두개 이상의 변수의 관계를 파악하는 것

 

- 다변량 분석에서 많이 사용하는 시각화

  • 분할표(Contingency Table): 두가지 이상의 범주형 빈도수를 기록한 표
  • 육각형 구간: 두 변수를 육각형 모양의 구간으로 나눈 그림
  • 등고 도표
  • 바이올린 도표

 

5-2) 교차 분석

- 범주형 자료를 대상으로 2개 이상의 변수들에 대한 관련성을 알아보기 위해서 결합 분포를 나타내는 교차분할표 작성

   => 상호 간의 관련성 여부 분석

- 교차 분석에서 사용되는 변수는 값을 10가지 미만으로 갖는 것이 좋다. 

- 교차 분석을 할 때는 범주형 데이터가 수치로 만들어져 있는 경우 다시 원래 의미로 변환해서 사용하는 것이 좋다.

  • 원본을 바꾸기보다 컬럼을 추가하는게 더 좋음. 수치로 변환했으면 이유가 있을테니까!
university = pd.read_csv('./python_statistics-main/data/descriptive.csv')

#gender 1:남자, 2: 여자
#성별 필드를 추가해서 남자와 여자로 기록
university['성별'] = '남자'

idx = 0
for val in university['gender']:
    if val == 2:
        university['성별'][idx] = '여자'
    idx = idx + 1
print(university['성별'].value_counts())
성별
남자    176
여자    124
Name: count, dtype: int64
#level 1: 고졸, 2:대졸, 3:대학원졸 이상, 나머지는 응답 없음
university['학력'] = '응답없음'

idx = 0
for val in university['level']:
    if val == 1.0:
        university['학력'][idx] = '고졸'
    elif val == 2.0:
        university['학력'][idx] = '대졸'
    else:
        university['학력'][idx] = '대학원졸'
        
    idx = idx + 1
#교차 분할표 생성은 pandas의 crosstab 함수 활용
print(pd.crosstab(university['학력'], university['성별']))
성별    남자  여자
학력          
고졸    67  50
대졸    60  40
대학원졸  49  34

 

 

5-3) 공분산(covariance)

- 2 종류의 데이터를 가지고 결합 분포의 평균을 중심으로 각 자료들이 어떻게 분포되어 있는지를 보여주는 수치

- 공분산이 분산과 다른점은 가로축과 세로축의 데이터가 다르기 때문에 편차들로 만든 도형이 직사각형이 되고 음의 면적도 만들 수 있음

  • (x1 - xM)(y1-yM) + (x2 - xM)(y2-yM) ... + (xn - xM)(yn-yM) / 데이터개수(편향)로 나눈 값 
    • 일반적으로 데이터개수보다   N - 1 - 절편향 
  • 키와 체중의 공분산을 구하는 경우
    • (첫번째 키 - 키의 평균)(첫번째 체중 - 체중의 평균)
      + (두번째 키 - 키의 평균)(두번째 체중 - 체중의 평균) 
      + (n번째 키 - 키의 평균)(n번째 체중 - 체중의 평균)
    • 이렇게 구해진 값을 데이터 개수로 나누면 됨
  • 공분산 > 0 : 변수 한쪽이 큰 값을 갖게 되는 경우 다른 한쪽도 커진다.
  • 공분산 < 0 : 변수 한쪽이 큰 값을 갖게 되는 경우 다른 한쪽은 작아진다.
  • 공분산 = 0 : 두개의 변수 사이에는 연관성이 없다.

- numpy.cov 함수: 공분산 값을 구함

  • 분산- 공분산 행렬 리턴

- ddof 옵션: 제약조건 개수 설정

- 자유도: 데이터의 수 - 제약조건의 수 

#공분산 직접 계산
cov_data = pd.read_csv('./python_statistics-main/data/cov.csv')
#공분산을 구하기 위해서는 평균을 알아야 하고 데이터 개수도 알아야 함
N = len(cov_data)
print(N) # 10
#x와 y의 평균
x = cov_data['x']
y = cov_data['y']

mu_x = np.mean(x)
mu_y = np.mean(y)

print(mu_x, mu_y)
21.020000000000003 
42.7
#자유도를 N-1로 해서 공분산 구하기
cov = sum((x - mu_x) * (y - mu_y)) / (N-1)
print("공분산:", cov)
공분산: 7.673333333333336
#자유도를 N으로 설정해서 분산-공분산 행렬 구하기
np.cov(x, y, ddof = 0)
array([[ 3.282,  6.906],
       [ 6.906, 25.21 ]])
#자유도를 N-1으로 설정해서 분산-공분산 행렬 구하기
np.cov(x, y, ddof = 1)
array([[ 3.646,  7.673],
       [ 7.673, 28.011]])

 

 

 

5-4) 상관계수

- 공분산은 단위나 자료의 범위에 따라 값의 차이가 크게 발생하기 때문에 여러 컬럼들 사이의 관련성을 확인하긴 어렵다.

- 2개 변수의 관계를 파악할 때 방향성만 분리해서 보는 것이 유용하기 때문에 새로운 지표를 생성

  • 공분산으로 연산을 수행해서 -1 ~ 1 사이의 값으로 변경한 것이 상관계수

- 수식

  • 상관계수 = 공분산 / (각 열의 표준 편차를 곱한 값)

- 해석

  • 부호 자체는 공분산과 동일하게 분석
  • 절대값 0.9 이상이면 매우 높은 상관관계
  • 절대값 0.7 이상이면 높은 상관관계
  • 절대값 0.4 이상이면 상관관계가 다소 높다.
  • 그 이외에는 상관관계가 약하거나 없다고 판정

 

상관계수나 공분산만으로 데이터를 파악할 수 없고 실제 분포도 확인해야 함!

상관 계수를 구하기 전에 산점도 등을 통해서 상관 계수를 구하는 것이 의미가 있는지 확인

  • matplotlib.pyplot 의 scatter 함수 이용
  • pandas 의 plot 함수를 호출, kind = scatter를 설정
  • seaborn 의 fairplot 함수를 이용할 수 있는데 이 함수는 DataFrame을 이용하면 모든 숫자 컬럼의 산점도를 모두 출력
  • eaborn에서는 regplot(산점도 와 회귀식) 이나 jointplot(산점도 와 히스토그램) 같은 두 개의 컬럼 만으로 산점도 와 히스토그램을 같이 그릴 수 도 있습니다.

- 상관계수의 종류

  • 피어슨 상관계수
  • 스피어만 상관계수
  • 켄달 상관계수

 

- 피어슨 상관계수

  • 일반적인 상관계수
  • 특잇값에 영향을 많이 받음
  • 선형 관계만 파악 가능
  •  pandas :  Dataframe에 corr() 함수 이용
  • scipy : scipy.stats 패키지에서도 pearsonr 함수 이용 : 피어슨 상관계수와 유의확률(p-value) 리턴

 

# 상관계수: 공분산 / (x의 표준편차 * y의 표준편차)

# -1 ~ 1사이의 숫자로 표준화

#auto-mpg 데이터 읽어오기
mpg = pd.read_csv('./data/auto-mpg.csv', header=None)
mpg.columns = ['mpg','cylinders','displacement','horsepower','weight',
              'acceleration','model year','origin','name']
#데이터프레임에 존재하는 모든 숫자 컬럼들의 산점도 전부 출력
import seaborn as sns
sns.pairplot(mpg)

 

mpg[['mpg', 'cylinders', 'displacement', 'weight']].corr()

 

#산점도 그래프

mpg.plot(kind='scatter', x='weight', y='mpg', c='coral', s=10)

 

#회귀선 출력

sns.regplot(x='weight', y='mpg', data=mpg)
sns.jointplot(x='weight', y='mpg', kind='reg', data=mpg)

 

csv나 txt 파일 형식으로 많은 양의 데이터를 저장해야 할 때 가장 좋은 방법은 적절한 크기로 분할해서 저장하는 것. 그런데 일반적으로 로그 파일을 만들 때는 1일 단위로 작성하는 경우가 많아서 어쩔 수 없이 파일의 크기가 커지는 경우가 있다. 이런 경우 gz로 압축해서 제공하면 pandas가 속도는 느려지지만 읽을 수 있다.
machine learning 패키지들은 디렉토리 단위로 데이터를 읽는 것이 가능

sp500_sym = pd.read_csv('./python_statistics-main/data/sp500_sectors.csv')
sp500_px = pd.read_csv('./python_statistics-main/data/sp500_data.csv.gz', index_col=0)

 

- 데이터의 상관관계를 상관계수로만 판단하면 안됨

  • 상관계수로 분포의 형상을 추측할 때 개별 자료가 상관계수에 미치는 영향력이 서로 다름
  • 피어슨 상관계수는 이상치나 극단치에 영향을 크게 받는 경우가 발생
  • 앤스콤 데이터
    • 4그룹의 데이터를 가지고 있는데 각 데이터 그룹의 상관계수가 0.816으로 동일
    • 첫번째 그룹은 일반적인 데이터 그룹, 두번째 그룹은 완전한 비선형 상관관계를 가진 데이터셋인데 피어슨 상관계수가 비선형 관계는 정확히 반영하지 못하기 때문에 0.816의 값을 가지고
    • 세번째 그룹과 네번째 그룹은 이상한 데이터가 1개가 영향력을 크게 미쳐서 상관계수가 0.816dl이 됨

 

# 앤스콤 데이터

import statsmodels.api as sm
data = sm.datasets.get_rdataset('anscombe')
df = data.data
#피어슨 상관계수 확인
print(df['x1'].corr(df['y1']))
print(df['x2'].corr(df['y2']))
print(df['x3'].corr(df['y3']))
print(df['x4'].corr(df['y4']))
0.81642051634484
0.8162365060002428
0.8162867394895984
0.8165214368885028
plt.subplot(221)
sns.regplot(x='x1', y='y1', data=df)
plt.subplot(222)
sns.regplot(x='x2', y='y2', data=df)
plt.subplot(223)
sns.regplot(x='x3', y='y3', data=df)
plt.subplot(224)
sns.regplot(x='x4', y='y4', data=df)
  • 첫번째는 일반적인 데이터. 납득 가능
  • 두번째는 비선형이다. 그런데 상관계수는 같다. 
  • 세번째, 네번째는 상관관계가 없는데 이상치 하나 때문에 영향을 받는다.

 

- 스피어만 상관계수

- 순위 기반으로 상관계수를 만들어내는 방식

- 값을 가지고 하지 않기 때문에 비선형 관계도 파악 가능

- 순위를 기반으로 하기 때문에 연속형 데이터가 아니어도 적용 가능

-  pandas :   corr()의 method 옵션 = spearman 설정

-  scipy : scipy.stats.spearmanr(데이터 1개, 데이터 1개, axis=0)

 

- 스피어만 상관계수 확인

s1 = pd.Series([1, 2, 3, 4, 5])
s2 = pd.Series([1, 4, 9, 16, 25])

sns.regplot(x=s1, y=s2)

#피어슨 상관 계수
print("피어슨 상관 계수:", s1.corr(s2))

#스피어만 상관 계수
print("스피어만 상관 계수:", s1.corr(s2, method='spearman'))
import scipy as sp

# 앤스콤 데이터의 스피어만 상관 계수
print("1의 스피어만 상관 계수: ", sp.stats.spearmanr(df['x1'], df['y1']))
print("2의 스피어만 상관 계수: ", sp.stats.spearmanr(df['x2'], df['y2']))
print("3의 스피어만 상관 계수: ", sp.stats.spearmanr(df['x3'], df['y3']))
print("4의 스피어만 상관 계수: ", sp.stats.spearmanr(df['x4'], df['y4']))
1의 스피어만 상관 계수:  SignificanceResult(statistic=0.8181818181818182, pvalue=0.0020831448404786904)
2의 스피어만 상관 계수:  SignificanceResult(statistic=0.690909090909091, pvalue=0.018565033381595004)
3의 스피어만 상관 계수:  SignificanceResult(statistic=0.990909090909091, pvalue=3.762571807085399e-09)
4의 스피어만 상관 계수:  SignificanceResult(statistic=0.5, pvalue=0.11730680301423815)

 

 

- 켄달의 상관계수

- 스피어만 상관계수와 비슷한데 순위를 기반으로 하는게 아니라 값의 증감만 확인

- pandas : method=kendall

- scipy : scipy.stats.kendalltau 호출

print("켄달 상관 계수:", s1.corr(s2, method='kendall'))

print("1의 켄달 상관 계수: ", sp.stats.kendalltau(df['x1'], df['y1']))
print("2의 켄달 상관 계수: ", sp.stats.kendalltau(df['x2'], df['y2']))
print("3의 켄달 상관 계수: ", sp.stats.kendalltau(df['x3'], df['y3']))
print("4의 켄달 상관 계수: ", sp.stats.kendalltau(df['x4'], df['y4']))
켄달 상관 계수: 0.9999999999999999

1의 켄달 상관 계수:  SignificanceResult(statistic=0.6363636363636364, pvalue=0.005707170915504249)
2의 켄달 상관 계수:  SignificanceResult(statistic=0.5636363636363636, pvalue=0.016540504248837583)
3의 켄달 상관 계수:  SignificanceResult(statistic=0.9636363636363636, pvalue=5.511463844797178e-07)
4의 켄달 상관 계수:  SignificanceResult(statistic=0.42640143271122083, pvalue=0.11384629800665805)
  • 스피어만 상관계수와 거의 유사하다. 

 

 

6. 분포 시각화

6-1) 수치형 데이터와 수치형 데이터의 분포 시각화

- 산점도

  • 데이터의 개수가 상대적으로 적을 때는 무난하지만 
  • 수십, 수백만의 레코드를 나타내는 경우에는 점들이 너무 밀집되어 있어서 알아보기 어려움
tips = sns.load_dataset('tips')
sns.jointplot(x='total_bill', y='tip', data=tips, kind='scatter')

 

- 육각형 구간 차트

  • 데이터를 점으로 표현하는 대신 기록값들을 육각형 모양의 구간들로 나누고 각 구간에 포함된 기록 값의 개수에 따라 색상을 표시
sns.jointplot(x='total_bill', y='tip', data=tips, kind='hex')

 

- 등고선 차트

  • 두 변수로 이루어진 지형에서의 등고선
  • matplotlib.pyplot : contoutf 함수 이용
  • seaborn : kdeplot 이용
  • 데이터가 많을 때 사용
kc_tax = pd.read_csv("./python_statistics-main/data/kc_tax.csv.gz")
print(kc_tax.shape)
(498249, 3)
#필터링
kc_tax0 = kc_tax.loc[(kc_tax.TaxAssessedValue < 750000) & 
                     (kc_tax.SqFtTotLiving > 100) &
                     (kc_tax.SqFtTotLiving < 3500), :]
print(kc_tax0.shape)
(432693, 3)
sns.kdeplot(data=kc_tax0, x="SqFtTotLiving", y="TaxAssessedValue")
plt.show()

 

 

6-2) 범주형 데이터와 범주형 데이터의 시각화

- 분할표 이용

  • 각 범주나 범주의 list를 인덱스와 컬럼에 배치해서 사용
  • crosstab이나 pivot_table 함수를 이용
  • pivot_table을 많이 이용하는데 이유는 피봇 테이블은 aggfunc라는 매개변수에 사용하고자 하는 함수를 적용할 수 있기 때문
    • pivot_table의 매개변수는 index에 컬럼이나 컬럼 list를 설정하고 columns에 동일하게 컬럼이나 컬럼의 list를 설정하고 aggfunc에 집계에 사용할 함수 설정
    • 컬럼들이 전부 범주형(명목척도와 순서척도)라면 데이터 개수를 세는 것 말고는 의미가 없음
  • lc_loans.csv 파일을 읽어서 grade와 status의 교차분할표 작성
lc_loans = pd.read_csv('./python_statistics-main/data/lc_loans.csv')
lc_loans.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 450961 entries, 0 to 450960
Data columns (total 2 columns):
 #   Column  Non-Null Count   Dtype 
---  ------  --------------   ----- 
 0   status  450961 non-null  object
 1   grade   450961 non-null  object
dtypes: object(2)
memory usage: 6.9+ MB
result = lc_loans.pivot_table(index='grade', columns='status', aggfunc=lambda x:len(x), margins=True)

 

# 비율 출력

# 비율 출력 - grade의 합계를 제외한 부분 가져오기
df = result.copy().loc['A':'G', :]
df.loc[:, "Charged Off":"Late"] = df.loc[:, "Charged Off":"Late"].div(df['All'], axis=0)
df['All'] = df['All'] / sum(df['All'])
df

 

 

 

6-3) 범주형과 수치형 데이터 시각화

- BoxPlot

  • pandas : boxplot 함수 제공

- ViolinPlot

  • BoxPlot은 데이터 범위 출력에 효율적이지만 밀도 추정 결과는 출력 불가
  • BoxPlot과 유사하지만 밀도 추정 결과를 두께로 출력
  • 사각형이나 수염의 개념은 없어서 이상치 탐색에는 적합하지 않음
  • 유사한 형태로 SwarmPlot : 색상을 칠하지 않고 점을 겹치지 않게 출력
  • seaborn : violinplot 함수 이용
airline_stats = pd.read_csv('./python_statistics-main/data/airline_stats.csv')
airline_stats.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 33468 entries, 0 to 33467
Data columns (total 4 columns):
 #   Column             Non-Null Count  Dtype  
---  ------             --------------  -----  
 0   pct_carrier_delay  33440 non-null  float64
 1   pct_atc_delay      33440 non-null  float64
 2   pct_weather_delay  33440 non-null  float64
 3   airline            33468 non-null  object 
dtypes: float64(3), object(1)
memory usage: 1.0+ MB

 

#박스플롯

airline_stats.boxplot(by='airline', column='pct_carrier_delay')

 

#바이올린 차트

sns.violinplot(data=airline_stats, x='airline', y='pct_carrier_delay')

 

 

 

 

 

 

 

'Python' 카테고리의 다른 글

[Python] 샘플링 _ 표본 추출  (1) 2024.02.26
[Python] 확률 분포 모형  (1) 2024.02.26
[Python] 이미지 데이터 다루기  (0) 2024.02.21
[Python] 확률  (0) 2024.02.21
[Python] 데이터 스케일링  (1) 2024.02.16

1. Computer Vision

1-1) 영상 처리와 컴퓨터 비전

- 영상 처리

  • 컴퓨터를 사용해서 입력 영상 데이터를 처리하는 분야

- 컴퓨터 비전

  • 인간의 시각을 흉내내는 컴퓨터 프로그램
  • 입력 영상에서 의미있는 정보를 추출하는 분야
  • 제품의 결함을 검사하는 분야나 얼굴 인식이나 지문 인식이나 물체 검출 등이 컴퓨터 비전의 대표적인 분야

 

1-2) 이미지 처리

- 머신 러닝에서는 원본 이미지를 학습 알고리즘이 사용할 수 있도록 변환 수행

 

1-3) 컴퓨터 비전 분야

- Object Classification: 객체 분류, 이미지 분류

- Object Detection & Localization: 객체 식별

- Object Segmentation: 객체 분할

- Image Captioning: 이미지의 상황을 텍스트로 설명하는 기술

- Object Tracking

- Action Classification

 

1-4) 컴퓨터 비전의 어려움

- 사람은 이미지를 보지만 컴퓨터는 숫자를 봄

- 사람은 적은 데이터로 이미지 유추가 가능하지만 컴퓨터는 많은 양의 데이터를 필요로 함

- AI 서비스 환경에서는 사용자 데이터가 실시간으로 쌓이고 이를 반영하기 위한 반복적인 학습이 필수인데, MLOps 나 DevOps를 처음부터 고려해서 설계된 체계적이고 견고한 시스템이 많지 않음

 

1-5) 비전 체험 서비스

- 구글 렌즈

- Which face is real: https://www.whichfaceisreal.com - 생성형 AI를 이용해서 가짜 얼굴 영상을 만들고 골라내는 서비스

 

1-6) 이미지 데이터 셋

- CIFAR-10: 10개의 클래스에 대한 6만개의 이미지 데이터셋

- fashion MNIST: 10개의 클래스에 대한 7만개의 흑백 이미지 데이터셋

- Image Net: 1400 만 개 정도의 공개 이미지 데이터 셋

  • 일상 생활에 사용하는 모든 이미지를 포함하고 있는 데이터 셋

- MS COCO: 이미지를 설명하는 캡션도 같이 제공

- Cityscapes: 자율 주행 관련 이미지

 -Open image: 구글이 제공하는 이미지로 라벨링 과 주석이 같이 있는 이미지 셋

  • 최근에는 음성까지 추가

 

 

2. Open CV

- intel에서 만든 영상 처리와 컴퓨터 비전 관련 오픈 소스 라이브러리

- C/C++ 로 구현했는데 여러 플랫폼에서 C, C++, Python, Java, C#, Javascript 언어로 사용 가능

- MMX나 SSE 명령어를 통해서 고속의 알고리즘을 구현해서 실시간 비전 응용에 강점을 가지고 있음

- 영상 및 비디오 입출력, 영상 처리, 컴퓨터 비전 관련 기본 알고리즘, 기계 학습 모듈이 내장되어 있음

- 최근의 버전에서는 딥러닝 모델도 추가

- CUDA(Compute Unified Device Architecture) 와 Open CL 인터페이스가 개발되어 사용하고 있음

- 공식 사이트

 

설치

- opencv-python 패키지 설치

pip install opencv-python
import cv2
cv2.__version__

 

 

 

3. 윈도우 제어

3-1) 윈도우 생성

- cv2.namedWindow(이름[, 플래그])

  • 플래그: 윈도우 크기 관련 옵션: cv2.WINDOW_NORMAL 이나 cv2.WINDOW_AUTOSIZE
  • 일반적으로 파이썬은 옵션의 값을 문자열로 설정하는데 Open CV에서는 상수를 이용해서 설정

 

3-2) 윈도우 출력

- cv2.imshow(윈도우이름, 윈도우에 표시되는 영상 배열 - numpy.ndarray)

 

3-3) 윈도우 파괴

- cv2.destroyAllWIndows()

 

3-4) 윈도우 생성 및 출력

#윈도우 생성
cv2.namedWindow("window")

#윈도우에 출력할 이미지
image = np.ones((200, 300), np.float64)

#윈도우에 이미지를 출력
cv2.imshow("window", image)

#무한 반복
while True:
    #키보드 입력을 받아서 27(esc)이면 무한 반복 종료
    key = cv2.waitKeyEx(100)
    if key == 27:
        break
        
cv2.destroyAllWindows()

 

 

 

4. 영상 입출력

4-1) 영상 가져오기

- cv2.imread("이미지 파일 경로", 이미지 옵션) -> numpy.ndarray

- 이미지 옵션

  • cv2.IMREAD_UNCHANGED: 알파채널 포함
  • cv2.IMREAD_GRAYSCALE: 흑백
  • cv2.IMREAD_COLOR: 컬러 영상의 기본
  • cv2.IMREAD_ANYDEPTH: 8비트 영상으로 생성
  • cv2.IMREAD_ANYCOLOR

 

4-2) matplotlib.pyplot.imshow

- matplotlib을 이용해서 이미지를 출력

- numpy 의 ndarray 나 PIL 이미지를 출력

- 컬러 이미지를 출력할 때 BGR 순으로 출력하기 때문에 빨강 색과 파랑 색이 반전되서 출력되므로 출력할 때 cv2.cvtColor(이미지 데이터, cv2.COLOR_BGR2RGB)를 이용해서 색상 값의 순서를 변경해 주어야 합니다.

 

 

4-3) 이미지를 흑백으로 출력하고 이미지 데이터의 차원을 확인

import cv2
from matplotlib import pyplot as plt

#이미지를 흑백으로 가져오기
image = cv2.imread("./data/redpanda.png", cv2.IMREAD_GRAYSCALE)

#이미지 출력
plt.imshow(image, cmap='gray')
plt.show()

*이미지 출처: https://mshk43.tistory.com/14
#흑백 이미지의 shape(차원): 2차원 좌표에 값이 있는 구조
#이미지와 관련된 딥러닝은 4차원 데이터만 다룬다.
print(image.shape)

#하나의 픽셀 값을 알고자 하는 경우
print(image[100, 100])

 

(732, 730)
66

 

 

4-4) 이미지를 컬러로 출력

#이미지를 컬러로 가져오기
#이미지를 가져올 때는 BGR 형태로 가져옵니다.
image = cv2.imread("./data/redpanda.png", cv2.IMREAD_COLOR)

# 컬러 이미지를 화면에 출력할 때는 R 과 B의 값을 스위치해야 합니다.
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

#이미지 출력
#BGR의 형태로 출력: 빨강색과 파랑색의 값이 스위치 되어서 출력
#plt.imshow(image)
plt.imshow(image_rgb)
plt.show()
print(image_rgb.shape)#컬럼 이미지는 3차원
(732, 730, 3)

 

 

 

 

5. 배열 처리

- 이미지를 전처리하는 것은 numpy의 ndarray를 조작하는 것

- 차원을 변경하거나 타입을 변경하고자 하면 reshape, flattern, astype 같은 함수 이용

 

5-1) 이미지 크기 변경

- 머신러닝(+딥러닝)에서는 배열 데이터만 다룰 수 있음

*배열 데이터는 동일한 자료형이어야 하고 동일한 차원의 데이터여야 한다.

- 많은 양의 이미지 데이터를 가지고 학습을 하면 메모리 사용량이 많기 때문에 머신이 제대로 학습을 못할 수 있기 때문에 줄여서 학습을 수행하기도 함

- 머신 러닝에서 많이 사용하는 이미지 크기는 32 X 32, 64 X 64, 96 X 96, 256 X 256 등 

- resize(이미지데이터, (가로 크기, 세로 크기)): 이미지 데이터를 가로 크기 와 세로 크기에 맞게 사이즈 조정을 수행

 

# 이미지 차원 변경

#이미지를 흑백으로 가져오기
image = cv2.imread("./data/redpanda.png", cv2.IMREAD_GRAYSCALE)
print("원본 이미지 구조:", image.shape)

#1차원으로 구조를 수정
img = image.reshape(image.shape[0] * image.shape[1])
print(img.shape)

img = image.flatten()
print(img.shape)
원본 이미지 구조: (732, 730)
(534360,)
(534360,)

 

image = cv2.imread("./data/redpanda_64.png", cv2.IMREAD_GRAYSCALE)

#64*64로 이미지 크기를 변경
result = cv2.resize(image, dsize=(64, 64))

plt.imshow(result, cmap='gray')
plt.show()

 

 

5-2) 배열에서 특정 영역 선택

- ROI(Region of Interest - 관심 영역) 선택

  • 이미지를 가지고 예측을 할 때 이미지의 모든 데이터가 필요한 것이 아니기 때문에 예측을 하기 위해서 필요한 영역을 선택하는 것은 중요한 작업
  • 회귀나 분류에서 feature selection(피처의 중요도를 파악해서 특정 피처를 선택하는 것) 과 유사한 작업

- 특정 영역을 선택한 후 이 데이터만 추출해서 작업을 하기도 하고 마스킹을 하기도 함

img = cv2.imread('./data/redpanda.png', cv2.IMREAD_GRAYSCALE)

#특정 영역을 선택해서 마스킹: 0이 검정색 255가 흰색
img[300:400, 200:450] = 255

plt.imshow(img, cmap="gray")
plt.show()

 

 

5-3) 회전

- 이미지 머신 러닝(딥 러닝)의 결과가 정확하지 않은 경우 혹은 이미지 데이터가 부족한 경우 이미지 증강을 위해서 수행

- 고정된 카메라가 영상을 만든다면 모든 영상의 각도가 일정하겠지만, 실제 카메라를 들고 촬영한 영상은 동일한 이미지더라도 각도가 다른 경우가 있기 때문에 학습을 할 때 이 부분을 고려하는 것이 좋다.

 

- 2차원 배열을 수직, 수평 양축으로 뒤집기

  • cv2.flip(src, flipCode[,dst])
    • src: 뒤집을 이미지
    • dst: 결과를 저장할 배열 (리턴하기 때문에 생략 가능)
    • flipCode: 배열을 뒤집는 축. 0이면 X축 기준으로 뒤집고, 1이면 Y축 기준으로 뒤집고, -1이면 양방향 모두 뒤집음

- 전치 (행열치환)

  • cv2.transpose(src[, dst]) -> dst
    • 매개변수dst: 저장할 배열.  리턴 타입에 있는 dst를 리턴하는 결과인데 값 자체는 동일

- 복사

  • cv2.repeat(src, nx, ny[,dst]) ->dst
    • nx 와 ny는 반복 횟수

 

# 이미지 복사 및 회전

x_axis
y_axis
xy_axis
trans_image
rep_image

 

 

5-4) 이미지의 연산

- 이미지는 산술 연산은 거의 수행하지 않음

  • 덧셈을 하는 경우에 255를 초과하면 의미가 없기 때문
  • 뺄셈을 하는 경우에 음수가 나오면 무의미

- 이미지는 비트 연산을 많이 수행

  • 비트 연산을 이용해서 이미지 합성을 수행
  • bitwise_or(둘 다 0인 경우만 0이고 나머지는 1), 
    bitwise_and(둘 다 1인 경우만 1이고 나머지는 0), 
    bitwise_xor(같으면 0 다르면 1)
  • 0 과 0 또는 1 과 1 이면 0 그 이외의 경우는 1), bitwise_not(0->1, 1->0) 으로 연산
#2개의 배열 생성
image1 = np.zeros((300, 300), np.uint8)
image2 = image1.copy()

#중앙점의 좌표 찾기
h, w = image1.shape[:2]
cx, cy = w//2, h//2

#배열에 원과 사각형 그리기
cv2.circle(image1, (cx, cy), 100, 255, -1)
cv2.rectangle(image2, (0, 0, cx, h), 255, -1)
#or를 하게되면 색상이 있는 영역이 합쳐집니다.
image3 = cv2.bitwise_or(image1, image2)
#and를 하게되면 색상이 없는 영역이 합쳐집니다.
image4 = cv2.bitwise_and(image1, image2)
#xor를 하게되면 색상이 색상이 같은 영역은 색상이 제거되고 그렇지 않은 영역만 색상이 
#칠해집니다.
image5 = cv2.bitwise_xor(image1, image2)

cv2.imshow("image1", image1)
cv2.imshow("image2", image2)
cv2.imshow("image3", image3)
cv2.imshow("image4", image4)
cv2.imshow("image5", image5)

cv2.waitKey(0)
image1
image2
image3
image4

image5

 

 

5-5) 통계 함수 사용 가능 - 이미지 선명화

image = cv2.imread("./data/redpanda.png", cv2.IMREAD_GRAYSCALE)

(min_val, max_val, _, _) = cv2.minMaxLoc(image)

ratio = 255/(max_val - min_val)
dst = np.round((image - min_val) * ratio).astype('uint8')

cv2.imshow("image", image)
cv2.imshow("dst", dst)
cv2.waitKey(0)

 

'Python' 카테고리의 다른 글

[Python] 확률 분포 모형  (1) 2024.02.26
[Python] 기술통계  (0) 2024.02.22
[Python] 확률  (0) 2024.02.21
[Python] 데이터 스케일링  (1) 2024.02.16
[Python] 데이터 전처리  (0) 2024.02.15

[Python] 확률

0ㅑ채
|2024. 2. 21. 11:17

1.주피터 노트북에서 수학 기호 사용

- TeX 라는 조판 언어를 이용해서 수식을 표현할 수 있음 - Markdown Cell을 생성한 후 셀 안에서 $ 기호를 이용해서 수기을 표현
- 그리스 문자는 \그리스문자이름


2. 공통 코드

import pandas as pd
import numpy as np

#dist 플롯이나 histogram
import seaborn as sns 

#시각화에서 한글을 사용하기 위한 설정
import platform
from matplotlib import font_manager, rc


font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)
    
#시각화에서 음수를 표현하기 위한 설정
import matplotlib
matplotlib.rcParams['axes.unicode_minus'] = False 

# Jupyter Notebook의 출력을 소수점 이하 3자리로 제한 
%precision 3

# precision은 소수점은 과학적 표기법으로 변환할 자릿수를 설정
# 아래와 같이 하면 소수점 셋째 자리 밑으로는 과학적 표기법으로 표시
pd.options.display.precision = 3

#경고 무시
import warnings
warnings.filterwarnings('ignore')

 

 

3. 집합

- 구별 가능한 객체의 모임 : 데이터 중복 불가
- 집합은 대문자로 표현, 원소는 소문자로 표현
- Python에서는 set과 frozenset 자료형으로 집합 표현

  • set: 내용 변경 가능한 mutable 자료형
  • frozenset: 내용 변경 불가한 immutable 자료형
    • dict의 key는 반드시 immutable만 가능하기 때문에 frozenset이다. 
    • dir(frozenset): set과 달리 'add' 가 없다. 수정 불가능하니까!

 

3-1) 집합 생성

# set 원소로 생성

a = {1, 2, 3, 1, 2}
print(a)
{1, 2, 3}

 

  • 중복이 자동 제거됨. 

 
# 빈 set 생성

b = {} # dict가 되므로 이렇게 만들면 안됨
print(type(b))

c = set()
print(type(c))
<class 'dict'>
<class 'set'>

 
#frozenset 생성

d = frozenset()
d.add()
AttributeError: 'frozenset' object has no attribute 'add'
  • immutable은 무조건 처음에 데이터를 가지고 만들어야 한다. 수정이 안되니까!
d = frozenset([1, 2, 3])
print(type(d))
<class 'frozenset'>

 
# 연산자 오버로딩
Overloading: 동일한 이름의 메소드가 2개 이상 존재하는 경우 

  • 원래는 함수이지만 연산자를 이용해서 호출할 수 있도록 작성
  • 대신 이 메소드는 매개변수의 개수나 자료형이 달라야 한다.
  • set은 |와 &연산자 가능 (__and__, __or__가 존재)
    원래 이렇게 쓰는 애는 아닌데, 클래스 안에서 재정의해서 다른 용도로 쓰게 하는 것을 연산자 오버로딩이라고 한다.
print({1, 2, 3} & {2, 3, 4})
print({1, 2, 3} | {2, 3, 4})
{2, 3} 
{1, 2, 3, 4}
#부등호도 가능 (__ne__, __lt__가 존재)
print({1, 2, 3} > {2, 3})
  • 부분 집합 여부를 부등호로 판정
True




 
 
 

3-2) 연산

- set에서 합집합, 교집합, 차집합을 구해주는 함수 제공

  • 합집합이나 교집합은 |나 &연산자로도 구할 수 있음

 
 
 

4. 확률

4-1) 확률의 정의와 의미

- 확률은 현실에서 해결하려는 문제와 결부해서 정의
- 어떤 문제가 어떤 답을 가질 수 있고, 답의 신뢰성이 얼마인지 계산하는 정량적 방법 제시

  • ex. 동전을 던졌을 때 앞면이 나올 것인가, 뒷면이 나올 것인가?

 

4-2) 확률에서 나오는 개념

- 확률 표본(random sample, probablistic sample):문제에서 발생할 수 있는 하나의 현상 혹은 선택될 수 있는 하나의 경우(표본)
- 표본 공간: 가능한 모든 표본의 집합   Ω

  • 표본 공간은 도메인 지식을 바탕으로 결정
    • ex. 동전던지기에서 앞면, 뒷면뿐만 아니라 동전이 세워지는 경우도 있다. 그런데 거의 일어나지 않기 때문에 표본 공간은 그냥 앞면, 뒷면만 설정한다.
  • 특별한 경우에는 표본공간의 개수가 무한대가 되기도 한다.
    • ex. 주식의 등락폭: -30% ~ 30%

- 시행: 조작
- 사건(event): 표본 공간의 부분 집합으로 우리가 관심을 가지는 일부 표본의 집합

  • 시행에서 얻을 수 있는 사건의 모든 집합을 전사상이라고 함
    • 동전 던지기에서 나올 수 있는 표본은 앞면과 뒷면
      전사상: {}, {앞면}, {뒷면}, {앞면, 뒷면}

- 확률: 모든 각각의 사건에 어떤 숫자(정량)를 할당하는 함수  P

  • 함수가 지켜야 할 규칙: 콜모고로프의 공리
  • 모든 사건에 대해 확률은 실수이고 0 또는 양수
  • 표본 공간이라는 사건에 대한 확률은 1
  • 공통 원소가 없는 두 사건의 합집합의 확률은 사건별 확률의 합

 

4-3) 확률을 바라보는 관점

- 빈도주의 관점

  • 반복적으로 선택된 표본이 사건 A의 원소가 될 경향을 사건의 확률이라고 본다. 
  • 확률이 0.5 : 10000번 시행했을 때 5000번이 나오는 경향
  • 반복했을 때 몇번 나오니?

 
- 베이지안 관점

  • 신뢰도를 의미한다.
  • 확률이 0.5 : 1번 시행했을 때 그 사건이 발생할 가능성이 50%다.

 
- 보는 각도에 따라 빈도주의 관점이 될 수 있고, 베이지안 관점이 될 수 있다. 

  • 의사가 나에게 병이 걸렸을 가능성이 70%라고 진단을 내렸다.
  • 의사 입장에서는 여러 명의 환자를 진찰하기 때문에 10명이 진료를 받았을 때 7명은 병에 걸린다는 의미. 
  • 환자 입장에서는 내가 병에 걸렸을 가능성이 70%라고 해석하게 된다.

 

4-4) 확률의 성질

- 발생할 수 없는 사건의 확률은 0
- 어떤 사건의 여집합 확률은  1- P
- 합집합은 각 사건의 확률의 합에서 두 사건의 교집합의 확률을 뺀 것
 

4-5) 결합 확률

- 사건 A와 사건 B가 동시에 발생할 확률
- 두 사건의 교집합

 

4-6) 주변 확률

- 개별 사건의 확률: 범인 찾기 문제

  • 전체 인원은 20명에서 남자는 12명, 여자는 8명
  • 남자는 머리가 긴 사람이 3명, 여자는 머리가 긴 사람이 7명일 때, 
  • 범인이 남자이고 머리가 길다면, 결합 확률은 남자이고 머리 긴 사람 3명이므로  3/20
  • 주변 확률: 남자일 확률은 12/30, 머리가 길 확률은 10/20

 

4-7) 조건부 확률

- B가 사실일 경우의 사건 A에 대한 확률이 사건 B에 대한 사건 A의 조건부 확률

- P(B|A): 가능도(likelihood)
- P(B): 정규화 상수(Normalizing Constant) 또는 증거(Evidence)라고 함
 

4-8) 확률을 구해주는 패키지 - pgmpy

- JointProbabilityDistribution(variables, cardinality, values)

  • 결합 확률 모형을 만드는 데 사용하는 클래스
  • variables: 확률 변수의 이름으로 문자열의 리스트로 정의. [문자열이 1개여도 list 사용해야 함!]
  • cardinality: 각 확률 변수의 표본 또는 사건의 개수 [리스트]
  • values: 확률 변수의 모든 표본에 대한 확률 값의 [리스트]

# 남자가 12명이고 여자가 8명일 때 독립 확률

from pgmpy.factors.discrete import JointProbabilityDistribution as JPD

px = JPD(['X'], [2], np.array([12, 8]) / 20) #[12/20, 12/8]
print(px)
  • 남자와 여자는 상호 배타적인 사건이라서 하나의 확률 변수 ['X']
  • 사건의 개수 [2]
+------+--------+
| X    |   P(X) |
+===+=====+
| X(0) | 0.6000 |
+------+--------+
| X(1) | 0.4000 |
+------+--------+

 
# 결합 확률: 확률 변수가 2개 이상인 경우

  • 남자 12명이고 머리가 짧은 사람 9명, 긴 사람 3명
  • 여자 8명이고 머리가 짧은 사람 1명, 긴 사람 7명
    • 남자와 여자는 배타적이지만, 성별과 머리길이는 배타적이지 않으므로 확률 변수가 2개 ( X, Y ) 필요
    • 각 확률변수의 사건의 개수는 긻다,짧다로 2개 
    • 확률 값은 9, 3, 1, 7
pxy = JPD(['X', 'Y'], [2, 2], np.array([3, 9, 7, 1]) / 20)
print(pxy)
  • X: 남자, 여자
  • Y: 머리 짧다, 길다
+------+------+----------+
| X    | Y    |   P(X,Y) |
+===+===+=======+
| X(0) | Y(0) |   0.1500 |
+------+------+----------+
| X(0) | Y(1) |   0.4500 |
+------+------+----------+
| X(1) | Y(0) |   0.3500 |
+------+------+----------+
| X(1) | Y(1) |   0.0500 |
+------+------+----------+

 
 
- marginal_distribution(values, inplace=True)

  • 주변 확률을 구해주는 함수
  • values: 주변 확률을 구할 확률 변수의 이름 [문자열 리스트]
  • inplace: 기본 값이 Ture
  • JPD 객체를 이용해서 호출

- marginalize(values, inplace=True)

  • values 어떤 확률 변수의 주변 확률을 구하기 위해서 없애줄 확률 변수의 이름 [리스트]

-conditional_distribution(values, inplace=True)

  • values 조건부 확률을 구할 확률 변수의 이름 문자열과 값을 묶은 튜플의 리스트
# x인수로 받은 확률 변수에 대한 주변 확률 분포
pmx = pxy.marginal_distribution(['X'], inplace=False)
print(pmx)

# 인수로 받은 확률 변수를 주변화(marginalize)하여 나머지 확률 변수에 대한 주변 확률 분포
pmx = pxy.marginalize(['Y'], inplace=False)
print(pmx)
+------+--------+
| X    |   P(X) |
+====+====+
| X(0) | 0.6000 |
+------+--------+
| X(1) | 0.4000 |
+------+--------+
+------+--------+
| X    |   P(X) |
+====+=====+
| X(0) | 0.6000 |
+------+--------+
| X(1) | 0.4000 |
+------+--------+

 
 
 

4-9) 베이즈 정리

  • 조건이 주어졌을 때 조건부 확률을 구하는 공식
  • 새로운 데이터가 주어지기 전에 이미 어느정도 확률 값을 알고 있을 때 이를 새로 수집한 데이터와 합쳐서 최종 결과에 반영
  • 데이터 개수가 부족할 때 유용
  • 데이터를 추가하는 상황에서 전체 데이터를 새로 분석할 필요 없이 이전 분석 결과에 새로 들어온 데이터를 합쳐서 업데이트만 수행하면 됨

- 베이즈 정리를 이용한 검사 시욕 문제

  • 병에 걸린 환자에게 시약을 테스트한 결과 99% 확률로 양성반응을 보인 경우, 병에 걸렸는지 알 수 없는 환자에게 시약을 테스트했을 때 양성이 나왔으면 실제 병에 걸렸을 경우는?
    => 환자가 실제로 병에 걸린 경우를 사건 D 라고 하면 병에 걸려있지 않은 경우는 사건 D의 여집합
    => 시약 테스트에서 양성 반응을 보이는 경우를 사건 S 라고 하면 음성 반응을 보이는 경우는 사건 S의 여집합
  • 추가 조사를 했는데 이 병에 걸린 사람은 전체 인구의 0.2%
    => 현재 주어진 확률 값은 병에 걸린 환자에게 시약을 테스트했을 때 양성 반응을 보이는 확률인데 병에 걸렸다는 것은 추가된 조건 혹은 정보이므로 이 확률은 P(S|D) 로 표기
  • 이 병에 걸리지 않은 사람에게 시약 검사를 하면 양성 반응이 나타날 확률 5%
    => 구해야 하는 값은 이것과 반대로 양성 반응을 보이는 환자가 병에 걸려있을 확률인데 이 때에는 양성 반응을 보인다라는 것이 추가된 정보이므로 이 확률은 P(D|S) 로 표기

P(D|S) = P(S|D)P(D) / P(S)

 
- 몬티홀 문제

  • 3개의 문 중에 1개의 문 뒤에 선물이 있고, 나머지 2개의 문에는 선물이 없음
  • 1개의 문을 열어서 선물이 없다는 걸 보여주면 현재 선택한 문을 바꿀 것인가?
  • 실제로 바꾸는 것이 바꾸지 않는 것보다 확률이 2배 높다.

문의 위치를 0, 1, 2로 표현

  • 선물이 있는 문 확률변수 C의 값은 0, 1, 2
  • 참가자가 선택한 문 확률변수 X의 값은 0, 1, 2
  • 진행자가 열어준 문 확률변수 H의 값은 0, 1, 2

참가자가 1번 문을 선택했고, 진행자가 2번 문을 열어 선물이 없음을 보여주면 이진 분류가 된다.

  • 선물은 0번 아니면 1번에 존재
  • 진행자가 어떤 문을 여는가 하는 것은 선물의 위치와 참가자의 선택에 따라 달라지게 된다. 따라서 독립적인 일이 아니라 영향을 받는 일이 된다.
    • 선물이 0번 문 뒤에 있고, 참가자가 1번을 선택하면 진행자는 2번 문을 열 수밖에 없다. 
    • 선물이 1번 문 뒤에 있고, 참가자가 1번을 선택하면 진행자는 0번 아니면 2번 문을 연다. 
  • 0번 문 뒤에 선물이 있을 확률
    (1/3) / ((1 * 1/3) + (1/2 + 1/3) + (0 * 1/3))
    => 2/3
  • 1번 문 뒤에 있을 확률
    (1 - 2/3)
    => 1/3

'Python' 카테고리의 다른 글

[Python] 기술통계  (0) 2024.02.22
[Python] 이미지 데이터 다루기  (0) 2024.02.21
[Python] 데이터 스케일링  (1) 2024.02.16
[Python] 데이터 전처리  (0) 2024.02.15
[Python] 데이터 시각화  (0) 2024.02.14

1. Standardization (표준화)

1-1) 개요

- 다변량 분석에서는 각 컬럼의 숫자 데이터의 크기가 상대적으로 달라서 분석 결과가 왜곡되는 현상 발생

  • 다변량 분석: 2개 이상의 컬럼을 같이 사용해서 수행하는 분석

- 상대적 크기 차이를 동일하게 맞추는 작업이 표준화 또는 스케일링

  • 일반적으로 0  ~ 1이나   -1 ~ 1로 생성
  • 방법을 달리하는 가장 큰 이유는 이상치 여부 때문

 

1-2) 표준값과 편차값

- 모든 값들의 표준 값 기준으로 차이를 구해서 비교
 

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

  • 표준 값 평균은 0, 표준 편차는 1
  • 정규분포와 비교하기 쉽다.

 
편차 값: 표준값 * 10 + 50

  • 양수로 바꿔주기. 음수는 오타나 오류를 많이 발생하고 직관적이지 않기 때문.
  • 편차 값의 평균은 50, 표준 편차는 10

 
표준점수 해석

  • 표준점수 0.0(=편차치 50) 이상은 전체의 50%
  • 표준점수 1.0(=편차치 60) 이상은 전체의 15.866%
  • 표준점수 2.0(=편차치 70) 이상은 전체의 2.275%
  • 표준점수 3.0(=편차치 80) 이상은 전체의 0.13499%
  • 표준점수 4.0(=편차치 90) 이상은 전체의 0.00315%
  • 표준점수 5.0(=편차치 100) 이상은 전체의 0.00002%
students = pd.read_csv("data/student.csv", encoding='ms949', index_col ='이름')

 
# 한글 폰트

import matplotlib.pyplot as plt
import platform
from matplotlib import font_manager, rc

font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)

 
# 음수 출력 설정

plt.rcParams['axes.unicode_minus'] = False

 
# 표준 값 구하기

#표준값과 편차값 구하기
kormean, korstd = students['국어'].mean(), students['국어'].std()
engmean, engstd = students['영어'].mean(), students['영어'].std()
matmean, matstd = students['수학'].mean(), students['수학'].std()

students['국어표준값'] = (students['국어'] - kormean)/korstd
students['영어표준값'] = (students['영어'] - engmean)/engstd
students['수학표준값'] = (students['수학'] - matmean)/matstd

students['국어편차값'] = students['국어표준값'] * 10 + 50
students['영어편차값'] = students['영어표준값'] * 10 + 50
students['수학편차값'] = students['수학표준값'] * 10 + 50
students[['국어편차값','영어편차값','수학편차값']].plot(kind='bar')

 
 
 

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

StandardScaler(데이터)

  • 평균이 0, 표준편차가 1이 되도록 변환
  • 주성분 분석 등에서 많이 사용
  • 표준화된 유사 형태의 데이터 분포로 반환

*주성분 분석: 여러 독립변수의 주된 특성만 선별하여 독립변수의 개수(=차원의 수)를 줄일 수 있다.

 

MinMaxScaler(데이터)

  • 특정 값에 집중되어 있는 데이터일수록 표준편차에 의한 스케일 변화 값이 커진다. 
  • 한쪽으로 쏠린 데이터 분포는 형태가 거의 유지된 채 범위 값이 조절된다. 
  • 최대값이 1, 최소값이 0이 되도록 변환
  • (데이터 - 최소값) / (최대값 - 최소값)
  • 신경망(딥러닝)에서 주로 사용

 

RobustScaler(데이터)

  • 중앙값 0, IQR이 1이 되도록 변환
  • 데이터의 이상치가 많으면 평균과 표준편차에 영향을 미치기 때문에 표준화에 부정적
  • 중간값과 사분위 범위를 사용하여 스케일을 조정
  • (데이터 - 중간값) / (75%값 - 25%값)
  • => 이상치에 덜 민감해짐
  • 표준화된 유사 형태의 데이터 분포로 반환

 

- MaxAbsScaler(데이터)

  • 0을 기준으로 절대값이 가장 큰 수가 1 또는 -1이 되도록 변환
  • 절대값이 0 ~ 1 사이가 되도록 매핑
  • 각 열 별로 절대값이 가장 큰 값으로 나누는 것
  • 양수 데이터로만 구성된 경우에는 MinMax와 유사하면 이상치에 민감
  • 음수 자체가 어떤 의미를 지닌 경우 사용

 

- QuantileTransformer

  • 데이터를 1000개의 분위로 나눠 0 ~ 1 사이에 고르게 분포시키는 방식
  • 이상치의 영향을 제거하기 어려운 경우 사용. 이상치의 영향을 덜 받게 해줌
이상치를 제거하는게 맞을까, 영향을 줄이는 게 맞을까, 이상치끼리 분석해주는게 맞을까?
어쩔 수 없이 이상치를 가지고 분석해야 하는 경우 이렇게 스케일링을 해줘야 한다. 
그래서 먼저 데이터를 그래프로 그려보고 박스플롯이나 분산차트 같은 걸로 이상치를 확인한다.

전처리는 scikit-learn으로 거의 다 됨

스케일링 시 Feature 별로 크기를 유사하게 만드는 것은 중요하지만, 특성에 따라 어떤 항목은 데이터의 분포를 유지해야 한다. 데이터가 한 곳에 집중된 Feature를 표준화시키면 작은 단위 변화가 큰 차이를 나타내는 것처럼 반영되기 때문이다. 

 
 
- 표준화 과정

  • 데이터를 입력해서 fit 메소드 호출 - 분포 모수를 객체 내에 저장
  • 데이터를 입력해서 transform 메소드 호출 - 표준화된 결과 리턴
    • 2개의 과정을 합친 fit_transform 메소드도 있다.
  • 훈련 세트와 테스트 세트로 나눈 경우 동일한 방식으로 표준화 작업
from sklearn import preprocessing
x = mpg[['horsepower']].values

#표준화 전의 기술통계 값
print('평균:', np.mean(x))
print('표준 편차:', np.std(x))
print('최대값:', np.max(x))
print('최소값:', np.min(x))
평균: 104.46938775510205
표준 편차: 38.44203271442593
최대값: 230.0
최소값: 46.0

 
#평균 0, 표준편차 1로 만드는 스케일링 - StandardScaler

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))
평균: -1.812609019796174e-16
표준 편차: 0.9999999999999998
최대값: 3.2654519904664348
최소값: -1.5209754434541274

 
# 이상치가 많은 경우 - RobustScaler

scaler = preprocessing.RobustScaler()
scaler.fit(x)
x_scaled = scaler.transform(x)
평균: 0.2150860344137655
표준 편차: 0.7537653473416848
최대값: 2.676470588235294
최소값: -0.9313725490196079

 

표준화 전 표준화 후

 
 
 
 

2. Normalization (정규화)

- 데이터 범위를 0과 1로 변환해서 데이터 분포 조정
- scikit-learn의 Normalizer 클래스의 객체를 만든 후 transform 함수 사용
- norm 옵션: L1, L2를 설정

  • L1: 맨해튼 거리
    • 각 좌표 거리의 합. 지도상에 보이는 경로 같은 거.
  • L2: 유클리드 거리
    • 각 좌표를 빼서 제곱하고 더한 다음에 루트 .. 그냥 대각선으로 가는 것

- 여러 개의 속성을 한꺼번에 스케일링
- 행 단위로 스케일링
- 여러 개의 컬럼을 가진 데이터(텍스트)에서 컬럼들이 독립적이지 않은 경우 이용
 

from sklearn.preprocessing import Normalizer
# 특성 행렬을 만듭니다.
features = np.array([[1, 2],
                     [2, 3],
                     [3, 8],
                     [4, 2],
                     [7, 2]])

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

# 특성 행렬을 변환
features_l1_norm = normalizer.transform(features)
print(features_l1_norm)
[[0.33333333 0.66666667]
 [0.4        0.6       ]
 [0.27272727 0.72727273]
 [0.66666667 0.33333333]
 [0.77777778 0.22222222]]
  • l1은 행 데이터의 합을 가지고 각 데이터를 나눈 값: 총계 1
normalizer = Normalizer(norm="l2")
features_l2_norm = normalizer.transform(features)
print(features_l2_norm)
[[0.4472136  0.89442719]
 [0.5547002  0.83205029]
 [0.35112344 0.93632918]
 [0.89442719 0.4472136 ]
 [0.96152395 0.27472113]]
  • l2를 설정하면 각 데이터를 제곱해서 더한 값의 제곱근
# max
normalizer = Normalizer(norm = "max")
features_l1_norm = normalizer.transform(features)
print(features_l1_norm)
[[0.5        1.        ]
 [0.66666667 1.        ]
 [0.375      1.        ]
 [1.         0.5       ]
 [1.         0.28571429]]

 
 
 
 

3. 다항 특성과 교차항 특성

  • 다항: 제곱해나가는 것
  • 교차항: 곱하는 것

- 컬럼들을 제곱하고 곱해서 데이터 추가
- 특성과 타겟 사이에 비선형 관계가 존재하는 경우 다항 특성 생성
- scikit-learn의 PolynomialFeatures 클래스 이용

  • degree 옵션: 몇차 항까지 생성할 것인지 설정
  • include_bias 옵션: 첫번째 항으로 상수 1을 추가할지 여부 설정
  • interation_only 옵션: True를 설정하면 교차항 특성만 생성
  • 생성된 객체의 get_feature_names_out 함수를 이용하면 변환식 이름으로 반환
from sklearn.preprocessing import PolynomialFeatures
# 특성 행렬을 만든다.
features = np.array([[2, 3],
                     [1, 2],
                     [22, 6]])

# PolynomialFeatures 객체를 만든다.
polynomial_interaction = PolynomialFeatures(degree=2, include_bias=False)

# 다항 특성을 만든다.
result = polynomial_interaction.fit_transform(features)
print(result)
[[  2.   3.   4.   6.   9.]
 [  1.   2.   1.   2.   4.]
 [ 22.   6. 484. 132.  36.]]
polynomial_interaction = PolynomialFeatures(degree=2, include_bias=True, interaction_only=True)

result = polynomial_interaction.fit_transform(features)
print(result)
[[  1.   2.   3.   6.]
 [  1.   1.   2.   2.]
 [  1.  22.   6. 132.]]

 
 
 

4. 특성 변환

- 컬럼에 함수를 적용해서 데이터를 변경
- pandas의 apply 함수 이용
- API: sklean.FunctionTransformer,  sklean.ColumnTransformer 

  • FunctionTransformer: 모든 열에 동일한 함수 적용하고자 할 때 사용
    • = pandas.apply
  • ColumnTransformer: 각 열에 다른 함수 적용하고자 할 때 사용
    • 함수, 변환기, 컬럼 이름의 리스트로 이루어진 튜플의 list를 매개변수로 받음

- 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)
[[2 4]
 [4 7]]
df = pd.DataFrame(features, columns = ['f1', 'f2'])
print(df.apply(add_one))
   f1  f2
0   2   4
1   4   7

 
 
 
 

5. 숫자 데이터의 이산화

- 데이터를 분석할 때 연속된 데이터는 일정한 구간으로 나눠서 분석하는 것이 효율적

  • ex. 가격, 비용, 효율, 나이 등
  • 연속적인 값을 일정한 수준이나 정도를 나타내는 이산적인 값으로 나타내어 구간별 차이를 드러나게 함

 

5-1) 구간 분할

- 연속형 데이터를 일정한 구간으로 나누고 각 구간을 범주형 이산 변수로 치환하는 과정 : binning (구간 분할)
- pandas의 cut 함수: 연속형 데이터를 여러 구간으로 분할해서 범주형 데이터로 변환가능
- 구간을 분할할 때 반드시 일정한 간격을 만들 필요는 없음

10대는 쪼개서 분류하는게 좋다.

 

df = pd.read_csv('./data/auto-mpg.csv', header=None)

# 열 이름을 설정
df.columns = ['mpg','cylinders','displacement','horsepower','weight',
              'acceleration','model year','origin','name'] 

df['horsepower'].replace('?', np.nan, inplace=True)      # '?'을 np.nan으로 변경
df.dropna(subset=['horsepower'], axis=0, inplace=True)   # 누락데이터 행을 삭제
df['horsepower'] = df['horsepower'].astype('float')      # 문자열을 실수형으로 변환

 
 
# 구간 분할

count, bin_dividers = np.histogram(df['horsepower'], bins=3)
print(count)
print(bin_dividers)
[257 103  32]
[ 46.         107.33333333      168.66666667 230.  ]
  • 모든 함수는 데이터를 하나만 리턴할 수 있다.
  • 튜플은 튜플을 리턴할 때 나누어서 할당이 가능 

 

# 3개의 bin에 이름 지정
bin_names = ['저출력', '보통출력', '고출력']

# pd.cut 함수로 각 데이터를 3개의 bin에 할당
df['hp_bin'] = pd.cut(x=df['horsepower'],     # 데이터 배열
                      bins=bin_dividers,      # 경계 값 리스트
                      labels=bin_names,       # bin 이름
                      include_lowest=True)    # 첫 경계값 포함 여부

df[['horsepower', 'hp_bin']].head(20)

 
 
- numpy. digitize 

  • 첫번째 데이터는 이산화할 데이터
  • bins = 이산화할 경계값 리스트
  • right = 경계값 포함 여부 설정

- sklearn. Binarizer

  • 생성할 때 하나의 경계값을 대입해서 생성하고 fit_transform을 호출하면
    경계값보다 작은 경우는 0, 그렇지 않은 경우는 1을 할당해서 리턴

- sklearn. KBinsDiscretizer

  • 첫번째 매개변수: 구간 개수
  • encode: ordinal(각 구간을 하나의 컬럼에 일련번호 형태 리턴), onehot(희소 행렬 리턴), onehot-dense(밀집 행렬 형태 리턴)
  • strategy: quantile(일정한 비율), uniform(구간 폭 일정)
  • bin_edges_: 각 구간의 값을 알고자 할 때 속성 확인
Label Encoding: 순서가 의미 있을 때 (0, 1, 2)
OneHot Encoding: 순서가 의미 없을 때
- Sparse Matrix (희소 행렬): 행렬 전체 반환
- Dense Matrix (밀집 행렬): 값의 위치만 반환

a = [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]] 

pandas의 메소드들은 DataFrame이나 Series를 이용해서 작업하지만
Machine Learning 관련된 메소드들은 2차원 이상의 ndarray로 작업

 
 
# pandas.cut()

age = np.array([[13],
                [30],
                [67],
                [36],
                [64],
                [24]])

# 30을 기준으로 분할
result = np.digitize(age, bins=[30])
print(result)
[[0]
 [1]
 [1]
 [1]
 [1]
 [0]]
  • 30이 안되면 0 / 30 이상이면 1로 변환
  • bins에는 여러개의 데이터 설정이 가능
#0-19, 20-29, 30-63, 64이상의 구간으로 분할
result = np.digitize(age, bins=[20,30,64])
print(result)
[[0]
 [2]
 [3]
 [2]
 [3]
 [1]]
#0-20, 21-30, 31-64, 64초과 구간으로 분할
result = np.digitize(age, bins=[20,30,64], right=True)
print(result)
print()
[[0]
 [1]
 [3]
 [2]
 [2]
 [1]]

 
 
# Binarizer를 이용한 구간 분할

from sklearn.preprocessing import Binarizer

#threshold(임계값)
binarizer = Binarizer(threshold=30.0)
print(binarizer.transform(age))
  • 흑백 이미지 데이터에서 뚜렷한 구분을 위해 임계값 아래와 위를 구분
[[0]
 [0]
 [1]
 [1]
 [1]
 [0]]

 
 
# 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))
[[0.]
 [1.]
 [3.]
 [2.]
 [3.]
 [0.]]

  (0, 0) 1.0
  (1, 1) 1.0
  (2, 3) 1.0
  (3, 2) 1.0
  (4, 3) 1.0
  (5, 0) 1.0

[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 0. 1.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]
 [1. 0. 0. 0.]]

 
 

5-2) 군집 분석

- 2개 이상의 컬럼의 값으로 이산화
- 데이터 전처리 단계에서 군집 분석한 결과를 기존 데이터의 컬럼으로 추가해서 이산화 수행 가능
- 수집 -> 전처리 -> 분석     *순서는 언제든지 변경되고 나선형으로 수행될 수 있음
- k 개의 그룹이 있다는 것을 아는 경우에는 k-means clustering을 사용하여 비슷한 샘플을 그룹화 할 수 있음

from sklearn.cluster import KMeans
# 모의 특성 행렬을 생성
sample = np.array([[13, 30],
                [30, 40],
                [67, 44],
                [36, 24],
                [64, 37],
                [24, 46]])

# 데이터프레임을 생성
dataframe = pd.DataFrame(sample, columns=["feature_1", "feature_2"])
# k-평균 군집 모델을 생성
clusterer = KMeans(3, random_state=42)

# 모델을 훈련
clusterer.fit(sample)

# 그룹 소속을 예측
dataframe["group"] = clusterer.predict(sample)

 
 
 
 

6. 이상치 탐지

- Z-score 이용

  • 중앙값을 기준으로 표준 편차가 절대값 3 범위의 바깥 쪽에 위치하는 데이터를 이상치로 간주
  • 데이터가 12개 이하이면 이상치 감지 불가

- Z-score 보정

  • 편차의 절대값 범위를 3.5로 설정한 후 0.6745를 곱해서 사용

 
- IQR(3사분위수 - 1사분위수)

  • 1사분위수보다 1.5(IQR) 이상 작은 값이나 3사분위수보다 1.5(IQR) 이상 큰 값을 이상치로 간주
  • boxplot에서 수염 바깥쪽 데이터를 이상치로 간주

파란 점이 이상치

 
- 투표: 3개 모두 사용해보고 2개 이상에서 이상치로 간주되는 데이터를 이상치 판정
- 데이터의 특정 비율을 이상치로 간주 

  • scikit-learn의 covariance.EllipticEnvelope 클래스 이용
  • 잘 제시되지는 않는 방법

 
- 수식을 이용한 이상치 탐지

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

 
 
# Z-score 이용

def outliers_z_score(ys):
    threshold = 3
    
    mean_y = np.mean(ys)
    print("평균:", mean_y)
    
    stdev_y = np.std(ys)
    print("표준 편차:", stdev_y)
    
    z_scores = [(y - mean_y) / stdev_y for y in ys]
    print("z_score:", z_scores)
    
    return np.where(np.abs(z_scores) > threshold)
평균: 1254.5

표준 편차: 4840.068065120572

z_score:
[array([-0.25712448, -0.25712448, -0.25774431, -0.25795092, -0.25836414, -0.25836414, -0.25857074, -0.25857074]), 
array([ 3.87298272, -0.25857074, -0.25815753, -0.25733109, -0.25877735, -0.25877735, -0.25877735, -0.25877735])]

(array([1], dtype=int64), array([0], dtype=int64))
  • 하지만 데이터가 12개 이하면 null 값이 뜬다.

 
 
# Z-score 보정 - 데이터 개수에 상관없이 이상치 감지

def outliers_modified_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)
(array([1], dtype=int64), array([0], dtype=int64))
  • 데이터가 12개 이하여도 상관없음

 
 
# IQR 

def outliers_iqr(ys):
    
    #1사분위, 3사분위
    quartile_1, quartile_3 = np.percentile(ys, [25, 75])
    
    #IQR
    iqr = quartile_3 - quartile_1
    
    #하한과 상한 구하기
    lower_bound = quartile_1 - (iqr * 1.5)
    upper_bound = quartile_3 + (iqr * 1.5)
    
    print("하한값:", lower_bound)
    print("상한값:", upper_bound)
    
    return np.where((ys > upper_bound) | (ys < lower_bound))
    
    
features = np.array([[10, 10, 7, 6, -4900],
                     [20000, 3, 5, 9, 10]])

print(outliers_iqr(features))
하한값: -1.875
상한값: 17.125
(array([0, 1], dtype=int64), array([4, 0], dtype=int64))
print(outliers_iqr(df['horsepower'].values))
하한값: -1.5
상한값: 202.5
(array([  6,   7,   8,  13,  25,  27,  66,  93,  94, 115], dtype=int64),)

 
 
# 일정한 비율의 데이터를 이상치로 간주

  • 값보다는 비율을 이용해서 이상치 판정
  • ex. 국가장학금 지급할 때 특정한 소득 기준은 없고 상위 10%를 제외한 학생에게 장학금을 지급하는 경우
#일정한 비율을 이상치로 간주하는 API
from sklearn.covariance import EllipticEnvelope
#데이터를 생성해주는 API
from sklearn.datasets import make_blobs
# 모의 데이터를 만듭니다.
features, _ = make_blobs(n_samples = 10,
                         n_features = 2,
                         centers = 1,
                         random_state = 1)
[[-1.83198811  3.52863145]
 [-2.76017908  5.55121358]
 [-1.61734616  4.98930508]
 [-0.52579046  3.3065986 ]
 [ 0.08525186  3.64528297]
 [-0.79415228  2.10495117]
 [-1.34052081  4.15711949]
 [-1.98197711  4.02243551]
 [-2.18773166  3.33352125]
 [-0.19745197  2.34634916]]

 

# 첫 번째 샘플을 극단적인 값으로 바꾼다.
features[0,0] = 10000
features[0,1] = 10000
print(features)

# 이상치 감지 객체 - 10%는 이상치로 간주
outlier_detector = EllipticEnvelope(contamination=0.1)

# 감지 객체를 훈련
outlier_detector.fit(features)

# 이상치를 예측 - (-1)이면 이상치, (1)이면 정상
outlier_detector.predict(features)
[[ 1.00000000e+04  1.00000000e+04]
 [-2.76017908e+00  5.55121358e+00]
 [-1.61734616e+00  4.98930508e+00]
 [-5.25790464e-01  3.30659860e+00]
 [ 8.52518583e-02  3.64528297e+00]
 [-7.94152277e-01  2.10495117e+00]
 [-1.34052081e+00  4.15711949e+00]
 [-1.98197711e+00  4.02243551e+00]
 [-2.18773166e+00  3.33352125e+00]
 [-1.97451969e-01  2.34634916e+00]]

array([-1,  1,  1,  1,  1,  1,  1,  1,  1,  1])
 
 
 
 
 

 
# 연습문제

log.txt
0.05MB

 

  • 파일은 공백으로 구분
  • 첫번째 데이터는 접속한 컴퓨터의 IP, 마지막 데이터는 트래픽

전체 트래픽의 합계 구하기
IP별 트래픽의 합계 구하기
 

더보기

#전체 트래픽의 합계 구하기

# 파일 불러오기
with open('./data/log.txt', 'r') as f:
    data = f.readlines()

# 공백 기준으로 데이터 분리하기
data_split = [x.strip().split() for x in data[1:]]

# 데이터프레임 생성, 열 이름 지정
df = pd.DataFrame(data_split, columns=['IP', 'x', 'x', 'datetime', 'um', 'GET/POST', 'path', 'port', 'server', 'traffic'])

# 필요없는 컬럼 제거
df = df.drop('x', axis=1)

# 트래픽 계산에 방해되는 놈들 제거
df['traffic'].replace('-', np.nan, inplace=True)
df['traffic'].replace('"-"', np.nan, inplace=True)
df = df.dropna()

# 트래픽 정수형 형변환
df.traffic = df.traffic.astype('int')

# 전체 트래픽 합계
nan.traffic.sum()

 

 

# IP별 트래픽의 합계 구하기

#IP별로 그룹화
grouped = df.groupby(['IP'])

ip_sum = grouped.sum()

 
=> Comprehension

더보기

Comprehension

r = []
for i in L:
    r.append(i**2)
print(r)

#map 
r = list(map(lambda x:x**2, L))
print(r)

#list comprehension
r = [i**2 for i in L]
print(r)

#필터링 : L의 모든 데이터를 i에 순차적으로 대입하고 조건문이 참인 경우만 list 생성
r = [i for i in L if i % 2 == 0]
print(r)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
[0, 2, 4, 6, 8]
li1 = [1, 2, 3]
li2 = [1, 2, 3]

#for 안에 for 사용
#먼저 나온 for가 바깥쪽, 뒤에 나온 for가 안쪽
r = [x * y for x in li1 for y in li2]
print(r)
[1, 2, 3, 2, 4, 6, 3, 6, 9]

 
 
 

 

 


 

7. 텍스트 데이터

7-1) 한글 형태소 분석

형태소 분석기 설치 - konlpy
- JDK(자바 개발 도구)를 설치
  JAVA_HOME 환경 변수에 JDK 경로를 설정
  PATH에 JDK의 bin 디렉토리 경로를 설정: Java 명령어를 콘솔에서 편리하게 사용하기 위해서
- Windows 의 경우에는 Visual C++ 재배포 패키지 설치
   Visual Studio 의 데스크톱 애플리케이션 개발 을 설치해도 됨
- Jpype-1 패키지 설치
   Python 3.11: pip install Jpype1-py3
   Python 3.12: pip install Jpype1
 
- 형태소 분석을 하는 이유

  • 우리나라는 공백 단위로 분할을 해서는 단어를 만들 수 없기 때문에 공백 단위로 쪼개고 어간 추출 필요
  • 데이터 분석을 하다보면 특정 품사의 데이터만 필요한 경우가 발생

 
- 한글 형태소 분석기 모듈

  • Hannanum
  • Kkma
  • Komoran
  • Mecab
  • Okt
  • Twitter (최근에는 사용을 권장하지 않음)
#형태소 분석할 문장
text = "태양계는 지금으로부터 약 46억년 전 거대한 분자구름의 일부분이 중력붕괴를 일으키면서 형성되었다."

#자연어 처리를 할 때 공백 단위로 토큰화 하는 것은 큰 의미가 없음
#공백 단위로 토큰화하는 것은 로그 분석을 할 때 입니다.
data = text.split(' ')
print(data)
['태양계는', '지금으로부터', '약', '46억년', '전', '거대한', '분자구름의', '일부분이', '중력붕괴를', '일으키면서', '형성되었다.']

 
 

from konlpy.tag import Kkma
kkma = Kkma()
#문장 분석 - 하나의 텍스트에 마침표가 있는 경우 분할
print(kkma.sentences(text))
#단어별 분석 - 명사만 추출
print(kkma.nouns(text))
#형태소 분석 - 품사를 같이 제공: 이 작업을 수행하는 것을 품사 태깅 - 자연어 처리의 한 분야
print(kkma.pos(text))
['태양계는 지금으로부터 약 46억 년 전 거대한 분자 구름의 일부분이 중력 붕괴를 일으키면서 형성되었다.']

['태양계', '지금', '46', '46억년', '억', '년', '전', '거대', '분자', '분자구름', '구름', '일부분', '중력', '중력붕괴', '붕괴', '형성']

[('태양계', 'NNG'), ('는', 'JX'), ('지금', 'NNG'), ('으로', 'JKM'), ('부터', 'JX'), ('약', 'MDN'), ('46', 'NR'), ('억', 'NR'), ('년', 'NNB'), ('전', 'NNG'), ('거대', 'NNG'), ('하', 'XSV'), ('ㄴ', 'ETD'), ('분자', 'NNG'), ('구름', 'NNG'), ('의', 'JKG'), ('일부분', 'NNG'), ('이', 'JKS'), ('중력', 'NNG'), ('붕괴', 'NNG'), ('를', 'JKO'), ('일으키', 'VV'), ('면서', 'ECE'), ('형성', 'NNG'), ('되', 'XSV'), ('었', 'EPT'), ('다', 'EFN'), ('.', 'SF')]

 
 

from konlpy.tag import Hannanum

hannanum = Hannanum()
print(hannanum.nouns(text))
#사용하는 단어 사전이 다르므로 앞의 결과와 다른 결과가 나올 수 있습니다.
print(hannanum.pos(text))

from konlpy.tag import Okt
['태양계', '지금', '약', '46억년', '전', '거대', '분자구름', '일부분', '중력붕괴', '형성']

[('태양계', 'N'), ('는', 'J'), ('지금', 'N'), ('으로부터', 'J'), ('약', 'N'), ('46억년', 'N'), ('전', 'N'), ('거대', 'N'), ('하', 'X'), ('ㄴ', 'E'), ('분자구름', 'N'), ('의', 'J'), ('일부분', 'N'), ('이', 'J'), ('중력붕괴', 'N'), ('를', 'J'), ('일으키', 'P'), ('면서', 'E'), ('형성', 'N'), ('되', 'X'), ('었다', 'E'), ('.', 'S')]

 
 
 

7-2) BoW (Bag of Word)

- 텍스트 데이터에서 특정 단어의 등장 횟수를 나타내는 특성을 만드는 작업
- 워드클라우드처럼 등장 횟수를 이용해서 시각화를 위해서 수행하고너 단어별 가중치를 적용하기 위해서 작업을 수행
- scikit-learn 의 CountVectorizer 클래스를 이용해서 인스턴스를 생성하고 fit_transform 함수에 문자열을 대입하면 BoW 특성 행렬을 만들어주고 to_array 함수를 호출하면 밀집 행렬로 결과를 리턴합니다.
 
- 인스턴스를 생성할 때 사용할 수 있는 옵션

  • ngram_range: 정수 형태의 튜플을 대입하면 ngram(단어가 연속으로 등장하는 경우 하나의 단어로 취급) 옵션 설정 가능
  • stop_words: 불용어 설정
  • vocabulary: 횟수를 계산할 단어의 list
  • max_df: 최대 개수
  • min_df: 최소 개수
  • max_feature: 상위 몇 개의 단어만 추출

 
- 인스턴스의 속성: get_feature_names()를 호출하면 각 특성에 연결된 단어를 확인
- 단어 별 빈도수 확인

#샘플 데이터 생성
import numpy as np
import pandas as pd

text_data = np.array(['I love Newziland newziland', 
                     'Sweden is best',
                     'Korea is not bad',
                     'Python Python Java Python R Java C'])

from sklearn.feature_extraction.text import CountVectorizer

#BoW 객체를 생성
#모든 문자열을 소문자로 변경해서 수행
#알파벳 한글자는 모두 제거
countVectorizer = CountVectorizer()
bag_of_words = countVectorizer.fit_transform(text_data)

#희소 행렬
print(bag_of_words)
#밀집 행렬
print(bag_of_words.toarray())
#각 열의 의미
print(countVectorizer.get_feature_names_out())
[[0]
 [0]
 [0]
 [0]]
['newziland, korea']

 
 
 

7-3) 단어 별 가중치 적용

tf-idf(term frequency - inverse document frequency)를 이용한 가중치 계산
가중치를 계산할 때는 tf * idf

  • idf = log((1 + 문서의개수)/1+등장한문서빈도) + 1
    • 여러 문서에서 등장하는 단어가 있다면 이 단어는 중요하지 않을 가능성이 높음
  • tf = 하나의 문서에서 등장한 빈도 수
    • 하나의 문서에 어떤 단어가 많이 등장할수록 중요한 단어

 

text_data = np.array(['I love Newziland newziland', 
                     'Sweden is best',
                     'Korea is not bad',
                     'Python Python Java Python R Java C'])

#단어별 가중치 확인
from sklearn.feature_extraction.text import TfidfVectorizer

tfidfVectorizer = TfidfVectorizer()
feature_matrix = tfidfVectorizer.fit_transform(text_data)
print(tfidfVectorizer.vocabulary_)
print(feature_matrix.toarray())

 

{'love': 5, 'newziland': 6, 'sweden': 9, 'is': 2, 'best': 1, 'korea': 4, 'not': 7, 'bad': 0, 'python': 8, 'java': 3}
[[0.         0.         0.         0.         0.         0.4472136
  0.89442719 0.         0.         0.        ]
 [0.         0.61761437 0.48693426 0.         0.         0.
  0.         0.         0.         0.61761437]
 [0.52547275 0.         0.41428875 0.         0.52547275 0.
  0.         0.52547275 0.         0.        ]
 [0.         0.         0.         0.5547002  0.         0.
  0.         0.         0.83205029 0.        ]]

 
 
 
 

8. 시계열 데이터

- 날짜와 시간을 이용해서 정렬된 데이터
- 일정한 패턴을 가진 데이터도 시계열 데이터로 간주
- 물리적 흔적: 의학, 청각학 또는 기상학 등에서 측정한 물리적 흔적
 
 

8-1) pandas의 시계열 자료형

- datetime64(TimeStamp)

  • 시계열 데이터를 저장하기 위한 자료형
  • tz 옵션: 시간대 설정 가능
  • 부등호 이용해서 크기 비교 가능
  • 뺄셈 수행해서 간격 확인 가능

- Period

  • 두 시점 사이의 일정한 기간을 나타내는 자료형
  • 데이터를 생성할 때 많이 이용

 
 

8-2) 자료형 변환

- 문자열 데이터를 datetime64로 변환

  • pandas.to_datetime 함수 이용
  • 날짜 형식의 문자열과 format 매개변수에 날짜 형식을 지정해서 생성하는데 format을 생략하면 유추
  • errors 매개변수에 ignore를 설정하면 에러가 발생했을 때 문자열을 그대로 리턴, coerce를 설정하면 문제가 발생할 때 NaT를 설정하며 raise를 설정하면 예외를 발생시켜서 중단

 

df = pd.read_csv("./data/stock-data.csv")
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20 entries, 0 to 19
Data columns (total 6 columns):
 #   Column  Non-Null Count  Dtype 
---  ------  --------------  ----- 
 0   Date    20 non-null     object
 1   Close   20 non-null     int64 
 2   Start   20 non-null     int64 
 3   High    20 non-null     int64 
 4   Low     20 non-null     int64 
 5   Volume  20 non-null     int64 
dtypes: int64(5), object(1)
memory usage: 1.1+ KB
  • 현재는 Date 컬럼이 object - 문자열
df['NewDate'] = pd.to_datetime(df['Date'])
df.info()
...
 0   Date     20 non-null     object 
...
  • 데이터를 출력해보면 Date 와 NewDate는 구분이 안되지만 자료형을 확인하면 다름
#데이터를 출력해보면 Date 와 NewDate는 구분이 안되지만 자료형을 확인하면 다름
print(df.head())
#연속된 날짜는 인덱스로 많이 사용
df.set_index('NewDate', inplace=True)
print(df.head())





 
 
 

8-3) Period

- to_period 함수를 이용해서 Timestamp 객체를 일정한 기간을 나타내는 Period 객체로 변환 가능

  • freq 옵션: 기준이 되는 기간 설정

dates = ['2017-01-01', '2018-06-03', '2019-11-02']
#문자열을 Timestamp로 변경
pddates = pd.to_datetime(dates)
print(pddates)
DatetimeIndex(['2017-01-01', '2018-06-03', '2019-11-02'], dtype='datetime64[ns]', freq=None)
#월로 변경 - 모든 날짜가 월 초 나 월 말로 변경
pr_months = pddates.to_period(freq='M')
print(pr_months)
PeriodIndex(['2017-01', '2018-06', '2019-11'], dtype='period[M]', freq='M')
#분기로 변경
pr_months = pddates.to_period(freq='Q')
print(pr_months)
PeriodIndex(['2017Q1', '2018Q2', '2019Q4'], dtype='period[Q-DEC]', freq='Q-DEC')

 
 
 

8-4) date_range

- 일정한 간격을 맞는 시계열 데이터 생성 함수
- 매개변수

  • start: 시작 날짜
  • end: 종료 날짜
  • periods: 생성할 데이터 개수
  • freq: 간격
  • tz: 시간대 설정('Asia/Seoul')

 
# 2024년 1월 1일 부터 일 단위로 30개의 데이터를 생성

ts_ms = pd.date_range(start='2024-01-01', end=None, periods=30, freq='D')
print(ts_ms)
DatetimeIndex(['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04',
               '2024-01-05', '2024-01-06', '2024-01-07', '2024-01-08',
               '2024-01-09', '2024-01-10', '2024-01-11', '2024-01-12',
               '2024-01-13', '2024-01-14', '2024-01-15', '2024-01-16',
               '2024-01-17', '2024-01-18', '2024-01-19', '2024-01-20',
               '2024-01-21', '2024-01-22', '2024-01-23', '2024-01-24',
               '2024-01-25', '2024-01-26', '2024-01-27', '2024-01-28',
               '2024-01-29', '2024-01-30'],
              dtype='datetime64[ns]', freq='D')

 
 
 

8-5) 날짜 데이터의 분리

- pandas 의 Series 의 dt.단위를 이용

  • year, month, day, hour, minute, weekday(월요일이 0) 를 이용

 
 

8-6) 시차 특성을 갖는 데이터 생성

- shift(간격, freq)를 이용하는데 이 함수는 pandas의 Series의 함수

df = pd.read_csv("./data/stock-data.csv")
df['NewDate'] = pd.to_datetime(df['Date'])
  • 년도만 추출해보면
print(df['NewDate'].dt.year)
0     2018
1     2018
2     2018
3     2018
...
Name: NewDate, dtype: int64
  • 이 경우는 간격이 하루 단위가 아니라서 앞의 데이터가 밀려들어오는 것처럼 보이지 않는데 전부 하루 간격이라면 밀려들어온다.
print(ts.shift(-1))
NewDate
2018-07-02    0.579450
2018-06-29   -0.774314
2018-06-28    0.650039
...

 
 
 

8-7) 결측치 처리

- 누락된 데이터의 기간을 삭제하거나
대체(Imputation)법: 관측에 기반에서 누락된 데이터를 채워넣는 방법

  • 누락된 데이터의 이전이나 이후값으로 채워넣는 방식
  • 이동 평균으로 데이터 대체
  • 보간: 인접한 데이터를 기반으로 누락된 데이터를 추정해서 대입하는 방법
    이전과 이후 데이터를 가지고 예측을 해서 설정 - 선형으로 예측
    실제 작업을 할 때는 데이터의 분포를 확인을 해서 선형인지 비선형인지 결정
    • DataFrame.interpolate 함수 이용
    • quadratic: 비선형으로 채움 (값 기반), 기본은 선형으로 채움
    • time: 시간 기반으로 채움
    • limit_direction: 보간 방향 설정

 
# 결측치 채우기

time_index = pd.date_range("01/01/2024", periods=5, freq='M')
dataframe = pd.DataFrame(index = time_index)

#결측치를 포함하는 데이터를 생성
dataframe['Sales'] = [1, 4, 9, np.nan, 25 ]
dataframe
#결측치 앞값으로 채우기
dataframe.ffill()
#결측치 뒤값으로 채우기
dataframe.bfill()

#선형으로 예측
dataframe.interpolate()

#비선형으로 예측
dataframe.interpolate(method="quadratic")

  • 중간에 날짜가 비어 있는 경우에는 앞 뒤의 날짜를 확인해서 
    다음 날짜가 맞는지 아닌지 확인해서 값을 기반으로 할지 날짜를 기반으로 할지 결정
from datetime import datetime

dates = [datetime(2024, 1, 1),datetime(2024, 1, 2), datetime(2024, 1, 3),
        datetime(2024, 1, 6), datetime(2024, 1, 7)]
        
dataframe = pd.DataFrame(index = dates)
dataframe['Sales'] = [1, 2, 3, np.nan, 7]

dataframe.interpolate(method='time')

 
 

8-9) resampling

- 시계열의 빈도를 변환하는 것
다운 샘플링(데이터의 빈도를 줄이는 것 - 언더 샘플링)

  • 원본 데이터의 시간 단위가 실용적이지 않은 경우: 어떤 것을 너무 자주 측정하는 경우
    게시판의 글을 분석하기 위해서 로그 데이터를 읽을 때 시간 단위가 초까지 측정된 경우가 있다면 다운 샘플링을 고려
  • 계절 주기의 특정 부분에 집중해야 하는 경우: 하나의 특정 계절에만 초점을 맞춘 분석을 해야 하는 경우
  • 한 데이터를 낮은 빈도로 측정된 다른 데이터와 맞춰 주기 위해서

업 샘플링(데이터의 빈도를 늘리는 것 - 오버 샘플링)

  • 시계열이 불규칙적인 경우
  • 입력이 서로 다른 빈도로 샘플링된 상황에서 주기가 긴 데이터를 주기가 짧은 데이터에 맞추기 위해서

- resample(freq, how, fill_method, closed, label, kind)

  • freq: 주기를 설정하는 것으로 이전에 사용하던 방식도 가능하고 '문자열단위', Hour, Minute, Second 등을 이용해서 설정하는 것도 가능(Second(30))
  • how: 채우는 값으로 기본은 mean 이고 first, last, median, max, min 설정 가능
  • fill_method: 업 샘플링을 할 때 데이터를 채우는 방법으로 기본은 None리고 ffill 이나 bfill 설정 가능
  • limit: 채우는 개수
  • closed: 다운 샘플링을 할 때 왼쪽 과 오른쪽 어떤 값을 사용할 지 여부를 설정
  • label: 다운 샘플링을 할 때 왼쪽 과 오른쪽 어떤 인덱스를 사용할 지 여부를 설정
range = pd.date_range('2024-02-01', '2024-02-02', freq='2min')

#2분마다 20개 이므로 총 시간은 40분
df = pd.DataFrame(index=range)[:20]
df['price'] = np.random.randint(low=10, high=100, size=20)
df['amount'] = np.random.randint(low=1, high=5, size=20)
df
#다운 샘플링 - 시간 간격을 10분으로 조정: 4개의 데이터로 변경
print(df.price.resample('10T').first())
2024-02-01 00:00:00    71
2024-02-01 00:10:00    41
2024-02-01 00:20:00    69
2024-02-01 00:30:00    15
Freq: 10T, Name: price, dtype: int32
#시계열 데이터의 경우 시간별 집계를 수행하는 방법
print(df.price.resample('10T').sum())
2024-02-01 00:00:00    360
2024-02-01 00:10:00    145
2024-02-01 00:20:00    267
2024-02-01 00:30:00    296
Freq: 10T, Name: price, dtype: int32

 
 

8-10) 이동 시간 윈도우

- 일반적인 기술 통계는 전체 데이터를 기반으로 계산하는데, 일정한 개수의 데이터를 가지고 움직이면서 기술 통계를 계산하는 것을 의미


 
- 단순이동평균

  • 현재 위치에서 일정한 개수를 설정하고 그 이전 데이터와의 평균 계산
  • DataFrame.rolling 함수 이용
  • window: 데이터 개수 설정

- 지수이동평균

  • 최근의 데이터에 가중치를 두는 방식
  • 알파 = 1- span(기간)
  • 현재데이터 + (1-알파)이전데이터 + (1-알파)제곱 * 이전에 이전 데이터... / 1 + (1-알파) + (1-알파)제곱..
  • Dataframe.ewm 함수 이용
  • span 옵션: span 설정
#2024년 1월 1일 부터 월 단위로 5개의 데이터를 생성
time_index = pd.date_range("01/01/2024", periods=5, freq='M')

#시계열 DataFrame을 생성
dataframe = pd.DataFrame(index=time_index)

dataframe['Stock_Price'] = [1, 2, 3, 4, 5]
  • pandas에서는 인덱스가 시간인 경우 또는 일정한 패턴인 경우를 시계열로 판정
#단순 이동 평균 계산
print(dataframe.rolling(window=2).mean())

#지수 이동 평균
print(dataframe.ewm(span=2).mean())
            Stock_Price
2024-01-31          NaN
2024-02-29          1.5
2024-03-31          2.5
2024-04-30          3.5
2024-05-31          4.5

            Stock_Price
2024-01-31     1.000000
2024-02-29     1.750000
2024-03-31     2.615385
2024-04-30     3.550000
2024-05-31     4.520661

 
 

8-11) 평활

- 시계열 자료의 체계적인 움직임을 찾아내기 위해서 과거 자료의 불규칙적인 변동 제거
- 불규칙 변동의 제거를 이동평균을 이용해서 분석
- 이동평균은 결국 과거 자료의 추세를 전 기간 또는 특정 기간 별로 평균을 계산해서 미래 자료를 예측한다는 것
- 과거 자료들의 가중치를 동일하게 적용하면 단순이동평균법, 각 자료의 가중치를 다르게 적용하면 지수평활법
 
 

8-12) 계절성 데이터

- 데이터의 계절성은 특정 행동의 빈도가 안정적으로 반복해서 나타나는 것으로 동시에 여러 빈도가 다르게 발생하는 것
- 시계열 데이터 중에는 일일, 주간, 연간의 계절적인 변화를 갖는 경향이 있는 데이터가 있음
- 그래프를 이용해서 대략적인 계절성 요인을 확인
 
계절성 시계열

  • 일련의 동작이 정해진 기간동안 반복되는 시계열
  • 주기가 일정

순환성 시계열

  • 반복적인 동작이 있기는 하지만 기간이 일정하지않은 경우
  • 주식 시작 이나 화산의 주기가 대표적인 순환성 시계열

 
- 계절성을 파악할 때는 산점도(scatter) 보다는 선(plot) 그래프를 이용하는 것이 좋다.
 
 
 
 
 

'Python' 카테고리의 다른 글

[Python] 이미지 데이터 다루기  (0) 2024.02.21
[Python] 확률  (0) 2024.02.21
[Python] 데이터 전처리  (0) 2024.02.15
[Python] 데이터 시각화  (0) 2024.02.14
[Python] 데이터 탐색  (0) 2024.02.14