지난번 포스팅에서는 독립된 2개 표본의 평균 차이를 검정하는 t-test 에 대해서 알아보았습니다. 
이번 포스팅에서는 Python을 이용한 짝을 이룬 표본의 평균 차이를 검정하는 paired t-test 에 대해서 소개하겠습니다. 
(R을 이용한 짝을 이룬 표본에 대한 평균 차이 검정은 https://rfriend.tistory.com/128 를 참고하세요)

짝을 이룬 t-test(paired t-test)는 짝을 이룬 측정치(paired measurements)의 평균 차이가 있는지 없는지를 검정하는 방법입니다. 데이터 값이 짝을 이루어서 측정되었다는 것에 대해 예를 들어보면, 사람들의 그룹에 대해 신약 효과를 알아보기 위해 (즉, 신약 투약 전과 후의 평균의 차이가 있는지) 신약 투약 전과 후(before-and-after)를 측정한 데이터를 생각해 볼 수 있습니다.  

 

 

[ One sample t-test vs. Independent samples t-test vs. Paired samples t-test ]

paired samples t-test, image source: https://datatab.net/tutorial/paired-t-test

 


짝을 이룬 t-test(paired t-test)는 종속 표본 t-test (the dependent samples t-test), 짝을 이룬 차이 t-test (the paired-difference t-test), 매칭된 짝 t-test (the matched pairs t-test), 반복측정 표본 t-test (the repeated-sample t-teset) 등의 이름으로도 알려져있습니다. 동일한 객체에 대해서 전과 후로 나누어서 반복 측정을 합니다.  

 

아래의 도표에는 짝을 이룬 두 표본의 대응 비교 데이터셋에 대한 모습입니다. (Xi, Yi) 가 동일한 대상에 대해서 before-after 로 반복 측정되어서, 각 동일 객체의 전-후의 차이 (즉, Di = Xi - Yi) 에 대해서 검정을 진행하게 됩니다. 

 

paired t-test



[ 가정사항 (Assumptions) ]

(1) 측정 대상이 독립적(independent)이어야 합니다. 하나의 객체에 대한 측정은 어떤 다른 객체의 측정에 영향을 끼치지 않아야 합니다.  
(2) 각 짝을 이룬 측정치는 동일한 객체로 부터 얻어야 합니다. 예를 들면, 신약의 효과를 알아보기 위해 투약 전-후(before-after) 를 측정할 때 동일한 환자에 대해서 측정해야 합니다. 
(3) 짝을 이뤄 측정된 전-후의 차이 값은 정규분포를 따라야한다는 정규성(normality) 가정이 있습니다. 만약 정규성 가정을 충족시키지 못하면 비모수 검정(nonparametric test) 방법을 사용해야 합니다. 

 


[ (예제) 신약 치료 효과 여부 검정 ]

 

새로운 당뇨병 치료제를 개발한 제약사의 예를 계속 들자면, 치료에 지대한 영향을 주는 외부요인을 통제하기 위해 10명의 당뇨병 환자를 선별하여 1달 동안 '위약(placebo)'을 투여한 기간의 혈당 (Xi)과 동일 환자에게 '신약(new medicine)'을 투여한 1달 기간 동안의 혈당 수치(Yi)를 측정하여 짝을 이루어 혈당 차이를 유의수준 5%에서 비교하는 방법이 짝을 이룬 표본에 대한 검정이 되겠습니다. (palacebo 와 신약 투여 순서는 무작위로 선정. 아래 예는 그냥 예시로 아무 숫자나 입력해본 것임. 혈당 수치 이런거 전 잘 몰라요. ^^;)

 

* 귀무가설 (Null Hypothesis, H0): 신약 투입 효과가 없다 (Mu1 = Mu2, ie. Difference=0)
* 대립가설 (Alternative Hypothesis, H1): 신약 투입 효과가 있다 (Mu1 > Mu2, ie. Difference > 0, right-sided test)

 

 

[ Python scipy 모듈을 이용한 paired t-test 실행 ]

 

scipy 모듈의 scipy.stats.ttest_rel 메소드를 사용해서 쌍을 이룬 t-test 를 실행합니다. "Calculate the t-test on TWO RELATED samples of scores, a and b." 라는 설명처럼 TWO RELATED samples 에서 rel 을 타서 메소드 이름을 지었습니다. (저라면 ttest_paired 라고 메소드 이름 지었을 듯요...) 

 

alternative='two-sided' 가 디폴트 설정인데요, 이번 예제에서는 'H1: 신약이 효과가 있다. (즉, 신약 먹기 전보다 신약 먹은 후에 혈당이 떨어진다)' 는 가설을 검정하기 위한 것이므로 alternative='greater' 로 설정을 해주었습니다. 

 

## -- Paired t-test

import numpy as np
from scipy import stats

## sample data-set (repeated measurements of Before vs. After for the same objects)
bef = np.array([51.4, 52.0, 45.5, 54.5, 52.3, 50.9, 52.7, 50.3, 53.8, 53.1])
aft = np.array([50.1, 51.5, 45.9, 53.1, 51.8, 50.3, 52.0, 49.9, 52.5, 53.0])


## paired t-test using Python scipy module
# H0: New medicine is not effective (i.e., no difference b/w before and after)
# H1: New medicine is effective (i.e., there is difference b/w before and after)
stat, p_val = stats.ttest_rel(bef, aft, alternative='greater')

print('statistic:', stat, '   p-value:', p_val)
# statistic: 3.550688262985491    p-value: 0.003104595950799298

 

분석 결과 p-value 가 0.003 으로서 유의수준 0.05 하에서 귀무가설 (H0: 신약은 효과가 없다. 즉, before와 after의 차이가 없다) 을 기각(reject)하고, 대립가설(H1: 신약은 효과가 있다. 즉, before 보다 after의 혈당 수치가 낮아졌다)을 채택(accept) 합니다. 




[ Reference ]
* The Paired t-test
  : https://www.jmp.com/en_nl/statistics-knowledge-portal/t-test/paired-t-test.html
* scipy.stats.ttest_rel 
  (Calculate the t-test on TWO RELATED samples of scores, a and b.)
  : https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.ttest_rel.html

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python을 사용해서 두 집단 간 평균이 같은지 아니면 다른지를 검정하는 t-test 를 해보겠습니다. 

 

연속형 확률분포인 t-분포 (Student's t-distribution) 에 대해서는 https://rfriend.tistory.com/110 를 참고하세요. 

R을 사용한 독립된 두 집단간 모평균 차이에 대한 검정은 https://rfriend.tistory.com/127 를 참고하세요. 

 

모집단의 평균과 분산에 대해서는 알지 못하는 경우가 많으므로, 보통은 모집단에서 무작위로 표본을 추출(random sampling)해서 모집단의 평균과 분산을 추정합니다. 표본의 크기가 작은 집단 간 평균의 차이가 있는지를 검정할 때 t-분포에 기반한 t-통계량(t-statistics)을 사용하여 검정을 합니다. 

 

t-검정은 대상 표본 집단이 1개인지 2개인지에 따라서 아래와 같이 구분할 수 있습니다. 

  * One-sample t-test : 모집단의 평균이 귀무가설의 특정 평균 값과 같은지를 검정

  * Two-sample t-test: 두 모집단의 평균이 같다는 귀무가설을 검정

 

One-sample t-test와 Two-sample t-test에서 사용하는 통계량에 대해서는 아래에 정리해보았습니다. 

 

 

여기서부터는 독립된 두 표본 간의 평균 차이에 대한 t-검정 (independent two-sample t-test) 에 대해서만 자세하게 소개하도록 하겠습니다. 

 

(1) Two-sample t-test 의 가설 (Hypothesis)

 

 - 귀무가설 (Null Hypothesis, H0): Mu1 = M2 (두 모집단의 평균이 같다)

 - 대립가설 (Alternative Hypothesis, H1)

    -. 양측검정 대립가설 (two-sided test H1): Mu1 ≠ Mu2 (두 모집단의 평균이 같지 않다)

    -. 우측검정 대립가설 (right-tailed test H1): Mu1 > M2 (모집단1의 평균이 모집단2의 평균보다 크다)

    -. 좌측검정 대립가설 (left-tailed test H1): M1 < M2 (모집단1의 평균이 모집단2의 평균보다 작다) 

 

t-test 를 통해 나온 p-value 가 유의수준보다 작으면 귀모가설을 기각하고 대립가설을 채택(즉, 두 모집단의 평균이 차이가 있다)하게 됩니다. 

 

 

 

(2) Two-sample t-test 의 가정사항 (Assumptions)

 

Two-sample t-test 의 결과가 유효하기 위해서는 아래의 가정사항을 충족시켜야 합니다. 

 

 (a) 한 표본의 관측치는 다른 표본의 관측치와 독립이다. (independent) 

 (b) 데이터는 정규분포를 따른다. (normally distributed)

 (c) 두 집단의 표본은 동일한 분산을 가진다. (the same variance).

       (--> 이 가설을 만족하지 못하면 Welch's t-test 를 실행합니다.)

 (d) 두 집단의 표본은 무작위 표본추출법을 사용합니다. (random sampling)

 

정규성 검정(normality test)을 위해서 Kolmogorov-Smirnov test, Shapiro-Wilk test, Anderson-Darling test 등을 사용합니다. 등분산성 검정(Equal-Variance test) 을 위해서 Bartlett test, Fligner test, Levene test 등을 사용합니다. 

 

 

 

(3) Python을 이용한 Two-sample t-test 실행 

 

(3-1) 샘플 데이터 생성

 

먼저 numpy 모듈을 사용해서 정규분포로 부터 각 관측치 30개를 가지는 표본을 3개 무작위 추출해보겠습니다. 이중 표본집단 2개는 평균과 분산이 동일한 정규분포로 부터 무작위 추출하였으며, 나머지 1개 집단은 평균이 다른 정규분포로 부터 무작위 추출하였습니다. 

 

## generating sample dataset
import numpy as np

np.random.seed(1004) # for reproducibility
x1 = np.random.normal(loc=0, scale=1, size=30) # the same mean
x2 = np.random.normal(loc=0, scale=1, size=30) # the same mean
x3 = np.random.normal(loc=4, scale=1, size=30) # different mean


x1
# array([ 0.59440307,  0.40260871, -0.80516223,  0.1151257 , -0.75306522,
#        -0.7841178 ,  1.46157577,  1.57607553, -0.17131776, -0.91448182,
#         0.86013945,  0.35880192,  1.72965706, -0.49764822,  1.7618699 ,
#         0.16901308, -1.08523701, -0.01065175,  1.11579838, -1.26497153,
#        -1.02072516, -0.71342119,  0.57412224, -0.45455422, -1.15656742,
#         1.29721355, -1.3833716 ,  0.3205909 , -0.59086187, -1.43420648])

x2
# array([ 0.60998011,  0.51266756,  1.9965168 ,  1.42945668,  1.82880165,
#        -1.40997132,  0.49433367,  0.9482873 , -0.35274099, -0.15359935,
#        -1.18356064, -0.75440273, -0.85981073,  1.14256322, -2.21331694,
#         0.90651805,  2.23629   ,  1.00743665,  1.30584548,  0.46669171,
#        -0.49206651, -0.08727244, -0.34919043, -1.11363541, -1.71982966,
#        -0.14033817,  0.90928317, -0.60012686,  1.03906073, -0.03332287])

x3
# array([2.96575604, 4.15929405, 4.33053582, 4.02563551, 3.90786096,
#        3.08148823, 4.3099129 , 2.75788362, 3.66886973, 2.35913334,
#        3.72460166, 3.94510997, 5.50604364, 2.62243844, 2.74438348,
#        4.16120867, 3.57878295, 4.2341905 , 2.79844805, 5.48131392,
#        4.29105321, 4.4022031 , 3.58533963, 5.00502917, 5.45376705,
#        3.92961847, 4.52897801, 1.62104705, 3.24945253, 5.10641762])


## Box plot
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(10, 8))
sns.boxplot(data=[x1, x2, x3])
plt.xlabel("Group", fontsize=16)
plt.ylabel("Value", fontsize=16)
plt.xticks([0, 1, 2], ["x1", "x2", "x3"], fontsize=14)
plt.show()

 

(3-2) t-test 가설 충족 여부 검정

 

t-검정의 가정사항으로서 정규성 검정(normality test)과 등분산성 검정 (equal variance test) 을 Python의 scipy 모듈을 사용해서 수행해보겠습니다. 

 

* Kolmogorov-Smirnov Test 정규성 검정 

  - (귀무가설, H0): 집단의 데이터가 정규 분포를 따른다. 

  - (대립가설, H1): 집단의 데이터가 정규 분포를 따르지 않는다. 

 

아래에 x1, x2, x3 의 세 집단에 대한 K-S 정규성 검정 결과 p-value 가 모두 유의수준 0.05 보다 크므로 귀무가설을 채택하여 세 집단의 데이터가 정규 분포를 따른다고 볼 수 있습니다. 

 

## (1) Normality test using Kolmogorov-Smirnov Test
import scipy.stats as stats

t_stat_x1, p_val_x1 = stats.kstest(x1, 'norm', args=(x1.mean(), x1.var()**0.5))
t_stat_x2, p_val_x2 = stats.kstest(x2, 'norm', args=(x2.mean(), x2.var()**0.5))
t_stat_x3, p_val_x3 = stats.kstest(x3, 'norm', args=(x3.mean(), x3.var()**0.5))

print('[x1]  t-statistics:', t_stat_x1, '  p-value:', p_val_x1)
print('[x2]  t-statistics:', t_stat_x2, '  p-value:', p_val_x2)
print('[x3]  t-statistics:', t_stat_x3, '  p-value:', p_val_x3)

# [x1]  t-statistics: 0.13719205314969185   p-value: 0.577558008887932
# [x2]  t-statistics: 0.11086245840821829   p-value: 0.8156064477001308
# [x3]  t-statistics: 0.09056001868899977   p-value: 0.9477307432911599

 

 

다음으로 집단 x1과 x2, 집단 x1과 x3에 대한 등분산 가정 검정 결과, p-value 가 모두 유의수준 0.05 보다 크므로 두 집단 간 분산이 같다고 할 수 있습니다. (귀무가설 H0: 두 집단 간 분산이 같다.)

 

## (2) Equal variance test using Bartlett's tes
var_test_stat_x1x2, var_test_p_val_x1x2 = stats.bartlett(x1, x2)
var_test_stat_x1x3, var_test_p_val_x1x3 = stats.bartlett(x1, x3)

print('[x1 vs. x2]', 'statistic:', var_test_stat_x1x2, '  p-value:', var_test_p_val_x1x2)
print('[x1 vs. x3]', 'statistic:', var_test_stat_x1x3, '  p-value:', var_test_p_val_x1x3)

# [x1 vs. x2] statistic: 0.4546474955289549   p-value: 0.5001361557169177
# [x1 vs. x3] statistic: 0.029962346601998174   p-value: 0.8625756934286083

 

처음에 샘플 데이터를 생성할 때 정규분포로 부터 분산을 동일하게 했었으므로 예상한 결과대로 잘 나왔네요. 

 

 

 

(3-3) 독립된 두 표본에 대한 t-test 평균 동질성 여부 검정

 

이제 독립된 두 표본에 대해 t-test 를 실행해서 두 표본의 평균이 같은지 다른지 검정을 해보겠습니다. 

 

 - (귀무가설 H0) Mu1 = Mu2 (두 집단의 평균이 같다)

 - (대립가설 H1) Mu1 ≠ Mu2 (두 집단의 평균이 다르다) 

 

분산은 서로 같으므로 equal_var = True 라고 매개변수를 설정해주었습니다. 

그리고 양측검정(two-sided test) 을 할 것이므로 alternative='two-sided' 를 설정(default)해주면 됩니다. (왜그런지 자꾸 에러가 나서 일단 코멘트 부호 # 로 막아놨어요. scipy 버전 문제인거 같은데요... 흠... 'two-sided'가 default 설정이므로 # 로 막아놔도 문제는 없습니다.)

 

## (3) Identification test using Independent 2 sample t-test

## x1 vs. x2
import scipy.stats as stats
t_stat, p_val = stats.ttest_ind(x1, x2, 
                                #alternative='two-sided', #‘less’, ‘greater’
                                equal_var=True)

print('t-statistic:', t_stat, '   p-value:', p_val)
#t-statistic: -0.737991822907993    p-value: 0.46349499774375136
#==> equal mean


## x1 vs. x3
import scipy.stats as stats
t_stat, p_val = stats.ttest_ind(x1, x3, 
                                #alternative='two-sided', #‘less’, ‘greater’
                                equal_var=True)

print('t-statistic:', t_stat, '   p-value:', p_val)
#t-statistic: -15.34800563666855    p-value: 4.370531118607397e-22
#==> different mean

 

(3-1)에서 샘플 데이터를 만들 때 x1, x2 는 동일한 평균과 분산의 정규분포에서 무작위 추출을 하였으며, x3만 평균이 다른 정규분포에서 무작위 추출을 하였습니다. 

 

위의 (3-3) t-test 결과를 보면 x1, x2 간 t-test 에서는 p-value 가 0.46으로서 유의수준 0.05 하에서 귀무가설(H0)을 채택하여 두 집단 x1, x2 의 평균은 같다고 판단할 수 있습니다. 

 

x1, x3 집단 간 t-test 결과를 보면 p-value 가 4.37e-22 로서 유의수준 0.05 하에서 귀무가설(H0)을 기각(reject)하고 대립가설(H1)을 채택(accept)하여 x1, x3 의 평균이 다르다고 판단할 수 있습니다. 

 

 

[ Reference ]

* Wikipedia Student's t-test: https://en.wikipedia.org/wiki/Student%27s_t-test

* Python scipy.stats.ttest_ind 메소드
: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.ttest_ind.html

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

지난번 포스팅에서는 차원 축소란 무엇이고 왜 하는지(https://rfriend.tistory.com/736)와 투영(projection) 방법을 사용하는 주성분 분석(PCA)을 통한 차원 축소 (https://rfriend.tistory.com/751) 에 대해서 소개하였습니다. 

 

이번 포스팅에서는 manifold learning approche 에 해당하는 비지도학습, 비선형 차원축소 방법으로서 LLE (Locally Linear Embedding) 알고리즘을 설명하겠습니다. 

 

(1) 매니폴드 학습 (Manifold Learning)

(2) Locally Linear Embedding (LLE) 알고리즘

(3) sklearn 을 사용한 LLE 실습

 

 

(1) 매니폴드 학습 (Maniflod Learning)

 

먼저 매니폴드 학습(Manifold Learning)에 대해서 간략하게 살펴보고 넘어가겠습니다.

매니폴드란 각 점 근처의 유클리드 공간과 국부적으로 유사한 위상 공간을 말합니다. (A manifold is a topological space that locally resembles Eucludian space near each point.) [1]  d-차원의 매니폴드는 n-차원 공간 (이때 d < n) 의 부분으로서 d-차원의 초평면(d-dimensional hyperplane)을 국부적으로 닮았습니다. 아래의 3차원 공간의 Swiss Roll 에서 데이터의 구성 특성과 속성을 잘 간직한 2차원의 평면(plane)을 생각해볼 수 있는데요, Swiss Roll 을 마치 돌돌 말린 종이(매니폴드)를 펼쳐서 2차원 공간으로 표현해본 것이 오른쪽 그림입니다. 

manifold geometry: unroll the swiss roll

매니폴드 학습(Manifold Learning)이란 데이터에 존재하는 매니폴드를 모델링하여 차원을 축소하는 기법을 말합니다. 매니폴드 학습은 대부분 실세계의 고차원 데이터가 훨씬 저차원의 매니폴드에 가깝게 놓여있다는 매니폴드 가정(Manifold Assumption) 혹은 매니폴드 가설(Maniflod Hypothesis)에 기반하고 있습니다. [2] 

 

위의 비선형인 3차원 Swiss Roll 데이터를 선형 투영 기반의 주성분 분석(PCA, Principal Component Analysis)이나 다차원척도법(MDS, Multi-Dimensional Scaling) 로 2차원으로 차원을 축소하려고 하면 데이터 내 존재하는 매너폴드를 학습하지 못하고 데이터가 뭉개지고 겹쳐지게 됩니다. 

 

매니폴드 가설은 분류나 회귀모형과 같은 지도학습을 할 때 고차원의 데이터를 저차원의 매니폴드 공간으로 재표현했을 때 더 간단하고 성과가 좋을 것이라는 묵시적인 또 다른 가설과 종종 같이 가기도 합니다. 하지만 이는 항상 그런 것은 아니며, 데이터셋이 어떻게 구성되어 있느냐에 전적으로 의존합니다. 

 

 

 

(2) Locally Linear Embedding (LLE) 알고리즘

 

LLE 알고리즘은 "Nonlinear Dimensionality Reduction by Locally Linear Embedding (2000)" [3] 논문에서 주요 내용을 간추려서 소개하겠습니다. 

 

LLE 알고리즘은 주성분분석이나(PCA) 다차원척도법(MDS)와는 달리 광범위하게 흩어져있는 데이터 점들 간의 쌍을 이룬 거리를 추정할 필요가 없습니다. LLE는 국소적인 선형 적합으로 부터 전역적인 비선형 구조를 복원합니다 (LLE revocers global nonlinear structure from locally linear fits.). 

 

논문에서 소개한 LLE 3단계 절차(steps of locally linear embedding)는 꽤 간단합니다. 

    (1단계) 각 데이터 점의 이웃을 선택 (Select neighbors)

    (2단계) 이웃으로부터 선형적으로 가장 잘 재구성하는 가중치를 계산 (Reconstruct with linear weights)

    (3단계) 가중치를 사용해 저차원의 임베딩 좌표로 매핑 (Map to embedded coordinates)

 

Steps of locally linear embedding (source: Sam T. Roweis and Lawrence K. Saul)

 

1단계에서 각 데이터 점별로 이웃을 할당할 때는 데이터 점들 간의 거리를 계산하는데요, 가령 K 최근접이웃(K nearest neighbors) 기법을 사용할 수 있습니다. 

 

 

2단계에서 각 데이터 점들의 이웃들로부터 각 점을 가장 잘 재구성하는 선형 회귀계수(linear coefficients, linear weights)를 계산해서 국소적인 기하 특성을 간직한 매너폴드를 학습합니다.  아래는 재구성 에러를 측정하는 비용함수인데요, 원래의 데이터점과 이웃들로 부터 계산한 선형 모형으로 재구성한 값과의 거리를 제곱하여 모두 더한 값입니다. 

the cost function of reconstruction

위의 비용함수를 최소로 하는 가중치 벡터 Wij 를 계산하게 되는데요, 이때 2가지 제약조건(constraints)이 있습니다. 

  - (a) 각 데이터 점은 단지 그들의 이웃들로 부터만 재구성됩니다.

          (만약 Xj 가 Xi의 이웃에 속하지 않는 데이터점이면 가중치 Wij = 0 이 됩니다.)

  - (b) 가중치 행렬 행의 합은 1이 됩니다. (sum(Wij) = 1) 

 

위의 2가지 제약조건을 만족하면서 비용함수를 최소로 하는 가중치 Wij 를 구하면 특정 데이터 점에 대해 회전(rotations), 스케일 조정(recalings), 그리고 해당 데이터 점과 인접한 데이터 점의 변환(translations) 에 있어 대칭(symmetry)을 따릅니다. 이 대칭에 의해서 (특정 참조 틀에 의존하는 방법과는 달리) LLE의 재구성 가중치는 각 이웃 데이터들에 내재하는 고유한 기하학적 특성(저차원의 매너폴드)을 모델링할 수 있게됩니다. 

 

 

3단계에서는 고차원(D) 벡터의 각 데이터 점 Xi 를 위의 2단계에서 계산한 가중치를 사용하여 매니폴드 위에 전역적인 내부 좌표를 표현하는 저차원(d) 벡터 Yi 로 매핑합니다.  이것은 아래의 임베팅 비용 함수를 최소로 하는 저차원 좌표 Yi (d-mimensional coordinates) 를 선택하는 것으로 수행됩니다. 

the cost function of embedding

임베팅 비용 함수는 이전의 선형 가중치 비용함수와 마찬가지로 국소 선형 재구성 오차를 기반으로 합니다. 하지만 여기서 우리는 Yi 좌표를 최적화하는 동안 선형 가중치 Wij 를 고정합니다. 임베팅 비용 함수는 희소 N x N 고유값 문제(a sparse N X N eignevalue problem)를 풀어서 최소화할 수 있습니다. 이 선형대수 문제를 풀면 하단의 d차원의 0이 아닌 고객벡터는 원점을 중심으로 정렬된 직교 좌표 집합을 제공합니다. 

 

LLE 알고리즘은 희소 행렬 알고리듬(sparse matrix algorithms)을 활용하기 위해 구현될 때 비선형 차원축소 기법 중 하나인 Isomap 보다 더 빠른 최적화와 많은 문제에 대해 더 나은 결과를 얻을 수 있는 몇 가지 장점이 있습니다. [4]

 

아래에 3차원 Swiss Roll 데이터를 여러가지 비선형 차원 축소 기법을 사용해서 적용한 결과인데요 [5], LLE 는 수행 시간이 짧은 장점이 있지만 매니폴드가 약간 찌그러져 있는 한계가 있는 반면에, Isomap과 Hessian LLE 는 국지적인 데이터 형상과 관계를 잘 재표현한 저차원 매니폴드를 잘 잡아내지만 수행 시간이 LLE 대비 굉장히 오래걸리는 단점이 있습니다. 

 

 

 

(3) sklearn 을 사용한 LLE 실습

 

먼저 sklearn 모듈의 make_swiss_roll 메소드를 사용해서 데이터 점 1,000개를 가지는 3차원의 Swiss Roll 샘플 데이터셋을 만들어보겠습니다. X 는 데이터 점이고, t는 매니폴드 내 점의 주 차원에 따른 샘플의 단변량 위치입니다. 

 

## Swiss Roll sample dataset
## ref: https://scikit-learn.org/stable/modules/generated/sklearn.datasets.make_swiss_roll.html
from sklearn.datasets import make_swiss_roll

X, t = make_swiss_roll(n_samples=1000, noise=0.1, random_state=1004)


## X: The points.
X[:5]
# array([[ 1.8743272 , 18.2196214 , -4.75535504],
#        [12.43382272, 13.9545544 ,  2.91609936],
#        [ 8.02375359, 14.23271056, -8.67338106],
#        [12.23095692,  2.37167446,  3.64973091],
#        [-8.44058318, 15.47560926, -5.46533069]])


## t: The univariate position of the sample according to the main dimension of the points in the manifold.
t[:10]
# array([ 5.07949952, 12.78467229, 11.74798863, 12.85471755,  9.99011767,
#         5.47092408,  6.89550966,  6.99567358, 10.51333994, 10.43425738])

 

 

 

다음으로 sklearn 모듈에 있는 LocallyLinearEmbedding 메소드를 사용해서 위에서 생성한 Swiss Roll 데이터에 대해 LLE 알고리즘을 적용하여 2차원 데이터로 변환을 해보겠습니다. 

 

## Manifold Learning
from sklearn.manifold import LocallyLinearEmbedding

lle = LocallyLinearEmbedding(n_components=2, n_neighbors=10)

X_reduced_lle = lle.fit_transform(X)

X_reduced_lle[:10]
# array([[-0.0442308 ,  0.0634603 ],
#        [ 0.04241534,  0.01060574],
#        [ 0.02712308,  0.01121903],
#        [ 0.04396569, -0.01883799],
#        [ 0.00275144,  0.01550906],
#        [-0.04178513,  0.05415933],
#        [-0.03073913,  0.023496  ],
#        [-0.02880368, -0.0230327 ],
#        [ 0.0109238 , -0.02566617],
#        [ 0.00979253, -0.02309815]])

 

 

마지막으로 matplotlib 모듈을 사용해서 2차원 평면에 위에서 LLE 로 매핑한 데이터를 시각화해보겠습니다. 색깔을 t 로 구분하였습니다. 

 

## 2D Scatter Plot
import matplotlib.pyplot as plt

plt.rcParams['figure.figsize'] = (8, 8)
plt.scatter(X_reduced_lle[:, 0], X_reduced_lle[:, 1], c=t, cmap=plt.cm.hot)
plt.title("Unrolled Swiss Roll by LLE", fontsize=20)
plt.xlabel("$y_1$", fontsize=14)
plt.ylabel("$y_2$", fontsize=14)
plt.axis([-0.065, 0.055, -0.1, 0.12])
plt.grid(True)

plt.show()

 

 

 

[Reference]

[1] Wikipedia, "Manifold", https://en.wikipedia.org/wiki/Manifold

[2] Aurelien Geron, "Hands-On Machine Learning with Scikit-Learn & Tensorflow"

[3] Sam T. Roweis, Lawrence K. Saul, "Nonlinear Dimensionality Reduction by Locally Linear Embedding"

[4] Wikipedia, "Nonlinear Dimensionality Reduction",  https://en.wikipedia.org/wiki/Nonlinear_dimensionality_reduction

[5] Nik Melchior, "Manifold Learning, Isomap and LLE":  https://www.cs.cmu.edu/~efros/courses/AP06/presentations/melchior_isomap_demo.pdf 

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

728x90
반응형
Posted by Rfriend
,

지난 포스팅에서는 차원 축소란 무엇이고 왜 하는지, 무슨 방법이 있는지에 대해서 알아보았습니다.

(https://rfriend.tistory.com/736)  차원축소하는 방법에는 크게 Projection-based dimensionality reduction, Manifold Learning 의 두가지 방법이 있다고 했습니다. 

 

이번 포스팅에서는 투사를 통한 차원축소 방법(dimensionality reduction via projection approach) 으로서 주성분분석을 통한 차원축소(dimensionality reduction using PCA, Principal Component Analysis)에 대해서 소개하겠습니다. 

 

(1) 주성분분석(PCA, Principal Component Analysis)을 통한 차원 축소

(2) 특이값 분해 (SVD, Singular Value Decomposition)을 통한 차원 축소

 

 

 

(1) 주성분 분석(PCA, Principal Component Analysis)을 통한 차원 축소

 

주성분 분석(PCA)의 핵심 아이디어만 간략하게 소개하자면요, 피쳐 공간(Feature Space)에서 데이터의 분산을 최대로 잡아낼 수 있는 축을 제1 주성분 축으로 잡고, 이 제1 주성분 축과 직교(orthogonal)하는 축을 제2 주성분 축으로 잡고, ..., 이렇게 최대 변수의 개수 p 개 만큼 주성분 축을 잡아줍니다. (물론, 차원축소를 하는 목적이면 주성분 개수 m 이 변수 개수 p 보다는 작아야 겠지요). 그리고 축을 회전시켜주면 돼요. 

 

아래의 예시 도면을 보면 파란색 제 1 주성분 축 (1st principal component axis)이 데이터 분산을 가장 많이 설명하고 있는 것을 알 수 있습니다. 빨간색 점선의 제 2 주성분 축(2nd principal component axis) 은 제1 주성분 축과 직교하구요. 

 

 

Principal Component Analysis

 

이제 Python 을 가지고 실습을 해볼께요. 

(R로 주성분 분석 하는 것은 https://rfriend.tistory.com/61 를 참고하세요.)

 

먼저 예제로 사용할 iris 데이터셋을 가져오겠습니다. sepal_length, sepal_width, petal_length, petal_width 의 4개 변수를 가진 데이터셋인데요, 4개 변수 간 상관관계 분석을 해보니 상관계수가 0.8 이상으로 꽤 높게 나온 게 있네요. 주성분분석으로 차원축소 해보면 이쁘게 나올거 같아요. 

 

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

## loading IRIS dataset
from sklearn.datasets import load_iris
data = load_iris()


data['data'][:10]
# array([[5.1, 3.5, 1.4, 0.2],
#        [4.9, 3. , 1.4, 0.2],
#        [4.7, 3.2, 1.3, 0.2],
#        [4.6, 3.1, 1.5, 0.2],
#        [5. , 3.6, 1.4, 0.2],
#        [5.4, 3.9, 1.7, 0.4],
#        [4.6, 3.4, 1.4, 0.3],
#        [5. , 3.4, 1.5, 0.2],
#        [4.4, 2.9, 1.4, 0.2],
#        [4.9, 3.1, 1.5, 0.1]])


## converting into pandas DataFrame
iris_df = pd.DataFrame(
    data['data'], 
    columns=['sepal_length', 'sepal_width', 
             'petal_length', 'petal_width'])

iris_df.head()
# 	sepal_length	sepal_width	petal_length	petal_width
# 0	5.1	3.5	1.4	0.2
# 1	4.9	3.0	1.4	0.2
# 2	4.7	3.2	1.3	0.2
# 3	4.6	3.1	1.5	0.2
# 4	5.0	3.6	1.4	0.2


## correlation matrix
iris_df.corr()
# 	sepal_length	sepal_width	petal_length	petal_width
# sepal_length	1.000000	-0.117570	0.871754	0.817941
# sepal_width	-0.117570	1.000000	-0.428440	-0.366126
# petal_length	0.871754	-0.428440	1.000000	0.962865
# petal_width	0.817941	-0.366126	0.962865	1.000000

 

 

주성분 분석은 비지도 학습 (Unsupervised Learning) 이다보니 정답이라는게 없습니다. 그래서 분석가가 주성분의 개수를 지정해주어야 하는데요, 주성분의 개수가 적을 수록 차원 축소가 많이 되는 반면 정보 손실(information loss)가 발생하게 되며, 반면 주성분 개수가 많을 수록 정보 손실은 적겠지만 차원 축소하는 의미가 퇴색됩니다. 그래서 적절한 주성분 개수를 선택(hot to decide the number of principal components)하는게 중요한데요, 주성분의 개수별로 설명 가능한 분산의 비율 (percentage of explained variance by principal components) 을 많이 사용합니다. 

 

아래의 예에서는 첫번째 주성분이 분산의 92.4%를 설명하고, 두번째 주성분이 분산의 5.3%를 설명하므로, 주성분 1 & 2 까지 사용하면 전체 분산의 97.7%를 설명할 수 있게 됩니다. (즉, 원래 4개 변수를 2개의 차원으로 축소하더라도 분산의 97.7%를 설명 가능하다는 뜻) 

 

참고로, 만약 주성분분석 결과를 지도학습(가령, 회귀분석)의 설명변수 인풋으로 사용한다면, cross validation을 사용해서 주성분 개수별로 모델의 성능을 평가(가령, 회귀분석의 경우 MSE)해서, 모델 성능지표가 가장 좋은 주성분 개수를 선택하는 것도 좋은 방법입니다. 

 

## how to decide the number of Principal Components
from sklearn.decomposition import PCA

pca = PCA(random_state=1004)
pca.fit_transform(iris_df)


## percentage of variance explained
print(pca.explained_variance_ratio_)
# [0.92461872 0.05306648 0.01710261 0.00521218]


## Principal 1 & 2 explain about 97.8% of variance
plt.rcParams['figure.figsize'] = (7, 7)
plt.plot(range(1, iris_df.shape[1]+1), pca.explained_variance_ratio_)
plt.xlabel("number of Principal Components", fontsize=12)
plt.ylabel("% of Variance Explained", fontsize=12)
plt.show()

Explained Variance by Principal Components

 

 

이제 주성분 개수를 2개로 지정(n_components=2)해서 주성분 분석을 실행해보겠습니다. Python의 sklearn 모듈의 decomposition.PCA 메소드를 사용하겠습니다. 

 

## Dimensionality Reduction with n_components=2
pca = PCA(n_components=2, random_state=1004)
iris_pca = pca.fit_transform(iris_df)


iris_pca[:10]
# array([[-2.68412563,  0.31939725],
#        [-2.71414169, -0.17700123],
#        [-2.88899057, -0.14494943],
#        [-2.74534286, -0.31829898],
#        [-2.72871654,  0.32675451],
#        [-2.28085963,  0.74133045],
#        [-2.82053775, -0.08946138],
#        [-2.62614497,  0.16338496],
#        [-2.88638273, -0.57831175],
#        [-2.6727558 , -0.11377425]])

 

 

 

위에서 실행한 주성분분석 결과를 가지고 시각화를 해보겠습니다. 4개 변수를 2개의 차원으로 축소를 했기 때문에 2차원의 산점도로 시각화를 할 수 있습니다. 이때 iris 데이터셋의 target 속성정보를 이용해서 붓꽃의 품종별로 색깔과 모양을 달리해서 산점도로 시각화해보겠습니다. 

 

## Visualization

## target
data['target'][:5]
# array([0, 0, 0, 0, 0])


## mapping target name using numpy vectorization
species_map_dict = {
    0: 'setosa', 
    1: 'versicolor', 
    2: 'virginica'
}

iris_pca_df = pd.DataFrame({
    'pc_1': iris_pca[:, 0], 
    'pc_2': iris_pca[:, 1], 
    'species': np.vectorize(species_map_dict.get)(data['target']) # numpy broadcasting
})


iris_pca_df.head()
# pc_1	pc_2	species
# 0	-2.684126	0.319397	setosa
# 1	-2.714142	-0.177001	setosa
# 2	-2.888991	-0.144949	setosa
# 3	-2.745343	-0.318299	setosa
# 4	-2.728717	0.326755	setosa


import seaborn as sns
import matplotlib.pyplot as plt

plt.rcParams['figure.figsize'] = (7, 7)
sns.scatterplot(
    x='pc_1', 
    y='pc_2',
    hue='species', 
    style='species',
    s=100,
    data=iris_pca_df
)

plt.title('PCA result of IRIS dataset')
plt.xlabel('Principal Component 1', fontsize=14)
plt.ylabel('Principal Component 2', fontsize=14)
plt.show()

PCA result of iris dataset

 

 

 

(2) 특이값 분해 (SVD, Singular Value Decomposition)을 통한 차원 축소

 

선형대수의 특이값 분해의 결과로 나오는 U, sigma, V 에서 V 가 주성분 분석의 주성분에 해당합니다.  

특이값 분해(SVD, Singular Value Decomposition)에 대한 이론적인 소개는 https://rfriend.tistory.com/185 를 참고하세요. 

 

numpy 모듈의 linalg.svd 메소드를 사용하여 특이값 분해를 하려고 할 때 먼저 데이터 표준화(standardization)을 수작업으로 진행해 줍니다. (sklearn 으로 주성분분석을 할 때 sklearn 모듈이 내부적으로 알아서 표준화해서 진행해줌). 

 

## Standardization first
from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
X_scaled = scaler.fit_transform(data['data'])


## PCA assumes that the dataset is centered around the origin.
X_centered = data['data'] - data['data'].mean(axis=0)
X_centered[:5]
# array([[-0.74333333,  0.44266667, -2.358     , -0.99933333],
#        [-0.94333333, -0.05733333, -2.358     , -0.99933333],
#        [-1.14333333,  0.14266667, -2.458     , -0.99933333],
#        [-1.24333333,  0.04266667, -2.258     , -0.99933333],
#        [-0.84333333,  0.54266667, -2.358     , -0.99933333]])

 

 

웨에서 표준화한 데이터를 numpy 모듈의 linalg.svd 메소드를 사용하여 특이값 분해를 해준 후에, V 를 transpose (T) 해주어서 첫번째와 두번째 열의 값을 가져오면 제1 주성분, 제2 주성분을 얻을 수 있습니다. 

 

## standard matrix factorization using SVD
U, s, V = np.linalg.svd(X_scaled.T)


## V contains all the principal components
pc_1 = V.T[:, 0]
pc_2 = V.T[:, 1]


## check pc_1, pc_2
pc_1[:10]
# array([0.10823953, 0.09945776, 0.1129963 , 0.1098971 , 0.11422046,
#        0.099203  , 0.11681027, 0.10671702, 0.11158214, 0.10439809])


pc_2[:10]
# array([-0.0409958 ,  0.05757315,  0.02920003,  0.05101939, -0.0552418 ,
#        -0.12718049, -0.00406897, -0.01905755,  0.09525253,  0.04005525])

 

 

 

위에서 특이값분해(SVD)로 구한 제1 주성분, 제2 주성분을 가지고 산점도를 그려보겠습니다. 이때 iris 의 target 별로 색깔과 모양을 달리해서 시각화를 해보겠습니다. 

 

## Visualization

iris_svd_df = pd.DataFrame({
    'pc_1': pc_1, 
    'pc_2': pc_2, 
    'species': np.vectorize(species_map_dict.get)(data['target']) # numpy broadcasting
})


import seaborn as sns
import matplotlib.pyplot as plt

plt.rcParams['figure.figsize'] = (7, 7)
sns.scatterplot(
    x='pc_1', 
    y='pc_2',
    hue='species', 
    style='species',
    s=100,
    data=iris_svd_df
)

plt.title('SVD result of IRIS dataset')
plt.xlabel('Principal Component 1', fontsize=14)
plt.ylabel('Principal Component 2', fontsize=14)
plt.show()

dimensionality reduction by SVD

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요. 

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python 을 이용해서 의사결정나무 (Decision Tree) 를 시각화하는 3가지 방법을 소개하겠습니다. 

 

(1) sklearn.tree.export_text 메소드를 이용해서 의사결정나무를 텍스트로 인쇄하기 (text representation)

(2) sklearn.tree.plot_tree 메소드와 matplotlib 을 이용해서 의사결정나무 시각화

(3) sklearn.tree.export_graphviz 메소드와 graphviz 를 이용해서 의사결정나무 시각화

 

 

먼저, 예제로 사용할 iris 데이터셋과 필요한 모듈을 불어오겠습니다. 

 

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt 

df = pd.DataFrame({
    'x1': [1, 2, 2, 2, 2, 3, 3, 4, 5, 5], 
    'x2': [2, 2, 3, 4, 5, 4, 6, 3, 2, 5], 
    'cat': [0, 1, 1, 0, 0, 1, 0, 1, 1, 0]
})


df

# x1	x2	cat
# 0	1	2	0
# 1	2	2	1
# 2	2	3	1
# 3	2	4	0
# 4	2	5	0
# 5	3	4	1
# 6	3	6	0
# 7	4	3	1
# 8	5	2	1
# 9	5	5	0


## scatter plot
import seaborn as sns

plt.figure(figsize=(7, 7))
sns.scatterplot(x='x1', y='x2', hue='cat', style='cat', s=100, data=df)
plt.show()

scatter plot by group

 

 

 

(0) sklearn.tree 메소드를 이용해서 의사결정나무(Decision Tree) 모델 훈련하기

 

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
from sklearn import tree

## getting X, y values
X = df[['x1', 'x2']]
y = df['cat']

## initiating DecisionTreeClassifer method
dt_clf = DecisionTreeClassifier(random_state = 1004)


## fitting a decision tree classifier
dt_clf_model = dt_clf.fit(X, y)


## feature importances
dt_clf_model.feature_importances_

#array([0.43809524, 0.56190476])

 

 

 

(1) sklearn.tree.export_text 메소드를 이용해서 의사결정나무를 텍스트로 인쇄하기 (text representation)

 

의사결정나무 모델을 적합한 후에 사용자 인터페이스가 없는 애플리케이션에서 로그(log) 텍스트로 모델 적합 결과를 확인할 때 사용할 수 있습니다. 또는 의사결정나무의 적합된 룰을 SQL 로 구현하여 운영 모드로 적용하고자 할 때 아래처럼 텍스트로 인쇄를 하면 유용하게 사용할 수 있습니다. 

 

## Text Representation
dt_clf_model_text = tree.export_text(dt_clf_model)

print(dt_clf_model_text)

|--- feature_1 <= 4.50
|   |--- feature_0 <= 1.50
|   |   |--- class: 0
|   |--- feature_0 >  1.50
|   |   |--- feature_1 <= 3.50
|   |   |   |--- class: 1
|   |   |--- feature_1 >  3.50
|   |   |   |--- feature_0 <= 2.50
|   |   |   |   |--- class: 0
|   |   |   |--- feature_0 >  2.50
|   |   |   |   |--- class: 1
|--- feature_1 >  4.50
|   |--- class: 0

 

 

 

(2) sklearn.tree.plot_tree 메소드와 matplotlib 을 이용해서 의사결정나무 시각화

 

(3)번 처럼 Graphviz 를 설치하지 않아도, matplotlib 을 사용해서 간편하게 의사결정나무를 시각화 할 때 유용합니다. 편리한 대신에 (3)번 Graphviz 대비 자유도가 떨어지는 단점이 있습니다. 

 

## Plot Tree with plot_tree
fig = plt.figure(figsize=(15, 8))
_ = tree.plot_tree(dt_clf_model, 
                  feature_names=['x1', 'x2'],
                  class_names=['0', '1'],
                  filled=True)

 

 

 

(3) sklearn.tree.export_graphviz 메소드와 graphviz 를 이용해서 의사결정나무 시각화

 

Graphviz 의 시각화 기능을 fully 사용할 수 있으므로 매우 강력한 시각화 방법이라고 할 수 있습니다. 

Graphviz 설치하고 Decision Tree 시각화하기는 https://rfriend.tistory.com/382 를 참고하세요. 

DOT language 에 대해서는 https://graphviz.org/doc/info/lang.html 를 참고하세요. 

 

## Visualizing Tree using Graphviz
from sklearn import tree
import graphviz

## exporting tree in DOT format
## refer to: https://scikit-learn.org/stable/modules/generated/sklearn.tree.export_graphviz.html
tree_dot = tree.export_graphviz(
    dt_clf_model, 
    feature_names=['x1', 'x2'], 
    class_names=['0', '1'],
    filled=True
)


## draw graph using Graphviz
dt_graph = graphviz.Source(tree_dot, format='png')
dt_graph

 

decition tree visualization using graphviz

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요~!

 

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python의 SciPy 모듈을 사용해서 각 원소 간 짝을 이루어서 유클리디언 거리를 계산(calculating pair-wise distances)하는 방법을 소개하겠습니다. 본문에서 scipy 의 거리 계산함수로서 pdist()와 cdist()를 소개할건데요, 반환하는 결과물의 형태에 따라 적절한 것을 선택해서 사용하면 되겠습니다. 마지막으로 scipy 에서 제공하는 거리를 계산하는 기준(distance metric)에 대해서 알아보겠습니다. 

(Scikit-Learn 모듈에도 거리 계산 함수가 있는데요, SciPy 모듈에 거리 측정 기준이 더 많아서 SciPy 모듈로 소개합니다.) 

 

(1) scipy.spatial.distance.pdist(): returns condensed distance matrix Y.

(2) scipy.spatial.distance.cdist(): returns M by N distance matrix.

(3) 거리 측정 기준(distance metric)

 

 

calculating pair-wise distances using python SciPy

 

 

먼저, 예제로 사용할 간단한 샘플 데이터셋으로서 x와 y의 두 개의 칼럼에 대해 4개의 원소를 가지는 numpy array 로 만들어보겠습니다. 그리고 matplotlib 으로 산점도를 그려서 4개의 원소에 대해 x와 y의 좌표에 산점도를 그려서 확인해보겠습니다. 

 

ID x y
1 3 2
2 3 3
3 5 5
4 6 7

 

import numpy as np
import matplotlib.pyplot as plt

X = np.array([
    # x, y
    [3, 2], # ID 1
    [3, 3], # ID 2
    [5, 5], # ID 3
    [6, 7], # ID 4
])

plt.plot(X[:, 0], X[:, 1], 'o')
plt.show()

scatter plot

 

 

(1) scipy.spatial.distance.pdist(X, metric='euclidean'): returns condensed distance matrix Y.

 

scipy.spatial.distance.pdist(X, metric='euclidean') 함수는 X 벡터 또는 행렬에서 각 원소(관측치) 간 짝을 이루어서 유클리디언 거리를 계산해줍니다. 그리고 응축된 형태의 거리 행렬(condensed distance matrix)을 반환합니다. 아래의 (1)번 pdist() 계산 결과가 반환된 형태를 (2) cdist() 로 계산 결과가 반환된 형태와 비교해보면 이해가 쉬울 거예요. 

 

pdist(X, metric='eudlidean') 계산 결과는 각 관측치 간 짝을 이룬 거리 계산이므로 아래 값들의 계산 결과입니다. 거리 계산 결과는 자기 자신과의 거리인 대각행렬 부분은 제외하고, 각 원소 간 쌍을 이룬 부분에 대해서만 관측치의 순서에 따라서 반환되었습니다. (예: ID1 : ID2, ID1 : ID3, ID1 : ID4, ID2 : ID3, ID2 : ID4, ID3 : ID4)

 

   * DISTANCE_euclidean(ID 1, ID 2) = sqrt((3-3)^2 + (2-3)^2) = 1.00

   * DISTANCE_euclidean(ID 1, ID 3) = sqrt((3-5)^2 + (2-5)^2) = 3.60

   * DISTANCE_euclidean(ID 1, ID 4) = sqrt((3-6)^2 - (2-7)^2) = 5.83

   * DISTANCE_euclidean(ID 2, ID 3) = sqrt((3-5)^2 - (3-5)^2) = 2.82

   * DISTANCE_euclidean(ID 2, ID 4) = sqrt((3-6)^2 - (3-7)^2) = 5.00

   * DISTANCE_euclidean(ID 3, ID 4) = sqrt((5-6)^2 - (5-7)^2) = 2.23

 

# scipy.spatial.distance.pdist(X, metric='euclidean', *, out=None, **kwargs)
# : Pairwise distances between observations in n-dimensional space.
# : Returns a condensed distance matrix Y.

from scipy.spatial import distance

X_pdist = distance.pdist(X, metric='euclidean')

X_pdist
# array([1.        , 3.60555128, 5.83095189, 2.82842712, 5.        ,
#        2.23606798])

 

 

 

(2) scipy.spatial.distance.cdist(): returns M by N distance matrix.

 

scipy.spatial.distance.cdist(XA, XB, metric='euclidean') 함수는 원소(관측치) 간 쌍을 이루어 유클리디언 거리를 계산합니다만, 위의 (1) pdist() 함수와는 달리, 

 

  - (a) input 으로 XA, XB 의 두 개의 행렬 (혹은 벡터)를 받으며, (vs. pdist() 는 X 행렬 한 개만 받음)

  - (b) output 으로 M by N 거리 행렬을 반환합니다. (vs. pdist() 는 condensed distance matrix 를 반환)

 

하는 차이점이 있습니다.  위의 (1)번 scipy.spatial.distance.pdist() 함수와 (2) scipy.spatial.distance.cdist() 함수의 input, output을 비교해보면 이해가 쉬울 거예요. 

 

# scipy.spatial.distance.cdist(XA, XB, metric='euclidean', *, out=None, **kwargs)
# : Compute distance between each pair of the two collections of inputs.
# : A M by N distance matrix is returned.(M for X, N for Y)

from scipy.spatial import distance

X_cdist = distance.cdist(X, X, metric='euclidean')
X_cdist
# array([[0.        , 1.        , 5.83095189, 3.60555128],
#        [1.        , 0.        , 5.        , 2.82842712],
#        [5.83095189, 5.        , 0.        , 2.23606798],
#        [3.60555128, 2.82842712, 2.23606798, 0.        ]])

 

 

 

(3) 거리 측정 기준(distance metric)

 

위의 (1), (2)번에서는 거리 측정 기준으로 디폴트 옵션인 '유클리디언 거리(metric='euclidean distance') 를 사용해서 원소 간 쌍을 이룬 거리를 계산하였습니다. 

 

scipy 모듈은 유클리디어 거리 외에도 다양한 거리 측정 기준을 제공합니다. (맨하탄 거리, 표준화 거리, 마할라노비스 거리, 자카드 거리, 코사인 거리, 편집 거리에 대해서는 아래의 Reference 의 링크를 참고하세요.)

 

[ scipy.spatial.distance.pdist(), cdist() 함수의 metric options (알파벳 순서) ]

‘braycurtis’, ‘canberra’, ‘chebyshev’, ‘cityblock’, ‘correlation’, ‘cosine’, ‘dice’, ‘euclidean’, ‘hamming’, ‘jaccard’, ‘jensenshannon’, ‘kulsinski’, ‘kulczynski1’, ‘mahalanobis’, ‘matching’, ‘minkowski’, ‘rogerstanimoto’, ‘russellrao’, ‘seuclidean’, ‘sokalmichener’, ‘sokalsneath’, ‘sqeuclidean’, ‘yule’.

 

아래에는 metric='cityblcok' 매개변수 설정을 통해서 '맨하탄 거리(Manhattan distance)'를 계산하여 보았습니다. cdist() 함수를 사용하였으므로 4 by 4 행렬의 형태로 관측치 간 쌍을 이룬 거리 계산 결과를 반환합니다. 

 

   * DISTANCE_manhattan(ID 1, ID 2) = |3-3|+|2-3| = 1

   * DISTANCE_manhattan(ID 1, ID 3) = |3-5|+|2-5| = 5

   * DISTANCE_manhattan(ID 1, ID 4) = |3-6|+|2-7| = 8

   * DISTANCE_manhattan(ID 2, ID 3) = |3-5|+|3-5| = 4

   * DISTANCE_manhattan(ID 2, ID 4) = |3-6|+|3-7| = 7

   * DISTANCE_manhattan(ID 3, ID 4) = |5-6|+|5-7| = 3

 

X
# array([[3, 2],
#        [3, 3],
#        [5, 5],
#        [6, 7]])


## Manhattan Distance
from scipy.spatial import distance

X_cdist_cityblock = distance.cdist(X, X, metric='cityblock')

X_cdist_cityblock
# array([[0., 1., 5., 8.],
#        [1., 0., 4., 7.],
#        [5., 4., 0., 3.],
#        [8., 7., 3., 0.]])

 

 

 

[ Reference ]

 

[1] scipy.spatial.distance.pdist
https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.pdist.html#scipy.spatial.distance.pdist
[2] scipy.spatial.distance.cdist
https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html#scipy.spatial.distance.cdist
[3] 맨하탄 거리(Manhattan distance), 유클리드 거리(Euclidean distance), 표준화 거리(), 마할라노비스 거리
https://rfriend.tistory.com/199
[4] 자카드 거리 (Jaccard distance): https://rfriend.tistory.com/318
[5] 코사인 거리 (cosine distance): https://rfriend.tistory.com/319
[6] 편집거리 (edit distance, Levenshtein metric): https://rfriend.tistory.com/320

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

주대각성분 또는 주대각선 (main diagonal, leading diagonal, principal diagonal, primary diagonal, major diagonal) 은 행렬의 k행 k열의 성분들을 말합니다. 

 

대각행렬(diagonal matrix) 및 그외 특수한 행렬에 대해서는 https://rfriend.tistory.com/141 를 참고하세요. 

 

 mail diagonal

 

 

이번 포스팅에서는 Python NumPy 를 이용해서 주대각성분(main diagonal)을 다루는 방법을 소개하겠습니다. 

 

(1) 주대각성분 가져오기 (returning a main diagonal)

(2) 주대각성분을 기준으로 수평/ 수직으로 뒤집기 (flip horizontally/ vertically by main diagonal) 

(3) 주대각성분 채우기 (filling main diagonal) 

(4) 대각행렬 만들기 (creating a diagonal matrix) 

 

 

 

(1) 주대각성분 가져오기 (returning a main diagonal): np.diagonal()

 

import numpy as np

## making a sample 2-D array
arr = np.arange(9).reshape(3, 3)
arr
# array([[0, 1, 2],
#        [3, 4, 5],
#        [6, 7, 8]])


## (1) main diagonal
np.diagonal(arr)
# array([0, 4, 8])


## (2) main diagonal
arr.diagonal()
# array([0, 4, 8])

 

 

 

(2) 수평/ 수직으로 뒤집은 후 주대각성분 가져오기 (returning main diagonal after flipping) 

 

(2-1) 수평으로 뒤집은 후 주대각성분 가져오기 (returning main diagonal after flipping horizontally)
          : np.fliplr(arr).diagonal()

 

# Horizontal flip
np.fliplr(arr) # flip from left to right
# array([[2, 1, 0],
#        [5, 4, 3],
#        [8, 7, 6]])


## main diagonal of horizontal flip
np.fliplr(arr).diagonal()
# array([2, 4, 6])

 

 

 

(2-2) 수직으로 뒤집은 후 주대각성분 가져오기 (returning main diagonal after flipping vertically)
         : np.flipud(arr).diagonal()

 

# Vertical flip
np.flipud(arr) # flip from up to down
# array([[6, 7, 8],
#        [3, 4, 5],
#        [0, 1, 2]])


## main diagonal of vertical flip
np.flipud(arr).diagonal()
# array([6, 4, 2])

 

 

 

(3) 주대각성분 채우기 (filling main diagonal)  : np.fill_diagonal()

 

## filling the main diagonal of the given array of any dimensionality
## using np.fill_diagonal(a, val)
np.fill_diagonal(arr, val=999)


arr
# array([[999,   1,   2],
#        [  3, 999,   5],
#        [  6,   7, 999]])

 

 

 

(4) 대각행렬 만들기 (creating a diagonal matrix)  

 

대각행렬(diagonal matrix)은 주대각성분에만 원소 값이 있고, 주대각성분 외의 원소는 값이 모두 '0'인 행렬을 말합니다.

아래의 예에서는 NumPy로 대각행렬을 만들기 위해서, 먼저 np.zeros([m, n]) 로 '0'의 값만을 가지는 m by n 행렬을 만든 후에, np.fill_diagonal(array, val) 의 val 부분에 원하는 주대각성분의 값(예: val=[1, 2, 3])을 입력해주어서 대각행렬(diag(1, 2, 3))을 만들어 보겠습니다. 

 

## making a diagonal matrix: diag(1, 2, 3)
arr2 = np.zeros([3, 3])
arr2
# array([[0., 0., 0.],
#        [0., 0., 0.],
#        [0., 0., 0.]])


## filling main diagonal with [1, 2 3] values using np.fill_diagonal()
np.fill_diagonal(arr2, val=[1, 2, 3])

arr2
# array([[1., 0., 0.],
#        [0., 2., 0.],
#        [0., 0., 3.]])

 

 

 

[ Reference ]

1. 주대각성분 (또는 주대각선, main diagonal)
    : https://en.wikipedia.org/wiki/Main_diagonal 

2. numpy.diagonal()
    : https://numpy.org/doc/stable/reference/generated/numpy.diagonal.html

3. numpy.fill_diagonal()
    : https://numpy.org/doc/stable/reference/generated/numpy.fill_diagonal.html

4. 대각행렬(diagonal matrix) 및 특수행렬: https://rfriend.tistory.com/141

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 Python pandas의 문자열 Series에서 문자열 패턴 매칭을 통해서 특정 패턴이 포함되어 있는지 여부를 확인하고, 특정 매칭을 포함한 데이터를 가져오는 방법을 소개하겠습니다. 

 

(1) pandas 문자열 Series에서 한개의 문자열 패턴 매칭하기 
     : Series.str.contains(pattern)

(2) pandas DataFrame에서 한개의 문자열 패턴 매칭이 되는 데이터 가져오기

(3) pandas DataFrame에서 여러개의 문자열 패턴 매팅이 되는 데이터 가져오기

 

 

먼저, 예제로 사용할 문자열이 포함된 DataFrame을 만들어보겠습니다. pandas 의 contains() 함수를 사용해서 문자열 뿐만 아니라 NaN 값과 '1004' 숫자도 포함시켜서 문자열 매칭 시 처리방법을 소개하겠습니다. 

 

## importing modules
import numpy as np
import pandas as pd

## creating a pandas DataFrame with strings, NaN, digit
df = pd.DataFrame({
    'id': [1, 2, 3, 4, 5, 6, 7]
    , 'fruit': ['apple', 'PERSIMON', 'grapes', 'mango', 'peach and perl', 
                np.NaN, 
                '1004']
})


print(df)
#    id           fruit
# 0   1           apple
# 1   2        PERSIMON
# 2   3          grapes
# 3   4           mango
# 4   5  peach and perl
# 5   6             NaN
# 6   7            1004

 

 

 

(1) pandas 문자열 Series에서 한개의 문자열 패턴 매칭하기: Series.str.contains(pattern)

 

먼저, 위에서 만든 DataFrame에서 'fruit' 칼럼만 가져와서 's1' 이라는 이름의 Series 를 만들어보겠습니다. 

 

## pandas Series
s1 = df['fruit']

print(type(s1)) # padnas.Series
print(s1)

# <class 'pandas.core.series.Series'>
# 0             apple
# 1          PERSIMON
# 2            grapes
# 3             mango
# 4    peach and perl
# 5               NaN
# 6              1004
# Name: fruit, dtype: object

 

 

pandas 의 contains() 메소드는 '문자열 Series (Series of a string)' 을 대상으로 문자열 매칭을 해서 Boolean Series 를 반환합니다. contains() 메소드의 구문은 아래와 같습니다. 

 

Series.str.contains(pattern, case=True, flags=0, na=None, regex=True)

 

이때 Series.str.contains() 메소드는 문자열 Series에 대하여 패턴 매칭(pattern matching)을 할 때 문자열 그 자체(literal itself)와 함께 정규표현식(regex=True: regular expression)까지도 사용해서 패턴 매칭을 할 수 있으며, '대/소문자 구분 (case=True: case sensitive)하며, 'NaN' 값에 대해서는 'NaN'을 반환(na=None)합니다. 

 

아래 예에서는 문자열 Series 's1'에 대해서 문자열 'pe'가 들어있는 패턴 매칭을 해서 Boolean Series 를 반환한 예입니다. (대소문자 구분, NaN은  NaN 반환) 

 

## returning a Series of Booleans using a literal pattern
s1.str.contains('pe')

# 0    False
# 1    False   # <-- case sensitive
# 2     True
# 3    False
# 4     True
# 5      NaN   # <-- returning NaN for NaN values
# 6    False
# Name: fruit, dtype: object

 

 

 

(2) pandas DataFrame에서 한개의 문자열 패턴 매칭이 되는 데이터 가져오기

 

pandas DataFrame에서 특정 문자열 칼럼에 대해서 문자열 패턴 매칭한 결과인 Boolean Series 를 이용해서 해당 행의 값만 가져올 수 있습니다. 이때 만약 문자열 패턴 매칭 결과 Boolean Seires 에 NaN 값이 포함되어 있을 경우 아래와 같은 ValueError 가 발생합니다. 

 

ValueError: Cannot mask with non-boolean array containing NA / NaN valu

 

 ## ValueError: Cannot mask with non-boolean array containing NA / NaN values
df[df['fruit'].str.contains('pe')]

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-ee5e3bc73f2f> in <module>
      1 ## ValueError: Cannot mask with non-boolean array containing NA / NaN values
----> 2 df[s1.str.contains('pe')]

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/frame.py in __getitem__(self, key)
   2890 
   2891         # Do we have a (boolean) 1d indexer?
-> 2892         if com.is_bool_indexer(key):
   2893             return self._getitem_bool_array(key)
   2894 

~/opt/anaconda3/lib/python3.8/site-packages/pandas/core/common.py in is_bool_indexer(key)
    132                 na_msg = "Cannot mask with non-boolean array containing NA / NaN values"
    133                 if isna(key).any():
--> 134                     raise ValueError(na_msg)
    135                 return False
    136             return True

ValueError: Cannot mask with non-boolean array containing NA / NaN values

 

 

'ValueError: Cannot mask with non-boolean array containing NA/NaN values' 를 해결하기 위해서는 Series.str.contains(pattern, na=False) 처럼 NaN 값을 Boolean의 'False'로 설정해주면 됩니다. 

 

## Specifying na to be False instead of NaN replaces NaN values with False.
df[df['fruit'].str.contains('pe'
                   , na=False) # specifying NA to be False
  ]

#  id	fruit
# 2	3	grapes
# 4	5	peach and perl

 

 

만약 문자열 매칭을 할 때 '대/소문자 구분없이 (case insensitive)' 하려면 'case=False' 옵션을 설정해주면 됩니다. 

아래 예에서는 case=False 로 설정한 상태에서 'pe' 문자열 매칭을 했더니 'PERSIMON' 대문자도 매칭이 되어서 가져오기가 되었습니다. 

 

## Specifying case sensitivity using case.
df[df['fruit'].str.contains('pe'
                   , na=False
                   , case=False) # case = False
  ] 

#   id	fruit
# 1	2	PERSIMON   # <-- case insensitive
# 2	3	grapes
# 4	5	peach and perl

 

 

Series.str.contains() 함수에는 정규표현식(regex=True: Regular Expression)을 사용해서 문자열 매칭을 할 수 있습니다.  아래의 예에서는 정규표현식을 이용해서 '숫자가 포함된 ('\\d' : returning any digits)' 문자열을 가져와보겠습니다. 

 

## returning any digit using regular expression
df[df['fruit'].str.contains(
    '\\d'        # returning any digit
    , regex=True # using regular expression
    , na=False
    )
  ]

#   id	fruit
# 6	7	1004

 

 

 

(3) pandas DataFrame에서 여러개의 문자열 패턴 매팅이 되는 데이터 가져오기

 

이번에는 문자열 매칭을 할 때 '여러개의 문자열 패턴 (multiple strings of pattern)' 과 매칭되는 문자열을 확인하고, pandas DataFrame으로 부터 해당 행의 데이터를 가져와보겠습니다. 

 

여러개의 문자열 패턴을 표현할 때 '|' 가 'or' 를 나타냅니다. 아래의 예의 경우, ['ap' or 'ma' or 'gr'] 이 포함된 문자열을 매칭해서 Boolean String을 반환하고 싶을 때 ['ap'|'ma'|'gr'] 을 패턴으로 입력해주면 됩니다. Python의 내장함수(built-in function) 중에서 join() 메소드를 이용하면 여러개의 문자열을 '|' 구분자(separator)를 넣어서 하나의 문자열로 묶어줄 수 있습니다. ('|'.join(['ap', 'ma', 'gr']) 은 ==> 'ap|ma|gr' 을 반환하며, ==> ['ap' or 'ma' or 'gr'] 을 의미함)

 

## join() method joins all itmes in a tuple into a string with a separartor
'|'.join(['ap', 'ma', 'gr'])

# 'ap|ma|gr'


## Returning ‘apple’ or ‘mango’ or 'grapes' 
## when either expression occurs in a string.
s1.str.contains(
    '|'.join(['ap', 'ma', 'gr']) # 'ap|ma|gr', ie. 'ap' or 'ma' or 'gr'
    , na=False
    , case=False
)

# 0     True
# 1    False
# 2     True
# 3     True
# 4    False
# 5    False
# 6    False
# Name: fruit, dtype: bool

 

 

이제 pandas DataFrame 에서 'fruit' 칼럼에서 'ap' or 'ma' or 'gr' 문자열이 포함되어 있는 모든 행을 가져와보겠습니다. 

 

## indexing data using a Series of Booleans 
df[df['fruit'].str.contains(
    '|'.join(['ap', 'ma', 'gr'])
    , na=False
    , case=False
    )
  ]

#   id	fruit
# 0	1	apple
# 2	3	grapes
# 3	4	mango

 

 

[ Reference ]

[1] pandas.Series.str.contains()
    : https://pandas.pydata.org/docs/reference/api/pandas.Series.str.contains.html

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

이번 포스팅에서는 정답 레이블 Y는 없고 오직 설명변수 X 만을 사용해서 데이터 내 관계, 패턴, 구조를 탐색하는데 사용하는 비지도학습 중에서 차원 축소 (Dimensionality Reduction) 에 대한 이론적인 부분을 알아보겠습니다. (Python 코딩은 다음번 포스팅부터 해볼께요.)

 

1. 차원 축소란 무엇인가? (What is Dimensionality Reduction?)

2. 왜 차원 축소가 필요한가? (Why is Dimensionality Reduction?) 

3. 차원 축소의 단점은? (Pitfall of Dimensionality Reduction)

4. 차원 축소하는 방법은? (How to do Dimensionality Reduction?)

 

 

 

1. 차원 축소란 무엇인가? (What is Dimensionality Reduction?)

 

차원 축소 (Dimensionality Reduction, 또는 Dimension Reduction) 는 저차원 표현(low-dimensional representation)이 고차원 원본 데이터의 의미 있는 특성을 이상적으로 원래의 차원에 가깝게 유지할 수 있도록 고차원 공간에서 저차원 공간으로 데이터를 변환(the transformation of data from a high-dimensional space into a low-dimensional space)하는 것을 말합니다.[wikipedia, 1]

차원 축소를 하게 되면 원본 데이터로부터 일부 정보 손실 (information loss)이 발생하는데요, 원본 데이터로부터의 정보 손실을 최소화하면서 저차원으로 얼마나 잘 재표현(representation)할 수 있느냐가 관건이 되겠습니다. 

 

 

[ 차원 축소 (Dimensionality Reduction) vs. 군집 분석 (Clustering) ]

dimensionality reduction vs. clustering

 

차원 축소 (Dimensionality Reduction)와 군집분석 (Clustering) 모두 정답 Label Y 가 필요없이 특성 변수(features) 만을 가지고 데이터의 구조와 특성을 파악하는 비지도학습(Unsupervised Learning) 에 속합니다. 

 

군집분석 (Clustering) 은 관측치, 객체 간의 유사성(similarity)을 측정해서 유사한 관측치, 객체끼리 그룹을 만드는 분석 기법을 말합니다. 반면에 차원 축소 (Dimensionality Reduction) 는 특성 변수(features, variables)를 대상으로 변수 간 상관성에 기초해서 고차원에서 저차원 공간으로 재표현하는 변수 변환을 말합니다. 

 

 

 

2. 왜 차원 축소가 필요한가? (Why is Dimensionality Reduction?) 

 

차원 축소를 하는 이유, 활용 목적에는 여러가지가 있는데요, 먼저 기계학습 측면에서는 차원 축소가 차원의 저주 (Curse of Dimensionality)를 피하고, 과적합 (Overfitting) 을 방지하는데 효과적입니다. 

 

차원의 저주(Curse of Dimensionality) 는 일상적 경험의 3차원 물리적 공간 등 저차원적 환경에서 일어나지 않는 고차원적 공간에서 데이터를 분석하고 정리할 때 발생하는 다양한 현상을 말합니다. 이 표현은 Richard E. Bellman 이 동적 프로그래밍의 문제를 고려할 때 처음 사용하였습니다. 차원 저주 현상은 수치 분석, 샘플링, 조합론, 기계 학습, 데이터 마이닝 및 데이터베이스와 같은 영역에서 발생합니다. 이러한 문제의 공통 주제는 차원성이 증가하면 공간의 부피가 너무 빠르게 증가하여 사용 가능한 데이터가 희박해진다는 것입니다. 신뢰할 수 있는 결과를 얻기 위해 필요한 데이터의 양은 차원성에 따라 기하급수적으로 증가하는 경우가 많습니다. 또한 데이터를 구성하고 검색하는 것은 종종 객체가 유사한 속성을 가진 그룹을 형성하는 영역을 감지하는 데 의존합니다. 그러나 고차원 데이터에서는 모든 객체가 여러 면에서 희박하고 유사하지 않아 일반적인 데이터 구성 전략이 효율적이지 못하게 됩니다. [wikipedia, 2]

 

통계학의 선형회귀모형에는 독립변수(independent variable, 혹은 설명변수 explanatory variable, 혹은 예측변수 predictor variable) 들 간의 독립을 가정합니다. (독립변수라는 이름 자체에 변수 간 독립을 명시함. ㅎㅎ)  그런데 모델링에 인풋으로 사용하는 설명변수 간 다중공선성(multicolleniarity)이 존재할 경우 추정된 회귀계수의 분산이 커져서 모델이 불안정하고 과적합에 빠지는 위험이 있습니다. 독립변수간 다중공선성(multicolleniarity)을 해결하는 몇가지 방법 중의 하나가 독립변수간 상관성에 기반해서 차원을 축소한 후에 회귀모형을 적합하는 방법입니다. 

 

탐색적 데이터 분석을 하는 단계에서 변수 간 관계를 파악하기 위해서 산점도(scatter plot)으로 시각화(visualization) 해서 보면 효과적인데요, 만약 특성변수(features)의 개수가 여러개일 경우에는 2개 특성변수 간 조합의 개수가 기하급수적으로 늘어나기 때문에 모든 조합을 시각화해서 살펴보는 것에 어려움이 있습니다.  이럴 경우, 차원축소를 해서 소수의 차원에 대해서 시각화를 하면 많은 양의 정보를 효과적으로 시각화해서 데이터 특성을 탐색해볼 수 있습니다. 

 

이밖에도 차원 축소를 하면 이후의 데이터 처리, 분석 시에 연산 속도를 향상(performance incease)시킬 수 있습니다. 그리고 데이터를 압축(data compression)하여 데이터 저장이나 전송 효율을 높이는데도 차원 축소를 사용할 수 있습니다. 

 

 

 

3. 차원 축소의 단점은? (Pitfall of Dimensionality Reduction)

 

차원 축소를 하게 되면 원본 데이터 대비 정도의 차이일 뿐 필연적으로 정보 손실 (Information Loss) 이 발생합니다. 그리고 원본 데이터 대비 차원 축소한 데이터를 해석하는데도 어려움이 (hard to interprete) 생깁니다. 또한 차원 축소를 위한 데이터 변환 절차가 추가되므로 데이터 파이프 라인 (data pipeline) 이 복잡해지는 단점도 있습니다. 

 

 

 

4. 차원 축소하는 방법은? (How to do Dimensionality Reduction?)

 

차원 축소하는 방법은 크게 (Linear) Projection(Non-linear) Manifold Learning 의 2가지로 나눌 수 있습니다. 

 

(4-1) Projection-based Dimensionality Reduction: 주성분분석 (PCA, Principal Component Analysis), 특이값 분해 (Singular Value Decomposition), 요인분석 (Factor Analysis)

 

(4-2) Manifold Learning: LLE (Locally-Linear Embedding), Isomap, Kernel Principal Component Analysis, Autoencoders, SOM(Self-Organizing Map) 

 

dimensionality reduction methods and algorithms

 

 

다음번 포스팅에서는 linear projection 방법 중에서 Python 의 Sklearn 모듈을 활용하여 주성분분석 (PCA, Principal Component Analysis)을 하는 방법을 소개하겠습니다. 

 

 

[ Reference ] 

[1] Wikipedia - Dimensionality Reduction: https://en.wikipedia.org/wiki/Dimensionality_reduction

[2] Wikipedia - Curse of Dimensionality: https://en.wikipedia.org/wiki/Curse_of_dimensionality

[3] Wikipedia - Nonlinear Dimensionality Reduction: https://en.wikipedia.org/wiki/Nonlinear_dimensionality_reduction

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,

시각화를 하다보면 기존의 그래프에 가독성을 높이기 위해 여러가지 종류의 도형을 추가해야 할 때가 있습니다.  Python  matplotlib.artist 의 Patch 클래스는 아래의 화면 캡쳐에서 보는 것처럼 Shadow, Spine, Wedge, Circle, Ellipse, Arrow, Rectangle, RegularPolygon, FancyArrowPatch, PathPatch 등의 다양한 도형을 쉽게 그릴 수 있는 클래스를 지원합니다. 

 

 

[ matplotlib artist inheritance diagrams, reference for matplotlib artists ]

 

matplotlib.artist Inheritance Diagrams and Reference for Matplotlib artists (* source: matplotlib.org)

 

이들 Reference for Matplotlib artists 중에서 이번 포스팅에서는 matplotlib의 add_patch() 함수matplotlib.patches 클래스의 다양한 artists 를 사용해서 

 

(1) matplotlib 그래프에 직사각형 추가하기 (adding a Rectangle in matplotlib's plot)

(2) matplotlib 그래프에 원 추가하기 (adding a Circle in matplotlib's plot)

(3) matplotlib 그래프에 타원 추가하기 (adding a Ellipse in matplotlib's plot)

(4) matplotlib 그래프에 다각형 추가하기 (adding a Polygon in matplotlib's plot)

(5) matplotlib 그래프에 화살표 추가하기 (adding a Arrow in matplotlib's plot)

 

하는 방법을 소개하겠습니다. 

 

 

먼저, 시각화를 위해 필요한 Python 모듈을 불러오고, 예제로 사용할 간단한 샘플 데이터셋을 만들어보겠습니다. 

 

## importing modules for visualization and data generation
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np


## generating sample datasets
np.random.seed(1004)
x1 = np.random.uniform(low=1.5, high=4.5, size=100)
y1 = np.random.uniform(low=1.5, high=2.5, size=100)
x2 = np.random.normal(loc=7, scale=0.6, size=100)
y2 = np.random.normal(loc=7, scale=0.6, size=100)
x = np.concatenate((x1, x2), axis=0)
y = np.concatenate((y1, y2), axis=0)

 

 

(1) matplotlib 그래프에 직사각형 추가하기 (adding a Rectangle in matplotlib's plot)

 

코드가 추가적인 설명이 필요 없을 정도로 간단하므로 예제 코드와 그 결과로 그려진 그래프 & 추가된 도형 위에 matplotlib.matches 클래스의 각 artist 별 매개변수를 표기해놓는 것으로 설명을 갈음하겠습니다. 

 

import matplotlib.pyplot as plt
import matplotlib.patches as patches

## plotting
fig, ax = plt.subplots(figsize=(7, 7))
ax.set_title('Adding a Rectangle in plot', fontsize=16)
ax.set_xlim(0.0, 10.0)
ax.set_ylim(0.0, 10.0)
ax.set_xlabel('X')
ax.set_ylabel('Y')

ax.plot(x, y, 'ro', alpha=0.5)

## (1) a Rectangle patch
## https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Rectangle.html
ax.add_patch(
   patches.Rectangle(
       (1.0, 1.0),      # (x, y) coordinates of left-bottom corner point
       4, 2,            # width, height
       edgecolor = 'blue',
       linestyle = 'dashed', 
       fill = True,
       facecolor = 'yellow',
   ))

plt.show()

adding a patch of a rectangle in matplotlib

 

 

 

(2) matplotlib 그래프에 원 추가하기 (adding a Circle in matplotlib's plot)

 

import matplotlib.pyplot as plt
import matplotlib.patches as patches

## plotting
fig, ax = plt.subplots(figsize=(7, 7))
ax.set_title('Adding a Circle in plot', fontsize=16)
ax.set_xlim(0.0, 10.0)
ax.set_ylim(0.0, 10.0)
ax.set_xlabel('X')
ax.set_ylabel('Y')

ax.plot(x, y, 'ro', alpha=0.5)

## (2) a Circle patch
## https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Circle.html
ax.add_patch(
   patches.Circle(
       (7.0, 7.0),      # (x, y) coordinates of center point
       radius = 2.,     # radius
       edgecolor = 'black',
       linestyle = 'dotted', 
       fill = True,
       facecolor = 'lightgray',
   ))

plt.show()

adding a patch of a circle in matplotlib

 

 

 

(3) matplotlib 그래프에 타원 추가하기 (adding a Ellipse in matplotlib's plot)

 

import matplotlib.pyplot as plt
import matplotlib.patches as patches

## plotting
fig, ax = plt.subplots(figsize=(7, 7))
ax.set_title('Adding an Ellipse in plot', fontsize=16)
ax.set_xlim(0.0, 10.0)
ax.set_ylim(0.0, 10.0)
ax.set_xlabel('X')
ax.set_ylabel('Y')

ax.plot(x, y, 'ro', alpha=0.5)

## (3) A scale-free ellipse.
## https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Ellipse.html
ax.add_patch(
   patches.Ellipse(
       xy = (5, 5), # xy xy coordinates of ellipse centre.
       width = 5,   # width Total length (diameter) of horizontal axis.
       height = 10, # height Total length (diameter) of vertical axis.
       angle = -40, # angle Rotation in degrees anti-clockwise. 0 by default
       edgecolor = 'black',
       linestyle = 'solid', 
       fill = True,
       facecolor = 'yellow',
   ))

plt.show()

adding a patch of an ellipse in matplotlib

 

 

 

(4) matplotlib 그래프에 다각형 추가하기 (adding a Polygon in matplotlib's plot)

 

다각형(Polygon)을 추가하려면 다각형의 각 꼭지점의 xy 좌표의 모음을 numpy array 형태로 입력해주면 됩니다. 그리고 closed=True 를 설정해주면 xy 좌표들의 모음의 처음 좌표와 마지막 좌표를 연결해서 다각형의 닫아주어서 완성해줍니다. 

 

import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np

## plotting
fig, ax = plt.subplots(figsize=(7, 7))
ax.set_title('Adding a Polygon in plot', fontsize=16)
ax.set_xlim(0.0, 10.0)
ax.set_ylim(0.0, 10.0)
ax.set_xlabel('X')
ax.set_ylabel('Y')

ax.plot(x, y, 'ro', alpha=0.5)

## (4) a Polygon patch
## https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Polygon.html
ax.add_patch(
   patches.Polygon(
       # xy is a numpy array with shape Nx2.
       np.array([[1, 1], [5, 1], [9, 5], [9, 9], [6, 9], [1, 4]]), # xy
       closed=True,
       edgecolor = 'black',
       linestyle = 'dashdot', 
       fill = True,
       facecolor = 'lightgray',
   ))

plt.show()

adding a patch of a polygon in matplotlib

 

 

 

(5) matplotlib 그래프에 화살표 추가하기 (adding a Arrow in matplotlib's plot)

 

import matplotlib.pyplot as plt
import matplotlib.patches as patches

## plotting
fig, ax = plt.subplots(figsize=(7, 7))
ax.set_title('Adding an Arrow in plot', fontsize=16)
ax.set_xlim(0.0, 10.0)
ax.set_ylim(0.0, 10.0)
ax.set_xlabel('X')
ax.set_ylabel('Y')

ax.plot(x, y, 'ro', alpha=0.5)

## (5) a Arrow patch
## https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Arrow.html
ax.add_patch(
   patches.Arrow(
       x = 3, # x coordinate of the arrow tail.
       y = 5, # y coordinate of the arrow tail.
       dx = 0, # dx Arrow length in the x direction.
       dy = -2, # dy Arrow length in the y direction.
       # width: Scale factor for the width of the arrow. 
       width = 1, # With a default value of 1, the tail width is 0.2 and head width is 0.6.
       edgecolor = 'black',
       linestyle = 'solid', 
       fill = True,
       facecolor = 'yellow',
   ))

plt.show()

adding a patch of an arrow in matplotlib

 

 

 

[ Reference ]

* Python matplotlib's Inheritance Diagrams 

   : https://matplotlib.org/stable/api/artist_api.html#artist-api

* Reference for matplotlib artists

   : https://matplotlib.org/stable/gallery/shapes_and_collections/artist_reference.html#sphx-glr-gallery-shapes-and-collections-artist-reference-py 

* Python matplotlib's Rectangle patch
   : https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Rectangle.html

* Python matplotlib's Circle patch
   : https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Circle.html

* Python matplotlib's scale-free Ellipse
   : https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Ellipse.html

* Python matploblib's Polygon patch
   : https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Polygon.html

* Python matplotlib's Arrow patch
   : https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Arrow.html

 

 

이번 포스팅이 많은 도움이 되었기를 바랍니다. 

행복한 데이터 과학자 되세요!  :-)

 

728x90
반응형
Posted by Rfriend
,