Growth Marketing/GM4

[TIL-260413] 멋쟁이사자처럼 그로스마케팅 4기 - 데이터분석 개론 day23 | 데이터 이상치 및 결측치 처리

pamsyra 2026. 4. 13. 17:12
▼오늘 배운 사항들
1. 라이브러리
2. Pandas로 이상치/결측치 처리
- 데이터 탐색 : df.info(), df.describe(), df.head()
- 데이터 선택 : loc[], iloc[]
- 결측치 파악 : df.isnull().sum() / .dropna(subset=), [''].fillna()
- 이상치 파악 : .quantile(), .copy()
- 데이터 처리(파생변수)  :  pd.cut()

Part 1. Pandas 로 이상치/결측치 처리를 위한 데이터 탐색 및 선택

[1. 데이터 탐색하기]

  • 데이터를 받으면 무조건 이 세 줄 먼저 실행하기
  • 데이터 분석 시, 데이터를 믿고 바로 계산하는 것이 가장 큰 실수 ▶ 함정 잡고 시작해야함
    - ex. Age 컬럼에 999가 들어있다 → 평균 나이가 말도 안 되게 높게 나옴
    - ex. Income 컬럼에 24개가 비어있다 → 평균 소득 계산이 틀림
    - ex. date 컬럼이 숫자로 읽힌다 → 월별 분석 자체가 불가능
df.info() 컬럼 이름, 타입, 결측치 수를 한번에 컬럼 타입 + 결측치 수 확인 가능
df.describe() 숫자 컬럼의 평균·최솟값·최댓값 등 기초 통계 숫자 통계 확인 가능
df.head(n) 처음 n행 미리보기 (기본값 5)  

 

 

[2. 데이터 꺼내기]

  • 판다스에서 데이터를 꺼내는 건 2가지 방법 사용 : loc, iloc
  • loc[ '행' , '열']  → 조건 넣어서 꺼낼수도 있음
# 특정 컬럼 선택
df.loc[:, 'age']                      # 모든 행의 age 컬럼
df.loc[:, ['name', 'age']]            # 여러 컬럼

# 조건으로 행 필터링 ← 실무에서 가장 자주 씀
df.loc[df['service'] == '멜론']       # 멜론 사용자만
df.loc[df['age'] >= 25]                # 25살 이상
  • 행 조건에 슬라이싱 활용해서 특정 값까지만 꺼낼 수 있음

범위 설정은 지난시간 배운 슬라이싱 활용

  • df['']를 사용해서 조건에 맞는 값만 추출 가능

ex. 멜론 만 보고 싶어 / 25살 이하만 보고 싶어

  • 행조건 + 특정 열 선택
    - 행 조건 : 특정 나이대
    - 열 선택 : Likes 부터 satisfaction까지
  • 조건 2개 구하기
    - 행 조건 : 나이가 25세 이상이면서, 동시에 서비스가 멜론인 사람
    - 열 선택 : 생략 (= 데이터프레임 전체 열 추출)
    - 컬럼으로 받아올 것이니 df[] 쓰되 조건이니까 () 괄호 넣어주기 

 

 

  • 만족도가 높은 고객을 타겟으로 특정 마케팅 플랜을 수립할 수 있음
    - satisfaction이 8 이상인 고객은 vip로 바꾸려고 할 때
  • grade plan 값을 초기화 하고 싶을 땐, none 활용 → 삭제 아님, 초기화!
  • 'inplace = Ture' : 원본에서 컬럼 삭제하고 싶을 때 사용
df.loc[df['satisfation'] >=8] #SEPT1. 만족도가 8이상인 고객 확인
df.loc[df['satisfation'] >=8, grade] #STEP2. 그 것의 grade 컬럼만 확인을 하고
df.loc[df['satisfaction'] >= 8, 'grade'] = 'vip' #STEP3. 이것을 vip로 지정하겠다

df['grade'] = None #지정한 값들 초기화
df.drop(columns=['grade']) #원본은 삭제 안됨
df.drop(columns=['grade'], inplace=True)  #원본 df에서 컬럼 삭제 원할시 사용

차례대로 1-2 / 3

 

[응용실습] satisfaction이 5점 이하인 분들은 grade를 '이탈위험'으로 바꾼다.

원본 df에서 컬럼 삭제 : inplace=true 사용

 

  • 삭제한 컬럼 재 생성 :  df[''] = ??? 로 재설정해주면 생성됨
#and조건 이용한 일반조건
df.loc[(df['satisfaction'] >= 5) & (df['satisfaction'] < 8) ,'grade'] = '일반'

 

[iloc]

  • iloc[0] / iloc[0:5] / iloc[-1] 등등 확인 가능하여 맨 마지막 행을 보고 싶을 때 주로 사용
  • 마케터는 loc 많이 씀
  • 한 행이어도 데이터프레임 표 형식으로 보고 싶으면 대괄호 한 번 더 씌워주기

 

  • 연습문제 뒤에서부터 3번째까지 행 추출 : [-3:] → 음수는 :위치를 뒤로!

 


Part 2. 결측치 처리

[결측치]

  • 결측치 : 비어있는 값
  • 결측치를 그냥두면 계산이 틀려지니 처리해야 함
  • 컬럼별 결측치 개수, 비율, 결측치에 특정 값 채우기, 결측치 컬럼 삭제, 평균값 계산 등 
df.isnull().sum() 컬럼별 결측치 개수
df.isnull().mean() * 100 결측치 비율(%)
df['컬럼명'].fillna(값) 결측치를 특정 값으로 채우기
df.dropna(subset=['컬럼명']) 특정 컬럼이 비어있는 행 제거
df['컬럼명'].mean() 평균값 계산
df['컬럼명'].median() 중앙값 계산

 

 

 

  • dropna(subset=[]) : 결측치 값이 있는 행 삭제 
# 방법 1: 행 제거 — 결측 비율이 낮을 때
df.dropna(subset=['likes']) # likes가 None인 Carol, Grace 행이 제거됨

#원본데이터에도 적용 방법
df.dropna(subset=['likes'], inplace=True) #1. inplace = True 사용
df = df.dropna(subset=['likes']) #2. df 값에 아예 넣어버리기

 

  • .fillna('') : none 값에 문자열 채우기

 

  • .fillna( df['income'].median()) : 결측치를 중앙값으로 채울 때
    - 마케팅은 한쪽으로 치우친 값이 많기 때문에 평균보다 중앙값이 안전하니까!
    - 중앙값보다 그룹별 중앙값을 넣는 걸 추천

fillna 괄호 안에 중앙값 넣어주면 됨 -> hank의 income이 변했습니다.

 

[선택 예시]

  • 결측치 5% 미만이면, 그냥 행제거
  • 숫자값 컬럼 비워져있으면,  fillna 중앙값 채우기
  • 문자열데이터가 비워져있는데 필요한 데이터면, fillna에 미응답 
  • 그룹자체 특성이 많은데 소득을 구해야하는 상황이면, 그룹별 중앙값 채우기

 

[이상치]

  • 이상치 : 다른값들과 동떨어진 비정상적인 값
  • 이상치 값의 기준 IQR 방식 ↓
#중간 50%데이터가 퍼진 범위를 IQR - 앞에서 25프로 뒤에서 25프로
# 기초 통계로 먼저 확인
df['age'].describe()    # max가 말도 안 되게 크면 이상치!

# IQR 방식으로 이상치 기준 계산
Q1  = df['age'].quantile(0.25)
Q3  = df['age'].quantile(0.75)
IQR = Q3 - Q1

upper = Q3 + 1.5 * IQR   # 이 값보다 크면 이상치 상한선 식 by. 통계학자

# 이상치 확인 후 제거
df_clean = df[df['age'] <= upper].copy()
  • ex. 나이가 51.5가 넘으면 이상한 값이다라고 판단
  • .copy() 사용해서 이상치 제거한 값만 활용 가능

IQR을 구하고 이상치 기준을 구할 수 있음

 

[파생변수]

  • 파생변수는 원재료를 마케터가 실제로 쓸 수 있는 지표로 바꾸는 작업
    - 출생연도 → 나이 → 연령대 → "30대 타겟" 캠페인 필터
    - 카테고리별 구매금액 → 총 구매금액 → 고가치 고객 식별
    - 만족도 점수 → VIP / 일반 → VIP에게 리뷰 요청, 일반에게 재구매 쿠폰
    - 구매수 / 조회수 → 전환율 → 전환율 낮은 고객에게 리타겟팅 광고

[pd.cut]

# pd.cut : 숫자를 구간으로 나눠주는 함수
# 필요한 것 : 어떤 컬럼 사용할지 / 어떤 구간으로 나눌지 / 나눈 구간을 지칭할 이름

#새로운 age_grop으로 지정해주기
df['age_group'] = pd.cut(              
                          df['age'], # 어떤 컬럼 사용할지 
                          bins = [0,29,39,49,100], # 어떤 구간으로 나눌지
                          labels = ['20대', '30대', '40대', '50대+']
                      )

 

 

[group by]

  • 마케터의 대부분은 전체평균이 아닌 세그먼트별 특징값을 보는 것
  • 세그먼트 별 특징에 따라 마케팅 플랜을 따로 수립
  • 따라서 그룹바이가 중요함

[agg]

  • 데이터별 집계 함수를 한꺼번에 확인하기
    - 좋아요 수 합계, 만족도 중앙값, 수입 중앙값 등 보고 싶은 데이터를 agg를 사용해 확인 가능
    - name count는 서비스별로 몇 명 쓰는지 보려고
#sql의 grupby와 같음
#문법 체크 : 괄호-대괄호 사용
#숫자 처리 : round()
#내림차순 : .sort_valuses(ascending=)
df.groupby('service')['satisfaction'].mean().round(2).sort_values(ascending=False)

#보고싶은 집계 함수를 한 번에?!
#agg써서  키값 한번에 확인하기
df.groupby('service').agg({
    'satisfaction' : 'mean',
    'income' : 'mean',
    'name' : 'count' })

agg : sum('likes')같은것도 한번에 볼 수있음

[실습 - 이상치/결측치 데이터 처리 단계]

  1. 비어있는 값 채우기  : 결측치 확인 후 5개 fillna로 결측값 바꾸기
  2. 이상한 값 바꾸기/삭제하기 : describe로 이상치 확인 후 → IQR 구해서 이상치 조건 만든 copy 데이터 생성
  3. 파생변수 생성해서 데이터 정리하기
    - pd.cut : 구간대 분류 후 분류값 부여
    - loc : 특정 조건에 지정값 입력
  4. .isnull().sum()으로 이상치/결측치 처리 최종 결과 확인
# fillna()로 5개 채워넣기

users['service'] = users['service'].fillna('미응답')
users['age'] = users['age'].fillna(users['age'].median())
users['listen_hours'] = users['listen_hours'].fillna(users['age'].median())
users['satisfaction'] = users['satisfaction'].fillna(users['satisfaction'].median())
users['monthly_fee'] = users['monthly_fee'].fillna(users['monthly_fee'].median())
users

 

좌측 fillna 전 / 우측 fillna 후 - 결측값 사라져있는 걸 확인할 수 있음

#IQR 식으로 이상치의 기준 구하기
#IQR = Q3 - Q1
#이상치의 기준 : Q3 + IQR*1.5

#IQR 계산
Q1 = users['age'].quantile(0.25)
Q3 = users['age'].quantile(0.75)
IQR = Q3 - Q1

print(IQR)

#이상치 기준값 추출
upper = Q3 + IQR*1.5
print(upper)

#이상치 기준 적용한 users copy 생성
users_clean = users[users['age'] <= upper].copy()

#user_clean 데이터 확인 - max age
users_clean['age'].describe()

나이가 999살인 이상치 확인 ->

 

#파생변수는 이상치 제거 다 한 이후에 생성
#연령별 구간 나누기 - pd.cut

users_clean['age_group'] = pd.cut(
                        users_clean['age'],
                        bins = [0, 29, 39, 49, 100],
                        labels = ['20대', '30대', '40대', '50대+'] 
                        )
users_clean
#loc[행조건, 열] = 'VIP'
users_clean['grade'] = '일반'
users_clean.loc[users_clean['satisfaction'] >= 8, 'grade'] = 'VIP'
users_clean.loc[users_clean['satisfaction'] <= 3, 'grade'] = '이탈 위험'

#pd.cut으로 쓰고싶으면,
users_clean['grade'] = pd.cut( 
                    users_clean['satisfaction'],
                    bins = [0,4,8,10],
                    labels = ['이탈위험', '일반', 'VIP'])
                    
#monthly_fee 기준으로도
users_clean.loc[users_clean['monthly_fee'] >= 10000, 'grade'] = 'VIP'
users_clean.loc[users_clean['monthly_fee'] < 10000, 'grade'] = '일반'
users_clean

#방법 바꾸고싶으면 초기화 시켜놓고 (전부 일반으로 만들어두기)
users_clean['grade'] = '일반'

 

[연습문제 - 분석]

 

 

 

더보기

오늘 날씨가 쾌청하다 모니터가 눈이 부실 정도로..나와 정반대로..

그래도 낙오란 없다...끝까지..잡초같이... 이해를 못해도 간다...ㅂㄷㅂㄷ