본문으로 건너뛰기
김신건의 로그

[Pandas] isin / isna / notna

· 수정 · 📖 약 1분 · 488자/단어 #python #pandas #filter #null
Pandas isin, Pandas isna, Pandas notna, Pandas isin / isna, Pandas null check

정의

메서드의미
.isin(values)각 원소가 values 안에 있는지 (boolean Series)
.isna()NaN/NaT/None 여부 (isnull 별칭)
.notna()위의 반대 (notnull 별칭)

사용 상황

  • isin: SQL IN (...) 처럼 여러 값 중 하나에 해당하는 행을 걸러낼 때
  • isna / notna: 결측치 유무 확인, 결측치가 있는/없는 행 선택 또는 집계 시
  • any / all 조합: 특정 컬럼 조합에 하나라도 결측치가 있는 행을 찾을 때

필터 흐름 시각화

flowchart TD
    DF["DataFrame / Series"] --> IsNull{"결측치 확인?"}
    IsNull -->|"예"| NA["isna / notna"]
    IsNull -->|"아니오, 값 목록 비교"| IN["isin(values)"]

    NA --> NAF["df[df['col'].isna()]"]
    NA --> NANF["df[df['col'].notna()]"]

    IN --> INF["df[df['col'].isin(list)]"]
    IN --> INNF["df[~df['col'].isin(list)]"]

isin

여러 값 중 하나에 해당하는지 검사. SQL 의 IN (...) 과 같음.

python
import pandas as pd
df = pd.DataFrame({
  'name': ['Alice', 'Bob', 'Charlie', 'Dave'],
  'city': ['Seoul', 'Busan', 'Daegu', 'Seoul'],
})
mask = df['city'].isin(['Seoul', 'Busan'])
print(df[mask])
결과
    name   city
0  Alice  Seoul
1    Bob  Busan
3   Dave  Seoul

부정 (NOT IN)

df[~df['city'].isin(['Seoul'])]    # Seoul 아닌 것

dict 전달로 컬럼별 조건

df.isin({'city': ['Seoul'], 'name': ['Alice']})
# city == 'Seoul' AND name == 'Alice' 각각 별도 boolean

Series.isin vs DataFrame.isin

# Series.isin -> boolean Series (행 필터에 바로 사용)
mask = df['city'].isin(['Seoul', 'Busan'])
df[mask]

# DataFrame.isin -> boolean DataFrame (같은 shape)
# 각 셀이 values 에 있는지 여부
df.isin(['Seoul', 'Alice'])

실전: 승인된 카테고리만 필터

ALLOWED_STATUSES = {'approved', 'pending'}
valid = df[df['status'].isin(ALLOWED_STATUSES)]
invalid = df[~df['status'].isin(ALLOWED_STATUSES)]

실전: 다른 DataFrame 의 키로 필터

# whitelist DataFrame 의 id 와 일치하는 행만 추출
valid_ids = whitelist_df['id'].tolist()
df[df['id'].isin(valid_ids)]

isna / notna

df = pd.DataFrame({'a': [1, None, 3], 'b': [None, 2, 3]})

df.isna()                # 같은 shape 의 boolean DataFrame
df['a'].isna()           # Series
df['a'].notna()          # 반대

df[df['a'].notna()]      # a 가 not null 인 행만
df.dropna()              # 어느 컬럼이든 NaN 있는 행 제거
df.dropna(subset=['a'])  # a 컬럼만 검사
python
import pandas as pd
import numpy as np
df = pd.DataFrame({
  'a': [1, np.nan, 3, np.nan],
  'b': [10, 20, np.nan, 40],
})
print(df.isna())
print('---null count---')
print(df.isna().sum())
결과
       a      b
0  False  False
1   True  False
2  False   True
3   True  False
---null count---
a    2
b    1
dtype: int64

any / all 과 결합

# 어느 컬럼이든 NaN 인 행
df[df.isna().any(axis=1)]

# 모든 컬럼이 NaN 인 행
df[df.isna().all(axis=1)]

# 컬럼별로 NaN 비율
df.isna().mean()

# 전체 NaN 개수
df.isna().sum().sum()

NaN, None, NaT, pd.NA 차이

타입대응
np.nanfloat수치형 결측
NoneNoneTypeobject dtype 결측, 자동 NaN 변환
pd.NaTNaTTypedatetime 결측
pd.NANATypenullable dtype 의 결측 (pandas 1.x+)

.isna() 는 위 모두를 True 로 판단한다.

import pandas as pd
import numpy as np

s = pd.Series([1, np.nan, None, pd.NaT, pd.NA], dtype=object)
s.isna()
# 0    False
# 1     True
# 2     True
# 3     True
# 4     True

실전 패턴

결측치 비율 리포트

null_report = pd.DataFrame({
    'count': df.isna().sum(),
    'pct': (df.isna().mean() * 100).round(2),
})
print(null_report[null_report['count'] > 0])

결측치가 있는 컬럼 목록

cols_with_null = df.columns[df.isna().any()].tolist()

특정 컬럼 조합 모두 비어 있는 행 제거

# name 또는 email 중 하나라도 있으면 유지
df = df.dropna(subset=['name', 'email'], how='all')

조건부 fillna (isna + where)

# status 가 NaN 인 행의 status 를 'unknown' 으로
df['status'] = df['status'].where(df['status'].notna(), 'unknown')
# 또는
df['status'] = df['status'].fillna('unknown')

isin + isna 동시 조건

# city 가 서울/부산이고 age 가 null 이 아닌 행
mask = df['city'].isin(['Seoul', 'Busan']) & df['age'].notna()
df[mask]

그룹별 결측치 비율

df.groupby('region')['sales'].apply(lambda s: s.isna().mean())

성능

# isin 은 내부적으로 set lookup
# 리스트보다 set 을 직접 전달하면 약간 빠름 (중복 없을 때)
values = {'Seoul', 'Busan', 'Daegu'}
df['city'].isin(values)

# isna 는 NumPy 연산이므로 매우 빠름 (C 레벨)
# 대량 데이터에서도 병목이 되지 않음

# 주의: object dtype 컬럼에서 isin 은 값 비교가 느릴 수 있음
# category dtype 으로 변환하면 속도 개선
df['city'] = df['city'].astype('category')
df['city'].isin(['Seoul', 'Busan'])  # 더 빠름

TIP

대용량 DataFrame 에서 isin 을 자주 쓴다면 해당 컬럼을 category dtype 으로 변환하라. 메모리 절약 + 비교 속도 향상 두 가지 이점이 있다.

함정

1. == np.nan 안 됨

df[df['a'] == np.nan]    # 항상 False
df[df['a'].isna()]        # 올바름

NaN != NaN 이라는 IEEE 754 규약 때문.

2. is None 도 권장 안 함

df['a'] is None    # Series 가 None 인지 비교 (항상 False)
df['a'].isna()     # 각 원소 검사

3. isin 의 dtype 불일치

df = pd.DataFrame({'id': [1, 2, 3]})  # int
df['id'].isin(['1', '2'])              # 모두 False (str vs int)
df['id'].isin([1, 2])                  # 올바름

WARNING

isin 에 전달하는 리스트의 dtype 이 컬럼의 dtype 과 다르면 일치하는 값이 없어 모두 False 가 된다. 특히 CSV 로 읽은 데이터에서 숫자 id 가 str 로 파싱된 경우 자주 발생.

4. isna 와 dropna 의 미묘한 차이

# isna().any(axis=1) 과 dropna 의 기본 동작은 동일
df[~df.isna().any(axis=1)]
df.dropna()              # 같은 결과 (how='any', axis=0)

# 하지만 subset 지정에서 차이
df.dropna(subset=['a'])
df[df['a'].notna()]      # 위와 같음

5. pd.NA vs np.nan (nullable dtype)

# nullable Int64 컬럼에서 isna 는 pd.NA 도 True 로 처리
s = pd.array([1, pd.NA, 3], dtype='Int64')
pd.Series(s).isna()
# True 로 정상 처리

관련 위키

이 글의 용어 (6개)
[Pandas] .loc / .ilocpandas
정의 - : 라벨 기반 인덱싱 (index/column 이름) - : 정수 위치 기반 인덱싱 (0-based) 이 두 indexer 가 pandas 선택의 표준. 만으로는 헷갈리…
[Pandas] Boolean Indexingpandas
정의 Boolean Indexing 은 True/False 의 Series 를 인덱서로 전달 해 행을 선택하는 패턴. pandas 의 가장 흔한 필터링 방법. 마스크 생성과 적용…
[Pandas] dropna / fillnapandas
정의 - : NaN 이 있는 행/열 제거 - : NaN 을 특정 값으로 대체 데이터 분석의 가장 기본적인 결측치 처리. 사용 상황 - CSV 로드 후 빈 셀이 NaN 으로 읽혔을…
[Pandas] Nullable Types (Int64, boolean, string[pyarrow])pandas
정의 Nullable dtypes 는 NaN/None 을 타입 그대로 표현할 수 있는 pandas 의 새 타입 시스템. NumPy 기반의 (NaN 미허용) 의 한계를 극복. - …
[Pandas] query / evalpandas
정의 - : 문자열로 boolean 표현식 을 전달해 행 필터링 - : 문자열로 계산 표현식 을 평가 의 가독성 있는 대안. 사용 상황 - 조건이 복잡해 boolean index…
[Pandas] value_countspandas
정의 는 각 고유값의 등장 빈도 를 내림차순 Series 로 반환. SQL 의 . pandas 1.1+ 부터는 도 가능 (여러 컬럼 조합). 기본 <CodeWithOutput l…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기