KNN을 생성할 때 weights 매개변수에 distance를 설정하면 거리에 따른 가중치 적용
- KNN은 게으른 알고리즘
데이터를 예측하기 위해서 모든 데이터가 메모리에 상주해야 하는 알고리즘
- 이해하기 쉬운 화이트 박스 알고리즘이고 별도의 훈련을 거치지 않는 간단한 알고리즘이라서 데이터 전처리(결측치 대체나 새로운 피처 추가)에 많이 이용
결측치나 새로운 피처 추가: 지역적 정보를 이용하기 때문에 다중공선성 문제는 고려할 필요가 없음. 전체 데이터를 사용하는게 아니라서 실제 예측하는 데는 사용하지 않음.
화이트 박스 알고리즘: 내가 보고 이해하기 쉬운 알고리즘, 대표적으로 KNN과 트리 / 블랙박스 알고리즘: 딥러닝
- API는 sklearn.neighbors.KNeighborsRegressor
.
- KNN을 사용할 때는 스케일링 반드시 수행
3) Decision Tree
- 트리 기반 - 불순도 지표
지니계수 (오분류 오차)
1 - (예측한 개수 / 전체 개수 - (예측한 개수 / 전체 개수) ... 한쪽으로만 분류가 되면 0: 가장 좋은 형태 가장 나쁜 값이 0.5
엔트로피
- API: DecisionTreeRegressor - RandomForest나 GBM, XGBoost, LightGBM의 기반이 되는 모델 - 다양한 파라미터가 존재하기 때문에 하이퍼 파라미터 튜닝 필수 - 스케일링 과정은 필요 없음
m = 200
X = np.random.rand(m, 1)
y = 4 * (X - 0.5) ** 2 + np.random.randn(m, 1)/10
from sklearn.tree import DecisionTreeRegressor
tree_reg = DecisionTreeRegressor(max_depth=2, random_state=42)
tree_reg.fit(X, y)
#시각화
from graphviz import Source
from sklearn.tree import export_graphviz
#decision_tree_dot 이라는 파일로 저장
export_graphviz(tree_reg, out_file='decision_tree.dot',
feature_names=['X'], class_names=['y'],
rounded=True, filled=True)
#파일을 출력
with open('decision_tree.dot') as f:
dot_graph = f.read()
src = Source(dot_graph)
src
- 분류에서는 가장 가까운 데이터와의 거리가 가장 먼 결정 경계를 만들어가는 방식 - 회귀에서는 제한된 마진 오류 안에서 가능한 많은 샘플이 들어가도록 학습
마진 오류의 폭은 epsilion 파라미터로 설정
#시드 고정 후 가우시안 분포를 따르는 데이터셋을 만듭니다.
np.random.seed(42)
m = 50
X = 2 * np.random.rand(m,1)
y = (4 + 3 * X + np.random.randn(m,1)).ravel()
X_train = X[:40]
X_test = X[40:]
y_train = y[:40]
y_test = y[40:]
# 모델 생성 과 훈련
from sklearn.svm import LinearSVR
epsilons = [0.1, 0.5, 1.0, 1.5, 2.0]
for epsilon in epsilons:
svm_reg = LinearSVR(epsilon=1.5, random_state=42)
svm_reg.fit(X_train,y_train)
# svm_reg = 마진이 큰 모형(epsilon=1.5)
y_pred = svm_reg.predict(X_test)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
print(epsilon, ":", rmse)
- 데이터를 가지고 어떤 결정을 해야 하는 문제를 접하는 경우, 결정해야 하는 Target이 이미 알려진 범주형일 때 이미 알려져있으므로 지도학습 - 분류의 유형은 이진분류(2가지 중 하나)와 다중분류(3가지 이상 중 하나)로 분류하기도 하고, 선형분류와 비선형분류와 나누기도 함 - sklearn의 분류기들은 예측하기 위한 함수로 2가지 사용
predict: 분류 결과
predict_proba: 각 클래스에 대한 확률(확률이 가장 높은 결과가 predict의 결과
1-1) 분류 알고리즘
- 판별 분석 - 랜덤 분류 - KNN - Support Vector Machine - 나이브 베이즈 - 로지스틱 회귀 - 결정 트리 - 최소 근접 - 신경망 - 앙상블
2. MNIST 데이터
- 0부터 9까지의 숫자 이미지 70,000개로 이루어진 데이터, 레이블이 존재 - sklearn을 이용해서 다운로드 받으면 data 속성에 피처가 존재하고 target 속성으로 레이블을 제공
1-1) MNIST 데이터 가져오기
from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1, as_frame=False)
X, y = mnist['data'], mnist['target']
print(X.shape)
print(y.shape)
(70000, 784) (70000,)
이미지가 7만개 존재, 각 이미지에는 784개의 특성 존재
특성은 픽셀의 개수 (28x28)
#이미지 출력
#이미지 데이터 1개 가져오기
some_digit = X[0]
#이미지를 다시 2차원으로 변경
some_digit_image = some_digit.reshape(28, 28)
#출력
plt.imshow(some_digit_image, cmap=mpl.cm.binary)
plt.axis('off')
plt.show()
1-2) 훈련 데이터와 테스트 데이터 분리
print(y.dtype)
object
정수로 형변환
#레이블 자료형을 정수로 변경
y = y.astype(np.uint8)
uint8
3. 이진 분류
3-1) 이진 분류를 위한 데이터 준비
- 타겟이 True 또는 False - 이진 분류는 맞다 틀리다를 구분하는 것 - 레이블의 값을 True와 False로 생성
- 확률적 경사 하강법 (Stochastic Gradient Descent-SGD) 사용하는 분류 모델 클래스
타겟을 찾아갈 때 한번에 찾아가지 않고 학습률 값을 이용해서 작게 분할해서 타겟을 찾아가는 방식
- 매우 큰 데이터 세트를 효율적으로 처리 - 한번에 하나씩 훈련 샘플을 독립적으로 처리하기 때문에 온라인 학습에 적합
온라인 학습: 실시간으로 데이터가 주어지는 형태의 학습 ↔ 배치 학습
- 하이퍼 파라미터
max_iter: 최대 수행 횟수
tol: 중지기준. 이 값보다 손실의 값이 작으면 훈련 중지 (어느정도까지 틀릴 것인지?)
random_state
# SGDClassifier를 이용한 훈련
#모델 생성 및 학습
from sklearn.linear_model import SGDClassifier
#max_iter는 작업 횟수이고 tol은 중지 기준으로 loss가 tol 보다 작으면 훈련 중지 random_state는 seed 값
sgd_clf = SGDClassifier(max_iter=1000, tol=1e-3, random_state=42)
#훈련
sgd_clf.fit(X_train, y_train_5)
#첫 번째 데이터를 이용해 이미지 감지\
sgd_clf.predict([some_digit])
array([ True])
3-3) 분류의 평가 지표
- 오차 행렬: 정답이 True와 False로 나누어져 있고 분류 결과도 True와 False로 나누어서 표로 만든 형태
from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix
#예측값 생성
y_train_pred = cross_val_predict(sgd_clf, X_train, y_train_5, cv=3)
#오차행렬 생성
confusion_matrix(y_train_5, y_train_pred)
array([[53892, 687], [ 1891, 3530]], dtype=int64)
53892는 5가 아닌 것으로 제대로 분류
687는 5라고 잘못 분류한 것
1891은 5 이미지가 맞는데 5가 아니라고 분류한 개수
3550은 5라고 맞게 분류한 데이터
- Accuracy (정확도)
True를 True로, False를 False로 예측한 비율
옳은 경우를 고려하는 지표
정확도는 가장 직관적으로 모델의 성능을 평가할 수 있는 지표
정확도를 사용할 때는 타겟의 비율을 확인해야 함
타겟의 비율이 어느정도 고르게 분포되어 있다면 고려할 수 있는 평가지표이지만, 타겟의 비율이 고르지 않다면 다른지표를 사용해야할 수 있다.
어느 곳의 날씨가 99일은 맑고 1일은 비가 온다면, 무조건 맑다고 예측할 때 정확도가 99%가 된다.
- Recall (재현율)
True를 True라고 예측한 비율
실제 날씨가 맑은데 맑다고 예측한 비율
sensitivity(민감도) 또는 hit rate 라고도 한다.
- Precision (정밀도)
True라고 예측한 것 중에서 실제 True인 것의 비율
날씨가 맑다고 예측했는데 실제로 맑은 비율
정밀도와 재현율은 같이 사용하는 경우가 많다.
- F1 Score
Precision과 Recall의 조화 평균
Target의 비율이 불균형 구조일 때 모델의 성능 지표로 많이 이용
- sklearn.metrics에서 지표계산 API 제공
대입해야 하는 데이터: 실제 값과 예측한 값의 배열
- 보통의 경우 F1 Score가 좋으면 성능이 좋은 분류기라고 하지만, 상황에 따라서는 다른 지표 사용 고려
감시 카메라를 이용해서 도둑을 잡아내는 분류기를 훈련시킨다고 하면 정확도가 낮더라도 재현율이 좋은 것이 좋은 분류기가 될 수 있다.
어린 아이에게 동영상을 추천하는 시스템의 경우는 어린 아이에게 성인이 볼 영상을 추천하면 안됨 이런 경우에는 안전한 것들만 추천해줘야 하므로 정밀도가 높은 것이 좋은 분류기가 될 수 있다.
일반적으로 정밀도를 올리면 재현율이 떨어지고, 재현율을 높이면 정밀도가 떨어진다. 이를 정밀도와 재현율의 트레이드 오프라고 한다.
#예측 - 양성 클래스의 확률
y_scores_forest = y_probas_forest[:, 1]
print(y_scores_forest[1])
0.01
y_scores_forest를 가지고 평가 지표를 구해야 함
print(roc_auc_score(y_train_5, y_scores_forest))
0.9983436731328145
RandomForest를 이용한 모델의 ROC 값이 이전 모델보다 높게 나옴
3-5) 교차 검증
- 여러 개의 fold로 나누어서 모델을 생성하고 훈련한 후 평가지표를 얻어서 평균을 구하기도 함
#교차 검증
from sklearn.model_selection import cross_val_score
#3번 수행해서 각 검증의 정확도 확인
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([0.95, 0.96, 0.96])
#교차 검증
from sklearn.model_selection import cross_val_score
#3번 수행해서 각 검증의 정확도 확인
cross_val_score(sgd_clf, X_train, y_train_5, cv=3, scoring="accuracy")
array([0.95, 0.96, 0.96])
4. 다중 분류
- 둘 이상의 클래스를 구별하는 작업 - SGD, RandomForest, Naive Bayes 알고리즘은 여러개의 범주를 직접 처리할 수 있지만 - Logistic Regression, Support Vector Machine 같은 알고리즘은 이진분류만 가능
이진 분류기를 여러개 결합하면 다중 분류가 가능
4-1) 이진 분류 알고리즘을 이용한 다중 분류
- OvR(One-versus-the-Rest)
범주의 개수만큼 이진 분류기를 만들어서 가장 높은 결정 점수를 가진 분류기가 분류한 것을 선택
숫자분류기라면 숫자는 0부터 9까지 총 10개가 있으므로 분류기를 10개 만든다.
각 숫자에 해당하는 확률을 구해서 그 확률 중에서 가장 높은 것을 선택한다.
- OvO(One-versus-One)
두가지로 분류하는 이진분류기를 모두 (범주개수 * 범주개수-1) / 2 생성한 후 가장 많이 양성으로 분류된 범주를 선택
0/1, 0/2, 0/3, ... 8/9 구분까지 생성
그 중에서 가장 많이 분류된 클래스 선택
sklearn에서는 다중 클래스 분류에 이진 분류 알고리즘을 사용하는 분류기를 선택하면 알아서 자동으로 알고리즘 선택
강제로 알고리즘을 선택할 수도 있다.
- SVM(Support Vector Machine)을 이용한 다중 분류
SVM은 이진 분류기: 기본적으로 다중 분류를 하지 못함
이 분류기를 이용해서 다중 분류를 위한 데이터를 학습시키면 스스로 알고리즘을 선택해서 다중분류 결과 예측 가능
### 이진 분류기를 이용한 다중 분류
from sklearn.svm import SVC
#모델 생성
svm_clf = SVC(random_state=42)
#훈련
svm_clf.fit(X_train[:1000], y_train[:1000])
#예측
svm_clf.predict([some_digit])
- 분류기가 샘플 별로 여러개의 결과를 출력하는 경우 - 이미지에서 객체를 탐지하는 경우 하나의 객체만 탐지하는 것이 아니고 여러개의 객체를 탐지해야 하는 경우나 얼굴에서 얼굴의 각 부위를 탐지하는 경우 등
#다중 레이블 생성
y_train_large = (y_train >= 7)
y_train_odd = (y_train % 2 == 1)
y_multilabel = np.c_[y_train_large, y_train_odd]
print(y_multilabel[0])
[False True]
6. 판별 분석 (Discriminant Analysis)
- 2개 이상의 모집단에서 추출된 표본들이 지니고 있는 정보를 이용해서 이 표본들이 어느 모집단에서 추출된 것인지를 결정해줄 수 있는 기준을 찾는 분석
은행에서 부동산 담보 대출할 때 이 고객이 대출을 상환할 것인가 아니면 상환하지 않을 것인가를 판별하는 경우 은행에서는 기존의 대출을 상환하지 않은 고객들의 특성과 대출을 상환한 고객들의 특성을 별도로 만들어두고 이 고객의 특성이 어느쪽에 더 가까운건지 파악할 수 있다.
- 초창기에는 LDA(Linear Discriminant Analysis - 선형 판별 분석)를 많이 사용
최근에는 신경망 등이 등장하면서 사용빈도가 낮아졌다.
아직도 다른 머신러닝 알고리즘의 기반 알고리즘으로 사용됨
- 전체 표본의 크기가 독립 변수의 개수보다 3배 이상 많아야 함
6-1) LDA
- 내부 제곱합에 대한 사이 제곱합의 비율을 최대화하는 것이 목적
그룹 내부의 제곱합은 적게, 그룹과 그룹 사이의 제곱합은 크게 만드는 알고리즘
클러스터링의 기반이 되는 방법
- sklearn.discriminant.LinearDicriminantAnalysis 클래스 이용
from sklearn.neighbors import KNeighborsClassifier
knn_clf = KNeighborsClassifier()
#훈련할 때 여러개의 label인 묶인 타겟을 설정하면 다중 레이블 분류
knn_clf.fit(X_train, y_multilabel)
knn_clf.predict([some_digit])
array([[False, False]])
some_digit = X[0]
knn_clf.predict([some_digit])
array([[False, True]])
7. DummyClassifier
- 랜덤하게 분류 - 타겟의 비율을 보고 그대로 랜덤하게 예측하는 방식
8. KNN(K-Nearest Neighbor) 최근접 이웃
- 특징들이 가장 유사한 K개의 데이터를 찾아서 K개의 데이터를 가지고 다수결로 클래스를 선택해서 할당 - 회귀에 사용할 때는 그 값의 평균을 구해서 예측
8-1) 특징
- 간단: 전처리 과정에서 결측치를 대체하는 데 사용하기도 함 - 모델을 피팅하는 과정이 없음 - 모든 예측 변수들은 수치형이어야 함
거리를 계산해야 하기 때문
이 경우 범주형 데이터는 특별한 경우가 아니면 원핫 인코딩을 수행해야 함
- 게으른 알고리즘이라고 하는데 훈련 데이터 세트를 메모리에 전부 저장하고 거리 계산을 수행
온라인 처리가 안됨
8-2) API
- sklearn.neighbors.KNeighborsClassifier 클래스
인스턴스를 생성할 때 n_neighbors를 이용해서 이웃의 개수 설정
metric을 이용해서 거리 계산 알고리즘을 설정하는데 1이면 맨하튼 거리, 2이면 유클리드 거리, 설정하지 않으면 mincowski 거리
- 데이터: loan_200.csv
payment_inc_ratio: 소득에 따른 대출 상환 비율
dti: 소득에 대한 부채 비율
outcome: 상환 여부
# KNeighborsClassifier를 이용해서 payment_inc_ratio와 dit에 따른 outcome 분류
#데이터 분리
#테스트를 위한 데이터 1개 추출
newloan = loan200.loc[0:0, ['payment_inc_ratio', 'dti']]
#피처 추출
X = loan200.loc[1:, ['payment_inc_ratio', 'dti']]
#타겟 추출
y = loan200.loc[1:, 'outcome']
from sklearn.neighbors import KNeighborsClassifier
# 인스턴스 생성 - 필수적인 파라미터는 n_neighbors
# 모델 생성
knn = KNeighborsClassifier(n_neighbors=21)
#훈련
knn.fit(X, y)
#예측
knn.predict(newloan)
array(['paid off'], dtype=object)
#클래스별 예측 확률
print(knn.predict_proba(newloan))
[[0.476 0.524]]
predict 함수는 거의 모든 분류 모델이 소유하고 있지만
predict_proba 함수는 없을 수 있다.
8-3) 거리 지표
- 유클리드 거리
서로의 차이에 대한 제곱합을 구한 뒤 그 값의 제곱근을 취하는 방식
유클리드 거리를 사용할 때는 수치형 데이터의 범위를 확인
- 맨하튼 거리
서로의 차이에 대한 절대값을 구한 뒤 모두 더한 거리
- 마할라노비스 거리
두 변수 간의 상관관계를 사용
유클리드 거리나 맨하튼 거리는 상관성을 고려하지 않기 때문에 상관관계가 있는 피처들의 거리를 크게 반영
주성분간의 유클리드 거리를 의미
많은 계산이 필요하고 복잡성이 증가
피처들의 상관관계가 높을 때 사용
- 민코프스키 거리
1차원 공간에서는 맨하튼 거리를 사용하고 2차원 공간에서는 유클리드 거리를 사용
8-4) 표준화
- 거리의 개념을 이용하므로 스케일링이나 표준화를 수행을 해주어야 함 - 표준화를 했을 때와 그렇지 않을 때 이웃이 달라지게 됨
10000 1 1 1 1 20000 1 1 1 1 11000 100 100 100 100 제곱을 하니까 단위 자체가 다름
8-5) 피처 엔지니어링
- KNN은 구현이 간단하고 직관적 - 성능은 다른 분류 알고리즘에 비해서 그렇게 우수한 편은 아니다. - 다른분류 방법들의 특정 단계에 사용할 수 있게 모델에 지역적 정보를 추가하기 위해서 사용하는 경우가 많음
새로운 피처를 만드는 데 많이 사용
기존의 피처를 이용해서 새로운 피처를 만드는 것이라서 다중 공선성 문제를 야기할 것 같은데 KNN으로 만들어진 피처는 다중 공선성 문제가 거의 발생하지 않는다.
KNN은 피처 전체를 이용하는 것이 주위 데이터 몇개만 이용하기 때문에 매우 지엽적인 정보 이용
- loan_data.csv.gz 데이터에서 새로운 피처 추가
csv 파일의 크기가 너무 커지는 경우 파일을 gz 타입으로 압축해서 용량 줄일 수 있음
pandas는 gz로 압축된 csv 파일의 내용을 읽을 수 있다.
python은 zip이나 tar로 압축된 파일을 압축을 해제할 수 있고 여러 파일을 압축할 수 있는 API를 제공
# outcome 컬럼을 타겟으로 하고 나머지를 피처 특성으로 만들고, 피처 특성으로 타겟을 예측하도록 KNN으로 학습한 후 그 때의 예측 확률을 새로운 피처로 추가
- 일반적인 머신러닝 기법 중에서 매우 강력하고 선형 또는 비선형, 회귀, 이상치 탐색에도 사용하는 머신러닝 모델 - 초창기에는 가장 인기있는 모델에 속함, 현재는 아님 - 복잡한 분류 문제에 적합했고, 작거나 중간 크기의 데이터 세트에 적합 - 모든 속성을 활용하는 전역적 분류 모형 - 군집별로 초평면을 만드는데 이 초평면은 다른 초평면의 가장 가까운 자료까지의 거리가 가장 크게 만드는 것
11-1) 특징
- 장점
에러율이 낮음
결과를 해석하기 용이
- 단점
파라미터 및 커널 선택에 민감
이진분류만 가능
특성의 스케일에 굉장히 민감. 반드시 스케일링 수행해야 함
11-2) 하드마진과 소프트마진
- 하드마진
모든 데이터가 정확하게 올바르게 분류된 것
하드마진이 성립되려면 데이터가 선형적으로 구분될 수 있어야 하고, 이상치에 민감
- 소프트마진
이상치로 인해 발생하는 문제를 피하기 위해서 좀더 유연한 모델을 생성
어느정도의 오류를 감안하고 결정경계를 만들어내는 방식
SVM에서 매개변수 C로 마진 오류 설정
마진 오류는 적은 것이 좋지만 너무 적게 설정하면 일반화가 잘 안됨 *일반화: 지금껏 보지 못한 데이터에 올바르게 적용되는 성질
11-3) 선형 SVM 모델을 이용한 붓꽃 분류
- Pipeline을 사용
피처 스케일링과 모델 훈련을 하나의 파이프라인으로 묶어서 수행
#피처 스케일링
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
#모델 생성해서 훈련
from sklearn.svm import LinearSVC
svc_clf = LinearSVC(C=1, random_state=42)
svc_clf.fit(X, y)
#예측
svc_clf.predict([[5.5, 1.7]])
array([1.])
- 모든 컬럼에 스케일링하는 것이 동일하다면 파이프라인으로 묶는 것이 가능
#피처 스케일링
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
#스케일러와 분류기를 하나로 묶어서 사용
svm_clf = Pipeline([
('scaler', StandardScaler()),
('linear_SVC', LinearSVC(C=1, random_state=42))
])
svm_clf.fit(X, y)
svc_clf.predict([[5.5, 1.7]])
array([1.])
11-4) 비선형SVM
- 선형 SVM 분류기가 효율적이지만 선형적으로 분류할 수 없는 데이터가 많음 - 선형 SVM(커널)의 문제점중 하나가 XOR을 구분할 수 없는 점 => 다항식을 이용하여 해결 - 다항식을 이용하는 SVM을 생성하기 위해서는 PolynomialFeatures 변환기를 추가하면 됨
#데이터 생성
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=100, noise=0.14, random_state=42)
#데이터 시각화
def plot_dataset(X, y, axes):
plt.plot(X[:, 0][y==0], X[:, 1][y==0], "bs")
plt.plot(X[:, 0][y==1], X[:, 1][y==1], "g^")
plt.axis(axes)
plt.grid(True, which='both')
plt.xlabel("x_1", fontsize=20)
plt.ylabel("x_2", fontsize=20, rotation=0)
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
숫자가 높아지면 잘 분류할 가능성은 높아지지만 학습할 피처의 개수가 늘어나고, 이로인해 훈련 속도도 느려지고 학습 속도가 느려진다.
OVerfitting 될 가능성이 높아진다.
11-5) 다항식 커널
- 다항식 특성을 추가하면 성능이 좋아졌는데 낮은 차수의 다항식은 매우 복잡한 데이터 세트에는 잘 맞지 않을 것이고 높은 차수의 다항식은 굉장히 많은 특성을 추가하므로 모델을 느리게 함 - Kernel, Trick이라는 수학적 기교를 이용해서 실제로는 특성을 추가하지 않으면서 다항식 특성을 추가한 것과 같은 효과를 얻는 방법 사용 - 이 기법을 사용할 때는 SVC 클래스에서 매개변수 kernel에 poly 설정, 매개변수 degree에 적용하고자 하는 차수를 설정, 매개변수 coef()에 정수값을 설정. 낮은 차수와 높은 차수 중에서 어떤 다항식에 영향을 받을지 설정
#다항식 커널은 실제로 다항식을 추가하는 것이 아니라 추가하는 것과 같은 효과를 냄
from sklearn.svm import SVC
#스케일러와 분류기를 하나로 묶어서 사용
poly_kernel_svm_clf = Pipeline([
('scaler', StandardScaler()),
('svm_SVC', SVC(kernel='poly', degree=10, coef0=1, C=1, random_state=42))
])
poly_kernel_svm_clf.fit(X, y)
plot_predictions(poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
plt.show()
11-6) 유사도 특성을 추가하는 가우시안 RBF 커널
- 몇 개의 랜드마크를 추가한 후 이 랜드마크와의 유사도를 측정해서 그 값을 특성으로 추가 - SVC로 생성, 매개변수 kernel에 RBF, gamma 설정
gamma를 너무 적은 수로 설정하면 선형 분류에 가까워지고 높게 설정하면 다항식을 이용하는 분류에 가까워짐
하나의 영역에 2가지 클래스가 배정된 경우 하나의 영역에 16개의 데이터가 배정었고 1번이 10개, 2번이 6개가 배정되면 10/16 * log2(10/16) - 6/16 * log2(6/16)
앞뒤 비율이 비슷해질수록 오분류 가능성이 커진다.
한쪽으로만 데이터가 배정되면 앞의 값이 0이 되고 뒤의 값도 0이 되어 결국 0이 됨. 0에 가까운 값이 좋은 값이다.
12-4) 규제 매개변수
- 결정 트리는 훈련 데이터에 대한 제약사항이 거의 없음 - 규제를 하지 않으면 트리가 훈련 데이터에 아주 가깝게 맞추려고 해서 과대적합이 발생 - 결정 트리는 모델의 하이퍼 파라미터가 없는 것이 아니라 훈련되기 전에 파라미터 수가 결정되지 않기 때문에 모델 구조가 데이터에 맞춰져서 고정되지 않고 자유로움
하이퍼 파라미터의 기본값이 대부분 Max 값이거나 None이다.
이전의 다른 모델은 하이퍼 파라미터의 값이 설정되어 있음
하이퍼 파라미터의 값이 기본값을 가지는 경우는 과대 적합될 가능성은 낮지만 과소 적합될 가능성이 있고 하이퍼 파라미터의 값이 기본값을 가지지 않고 MAX 값을 가지는 경우 과대 적합될 가능성은 높지만 과소 적합될 가능성은 낮다.
비선형 커널에서 degree라는 하이퍼 파라미터가 있다. degree가 높으면 데이터를 정확하게 분류할 확률이 높다. degree를 -1로 설정할 수 있다고 하면 정확하게 분류할 가능성은 높아지지만
- 결정 트리의 형태를 제한하는 매개변수
max_depth: 최대 깊이, 특성의 개수를 확인해서 적절하게 설정
min_samples_split: 분할을 할 때 가져야 하는 데이터의 최소 개수
min_samples_leaf: 하나의 터미널이 가져야 하는 최소 개수
min_weight_fraction_leaf: 개수가 아니라 비율로 설정
max_leaf_nodes: 터미널의 개수
max_features: 분할에 사용할 특성의 개수
min으로 시작하는 매개변수를 증가시키거나 max로 시작하는 매개변수를 감소시키면 규제가 커짐
규제가 커지면 오차가 발생할 확률은 높아진다.
12-5) 트리 모델의 단점
- 훈련 데이터의 작은 변화에도 매우 민감
조건문을 사용하는 방식이기 때문에 하나의 데이터가 추가되었는데 이 데이터가 조건에 맞지 않으면 트리를 처음부터 다시 만들어야 할 수도 있다.
트리 모델을 훈련시켜서 다른트리 모델과 비교하고자 할 때는 반드시 random_state를 고정시켜야 한다.
12-6) 피처의 중요도 확인
- 트리 모델에는 feature_importance
#피처의 중요도 확인
print(tree_clf.feature_importances_)
print(iris.feature_names[2:])
from sklearn.datasets import load_iris
#붓꽃 데이터 가져오기
iris = load_iris()
#피처 선택
#iris.data - 열의 개수가 4개인 2차원 배열
#iris.feature_names - 피처 이름 확인
#print(iris.feature_names)
#피처 선택
X = iris.data
#타켓 선택
y = iris.target
#결정 트리를 생성해서 훈련
from sklearn.tree import DecisionTreeClassifier
#max_depth 가 깊으면 정확한 분류를 해낼 가능성은 높지만 시간이 오래 걸리고
#과대 적합 문제 발생 가능성이 있음
#트리 모델은 트리를 한 개 생성하는 것이 아니고 여러 개 생성하므로
#각 트리가 동일한 데이터를 가지고 훈련해야 하므로 random_state를 고정시켜야 합니다.
tree_clf = DecisionTreeClassifier(max_depth=2, random_state=42)
tree_clf.fit(X, y)
12-8) 예측
#예측
result = tree_clf.predict([[5.1, 3.5, 1.4, 0.2]])
print(result)
12-9) 트리 모델 시각화
1) 트리 모델의 강점
- 화이트박스, 이해가 쉽다.
트리 구조 시각화
- 피처의 중요도 파악이 쉽다 - 별도의 파라미터 튜닝을 하지 않아도 좋은 성능 - 데이터 전처리가 거의 필요 없음
2) 시각화 준비
- graphviz 설치: 화면에 트리를 출력하는 것이 목적 - Windows https://graphviz.org/download/#windows 에서 graphviz를 다운로드 받아서 설치 graphviz 가 설치된 디렉토리의 bin 디렉토리 와 bin 안에 있는 dot.exe 경로를 path에 추가
전문가의 도움을 받아서 개발자가 알고리즘을 작성해서 컴퓨터에 저장하고 이 알고리즘을 따라서 문제 해결
컴퓨터의 역할은 결과를 만들어내는 것
- Machine Learning
Data와 Output을 주면 컴퓨터가 알고리즘을 만들어내는 방식
인공지능과 머신러닝의 관계
- 인공지능 > 기계학습(머신러닝) > 딥러닝, 강화학습 - 전문가 시스템 -> 머신 러닝 -> 딥러닝, 강화학습
딥러닝은 이겼다, 졌다로 판정해주지만 강화학습은 순간순간 확률을 보여준다.=> 게임에 적합
이미지 데이터가 존재하는 경우 머신 러닝은 이미지 데이터 1개를 데이터 1개로 바라보지만 딥러닝은 이를 작게 쪼개서 그 안에서 알고리즘을 찾을 수 있음
2. Machine Learning
- 데이터를 가지고 학습하도록 컴퓨터를 프로그래밍하는 과학 - 명시적으로 프로그램 되는 것이 아니라 훈련되며 작업과 관련있는 샘플을 제공하면 이 데이터에서 통계적 구조를 찾아 그 작업을 자동화하기 위한 규칙을 만들어내는 것 - 필요 요소
입력 데이터 포인트: 최근에는 여러 입력 데이터 포인트로 얻어진 데이터를 한 곳에 모아서 처리하는 부분에 대해 중점
데이터 발생지가 여러 곳인 경우 별도로 처리하는 것이 어렵기 때문에 한 곳에 잘 정리를 해서 모으는 것이 중요
기대 출력: 어떤 결과를 원하는지
알고리즘 성능 측정 방법: 평가 지표
1) 사용하는 이유
- 전통적인 방식으로는 너무 복잡하거나 알려진 알고리즘이 없는 문제
음성인식은 직접 알고리즘을 만들기에는 너무 복잡해서 머신러닝을 이용
- 머신 러닝을 통해 학습을 할 수 있기 때문 대용량의 데이터를 분석하다보면 기존에 알고 있지 않은 또는 겉으로는 보이지 않는 패턴 발견 가능 - 데이터 마이닝
2) 역사
- 확률적 모델링
나이브 베이즈
로지스틱 회귀
- 신경망
등장은 1950년대, 이때는 컴퓨터 성능이 좋지 못해서 효과적인 훈련 방법을 찾지 못했다.
1989년 얀 르쿤에 의해 초창기 합성곱 신경망이 등장하면서 다시 각광
- 커널 방법
분류에 사용되었는데 선형이 아닌 비선형으로 결정 경계 만들기 시작
- Decision Tree, Random Forest, Gradient Boosting: 앙상블 모형
컴퓨터 성능이 좋아지면서 하나의 알고리즘을 부트스트랩을 이용하거나 여러 개의 알고리즘을 한꺼번에 학습하는 방법
- 신경망 - 생성형 AI
3) 분류
- 레이블(정답)의 존재 여부에 따른 분류
지도학습: 레이블 존재 (회귀 or 분류)
비지도학습: 레이블 없음 (주성분분석, 군집, 연관분석 등)
준지도학습: 레이블을 만드는 작업 (레이블이 일부분밖에 없어서)
강화학습: 보상이 주어지는 방식
- 실시간 점진적으로 학습을 할 수 있는지 여부
온라인학습: 점진적 학습 가능
실시간으로 데이터가 들어오는 경우도 학습이 가능하다.
배치학습: 점진적 학습 불가능
데이터가 확정이 되어있어야 한다.
- 사례 기반 학습과 모델 기반 학습
사례 기반 학습: 알고 있는 데이터와 새 데이터를 비교하는게 목적
모델 기반 학습: 패턴을 발견해서 예측 모델을 만드는 방식
4) 지도학습 (Supervised Learning)
- 레이블이 존재하는 학습 - 입력-출력 쌍들을 매핑해주는 함수 학습 - 종류
출력이 이산적일 때: 분류
출력이 연속적일 때: 회귀
출력이 확률인 경우: 추정 (Deep Learning)
- 단점
사용할 수 있는 데이터에 한계
데이터를 생성하는 데 많은 비용
- 선형회귀 - 로지스틱 회귀 - k-최근접 이웃 - 서포트 벡터 머신 - 결정 트리 - 랜덤 포레스트 - 신경망
- 로지스틱 회귀를 제외하고는 분류와 회귀에 모두 사용 가능
로지스틱 회귀는 분류만 가능
5) 비지도 학습(Unsupervised Learning)
- 레이블이 존재하지 않는 학습 - 데이터에 내재된 고유의 특징을 탐색하기 위해서 사용 - 지도 학습에 비해서 학습하기 어려움
- 군집
k means
DBSCAN
계층군집
이상치 탐지와 특이치 탐지
원클래스
아이솔레이션 포레스트
- 시각화와 차원 축소
PCA (주성분 분석)
LLE (지역적 선형 임베딩)
t-SNE
- 연관 규칙 학습
Apriori
eclat
6) 준지도 학습
- 라벨링이 일부분만 되어있어서 그 데이터를 이용해서 라벨이 없는 데이터에 라벨을 붙이기 위해서 사용
7) 강화 학습
- 결과가 바로 주어지지 않고 시간이 지나서 주어지는 방식 - 최근에 로봇 같은 분야에서 많이 이용
8) 애플리케이션 사례
- Netflix의 영화 추천 시스템: 고객의 평점작성 내역과 구매 내역을 이용해서 추천 - 미국 국가 안보국의 SKYNET: 파키스탄의 테러리스트를 식별해서 사살하기 위한 프로그램
휴대 전화 기록을 이용하여 휴대 전화를 자주 끄거나 USIM을 변경하는 사람을 테러리스트로 식별해서 사살했는데 잘못된 알고리즘으로 무고한 사람이 희생됨
3. scikit learn
- 파이썬 머신러닝 패키지 중 가장 많이 사용되는 라이브러리 - 가장 Python스러운 API - 패키지: sklearn
아나콘다는 내장
4. 데이터 표현 방식
1) 테이블로서의 데이터
- 기본 테이블은 2차원 데이터 그리드 형태 - 행: 데이터 세트의 개별 요소, sample이라 부르고 행의 개수는 n_sample이라 표현 - 열:각 요소와 관련된 수량, feature 또는 target이라고 부르는 경우가 많고 열의 개수를 n_features라고 표현
feature: 독립적인 데이터
Target : feature로 인해서 만들어지는 데이터로 label이라고도 함
2) feature
- 보통은 X라는 변수에 저장 - 특징 행렬이라는 표현을 사용. [n_sampels, n_features]의 모양을 가진 2차원 행렬이라 가정하며 실제 자료형은 numpy의 ndarray나 pandas의 DataFrame으로 되어 있는 경우가 많은데 가끔 sklearn의 희소 행렬인 경우도 있음 - 정량적인 데이터여야 하기 때문에 대부분의 경우는 실수지만 이산적인 데이터아 부울도 가ㅡㄴㅇ
3) target
- 대상 행렬이라고 하는데 y로 표시 - numpy의 1차원 ndarray나 pandas의 Series인 경우가 많음 - 연속적인 수치가 이산 클래스를 가질 수 있음
import seaborn as sns
iris = sns.load_dataset('iris')
템플릿 메소드 패턴: 공통으로 사용될 것 같은 메소드를 인터페이스에 등록하고 이를 클래스에서 구현해서 사용
- 검사 (inspection)
parameter: 함수나 기능을 수행하기 위해서 내부적으로 사용하는 데이터. argument, 인수, 인자, 매개변수라고 하기도 함
hyper parameter: 개발자가 직접 설정하는 파라미터.
hyper parameter 튜닝은 값을 변경해서 더 좋은 모델을 만들거나 최적의 값을 찾아가는 작업이다.
모든 hyper parameter를 public 속성으로 노출해서 확인이 가능하도록 함
- 제한된 객체 계층 구조
알고리즘만 python 클래스에 의해 표현, 데이터세트는 표준 포맷으로 표현, 매개변수 이름은 문자열
표준 포맷(numpy의 ndarray, pandas의 DataFrame, scipy의 Sparse Matrix)
- 구성
대부분의 머신러닝 작업은 기본 알고리즘의 시퀀스로 나타낼 수 있음 (순서대로)
- 합리적인 기본값
대다수의 하이퍼 파라미터는 라이브러리가 적절한 기본값을 가지도록 정의
2) 사용 방법
- 적절한 모델 클래스 mport - 모델 클래스를 인스턴스화 할 때 적절한 하이퍼 파라미터를 설정 - 데이터를 특징 배열과 타겟 배열로 생성 - 모델 클래스의 인스턴스의 fit 메소드를 호출해서 모델을 데이터에 적합하도록 훈련 - 모델을 새 데이터에 적용
지도 학습의 경우:predict 함수에 새로운 데이터 사용해서 예측
비지도 학습의 경우: transform이나 predict를 이용해서 데이터의 속성을 변환하거나 예측
3) 선형 회귀 수행
- sklearn.linear_model.LinearRegression
#샘플 데이터 생성
rng = np.random.RandomState(42)
#데이터 50개 생성
x = 10 * rng.rand(50)
#데이터를 이용해서 타겟 데이터 생성 - rng.randn(50)은 잡음
y = 2 * x -1 + rng.randn(50)
#x 데이터를 특징 행렬로 변환
print(x.shape) #1차원 배열 - 특성 배열은 2차원 배열, DataFrame, 희소 행렬
X = x.reshape(x.shape[0], -1) #x[:, np.newaxis]도 가능
print(X.shape) # X는 특성 배열이 되었다.
(50,) (50, 1)
#추정기 인스턴스 생성
from sklearn.linear_model import LinearRegression
model = LinearRegression(fit_intercept=True)
#기존 데이터로 훈련
model.fit(X, y)
#훈련 결과 확인
print("회귀 계수:", model.coef_)
print("절편:", model.intercept_)
#8:2로 분할 - 데이터를 하나의 데이터로 제공하면 2개로 리턴하고
#특성 배열과 타겟 배열 2개를 대입하면 4개로 리턴
train_set, test_set = sklearn.model_selection.train_test_split(housing,
test_size=0.2,
random_state=42)
#분할된 데이터의 차원 확인
print(train_set.shape)
print(test_set.shape)
(16512, 10) (4128, 10)
X = housing.drop('median_house_value', axis=1)
y = housing['median_house_value']
result = sklearn.model_selection.train_test_split(X, y, test_size=0.2, random_state=42)
print(type(result))
<class 'list'>
- 층화 추출: 계층적 샘플링 (데이터를 일정한 비율로 샘플링)
회귀나 분류를 할 때 타겟의 데이터 분포가 일정하지 않은 경우 왜곡된 결과를 만들 수 있음
분류의 경우 1과 0의 비율을 10대 1 정도 되는 상황에서 훈련 데이터에 0으로 분류되는 데이터가 하나도 없다면 이 경우 모델은 테스트 데이터에 결과가 좋지 않을 것
0으로 분류되는 모든 데이터가 훈련 데이터에 포함되어 버리면 잘못하면 테스트 데이터에 완전하게 맞는 결과가 나와버릴 수 있다.
회귀의 경우 타겟이 연속형이라면 범주형으로 변환해서 수행해야 한다.
이때 사용할 수 있는 함수는 pandas의 cut 함수
데이터와 구간의 리스트, 레이블의 리스트 대입
API 함수는 StratifiedShuffleSplit
이 함수의 리턴되는 데이터는 데이터가 아니고 데이터의 인덱스
결과를 가지고 다시 데이터 추출을 해야 함
X = housing.drop('median_house_value', axis=1)
y = housing['median_house_value']
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(X, y, test_size=0.2, random_state=42)
print(X_train.shape)
print(X_test.shape)
print(y_train.shape)
print(y_test.shape)
(16512, 9) (4128, 9) (16512,) (4128,)
- median_income의 비율을 이용한 층화 추출 (계층적 샘플링)
# 연속형 데이터를 범주형으로 변환
#pd.cut(데이터, bins =[경계값 나열] ,labels =[ 레이블 나열 ])
housing['income_cat'] = pd.cut(housing['median_income'],
bins=[0, 1.5 ,3.0 ,4.5, 6, np.inf],
labels=[1,2,3,4,5])
housing['income_cat'].value_counts()
방의 개수와 침실의 개수는 상관계수가 낮다. 그런데 bedrooms_per_room은 -0.25니까 회귀를 하려면 차라리 이걸 쓰는게 낫다.
그래서 도메인 지식이 중요하다.
- 피처와 타겟 분리
#훈련 데이터 복제 - 레이블을 제외한 데이터 복제
housing_feature = start_train_set.drop('median_house_value', axis=1)
#훈련 세트를 위해 레이블 삭제
#레이블에 변형을 적용하지 않기 위해서 레이블값도 복제
housing_leabels = start_train_set['median_house_value'].copy()
- 전처리 - 누락된 데이터 처리
#결측치 확인
#NaN 결측치 확인
sample_incomplete_rows = housing[housing.isnull().any(axis=1)].head()
sample_incomplete_rows
#sklearn 의 변환기들은 2차원 배열을 요구합니다.
from sklearn.preprocessing import OrdinalEncoder
oridinalEncoder = OrdinalEncoder()
result = oridinalEncoder.fit_transform(housing_features['ocean_proximity'])
print(result[:10])
ValueError: Expected 2D array, got 1D array instead: array=['INLAND' 'NEAR OCEAN' 'INLAND' ... '<1H OCEAN' '<1H OCEAN' 'INLAND']. Reshape your data either using array.reshape(-1, 1) if your data has a single feature or array.reshape(1, -1) if it contains a single sample.
#sklearn 의 변환기들은 2차원 배열을 요구합니다.
from sklearn.preprocessing import OrdinalEncoder
oridinalEncoder = OrdinalEncoder()
result = oridinalEncoder.fit_transform(housing_features[['ocean_proximity']])
print(result[:10])
#원 핫 인코딩 - 범주형의 개수 만큼 열을 만들어서 해당하는 열에만 1을 표시
from sklearn.preprocessing import OneHotEncoder
oneHotEncoder = OneHotEncoder()
#기본적으로 희소 행렬(sparse matrix)
result = oneHotEncoder.fit_transform(housing_features[['ocean_proximity']])
print(result[:10])
#밀집 행렬로 변환
print(result.toarray()[:10])
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)
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())
#훈련 데이터와 테스트 데이터 분리
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)
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
* 시간 때문에 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)
from pathlib import Path
import random
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import font_manager, rc
import platform
import matplotlib
import seaborn as sns
import scipy as sp
from scipy import stats
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.stats import power
기존에 사용하던 옵션과 비교해서 새 옵션이 어떤지 검정을 하고자 하는 경우 새로운 옵션이 기존의 옵션보다 성능이 좋아야 변경을 할 것이므로 새 옵션이 기존의 옵션보다 성능이 우수하게 평가되는 것이 우연에 의해서 발생한 것이 아니라는 것을 검정해야 하는데, 이러한 경우를 단측검정이라고 한다.
(다르다가 아니라 좋다 나쁘다)
이원검정
(다르다)
A와 B는 다르다는 것을 검정.
R과 파이썬의 scipy는 기본적으로 이원 검정(보수적)을 채택
통계는 대부분 이원검정을 쓰지만, 사실 실무에서는 일원검정을 많이 사용한다.
5-5) 통계적 유의성
- 통계학자가 자신의 실험 결과가 우연히 일어난 것인지 아니면 우연히 일어날 수 없는 것인지 판단하는 방법
- 결과가 우연히 벌어질 수 있는 변동성의 바깥에 존재한다면 이 경우를 통계적으로 유의하다고 판단
- p-value(유의확률): 귀무가설을 구체화한 기회 모델이 주어졌을 때, 관측된 결과와 같이 특이하거나 극단적인 결과를 얻을 확률.
p-value가 낮으면 귀무 가설을 기각 - 대립 가설 채택
유의 확률이 0.02 라면 귀무 가설을 기각 했을 때 이 기각 결정이 잘못될 확률이 2%라는 것
- alpha(유의수준): 우연에 의한 결과가 능가해야 하는 비정상적인 가능성의 임계 확률.
유의 확률 < 유의 수준: 귀무 가설을 기각하고 대립 가설을 채택
유의 확률 > 유의 수준: 대립 가설을 기각하고 귀무 가설을 채택
이 값은 분석하는 사람이 결정
- 제1종 오류: 우연에 의한 결과를 실제 효과라고 잘못 결론내리는 것
데이터가 부족하기 때문이다.
데이터 샘플을 늘리면 문제 해결됨
- 제2종 오류: 실제 효과를 우연에 의한 효과라고 잘못 결론내리는 것
2종 오류를 찾아내는게 어렵다.
- 기각역(Critical Value): 유의 수준에 대해서 계산된 검정 통계량
- 동전을 15번 던졌을 때 한쪽 면이 12번 나온 경우 이 동전은 공정한 동전인가?
동전 던지기는 이항분포(베르누이 분포)이고 확률은 0.5
이항 분포의 누적밀도함수(cdf) 이용해서 12번 나올 확률을 찾음
귀무가설: 앞면과 뒷면이 나올 확률은 일치한다. 이 동전은 공정하다. 대립가설: 앞면과 뒷면이 나올 확률은 일치하지 않는다. 이 동전은 공정하지 않다.
유의 수준을 설정해서 유의 수준보다 작은 확률이었다면 귀무 가설 기각, 유의 수준보다 크면 귀무가설 채택
N = 15 # 시행 횟수
mu = 0.5 # 1이 나올 확률
rv = sp.stats.binom(N, mu)
#축 설정을 위한 데이터
xx = np.arange(N + 1)
#확률 질량 함수(pmf) - 이산 확률 분포에서 각 사건이 발생할 확률을 계산해주는 함수
#그래프를 2행 1열로 표시하고 첫번째 영역에 표시
plt.subplot(211)
#이산 확률 질량 함수 와 누적 밀도 함수는 stem 으로 시각화
plt.stem(xx, rv.pmf(xx))
plt.ylabel('pmf')
plt.title("확률 질량 함수")
plt.annotate("검정 통계량 t = 12", xy=(12, 0.02), xytext=(12, 0.1),
arrowprops={"facecolor":"black"})
plt.show()
plt.subplot(212)
#이산 확률 질량 함수 와 누적 밀도 함수는 stem 으로 시각화
plt.stem(xx, rv.cdf(xx))
plt.ylabel('cdf')
plt.title("누적 확률 밀도 함수 함수")
plt.annotate("검정 통계량 t = 12", xy=(12, 0.02), xytext=(12, 0.1),
arrowprops={"facecolor":"black"})
plt.show()
이산확률질량함수와 누적밀도함수는 stem으로 시각화
# 양측 검정
동전을 던졌을 때 15번 던졌을 때 12번 나온 경우에 대한 검정
대립가설: μ ≠ 0.5
N = 15 # 시행 횟수
mu = 0.5 # 1이 나올 확률
rv = sp.stats.binom(N, mu)
#양측 검정에서 사용할 유의 확률: 11번까지 나올 수 있는 확률을 1에서 빼면 됩니다.
p = 2 * (1 - rv.cdf(12-1))
print(p)
#우연히 12번 이상 나올 확률은 0.035(대략 3.5% 정도)
#유의 수준을 5%로 설정하면 이 경우는 귀무 가설이 기각 - 이 동전은 공정하지 않다.
#유의 수준을 1%로 설정하면 이 경우는 귀무 가설을 기각할 수 없음 - 이 동전은 공정한 동전
0.03515625
우연히 12번 이상 나올 확률은 0.035 (약 3.5%)
유의 수준 5%에서는 귀무가설 기각, 이 동전은 공정하지 않다.
유의 수준 1%에서는 귀무가설 채택, 이 동전은 공정하다.
# 단측 검정
앞면이 12번 이상 나왔을 때 이 동전은 앞면이 더 많이 나오는 동전인가?
특정 면에 대해서 더 많이, 적게 나온 것을 구하기에 단측 검정!
대립 가설: μ > 0.5
이항분포, 1이 나올 확률 0.5, 시행횟수 15번
#단측 검정 - 앞 면이 12번 이상 나왔을 때 이 동전은 앞 면이 더 많이 나오는 동전인가
#어떤 분포 - 이항 분포
#1이 나올 확률 - 0.5
N = 15
mu = 0.5
rv = sp.stats.binom(N, mu)
#앞면이 11번까지 나올 수 있는 확률을 구해서 1에서 빼기
p = (1 - rv.cdf(11))
print(p)
#이제 유의 확률을 계산했으므로 유의 수준을 설정해서 판정
if 0.01 < p:
print("유의 확률이 더 높으므로 귀무 가설 채택")
else:
print("유의 확률이 더 높으므로 대립 가설 채택")
0.017578125 유의 확률이 더 높으므로 귀무 가설 채택
6. 이항 검정
6-1) A/B 검정 (A/B Test)
- 두 처리 방법 또는 제품중 어느 쪽이 다른 쪽보다 더 우월한지를 입증하기 위해 실험군을 두 그룹으로 나누어 동시에 진행하는 실험
대조군: 기준이 되는 기존 방법. 아무런 조치도 적용하지 않음.
처리군: 반대되는 실험에 의해 만들어진 집단이나 처리법.
가설: 새로운 처리법을 적용한 처리군이 대조군보다 더 낫다.
- 디자인이나 마케팅에서 일반적으로 사용
- 웹 전환율을 A/B 테스트로 검정하고자 하는 경우 부트스트래핑 방식을 추가적으로 도입해야 함
실제 웹의 전환율(다음 행위를 할 확률)은 굉장히 낮기 때문에 하나의 실험 결과만으로 판정하기는 어렵다.
다른 독립변인의 영향을 받아서 실험의 결과가 달라질 수 있기 때문에 실험군을 만드는게 굉장히 어렵다.
alternative: 양측 검정인 경우에는 two-sided, 단측 검정인 경우에는 less 또는 'greater‘
* 이항 검정은 이산이라서 직접 연산을 수행하여 판정하는 것이 쉽기 때문에 누적확률분포함수나 확률질량함수로 직접 판정하는 경우도 많음.
# 게임에서 내가 이길 확률이 0.3인데 100번 했을 때 60번 이상 이기는 것이 가능한지 유의 수준 5%로 검정
#누적확률분포함수 이용해서 59번 이길 확률을 빼주면 60번 이상 확률 계산
r = 1-sp.stats.binom(100, 0.3).cdf(30 - 1)
print(p)
if r > 0.05:
print("p-value가 0.05보다 크므로 정상적인 상황입니다.")
else:
print("p-value가 0.05보다 작으므로 발생할 가능성이 낮은 상황입니다.")
5.12994979828818e-10 귀무 가설을 기각하고 대립 가설을 채택해서 이런 결과가 나올 확률은 극히 희박
# 여자 손님 중 비흡연자가 흡연자보다 많다고 할 수 있는가? (유의 수준 10%)
#데이터 가져오기
tips = sns.load_dataset("tips")
#print(tips.head())
#female이 Female 인 데이터만 추출
female = tips[tips['sex'] == 'Female']
#print(female['sex'].value_counts())
#흡연자의 인원 수 와 비흡연자의 인원 수를 계산
#전체 인원수
fem_cnt = female['sex'].count()
smoke_cnt = female[female['smoker'] == 'Yes'].count()
non_smoke_cnt = female[female['smoker'] == 'No'].count()
#print(fem_cnt)
#print(smoke_cnt)
#print(non_smoke_cnt)
#흡연자의 유의 확률
p = 1- (sp.stats.binom(fem_cnt, 0.5).cdf(smoke_cnt[0] - 1))
print(p)
if p > 0.1:
print("귀무 가설을 채택 - 흡연자의 수가 비흡연자의 수보다 많다고 할 수 없다.")
else :
print("대립 가설을 채택 - 흡연자의 수가 비흡연자의 수보다 많다고 할 수 있다.")
0.991086223535132 귀무 가설을 채택 - 흡연자의 수가 비흡연자의 수보다 많다고 할 수 없다.
N = 10
mu_0 = 0
np.random.seed(0)
#정규 분포를 따르고 평균이 0인 샘플 데이터 10개 추출
x = sp.stats.norm(mu_0).rvs(N)
p = sp.stats.ttest_1samp(x, popmean=0)
print(p) #유의확률이 0.0478
#유의수준이 5% 라면 귀무가설이 기각
#유형 1의 오류 - 귀무가설이 맞는데 귀무가설이 기각되는 경우 - 샘플 데이터가 적어서
N = 100
mu_0 = 0
np.random.seed(0)
#정규 분포를 따르고 평균이 0인 샘플 데이터 100개 추출
x = sp.stats.norm(mu_0).rvs(N)
p = sp.stats.ttest_1samp(x, popmean=0)
print(p) #유의확률이 0.0478
#유의수준이 5% 라면 귀무가설이 기각
#유형 1의 오류 - 귀무가설이 맞는데 귀무가설이 기각되는 경우 - 샘플 데이터가 적어서
#아래와 같은 데이터가 있을 때 펴균이 50이라고 할 수 있는지 유의수준 0.05에서 검정
data = np.array([49, 52, 45, 53, 49, 50, 55, 43, 44, 48])
stat,pv = stats.ttest_1samp(data,50)
print(f"양측 검정으로 p-value는 {pv:.3f}")
if pv>0.05:
print("귀무 가설을 기각할 수 없다. 따라서 광고는 유의하다. ")
print("과자 A에는 50개의 알사탕이 들어있다는 것은 타당하다.")
else:
print("귀무 가설을 기각한다. 따라서 광고는 유의하지 않다.")
print("과자 A에는 50개의 알사탕이 들어있다는 것은 타당하지 않다.")
#신뢰 구간을 구하기 위해서 t 분포를 생성
t_ = sp.stats.t(len(data) - 1)
#0.975 되는 지점의 좌표를 찾기 - 양쪽 검정이므로
p_05 = t_.ppf(0.975)
data_mean = data.mean()
data_std = data.std(ddof = 1)
#하한 과 상한 구하기
lp = data_mean - p_05 * (data_std / np.sqrt(len(data)))
hp = data_mean + p_05 * (data_std / np.sqrt(len(data)))
print(f'유의 수준 0.05 일 때 신뢰 구간: [{lp}, {hp}]')
유의 수준 0.05 일 때 신뢰 구간: [45.98262777665255, 51.617372223347445]
신뢰구간 95%(유의수준 0.05)에서 홍길동의 몸무게가 73보다 작다고 할 수 있는지 검정
alternative 인자를 ‘less’ 혹은 ‘greater’로 전달하면 단측 검정을 할 수 있음
#데이터를 측정한 결과:
data = np.array([74, 73, 75, 73, 75, 74, 73, 73, 74, 72])
#신뢰구간 95%(유의수준 - 5%)에서 평균이 73보다 작다고 할 수 있는지 검정?
mean = 73
_, p = sp.stats.ttest_1samp(data, mean, alternative='less')
print(p)
if p > 0.05:
print("귀무가설을 기각할 수 없다. 평균은 73보다 크거나 같다.")
else:
print("귀무가설을 기각할 수 있다. 평균은 73보다 작다.")
0.040563094422921574 귀무가설을 기각할 수 있다. 평균은 73보다 작다.
7-2) 대응(종속) 표본 t 검정
- 동일한 표본에 대해서 관측된 결과의 기댓값이 같은지 검정
실제로는 기대값이 다르다는 것을 확인하기 위해서 수행
- 예시:
어떤 학생들이 특강을 수강하기 전과 수강한 이후에 대해서 시험을 치렀을 때 학생들의 점수 변화가 있었는지
어떤 약을 복용하기 전과 후가 달라졌는지 확인
#귀무 가설: 2집단의 평균이 같다
#대립 가설: 2집단의 평균이 다르다
x1 = np.array([0.7, -1.6, 0.2])
x2 = np.array([1.2, -1.1, 0.2])
_, pvalue = sp.stats.ttest_rel(x1, x2)
if pvalue > 0.05:
print("귀무 가설을 기각할 수 없다. 두 집단의 평균은 같다")
else:
print("귀무 가설을 기각할 수 있다. 두 집단의 평균은 다르다")
귀무 가설을 기각할 수 없다. 두 집단의 평균은 같다
#당뇨 약 복용 전과 복용 후 검정
동일한 환자에 대한 측정치라고 가정: 대응표본t검정
변화가 있는지 알아보는 검정 - 양측 검정
유의 수준 5%
#데이터
data_a = np.array([440, 90, 120, 220, 230, 320, 450, 180])
data_b = np.array([220, 80, 100, 110, 180, 250, 350, 170])
#양측 검정 수행
_, p = sp.stats.ttest_rel(data_a, data_b, alternative = "two-sided")
print(p)
if p > 0.05:
print("귀무 가설을 기각할 수 없다, 2개 데이터 집단의 평균은 같지 않다라고 할 수 없다.")
else:
print("귀무 가설을 기각할 수 있다, 2개 데이터 집단의 평균은 같지 않다라고 할 수 있다.")
0.02139085535260364 귀무 가설을 기각할 수 있다, 2개 데이터 집단의 평균은 같지 않다라고 할 수 있다.
#단측 검정 수행
#단측 검정을 수행할 때는 앞의 데이터를 기준으로 판정
#앞의 데이터가 더 커야 하면 greater 더 작아야 하면 less
_, p = sp.stats.ttest_rel(data_a, data_b, alternative = "greater")
#단측 검정의 p-value 값이 양측 검정의 p-value/2 입니다.
print(p)
if p > 0.05:
print("귀무 가설을 기각할 수 없다, 첫번째 데이터가 더 크지 않다.");
else:
print("귀무 가설을 기각할 수 있다, 첫번째 데이터의 평균이 더 크다라고 할 수 있다.")
nan 귀무 가설을 기각할 수 있다, 첫번째 데이터의 평균이 더 크다라고 할 수 있다.
7-3) 독립 표본 t검정
- 두 개의 독립적인 정규 분포에서 나온 두 개의 데이터 셋을 사용해서 두 정규 분포의 기대값(평균)이 동일한지를 검사
- 이 경우에도 샘플 데이터의 개수가 너무 적으면 제2종 오류 (실제 효과를 우연에 의한 효과라고 잘못 결론 내리는 것 - 대립 가설을 채택해야 하는데 귀무가설을 채택해야 하는 경우) 발생
np.random.seed(0)
#첫번째 데이터
N_1 = 10
mu_1 = 0
sigma_1 = 1
#두번째 데이터
N_2 = 10
mu_2 = 0.5
sigma_2 = 1
#2개 데이터 집단의 평균은 0 과 0.5 로 다름
x1 = sp.stats.norm(mu_1, sigma_1).rvs(N_1)
x2 = sp.stats.norm(mu_2, sigma_2).rvs(N_2)
#그래프로 출력
ax = sns.distplot(x1, kde=False, fit=sp.stats.norm, label="1번 데이터 집합")
ax = sns.distplot(x2, kde=False, fit=sp.stats.norm, label="2번 데이터 집합")
ax.lines[0].set_linestyle(":")
plt.legend()
plt.show()
print(sp.stats.ttest_ind(x1, x2, equal_var=False))
- 샘플의 개수가 적으면 제2종 오류 발생 가능성이 높아짐
7-4) 등분산 검정
- 독립표본t검정에서는 기대값이 같은지 확인을 하고자 하는 경우 분산의 값이 다른지 같은지를 알아야 한다.
- 등분산 검정에는 sp.stats 패키지 bartlett, fligner, levene 검정 함수 제공
2개 데이터의 집단만 대입하면 p-value 값을 얻어낼 수 있다.
#첫번째 데이터 - 분산이 1
N_1 = 100
mu_1 = 0
sigma_1 = 1
#두번째 데이터 - 분산이 1.5
N_2 = 100
mu_2 = 0
sigma_2 = 1.5
np.random.seed(0)
#평균은 같은데 분산이 다른 데이터를 생성
x1 = sp.stats.norm(mu_1, sigma_1).rvs(N_1)
x2 = sp.stats.norm(mu_2, sigma_2).rvs(N_2)
_, p = sp.stats.bartlett(x1, x2)
#유의 확률이 낮게 나오기 때문에 2개 데이터의 분산이 다르다
print(p)
2.4364708986190797e-05
8. 윌콕슨의 분호 순위 검정
- 대응 표본(종속표본 - 동일한표본) 차이에 정규분포를 적용할 수 없는 경우 사용하는 중앙값의 차이에 대한 검정
- 차이의 절대값을 이용해서 순위 부여
부여된 순위에 평균의 차이를 +와 -부호를 부여해서 곱한 후 얻어진 2개의 값의 차이를 가지고 판정
차이가 있는 경우에는 순위의 편차가 심할 것이고 그렇지 않은 경우는 순위의 편차가 별로 없음
- 차이에 편향이 있을수록 순위에도 편향이 생기고, 검정 통계량은 작은 값이 돼서 중앙값에 차이가 있다는 주장을 할 수 있음
- 알고리즘을 직접 구현해도 되고 scipy.stats.wilcoxon 함수 제공
함수를 이용하게 되면 중간에 표준화를 수행하고 정규분포로 검정을 하기 때문에 직접 구현하는 경우와 값은 다르게 나옴
8-1) 알고리즘 직접 구현
#데이터 가져오기 - 전 후의 차이가 별로 없는 경우
training_rel = pd.read_csv('./data/training_rel.csv')
#print(training_rel.head())
#알고리즘을 이해하기 위해서 데이터의 개수를 줄임
toy_df = training_rel[:6].copy()
#print(toy_df)
#데이터의 차이를 계산
diff = toy_df['후'] - toy_df['전']
toy_df['차'] = diff
#print(toy_df)
#차이의 절대값 순위를 계산
#차이가 적은 데이터의 순위가 1 차이가 많은 데이터의 순위가 뒤로 갑니다.
rank = sp.stats.rankdata(abs(diff)).astype(int)
toy_df['순위'] = rank
print(toy_df)
#차이가 음수인 데이터의 순위 합 과 차이가 양수인 데이터의 순위합을 구함
r_minus = np.sum((diff < 0) * rank)
r_plus = np.sum((diff > 0) * rank)
#낮은 쪽이 검정 통계량
print(r_minus, r_plus)
#이 값이 임계값보다 작은 경우에 귀무 가설이 기각되는 단측 검정
print("검정 통계량:", r_minus)
전 후 차 순위 0 59 41 -18 5 1 52 63 11 3 2 55 68 13 4 3 61 59 -2 1 4 59 84 25 6 5 45 37 -8 2 8 13 검정 통계량: 8
#데이터 가져오기 - 전 후의 차이가 있는 경우
toy_df['후'] = toy_df['전'] + np.arange(1, 7)
#데이터의 차이를 계산
diff = toy_df['후'] - toy_df['전']
toy_df['차'] = diff
#print(toy_df)
#차이의 절대값 순위를 계산
#차이가 적은 데이터의 순위가 1 차이가 많은 데이터의 순위가 뒤로 갑니다.
rank = sp.stats.rankdata(abs(diff)).astype(int)
toy_df['순위'] = rank
print(toy_df)
#차이가 음수인 데이터의 순위 합 과 차이가 양수인 데이터의 순위합을 구함
r_minus = np.sum((diff < 0) * rank)
r_plus = np.sum((diff > 0) * rank)
#이번에는 고의적으로 데이터 값의 차이를 1, 2, 3, 4, 5, 6 으로 만들어서 순위를 구함
#이렇게 차이가 많이 나면서 증가하거나 감소하면 t 검정을 사용하기가 어려움
#t 검정은 데이터의 분포가 정규분포라는 가정이 있어합니다.
#이 데이터는 각 데이터들의 차이가 서로 완전히 다르기 때문에 정규 분포라고 가정하기가 어려움
#이런 경우 차이의 순위를 이용해서 일정한 순위 형태로 늘어났는지 확인할 수 있는 검정이
#윌콕슨의 부호 순위 검정
#낮은 쪽이 검정 통계량
print(r_minus, r_plus)
#이 값이 임계값보다 작은 경우에 귀무 가설이 기각되는 단측 검정
print("검정 통계량:", r_minus)
전 후 차 순위 0 59 60 1 1 1 52 54 2 2 2 55 58 3 3 3 61 65 4 4 4 59 64 5 5 5 45 51 6 6 0 21 검정 통계량: 0
#데이터 가져오기 - 전 후의 차이가 랜덤한 경우
#순위가 낮아진 것도 높아진 것도 있기 때문에 줄어든 데이터의 순위 와 늘어난 데이터의 순위
#합의 차이가 얼마 안남
#순위의 변화 합을 계산하면 전체적으로 늘어나거나 줄어들었는지 아니면
#랜덤한 변화를 가져왔는지 알 수 있습니다.
toy_df['후'] = toy_df['전'] + [1, -2, -3, 4, 5, -6]
#데이터의 차이를 계산
diff = toy_df['후'] - toy_df['전']
toy_df['차'] = diff
#print(toy_df)
#차이의 절대값 순위를 계산
#차이가 적은 데이터의 순위가 1 차이가 많은 데이터의 순위가 뒤로 갑니다.
rank = sp.stats.rankdata(abs(diff)).astype(int)
toy_df['순위'] = rank
print(toy_df)
#차이가 음수인 데이터의 순위 합 과 차이가 양수인 데이터의 순위합을 구함
r_minus = np.sum((diff < 0) * rank)
r_plus = np.sum((diff > 0) * rank)
#이번에는 고의적으로 데이터 값의 차이를 1, 2, 3, 4, 5, 6 으로 만들어서 순위를 구함
#이렇게 차이가 많이 나면서 증가하거나 감소하면 t 검정을 사용하기가 어려움
#t 검정은 데이터의 분포가 정규분포라는 가정이 있어합니다.
#이 데이터는 각 데이터들의 차이가 서로 완전히 다르기 때문에 정규 분포라고 가정하기가 어려움
#이런 경우 차이의 순위를 이용해서 일정한 순위 형태로 늘어났는지 확인할 수 있는 검정이
#윌콕슨의 부호 순위 검정
#낮은 쪽이 검정 통계량
print(r_minus, r_plus)
#이 값이 임계값보다 작은 경우에 귀무 가설이 기각되는 단측 검정
print("검정 통계량:", r_minus)
전 후 차 순위 0 59 60 1 1 1 52 50 -2 2 2 55 52 -3 3 3 61 65 4 4 4 59 64 5 5 5 45 39 -6 6 11 10 검정 통계량: 11
8-2) 제공되는 API 활용
_, p = sp.stats.wilcoxon(training_rel['전'], training_rel['후'])
print(p)
0.03623390197753906
변화가 없다가 귀무 가설
이 경우는 유의 수준을 0.05 로 잡으면 귀무가설을 기각할 수 있고 0.01로 잡으면 귀무 가설을 기각할 수 없음
9. Mann-Whitney Rank Test
- 대응되는 표본이 아닌 독립 표본인 경우 정규 분포를 가정할 수 없는 경우 중앙값의 차이에 대한 검정
- 정규 분포가 아니라면 평균이나 분산을 가정할 수 없어서 평균을 사용하지 못하고 중앙값을 사용
- 윌콕슨의 부호 순위 합 검정은 대응되는 표본이라서 각 열의 차이를 가지고 순위를 구하고 순위의 변화량을 측정하는데 전체 데이터를 가지고 순위를 구한 후 각 열의 순위 합을 가지고 검정
9-1) 실습
#열의 각 데이터가 동일한 표본이 아닌 경우
training_rel = pd.read_csv('./data/training_ind.csv')
toy_df = training_rel[:6].copy()
#print(toy_df)
#두 열의 데이터를 합산해서 순위를 구하는 구조
rank = sp.stats.rankdata(np.concatenate([toy_df["A"], toy_df["B"]]))
rank_df = pd.DataFrame({"A":rank[:5], "B":rank[5:10]}).astype(int)
print(rank_df)
A B 0 4 3 1 7 6 2 1 9 3 12 11 4 2 5
데이터가 일정한 변화를 가져온다면 A 나 B에 좋은 순위가 모여있을 가능성이 높음
데이터의 편차가 아주 크지 않다면 이 이론은 성립
한쪽에 좋은 순위나 나쁜 순위가 몰려있으면 데이터에 편향이 발생한 것이므로 유의미한
차이가 있다라고 판정하는 방식
10. 분산 분석
- 여러 글룹의 수치 데이터를 비교해서 여러 그룹 간의 통계적으로 유의미한 차이를 검정하는 통계적 절차가 분산 분석 또는 ANOVA
- 3가지 조건이 필요
정규성: 각각의 그룹에서 변인은 정규 분포
분산의 동질성: 모집단의 분산은 각각의 모집단에서 동일
관찰의 독립성: 각각의 모집단에서 크기가 각각인 표본들이 독립적으로 표집
- 용어
쌍별 비교: 여러 그룹 중에서 두 그룹 간의 가설 검정
총괄 검정: 여러 그룹 평균들의 전체 분산에 대한 단일 가설 검정
분산 분해: 구성 요소를 분리하는 것으로 전체 평균, 처리 평균, 잔차 오차로부터 개별 값들에 대한 기여
F 통계량: 그룹 평균 간의 차이가 랜덤 모델에서 예상하는 것보다 벗어나는 정도를 측정하는 표준적인 통계량
SS(Sum of Squares): 어떤 평균으로부터의 편차들의 제곱합
10-1) 분산의 중요성
- 어떤 그룹 사이에 평균이 같다고 해서 2 그룹 사이에 연관성이 있다고 말하기는 어려움
분산이 다르면 분포는 완전히 달라지기 때문
그룹 과 그룹을 비교할 때는 평균 과 분산을 모두 확인해야 함
#중앙점의 좌표 - 평균
centers = [5, 5.3, 4.7]
#표준 편차 - 분산의 제곱근
#0.1 과 2.0으로 변경했을 때 분포가 아예 달라집니다.
#0.1 인 경우 2개의 분포가 상단에 배치되고 1개의 분포가 하단에 배치됩니다.
#2.0 인 경우 3개의 분포가 동일선상에 배치가 됩니다.
#std = 0.1
std = 2.0
colors = 'brg'
data_1 = []
for i in range(3):
data_1.append(sp.stats.norm(centers[i], std).rvs(100))
plt.plot(np.arange(len(data_1[i])) + i * len(data_1[0]), data_1[i], '.', color=colors[i])
분산이 클수록 집단의 평균 값의 차이는 무의미
집단 평균 값의 분산이 클수록 집단 내 분산이 작아질수록 평균의 차이가 분명해짐
- 집단 간 분산 과 집단 내 분산 이 두 가지를 이용해서 분석을 하는 것이 분산 분석 - 가장 기본적인 클러스터링(그룹화 - 집단 내의 분산은 작게 집단 과 집단의 분산은 크게)을 할 때 원리
- 분산 분석에서는 일원 분산 분석(종속 변인이 1개이고 독립 변인의 수도 1개) 과 이원 분산 분석(독립 변인의 수가 2개 이상)으로 나눔
10-2) 일원 분산 분석
- 종속 변인이 1개이고 독립 변인의 집단도 1개인 경우
- 한 가지 변수의 변화(독립 변인)가 결과 변수(종속 변인)에 미치는 영향을 알아보기 위해서 수행
- python에서는 One-Way ANOVA 분석은 scipy.stats 나 statsmodel 라이브러리를 이용
statsmodel를 이용하는 것이 더 많은 분석 정보를 얻어낼 수 있음
- Altman 910 데이터
22명의 심장 우회 수술을 받은 환자를 3개의 그룹으로 분류해서 시술한 결과
그룹1 - 50% 아산화 질소 와 50%의 산소 혼합물을 24시간 동안 흡입
그룹2 - 50% 아산화 질소 와 50%의 산소 혼합물을 수술받는 동안만 흡입
그룹3 - 35% ~ 50%의 산소만 24시간 동안 흡입
적혈구의 엽산 수치를 24시간 이후에 측정
적혈구의 엽산 수치가 종속 변인이 되고 3개의 그룹으로 나눈 변인이 독립 변인
독립 변인에 따른 종속 변인의 평균에 차이가 있는 지 검정
p-value 가 유의 수준보다 낮으면 평균에 차이가 있는 것이고 그렇지 않으면 평균에 차이가 없다라고 판정
- scipy.stats.f_oneway(그룹별 데이터 설정) 와 statsmodels.formula.api 를 이용(R 의 형태를 갖는 API
여러 개의 컬럼을 설정할 때 python 은 컬럼 이름의 list를 이용하지만 R은 종속 변인 과 독립 변인을 설정할 때 ~ 로 구분해서 설정
#Altman 910 데이터 가져오기
#텍스트 파일의 내용을 가지고 numpy 의 ndarray 생성
#대부분의 경우 pandas를 이용해서 데이터를 읽어오고 ndarray 로 변환하지만
#텍스트 데이터(txt, tsv, csv )의 경우는 데이터를 가지고 직접 ndarray 생성 가능
import urllib.request
url = 'https://raw.githubusercontent.com/thomas-haslwanter/statsintro_python/master/ipynb/Data/data_altman/altman_910.txt'
data = np.genfromtxt(urllib.request.urlopen(url), delimiter=',')
#print(data)
#첫번째 열이 측정치이고 두번째 열이 그룹
#데이터를 그룹 별로 분할
group1 = data[data[:, 1] == 1, 0]
group2 = data[data[:, 1] == 2, 0]
group3 = data[data[:, 1] == 3, 0]
#print(group1)
#시각화를 통해서 데이터의 분포를 확인
#중앙값의 위치나 박스 와 수염의 크기가 거의 비슷하다면 데이터에 차이가 없다라고 판단
plot_data = [group1, group2, group3]
plt.boxplot(plot_data)
plt.show()
# sp.stats.f_oneway API
_, p_value = sp.stats.f_oneway(group1, group2, group3)
print("유의 확률:", p_value)
#유의 수준을 5%
if p_value > 0.05:
print("유의 수준이 유의 확률보다 크기때문에 이 데이터들은 평균 값이 유의미하게 차이가 나지 않음")
else:
print("유의 수준이 유의 확률보다 작거나 같기때문에 이 데이터들은 평균 값이 유의미하게 차이가 납니다.")
print(sp.stats.f_oneway(group1, group2, group3))
유의 확률: 0.043589334959178244 유의 수준이 유의 확률보다 작거나 같기때문에 이 데이터들은 평균 값이 유의미하게 차이가 납니다. F_onewayResult(statistic=3.7113359882669763, pvalue=0.043589334959178244)
# statsmodels API 활용
from statsmodels.formula.api import ols
#기존 배열을 가지고 DataFrame을 생성
df = pd.DataFrame(data, columns=['value', 'treatment'])
#모델을 생성
model = ols('value ~ C(treatment)', df).fit()
#print(model)
#훈련을 수행하고 결과를 확인
import statsmodels.api as sm
print(sm.stats.anova_lm(model)) #가장 마지막 열의 첫번째 데이터가 p-value
print(type(sm.stats.anova_lm(model)))
df sum_sq mean_sq F PR(>F) C(treatment) 2.0 15515.766414 7757.883207 3.711336 0.043589 Residual 19.0 39716.097222 2090.320906 NaN NaN <class 'pandas.core.frame.DataFrame'>
10-3) 이원 분산 분석
- 독립 변인의 수가 2개 이상일 때 집단 간 차이가 유의한지를 검증
- Interaction Effect(상호 작용 효과)를 확인하기 위해 사용
한 변인의 변화가 결과에 미치는 영향이 다른 변인의 수준에 따라 달라지는가 하는 것을 확인
- altman_12_6
태아의 머리 둘레 측정 데이터
4명의 관측자가 3명의 태아를 대상으로 측정
inFile = 'altman_12_6.txt'
url_base = 'https://raw.githubusercontent.com/thomas-haslwanter/statsintro_python/master/ipynb/Data/data_altman/'
url = url_base + inFile
#첫번째 열이 머리 둘레 크기 - 종속 변인
#두번째 열이 태아를 구별하기 위한 번호 - 독립 변인
#세번째 열이 관측자를 구별하기 위한 번호 - 독립 변인
data = np.genfromtxt(urllib.request.urlopen(url), delimiter=",")
#print(data)
#이 경우는 이원 분산 분석을 수행
df = pd.DataFrame(data, columns=['head_size', 'fetus', 'observer'])
model = ols('head_size ~ C(fetus) + C(observer) + C(fetus):C(observer)', df).fit()
#print(model)
#훈련을 수행하고 결과를 확인
import statsmodels.api as sm
print(sm.stats.anova_lm(model)) #가장 마지막 열의 첫번째 데이터가 p-value
print(type(sm.stats.anova_lm(model)))
상품 목록 페이지에서 상품 상세 보기 페이지로 이동하는 것 또는 상품 상세 보기 페이지에서 상품 결재 페이지로 이동하는 것 등의 비율
포탈의 광고 또는 SNS 광고를 통해서 들어오는 고객의 비율
- 웹 점착성
방문자가 페이지에서 보낸 시간
여러 명의 유저가 여러 페이지에서 보낸 시간을 가진 데이터를 가지고 분산 분석을 수행
이 데이터가 앞의 데이터 와 다른 점은 페이지를 선택하는 권리가 유저에게 있다는 것
모든 유저가 모든 페이지에 방문하는 것은 아니며 그 확률은 매우 낮음
부트스트래핑 이나 순열 검정(재표본 추출) 같은 방식을 이용
모든 데이터를 한 상자에 모아서 페이지 개수 별로 각 페이지의 개수만큼 추출
각 그룹의 평균을 기록한 후 각 그룹의 평균 사이의 분산을 기록
이 작업을 여러 번 반복한 후 이 분산이 관찰된 변화가 p 값
#웹 페이지에 머무르는 시간을 웹 점착성이라고 합니다.
#4개의 페이지에 머무르는 시간이 기록된 데이터를 가져오기
four_sessions = pd.read_csv('./data/four_sessions.csv')
#print(four_sessions.head())
#그룹 별 Time 데이터의 평균의 분산을 구합니다.
observed_variance = four_sessions.groupby('Page').mean().var()[0]
print("그룹 별 Time 의 평균에 대한 분산:", observed_variance)
#랜덤하게 섞어서 그룹 별로 TIme에 대한 평균의 분산을 구해주는 함수
def perm_test(df):
df = df.copy()
df['Time'] = np.random.permutation(df['Time'].values)
return df.groupby('Page').mean().var()[0]
#print(perm_test(four_sessions))
np.random.seed(1)
#3000번을 수행해서 observed_variance 보다 분산이 높은 비율을 계산
perm_variance = [perm_test(four_sessions) for _ in range(3000)]
#이 비율의 값이 p-value
#유의 수준을 정해서 분산이 의미가 있는지 판정
#유의 수준 보다 p-value 값이 작다면 분산의 차이가 의미가 있습니다.
#점착성에 차이가 있는 것
#2개의 동일한 콘텐츠를 가진 페이지로 이 작업을 수행하게 되면 A/B 테스트가 됩니다.
print("p-value:", np.mean([var > observed_variance for var in perm_variance]))
그룹 별 Time 의 평균에 대한 분산: 55.426666666666655 p-value: 0.08433333333333333
11. 카이 제곱 검정
- 범주 별로 관측빈도와 기대빈도의 차이를 통해서 확률 모형이 데이터를 얼마나 잘 설명하는지 검정하는 통계 방법
- 관측된 데이터를 대상으로 유의 확률을 적용하여 적합도 검정이나 변수 간의 독립성 여부를 검정하거나 동질성 검정을 수행할 수 있다.
적합도 검정: 표본이 기대에 적합한지 검정
독립성 검정: 두 항목이 서로 독립인지 검정
동질성 검정: 두 집단의 데이터 분포가 같은지 검정
- 웹 테스트 시 종종 단순한 A/B 검정을 넘어서 동시에 여러가지 처리를 한 번에 테스트할 필요가 있을 때 카이제곱검정 사용
리스트 페이지에 3개의 페이지에 대한 링크가 존재할 때 1000명의 유저가 방문하면
A 페이지
B페이지
C페이지
클릭
14
8
12
클릭하지 않은 경우
986
992
998
전환율이 실제로 굉장히 낮기 때문에 단순 비교는 어렵다.=> 재표본 추출(순열검정, 부트스트랩)을 이용해서 클릭률이 우연히 발생할 수 있는 것보다 유의미한 정도로 큰 차이인지 검정
귀무가설: 모두가 동일한 클릭률을 갖는다.
전체 클릭률은 34/3000, 계산해서 1.133%이며 이렇게 되는 경우 아래와 같이 통계 가정
A 페이지
B페이지
C페이지
클릭
11.3
11.33
11.33
클릭하지 않은 경우
988.67
988.67
988.67
피어슨 잔차 계산: (실제 관측값 - 기대값) / 기대값의 제곱근
카이제곱 통계량 계산: 피어슨 잔차들의 제곱합
카이제곱 통계량이 귀무가설로부터 얻을 수 있는 값보다 클까? => 재표본 알고리즘을 통해 검정
34개의 1과 2966개의 0이 들어있는 배열 생성
상자의 내용물을 랜덤하게 1000개씩 추출한 후 1의 개수 계산
이렇게 얻은 횟수와 기대한 횟수의 차이를 제곱해서 합산 (=카이제곱통계량계산)
이 과정을 1000번 반복
처음 계산한 카이제곱통계량보다 몇번이나 초과되었는지 확인 (p-value)
- 카이제곱 검정을 수행하는 함수나 메소드를 만들어야 한다면 수식을 기억해야하고, 그렇지 않으면 API 함수를 사용
주성분분석 같은 차원 축소 과정에서 각 성분의 분산을 최대화하는 가중치와 계수의 관계 표현
- 선형독립성
각 열의 독립적인 정도
하나의 벡터를 다른 벡터들의 선형 가중 결합으로 나타낼 수 있는지 확인 상관계수는 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]
#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)
전수조사의 단점을 보완하지만 모집단의 특성을 반영하는 표본이 제대로 추출되지 못하면 수집된 자료가 무용지물
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', '에스파']
- 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)
타켓의 비율이 편차가 심한 경우 랜덤하게 추출했을 때 비율이 낮은 데이터가 추출이 안될 수 도 있고 너무 많이 추출될 수 도 있다.
이렇게 머신러닝의 결과가 엉뚱한 결과가 추출될 수 있습니다.
층화 추출을 할 때는 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를 도입했을 때 결과 비교 등에 사용하는 방식) 등에 이용
여러 그룹의 결과를 하나의 데이터 집합으로 합침
결합된 데이터를 잘 섞은 다음 A 그룹과 동일한 크기의 표본을 비복원 무작위로 추출
나머지 데이터에서 B 그룹과 동일한 크기의 샘플을 비복원 무작위로 추출
그룹이 더 존재하면 동일한 방식으로 추출
원래의 표본에 대해 구한 통계량 또는 추정값이 무엇이었든지 간에 추출한 재표본에 대해서 다시 계산하고 기록을 한 다음 원래의 표본에 대해서 구한 값과 비교
이 작업을 여러 번 반복
부트스트래핑
- 모수의 분포를 추정하는 방법으로 현재있는 표본에서 추가적으로 표본을 복원 추출하고 각 표본에 대한 통계량을 다시 계산하는 것
- 개념적으로만 보면 원래 표본을 수천 수백만 번 복제하는 것
1억개의 모집단에서 200개의 표본을 추출
200개의 표본 중에서 하나를 추출해서 기록하고 복원
이 작업을 n번 반복
n번 반복한 데이터를 가지고 통계량을 계산
이전 과정을 여러 번 반복해서 통계량을 구하고 이 통계량을 이용해서 신뢰구간을 구하는 방식
- 시간이 오래 걸린다.
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())
#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)
현재 메일이 온 경우 메일에 포함된 내용들을 원핫 인코딩 해서 기존에 만들어진 단어들의 스팸 비율 확인해서 판정
감성분석, 욕설 방지 등에 사용 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)
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]
주가의 수익률이 정규 분포라면 주가 자체는 로그 정규 분포(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
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명의 손님이 방문할 확률은?
# 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")