2일차부터는 실제로 데이터를 열어보고 확인하는 단계다. 이 글에서는 모델링에 들어가기 전에 반드시 확인해야 하는 기본 분포와 결측 구조만 다룬다.
1. 데이터 확인
1.1 데이터 병합
데이터 전처리에 앞서 train_transaction, train_identity 데이터들을 TransactionID 컬럼을 기준으로 하나로 병합해서 처리한다.
df = df_tr.merge(df_id, on='TransactionID', how='left')
1.2 데이터 타입 확인
info()를 통해 전체 데이터 구조 먼저 확인한다.
병합된 데이터는 총 590,540건의 거래 데이터와 434개의 컬럼으로 구성되어 있으며,컬럼 타입을 살펴보면 다음과 같은 특징을 가진다.
- float64 타입 컬럼: 399개
- int64 타입 컬럼: 4개
- object 타입 컬럼: 31개
1.3 결측치 및 이상치 확인
병합된 데이터는 총 434개의 컬럼으로 구성되어 있으며, 이 중 다수의 컬럼에서 결측치가 대량으로 존재한다.
특히 결측률을 기준으로 상위 컬럼을 확인한 결과, identity 계열(id_XX) 변수들이 대부분 99% 이상 결측을 가지고 있음을 확인할 수 있었다.
df.isnull().mean().sort_values(ascending=False)
겉으로 보기에는 분석에 도움이 되지 않을 것처럼 보이지만, 결측 여부에 따라 사기 거래 비율이 달라지는지 함께 확인해보았다
# 결측 여부에 따라 사기 거래 비율 확인
cols = df.isnull().mean().sort_values(ascending=False).head(10).index.tolist()
res = []
for c in cols:
res.append({
'col': c,
'miss_rate': df.isnull().mean().sort_values(ascending=False)[c],
'fraud_miss': df.loc[df[c].isnull(), 'isFraud'].mean(),
'fraud_not': df.loc[df[c].notnull(), 'isFraud'].mean()
})
pd.DataFrame(res)
| col | missing_rate | fraud_rate_if_missing | fraud_rate_if_not_missing |
| id_24 | 0.991962 | 0.034587 | 0.084685 |
| id_25 | 0.99131 | 0.034584 | 0.081255 |
| id_07 | 0.991271 | 0.03457 | 0.082638 |
| id_08 | 0.991271 | 0.03457 | 0.082638 |
| id_21 | 0.991264 | 0.034571 | 0.082574 |
| id_26 | 0.991257 | 0.034573 | 0.082316 |
| id_27 | 0.991247 | 0.034571 | 0.082414 |
| id_23 | 0.991247 | 0.034571 | 0.082414 |
| id_22 | 0.991247 | 0.034571 | 0.082414 |
| dist2 | 0.936284 | 0.030623 | 0.099158 |
대부분의 id_XX 변수에서 값이 없는 경우보다 값이 존재하는 경우 사기 비율이 더 높게 나타났다.
예를 들어 id_24는 결측일 때 사기 비율이 약 3.4%인 반면, 값이 존재할 때는 8% 이상으로 차이가 뚜렷하다.
이는 identity 정보가 포함된 거래가 일반 거래와는 다른 성격을 가질 가능성이 있음을 보여준다.
따라서 이러한 컬럼들은 단순히 제거하기보다는 결측 여부 자체를 하나의 피처로 활용하는 방향이 더 적절해 보인다.
2. EDA
앞선 단계에서는 데이터의 전체 구조와 결측 특성을 중심으로 살펴보았다.
이제부터는 개별 변수들이 사기 거래와 어떤 패턴을 가지는지, 거래 환경에 따라 데이터의 성격이 어떻게 달라지는지를 탐색한다.
2.1 사용자 거래 식별 패턴 분석 (UID)
사기 거래는 단일 거래보다는 특정 사용자 또는 유사한 거래 환경에서 반복적으로 발생하는 경우가 많다.
이를 확인하기 위해 카드 정보와 주소 정보를 조합한 사용자 식별자(uid)를 생성하여 반복 거래 패턴을 분석하였다.
2.1-1 UID 생성 방식
카드 관련 컬럼 중 card1부터 card4는 카드 번호 및 네트워크에 대한 상위 수준의 안정적인 정보를 담고 있는 반면, card5, card6과 같은 컬럼은 결측이 많거나 값의 변동성이 커 제외하였다.
keys = [c for c in ['card1', 'card2', 'card3', 'card4', 'addr1'] if c in df.columns]
df['uid'] = df[keys].astype(str).fillna('NA').agg('_'.join, axis=1)
반복성이 충분히 확보되는 수준에서 uid를 구성하기 위해 card1 ~ card4, 그리고 지역 정보를 대표하는 addr1까지만 사용하였다.
2.1-2 UID 등장 횟수에 따른 사기 비율
생성한 uid를 기준으로 각 uid의 등장 횟수(uid_cnt)를 계산하고 등장 빈도에 따라 사기 거래 비율이 어떻게 달라지는지 확인하였다.
df.groupby(pd.qcut(df['uid_cnt'], 10, duplicates='drop'))['isFraud'].agg(['mean', 'count'])
| uid_cnt | mean | count |
| (0.999, 6.0] | 0.030936 | 63002 |
| (6.0, 17.0] | 0.028181 | 58230 |
| (17.0, 34.0] | 0.026624 | 57092 |
| (34.0, 65.0] | 0.029435 | 58399 |
| (65.0, 119.0] | 0.031746 | 58621 |
| (119.0, 231.0] | 0.043088 | 59413 |
| (231.0, 474.0] | 0.033477 | 58846 |
| (474.0, 890.0] | 0.040216 | 59528 |
| (890.0, 2104.0] | 0.044137 | 59134 |
| (2104.0, 9900.0] | 0.041853 | 58275 |
uid의 등장 횟수가 많아질수록 사기 거래 비율이 전반적으로 증가하는 경향을 보였으며
이를 바탕으로 uid_cnt를 반복 거래 패턴을 반영하는 피처로 사용하였다.
2.2 UID 기반 거래 금액 집단 통계 분석
앞선 분석에서 uid의 반복 빈도가 사기 거래와 일정한 연관성을 보였다. 다음으로는 동일한 uid 내에서 거래 금액 패턴이 어떻게
나타나는지 확인하기 위해 uid 기준의 거래 금액 집단 통계를 생성하였다.
2.2-1 UID 기준 거래 금액 통계 생성
먼저 uid별 거래 금액의 평균과 표준편차를 계산하고
각 거래가 해당 uid의 평균적인 거래 패턴에서 얼마나 벗어났는지를 나타내는 지표를 생성하였다.
g = df.groupby('uid')['TransactionAmt']
df['uid_amt_mean'] = g.transform('mean')
df['uid_amt_std'] = g.transform('std').fillna(0)
df['uid_amt_z'] = ((df['TransactionAmt'] - df['uid_amt_mean']) /(df['uid_amt_std'] + 1e-6))
df[['uid_amt_mean', 'uid_amt_std', 'uid_amt_z']].describe()
2.2-2 UID 기준 상대 금액과 사기 거래 비율
다음으로 uid_amt_z 값이 실제로 사기 거래와 어떤 관계를 가지는지 확인하기 위해 해당 값을 구간화한 뒤
구간별 사기 거래 비율을 비교하였다.
df.groupby(pd.qcut(df['uid_amt_z'], 10, duplicates='drop'))['isFraud'].agg(['mean', 'count'])
| uid_amt_z | mean | count |
| (-4.127000000000001, -0.707] | 0.035527 | 59054 |
| (-0.707, -0.544] | 0.031446 | 59054 |
| (-0.544, -0.445] | 0.025366 | 59055 |
| (-0.445, -0.361] | 0.027507 | 59075 |
| (-0.361, -0.262] | 0.027289 | 59034 |
| (-0.262, -0.11] | 0.032361 | 59052 |
| (-0.11, 0.0089] | 0.036289 | 59054 |
| (0.0089, 0.354] | 0.03727 | 59055 |
| (0.354, 0.99] | 0.046837 | 59056 |
| (0.99, 26.235] | 0.050008 | 59051 |
분석 결과, uid_amt_z 값이 클수록 즉, 해당 uid의 평균적인 거래 금액 대비 상대적으로 큰 금액의 거래일수록
사기 거래 비율이 높아지는 경향을 확인할 수 있었다.
2.3 범주형 변수 처리 및 빈도 기반 특징 생성
앞선 분석에서는 uid를 기준으로 반복 거래 패턴과 거래 금액의 상대적인 변화가 사기 거래와 유의미한 연관성을 가짐을 확인하였다.
다음으로는 카드, 기기, 이메일 등과 같이 범주형으로 표현된 거래 환경 정보를 중심으로 사기 거래와의 관계를 확인한다.
2.3-1 범주형 변수 특성 확인
먼저 범주형(object) 타입 변수들을 확인하였다. 확인 결과, 총 32개의 범주형 변수가 존재하며
구성은 다음과 같이 크게 네 가지 유형으로 나눌 수 있다.
['ProductCD',
'card4', 'card6',
'P_emaildomain', 'R_emaildomain',
'M1' ~ 'M9',
'id_12', 'id_15', 'id_16', 'id_23', 'id_27', 'id_28', 'id_29', 'id_30',
'id_31', 'id_33', 'id_34', 'id_35', 'id_36', 'id_37', 'id_38',
'DeviceType', 'DeviceInfo',
'uid']
2.3-2 빈도 기반 인코딩
범주형 변수는 고유 값 종류가 많고 결측도 많기 때문에, 값 자체를 원-핫으로 펼치기보다는 등장 빈도를 수치형으로 변환해 사용한다. 각 범주형 값이 데이터에서 얼마나 자주 등장했는지를 *_cnt 컬럼으로 생성하였다.
(1) count 인코딩 생성
cat_cols = df.select_dtypes(include='object').columns.tolist()
for c in cat_cols:
vc = df[c].fillna('NA').value_counts(dropna=False)
df[c + '_cnt'] = df[c].fillna('NA').map(vc).astype('int32')
(2) 대표 변수에서 빈도 구간별 사기 비율 확인
chk = ['card4_cnt', 'P_emaildomain_cnt', 'DeviceInfo_cnt']
for c in chk:
if c in df.columns:
print('\n==', c, '==')
display(df.groupby(pd.qcut(df[c], 10, duplicates='drop'))['isFraud'].agg(['mean','count']))
| card4_cnt | mean | count |
| (1576.999, 189217.0] | 0.035427 | 205773 |
| (189217.0, 384767.0] | 0.034756 | 384767 |
| P_emaildomain_cnt | mean | count |
| (31.999, 28289.0] | 0.028126 | 84547 |
| (28289.0, 36998.0] | 0.023217 | 36998 |
| (36998.0, 94456.0] | 0.037121 | 139706 |
| (94456.0, 100934.0] | 0.022757 | 100934 |
| (100934.0, 228355.0] | 0.043542 | 228355 |
| DeviceInfo_cnt | mean | count |
| (0.999, 19782.0] | 0.077329 | 70944 |
| (19782.0, 47722.0] | 0.0654 | 47722 |
1️⃣ card4_cnt
- card4_cnt에서는 빈도 구간에 따른 사기 비율 차이가 거의 나타나지 않음
2️⃣ P_emaildomain_cnt
- 빈도에 따라 사기 비율이 달라짐
- 특히 가장 자주 등장하는 이메일 도메인 구간에서 사기율 상승
3️⃣ DeviceInfo_cnt
- 희귀한 디바이스 정보일수록 사기 비율이 매우 높음
이메일 도메인과 디바이스 정보는 값 자체보다 등장 빈도를 기준으로 볼 때 거래 환경의 희귀성과 반복성이 사기 거래와 뚜렷한 연관성을 보였다.
이후 모델링 단계에서는 이러한 빈도 기반 피처들을 주요 입력 변수로 활용한다.
'Project' 카테고리의 다른 글
| [Team Project] 네이버 플레이스 광고/체험단 리뷰 판별 모델 개발 - EDA (2) (0) | 2026.01.19 |
|---|---|
| [Kaggle] IEEE-CIS Fraud Detection - 모델링 (3) (0) | 2026.01.13 |
| [Team Project] 네이버 플레이스 광고/체험단 리뷰 판별 모델 개발 - 아이디어 선정 (1) (0) | 2026.01.08 |
| [Kaggle] IEEE-CIS Fraud Detection - 이상거래 탐지 문제 정의와 데이터 구조 (1) (0) | 2026.01.08 |
HELLO WORLD
안녕하세요. 데이터로 말하는 분석가 모모입니다.
데이터를 구조화하고 분석하는 과정과 실무에 활용되는 도구 중심의 내용을 기록합니다.