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
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
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
Drive already mounted at /content/drive; to attempt to forcibly remount, call drive.mount("/content/drive", force_remount=True).
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

1
2
# 데이터프레임의 기본 정보 확인
data.info()
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
1
2
# 각 변수의 기술 통계량 확인
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

중복값 처리

1
2
# 중복값 확인
data.duplicated(keep=False).sum()
1
0

결측치 처리

1
2
3
data = data.drop(columns='CUST_ID')
data.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
2
3
4
# 신용 한도 결측치 1개는 단순제거를 수행한다
data = data.dropna(subset=['CREDIT_LIMIT'])

data['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
# 최소 지불액 시각화 : 75%가 825인데 max값이 76406이다. 결측치는 중앙값처리가 더 적절해보인다.
sns.histplot(data['MINIMUM_PAYMENTS'])
plt.title("MINIMUM_PAYMENTS의 데이터 분포")
plt.show()

png

1
2
# 최소지불액과 지불액 관계 : 최소지불액 결측치가 지불액에서 어떻게 나타나는지 확인
data['PAYMENTS'][data['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 = data['MINIMUM_PAYMENTS'].median()

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

data.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
data.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'],
      dtype='object')
1
2
3
4
5
6
7
8
9
10
11
# 이상치 제거 전 시각화
plt.figure(figsize=(20,16))
for index, col in enumerate(data):
    plt.subplot(4, 5, index + 1)
    sns.boxplot(data=data, y=col)
    plt.title(f"{col} 데이터 분포")
    plt.xlabel('')
    plt.ylabel('')

plt.tight_layout()
plt.show()

png

1
2
3
fig, ax = plt.subplots(figsize=(12, 4))
sns.boxplot(data=data[['PURCHASES','ONEOFF_PURCHASES','INSTALLMENTS_PURCHASES']], orient='h')
sns.despine()

png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 이상치가 전반적으로 많다. 조금 넓게 범위를 잡아 이상치를 처리해보자
# 이상치 처리 : IQR 0.10 ~ 0.90 기준
data1 = data.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(data1, weight=1.5)

# 이상치를 제거한 데이터 프레임만 추가
data1 = data1[~outlier_idx_cust_df]
data1
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
# 이상치 처리후 약 88% 데이터가 남아있음
1
2
3
4
5
6
7
8
9
10
11
# 이상치 제거 후 시각화
plt.figure(figsize=(20,16))
for index, col in enumerate(data1):
    plt.subplot(4, 5, index + 1)
    sns.boxplot(data=data1, y=col)
    plt.title(f"{col} 데이터 분포")
    plt.xlabel('')
    plt.ylabel('')

plt.tight_layout()
plt.show()

png

1
2
3
fig, ax = plt.subplots(figsize=(12, 4))
sns.boxplot(data=data1[['PURCHASES','ONEOFF_PURCHASES','INSTALLMENTS_PURCHASES']], orient='h')
sns.despine()

png

1
2
print(data.shape)
print(data1.shape)
1
2
(8949, 17)
(7860, 17)

데이터 스케일링(표준화)

1
2
3
4
from sklearn.preprocessing import StandardScaler
X = pd.DataFrame(StandardScaler().fit_transform(data1))
X.columns = data1.columns
X
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENURE
0-0.759230-0.215174-0.647074-0.563016-0.438529-0.554399-0.781710-0.655372-0.686239-0.681795-0.583719-0.642731-0.963612-0.757110-0.619439-0.5195220.342786
11.1337220.157732-0.749856-0.563016-0.6364884.353856-1.201890-0.655372-0.8992980.7413500.340635-0.7774280.8870542.0542700.8390210.2535790.342786
20.7102230.5306370.0831430.554549-0.636488-0.5543991.3191862.889453-0.899298-0.681795-0.5837190.0307521.041276-0.4542510.143187-0.5195220.342786
30.214181-0.9609850.8651391.603686-0.636488-0.397629-0.991802-0.359971-0.899298-0.207415-0.352631-0.7100801.041276-0.902537-0.837560-0.5195220.342786
4-0.2941220.530637-0.732618-0.539889-0.636488-0.554399-0.991802-0.359971-0.899298-0.681795-0.583719-0.710080-0.901923-0.413702-0.454834-0.5195220.342786
......................................................
7855-0.7709310.530637-0.520514-0.563016-0.194773-0.5543991.319186-0.6553721.292180-0.681795-0.583719-0.305989-0.963612-0.780234-0.6759162.959437-4.300951
7856-0.3997580.5306370.0581800.521058-0.636488-0.554399-0.841737-0.148969-0.899298-0.681795-0.583719-0.710080-0.963612-0.826449-0.364226-0.519522-4.300951
7857-0.5705030.530637-0.2488730.109110-0.636488-0.554399-0.841737-0.148969-0.899298-0.681795-0.583719-0.710080-0.963612-0.816943-0.666785-0.519522-4.300951
7858-0.765332-2.399339-0.002778-0.5630160.802390-0.5543990.598879-0.6553720.561688-0.681795-0.583719-0.305989-0.963612-0.070544-0.812773-0.519522-4.300951
7859-0.558265-0.055357-0.189617-0.158295-0.1384770.3433120.959032-0.1489690.9269343.3843361.496078-0.305989-0.963612-0.232763-0.6750710.176270-4.300951

7860 rows × 17 columns

데이터 분석

1
data1.describe()
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENURE
count7860.0000007860.0000007860.0000007860.0000007860.0000007860.0000007860.0000007860.0000007860.0000007860.0000007860.0000007860.0000007860.0000007860.0000007860.0000007860.0000007860.000000
mean1308.9498290.870638695.998356389.514023306.734674727.7455250.4767370.1848810.3517390.1197692.52595411.5433844124.1035101252.410911535.7026760.14933311.630916
std1670.2841120.243801928.235177691.878859481.9478551312.7589710.3966810.2821190.3911500.1756784.32762114.8491173242.2840511387.744782639.6400530.2874611.076788
min0.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.0000000.00000050.0000000.0000000.0000000.0000007.000000
25%101.1670290.87500037.8775000.0000000.0000000.0000000.0833330.0000000.0000000.0000000.0000001.0000001500.000000368.707269161.9208320.00000012.000000
50%757.8189311.000000338.64500020.00000079.9250000.0000000.4166670.0833330.1666670.0000000.0000007.0000003000.000000771.822248269.9247550.00000012.000000
75%1765.0449171.000000972.447500483.605000426.602500950.9781730.9166670.2500000.7500000.1666673.00000015.0000006000.0000001605.538063666.7029140.12500012.000000
max10598.4677701.0000006058.0300004000.0000002840.0600007663.9062581.0000001.0000001.0000001.00000025.00000092.00000021500.0000009481.4840584192.5650711.00000012.000000

EDA

1
2
plt.figure(figsize=(10,8))
sns.heatmap(data1.corr(), annot=True, fmt='.2f')
1
<Axes: >

png

1
2
corr_list = data1.corr()['PURCHASES'].abs().sort_values(ascending = False)
corr_list
PURCHASES
PURCHASES1.000000
ONEOFF_PURCHASES0.862674
PURCHASES_TRX0.750810
INSTALLMENTS_PURCHASES0.687412
ONEOFF_PURCHASES_FREQUENCY0.616113
PURCHASES_FREQUENCY0.571824
PURCHASES_INSTALLMENTS_FREQUENCY0.437147
PAYMENTS0.407756
CREDIT_LIMIT0.279801
PRC_FULL_PAYMENT0.196942
CASH_ADVANCE_FREQUENCY0.189060
BALANCE_FREQUENCY0.171853
CASH_ADVANCE_TRX0.157633
CASH_ADVANCE0.148370
TENURE0.089850
MINIMUM_PAYMENTS0.048712
BALANCE0.039330


  • 총 구매액 관련 상관관계는 일회성 구매액, 구매 횟수, 할부 구매액, 구매빈도 등이 높은 상관관계가 관찰되었다
1
2
3
4
5
6
7
8
9
10
plt.figure(figsize=(20,16))
for index, col in enumerate(data1):
    plt.subplot(4, 5, index + 1)
    sns.histplot(data1[col], kde=True)
    plt.title(f"{col} 데이터 분포")
    plt.xlabel('')
    plt.ylabel('')

plt.tight_layout()
plt.show()

png

  • 대부분 한쪽으로 치우쳐진 분포를 보이며 정규분포 형태는 관찰되지않음

주성분분석(PCA)

적절한 주성분 수 결정을 위해 분산 설명 비율 계산

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
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA

pca = PCA()
pca.fit(X)

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

# 설명력 시각화
plt.figure(figsize=(10, 6))
plt.plot(range(1, len(explained_variance) + 1), explained_variance, marker='o')
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()

# 누적 분산 비율 계산
cumulative_variance_ratio = np.cumsum(pca.explained_variance_ratio_)
print(cumulative_variance_ratio)

png

1
2
3
[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.        ]
1
2
3
4
5
# 주성분의 설명력이 70% 이상은 아니지만 완만하게 바뀌는 지점인 주성분 3차원 기준으로 데이터 변환 진행
pca = PCA(n_components=3)
pca.fit(X)
loadings = pd.DataFrame(pca.components_.T, columns=['PC1','PC2','PC3'], index=X.columns)
loadings
PC1PC2PC3
BALANCE-0.0902410.4200460.082117
BALANCE_FREQUENCY0.0697630.2289700.214685
PURCHASES0.3833080.167065-0.174996
ONEOFF_PURCHASES0.2856600.174523-0.462194
INSTALLMENTS_PURCHASES0.3281990.0710880.326693
CASH_ADVANCE-0.1885390.3610440.050141
PURCHASES_FREQUENCY0.3787070.0074120.262844
ONEOFF_PURCHASES_FREQUENCY0.2739710.140280-0.406676
PURCHASES_INSTALLMENTS_FREQUENCY0.320546-0.0218930.494744
CASH_ADVANCE_FREQUENCY-0.2203660.3433920.084174
CASH_ADVANCE_TRX-0.2016930.3497790.094663
PURCHASES_TRX0.3830840.1284400.078035
CREDIT_LIMIT0.0894930.256659-0.178085
PAYMENTS0.1106070.295697-0.151794
MINIMUM_PAYMENTS-0.0542200.3545950.193431
PRC_FULL_PAYMENT0.174370-0.137956-0.008513
TENURE0.0662600.048020-0.007403
1
2
3
4
5
6
7
8
9
10
11
# 각 주성분별 막대 그래프 생성
fig, axes = plt.subplots(1, 3, figsize=(15, 6))
axes = axes.flatten()

# 각 주성분별 막대 그래프 생성
for i in range(3):
    loadings.iloc[:, i].sort_values(ascending=True).plot(kind='barh', ax=axes[i])
    axes[i].set_title(f'주성분 {i+1}')

plt.tight_layout()
plt.show()

png

  • 주성분1 : 구매 관련 변수에서 높은 수치, 현금서비스 관련 낮은 수치가 관찰됨
  • 주성분2 : 잔액과 현금서비스, 지불액에서 높은 수치, 구매 관련 변수에서 낮은 수치가 관찰됨
  • 주성분3 : 할부 구매 관련 변수에서 높은 수치, 일회성 구매는 낮은 수치가 관찰됨

PCA이용 차원축소후 K-means 클러스터링

1
2
3
4
pca.fit(X)
x_pca = pca.transform(X)
pca_df = pd.DataFrame(x_pca, columns=['PC1', 'PC2', 'PC3'])
pca_df.head()
PC1PC2PC3
0-1.272830-2.012145-0.178806
1-2.6941683.122301-0.243752
21.3088450.557380-1.804754
3-0.377355-0.426880-2.183236
4-1.412074-1.458696-0.358182
1
2
3
4
5
6
7
8
9
10
11
12
13
# 엘보우 기법 사용 적절한 k값 찾기
from sklearn.cluster import KMeans

ks = range(1,10)
inertias = []

for k in ks:
    model = KMeans(n_clusters=k)
    model.fit(pca_df)
    inertias.append(model.inertia_)

#시각화
sns.lineplot(x=ks, y=inertias, marker='o')
1
<Axes: >

png

1
2
3
4
5
# 엘보우기법 시각화결과 군집개수 4개 기준으로 클러스터링 수행
optimal_k = 4
kmeans = KMeans(n_clusters=optimal_k, random_state=21)
pca_df['cluster'] = kmeans.fit_predict(pca_df)
pca_df
PC1PC2PC3cluster
0-1.272830-2.012145-0.1788062
1-2.6941683.122301-0.2437521
21.3088450.557380-1.8047543
3-0.377355-0.426880-2.1832362
4-1.412074-1.458696-0.3581822
...............
78550.767003-2.5221121.5924770
7856-1.045493-1.483827-0.9164292
7857-1.248002-1.783215-0.7462842
78580.060164-2.3813320.5437610
7859-1.0564410.6333221.3011612

7860 rows × 4 columns

1
2
3
4
# 클러스터 개수 확인
cluster_counts = pca_df['cluster'].value_counts()
print("Cluster Counts:")
print(cluster_counts)
1
2
3
4
5
6
7
Cluster Counts:
cluster
2    3360
0    2048
1    1336
3    1116
Name: count, dtype: int64
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 3D 시각화
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')

# 클러스터에 따라 다른 색상으로 시각화
scatter = ax.scatter(pca_df['PC1'], pca_df['PC2'], pca_df['PC3'],
                     c=pca_df['cluster'], cmap='viridis', s=50)

# 축 레이블 설정
ax.set_xlabel('PC1')
ax.set_ylabel('PC2')
ax.set_zlabel('PC3')
ax.set_title('3D PCA 클러스터링 결과')

# 범례 추가
legend1 = ax.legend(*scatter.legend_elements(), title="Clusters")
ax.add_artist(legend1)

plt.show()

png

1
2
data3 = X.copy()
data3['cluster'] = pca_df['cluster']
1
data3
BALANCEBALANCE_FREQUENCYPURCHASESONEOFF_PURCHASESINSTALLMENTS_PURCHASESCASH_ADVANCEPURCHASES_FREQUENCYONEOFF_PURCHASES_FREQUENCYPURCHASES_INSTALLMENTS_FREQUENCYCASH_ADVANCE_FREQUENCYCASH_ADVANCE_TRXPURCHASES_TRXCREDIT_LIMITPAYMENTSMINIMUM_PAYMENTSPRC_FULL_PAYMENTTENUREcluster
0-0.759230-0.215174-0.647074-0.563016-0.438529-0.554399-0.781710-0.655372-0.686239-0.681795-0.583719-0.642731-0.963612-0.757110-0.619439-0.5195220.3427862
11.1337220.157732-0.749856-0.563016-0.6364884.353856-1.201890-0.655372-0.8992980.7413500.340635-0.7774280.8870542.0542700.8390210.2535790.3427861
20.7102230.5306370.0831430.554549-0.636488-0.5543991.3191862.889453-0.899298-0.681795-0.5837190.0307521.041276-0.4542510.143187-0.5195220.3427863
30.214181-0.9609850.8651391.603686-0.636488-0.397629-0.991802-0.359971-0.899298-0.207415-0.352631-0.7100801.041276-0.902537-0.837560-0.5195220.3427862
4-0.2941220.530637-0.732618-0.539889-0.636488-0.554399-0.991802-0.359971-0.899298-0.681795-0.583719-0.710080-0.901923-0.413702-0.454834-0.5195220.3427862
.........................................................
7855-0.7709310.530637-0.520514-0.563016-0.194773-0.5543991.319186-0.6553721.292180-0.681795-0.583719-0.305989-0.963612-0.780234-0.6759162.959437-4.3009510
7856-0.3997580.5306370.0581800.521058-0.636488-0.554399-0.841737-0.148969-0.899298-0.681795-0.583719-0.710080-0.963612-0.826449-0.364226-0.519522-4.3009512
7857-0.5705030.530637-0.2488730.109110-0.636488-0.554399-0.841737-0.148969-0.899298-0.681795-0.583719-0.710080-0.963612-0.816943-0.666785-0.519522-4.3009512
7858-0.765332-2.399339-0.002778-0.5630160.802390-0.5543990.598879-0.6553720.561688-0.681795-0.583719-0.305989-0.963612-0.070544-0.812773-0.519522-4.3009510
7859-0.558265-0.055357-0.189617-0.158295-0.1384770.3433120.959032-0.1489690.9269343.3843361.496078-0.305989-0.963612-0.232763-0.6750710.176270-4.3009512

7860 rows × 18 columns

1
2
3
4
# 클러스터별 고객 특성 파악하기
mask1 = data3.groupby('cluster').mean()
mask1['cluster_counts'] = data3['cluster'].value_counts()
mask1.T
cluster0123
BALANCE-0.4396211.345252-0.3118160.135114
BALANCE_FREQUENCY0.1619790.409125-0.3939790.399143
PURCHASES0.060833-0.425654-0.4766331.832951
ONEOFF_PURCHASES-0.364703-0.291965-0.2704821.833151
INSTALLMENTS_PURCHASES0.641619-0.401137-0.5299120.898198
CASH_ADVANCE-0.4507761.544578-0.238864-0.302675
PURCHASES_FREQUENCY0.982258-0.612000-0.7027821.045982
ONEOFF_PURCHASES_FREQUENCY-0.350751-0.291416-0.2532451.754995
PURCHASES_INSTALLMENTS_FREQUENCY1.164645-0.535940-0.6884150.576969
CASH_ADVANCE_FREQUENCY-0.5084661.524861-0.181088-0.347151
CASH_ADVANCE_TRX-0.4570041.552293-0.232616-0.319293
PURCHASES_TRX0.360227-0.449306-0.5589471.559669
CREDIT_LIMIT-0.2867680.553079-0.2934690.747710
PAYMENTS-0.2779610.578240-0.3415140.846080
MINIMUM_PAYMENTS-0.1934861.073920-0.3207390.035110
PRC_FULL_PAYMENT0.402595-0.433647-0.2019800.388433
TENURE-0.002320-0.029130-0.0610530.222947
cluster_counts2048.0000001336.0000003360.0000001116.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
for i in range(4):  # 0, 1, 2, 3 클러스터 모두 포함
    if i not in mask1.index:
        mask1.loc[i] = [0] * (mask1.shape[1] - 1) + [0]  # 평균값 0으로 설정
    else:
        mask1.loc[i, 'cluster_counts'] = mask1['cluster_counts'].get(i, 0)  # 클러스터 개수 유지

mask1 = mask1.sort_index()  # 인덱스 정렬

# 그래프 생성
fig, axes = plt.subplots(3, 2, figsize=(12, 18))  # 3행 2열의 서브플롯 설정
axes = axes.flatten()

# 각 클러스터별 평균값 시각화
for i in range(len(mask1) - 1):  # 클러스터 평균값에 대해서만
    mask1.iloc[i].drop('cluster_counts').sort_values(ascending=True).plot(kind='barh', ax=axes[i])
    axes[i].set_title(f'클러스터 {i}')

# 클러스터 개수 그래프
mask1['cluster_counts'][:4].plot(kind='barh', ax=axes[4], color='orange')
axes[4].set_xlabel('고객 수')
axes[4].set_ylabel('클러스터')

# 남는 서브플롯 비우기
axes[5].axis('off')  # 여섯 번째 서브플롯은 비워둡니다.

plt.tight_layout()
plt.show()

png

  • 클러스터0(할부 구매가 활발한 고객)
    • 구매 빈도와 할부 구매 비율이 높고, 현금서비스와 일회성 구매는 낮게 관찰됨
    • 일회성 구매보다는 소액으로 나누어 자주 구매하는 성향을 보이고 있음
    • 마케팅 전략
      • 장기 할부 혜택이나 포인트 적립 프로그램을 제안하여 다양한 상품을 구매할 수 있도록 유도.특정 고가의 상품을 할부로 구매하는 방향도 검토 필요
      • 해당유형은 빈번한 할부 구매로 회사의 제품에 만족하고있다. 지인을 추천할 경우 다양한 혜택을 제공하여 더많은 충성고객 확보(비슷한 성향의 사람들이 유입되어 충성고객이 늘어날 수 있음
  • 클러스터1(현금 서비스 이용고객)
    • 현금 서비스 사용 빈도가 높고 잔액과 최소지불액 또한 높게 관찰됨
    • 구매 관련 변수들이 평균이하로, 구매보다는 현금 서비스를 많이 이용하는 고객
    • 마케팅 전략
      • 대출 이자율을 개선해주는 프로모션이나 현금서비스 대체 상품 프로그램 제공
      • 고객의 재무관리 상담 서비스를 도입하여 원활한 현금흐름 유도
  • 클러스터2(소극적인 고객)
    • 모든 지표들이 평균이하로, 특히 구매관련빈도가 매우 낮게 관찰됨
    • 카드 사용 활동이 거의 없고, 소비에 대해 신중해 구매를 자주하지 않고 필요할 때만 거래를 하는 경향이 관찰됨
    • 마케팅 전략
      • 카드 사용 유도를 위해 사용 빈도에 따라 할인 혜택을 제공하거나 초기 혜택 제공
      • 관심 상품들을 분석하여 특별 할인이나 1+1 행사 등 관심을 지속적으로 유도하여 구매빈도를 높임
      • 고객의 구매 주기를 파악하여 적절한 주기를 설정하여 관심 상품에 대한 안내를 개인화하여 고객의 관심유도
      • 현금서비스 이용을 유도하기 위해 낮은 이자율이나 장기적인 대출 서비스 제공 검토 혹은 재무관리 상담 서비스 제공
  • 클러스터3(VIP 고객)
    • 총 구매액과 일회성 구매액, 구매 빈도 등 가장 높게 관찰됨
    • 현금서비스 이용은 평균이하로 관찰되며, 고액상품을 일시불로 구매하는 유형
    • 마케팅 전략
      • VIP서비스나 고액상품 구매시 제공되는 혜택을 강조하고, VIP대상 이벤트나 한정된 고가상품 구매 우선권 제공 등 구매를 유도할 수 있는 이벤트 제공
This post is licensed under CC BY 4.0 by the author.