from sklearn.metrics import confusion_matrix

y_true = [2, 0, 2, 2, 0, 1]
y_pred = [0, 0, 2, 2, 0, 2]
confusion_matrix(y_true, y_pred)

y_true = ["cat", "ant", "cat", "cat", "ant", "bird"]
y_pred = ["ant", "ant", "cat", "cat", "ant", "cat"]
confusion_matrix(y_true, y_pred, labels=["ant", "bird", "cat"])

"""
Explanation: 분류(classification) 성능 평가
분류 문제는 회귀 분석과 달리 모수에 대한 t-검정, 신뢰 구간(confidence interval) 추정 등이 쉽지 않기 때문에 이를 보완하기 위해 다양한 성능 평가 기준이 필요하다.
Scikit-Learn 에서 지원하는 분류 성능 평가 명령

sklearn.metrics 서브 패키지
confusion_matrix()
classfication_report()
accuracy_score(y_true, y_pred)
precision_score(y_true, y_pred)
recall_score(y_true, y_pred)
fbeta_score(y_true, y_pred, beta)
f1_score(y_true, y_pred)

분류 결과표 Confusion Matrix
분류 결과표는 타겟의 원래 클래스와 모형이 예측한 클래스가 일치하는지는 갯수로 센 결과이다.
원래 클래스는 행(row)으로 예측한 클래스는 열(column)로 나타낸다.
| | 예측 클래스 0 | 예측 클래스 1 | 예측 클래스 2 | 
|-|-|-|-|
| 원 클래스 0 | <small>원 클래스가 0, 예측 클래스가 0인 표본의 수</small> | <small>원 클래스가 0, 예측 클래스가 1인 표본의 수</small> | <small>원 클래스가 0, 예측 클래스가 2인 표본의 수</small> |
| 원 클래스 1 | <small>원 클래스가 1, 예측 클래스가 0인 표본의 수</small> | <small>원 클래스가 1, 예측 클래스가 1인 표본의 수</small> | <small>원 클래스가 1, 예측 클래스가 2인 표본의 수</small> |
| 원 클래스 2 | <small>원 클래스가 2, 예측 클래스가 0인 표본의 수</small> | <small>원 클래스가 2, 예측 클래스가 1인 표본의 수</small> | <small>원 클래스가 2, 예측 클래스가 2인 표본의 수</small> |
End of explanation
"""


#F score
from sklearn.metrics import classification_report

y_true = [0, 1, 2, 2, 2]
y_pred = [0, 0, 2, 2, 1]
target_names = ['class 0', 'class 1', 'class 2']
print(classification_report(y_true, y_pred, target_names=target_names))

y_true = ["cat", "ant", "cat", "cat", "ant", "bird"]
y_pred = ["ant", "ant", "cat", "cat", "ant", "cat"]
print(classification_report(y_true, y_pred, target_names=["ant", "bird", "cat"]))

"""
Explanation: 이진 분류 결과표 Binary Confusion Matrix
클래스가 0과 1 두 종류 밖에 없는 경우에는 일반적으로 클래스 이름을 "Positive"와 "Negative"로 표시한다. 
또, 분류 모형의 예측 결과가 맞은 경우, 즉 Positive를 Positive라고 예측하거나 Negative를 Negative라고 예측한 경우에는 "True"라고 하고 예측 결과가 틀린 경우, 즉 Positive를 Negative라고 예측하거나 Negative를 Positive라고 예측한 경우에는 "False"라고 한다.
이 경우의 이진 분류 결과의 명칭과 결과표는 다음과 같다. 이 표는 외우는 것이 좋다.
| | Positive라고 예측  | Negative라고 예측 | 
|-|-|-|
| 실제 Positive | True Positive   |  False Negative |           
| 실제 Negative | False Positive  |  True Negative  |   
FDS(Fraud Detection System)의 예
FDS(Fraud Detection System)는 금융 거래, 회계 장부 등에서 잘못된 거래, 사기 거래를 찾아내는 시스템을 말한다. FDS의 예측 결과가 Positive 이면  사기 거래라고 예측한 것이고 Negative 이면 정상 거래라고 예측한 것이다. 이 결과가 사실과 일치하는지 틀리는지에 따라 다음과 같이 말한다.

True Positive: 사기를 사기라고 정확하게 예측
True Negative: 정상을 정상이라고 정확하게 예측
False Positive: 정상을 사기라고 잘못 예측
False Negative: 사기를 정상이라고 잘못 예측

|                     | 사기 거래라고 예측                | 정상 거래라고 예측                         | 
| --------------------| ------------------------ | --------------------------------- |
| 실제로 사기 거래  | True Positive            |  False Negative                   |           
| 실제로 정상 거래  | False Positive            |  True Negative                    |   
평가 스코어
Accuracy 정확도

전체 샘플 중 맞게 출력한 샘플 수의 비율
실제로는 잘 안 쓴다.

$$\text{accuracy} = \dfrac{TP + TN}{TP + TN + FP + FN}$$
Precision 정밀도

실제로는 정밀도와 재현율을 많이 쓴다.
클래스에 속한다고 출력한 샘플 중 실제로 클래스에 속하는 샘플 수의 비율
FDS의 경우, 사기 거래라고 판단한 거래 중 실제 사기 거래의 비율. 유죄율

$$\text{precision} = \dfrac{TP}{TP + FP}$$
Recall 재현율

TPR: true positive rate
실제 클래스에 속한 샘플 중에 클래스에 속한다고 출력한 샘플의 수
FDS의 경우, 실제 사기 거래 중에서 실제 사기 거래라고 예측한 거래의 비율. 검거율

$$\text{recall} = \dfrac{TP}{TP + FN}$$
Fall-Out

FPR: false positive rate
실제 클래스에 속하지 않는 샘플 중에 클래스에 속한다고 출력한 샘플의 수
FDS의 경우, 실제 정상 거래 중에서 FDS가 사기 거래라고 예측한 거래의 비율

$$\text{fallout} = \dfrac{FP}{FP + TN}$$
F (beta) score

정밀도(Precision)과 재현율(Recall)의 가중 조화 평균

$$
F_\beta = (1 + \beta^2) \, ({\text{precision} \times \text{recall}}) \, / \, ({\beta^2 \, \text{precision} + \text{recall}})
$$

F1 score
beta = 1 

$$
F_1 = 2 \cdot \text{precision} \cdot \text{recall} \, / \, (\text{precision} + \text{recall})
$$
End of explanation
"""


from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
X, y = make_classification(n_features=1, n_redundant=0, n_informative=1, n_clusters_per_class=1, random_state=4)
model = LogisticRegression().fit(X, y)

print(confusion_matrix(y, model.predict(X)))

print(classification_report(y, model.predict(X)))

from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y, model.decision_function(X))
#decision_function은 값이 크면 클수록 class 1쪽으로 더 많이 들어간 것이고
#무한대로 간 것은 class0으로 더 많이 들어간 것.

plt.plot(fpr, tpr)
plt.plot([0, 1], [0, 1], 'k--', label="random guess")
#random guess 점선은 무능한 경찰의 기준. 일을 열심히 하면 위에, 아니면 아래의 곡선상의 위치
plt.xlabel('False Positive Rate (Fall-Out)')
plt.ylabel('True Positive Rate (Recall)')
plt.title('Receiver operating characteristic example')
plt.show()

tpr

fpr

"""
Explanation: ROC 커브
ROC(Receiver Operator Characteristic) 커브는 클래스 판별 기준값의 변화에 따른 Fall-out과 Recall의 변화를 시각화한 것이다.
모든 이진 분류 모형은 판별 평면으로부터의 거리에 해당하는 판별 함수(discriminant function)를 가지며 판별 함수 값이 음수이면 0인 클래스, 양수이면 1인 클래스에 해당한다고 판별한다. 즉 0 이 클래스 판별 기준값이 된다. ROC 커브는 이 클래스 판별 기준값이 달라진다면 판별 결과가 어떻게 달라지는지는 표현한 것이다.
Scikit-Learn 의 Classification 클래스는 판별 함수 값을 계산하는 decision_function 메서드를 제공한다. ROC 커브는 이 판별 함수 값을 이용하여 다음과 같이 작성한다.

모든 표본 데이터에 대해 판별 함수 값을 계산한다.
계산된 판별 함수 값을 정렬한다.
만약 0이 아닌 가장 작은 판별 함수값을 클래스 구분 기준값으로 하면 모든 표본은 클래스 1(Positive)이 된다. 
   이 때의 Fall-out과 Recall을 계산하면 Recall과 Fall-out이 모두 1이된다.
두번째로 작은 판별 함수값을 클래스 구분 기준값으로 하면 판별 함수 값이 가장 작은 표본 1개를 제외하고 나머지 표본은 클래스 1(Positive)이 된다. 마찬가지로 이 때의 Fall-out과 Recall을 계산하여 기록한다.
가장 큰 판별 함수값이 클래스 구분 기준값이 될 때까지 이를 반복한다. 이 때는 모든 표본이 클래스 0(Negative)으로 판별되며 Recall과 Fall-out이 모두 0이된다.

일반적으로 클래스 판별 기준이 변화함에 따라 Recall과 Fall-out은 같이 증가하거나 감소한다. Recall이 크고 Fall-out이 작은 모형은 좋은 모형으로 생각할 수 있다.
End of explanation
"""


from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
iris = load_iris()
model = LogisticRegression().fit(iris.data, iris.target)

from sklearn.metrics import roc_curve
fpr0, tpr0, thresholds0 = roc_curve(iris.target, model.decision_function(iris.data)[:, 0], pos_label=0)
fpr1, tpr1, thresholds1 = roc_curve(iris.target, model.decision_function(iris.data)[:, 1], pos_label=1)
fpr2, tpr2, thresholds2 = roc_curve(iris.target, model.decision_function(iris.data)[:, 2], pos_label=2)

fpr0, tpr0, thresholds0

"""
Explanation: Multi-Class 예제
End of explanation
"""


plt.plot(fpr0, tpr0, "r-", label="class 0 ") #빨간선이 가장 좋은선
plt.plot(fpr1, tpr1, "g-", label="class 1")
plt.plot(fpr2, tpr2, "b-", label="class 2")
plt.plot([0, 1], [0, 1], 'k--', label="random guess")
plt.xlim(-0.05, 1.0)
plt.ylim(0, 1.05)
plt.xlabel('False Positive Rate (Fall-Out)')
plt.ylabel('True Positive Rate (Recall)')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()

print(confusion_matrix(iris.target, model.predict(iris.data)))

print(classification_report(iris.target, model.predict(iris.data)))

from sklearn.preprocessing import label_binarize
yb0 = label_binarize(iris.target, classes=[0, 1, 2])
yb1 = label_binarize(model.predict(iris.data), classes=[0, 1, 2])

print(yb0[:, 0].sum(), yb1[:, 0].sum())
plt.plot(yb0[:, 0], 'ro-', markersize=10, alpha=0.4, label="actual class 0")
plt.plot(yb1[:, 0], 'bs-', markersize=10, alpha=0.4, label="predicted class 0")
plt.legend()
plt.xlim(0, len(iris.target)-1);
plt.ylim(-0.1, 1.1);

print(yb0[:, 1].sum(), yb1[:, 1].sum())
plt.plot(yb0[:, 1], 'ro-', markersize=10, alpha=0.6, label="actual class 1")
plt.plot(yb1[:, 1], 'bs-', markersize=10, alpha=0.6, label="predicted class 1")
plt.legend()
plt.xlim(45, 145);
plt.ylim(-0.1, 1.1);

"""
Explanation: thresholds를 줄이면 아래로 늘리면 오른쪽으로
End of explanation
"""


from sklearn.metrics import auc
auc(fpr0, tpr0), auc(fpr1, tpr1), auc(fpr2, tpr2)

"""
Explanation: AUC (Area Under the Curve)

AUC는 ROC curve의 면적(밑의 면적)을 뜻한다.
그래서 판단 수치를 볼 수 있음.
Fall-Out 대비 Recall 값이 클 수록 AUC가 1에 가까운 값이며 우수한 모형이다.
End of explanation
"""


from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
X, y = make_classification(n_features=1, n_redundant=0, n_informative=1, n_clusters_per_class=1, weights=[0.9, 0.1], random_state=4)
model = LogisticRegression().fit(X, y)

from sklearn.metrics import precision_recall_curve
pre, rec, thresholds = precision_recall_curve(y, model.decision_function(X))

plt.plot(rec, pre)
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.title('Precision-Recall Curve')
plt.show()

y.sum(), len(y)

"""
Explanation: Precision-Recall 커브
Precision-Recall 커브는 ROC를 계산하는 것과 동일한 방법으로 판별 기준값의 변화에 따른 Precision과 Recall 의 변화를 살펴보는 것이다.
판별 기준값이 증가하면 Recall은 무조건적으로 증가(또는 동일)하지만 Precision은 감소할 수 있다.

보통 precision과 accuracy가 있다. 여기에는 속으면 안 되는 부분이 있다.
precision 성능이 65%가 나왔을 때, 좋다 안좋다를 말할 수 없다.
90%라고 해도 좋다 안좋다의 평가가 불가능하다. 보통은 많이 얘기한다고 한다.
End of explanation
"""


from sklearn.metrics import confusion_matrix
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
X, y = make_classification(n_features=1, n_redundant=0, n_informative=1, n_clusters_per_class=1, random_state=4)
model = LogisticRegression().fit(X, y)

from sklearn.metrics import roc_curve
fpr, tpr, thresholds = roc_curve(y, model.decision_function(X))

plt.plot(fpr, tpr)
plt.plot([0, 1], [0, 1], 'k--', label="random guess")
plt.xlabel('False Positive Rate (Fall-Out)')
plt.ylabel('True Positive Rate (Recall)')
plt.title('Receiver operating characteristic example')
plt.show()

print(confusion_matrix(y, model.predict(X)))

print(classification_report(y, model.predict(X)))

tpr

fpr

v = model.decision_function(X)

v

plt.plot(v)

v2 = sorted(v)

v2

plt.plot(v2)

v.sort()
plt.plot(v)

v < 0

(v < 0).sum()

idx = (v < 0).sum()
v[idx-1], v[idx]

thresholds    # 값이 변하는 변곡점들을 출력한 결과

idx = np.sum(thresholds > 0)
idx

thresholds[idx-1], thresholds[idx]

tpr[idx-1], tpr[idx]

fpr[idx-1], fpr[idx]

idx = (thresholds > 0).sum()
tpr0 = tpr[idx-1:idx+1].mean()
fpr0 = fpr[idx-1:idx+1].mean()

plt.plot(fpr, tpr)
plt.plot([0, 1], [0, 1], 'k--', label="random guess")
plt.plot(fpr0, tpr0, "ro", ms=15, alpha=0.8)  #알파값??
plt.xlabel('False Positive Rate (Fall-Out)')
plt.ylabel('True Positive Rate (Recall)')
plt.title('Receiver operating characteristic example')
plt.show()

from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
iris = load_iris()
model = LogisticRegression().fit(iris.data, iris.target)

from sklearn.metrics import roc_curve
fpr0, tpr0, thresholds0 = roc_curve(iris.target, model.decision_function(iris.data)[:, 0], pos_label=0)
fpr1, tpr1, thresholds1 = roc_curve(iris.target, model.decision_function(iris.data)[:, 1], pos_label=1)
fpr2, tpr2, thresholds2 = roc_curve(iris.target, model.decision_function(iris.data)[:, 2], pos_label=2)

fpr0, tpr0, thresholds0

idx0 = (thresholds0 > 0).sum()
tpr01 = tpr[idx0-1:idx0+1].mean()
fpr01 = fpr[idx0-1:idx0+1].mean()
idx1 = (thresholds1 > 0).sum()
tpr11 = tpr[idx1-1:idx1+1].mean()
fpr11 = fpr[idx1-1:idx1+1].mean()
idx2 = (thresholds2 > 0).sum()
tpr21 = tpr[idx2-1:idx2+1].mean()
fpr21 = fpr[idx2-1:idx2+1].mean()

plt.plot(fpr01, tpr01, "ro", ms=12 , alpha=0.8)
plt.plot(fpr11, tpr11, "go", ms=12)
plt.plot(fpr21, tpr21, "bo", ms=12)
plt.plot(fpr0, tpr0, "r-", label="class 0 ")
plt.plot(fpr1, tpr1, "g-", label="class 1")
plt.plot(fpr2, tpr2, "b-", label="class 2")
plt.plot([0, 1], [0, 1], 'k--', label="random guess")
plt.xlim(-0.05, 1.0)
plt.ylim(0, 1.05)
plt.xlabel('False Positive Rate (Fall-Out)')
plt.ylabel('True Positive Rate (Recall)')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()
#이거 방법 다시 강구해보기

"""
Explanation: 실습
End of explanation
"""


