메뉴 닫기

Pandas 조건부 치환 where로 점수 음수값 NaN 처리 데이터 정리 레시피

Pandas 조건부 치환 where로 점수 음수값 NaN 처리 데이터 정리 레시피

📌 한 줄로 품질을 지키는 데이터 정리 패턴과 안전한 전처리 비법을 소개합니다

데이터 분석을 시작하기 전에 가장 먼저 점검해야 할 것은 값의 신뢰도와 일관성입니다.
특히 점수나 지표처럼 음수가 나오면 안 되는 열에서 예외값이 섞여 있으면 모델 학습이나 통계 요약이 쉽게 왜곡됩니다.
불필요한 반복문 없이도 판다스의 벡터화 연산을 활용하면 이런 문제를 간단히 해결할 수 있습니다.
업무 현장에서는 처리 속도와 가독성, 추후 유지보수까지 고려해야 하므로, 한 줄로 의도를 드러내면서도 재사용 가능한 패턴이 큰 힘을 발휘합니다.
오늘은 조건을 만족하지 않는 값을 결측치로 바꿔 데이터 무결성을 확보하는 실전 공식을 친절하게 정리했습니다.
실수하기 쉬운 포인트와 확인 방법까지 함께 담았으니, 실무 데이터셋을 다루는 분들에게 특히 유용할 것입니다.

핵심은 판다스의 where 메서드를 이용해 음수 점수를 깔끔하게 제거하고 분석 가능한 형태로 정돈하는 것입니다.
가장 기본이 되는 코드는 다음 한 줄로 요약됩니다.
df[‘score’]=df[‘score’].where(df[‘score’]>=0, np.nan)
이 구문은 조건에 맞는 값은 그대로 두고, 그렇지 않은 값만 NaN으로 치환해 후속 처리에서 자연스럽게 결측치로 다루게 합니다.
데이터의 의미를 해치지 않으면서 품질을 높일 수 있어, 리포팅과 모델링 모두에서 일관된 결과를 얻는 데 도움이 됩니다.
이 글에서는 이 패턴의 동작 원리와 주의점, 성능 관점, 그리고 실전 응용까지 단계적으로 정리해 드립니다.



🔗 조건부 치환의 개념과 pandas where 동작 원리

조건부 치환은 특정 조건을 만족하는 값만 유지하고, 나머지는 다른 값으로 바꾸는 전처리 기법을 뜻합니다.
판다스의 Series.whereDataFrame.where는 이 개념을 벡터화 연산으로 구현해, 반복문 없이 한 번에 대용량 데이터를 깔끔하게 다룹니다.
동작 원리는 간단합니다.
조건이 True인 위치는 원래 값을 그대로 두고, False인 위치만 지정한 값으로 치환합니다.
이때 치환값을 np.nan으로 주면, 통계 요약이나 시각화 과정에서 자연스럽게 결측치로 처리되어 분석 결과를 왜곡하지 않도록 돕습니다.

이번 글의 핵심 레시피는 점수 열에 음수가 들어왔을 때 이를 결측치로 바꿔 신뢰 가능한 분포를 확보하는 것입니다.
반드시 포함해야 할 기준 코드는 다음 한 줄입니다.
이 공식은 음수 값을 제거(치환)하되, 정상 범위(0 이상)는 그대로 보존하는 가장 안전한 패턴입니다.

CODE BLOCK
import numpy as np

# 핵심 레시피 (필수 포함)
df['score'] = df['score'].where(df['score'] >= 0, np.nan)

여기서 조건식 df[‘score’] >= 0는 불리언 시리즈를 만들고, where는 그 마스크를 기준으로 값을 선택합니다.
즉, 조건을 만족하는 위치는 원래 값, 만족하지 않는 위치는 np.nan이 됩니다.
이 방식은 음수가 나올 수 없는 점수, 개수, 비율(0~1 범위) 등에 바로 적용할 수 있습니다.
또한 inplace 옵션 없이도 좌변 대입만으로 명확하게 의도를 드러내 코드 가독성이 좋아집니다.

🔍 where와 mask의 비교

동일한 결과를 내는 대칭 메서드로 mask가 있습니다.
차이는 where는 조건이 True면 보존하고 False면 치환하는 반면, mask는 그 반대라는 점입니다.
즉, 음수만 치환하려면 mask(df[‘score’] < 0, np.nan)처럼 부정 조건을 사용합니다.
팀 내 합의가 있다면 일관되게 한 가지 스타일을 유지하는 것이 유지보수에 유리합니다.

CODE BLOCK
# 동일한 효과 (음수만 NaN으로)
df['score'] = df['score'].mask(df['score'] < 0, np.nan)

💡 TIP: 정수형 열에 NaN이 들어가면 판다스가 자동으로 부동소수(float)로 승격합니다.
메모리가 민감하다면 Int64(nullable integer) dtype을 고려해 보세요.

⚠️ 주의: where는 조건이 참인 값은 보존합니다.
조건 자체를 잘못 지정하면 정상값이 결측치로 변해 손실이 발생할 수 있으니, 항상 조건식을 명확히 정의하고 적용 전후 요약 통계를 비교하세요.

  • 🧪치환 전후로 describe() 결과를 비교해 분포 변화를 확인
  • 🧭도메인 규칙(예: 점수는 0 이상)을 명확히 정의하고 조건식에 반영
  • 🧩후속 단계에서 결측치 처리 전략(삭제, 대치, 모델별 지원)을 미리 결정

💬 조건부 치환은 ‘정상 범위를 보존’하고 ‘이상치만 결측화’하는 전략으로, 해석 가능성과 데이터 무결성을 동시에 확보합니다.

🛠️ 음수 점수를 NaN으로 바꾸는 기본 패턴

데이터 분석에서 점수, 비율, 개수처럼 음수가 나올 수 없는 열에 잘못된 입력값이 들어가면 전체 분석 결과가 왜곡될 수 있습니다.
예를 들어 설문 응답 점수나 시험 성적 데이터에서 음수가 발생하면 평균값이 낮게 나오거나, 분포가 비정상적으로 꼬여서 시각화 결과가 왜곡되기도 합니다.
이런 경우 단순히 필터링으로 제거하기보다, 결측치(NaN)로 치환해 일관된 데이터 구조를 유지하는 것이 더 안전합니다.

판다스에서는 조건에 따라 값을 바꾸는 다양한 방법이 있지만, 가장 간결하고 명확한 공식은 where()입니다.
다음 예시는 점수 데이터에 음수 값이 들어올 때 이를 결측치로 바꾸는 기본 패턴입니다.

CODE BLOCK
import pandas as pd
import numpy as np

df = pd.DataFrame({'score': [80, 95, -10, 60, -3]})

df['score'] = df['score'].where(df['score'] >= 0, np.nan)

print(df)

출력 결과는 다음과 같습니다.

CODE BLOCK
   score
0   80.0
1   95.0
2    NaN
3   60.0
4    NaN

이처럼 음수였던 값(-10, -3)이 NaN으로 치환되어 통계 계산 시 자연스럽게 제외됩니다.
이 방식은 단순하면서도 강력합니다.
조건을 변경하기만 하면 양수 필터, 특정 구간 필터 등으로 쉽게 확장할 수 있기 때문입니다.

📊 다른 방법과의 비교

같은 작업을 조건문으로 처리할 수도 있지만, numpy.where나 반복문보다 판다스의 Series.where는 더 직관적이며 속도 면에서도 효율적입니다.
특히 대규모 데이터프레임에서는 벡터화된 연산이 훨씬 빠르게 수행되므로, 루프 기반 치환보다 실행 시간이 짧고 메모리 점유율도 낮습니다.

방식 특징
df[‘score’] = np.where(df[‘score’] >= 0, df[‘score’], np.nan) numpy 함수로 가능하나 가독성이 낮음
for 루프 + if 조건문 처리 속도가 느리고 비추천
df[‘score’] = df[‘score’].where(df[‘score’] >= 0, np.nan) 가독성·성능 모두 우수한 권장 방식

💎 핵심 포인트:
데이터의 유효 범위가 명확할 때는, 음수나 비정상 값을 NaN으로 바꿔 ‘무시 가능한’ 상태로 만드는 것이 최선의 정리 전략입니다.

💬 단 한 줄의 where 구문으로 데이터 신뢰도를 높이면, 이후의 모든 분석 결과가 더 정확해집니다.



⚙️ 결측치 처리와 다운스트림 분석 영향

조건부 치환으로 음수 값을 NaN으로 바꾸면 데이터가 훨씬 깨끗해지지만, 그 이후의 분석 단계에서도 이 결측치를 어떻게 다루느냐가 중요합니다.
결측치는 단순히 ‘빈칸’이 아니라, 의미 있는 정보 손실을 나타내므로 처리 전략에 따라 통계 결과가 크게 달라질 수 있습니다.

예를 들어, 평균 계산 시 판다스는 자동으로 NaN을 무시하지만, 표본 개수나 비율 계산에는 영향을 줄 수 있습니다.
또한 머신러닝 모델에서는 결측치를 허용하지 않는 경우가 많아, 제거(drop) 또는 대체(fillna) 과정을 반드시 거쳐야 합니다.
이 과정에서도 데이터의 본질적 특성을 왜곡하지 않는 것이 핵심입니다.

📈 결측치 통계 점검하기

치환 후 결측치가 얼마나 생겼는지 확인하려면 isna()sum()을 함께 사용합니다.
이는 결측치 분포를 빠르게 파악해 후속 처리 범위를 결정하는 데 도움을 줍니다.

CODE BLOCK
# 결측치 개수 확인
df['score'].isna().sum()

또는 전체 데이터프레임 기준으로 다음처럼 요약할 수 있습니다.

CODE BLOCK
# 열별 결측치 개수 출력
df.isna().sum()

이 결과를 통해 어떤 열에서 음수값(→결측치)이 집중적으로 발생했는지 파악할 수 있습니다.
이를 시각화하려면 seaborn.heatmap이나 missingno 패키지를 활용하면 한눈에 확인할 수 있습니다.

🧮 결측치 처리 전략

조건부 치환 이후의 결측치는 상황에 따라 다르게 다뤄야 합니다.
단순히 제거할 수도 있지만, 평균, 중앙값, 예측모델로 대체하는 방법도 있습니다.
다음은 대표적인 세 가지 접근법입니다.

  • 🧹dropna()로 결측 행 전체 제거 — 데이터 양이 충분할 때 적합
  • 🔧fillna(df[‘score’].mean()) — 평균값으로 대체해 균형 유지
  • 🤖머신러닝 회귀 모델을 활용한 예측 대체 — 고급 방식이지만 정확도 향상 가능

💡 TIP: 결측치가 많지 않다면 단순 제거도 무방하지만, 데이터 손실이 크다면 평균 대체보다 중앙값(median)이 왜곡을 줄여주는 경우가 많습니다.

💬 결측치는 ‘버릴지, 살릴지’를 명확히 결정해야 합니다. 단순 치환보다 데이터 의미를 해치지 않는 전략적 접근이 중요합니다.

🔌 성능과 메모리 관점에서의 벡터화 이점

데이터 전처리를 수행할 때, 동일한 연산이라도 for문으로 처리하느냐 pandas.where()처럼 벡터화된 함수를 사용하느냐에 따라 성능 차이는 매우 큽니다.
이는 특히 수십만 건 이상의 데이터프레임을 다룰 때 명확히 드러납니다.
벡터화는 내부적으로 C 기반 최적화 연산을 사용하므로, 파이썬 레벨의 반복문보다 훨씬 빠르게 수행되며 메모리 접근 방식도 효율적입니다.

예를 들어 다음 두 코드는 기능은 같지만 실행 속도에서 큰 차이를 보입니다.
하나는 루프 기반 처리, 다른 하나는 벡터화 기반 처리입니다.

CODE BLOCK
import pandas as pd
import numpy as np
import time

df = pd.DataFrame({'score': np.random.randint(-10, 100, size=1000000)})

# for문 버전
start = time.time()
df_loop = []
for x in df['score']:
    if x >= 0:
        df_loop.append(x)
    else:
        df_loop.append(np.nan)
print("for문 처리시간:", round(time.time() - start, 3), "초")

# where 벡터화 버전
start = time.time()
df['score'] = df['score'].where(df['score'] >= 0, np.nan)
print("where 처리시간:", round(time.time() - start, 3), "초")

일반적으로 루프 방식은 수 초 이상 걸리지만, where() 방식은 0.01초 이내로 처리됩니다.
이는 내부에서 NumPy 배열을 직접 조작하기 때문이며, 불필요한 파이썬 오브젝트 접근이 생략됩니다.

⚡ 메모리 효율성 확보

where 구문을 사용할 때 한 가지 유의할 점은, NaN이 들어가면 열의 dtype이 float64로 자동 변환된다는 것입니다.
정수형 데이터를 유지해야 한다면 Int64(nullable integer) 타입을 명시적으로 지정해주면 됩니다.
이 타입은 결측치를 허용하면서도 정수 연산을 그대로 수행할 수 있습니다.

CODE BLOCK
df['score'] = df['score'].where(df['score'] >= 0, np.nan).astype('Int64')

이처럼 메모리 사용량을 최소화하면서 데이터 타입의 일관성을 유지할 수 있습니다.
특히 빅데이터 환경에서는 이런 세밀한 dtype 관리가 성능 향상에 큰 영향을 줍니다.

💎 핵심 포인트:
벡터화는 속도와 메모리 효율을 동시에 잡는 방법입니다.
조건부 치환을 반복문으로 구현하면 코드 유지보수와 실행 시간이 모두 비효율적으로 늘어나므로, 항상 where() 구문으로 작성하는 것이 좋습니다.

⚠️ 주의: 불필요한 copy() 호출이나 중복 대입은 피해야 합니다.
시리즈를 여러 번 재할당하면 메모리 파편화가 생기므로, where() 구문 한 줄로 처리하는 것이 가장 안전합니다.

💬 벡터화 연산은 판다스의 진정한 강점입니다.
성능·가독성·확장성까지 모두 확보하는 가장 ‘파이써닉’한 방법이죠.



💡 실제 예제 응용과 테스트 전략

이제 where() 구문을 실제 프로젝트 상황에서 어떻게 활용할 수 있는지 살펴보겠습니다.
음수 점수를 NaN으로 바꾸는 기본 원리는 같지만, 데이터의 형태나 분석 목적에 따라 응용 방식이 조금씩 달라집니다.
이 섹션에서는 몇 가지 대표적인 실무 예제를 중심으로 실질적인 활용법을 정리합니다.

🧪 예제 1: 시험 점수 데이터 정리

다음은 학교 시험 점수 데이터프레임입니다.
실제 수집 과정에서 오류 입력으로 인해 일부 점수가 음수로 기록되었습니다.
이때 where()를 적용해 결측치로 처리하면 평균, 분산 계산에서 자동으로 제외됩니다.

CODE BLOCK
data = {'name': ['Kim', 'Lee', 'Park', 'Choi', 'Jung'],
        'score': [88, -5, 92, 77, -10]}
df = pd.DataFrame(data)

df['score'] = df['score'].where(df['score'] >= 0, np.nan)
print(df)
print(df['score'].mean())

결과적으로 LeeJung의 음수 점수는 NaN으로 바뀌며, 평균 계산 시 제외되어 올바른 평균 점수가 산출됩니다.
이 패턴은 모든 ‘비정상 값 제거 후 계산’ 상황에서 그대로 사용할 수 있습니다.

🌡️ 예제 2: 센서 데이터 클렌징

온도, 습도, 압력 같은 센서 데이터에서는 가끔 하드웨어 오류로 인해 음수값이나 비정상적으로 높은 수치가 발생합니다.
이 경우 다음처럼 여러 조건을 동시에 걸 수 있습니다.

CODE BLOCK
# 온도 범위가 -20~50도일 때만 정상 처리
df['temperature'] = df['temperature'].where(
    (df['temperature'] >= -20) & (df['temperature'] <= 50),
    np.nan
)

이처럼 여러 조건을 결합하면 특정 범위를 벗어나는 이상치를 한 번에 결측치로 처리할 수 있습니다.
이는 공정 모니터링, IoT 데이터 전처리 등에서 매우 유용한 기법입니다.

🧭 테스트 및 검증 방법

where 구문이 의도대로 작동했는지 검증하려면, 조건에 맞지 않는 값이 모두 NaN으로 바뀌었는지 확인해야 합니다.
다음 코드를 통해 음수값이 남아 있지 않은지 간단히 테스트할 수 있습니다.

CODE BLOCK
# NaN 아닌 값 중 음수가 존재하는지 검사
(df['score'].dropna() < 0).any()

결과가 False라면, 모든 음수값이 성공적으로 NaN 처리되었다는 의미입니다.
이 검증 단계를 통해 데이터 전처리 코드의 신뢰성을 높일 수 있습니다.

💎 핵심 포인트:
데이터 정리의 핵심은 복잡한 연산이 아니라, 간단하고 재현 가능한 검증 로직을 갖추는 것입니다.
조건부 치환과 검증 구문을 함께 사용하면 분석 전처리의 품질을 높일 수 있습니다.

💬 좋은 데이터 정리는 코드의 길이가 아니라, 명확한 조건과 검증 절차로 결정됩니다.

자주 묻는 질문 (FAQ)

where()와 mask()의 차이는 무엇인가요?
where()는 조건이 참일 때 값을 유지하고, 거짓일 때 치환합니다. 반대로 mask()는 조건이 참일 때 값을 바꿉니다. 즉, 조건의 의미가 반대이므로 혼동하지 않도록 주의해야 합니다.
np.nan 대신 다른 값으로 치환할 수도 있나요?
가능합니다. 예를 들어 df[‘score’].where(df[‘score’] >= 0, 0)처럼 지정하면 음수를 0으로 바꿀 수 있습니다. 다만 분석 목적에 따라 NaN으로 처리하는 편이 더 유연할 때가 많습니다.
음수 대신 None을 넣어도 동일한 효과가 있나요?
None도 결측치로 처리되지만, 데이터 타입이 object로 바뀌는 부작용이 있습니다. 수치형 열이라면 np.nan을 사용하는 것이 더 안정적입니다.
NaN으로 바뀐 뒤 평균 계산 시 결과가 달라지나요?
네, 달라집니다. NaN은 자동으로 계산에서 제외되므로, 음수값이 포함되어 있던 평균보다 더 높게 나올 수 있습니다. 이는 데이터의 정확한 분포를 반영하는 자연스러운 결과입니다.
where 구문이 적용되지 않는 경우는 언제인가요?
Series나 DataFrame이 아닌 파이썬 리스트나 다른 자료형에는 작동하지 않습니다. 반드시 pandas 객체에서 사용해야 하며, 조건식이 같은 크기의 불리언 시리즈여야 합니다.
결측치가 많을 때 모델 학습에 영향이 있나요?
있습니다. 결측치가 많으면 학습 데이터가 줄어들고, 특정 특성의 중요도가 왜곡될 수 있습니다. 따라서 사전에 대체값 처리나 결측치 예측을 수행하는 것이 좋습니다.
where()는 inplace=True 옵션이 있나요?
있습니다. inplace=True로 지정하면 원본 데이터프레임을 직접 수정합니다. 하지만 실수로 원본을 덮어쓸 수 있으므로 대부분의 경우 대입 방식(df[‘col’] = …)을 권장합니다.
where 구문은 NaN이 이미 있는 열에도 적용할 수 있나요?
네, 가능합니다. NaN은 불리언 조건 평가 시 False로 취급되지 않으므로, where를 적용해도 기존 NaN이 유지됩니다. 이는 결측치가 덮어씌워지지 않음을 의미합니다.

📘 조건부 치환으로 데이터 품질을 높이는 실전 정리

데이터 분석의 첫걸음은 신뢰할 수 있는 입력값을 확보하는 것입니다.
특히 점수, 비율, 센서값처럼 음수가 나올 수 없는 열에서는 df[‘score’]=df[‘score’].where(df[‘score’]>=0, np.nan) 한 줄이 데이터의 품질을 지키는 강력한 방어막이 됩니다.
이 구문은 조건에 맞는 값만 유지하고, 그렇지 않은 값은 NaN으로 치환해 후속 분석에서 자연스럽게 제외하도록 돕습니다.
복잡한 if문이나 반복문을 사용하지 않아도 빠르고 안전하게 데이터의 무결성을 확보할 수 있습니다.

또한 where() 메서드는 벡터화 연산으로 작동하므로, 대용량 데이터에서도 우수한 성능을 보입니다.
조건식만 변경하면 다양한 상황에도 즉시 적용할 수 있어, 데이터 클렌징의 기본 도구로 널리 활용됩니다.
결측치가 생겼을 때는 dropna() 또는 fillna()로 후속 처리하면 되고, 결측률이 높을 경우 중앙값 대체나 예측 모델을 적용하는 등 분석 목적에 맞게 조정할 수 있습니다.

한 줄의 코드가 데이터 신뢰도를 결정합니다.
조건부 치환 패턴을 습관적으로 사용하면, 향후 통계 요약·시각화·모델링 과정에서도 예상치 못한 오류를 최소화할 수 있습니다.
코드의 단순함이 곧 데이터 품질을 높이는 가장 강력한 무기가 됩니다.


🏷️ 관련 태그 : pandas, 데이터정리, where함수, 조건부치환, 데이터클렌징, NaN처리, 파이썬전처리, 데이터분석, 벡터화, 데이터무결성