1. 결측치 처리(전처리 작업): 누락 데이터와 중복 데이터의 처리
정확한 데이터 분석을 위해서는 정확한 데이터가 준비되어야 할 필요가 있다. 이를 위해 누락된 데이터나 중복 데이터를 제거해주는 전처리 작업이 요구된다.
결측치를 NaN 값이라고 보통 부르는데, 이러한 결측치는 정확한 분석을 하는 데 방해를 줄 수 있다. 아래와 같이 값이 끊기거나, NaN 값을 만나면 오류가 발생하는 함수도 있기 때문에 이는 반드시 어떤 방식으로든 처리해주는 것이 필요하다.
결측치를 처리하는 방법은 두 가지로, 제거하거나 다른 값(EX. 평균값, 최빈값 등)으로 채우는 방법이 있다.
1) 결측치 찾기: info(), isnull(), notnull(), sum()
결측치 처리에 앞서 해야 할 일은, 결측치의 존재 여부를 확인하는 것이다. 결측치가 어느 정도 있는지 파악하고, 이를 어떻게 처리할지 그 뒤에 방법을 결정해야 한다. 결측치를 확인하는 방법은 여러 가지가 있다.
(1) info() 메서드로 확인
몇 개의 값이 non-null인지 확인해본다. non-null 앞에 쓰인 수만큼 결측치가 아닌 값이라고 파악하면 된다.
# 값 개수로 결측치 존재 여부 확인
air.info()
(2) isnull(), notnull() / isna(), notna() 메서드 사용
isnull() 메서드는 결측치인 경우 True, 유효값이면 False를 반환하며, notnull()은 반대로 결측치에 대해 False를 반환한다. isnull() 대신 isna(), notnull() 대신 notna() 메서드를 사용하므로 자유롭게 선택해 사용하자.
# 전체 데이터 중에서 결측치는 True로 표시
air.isna()
(3) sum() 메서드를 결합하여 특정 df나 열의 결측치 개수 확인
sum() 메서드를 사용하면 True 값의 개수를 확인 가능하며, 이를 통 특정 df나 열에 대해 결측치 개수를 정확하게 확인해볼 수 있다. (True만 다 더해준다고 하면, 결측치 개수를 확인 가능하다)
# 열의 결측치 개수 확인
air.isna().sum()
# 비율로 표시
air.isna().sum() / len(air) * 100
# 참고: 그래프로 표시
air.isna().sum().plot(kind='bar')
2) 결측치 제거: dropna()
dropna() 메서드를 활용해서 결측치가 있는 열이나 행을 제거 가능하다. 이때, 주의할 점은 inplace=True를 괄호 안에 적어주어야 해당 데이터프레임에 실제 반영된다는 것이다. axis 매개변수를 통해서는 행을 제거할지 열을 제거할지 지정해줄 수 있다.
axis 매개변수 지정할 때 결정해야 할 것
- axis=0: 행 제거(기본값)
- axis=1: 열 제거
# 결측치가 하나라도 있는 행 제거
air_test.dropna(axis=0, inplace=True)
# air_test.dropna(inplace=True) # 여러번 돌려도 ok # axis=0이 디폴트여서 생략 가능
# Ozone 열이 결측치인 행 제거
air_test.dropna(subset=['Ozone'], axis=0, inplace=True)
# 결측치가 있는 모든 열 제거
air_test.dropna(axis=1, inplace=True)
3) 결측치 채우기: fillna()
제거하는 방법과는 다르게, 결측치를 다른 값으로 채우는 방법도 있다. 대신, 채울 수 있는 근거는 얘기해줄 수 있어야 한다. 이를테면 평균값이나 중앙값 등으로 대체해볼 수 있을 것이다.
(1) 평균값으로 채우기
결측치가 있는 열의 평균값을 구한 후에 결측치를 그 값으로 채운다.
# 데이터프레임 복사
air_test = air.copy()
# Ozone 평균 구하기
mean_Ozone = air_test['Ozone'].mean()
# 결측치를 평균값으로 채우기
air_test['Ozone'] = air_test['Ozone'].fillna(mean_Ozone)
(2) 특정 값으로 채우기
모든 결측치, 또는 일부 결측치를 특정 값으로 채운다.
# Solar.R 열의 누락된 값을 0으로 채우기
air_test['Solar.R'] = air_test['Solar.R'].fillna(0)
(3) 직전 행의 값 또는 다음 행의 값으로 채우기
이 방법은 날짜 또는 시간의 흐름에 따른 값을 갖는 시계열 데이터 처리시 유용하다.
- method='ffill': 바로 앞의 값으로 변경(Forward Fill)
- method='bfill': 바로 다음 값으로 변경(Backward Fill)
# Ozone 열의 누락된 값을 바로 앞의 값으로 채우기
air_test['Ozone'] = air_test['Ozone'].ffill()
# Solar.R 열의 누락된 값을 바로 뒤의 값으로 채우기
air_test['Solar.R'] = air_test['Solar.R'].bfill()
(4) 선형보간법으로 채우기: interpolate()
이 방법은 시계열 데이터에 대해서만 가능한 방법이다. (누가 20대이고, 옆은 40대인데, 중간 빈 값을 30대라 해버리면 안 되는 것처럼.) 주로 연속된 데이터의 패턴을 고려해 자연스럽게 값을 채워넣는 방법이다. 마치 두 점 사이를 부드럽게 잇는 선을 그리듯 결측치를 보완하는 방식이라고 생각하면 된다.
# 선형보간법으로 채우기
air_test.interpolate(method='linear', inplace=True)
# 특정 열만 선형보간법으로 채우기
air_test['Solar.R'] = air_test['Solar.R'].interpolate(method='linear')
2. 가변수(Dummy Variable) 생성: get_dummies()
가변수는 일정하게 정해진 범위의 값을 갖는 데이터(범주형 데이터)를 독립된 열로 변환한 것이다. 쉽게 생각하면, 범주형 문자열 데이터를 머신러닝 알고리즘에 그대로 사용할 수가 없다. 얘를 숫자로 변환해줘야 알고리즘에 사용이 가능하다. 이를테면 adult라는 열에 대해서 값이 yes, no 두 가지가 있다고 하자. 이대로 알고리즘에 던지면 에러가 나기 때문에 1, 0으로 바꿔줘야 한다. 이때, get_dummies() 함수를 사용하게 된다.
왜 필요할까? 숫자형으로 되어있는 범주값이 숫자로서의 의미를 상실하도록 하기 위해!
홍길동과 일지매가 만났다. 학기 초에 반배정이 되었는데, 길동이가 '나는 3학년 5반이야'라고 했더니 지매가 자기가 졌다면서 펑펑 울었다. 왜? '나는 3학년 1반이야. 내가 너보다 4가 더 작아' 너무 이상한 이야기이다. 이처럼 범주형 데이터는 숫자의 의미라고 보기 어렵다. 이것들을 숫자로서의 의미를 상실한 채로 알고리즘에 적용할 수 있도록 해야 한다.
1) 범주형 변수 확인
우선, get_dummies() 함수를 적용하려면 먼저 범주형인지부터 여부를 확인해줘야 한다. 대개 문자열 값을 갖는 열이 범주형 값을 갖는 경우가 많다.
# 열 확인
tip.info()
info()로 확인해서, object라고 되어있는 것은 범주값이라고 생각해야 한다. 이들은 후에 머신러닝 알고리즘에 넣기 전에 무조건 가변수화해줘야 한다. (문자열을 머신러닝에게 던지면 오류가 나기 때문!)
2) 변수 개별 처리
columns 옵션에 열을 하나 지정하면 자동으로 원본 열이 제거되고, 원본 열 이름은 prefix로 사용되어 새로 생긴 열들 이름 앞에 '(원본 열 이름)_' 이런 식으로 붙는다.
한편, 다중공선성 문제를 없애기 위해 drop_first=True 옵션을 지정할 수 있다.
왜 하느냐? 같은 말을 하고 있는 게 있으면, 머신은 헷갈려할 수 있다. 이처럼 다른 열이 특정 열을 설명하는 것 때문에 발생하는 문제를 다중공선성 문제라고 한다. 머신러닝 성능을 좋게 하려면, 이 열들 중에 같은 말을 하고 있는 애를 제거해주면 좋다. 그래야 예측 성능이 높아지게 된다. 이것이 바로 다중공선성 문제를 해결하는 방법이다. 안해도 되나? 날렸더니 성능이 더 떨어졌나? 그럼 안하는 것이다. 그러나 안해보고는 알 수 없다. 일단 해보자.
다중공선성 문제란?
다중공선성은 독립 변수(피처)들 간에 강한 상관관계가 있을 때 발생한다. 이는 회귀분석의 전제 가정을 위배하는 것이므로 적절한 회귀분석을 위해 해결해야 하는 문제이다. 쉽게 생각하면, 회귀 분석 같은 모델에서는 변수 간의 상관관계가 높으면, 모델이 특정 변수를 제대로 구별하지 못하고 해석이 불안정해질 수 있다. 중복된 정보가 많을 때 모델이 혼란스러워지는 상황이라고 생각해보자.
# 가변수화: sex
dumm_cols = ['sex']
tip = pd.get_dummies(tip, columns=dumm_cols, drop_first=True, dtype=int) # dtype=bool이 디폴트이다.
왜 int 타입으로 바꿔주는가?
머신러닝 모델의 경우 숫자(int 또는 float) 데이터를 사용해 학습하게 된다. bool 타입은 각 값이 0 또는 1로 표현되지만 실제로는 True/False로 저장된 것이다. 이를 명확하게 정수형(int) 타입으로 변환하면 모델이 처리할 때 더 일관성 있게 작동하게 된다. 한편으로는, 분석 중에 가변수를 합산하거나 수치 계산을 할 경우가 있을 수 있으므로, 계산 시 int 타입이 더 편리한만큼 미리 바꿔주는 것이 좋을 수 있다.
3) 일괄 처리
columns 옵션에 대상 열을 리스트로 지정해서 한 번에 처리할 수 있다. 이때도 마찬가지로 자동으로 원본 열이 제거되며 원본 열 이름이 prefix로 지정되어 새로 생긴 열 앞에 표시된다. (columns 옵션을 지정하지 않으면 문자열 값을 갖는 열 모두를 대상으로 하게 된다.)
# 여러 범주형 변수를 가변수화: smoker, day, time
dumm_cols = ['smoker', 'day', 'time']
tip = pd.get_dummies(tip, columns=dumm_cols, drop_first=True, dtype=int)
가변수화가 되면 우리의 데이터가 아니라고 봐야 한다. 머신, 기계를 위한 데이터이다.
따라서 get_dummies 하기 전 데이터를 가지고 시각화를 먼저 해야 한다. 그래야 우리의 언어로 시각화가 될 수 있다. 따라서 가변수화는 머신에게 던지기 직전에 하는 것이라고 이해하자. 나중에 머신에게 던질 때에는 total_bill과 같은 열의 값들도 0과 1 사이의 데이터로 다 스케일링을 해주어야 한다.
범주값이 너무 많은 경우? 범주가 1000개인 데이터라면?
이런 경우에는 원-핫 인코딩을 사용하지 않는다. 너무 낭비다..
'데이터 분석 > Pandas 기초' 카테고리의 다른 글
데이터프레임 변경(4): Rolling과 Shift / Pivot과 Melt (0) | 2024.09.26 |
---|---|
데이터프레임 변경(3): 합치기(Concat)와 조인(Merge) (0) | 2024.09.25 |
데이터프레임 변경(1): 열(이름변경, 추가, 삭제) / 범주값(변경, 생성) (0) | 2024.09.24 |
데이터프레임 생성, 탐색, 조회, 집계 (1) | 2024.09.23 |
넘파이 배열의 기본 개념 (1) | 2024.09.21 |