회사에서 데이터 전처리할 일이 있어서 며칠 끙끙거렸다. 이번에 기록하고 정리하는 습관이 얼마나 중요한지 깨달았다... 이전에 활용한 적이 있지만 정리를 제대로 하지 않았고, 내가 생각한 기능을 하는 함수들을 구글에서 서치 하거나 ChatGPT한테 묻느라 시간이 너무 오래 걸렸다. 제대로 기록해 놓지 않은 스스로에게 짜증이 났지만, 이번 기회에 Pandas, Numpy의 기능들을 많이 알게 된 것은 수확이다.
이번에 데이터 전처리하면서 깨닫고 정리한 내용들을 블로그에도 포스팅하려 한다. 가장 먼저 쓸 글은 np.where 에 대해서인데, 이번 데이터 전처리에서 막혔던 부분을 해결하는데 핵심적인 역할을 했기 때문이다.
np.where 개념
1. np.where사용하면 아래의 기능들을 구현할 수 있다.
1) 특정 배열에서 원하는 조건에 맞는 값들 필터링하기
2) 이 과정에서 조건을 만족하는 값들, 만족하지 못하는 값들의 대체값을 지정해 줄 수 있다
3) 특정 배열에서 False가 아닌 값들의 인덱스 추출하기 (대체값을 지정해주지 않은 경우)
2. pd.DataFrame.where 과 거의 같은 기능을 한다. Pandas의 where과 차이점이 있는데, np.where은 조건이 참인 경우의 대체값도 지정해줘야 한다는 점이다. Pandas의 where은 조건이 거짓일 때의 대체값만 지정해 주면 된다.
3. np.where(조건문, 참일 때 대체값, 거짓일 때 대체값) 의 형태로 사용한다.
예시 코드, 활용
1. 이번 전처리에서는 위에서 적은 3가지 기능 중, 세 번째 기능이 핵심적이었다. np.where의 특이한 점은 대체값을 지정해주지 않으면, 대상이 되는 배열에서 True인 값들의 인덱스를 반환한다는 점이다. 아래 예시 코드를 살펴보자.
import numpy as np
test_array = np.random.randint(1, 101, size=20)
print(test_array) # 랜덤 배열 확인
# 결과 [64 62 38 74 27 75 47 39 69 41 44 80 82 80 28 75 59 53 66 63]
print(np.where(test_array>50)) # 랜덤 배열 중 50보다 큰 수들의 인덱스 확인
# 결과 (array([ 0, 1, 3, 5, 8, 11, 12, 13, 15, 16, 17, 18, 19], dtype=int64),)
####################
# 대체값 넣어주지 않은 경우에 대해 더 살펴보기
print(type(np.where(test_array>50)))
# 결과 <class 'tuple'>
print(len(np.where(test_array>50)))
# 결과 1
print(np.where(test_array>50)[0])
# 결과 [ 0 1 3 5 8 11 12 13 15 16 17 18 19]
####################
print(np.where(test_array>50, test_array, 0)) # 랜덤 배열 중 50보다 큰 수들은 그대로, 작은 수는 0 출력
# 결과 [64 62 0 74 0 75 0 0 69 0 0 80 82 80 0 75 59 53 66 63]
print(np.where(test_array>50, 0)) # 조건을 만족하거나 만족하지 못하는 경우의 대체값을 빼먹은 경우
# 결과 ValueError: either both or neither of x and y should be given
1~101 사이의 숫자를 랜덤하게 20개 뽑아 길이 20의 배열인 test_array를 생성했다.
1) print(test_array) 는 test_array에 어떤 숫자들이 뽑혔는지 확인하기 위한 코드다.
2) print(np.where(test_array>50)) 는 '배열 중에서 50보다 큰 수들' 이라는 조건만 입력하고, 조건을 만족하는 참값과 거짓값의 대체값을 넣지 않은 경우다. 결과는 test_array에서 50보다 큰 수들의 인덱스가 출력된다.
여기서 특이한 점은, 이렇게 대체값을 넣어주지 않은 경우, 조건을 만족하는 값들의 인덱스가 튜플 형태로 출력된다는 것. 실제로, type(np.where(test_array>50)) 을 확인해 보면 tuple임을 알 수 있다. len으로 길이도 확인할 수 있는데, 길이 1인 튜플을 결괏값으로 내놓는다는 걸 알 수 있다. 때문에, 조건을 만족하는 값들의 인덱스를 제대로 활용하기 위해선, np.where(test_array>50)[0] 이렇게 인덱스 0을 적어줘야 한다.
3) print(np.where(test_array>50, test_array, 0)) 는 배열에서 50보다 큰 수들은 그대로, 작은 수는 0 출력하는 코드다.
4) print(np.where(test_array>50, 0)) pd.where을 사용하듯이, '50보다 크다는 조건을 만족하지 못하면 0으로 출력해야지' 하면서 짠 코드다. ValueError가 발생함을 알 수 있다. 에러 문구도 대체값을 둘 다 쓰던지, 아니면 둘 다 쓰지 말라고 한다. (both or neither)
2. 그렇다면, 이를 어디에 활용할 수 있을까? 이번 데이터 전처리에서 가장 문제가 됐던 부분은 아래와 같다.
1) 데이터프레임 A 의 각 행에서 조건을 만족하는 데이터들 고르기
2) 해당 데이터들을 새로운 데이터프레임 B 에 정리
3) 단, 데이터프레임 A 에서 행 인덱스가 0, 1, 2, ... 라면 데이터프레임 B 에서는 A의 인덱스 * 3 만큼씩 뒤로 밀릴 것
그림으로 보자.
위 이미지처럼 (1) 조건을 만족하는 행들을 필터링하고, (2) 특정한 규칙에 따라 재배치하여 새로운 데이터프레임을 만드는 경우에 np.where 매우 유용한 기능이었다. 이를 구현한 코드도 같이 첨부한다.
# False만 추출하고 싶을 때
for i in range(len_df):
now_start_num = df.index[i] * 3 # 현재 인덱스에 따른 시작 숫자
row_data = df_for_compare.iloc[i].to_numpy()
mask = ~df_tf.iloc[i].to_numpy() # False만 추출
insert_values = row_data[mask] # False인 값들
insert_positions = np.where(mask)[0] + now_start_num # False 데이터들 인덱스
df_addr.iloc[i, insert_positions] = insert_values # 새로운 데이터프레임에 False 데이터들 쌓기
참고 자료
- pd.where vs np.where 비교 : https://yganalyst.github.io/data_handling/memo_3/
- pd.where 공식 문서 : https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.where.html
- np.where 공식 문서 : https://numpy.org/doc/2.2/reference/generated/numpy.where.html
numpy.where — NumPy v2.2 Manual
Return elements chosen from x or y depending on condition. Note When only condition is provided, this function is a shorthand for np.asarray(condition).nonzero(). Using nonzero directly should be preferred, as it behaves correctly for subclasses. The rest
numpy.org
'컴퓨터 프로그래밍' 카테고리의 다른 글
생성형 AI 프로젝트 3단계 - 챗봇 UI 만들기 (Streamlit) (0) | 2025.05.26 |
---|---|
생성형 AI 프로젝트 - 2단계. AI 모델 학습시키기 (BERT, FinancialBERT Fine-Tuning) (0) | 2025.05.25 |
생성형 AI 프로젝트 - 1단계. PRAW로 레딧 댓글 스크랩하기 (2) | 2025.05.24 |
파이썬 동적 변수 할당 방법 3가지 - globals(), 딕셔너리, setattr() (0) | 2025.05.19 |
트랜스포머(Transformer) 구조, 흐름 정리 (0) | 2025.05.13 |