● 이상치 : 전체 데이터 범위에서 벗어난 아주 작은 값이나 큰 값
● 결측치: 데이터 수집 과정에서 측정되지 않거나 누락된 데이터
● 데이터EDA : 분석 전에 데이터 정제 과정, Data Cleaning
이상치처리, 결측치 처리, 데이터 시각화
1. 결측치 : 누락된 데이터
-결측치 제거
결측값 확인
.isnull(), .isna() : 결측치인 값 확인 True/False , 결측치이면 True
.notnull(), .notna() : 결측치가 아닌 값 확인 True/False, 결측치이면 False
결측치의 개수
.isnull().sum()
결측치 제거 .dropna()
df.dropna(axis=0, how='any', inplace=False)
- axis = 0 행index / 1 열columns
- how = any 하나라도 있으면 제거 / all 모든값이 결측치이면 제거
- inplace = True 원본변경 / False 원본 유지
- # 열 전체를 제거 : axis=1
df3 = df3.drop('Unnamed: 4', axis=1)
✅ .drop('열/행 이름',axis=1) : 특정 행,열을 결측치에 상관없이 이름/위치로 직접 제거
✅ .dropna(anxis=0, how='any') : 결측치(NaN)를 기준으로 행,열 제거
- # 결측치가 있는 행 모두 제거 : axis=0, how='any'
df3.dropna()
df3.dropna(axis=0, how='any') - # 결측치가 (하나라도) 있는 열 모두 제거 : axis=1
열로 제거 -> 컬럼이 제거되는 현상이 발생하므로 매우 위험
df3.dropna(axis=1) - # 전체 행이 결측값인 경우만 삭제 : how='all'.
df3.dropna(how='all') - # 결측치 제거 후 결과를 바로 저장하고 싶을 때 : inplace=True
df3.dropna(inplace=True)
df3 =df3.drop('Unnamed: 4', axis=1) -- df3. 처리~~한 거를 df3 으로 받아줌 (업데이트)
# drop 이후 결측치가 잘 제거되었는지 체크
df3.isnull().sum() = 0 이 나와야함
-결측치 대체 : 리스크가 큼
최빈값 -> 범주형 변수에 주로 사용
중앙값 ,평균값 -> 수치형 변수에 주로 사
행 기준 바로 위,아래 값
group by 연산 결과
결측값 대체(변경) df[컬럼].fillna(어떤값으로 대체할건지)
df[컬럼].fillna (value=None, method=None, axis=None, inplace=False, limit=None, downcast=None)
- axis = 0 index / 1 columns
- how = any 존재하면 제거 / all 모든 결측치 제거
- inplace = True 원본변경 / False 원본 유지
대체 후 검증 df.isnul.sum() = 0
대체 후 인덱스 재구성 .reset_index() *재구성시 결측치는 카운트 되지 않음*
결측치(NaN)을 대체한 후 df의 인덱스를 재구성
대체 값 종류
- 최빈값(가장많이나온값) .mode <범주형 데이터>
df[컬럼] = df[컬럼].fillna( df[컬럼].mode()[0] )
# mode 는 최빈값을 의미
# df3 의 Interaction type 컬럼을 fillna함수를 이용하여 채워주되, mode() 함수를 사용하여 최빈값으로 넣어줌
# mode 함수는 시리즈를 output으로 가집니다.
# 따라서,[0]을 통해 시리즈 중 단일값을 가져와야 합니다.
df3 = df3['Interaction type'].fillna(df3['Interaction type'].mode()[0])
[적용]
df3의 전체 행 = 3298
Interaction type 결측치 = 423개
Interaction type그룹을 기준으로 product id를 .count()한 뒤 reset_index() 로 재구성하면
각 type별 product id가 나옴 1145+855+871 = NaN을 뺸 Interactiontype의 index갯수
=> NaN을 뺸 Interaction type의 index갯수 + 결측치NaN = 전체행
1145+855+871 + NaN = 3294
NaN = 3294 - (1145+855+871) = 423 == 결측치 423 [검증 완]
0. Interaction type의 index 중 가장 많은 값 1145개 = Like
1. 컬럼의 최빈값 구하기 df[컬럼].mode()[0]
+
2. 구한 최빈값을 .fillna() 에 넣기
=> df3 = df3['Interaction type'].fillna(df3['Interaction type'].mode()[0])
= .mode()로 최빈값이 Like 임을 구한 뒤 그 Like로 NaN을 채워넣는다는 것을 의미
3. 검증
: 최빈값 like 로 NaN 값들이 다 채워졌을까?
df3.groupby('Interaction type').count() = 기존 NaN값 + 기존 최빈값이 가지고 잇던 값
- 평균값 .mean
- df['sw'] = df['sw'].fillna(df['sw'].mean()) df.isnull().sum() -- 검증
- 중간값 .median
- df['sw'] = df['sw'].fillna(df['sw'].median()) df.isnull().sum()
- inplace=True 로 하면 원본 데이터가 바뀌게 됩니다
- 바로 위 값으로 대체 method='ffill'
- df['sw'] = df['sw'].fillna(method='ffill') df.isnull().sum()
- 바로 아래 값으로 대체 method='bfill'
- df['sw'] = df['sw'].fillna(method='bfill') #df.isnull().sum()
- group by 값(그룹의 통계값)으로 대체 : .fillna ( df.groupby, .transform )
- df['sw'] = df['sw'].fillna(df.groupby(기준컬럼)[계산할컬럼].transform(계산방식)) df['sw'] = df['sw'].fillna(df.groupby('Is Amazon Seller')['sw'].transform('median')) df.isnull().sum()
[적용]
1. 사전 데이터 확인 = 그룹별 통계값 구하기 .groupby()
df.groupby('Is Amazon Seller')['sw'].median()
📌 데이터 그룹화 groupby()
df.groupby('기준그룹')['계산할컬럼'].집계함수()
#그룹이 여러개 [ , ]
df.groupby(['기준컬럼1','기준컬럼2'])['계산할컬럼'].집계함수()
#여러개의 집계연산 .agg()
df.groupby('카테고리').agg({'계산할컬럼1':'집계함수1'},{'계산할컬럼2':'집계함수2'})
2. group by한 데이터를 + 데이터프레임의 컬럼에 추가하기 : .transform 함수 사용
== 평균값 구한걸 .fillna() 결측치자리에 넣기
2. 이상치
-'이상하다'의 기준 ? 정답 x 통용되는 이상치 처리 방식은 존재 but 각각 방식마다 결과를 보고 분석가가 선택해야함.
-데이터 크기 가늠 할 수 없을 때는 ?
(컬럼별로 하나하나) Z-Scoore, IQR 둘 다 해보고 비교해보기~
방법론 1. Z-Score 🔥🔥
(데이터의 분포가 정규분포일때) 평균으로부터 얼마나 떨어져있는가 ? python의 scikit-learn 라이브러리가 지원
1.데이터 타입을 보고 필요한 양식에 맞춰 변경
2. 데이터마다 z-score (표준점수) 구하기.
- 표준점수(Z) = = X에서 평균을 뺀 데이터를 표준편차로 나눈 값
= 평균으로부터 얼마나 멀리 떨어져 있는지?
(일반적으로 -3 ~ 3 사이 값, ±3 이상이면 이상치로 간주)- Z-score = 0 : 평균에서 떨어진 거리가 0 = 평균과 같음
- Z-score > 0 : 평균으로부터 떨어진 거리가 n, 양수(+) 평균보다 높은 값
- Z-score < 0 : 평균으로부터 떨어진 거리가 n, 음수(-) 평균보다 낮은 값
- 표준화 도구 : Scikit-learn의 StandardScaler
- 데이터는 2차원 형태 [[ ]]
- .fit_transform() 메소드로 변환함
- 표준화된 데이터는 평균이 0, 표준편차가 1
scale_df = StandardScaler().fit_transform(데이터)
- 이상치 조건 설정 : Z-SCORE 기반, -3 보다 작거나 (OR) 3보다 큰 경우를 이상치로 판별
mask = ((merge_df['zscore']<-3) | (merge_df['zscore']>3))
strange_df = merge_df[mask]
[적용]
df 의 Shipping Weight(배송무게)의 이상치를 Z-Score를 이용해 찾기!
1. 데이터 불러오기
df = pd.read_csv("p.csv")
📌 데이터 확인하기
df.info() : 총 행(row) 수와 열(column), 열에 포함된 값의 개수(Non-Null Count), 데이터타입(dtype) 표시
df.dtypes : 각 열의 데이터타입 확인 (메소드형태 x)
특정열.dtype : 특정 열의 데이터 타입 확인
shipping weight = object이므로 숫자만을 뽑아서 타입을 변경해야함 : string -> float -> int
1-1. split으로 문자열 자르고
df['sw'] = df['Shipping Weight'].str.split().str[0]
✅ .split으로 문자열을 자르고 + 리스트에서 숫자만 꺼내기 [ ]
.split() : 공백을 기준으로 문자열을 나누는 함수 -> 자른 문자열을 리스트로 변환
df['Shipping Weight'].str.split() # "3.5kg" -> ["3.5", "kg"]
.str[0] : 나눈 문자열의 리스트str의 첫번째 요소(숫자 부분)[0]만 가져옴
1-2 .문자열 -> 숫자로 변환
df['sw'] = pd.to_numeric(df['sw'] , errors='coerce').fillna(0.0).astype(int)
- .fillna(0.0) -> 0.0 으로 채우는 이유 : sw가 소수점으로 되어있는 숫자(실수 float)이므로 0.0
-> NaN(비어있는 값)을 0.0으로 채운다
- .astype(int) 로 실수를 정수(int)로 변환
=> sw 컬럼에 Shipping Weight의 숫자값만 정수형으로 저장됨
📌데이터 타입 변경하기
.astype() : df의 컬럼타입 변경
df[컬럼이름].astype(변경할타입) - (int, float, str)
df.astype({컬럼명1:변경할타입1, 컬럼명2:변경할타입2}) : 여러 열의 타입 변경
pd.to_datetime() 함수 : 문자 -> 날짜 형식으로 변환
df[컬럼명] = to_datetime(df[컬럼명])
- errors='coerce' : 변환할 수 없는 값 NaN(결측값) 처리
pd.to_numeric() : 문자열 -> 숫자로 변환
df['A'] = pd.to_numeric(df['A'], errors='coerce')
.map() : 데이터 값을 매핑하여 타입 변경
df['backend'] = df['backend'].map({'true': True, 'false': False})
.apply() : 사용자 정의 함수를 적용하여 데이터 타입을 변경
df['A'] = df['A'].apply(int)
2. 표준화 진행 = Z-Score 구하기
2-1. z-score 를 적용할 컬럼(객체) 지정 [[ ]]
df1 = df[['sw']]
df[['sw']] 에 df[ [ ] ] 를 쓰는 이유 : Scikit-learn의 StandardScaler는 2차원 데이터만 취급
-> 데이터를 표 형태로 만들어줌
df['sw'] → 1차원 데이터 (리스트) # [1.2, 2.3, 3.4]
df[['sw']] → 2차원 데이터 (행과 열이 있는 표 형)
#
sw
0 1.2
1 2.3
2 3.4
2-2. 데이터 표준화 진행 StandardScaler
평균과 표준편차를 계산한 후 데이터를 표준화(Z-Score 변환)해주는 함수
데이터 평균을 0, 표준편차를 1로 만들어줌
scale_df = StandardScaler().fit_transform(데이터)
함수 적용 후 변수로 이름지어줌.
scale_df = StandardScaler().fit_transform(df1)
2-3.데이터프레임에 추가 : 데이터 합치는 작업 .concat()
: .concat() 함수로 원본 데이터(df) + 표준화된 데이터(scal_df)를 옆으로 합침
merge_df = pd.concat([df1, pd.DataFrame(StandardScaler().fit_transform(df1))],axis=1)
merge_df.columns = ['Shipping Weight', 'zscore']
✅ pd.concat( [데이터1, 데이터2], axis=방향) : 데이터 합치기
-axis = 0 세로방향(행 기준) / 1 가로방향(열 기준)
✅ 컬럼 이름 바꾸기 .columns
데이터프레임.columns = [새로운 컬럼이름1, 새로운 컬럼이름2 ... ]
3. 이상치 감지
3-1.이상치 조건 설정 : Z-SCORE 기반, -3 보다 작거나 (OR) 3보다 큰 경우를 이상치로 판별
mask = ((merge_df['zscore']<-3) | (merge_df['zscore']>3))
or
📌 조건 걸기
df [ 조건 ] : 특정 조건이 True인 행만 필터링, 기본조건
df[(조건1) & (조건2)] :여러 조건 조합
& : AND / | : OR / ~ : NOT
df.loc[조건, 컬럼이름] = 값 # 조건에 맞는 컬럼을 값으로 변경
.where(조건) : 조건에 만족하는 행은 정상출력, 나머지 NaN
반복문을 사용하여 각 행 별로 조건을 처리
for i in range(len(df)):
if df.loc[i, '열 이름'] 조건:
df.loc[i, '열 이름'] = 값
.apply() : 각 행, 열에 대해 사용자 정의 함수를 적용
df['새로운 열'] = df.apply(함수, axis=1) # 행 단위로 함수 적용
.filter() : 특정 열, 행 이름을 기준으로 데이터 필터링
df.filter(items=[열 이름1, 열 이름2], axis=1) # 열 기준 필터링
df.filter(like='패턴', axis=0) # 행 기준 필터링
3-2. 이상치만 필터링해서 따로 저장
: mask 라는 변수를 만들어서 조건을 저장 -> 이 변수 mask를 사용하여 데이터 필터링
왜? 일반 조건을 거는 df[조건] 은 True인 행만 필터링 하니까.
mask = ((merge_df['zscore']<-3) | (merge_df['zscore']>3)) -> 이상치가 되는 조건을 mask라는 변수에 저장
strange_df = merge_df[mask] -> 이 조건을 만족한 것을 또 새로운 변수strange_df에 저장
📌 변수에 원하는 조건 저장 후-> 필터링하기
mask = ( (조건1) & (조건2) )
df[mask] -- mask라는 조건을 만족하는(True)인 애들만 필터링함.
4. 이상치 개수 확인 : 총 55 건 탐지
strange_df.count() -> strange_df의 갯수 세기 .count
plus. 이상치를 뺸 정상치를 구하려면?
# Z-score 기준 정상치 찾기 (-3 이상, 3 이하)
normal_df = merge_df[(merge_df['Z-score'] >= -3) & (merge_df['Z-score'] <= 3)]
# 정상치 개수 확인
print(normal_df.count())
<Z-Score를 이용하여 이상치 탐지하기>
import pandas as pd
from sklearn.preprocessing import StandardScaler
# Step 1: 데이터 불러오기 및 준비
df = pd.read_csv("p.csv") # 데이터 불러오기
df['sw'] = df['Shipping Weight'].str.split().str[0] # 문자열에서 숫자 추출
df['sw'] = pd.to_numeric(df['sw'], errors='coerce').fillna(0.0).astype(int) # 숫자로 변환
# Step 2: Z-Score 계산
# z-score 를 적용할 컬럼 선정
df1 = trans_df[['price']]
#표준화하기
from sklearn.preprocessing import StandardScaler
scale_df = StandardScaler().fit_transform(df1)
scale_df = pd.DataFrame(scale_df)
merge_df = pd.concat([df1, scale_df],axis=1)
merge_df.columns = ['Shipping Weight', 'z-score'] # Z-Score 계산
# Step 3: 이상치 조건 설정 및 필터링
mask = ((merge_df['z-score']<-3) | (merge_df['z-score']>3)) # Z-Score가 -3보다 작거나 3보다 큰 경우
strange_df = merge_df[mask] # 이상치만 필터링
# Step 4: 이상치 개수 확인
strange_df.count()
#step5 : 정상치 찾기
normal_df = merge_df.drop(strange_df.index) #전체df에서 이상치 삭제
normal_df = normal_df.drop('Z-score', axis=1) #z-scroe컬럼 제거
normal_df.reset_index(drop=True) #인덱스 재정렬
# step 6 # 이상치 삭제 #trans_df에서도 적용시키기
trans_df1 = trans_df.drop(strange_df.index)
trans_df1.reset_index(drop=True)
#step7 : 통계
# 소수점 2자리까지만 보여주는 옵션
pd.options.display.float_format = '{:.2f}'.format #지수 표현식 x
#price 컬럼의 통계
trans_df['price'].describe()
방법론 2. IQR (Interquartile Range)🔥
: 데이터 분포가 정규분포를 이루지 않을 때 (모집단이 작은경우) (근데 둘 다 가능)
데이터의 상위 25% ~ 75% 의 범위(IQR) / 벗어나면 이상치
1.
I
- Q3,Q1, IQR 구하는 방법
df.quantile() : 백분위수 구해주는 함수
q3 = df1['sw'].quantile(0.75) # 상위 25% (Q3)
q1 = df1['sw'].quantile(0.25) # 하위 25% (Q1)
iqr = q3 - q1 # IQR 계산
- 이상치 경계 설정
Q3 + 1.5 * IQR < 이상치 < Q1 - 1.5 * IQR - 경계 계산
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
- 이상치 strange_df 탐지하기 <- outlier 지정
iqr_outliers = trans_df[(trans_df['price'] < lower_bound) | (trans_df['price'] > upper_bound)]
iqr_outliers['price'].count()
[적용]
df 의 Shipping Weight(배송무게)의 이상치를 IQR을 이용해 찾기!
1. 데이터 준비
df = pd.read_csv("p.csv")
shipping weight = object이므로 숫자만을 뽑아서 타입을 변경해야함 : string -> float -> int
1-1. 문자열 자르기 .split
df['sw'] = df['Shipping Weight'].str.split().str[0]
1-2 .문자열 -> 숫자 변환
df['sw'] = pd.to_numeric(df['sw'] , errors='coerce').fillna(0.0).astype(int)
- .fillna(0.0) -> 0.0 으로 채우는 이유 : sw가 소수점으로 되어있는 숫자(실수 float)이므로 0.0
-> NaN(비어있는 값)을 0.0으로 채운다
- .astype(int) 로 실수를 정수(int)로 변환
=> sw 컬럼에 Shipping Weight의 숫자값만 정수형으로 저장됨
2. IQR 계산
2-1. 이상치를 감지할 컬럼 선정
df1 = df[['sw']] # 2차원 데이터 값
2-2. Q3, Q1, IQR 값 구하기
: 백분위수를 구해주는 quantile 함수를 적용하여 쉽게 구할 수 있음
데이터프레임 전체 혹은 특정 열에 대하여 모두 적용이 가능
✅ df.quantile() : 백분위수 구해주는 함수
df[컬럼명].quantile(백분위수)
q3 = df1['sw'].quantile(0.75) # 상위 25% (Q3)
q1 = df1['sw'].quantile(0.25) # 하위 25% (Q1)
iqr = q3 - q1 # IQR 계산
2-3. 이상치 경계 설정 : 아래쪽 경계, 위쪽 경계 계산
Q3 + 1.5 * IQR < 이상치 < Q1 - 1.5 * IQR
lower_bound = q1 - 1.5 * iqr # 아래쪽 경계
upper_bound = q3 + 1.5 * iqr # 위쪽 경계
3. 이상치 판별 및 dataframe 저장
3-1. 이상치 판별 함수 is_outlier() 정의
[복붙하시오]
def is_outlier(df1):
score = df1['sw']
if score > 7 + (1.5 * 6) or score < 1 - (1.5 * 6):
return '이상치'
else:
return '이상치아님'
3-2. 각 값의 이상치 여부 판별 후 apply 함수를 통해 저장
= 새롭게 컬럼을 만들어서 이상치/이상치아님 나타내기
df1['이상치여부'] = df1.apply(is_outlier, axis = 1)
*axis = 1 : 이상치 함수를 모든 열에 적용하겠다 는 뜻
혹은
# step4 이상치 경계 계산하기
lower_bound = q1 - 1.5 * iqr
upper_bound = q3 + 1.5 * iqr
후 바로 이상치outliers 판별하기
# Step 5: 이상치outliers 찾기
# outliers = 상한선upper_bound과 하한선lower_bound 바깥에 있는 값
outliers = df[(df['sw'] < lower_bound) | (df['sw'] > upper_bound)]
# plus: 이상치를 제거하기
# cleand_df = df[(df['sw'] >= lower_bound) | (df['sw'] <= upper_bound)]
4. 이상치 개수 확인
df1.groupby('이상치여부').count()
#IQR 방식으로 구한 이상치 개수는 349 개
<IQR 방식으로 이상치 탐지하기>
import pandas as pd
# Step 1: 데이터 불러오기 및 준비
df = pd.read_csv("p.csv") # 데이터 불러오기
df['sw'] = df['Shipping Weight'].str.split().str[0] # 문자열에서 숫자 추출
df['sw'] = pd.to_numeric(df['sw'], errors='coerce').fillna(0.0).astype(int) # 숫자로 변환
# Step 2: IQR 계산
q3 = df['sw'].quantile(0.75) # 상위 25% (Q3)
q1 = df['sw'].quantile(0.25) # 하위 25% (Q1)
iqr = q3 - q1 # IQR 계산
# Step 3: 이상치 경계 설정
lower_bound = q1 - (1.5 * iqr) # 아래쪽 경계
upper_bound = q3 + (1.5 * iqr) # 위쪽 경계
# Step 4: 이상치 판별 함수 정의 및 적용
def is_outlier(row):
score = row['sw']
if score > upper_bound or score < lower_bound:
return '이상치'
else:
return '정상'
df['이상치여부'] = df.apply(is_outlier, axis=1)
# Step 5: 결과 분석
df1[df1['이상치여부'] == '이상치'].count() ##349
혹은
# Step 4: 이상치 경계 설정
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# Step 5: 이상치outliers 찾기
# outliers = 상한선upper_bound과 하한선lower_bound 바깥에 있는 값
outliers = df[(df['sw'] < lower_bound) | (df['sw'] > upper_bound)]
outlier['sw'].count() ##np.int64(349)
# plus: 이상치를 제거하기
# cleand_df = df[(df['sw'] >= lower_bound) | (df['sw'] <= upper_bound)]
print("이상치 경계값: ({}, {})".format(lower_bound, upper_bound))
print("이상치:", outliers.values)
방법론 3. Isolation Forest🔥🔥
머신러닝 기법, 컬럼갯수가 많을 때 이상치 판별에 용이
결정트리 형태 : 이상치의 경우, 질문 했을 때 어디에도 속하지 않을 확률이 높음.
데이터의 경로길이가 짧은 편. <1 에 가까워질수록 이상치로 간주.
방법론 4. DBScan
밀도기반 클러스터링(집단) 알고리즘,
핵심 포인트들이 서로 연결되어 군집 = 이상치
연결되지 않고, 어떠한 집단에도 포함되지 않은 데이터 = 이상치로 판별
크고 복잡한 구조의 데이터에서 이상치 식별에 용이
ex-지리 데이터, 이미지 데이터
'데이터 분석 > Python' 카테고리의 다른 글
| df 형태 변환 5가지 : Transpose, pivot_table, melt, stack, unstack (0) | 2025.04.03 |
|---|---|
| Pandas 메소드와 함수 (0) | 2025.03.19 |
| python 개인과제 해설 : 최댓값 찾기, try-except, continue-break, 문자열 누적갯수 {} (0) | 2025.03.18 |
| pandas : Merge, join, concat, append , lamda, split, rrule (0) | 2025.03.18 |
| [start_coding] PYTHON 문법 : 리스트, 튜플, 딕셔너리, 함수 (0) | 2025.03.14 |