Post

파이썬 데이터분석 클러스터와 차원축소 실습

1
2
3
4
5
6
### 개발환경 세팅하기

# ▶ 한글 폰트 다운로드
!sudo apt-get install -y fonts-nanum
!sudo fc-cache -fv
!rm ~/.cache/matplotlib -rf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
  fonts-nanum
0 upgraded, 1 newly installed, 0 to remove and 49 not upgraded.
Need to get 10.3 MB of archives.
After this operation, 34.1 MB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu jammy/universe amd64 fonts-nanum all 20200506-1 [10.3 MB]
Fetched 10.3 MB in 0s (39.8 MB/s)
debconf: unable to initialize frontend: Dialog
debconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 78, <> line 1.)
debconf: falling back to frontend: Readline
debconf: unable to initialize frontend: Readline
debconf: (This frontend requires a controlling tty.)
debconf: falling back to frontend: Teletype
dpkg-preconfigure: unable to re-open stdin: 
Selecting previously unselected package fonts-nanum.
(Reading database ... 123599 files and directories currently installed.)
Preparing to unpack .../fonts-nanum_20200506-1_all.deb ...
Unpacking fonts-nanum (20200506-1) ...
Setting up fonts-nanum (20200506-1) ...
Processing triggers for fontconfig (2.13.1-4.2ubuntu5) ...
/usr/share/fonts: caching, new cache contents: 0 fonts, 1 dirs
/usr/share/fonts/truetype: caching, new cache contents: 0 fonts, 3 dirs
/usr/share/fonts/truetype/humor-sans: caching, new cache contents: 1 fonts, 0 dirs
/usr/share/fonts/truetype/liberation: caching, new cache contents: 16 fonts, 0 dirs
/usr/share/fonts/truetype/nanum: caching, new cache contents: 12 fonts, 0 dirs
/usr/local/share/fonts: caching, new cache contents: 0 fonts, 0 dirs
/root/.local/share/fonts: skipping, no such directory
/root/.fonts: skipping, no such directory
/usr/share/fonts/truetype: skipping, looped directory detected
/usr/share/fonts/truetype/humor-sans: skipping, looped directory detected
/usr/share/fonts/truetype/liberation: skipping, looped directory detected
/usr/share/fonts/truetype/nanum: skipping, looped directory detected
/var/cache/fontconfig: cleaning cache directory
/root/.cache/fontconfig: not cleaning non-existent cache directory
/root/.fontconfig: not cleaning non-existent cache directory
fc-cache: succeeded
1
2
3
4
5
6
7
8
9
10
11
12
# ▶ 한글 폰트 설정하기
import matplotlib.pyplot as plt
plt.rc('font', family='NanumBarunGothic')
plt.rcParams['axes.unicode_minus'] =False

# ▶ Warnings 제거
import warnings
warnings.filterwarnings('ignore')

# ▶ Google drive mount or 폴더 클릭 후 구글드라이브 연결
from google.colab import drive
drive.mount('/content/drive')
1
Mounted at /content/drive
1
2
3
4
5
6
7
8
9
10
11
12
13
14
##########################################
### 한글이 깨지는 경우 아래 코드 실행하기 !!!###
##########################################
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm

# 나눔고딕 폰트를 설치합니다.
!apt-get install -y fonts-nanum
!fc-cache -fv

# 설치된 나눔고딕 폰트를 matplotlib에 등록합니다.
font_path = '/usr/share/fonts/truetype/nanum/NanumGothic.ttf'
fm.fontManager.addfont(font_path)
plt.rcParams['font.family'] = 'NanumGothic'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
fonts-nanum is already the newest version (20200506-1).
0 upgraded, 0 newly installed, 0 to remove and 49 not upgraded.
/usr/share/fonts: caching, new cache contents: 0 fonts, 1 dirs
/usr/share/fonts/truetype: caching, new cache contents: 0 fonts, 3 dirs
/usr/share/fonts/truetype/humor-sans: caching, new cache contents: 1 fonts, 0 dirs
/usr/share/fonts/truetype/liberation: caching, new cache contents: 16 fonts, 0 dirs
/usr/share/fonts/truetype/nanum: caching, new cache contents: 12 fonts, 0 dirs
/usr/local/share/fonts: caching, new cache contents: 0 fonts, 0 dirs
/root/.local/share/fonts: skipping, no such directory
/root/.fonts: skipping, no such directory
/usr/share/fonts/truetype: skipping, looped directory detected
/usr/share/fonts/truetype/humor-sans: skipping, looped directory detected
/usr/share/fonts/truetype/liberation: skipping, looped directory detected
/usr/share/fonts/truetype/nanum: skipping, looped directory detected
/var/cache/fontconfig: cleaning cache directory
/root/.cache/fontconfig: not cleaning non-existent cache directory
/root/.fontconfig: not cleaning non-existent cache directory
fc-cache: succeeded
1
2
3
4
import os
import pandas as pd
import numpy as np
import seaborn as sns

데이터 준비하기


데이터 준비하기

  • 데이터 출처: Kaggle Credit Card Dataset

  • 데이터 명세

No.표준항목명영문명설명표현형식/단위예시
1고객 IDCUST_ID고객을 식별하기 위한 고유 ID-C10001
2잔액BALANCE신용카드 계좌의 현재 잔액N40.9
3잔액 업데이트 빈도BALANCE_FREQUENCY잔액이 업데이트 되는 빈도N0.818
4총 구매액PURCHASES신용카드로 이루어진 총 구매액N95.4
5일회성 구매액ONEOFF_PURCHASES일회성으로 이루어진 구매액N0.0
6할부 구매액INSTALLMENTS_PURCHASES할부로 이루어진 구매액N95.4
7현금 서비스 금액CASH_ADVANCE현금 서비스로 인출한 금액N0.0
8구매 빈도PURCHASES_FREQUENCY구매가 이루어진 빈도N0.167
9일회성 구매 빈도ONEOFF_PURCHASES_FREQUENCY일회성 구매가 이루어진 빈도N0.0
10할부 구매 빈도PURCHASES_INSTALLMENTS_FREQUENCY할부 구매가 이루어진 빈도N0.083
11현금 서비스 빈도CASH_ADVANCE_FREQUENCY현금 서비스가 이루어진 빈도N0.0
12현금 서비스 거래 횟수CASH_ADVANCE_TRX현금 서비스 거래의 횟수N0
13구매 횟수PURCHASES_TRX총 구매 거래의 횟수N2
14신용 한도CREDIT_LIMIT신용카드의 신용 한도N1000.0
15지불액PAYMENTS신용카드 계좌에 지불한 총 금액N201.8
16최소 지불액MINIMUM_PAYMENTS신용카드 계좌의 최소 지불액N139.5
17전액 지불 비율PRC_FULL_PAYMENT신용카드 결제 금액 중 전액을 지불한 비율N0.0
18카드 소지 기간TENURE신용카드 계좌를 소지한 기간 (월)N12
1
2
3
# 데이터 불러오기
data = pd.read_csv("/content/drive/MyDrive/CC GENERAL.csv")
data
CUST_IDBALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENURE
0C1000140.9007490.81818295.400.0095.400.0000000.1666670.0000000.0833330.000000021000.0201.802084139.5097870.00000012
1C100023202.4674160.9090910.000.000.006442.9454830.0000000.0000000.0000000.250000407000.04103.0325971072.3402170.22222212
2C100032495.1488621.000000773.17773.170.000.0000001.0000001.0000000.0000000.0000000127500.0622.066742627.2847870.00000012
3C100041666.6705420.6363641499.001499.000.00205.7880170.0833330.0833330.0000000.083333117500.00.000000NaN0.00000012
4C10005817.7143351.00000016.0016.000.000.0000000.0833330.0833330.0000000.000000011200.0678.334763244.7912370.00000012
.........................................................
8945C1918628.4935171.000000291.120.00291.120.0000001.0000000.0000000.8333330.000000061000.0325.59446248.8863650.5000006
8946C1918719.1832151.000000300.000.00300.000.0000001.0000000.0000000.8333330.000000061000.0275.861322NaN0.0000006
8947C1918823.3986730.833333144.400.00144.400.0000000.8333330.0000000.6666670.000000051000.081.27077582.4183690.2500006
8948C1918913.4575640.8333330.000.000.0036.5587780.0000000.0000000.0000000.16666720500.052.54995955.7556280.2500006
8949C19190372.7080750.6666671093.251093.250.00127.0400080.6666670.6666670.0000000.3333332231200.063.16540488.2889560.0000006

8950 rows × 18 columns

데이터 파악 및 EDA (6문제)

문제1: 데이터셋 기본 정보 확인하기

  • 데이터프레임의 기본 정보를 확인하고, 각 변수의 데이터 타입과 결측치 유무를 파악하세요.
1
2
3
4
# 데이터프레임의 기본 정보 확인
data.info()
# 최소 지불액 변수에 일부 결측치 파악됨
# id를 제외하면 수치형 변수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 8950 entries, 0 to 8949
Data columns (total 18 columns):
 #   Column                            Non-Null Count  Dtype  
---  ------                            --------------  -----  
 0   CUST_ID                           8950 non-null   object 
 1   BALANCE                           8950 non-null   float64
 2   BALANCE_FREQUENCY                 8950 non-null   float64
 3   PURCHASES                         8950 non-null   float64
 4   ONEOFF_PURCHASES                  8950 non-null   float64
 5   INSTALLMENTS_PURCHASES            8950 non-null   float64
 6   CASH_ADVANCE                      8950 non-null   float64
 7   PURCHASES_FREQUENCY               8950 non-null   float64
 8   ONEOFF_PURCHASES_FREQUENCY        8950 non-null   float64
 9   PURCHASES_INSTALLMENTS_FREQUENCY  8950 non-null   float64
 10  CASH_ADVANCE_FREQUENCY            8950 non-null   float64
 11  CASH_ADVANCE_TRX                  8950 non-null   int64  
 12  PURCHASES_TRX                     8950 non-null   int64  
 13  CREDIT_LIMIT                      8949 non-null   float64
 14  PAYMENTS                          8950 non-null   float64
 15  MINIMUM_PAYMENTS                  8637 non-null   float64
 16  PRC_FULL_PAYMENT                  8950 non-null   float64
 17  TENURE                            8950 non-null   int64  
dtypes: float64(14), int64(3), object(1)
memory usage: 1.2+ MB
  • 데이터프레임의 총 행 수와 열 수, 각 변수의 데이터 타입 및 결측치 여부를 확인할 수 있습니다.
  • MINIMUM_PAYMENTS와 CREDIT_LIMIT 변수에 결측치가 있습니다.

문제2: 기술 통계량 계산하기

각 변수의 기술 통계량을 계산하여 평균, 표준편차, 최소값, 최대값 등을 파악하세요.

1
2
3
# 각 변수의 기술 통계량 계산
data.describe()
# 금수저가 관찰됨
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENURE
count8950.0000008950.0000008950.0000008950.0000008950.0000008950.0000008950.0000008950.0000008950.0000008950.0000008950.0000008950.0000008949.0000008950.0000008637.0000008950.0000008950.000000
mean1564.4748280.8772711003.204834592.437371411.067645978.8711120.4903510.2024580.3644370.1351443.24882714.7098324494.4494501733.143852864.2065420.15371511.517318
std2081.5318790.2369042136.6347821659.887917904.3381152097.1638770.4013710.2983360.3974480.2001216.82464724.8576493638.8157252895.0637572372.4466070.2924991.338331
min0.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.00000050.0000000.0000000.0191630.0000006.000000
25%128.2819150.88888939.6350000.0000000.0000000.0000000.0833330.0000000.0000000.0000000.0000001.0000001600.000000383.276166169.1237070.00000012.000000
50%873.3852311.000000361.28000038.00000089.0000000.0000000.5000000.0833330.1666670.0000000.0000007.0000003000.000000856.901546312.3439470.00000012.000000
75%2054.1400361.0000001110.130000577.405000468.6375001113.8211390.9166670.3000000.7500000.2222224.00000017.0000006500.0000001901.134317825.4854590.14285712.000000
max19043.1385601.00000049039.57000040761.25000022500.00000047137.2117601.0000001.0000001.0000001.500000123.000000358.00000030000.00000050721.48336076406.2075201.00000012.000000

문제3 : 중복값 확인하기

  • 데이터프레임에서 중복값이 있는 지 확인해보세요. 있다면 중복값을 제거하세요.
1
data.duplicated(keep=False).sum()  # 중복값 없음
1
0

문제4: 결측치 파악하기

  • 데이터프레임에서 결측치가 존재하는 부분을 시각화해보세요.
1
2
3
sns.heatmap(data.isnull())
plt.show()
# 최소 지불액 변수에 일부 결측치 관찰됨

png

문제5: 데이터 분포 시각화하기 - 히스토그램

각 변수의 데이터 분포를 히스토그램으로 시각화하세요.

1
data.columns
1
2
3
4
5
6
7
Index(['CUST_ID', 'BALANCE', 'BALANCE_FREQUENCY', 'PURCHASES',
       'ONEOFF_PURCHASES', 'INSTALLMENTS_PURCHASES', 'CASH_ADVANCE',
       'PURCHASES_FREQUENCY', 'ONEOFF_PURCHASES_FREQUENCY',
       'PURCHASES_INSTALLMENTS_FREQUENCY', 'CASH_ADVANCE_FREQUENCY',
       'CASH_ADVANCE_TRX', 'PURCHASES_TRX', 'CREDIT_LIMIT', 'PAYMENTS',
       'MINIMUM_PAYMENTS', 'PRC_FULL_PAYMENT', 'TENURE'],
      dtype='object')
1
2
3
# 히스토그램 시각화
numeric_columns = data.drop(columns='CUST_ID')
numeric_columns
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENURE
040.9007490.81818295.400.0095.400.0000000.1666670.0000000.0833330.000000021000.0201.802084139.5097870.00000012
13202.4674160.9090910.000.000.006442.9454830.0000000.0000000.0000000.250000407000.04103.0325971072.3402170.22222212
22495.1488621.000000773.17773.170.000.0000001.0000001.0000000.0000000.0000000127500.0622.066742627.2847870.00000012
31666.6705420.6363641499.001499.000.00205.7880170.0833330.0833330.0000000.083333117500.00.000000NaN0.00000012
4817.7143351.00000016.0016.000.000.0000000.0833330.0833330.0000000.000000011200.0678.334763244.7912370.00000012
......................................................
894528.4935171.000000291.120.00291.120.0000001.0000000.0000000.8333330.000000061000.0325.59446248.8863650.5000006
894619.1832151.000000300.000.00300.000.0000001.0000000.0000000.8333330.000000061000.0275.861322NaN0.0000006
894723.3986730.833333144.400.00144.400.0000000.8333330.0000000.6666670.000000051000.081.27077582.4183690.2500006
894813.4575640.8333330.000.000.0036.5587780.0000000.0000000.0000000.16666720500.052.54995955.7556280.2500006
8949372.7080750.6666671093.251093.250.00127.0400080.6666670.6666670.0000000.3333332231200.063.16540488.2889560.0000006

8950 rows × 17 columns

1
2
3
4
5
6
7
8
9
10
plt.figure(figsize=(20,16))
for index, col in enumerate(numeric_columns):
    plt.subplot(4, 5, index + 1)
    sns.histplot(numeric_columns[col], bins=15, kde=True)
    plt.title(f"{col} 데이터 분포")
    plt.xlabel('')
    plt.ylabel('')

plt.tight_layout()
plt.show()

png

1
# 전체데이터로는 대부분 한쪽으로 치우쳐진 형태를 보이고 있다

문제6: 데이터의 이상치 파악하기

  • 데이터의 이상치를 파악하세요. 이를 위해 각 변수의 분포를 상자 그림으로 시각화하세요.
1
2
3
4
5
6
7
8
9
10
plt.figure(figsize=(20,16))
for index, col in enumerate(numeric_columns):
    plt.subplot(4, 5, index + 1)
    sns.boxplot(data=numeric_columns, y=col)
    plt.title(f"{col} 데이터 분포")
    plt.xlabel('')
    plt.ylabel('')

plt.tight_layout()
plt.show()

png

1
# 금수저들에의해 이상치가 무수히 발생하고있다

데이터 전처리 (3문제)

문제7: 결측치 처리하기

  • 변수의 결측치 수를 확인하고, 결측치가 존재하는 변수들을 적절한 방법으로 전처리하세요.
1
2
3
data2 = numeric_columns
data2.isna().sum().sort_values(ascending=False)
# 최소 지불액 변수에서 313개 결측치, 신용 한도에서 1개 결측치 관찰됨
0
MINIMUM_PAYMENTS313
CREDIT_LIMIT1
BALANCE0
CASH_ADVANCE_FREQUENCY0
PRC_FULL_PAYMENT0
PAYMENTS0
PURCHASES_TRX0
CASH_ADVANCE_TRX0
PURCHASES_INSTALLMENTS_FREQUENCY0
BALANCE_FREQUENCY0
ONEOFF_PURCHASES_FREQUENCY0
PURCHASES_FREQUENCY0
CASH_ADVANCE0
INSTALLMENTS_PURCHASES0
ONEOFF_PURCHASES0
PURCHASES0
TENURE0


1
data2[data2['CREDIT_LIMIT'].isnull()]
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENURE
520318.4004720.1666670.00.00.0186.8530630.00.00.00.16666710NaN9.04001714.4187230.06
1
2
# 신용 한도 결측치 1개는 단순제거를 수행한다
data2 = data2.dropna(subset=['CREDIT_LIMIT'])
1
data2['MINIMUM_PAYMENTS'].describe()
MINIMUM_PAYMENTS
count8636.000000
mean864.304943
std2372.566350
min0.019163
25%169.163545
50%312.452292
75%825.496463
max76406.207520


1
2
3
4
5
6
7
8
# 최소 지불액 시각화 : 75%가 825인데 max값이 76406이다. 결측치는 중앙값처리가 더 적절해보인다.
sns.histplot(data2['MINIMUM_PAYMENTS'])
plt.title("MINIMUM_PAYMENTS의 데이터 분포")
plt.xlabel('')
plt.ylabel('')

plt.tight_layout()
plt.show()

png

1
2
# 최소지불액과 지불액 관계 : 최소지불액 결측치가 지불액에서 어떻게 나타나는지 확인
data2['PAYMENTS'][data2['MINIMUM_PAYMENTS'].isna()].value_counts().sort_values(ascending=False)
count
PAYMENTS
0.000000240
432.9272811
746.6910261
1159.1350641
29272.4860701
......
295.9371241
3905.4308171
5.0707261
578.8193291
275.8613221

74 rows × 1 columns


1
2
3
4
5
6
7
8
9
10
11
12
13
# 최소지불액 결측치 처리
# 1) 지불액 변수값이 0이다 : 최소지불액 결측치도 0으로 처리
# 2) 지불액 변수값이 0이 아니다 : 최소지불액 결측치 중앙값 처리
median_minimum_payments = data2['MINIMUM_PAYMENTS'].median()

for index, row in data2.iterrows():
    if pd.isnull(row['MINIMUM_PAYMENTS']):
        if row['PAYMENTS'] == 0:
            data2.at[index, 'MINIMUM_PAYMENTS'] = 0
        else:
            data2.at[index, 'MINIMUM_PAYMENTS'] = median_minimum_payments

data2.isna().sum().sort_values(ascending=False)
0
BALANCE0
CASH_ADVANCE_FREQUENCY0
PRC_FULL_PAYMENT0
MINIMUM_PAYMENTS0
PAYMENTS0
CREDIT_LIMIT0
PURCHASES_TRX0
CASH_ADVANCE_TRX0
PURCHASES_INSTALLMENTS_FREQUENCY0
BALANCE_FREQUENCY0
ONEOFF_PURCHASES_FREQUENCY0
PURCHASES_FREQUENCY0
CASH_ADVANCE0
INSTALLMENTS_PURCHASES0
ONEOFF_PURCHASES0
PURCHASES0
TENURE0


1
2
3
4
# 추가 점검 : 총 지불액이 최소지불액보다 작을수있을까?
payment_check = data2[data2['MINIMUM_PAYMENTS'] > data2['PAYMENTS']]
print(f"최소지불액보다 작은 총 지불액 개수 : {payment_check.shape[0]}")
print(f"전체 데이터 중 약 {round((payment_check.shape[0])/(data2.shape[0])*100)}%에서 발견됨")
1
2
최소지불액보다 작은 총 지불액 개수 : 2397
전체 데이터 중 약 27%에서 발견됨
1
payment_check[['MINIMUM_PAYMENTS','PAYMENTS']]
MINIMUM_PAYMENTSPAYMENTS
2627.284787622.066742
52407.2460351400.057770
102172.6977651083.301007
14989.962866805.647974
152109.9064901993.439277
.........
8939110.95079872.530037
8946312.452292275.861322
894782.41836981.270775
894855.75562852.549959
894988.28895663.165404

2397 rows × 2 columns

1
2
3
4
5
6
7
8
9
10
11
# 총 지불액이 최소 지불액보다 작은경우?
# 1) 환불발생 : 현재데이터로 확인불가
# 2) 할인발생 : 현재데이터로 확인불가
# 3) 할부구매 : INSTALLMENTS_PURCHASES, PURCHASES_INSTALLMENTS_FREQUENCY의 값이 0보다 크다면 할부금액으로 인한 지불액 차이가 고려될 수 있다.
# 4) 신용한도 초과 : CREDIT_LIMIT에 영향을 받을수있다. 신용한도가 낮아 최소 결제 금액을 충족하지 못한 경우.
# 5) 미지급 : 신용카드 결제일에 고객이 전액을 지급하지 않거나 일부만 납부한 경우. 현재데이터로 확인불가
# 6) 현금서비스 : CASH_ADVANCE, CASH_ADVANCE_FREQUENCY의 값이 0보다 크다면 고려될 수 있다.
# 7) 기타 : 시스템오류나 고객의 단순 지불액 착각, 돌려막기 등...

# 하지만 현재데이터상으로는 이상치로 판단하기에는 너무많은 데이터가 소실될 위험이 있다.
# 주어진 데이터의 한계로 위와같은 데이터가 제공되지 않아 비정상적으로 보일 수 있다고 판단하여 이상치를 제거하지 않음.

문제8: 이상치 처리하기

  • 데이터에 이상치가 있는지 확인해보세요. 확인 후, 전처리가 필요하다면 진행해주세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
# 이상치 제거 전 시각화
data3 = data2.copy()

plt.figure(figsize=(20,16))
for index, col in enumerate(data3):
    plt.subplot(4, 5, index + 1)
    sns.boxplot(data=data3, y=col)
    plt.title(f"{col} 데이터 분포")
    plt.xlabel('')
    plt.ylabel('')

plt.tight_layout()
plt.show()

png

1
2
3
4
# 이상치가 너무 많다 : 다양한방법으로 시도해보자
# 1. IQR기준 이상치 처리
# 2. IQR 0.10 ~ 0.90 기준 이상치 처리
# 3. 특정변수만 이상치처리
  • 이상치 처리 : 1. IQR기준 이상치 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 이상치 제거 (전처리 진행)
data4 = data2.copy()
def get_outlier_mask(df, weight=1.5):
    Q1 = df.quantile(0.25)
    Q3 = df.quantile(0.75)

    IQR = Q3 - Q1
    IQR_weight = IQR * weight

    range_min = Q1 - IQR_weight
    range_max = Q3 + IQR_weight

    outlier_per_column = (df < range_min) | (df > range_max)

    is_outlier = outlier_per_column.any(axis=1)

    return is_outlier

outlier_idx_cust_df = get_outlier_mask(data4, weight=1.5)

# 이상치를 제거한 데이터 프레임만 추가
data4 = data4[~outlier_idx_cust_df]
data4
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENURE
040.9007490.81818295.400.0095.400.0000000.1666670.0000000.0833330.000000021000.0201.802084139.5097870.00000012
4817.7143351.00000016.0016.000.000.0000000.0833330.0833330.0000000.000000011200.0678.334763244.7912370.00000012
71823.6527431.000000436.200.00436.200.0000001.0000000.0000001.0000000.0000000122300.0679.065082532.0339900.00000012
81014.9264731.000000861.49661.49200.000.0000000.3333330.0833330.2500000.000000057000.0688.278568311.9634090.00000012
142772.7727341.0000000.000.000.00346.8113900.0000000.0000000.0000000.083333103000.0805.647974989.9628660.00000012
......................................................
8738981.2860081.0000001370.001370.000.000.0000000.0833330.0833330.0000000.000000011400.0596.685481451.5848470.00000012
874287.0260091.000000605.520.00605.520.0000001.0000000.0000000.9166670.0000000121500.0511.637312175.0127050.00000012
874716.4283260.909091441.50124.70316.800.0000001.0000000.1666670.9166670.0000000141000.0482.54784891.3285360.33333312
875967.3772431.000000295.000.00295.000.0000000.5000000.0000000.4166670.000000061000.0245.689379167.1260340.30000012
8760307.1277541.000000909.30409.30500.00237.3788940.5833330.1666670.5000000.1666674121000.0943.278170179.2585750.00000012

2987 rows × 17 columns

1
2
3
4
# IQR기준으로 이상치를 처리하면 데이터가 33% 밖에 남지 않는다.
# 이번데이터는 마케팅 전략을 수립하기 위해 고객 세분화 방안을 찾는것이다.
# 그런데 전체 데이터를 67% 버리고 고객 세분화 방안을 찾는것은 원래목적의 의미가 퇴색될수있다.
# 다른방안을 선택하는것이 좋아보인다.
  • 이상치 처리 : 2. IQR 0.10 ~ 0.90 기준 이상치 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. IQR 0.10 ~ 0.90버전
data5 = data2.copy()
def get_outlier_mask(df, weight=1.5):
    Q1 = df.quantile(0.10)
    Q3 = df.quantile(0.90)

    IQR = Q3 - Q1
    IQR_weight = IQR * weight

    range_min = Q1 - IQR_weight
    range_max = Q3 + IQR_weight

    outlier_per_column = (df < range_min) | (df > range_max)

    is_outlier = outlier_per_column.any(axis=1)

    return is_outlier

outlier_idx_cust_df = get_outlier_mask(data5, weight=1.5)

# 이상치를 제거한 데이터 프레임만 추가
data5 = data5[~outlier_idx_cust_df]
data5
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENURE
040.9007490.81818295.400.0095.400.0000000.1666670.0000000.0833330.000000021000.0201.802084139.5097870.00000012
13202.4674160.9090910.000.000.006442.9454830.0000000.0000000.0000000.250000407000.04103.0325971072.3402170.22222212
22495.1488621.000000773.17773.170.000.0000001.0000001.0000000.0000000.0000000127500.0622.066742627.2847870.00000012
31666.6705420.6363641499.001499.000.00205.7880170.0833330.0833330.0000000.083333117500.00.0000000.0000000.00000012
4817.7143351.00000016.0016.000.000.0000000.0833330.0833330.0000000.000000011200.0678.334763244.7912370.00000012
......................................................
890821.3572671.000000212.870.00212.870.0000001.0000000.0000000.8571430.000000071000.0169.713838103.3873621.0000007
8909641.2825191.000000750.00750.000.000.0000000.1428570.1428570.0000000.000000011000.0105.582942302.7438810.0000007
8910356.1086941.000000465.00465.000.000.0000000.1428570.1428570.0000000.000000011000.0118.775188109.2271760.0000007
891130.7091720.285714693.420.00693.420.0000000.7142860.0000000.5714290.000000071000.01154.52008515.8538730.0000007
8912376.5474210.857143520.00280.00240.001178.4024160.8571430.1428570.7142860.714286971000.0929.415656103.9278870.2000007

7860 rows × 17 columns

1
2
# IQR을 0.10 ~ 0.90 기준으로 이상치를 처리하면 88% 데이터가 남는다.
# 대부분의 데이터가 살아있지만 시각화를 통해 한번더 확인이 필요해보인다.
  • 이상치 처리 : 3. 특정변수만 이상치처리
1
2
3
# max값이 많이 튀는 특정 변수만 이상치 처리 : BALANCE, PURCHASES, ONEOFF_PURCHASES, INSTALLMENTS_PURCHASES, CASH_ADVANCE, CREDIT_LIMIT, PAYMENTS, MINIMUM_PAYMENTS
data6 = data2.copy()
data6.describe()
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENURE
count8949.0000008949.0000008949.0000008949.0000008949.0000008949.0000008949.0000008949.0000008949.0000008949.0000008949.0000008949.0000008949.0000008949.0000008949.0000008949.0000008949.000000
mean1564.6475930.8773501003.316936592.503572411.113579978.9596160.4904050.2024800.3644780.1351413.24907814.7114764494.4494501733.336511836.6238130.15373211.517935
std2081.5840160.2367982136.7278481659.968851904.3782052097.2643440.4013600.2983450.3974510.2001326.82498724.8585523638.8157252895.1681462335.3632280.2925111.337134
min0.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.00000050.0000000.0000000.0000000.0000006.000000
25%128.3657820.88888939.8000000.0000000.0000000.0000000.0833330.0000000.0000000.0000000.0000001.0000001600.000000383.282850164.6876390.00000012.000000
50%873.6802791.000000361.49000038.00000089.0000000.0000000.5000000.0833330.1666670.0000000.0000007.0000003000.000000857.062706299.9350430.00000012.000000
75%2054.3728481.0000001110.170000577.830000468.6500001113.8686540.9166670.3000000.7500000.2222224.00000017.0000006500.0000001901.279320788.7216090.14285712.000000
max19043.1385601.00000049039.57000040761.25000022500.00000047137.2117601.0000001.0000001.0000001.500000123.000000358.00000030000.00000050721.48336076406.2075201.00000012.000000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
data6 = data2.copy()

def get_outlier_mask(df, columns, weight=1.5):
    # 선택한 열만 사용
    df_selected = df[columns]

    Q1 = df_selected.quantile(0.25)
    Q3 = df_selected.quantile(0.75)

    IQR = Q3 - Q1
    IQR_weight = IQR * weight

    range_min = Q1 - IQR_weight
    range_max = Q3 + IQR_weight

    outlier_per_column = (df_selected < range_min) | (df_selected > range_max)

    is_outlier = outlier_per_column.any(axis=1)

    return is_outlier

# 이상치를 처리할 열 리스트
columns_to_check = ['BALANCE', 'PURCHASES', 'ONEOFF_PURCHASES', 'INSTALLMENTS_PURCHASES', 'CASH_ADVANCE', 'CREDIT_LIMIT', 'PAYMENTS', 'MINIMUM_PAYMENTS']

# 이상치 인덱스 생성
outlier_idx_cust_df = get_outlier_mask(data6, columns_to_check, weight=1.5)

# 이상치를 제거한 데이터프레임 생성
data6 = data6[~outlier_idx_cust_df]
data6
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENURE
040.9007490.81818295.400.0095.400.0000000.1666670.0000000.0833330.000000021000.0201.802084139.5097870.0012
22495.1488621.000000773.17773.170.000.0000001.0000001.0000000.0000000.0000000127500.0622.066742627.2847870.0012
4817.7143351.00000016.0016.000.000.0000000.0833330.0833330.0000000.000000011200.0678.334763244.7912370.0012
71823.6527431.000000436.200.00436.200.0000001.0000000.0000001.0000000.0000000122300.0679.065082532.0339900.0012
81014.9264731.000000861.49661.49200.000.0000000.3333330.0833330.2500000.000000057000.0688.278568311.9634090.0012
......................................................
894528.4935171.000000291.120.00291.120.0000001.0000000.0000000.8333330.000000061000.0325.59446248.8863650.506
894619.1832151.000000300.000.00300.000.0000001.0000000.0000000.8333330.000000061000.0275.861322312.4522920.006
894723.3986730.833333144.400.00144.400.0000000.8333330.0000000.6666670.000000051000.081.27077582.4183690.256
894813.4575640.8333330.000.000.0036.5587780.0000000.0000000.0000000.16666720500.052.54995955.7556280.256
8949372.7080750.6666671093.251093.250.00127.0400080.6666670.6666670.0000000.3333332231200.063.16540488.2889560.006

5869 rows × 17 columns

1
2
# 특정변수만 이상치를 처리하면 66% 데이터가 남는다.
# 시각화를 통해 2번과 3번 방안중에 선택고려
1
2
3
4
5
6
7
8
9
10
11
12
# 이상치 제거 후 시각화
# 2. IQR 0.10 ~ 0.90 버전
plt.figure(figsize=(20,16))
for index, col in enumerate(data5):
    plt.subplot(4, 5, index + 1)
    sns.boxplot(data=data5, y=col)
    plt.title(f"{col} 데이터 분포")
    plt.xlabel('')
    plt.ylabel('')

plt.tight_layout()
plt.show()

png

1
# 이상치 제거 전 박스플롯과 비슷해보이지만, 전반적으로 변수별로 이상치들이 잘 모여있다.
1
2
3
4
5
6
7
8
9
10
11
12
# 3. 특정변수만 이상치 제거 버전
# max값이 많이 튀는 특정 변수만 이상치 처리 : BALANCE, PURCHASES, ONEOFF_PURCHASES, INSTALLMENTS_PURCHASES, CASH_ADVANCE, CREDIT_LIMIT, PAYMENTS, MINIMUM_PAYMENTS
plt.figure(figsize=(20,16))
for index, col in enumerate(data6):
    plt.subplot(4, 5, index + 1)
    sns.boxplot(data=data6, y=col)
    plt.title(f"{col} 데이터 분포")
    plt.xlabel('')
    plt.ylabel('')

plt.tight_layout()
plt.show()

png

1
2
# 2번 IQR 0.1 ~ 0.9 버전보다 3번 방식으로 선택한 변수들은 데이터가 더 잘모여있고, 선택되지않은 데이터들은 이상치가 조금 튀고있다.
# 우선, 2번 방식으로 데이터분석을 진행하되, 적절한 분석이 안될경우 2번 데이터를 추가적인 전처리를 취하거나 3번 데이터를 사용한다.

문제9: 데이터 스케일링하기

  • 각 변수의 데이터를 표준화 혹은 정규화하세요.
1
2
3
4
5
6
7
8
data7 = data5.copy()

df_mean = data7.mean()  # 각 컬럼의 평균값
df_std = data7.std()  # 각 컬럼의 표준편차

scaled_df = (data7 - df_mean)/df_std  # 컬럼별 표준화 진행

scaled_df
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENURE
0-0.759182-0.215160-0.647033-0.562980-0.438501-0.554363-0.781661-0.655331-0.686195-0.681752-0.583682-0.642690-0.963550-0.757062-0.619400-0.5194890.342764
11.1336500.157721-0.749808-0.562980-0.6364484.353579-1.201814-0.655331-0.8992410.7413030.340613-0.7773790.8869972.0541400.8389680.2535630.342764
20.7101780.5306030.0831380.554513-0.636448-0.5543631.3191022.889269-0.899241-0.681752-0.5836820.0307501.041209-0.4542220.143178-0.5194890.342764
30.214168-0.9609240.8650841.603584-0.636448-0.397603-0.991739-0.359948-0.899241-0.207402-0.352608-0.7100341.041209-0.902479-0.837506-0.5194890.342764
4-0.2941030.530603-0.732571-0.539855-0.636448-0.554363-0.991739-0.359948-0.899241-0.681752-0.583682-0.710034-0.901865-0.413676-0.454805-0.5194890.342764
......................................................
8908-0.7708820.530603-0.520481-0.562980-0.194761-0.5543631.319102-0.6553311.292097-0.681752-0.583682-0.305970-0.963550-0.780185-0.6758732.959249-4.300677
8909-0.3997330.5306030.0581770.521025-0.636448-0.554363-0.841684-0.148960-0.899241-0.681752-0.583682-0.710034-0.963550-0.826397-0.364203-0.519489-4.300677
8910-0.5704670.530603-0.2488580.109103-0.636448-0.554363-0.841684-0.148960-0.899241-0.681752-0.583682-0.710034-0.963550-0.816891-0.666743-0.519489-4.300677
8911-0.765283-2.399186-0.002778-0.5629800.802339-0.5543630.598841-0.6553310.561652-0.681752-0.583682-0.305970-0.963550-0.070540-0.812721-0.519489-4.300677
8912-0.558230-0.055354-0.189605-0.158285-0.1384690.3432900.958971-0.1489600.9268753.3841211.495983-0.305970-0.963550-0.232748-0.6750280.176258-4.300677

7860 rows × 17 columns

  • 표준화는 데이터의 평균을 0, 표준편차를 1로 맞추어 변환합니다.
  • 정규화는 데이터 값을 0과 1 사이로 변환합니다.
  • 표준화와 정규화는 모델 학습의 성능을 향상시킬 수 있습니다.

이후 분석은 표준화된 데이터를 바탕으로 진행하겠습니다.

클러스터 분석 (11문제)

###문제10: K-means 클러스터링 모델 학습

  • K-means 알고리즘을 사용하여 클러스터링 모델을 학습하세요. 클러스터의 수는 3로 설정하세요.
1
2
3
4
from sklearn.cluster import KMeans
data10 = scaled_df.copy()
model = KMeans(n_clusters=3, random_state=21)
model.fit(data10)
KMeans(n_clusters=3, random_state=21)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
1
2
3
4
5
6
# 군집 레이블을 데이터프레임에 추가
data10['cluster'] = model.predict(data10)

# 군집별 데이터 개수 확인
cluster_counts = data10['cluster'].value_counts()
cluster_counts
count
cluster
04610
11653
21597


문제11 : 클러스터링 결과 시각화 (2D)

  • 두 개의 주요 변수(BALANCE, PURCHASES)를 사용하여 클러스터링 결과를 2D로 시각화하세요.
1
2
3
4
# 시각화

sns.scatterplot(x=data10['BALANCE'], y=data10['PURCHASES'], hue=data10['cluster'], s=50, palette='bright')

1
<Axes: xlabel='BALANCE', ylabel='PURCHASES'>

png

문제12: 클러스터 수 결정 (Elbow Method)

  • Elbow Method를 사용하여 최적의 클러스터 수를 결정하세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 엘보우 기법을 위한 KMeans 모델 실행 및 inertia 계산
inertia_list = []
k_values = range(1, 11)

for k in k_values:
    kmeans = KMeans(n_clusters=k, random_state=21)
    kmeans.fit(data10)
    inertia_list.append(kmeans.inertia_)  # 각 k에 대한 inertia 저장

# 3. 엘보우 기법 시각화
plt.figure(figsize=(10, 6))
plt.plot(k_values, inertia_list, 'bo-', markersize=8)
plt.xlabel('Number of clusters (k)')
plt.ylabel('Inertia')
plt.title('Elbow Method for Optimal k')
plt.grid(True)
plt.show()

png

1
# 엘보우기법 상 클러스터수는 3개 혹은 4개가 최적으로 보인다

문제13 : 결과를 해석해보세요.

  • 아래는 클러스터의 수를 5로 진행하고, 2가지 변수(BALANCE, PURCHASES)로 시각화를 한 결과입니다.
  • 결과를 보고 추가 분석을 진행하여 인사이트를 발견해보세요. 클러스터 별로 어떤 차이가 있나요?
1
2
3
4
5
6
7
8
9
10
11
12
data11 = scaled_df.copy()
model = KMeans(n_clusters=5, random_state=21)
model.fit(data11)
data11['cluster'] = model.predict(data11)

# 군집별 데이터 개수 확인
cluster_counts2 = data11['cluster'].value_counts()
cluster_counts2

# 시각화
plt.figure(figsize=(16,10))
sns.scatterplot(x=data11['BALANCE'], y=data11['PURCHASES'], hue=data11['cluster'], s=50, palette='bright')
1
<Axes: xlabel='BALANCE', ylabel='PURCHASES'>

png

1
2
3
mask1 = data11.groupby('cluster').mean()
mask1['cluster_counts'] = data11['cluster'].value_counts()
mask1
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENUREcluster_counts
cluster
0-0.729883-2.035002-0.426298-0.297514-0.393996-0.366573-0.511288-0.388173-0.415927-0.505307-0.442185-0.500797-0.147300-0.338790-0.6385240.273897-0.1769961215
10.1473310.4122281.8613111.8430910.938551-0.3086831.0532601.7469640.601723-0.344255-0.3186081.6050480.7107470.8247710.0523320.3472330.2179271101
21.4715560.383702-0.385522-0.268878-0.3569741.829729-0.547150-0.272870-0.4743171.6824731.768427-0.4062990.6801890.7423011.151377-0.416675-0.0698911078
3-0.4478530.3075180.077789-0.3536650.658288-0.4507741.017808-0.3011071.169951-0.505259-0.4561850.374902-0.260058-0.267901-0.1970940.461860-0.0047011903
4-0.0036980.397903-0.493090-0.275025-0.555031-0.128512-0.735656-0.228098-0.7304910.054925-0.058604-0.559555-0.328488-0.306995-0.057716-0.4466770.0231762563

- 결과 해석

  • 클러스터0 : 가장 낮은 잔액과 비교적 낮은 구매액을 가진 고객. 현금서비스이용도 거의 사용안하고있음. 총 지불액이 가장 낮게 나타남.
  • 클러스터1 : 중간 정도의 잔액과 가장 높은 구매액 및 구매빈도를 가진 고객. 할부로 이루어진 구매액이 높고 총 지불액도 가장 높게 나타나고있음.
  • 클러스터2 : 가장 높은 잔액과 비교적 낮은 구매액, 현금서비스를 가장 많이 이용한 고객. 총 지불액도 비교적 높게 나타나고있음. 최소 지불액이 가장 높고 다른 군집보다 확연하게 높음.
  • 클러스터3 : 비교적 낮은 잔액과 비교적 높은 구매액 및 높은 구매빈도를 가진 고객. 할부 구매 빈도가 가장 높게 나타나고있음. 현금서비스 이용을 가장 낮게 사용하고있음.
  • 클러스터4 : 중간 정도의 잔액과 구매빈도가 가장 낮은 고객. 할부 구매 빈도는 가장 낮게 나타남. 총 지불액이 비교적 낮게 나타남.

  • 클러스터1은 구매빈도가 높은 충성고객으로 이들이 이탈하지않을만한 꾸준한 서비스를 제공할 필요가 있음
  • 클러스터2는 다른 클러스터 집단보다 잔액이 압도적으로 많이 보유하고있는 고객들임. 다만, 현금서비스 이용도 높고 최소지불액도 높은것에비해 구매이용률이 낮아 구매를 유도할만한 상품이나 서비스가 필요함
  • 클러스터0,3,4는 잔액과 구매빈도가 낮은편이다. 이들이 구매활동을 하기위한 적절한 서비스가 필요하다

문제14: 계층적 클러스터링 모델 학습 및 덴드로그램 시각화

  • 계층적 클러스터링을 scipy를 사용하여 모델을 학습하고, 덴드로그램을 시각화하세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from scipy.cluster.hierarchy import dendrogram, linkage, cut_tree
import matplotlib.pyplot as plt

data12 = scaled_df.copy()

# 거리 : ward method 사용
model = linkage(data12, 'ward')  # single, complete, average, centroid, median 등

labelList = data12.index

# 덴드로그램 사이즈와 스타일 조정
plt.figure(figsize=(16,9))
plt.style.use("default")

dendrogram(model, labels=labelList)
plt.show()

png

1
2
3
4
5
6
7
cluster_num = 5

# 고객별 클러스터 라벨 구하기
data12['label'] = cut_tree(model, cluster_num)

pd.DataFrame(data12['label'].value_counts())

count
label
22971
02780
11135
4570
3404
1
2
3
4
5
6
sns.set(style="darkgrid",
        rc = {'figure.figsize':(16,9)})

# 시각화
sns.scatterplot(x=data12['BALANCE'], y=data12['PURCHASES'], hue=data12['label'], s=50, palette='bright')

1
<Axes: xlabel='BALANCE', ylabel='PURCHASES'>

png

문제15: DBSCAN 클러스터링 모델 학습

  • DBSCAN 알고리즘을 사용하여 클러스터링 모델을 학습하고, 결과를 얻으세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd
from sklearn.cluster import DBSCAN
import matplotlib.pyplot as plt
import seaborn as sns

data13 = scaled_df.copy()

# DBSCAN 모델 생성
# eps와 min_samples는 데이터에 따라 조정해야 합니다.
dbscan = DBSCAN(eps=0.5, min_samples=5)

# DBSCAN을 사용하여 클러스터링 수행
clusters = dbscan.fit_predict(data13)

# 클러스터 레이블을 데이터프레임에 추가
data13['cluster'] = clusters

# 클러스터 개수 확인
cluster_counts = data13['cluster'].value_counts()
print("Cluster Counts:")
print(cluster_counts)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Cluster Counts:
cluster
-1     6623
 1      448
 2      262
 0      215
 3      151
 8       16
 17      15
 15      13
 12      11
 6       11
 9       10
 7        9
 4        9
 11       9
 16       8
 14       7
 20       6
 5        6
 21       6
 22       5
 18       5
 10       5
 13       5
 19       5
Name: count, dtype: int64

문제16: DBSCAN 클러스터링 모델 학습 (매개변수 조정)

  • DBSCAN의 eps와 min_samples 값을 조정하여, 클러스터링 결과가 개선되도록 하세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# DBSCAN 모델 학습 (매개변수 조정)
data14 = scaled_df.copy()

# DBSCAN 모델 생성
# eps와 min_samples는 데이터에 따라 조정해야 합니다.
dbscan = DBSCAN(eps=0.8, min_samples=5)

# DBSCAN을 사용하여 클러스터링 수행
clusters = dbscan.fit_predict(data14)

# 클러스터 레이블을 데이터프레임에 추가
data14['cluster'] = clusters

# 클러스터 개수 확인
cluster_counts = data14['cluster'].value_counts()
print("Cluster Counts:")
print(cluster_counts)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Cluster Counts:
cluster
-1     4891
 0     2711
 9       47
 4       28
 2       16
 6       15
 13      12
 1       10
 20       8
 11       7
 17       7
 12       7
 27       7
 28       7
 23       6
 5        6
 10       6
 15       6
 7        6
 3        6
 26       5
 19       5
 22       5
 24       5
 8        5
 18       5
 16       5
 29       5
 14       4
 25       4
 21       3
Name: count, dtype: int64

문제17: 클러스터링 결과 시각화 및 노이즈 탐지

  • DBSCAN 결과를 시각화하고, 노이즈 데이터를 탐지하세요.
1
2
3
4
5
6
7
# DBSCAN 결과 시각화 (최적 매개변수 사용 후)
plt.figure(figsize=(10, 6))
plt.scatter(data14['PURCHASES'], data14['BALANCE'], c=data14['cluster'], cmap='viridis')
plt.xlabel('PURCHASES')
plt.ylabel('BALANCE')
plt.title('DBSCAN Clustering Results (2D)')
plt.show()

png

1
2
3
4
# 노이즈 데이터 개수 확인
noise_check = data14[data14['cluster'] == -1]
print(f"Number of noise points: {len(noise_check)}")

1
Number of noise points: 4891

문제18: GMM 클러스터링 모델 학습

  • Gaussian Mixture Model을 사용하여 클러스터링 모델을 학습하고, 결과를 얻으세요. 클러스터의 수는 3으로 설정하세요.
1
2
3
4
5
6
7
8
9
10
from sklearn.mixture import GaussianMixture
data15 = scaled_df.copy()
n_components = 3
random_state = 21
model = GaussianMixture(n_components=n_components, random_state=random_state)

# GMM 모델 학습
model.fit(data15)
data15['gmm_label'] = model.predict(data15)
print(data15['gmm_label'].value_counts())
1
2
3
4
5
gmm_label
1    3534
2    2572
0    1754
Name: count, dtype: int64

문제19: 클러스터링 결과 시각화 및 클러스터 확률 해석

  • GMM 결과를 시각화하고, 각 데이터 포인트의 클러스터 소속 확률을 확인하세요.
1
2
3
import seaborn as sns
# 시각화
sns.scatterplot(x=data15['PURCHASES'], y=data15['BALANCE'], hue=data15['gmm_label'], palette='rainbow', alpha=0.7, s=100)
1
<Axes: xlabel='PURCHASES', ylabel='BALANCE'>

png

1
2
3
# 클러스터 소속 확률 확인
data15['Cluster_probabilities'] = model.predict_proba(data7).max(axis=1)
print(data15[['gmm_label', 'Cluster_probabilities']].head())
1
2
3
4
5
6
   gmm_label  Cluster_probabilities
0          1                    1.0
1          2                    1.0
2          1                    1.0
3          0                    1.0
4          2                    1.0

문제20: 클러스터링 결과 해석 및 비교

  • K-means, 계층적 클러스터링, DBSCAN, GMM의 클러스터링 결과를 비교해보세요.
  • 각 방법의 장단점, 특징도 고려해서 결과 분석을 진행해보세요.
1
2
3
4
print("K-means 클러스터 수:\n", data10['cluster'].value_counts())
print("계층적 클러스터링 클러스터 수:\n", data12['label'].value_counts())
print("DBSCAN 클러스터 수:\n", data14['cluster'].value_counts())
print("GMM 클러스터 수:\n", data15['gmm_label'].value_counts())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
K-means 클러스터 수:
 cluster
0    4610
1    1653
2    1597
Name: count, dtype: int64
계층적 클러스터링 클러스터 수:
 label
2    2971
0    2780
1    1135
4     570
3     404
Name: count, dtype: int64
DBSCAN 클러스터 수:
 cluster
-1     4891
 0     2711
 9       47
 4       28
 2       16
 6       15
 13      12
 1       10
 20       8
 11       7
 17       7
 12       7
 27       7
 28       7
 23       6
 5        6
 10       6
 15       6
 7        6
 3        6
 26       5
 19       5
 22       5
 24       5
 8        5
 18       5
 16       5
 29       5
 14       4
 25       4
 21       3
Name: count, dtype: int64
GMM 클러스터 수:
 gmm_label
1    3534
2    2572
0    1754
Name: count, dtype: int64
1
data10.columns
1
2
3
4
5
6
7
Index(['BALANCE', 'BALANCE_FREQUENCY', 'PURCHASES', 'ONEOFF_PURCHASES',
       'INSTALLMENTS_PURCHASES', 'CASH_ADVANCE', 'PURCHASES_FREQUENCY',
       'ONEOFF_PURCHASES_FREQUENCY', 'PURCHASES_INSTALLMENTS_FREQUENCY',
       'CASH_ADVANCE_FREQUENCY', 'CASH_ADVANCE_TRX', 'PURCHASES_TRX',
       'CREDIT_LIMIT', 'PAYMENTS', 'MINIMUM_PAYMENTS', 'PRC_FULL_PAYMENT',
       'TENURE', 'cluster'],
      dtype='object')
1
2
3
4
5
# 클러스터별 평균 지출 금액 및 잔액 확인
print("K-means : \n", data10.groupby('cluster')[['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'PAYMENTS']].mean())
print("계층적 클러스터링 : \n", data12.groupby('label')[['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'PAYMENTS']].mean())
print("DBSCAN : \n", data14.groupby('cluster')[['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'PAYMENTS']].mean())
print("GMM : \n", data15.groupby('gmm_label')[['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'PAYMENTS']].mean())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
K-means : 
           BALANCE  PURCHASES  CASH_ADVANCE  PAYMENTS
cluster                                             
0       -0.394715  -0.373719     -0.367362 -0.391423
1        0.005521   1.493598     -0.337212  0.623774
2        1.133694  -0.467172      1.409488  0.484259
계층적 클러스터링 : 
         BALANCE  PURCHASES  CASH_ADVANCE  PAYMENTS
label                                             
0     -0.316863  -0.507112     -0.309739 -0.394281
1      1.143575  -0.555477      1.630805  0.605988
2     -0.091108   0.371079     -0.323698  0.044065
3      0.072240   2.703237     -0.344377  1.284015
4     -0.308039  -0.270784      0.194640 -0.423429
DBSCAN : 
           BALANCE  PURCHASES  CASH_ADVANCE  PAYMENTS
cluster                                             
-1       0.204454   0.308840      0.220776  0.316698
 0      -0.332428  -0.517793     -0.371473 -0.525591
 1      -0.750215  -0.252501     -0.554363 -0.591529
 2      -0.246830  -0.726132      0.385987 -0.543469
 3       1.865787  -0.736625      0.180720 -0.023997
 4      -0.121388  -0.705041     -0.344316 -0.633206
 5      -0.699802  -0.337088     -0.554363 -0.592240
 6      -0.636324  -0.303431     -0.542537 -0.665515
 7      -0.752321  -0.128951     -0.554363 -0.492969
 8      -0.734494   0.252138     -0.554363 -0.279774
 9      -0.782024  -0.407821     -0.554363 -0.690939
 10      2.264016  -0.737627      1.372147  0.015497
 11     -0.590144  -0.749808      0.344613  0.998125
 12     -0.758299  -0.398275     -0.554363 -0.710500
 13     -0.753246  -0.111330     -0.554363 -0.423695
 14     -0.734383  -0.118559     -0.554363 -0.538804
 15     -0.766121  -0.458413     -0.554363 -0.740171
 16      3.126313  -0.749808      1.449965  0.255077
 17     -0.752260  -0.510008     -0.554363 -0.763259
 18     -0.663792   1.271664     -0.554363  0.358995
 19     -0.725419   0.450775     -0.554363 -0.015808
 20     -0.759766  -0.231603     -0.554363 -0.599557
 21     -0.315080   0.152215     -0.554363 -0.355780
 22      0.280371  -0.741190      1.052935  0.860523
 23     -0.730730  -0.463189     -0.554363 -0.826363
 24     -0.238408  -0.749808      0.327747 -0.760784
 25     -0.514970  -0.749808      0.060645 -0.517330
 26     -0.772262  -0.576344     -0.554363 -0.611633
 27     -0.169226  -0.732417     -0.272267 -0.765029
 28     -0.764356  -0.343223     -0.554363 -0.668904
 29     -0.754164  -0.494573     -0.554363 -0.765622
GMM : 
             BALANCE  PURCHASES  CASH_ADVANCE  PAYMENTS
gmm_label                                             
0          0.567485   0.456135      0.431318  0.452079
1         -0.483555   0.274891     -0.554274 -0.174449
2          0.277416  -0.688774      0.467447 -0.068601
  • K-means의 장단점
    • 장점 :
      • 각 변수(특성)들에 대한 배경지식, 역할, 영향도에 대해 모르더라도 데이터 사이의 거리만 구할 수 있다면 사용할 수 있다.
      • 알고리즘이 비교적 간단하여 이해와 해석이 용이하다
    • 단점 :
      • 최적의 클러스터 개수인 k를 정하는게 어려울 수 있다. elbow method등의 방법으로 추론할 수 있지만 항상 정답은 아니다.
      • 이상치에 영향을 많이 받는다. 이상치가 포함될 경우 중심점 위치가 크게 변동될 수 있고 클러스터가 원치않게 묶여질 수 있다.
      • 차원이 높은 데이터에 적용할 때 성능이 떨어진다
    • 단점 보완 : 중심점을 찾아주는 과정을 보안해주는 k-means++ 모델 등장
  • 계층적 클러스터링 장단점
    • 장점 :
      • 모델을 학습시킬 때 클러스터의 개수를 미리 가정하지 않아도 됨
      • 따라서 클러스터의 개수를 몇개로 해야할 지 모를 때 유용함
    • 단점 :
      • 모든 데이터끼리의 거리를 반복해서 계산해야 하기 때문에 많은 연산이 필요함
      • 따라서 대용량 데이터에 적용하는데 어려움이 있음
  • DBSCAN 장단점
    • 장점 : 데이터의 밀도에 따라 클러스터를 만들기 때문에 복잡하거나 기하학적인 형태를 가진 데이터 세트에 효과적이다
    • 단점 : 고차원 데이터일수록 데이터 간 밀도를 계산하기 어려워 연산이 많아지며 학습 속도가 느려질 수 있다
  • GMM은 데이터가 서로 다른 k 개의 정규분포에서 생성되었다고 가정하는 모델 기법
    • 데이터가 정규 분포를 따를 때 값이 특정 구간에 속할 확률을 계산할 수 있고, GMM은 이 확률을 통해 클러스터를 구분한다
    • 특정 데이터의 값이 어떤 분포에 포함될 확률이 더 큰지를 따져서 각 클러스터로 구분하는게 GMM 방법이다
    • GMM을 사용하면 데이터가 단순한 원형 분포 뿐만 아니라 타원형이나 비대칭 등의 데이터도 효과적으로 클러스터링을 할 수 있다
    • 다만, k-means와 비슷하게 사전에 클러스터 개수를 설정해야 하며, k의 개수에 따라 결과가 달라질 수 있다
    • 또한, 특정 분포에 할당되는 데이터 수가 적으면 모수 추정이 잘 이뤄지지 않아 많은 수의 데이터가 없으면 적용하기 어렵다
    • 그리고 정규분포가 아닌 범주형 데이터 등에는 다룰 수 없다
  • 결과해석 :

    • k-means 클러스터링은 0과 1,2 집단의 수 차이가 심하다. 데이터에 이상치가 많고, 차원이 높은것이 영향이 있던것으로 파악된다.
  • 계층적 클러스터링은 클러스터의 개수를 임의로 정할수 있지만 해석이 어렵다.

  • DBSCAN도 차원이 높은 데이터를 사용하고있어 노이즈가많고 해석이 어렵다.

  • GMM 클러스터는 겉으로는 집단을 잘 분배한것처럼 보이지만, 정규분포가 아닌 데이터가 대부분이라 효과적인지 판단하기 어렵다.

  • 따라서 주성분분석 등을 이용하여 차원을 축소하고 클러스터링을 수행할 필요가 있다

  • K-means :
    • 클러스터0은 잔액과 구매금액, 현금서비스 모두 낮게 나타남. 이들이 소비하기위해 저가형 상품을 진열할 필요가 있다.
    • 클러스터1은 보통의 잔액이지만 구매금액이 압도적으로 높은 충성고객으로 이들을 이탈시키지 않도록 꾸준한 관리가 필요하다
    • 클러스터2는 잔액과 현금서비스 이용이 높지만 구매금액이 낮은 고객으로 이들의 소비를 촉진시키기위한 상품과 서비스를 개발할 필요가 있다
  • 계층적 클러스터링 :
    • 클러스터1은 k-means의 클러스터2와 유사한 고객유형이다
    • 클러스터0,4는 k-means의 클러스터0과 유사한 고객유형이다
    • 클러스터3은 k-means의 클러스터1과 유사한 고객유형이다
  • DBSCAN :
    • k-means의 클러스터0과 유사한 고객유형들(클러스터 0, 1, 2, 4, 5 …)
    • k-means의 클러스터2와 유사한 고객유형들(클러스터 10, 16)
  • GMM :
    • 클러스터0은 잔액과 구매금액 등 중상층 유형의 고객유형이다. 사람들이 많이찾는 상품들을 추가할 필요가 있을수있다.
    • 클러스터1은 잔액과 현금서비스 이용은 낮은데 구매는 평균이상의 고객이다. 저가형 상품등 추가할 필요가 있다.
    • 클러스터2는 평균이상의 잔액과 현금서비스 이용이지만 구매금액이 낮다. 이들이 구입한 상품들을 찾아 관심을 끌만한 상품을 추가해야한다

PCA (3문제)

문제21: PCA의 주성분 계산 및 시각화

  • 주성분 분석(PCA)을 사용하여 주성분을 계산하고, 첫 두 주성분을 시각화하세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sklearn.decomposition import PCA
data20 = scaled_df.copy()

pca = PCA(n_components=2)
pca_transformed = pca.fit_transform(data20)

# 주성분을 데이터프레임으로 변환
pca_df = pd.DataFrame(data=pca_transformed, columns=['PC1', 'PC2'])

# PCA 결과 시각화
plt.figure(figsize=(10, 6))
sns.scatterplot(x='PC1', y='PC2', data=pca_df, s=100)
plt.title('PCA 2D')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.grid()
plt.show()

png

문제22: 적절한 주성분 수 결정

  • 적절한 주성분 수를 결정하기 위해, 주성분의 설명력(분산 설명 비율)을 시각화하세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA

data21 = scaled_df.copy()

pca = PCA()
pca.fit(data20)

# 주성분의 설명력 (분산 설명 비율)
explained_variance = pca.explained_variance_ratio_

# 설명력 시각화
plt.figure(figsize=(10, 6))
plt.plot(range(1, len(explained_variance) + 1), explained_variance, marker='o', linestyle='--')
plt.title('Explained Variance by Principal Components')
plt.xlabel('Principal Component')
plt.ylabel('Explained Variance Ratio')
plt.xticks(range(1, len(explained_variance) + 1))
plt.grid()
plt.show()

png

1
2
3
4
# 누적 분산 비율 계산
cumulative_variance_ratio = np.cumsum(pca.explained_variance_ratio_)

cumulative_variance_ratio
1
2
3
4
array([0.28944758, 0.50530762, 0.59853074, 0.67316781, 0.73860167,
       0.79126589, 0.83743209, 0.87564215, 0.90736008, 0.93555131,
       0.95312777, 0.97006002, 0.982218  , 0.9918921 , 0.99784582,
       0.99999769, 1.        ])

문제23: 차원 축소를 활용한 데이터 시각화

  • 적절한 주성분 수를 선택한 후, 이를 사용하여 데이터를 저차원으로 변환하세요.
  • 시각화가 가능하다면, 데이터 시각화를 진행해주세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 문제 22번을 통해 완만하게 바뀌는 지점인 주성분 3차원 기준으로 데이터 변환 진행
# 만약, 적정한 설명력을 가진 차원 축소를 하고싶다면 70% 이상인 5-7개의 주성분을 선택해야 함
data23 = scaled_df.copy()
pca = PCA(n_components=3)
principal_components = pca.fit_transform(data23)

pca_df = pd.DataFrame(data=principal_components, columns=['PC1','PC2','PC3'])

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(pca_df['PC1'], pca_df['PC2'], pca_df['PC3'], s=50)

ax.set_title('3D PCA')
ax.set_xlabel('Principal Component 1')
ax.set_ylabel('Principal Component 2')
ax.set_zlabel('Principal Component 3')
plt.show()

png

PCA를 활용한 클러스터링 (6문제)

CUST_ID를 제외하면 분석 중인 데이터에 총 17개의 열이 있습니다. 차원이 너무 많아서 데이터의 분포나 특징을 시각화로 파악하는 것이 어렵네요. 관련하여, PCA를 통해 모든 차원의 특징을 최대한 살리면서, 동시에 데이터의 특징을 한눈에 파악할 수 있도록 2차원으로 차원을 축소해 봅시다.

문제24: PCA를 활용한 차원 축소 후 K-means 클러스터링

  • PCA를 통해 차원 축소한 데이터를 사용하여 K-means 클러스터링을 수행하고 결과를 분석해보세요.
  • 최적의 군집 개수를 Elbow plot를 통해 확인해보세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# PCA 2차원 축소
data24 = scaled_df.copy()
data24.reset_index(drop=True, inplace=True)
pca = PCA(n_components=2)
pca_transformed = pca.fit_transform(data24)
pca_df = pd.DataFrame(data=pca_transformed, columns=['PC1', 'PC2'])

# 차원 축소한 데이터를 K-means 클러스터링 수행
# 엘보우 기법 이용 최적의 군집 개수 확인
inertia = []

# 클러스터 수를 1에서 10까지 변화시키며 K-Means 수행
for k in range(1, 11):
    kmeans = KMeans(n_clusters=k, random_state=42)
    kmeans.fit(pca_df)
    inertia.append(kmeans.inertia_)

# Elbow plot 시각화
plt.figure(figsize=(10, 6))
plt.plot(range(1, 11), inertia, marker='o')
plt.title('Elbow Method for Optimal k')
plt.xlabel('Number of Clusters (k)')
plt.ylabel('Inertia')
plt.xticks(range(1, 11))
plt.grid()
plt.show()

png

1
2
3
4
5
# 엘보우기법 시각화결과 군집개수 3개 기준으로 클러스터링 수행
optimal_k = 3
kmeans = KMeans(n_clusters=optimal_k, random_state=21)
pca_df['cluster'] = kmeans.fit_predict(pca_df)
pca_df
PC1PC2cluster
0-1.272749-2.0120170
1-2.6939973.1221031
21.3087610.5573582
3-0.377331-0.4268540
4-1.411984-1.4586040
............
78550.766955-2.5219540
7856-1.045427-1.4837330
7857-1.247922-1.7831020
78580.060160-2.3811760
7859-1.0563730.6332810

7860 rows × 3 columns

1
2
3
4
# 클러스터 개수 확인
cluster_counts = pca_df['cluster'].value_counts()
print("Cluster Counts:")
print(cluster_counts)
1
2
3
4
5
6
Cluster Counts:
cluster
0    4429
2    1751
1    1680
Name: count, dtype: int64

문제25 : K-means 결과 해석하기

  • 위 클러스터링 결과를 시각화해보세요.
  • K-means의 결과를 해석해봅시다.

    • 각 클러스터별로 고객들은 어떤 특징을 가지나요?
    • 위 분석을 토대로 대출 서비스를 제안한다면, 어떤 전략이 좋을까요?
1
2
3
4
5
6
7
8
9
# 클러스터링 결과 시각화
plt.figure(figsize=(10, 6))
sns.scatterplot(x='PC1', y='PC2', hue='cluster', data=pca_df, palette='bright', s=100)
plt.title('K-Means Clustering on PCA')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.legend(title='Cluster')
plt.grid()
plt.show()

png

1
2
3
4
5
6
7
# PCA 모델 학습
pca = PCA(n_components=2)
pca.fit(data24)

# 주성분 로딩 값 확인
loadings = pd.DataFrame(pca.components_.T, columns=['PC1', 'PC2'], index=data24.columns)
print(loadings)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
                                       PC1       PC2
BALANCE                          -0.090241  0.420046
BALANCE_FREQUENCY                 0.069763  0.228970
PURCHASES                         0.383308  0.167065
ONEOFF_PURCHASES                  0.285660  0.174522
INSTALLMENTS_PURCHASES            0.328199  0.071088
CASH_ADVANCE                     -0.188539  0.361044
PURCHASES_FREQUENCY               0.378707  0.007411
ONEOFF_PURCHASES_FREQUENCY        0.273971  0.140280
PURCHASES_INSTALLMENTS_FREQUENCY  0.320547 -0.021894
CASH_ADVANCE_FREQUENCY           -0.220366  0.343392
CASH_ADVANCE_TRX                 -0.201693  0.349779
PURCHASES_TRX                     0.383084  0.128441
CREDIT_LIMIT                      0.089493  0.256660
PAYMENTS                          0.110606  0.295697
MINIMUM_PAYMENTS                 -0.054220  0.354595
PRC_FULL_PAYMENT                  0.174370 -0.137956
TENURE                            0.066260  0.048020
  • PC1 : 구매관련변수에 영향력있음
  • PC2 : 잔액, 현금서비스, 지불액 관련 변수가 영향력있음
1
2
data24['cluster'] = pca_df['cluster']
data24
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENUREcluster
0-0.759182-0.215160-0.647033-0.562980-0.438501-0.554363-0.781661-0.655331-0.686195-0.681752-0.583682-0.642690-0.963550-0.757062-0.619400-0.5194890.3427640
11.1336500.157721-0.749808-0.562980-0.6364484.353579-1.201814-0.655331-0.8992410.7413030.340613-0.7773790.8869972.0541400.8389680.2535630.3427641
20.7101780.5306030.0831380.554513-0.636448-0.5543631.3191022.889269-0.899241-0.681752-0.5836820.0307501.041209-0.4542220.143178-0.5194890.3427642
30.214168-0.9609240.8650841.603584-0.636448-0.397603-0.991739-0.359948-0.899241-0.207402-0.352608-0.7100341.041209-0.902479-0.837506-0.5194890.3427640
4-0.2941030.530603-0.732571-0.539855-0.636448-0.554363-0.991739-0.359948-0.899241-0.681752-0.583682-0.710034-0.901865-0.413676-0.454805-0.5194890.3427640
.........................................................
7855-0.7708820.530603-0.520481-0.562980-0.194761-0.5543631.319102-0.6553311.292097-0.681752-0.583682-0.305970-0.963550-0.780185-0.6758732.959249-4.3006770
7856-0.3997330.5306030.0581770.521025-0.636448-0.554363-0.841684-0.148960-0.899241-0.681752-0.583682-0.710034-0.963550-0.826397-0.364203-0.519489-4.3006770
7857-0.5704670.530603-0.2488580.109103-0.636448-0.554363-0.841684-0.148960-0.899241-0.681752-0.583682-0.710034-0.963550-0.816891-0.666743-0.519489-4.3006770
7858-0.765283-2.399186-0.002778-0.5629800.802339-0.5543630.598841-0.6553310.561652-0.681752-0.583682-0.305970-0.963550-0.070540-0.812721-0.519489-4.3006770
7859-0.558230-0.055354-0.189605-0.158285-0.1384690.3432900.958971-0.1489600.9268753.3841211.495983-0.305970-0.963550-0.232748-0.6750280.176258-4.3006770

7860 rows × 18 columns

1
2
3
4
# 클러스터별 고객 특성 파악하기
mask1 = data24.groupby('cluster').mean()
mask1['cluster_counts'] = data24['cluster'].value_counts()
mask1.T
cluster012
BALANCE-0.4233971.1090250.006890
BALANCE_FREQUENCY-0.2970380.3660010.400171
PURCHASES-0.379753-0.4777101.418891
ONEOFF_PURCHASES-0.314337-0.3201091.102216
INSTALLMENTS_PURCHASES-0.279808-0.4610051.150061
CASH_ADVANCE-0.3735241.327753-0.329119
PURCHASES_FREQUENCY-0.158581-0.7084161.080807
ONEOFF_PURCHASES_FREQUENCY-0.300788-0.3297861.077230
PURCHASES_INSTALLMENTS_FREQUENCY-0.107056-0.6138250.859723
CASH_ADVANCE_FREQUENCY-0.3671451.359571-0.375781
CASH_ADVANCE_TRX-0.3688861.332030-0.344954
PURCHASES_TRX-0.338236-0.5132531.347979
CREDIT_LIMIT-0.3472750.4003590.494277
PAYMENTS-0.4045500.4532190.588431
MINIMUM_PAYMENTS-0.3604730.8782800.069117
PRC_FULL_PAYMENT0.006958-0.4290230.394028
TENURE-0.065700-0.0607730.224490
cluster_counts4429.0000001680.0000001751.000000
  • 클러스터별 고객 특성
    • 클러스터 0 : 잔액이 가장 적은 고객. 총구매액과 구매빈도가 비교적 낮은 고객. 현금서비스 이용이 낮은 고객. 신용한도는 비교적 낮은 고객. 총 지불액이 가장 낮고 최소 지불액도 가장낮은 고객.
    • 클러스터 1 : 잔액을 가장 많이 보유한 고객. 총구매액과 구매빈도가 가장 낮은 고객. 현금서비스 인출 금액과 빈도, 거래 횟수가 가장 높은 고객. 신용 한도는 비교적 높은 고객. 총 지불액은 비교적 높고 최소 지불액이 가장 높은 고객.
    • 클러스터 2 : 평균적인 잔액 보유 고객. 총구매액과 구매빈도가 가장 높은 고객. 현금서비스 이용이 낮은 고객. 신용한도는 가장 높은 고객. 총 지불액이 가장 높고 최소 지불액은 평균인 고객.
  • 군집별 대출서비스 제안
    • 클러스터 0 : 잔액이 낮아 현금서비스 이용을 유도해야한다. 소액대출 서비스나 장기대출 서비스 혹은 낮은 이자율로 단기대출 서비스 등을 제공하여 현금을 마련할수있도록 유도하고 구매를 촉진
    • 클러스터 1 : 잔액과 현금서비스 이용도 높고 구매빈도는 낮지만 최소지불액은 높은것으로보아 고가형 상품을 구매하는 고객으로 판단된다. 이들이 고가형 상품을 구매할수있도록 흥미있는 상품들을 제시함과 동시에 고가상품 대상으로 잔액과 신용한도 등을 고려하여 합리적인 대출서비스를 제안하여 구매를 촉진한다.
    • 클러스터 2 : 구매빈도에 따른 대출 이자율 할인 등으로 충성고객에 맞춤형 대출서비스를 제안한다. 이들은 현금서비스 이용률이 높지않아 소액대출 등의 금전적인 부담이 적은 대출을 제안할 필요가 있다

문제26 : PCA를 활용한 차원 축소 후 계층적 클러스터링

  • PCA를 통해 차원 축소한 데이터를 사용하여 계층적 클러스터링을 수행하고 덴드로그램을 시각화하세요.
1
2
3
4
5
6
7
8
9
10
11
from scipy.cluster.hierarchy import dendrogram, linkage, cut_tree
import matplotlib.pyplot as plt

# 거리 : ward method 사용
model = linkage(pca_transformed, 'ward')  # single, complete, average, centroid, median 등

# 덴드로그램 사이즈와 스타일 조정
plt.figure(figsize=(10, 7))
dendrogram(model, orientation='top', distance_sort='descending', show_leaf_counts=True)
plt.title('Hierarchical Clustering Dendrogram')
plt.show()

png

문제27 : PCA를 활용한 차원 축소 후 DBSCAN 클러스터링

  • PCA를 통해 차원 축소한 데이터를 사용하여 DBSCAN 클러스터링을 수행하고 결과를 시각화하세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from sklearn.cluster import DBSCAN

# DBSCAN 모델 학습
dbscan_pca = DBSCAN(eps=0.5, min_samples=5)
dbscan_pca.fit(pca_transformed)

# 클러스터 할당 결과
pca_df_dbscan = pd.DataFrame(data=pca_transformed, columns=['PC1', 'PC2'])
pca_df_dbscan['Cluster'] = dbscan_pca.labels_

# 클러스터링 결과 시각화
plt.figure(figsize=(10, 6))
plt.scatter(pca_df_dbscan['PC1'], pca_df_dbscan['PC2'], c=pca_df_dbscan['Cluster'], cmap='viridis')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.title('DBSCAN Clustering on PCA-Reduced Data (2D)')
plt.show()

png

문제28 : PCA를 활용한 차원 축소 후 GMM 클러스터링

  • PCA를 통해 차원 축소한 데이터를 사용하여 GMM 클러스터링을 수행하고 결과를 시각화하세요.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from sklearn.mixture import GaussianMixture

# GMM 모델 학습
gmm_pca = GaussianMixture(n_components=3, random_state=42)
gmm_pca.fit(pca_transformed)

# 클러스터 할당 결과
pca_df_gmm = pd.DataFrame(data=pca_transformed, columns=['PC1', 'PC2'])
pca_df_gmm['Cluster'] = gmm_pca.predict(pca_transformed)

# 클러스터링 결과 시각화
plt.figure(figsize=(10, 6))
plt.scatter(pca_df_gmm['PC1'], pca_df_gmm['PC2'], c=pca_df_gmm['Cluster'], cmap='viridis')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.title('GMM Clustering on PCA-Reduced Data (2D)')
plt.show()

png

문제29 : 원본 데이터와 차원 축소 데이터를 사용한 클러스터링 결과 비교

  • 원본 데이터와 차원 축소 데이터를 사용하여 다양한 클러스터링 기법을 적용한 결과를 비교해보세요.
1
# 위에 분석한 내용을 바탕으로 자유롭게 의견을 작성해주세요.
1
2
data28 = data24.copy()
data28['label'] = cut_tree(model, 5)
1
2
3
4
5
print("K-means 클러스터 수:\n", data10['cluster'].value_counts())
print("PCA K-means 클러스터 수:\n", pca_df['cluster'].value_counts())

print("K-means : \n", data10.groupby('cluster')[['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'PAYMENTS']].mean())
print("PCA K-means : \n", data24.groupby('cluster')[['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'PAYMENTS']].mean())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
K-means 클러스터 수:
 cluster
0    4610
1    1653
2    1597
Name: count, dtype: int64
PCA K-means 클러스터 수:
 cluster
0    4429
2    1751
1    1680
Name: count, dtype: int64
K-means : 
           BALANCE  PURCHASES  CASH_ADVANCE  PAYMENTS
cluster                                             
0       -0.394715  -0.373719     -0.367362 -0.391423
1        0.005521   1.493598     -0.337212  0.623774
2        1.133694  -0.467172      1.409488  0.484259
PCA K-means : 
           BALANCE  PURCHASES  CASH_ADVANCE  PAYMENTS
cluster                                             
0       -0.423397  -0.379753     -0.373524 -0.404550
1        1.109025  -0.477710      1.327753  0.453219
2        0.006890   1.418891     -0.329119  0.588431
  • K-means 클러스터는 PCA 전/후 집단별 표본수는 비슷하다.
  • 클러스터별 변수 데이터 평균또한 PCA에 의해 달라지지도 명확해지지도 않았다.
1
2
3
4
5
print("계층적 클러스터링 클러스터 수:\n", data12['label'].value_counts())
print("PCA 계층적 클러스터링 클러스터 수:\n", data28['label'].value_counts())

print("계층적 클러스터링 : \n", data12.groupby('label')[['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'PAYMENTS']].mean())
print("PCA 계층적 클러스터링 : \n", data28.groupby('label')[['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'PAYMENTS']].mean())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
계층적 클러스터링 클러스터 수:
 label
2    2971
0    2780
1    1135
4     570
3     404
Name: count, dtype: int64
PCA 계층적 클러스터링 클러스터 수:
 label
0    2507
3    2003
4    1928
2     965
1     457
Name: count, dtype: int64
계층적 클러스터링 : 
         BALANCE  PURCHASES  CASH_ADVANCE  PAYMENTS
label                                             
0     -0.316863  -0.507112     -0.309739 -0.394281
1      1.143575  -0.555477      1.630805  0.605988
2     -0.091108   0.371079     -0.323698  0.044065
3      0.072240   2.703237     -0.344377  1.284015
4     -0.308039  -0.270784      0.194640 -0.423429
PCA 계층적 클러스터링 : 
         BALANCE  PURCHASES  CASH_ADVANCE  PAYMENTS
label                                             
0     -0.585739  -0.354672     -0.520650 -0.496698
1      2.132258  -0.527027      2.378315  0.887991
2      0.769819   0.304411      0.411214  0.564403
3      0.112583  -0.706605      0.356102 -0.184201
4     -0.246044   1.167836     -0.462507  0.344250
  • 계층적 클러스터링은 PCA 사용후 좀 더 고르게 군집별 표본이 분포되었다
  • 데이터또한 PCA 사용후 vip집단(클러스터1), 저소득 혹은 저지출(클러스터0) 등 집단 특성이 명확해졌고, 클러스터3같이 평균의 잔액에서 구매금액이 낮은 유형의 고객들을 파악할수있었다
1
2
data29 = data24.copy()
data29['cluster'] = pca_df_dbscan['Cluster']
1
2
3
4
5
6
print("DBSCAN 클러스터 수:\n", data14['cluster'].value_counts())
print("PCA DBSCAN 클러스터 수:\n", data29['cluster'].value_counts())

print("DBSCAN : \n", data14.groupby('cluster')[['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'PAYMENTS']].mean())
print("PCA DBSCAN : \n", data29.groupby('cluster')[['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'PAYMENTS']].mean())

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
DBSCAN 클러스터 수:
 cluster
-1     4891
 0     2711
 9       47
 4       28
 2       16
 6       15
 13      12
 1       10
 20       8
 11       7
 17       7
 12       7
 27       7
 28       7
 23       6
 5        6
 10       6
 15       6
 7        6
 3        6
 26       5
 19       5
 22       5
 24       5
 8        5
 18       5
 16       5
 29       5
 14       4
 25       4
 21       3
Name: count, dtype: int64
PCA DBSCAN 클러스터 수:
 cluster
 0    7804
-1      47
 1       5
 2       4
Name: count, dtype: int64
DBSCAN : 
           BALANCE  PURCHASES  CASH_ADVANCE  PAYMENTS
cluster                                             
-1       0.204454   0.308840      0.220776  0.316698
 0      -0.332428  -0.517793     -0.371473 -0.525591
 1      -0.750215  -0.252501     -0.554363 -0.591529
 2      -0.246830  -0.726132      0.385987 -0.543469
 3       1.865787  -0.736625      0.180720 -0.023997
 4      -0.121388  -0.705041     -0.344316 -0.633206
 5      -0.699802  -0.337088     -0.554363 -0.592240
 6      -0.636324  -0.303431     -0.542537 -0.665515
 7      -0.752321  -0.128951     -0.554363 -0.492969
 8      -0.734494   0.252138     -0.554363 -0.279774
 9      -0.782024  -0.407821     -0.554363 -0.690939
 10      2.264016  -0.737627      1.372147  0.015497
 11     -0.590144  -0.749808      0.344613  0.998125
 12     -0.758299  -0.398275     -0.554363 -0.710500
 13     -0.753246  -0.111330     -0.554363 -0.423695
 14     -0.734383  -0.118559     -0.554363 -0.538804
 15     -0.766121  -0.458413     -0.554363 -0.740171
 16      3.126313  -0.749808      1.449965  0.255077
 17     -0.752260  -0.510008     -0.554363 -0.763259
 18     -0.663792   1.271664     -0.554363  0.358995
 19     -0.725419   0.450775     -0.554363 -0.015808
 20     -0.759766  -0.231603     -0.554363 -0.599557
 21     -0.315080   0.152215     -0.554363 -0.355780
 22      0.280371  -0.741190      1.052935  0.860523
 23     -0.730730  -0.463189     -0.554363 -0.826363
 24     -0.238408  -0.749808      0.327747 -0.760784
 25     -0.514970  -0.749808      0.060645 -0.517330
 26     -0.772262  -0.576344     -0.554363 -0.611633
 27     -0.169226  -0.732417     -0.272267 -0.765029
 28     -0.764356  -0.343223     -0.554363 -0.668904
 29     -0.754164  -0.494573     -0.554363 -0.765622
PCA DBSCAN : 
           BALANCE  PURCHASES  CASH_ADVANCE  PAYMENTS
cluster                                             
-1       2.226393   2.788045      1.783141  2.299069
 0      -0.016820  -0.017252     -0.014855 -0.016116
 1       2.652106   0.180706      3.955163  2.736456
 2       3.341396   0.673516      3.086260  1.008206
  • DBSCAN은 차원축소를 통해 노이즈를 대폭감소하였다(4,891개 → 47개)
  • 다만, 클러스터0에서 대부분의 데이터가 모여있어 데이터를 구분하지 못하였다고 할수있다
  • eps와 최소샘플등을 조정하거나 추가적인 모델개선작업이 필요하다
1
2
data30 = data24.copy()
data30['cluster'] = pca_df_gmm['Cluster']
1
2
3
4
5
print("GMM 클러스터 수:\n", data15['gmm_label'].value_counts())
print("PCA GMM 클러스터 수:\n", data30['cluster'].value_counts())

print("GMM : \n", data15.groupby('gmm_label')[['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'PAYMENTS']].mean())
print("PCA GMM : \n", data30.groupby('cluster')[['BALANCE', 'PURCHASES', 'CASH_ADVANCE', 'PAYMENTS']].mean())
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
GMM 클러스터 수:
 gmm_label
1    3534
2    2572
0    1754
Name: count, dtype: int64
PCA GMM 클러스터 수:
 cluster
1    3824
2    2394
0    1642
Name: count, dtype: int64
GMM : 
             BALANCE  PURCHASES  CASH_ADVANCE  PAYMENTS
gmm_label                                             
0          0.567485   0.456135      0.431318  0.452079
1         -0.483555   0.274891     -0.554274 -0.174449
2          0.277416  -0.688774      0.467447 -0.068601
PCA GMM : 
           BALANCE  PURCHASES  CASH_ADVANCE  PAYMENTS
cluster                                             
0        0.863400   1.118011      0.511977  0.955534
1       -0.488237  -0.038512     -0.493579 -0.305846
2        0.187684  -0.705306      0.437251 -0.166847
  • GMM은 차원축소 전과 후의 군집별 표본차이는 비슷하게 나왔다.
  • 다만, 클러스터 0처럼 일부 군집은 데이터 특성이 좀 더 명확하게 관찰되었다.
This post is licensed under CC BY 4.0 by the author.