HOWTO · Pandas

pandas에서 열 값을 기준으로 DataFrame 행 필터링하기

불리언 마스크, loc, isin, between, 문자열·결측값 검사 및 query로 pandas DataFrame 행을 필터링합니다.

이 페이지의 내용

행 필터링은 각 행마다 불리언 값을 만들고 True인 행만 유지하는 작업입니다. 일반적인 형태는 df.loc[조건]이며 두 번째 인수로 출력 열도 함께 선택할 수 있습니다.

예제 DataFrame 만들기

예제 표에는 텍스트, 숫자, 날짜 및 결측 상품명이 있습니다. 이후 예제는 orders를 재사용합니다. 원래 인덱스가 유지되며 새 인덱스가 꼭 필요할 때만 .reset_index(drop=True)를 사용합니다.

import pandas as pd

orders = pd.DataFrame(
    {
        "order_id": [101, 102, 103, 104, 105, 106],
        "region": ["East", "West", "East", "North", "West", "East"],
        "product": ["Desk", "Chair", "Desk lamp", "Desk", None, "Chair"],
        "sales": [450, 275, 125, 700, 320, 410],
        "status": ["paid", "pending", "paid", "paid", "cancelled", "pending"],
        "ordered_at": pd.to_datetime(["2026-08-01", "2026-08-03", "2026-08-08", "2026-08-11", "2026-08-14", "2026-08-18"]),
    }
)

하나의 열 값으로 행 필터링하기

Series를 값과 비교하면 마스크가 생성됩니다. 단순 대괄호도 같은 행을 선택하지만 .loc는 열도 명시할 수 있습니다. 제외에는 !=, 결측값에는 isna()notna()를 사용합니다.

east_orders = orders.loc[orders["region"] == "East"]
print(east_orders[["order_id", "region", "sales"]].to_string(index=False))

출력:

 order_id region  sales
      101   East    450
      103   East    125
      106   East    410
large_orders = orders.loc[orders["sales"] >= 400, ["order_id", "sales"]]
print(large_orders.to_string(index=False))

출력:

 order_id  sales
      101    450
      104    700
      106    410

여러 조건 결합하기

요소별 조건은 &, |, ~로 결합하고 각 비교를 괄호로 감쌉니다. Python의 andor는 하나의 참값을 요구하므로 Series에서는 참값이 모호하다는 오류가 발생합니다.

mask = (orders["region"] == "East") & (orders["sales"] >= 400)
result = orders.loc[mask, ["order_id", "region", "sales"]]
print(result.to_string(index=False))

출력:

 order_id region  sales
      101   East    450
      106   East    410
mask = (orders["status"] == "paid") | (orders["sales"] > 600)
result = orders.loc[mask, ["order_id", "status", "sales"]]
# Raises ValueError: The truth value of a Series is ambiguous.
orders.loc[(orders["region"] == "East") and (orders["sales"] >= 400)]

값 목록으로 필터링하기

Series.isin()은 긴 동등 비교를 대체합니다. 단일 문자열이 아니라 목록과 같은 컬렉션을 전달합니다. 값을 제외하려면 isin()의 전체 결과 앞에 ~를 붙입니다.

allowed = ["East", "West"]
result = orders.loc[orders["region"].isin(allowed), ["order_id", "region"]]
print(result.to_string(index=False))

출력:

 order_id region
      101   East
      102   West
      103   East
      105   West
      106   East
excluded = ["cancelled", "pending"]
paid_only = orders.loc[~orders["status"].isin(excluded)]

범위와 날짜 필터링하기

between()은 기본적으로 양쪽 끝을 포함하며 pd.to_datetime()으로 변환한 날짜에도 적용됩니다. 시간대가 있는 데이터는 경계의 시간대도 호환되어야 합니다.

mid_value = orders.loc[orders["sales"].between(300, 500), ["order_id", "sales"]]
print(mid_value.to_string(index=False))

출력:

 order_id  sales
      101    450
      105    320
      106    410
date_mask = orders["ordered_at"].between("2026-08-03", "2026-08-14")
result = orders.loc[date_mask, ["order_id", "ordered_at"]]

결측값 또는 비결측값 필터링하기

isna()None, NaN, NaT를 찾고 notna()는 값이 있는 행을 찾습니다. 빈 문자열은 자동으로 결측값이 아니므로 별도로 정규화하거나 검사해야 합니다.

missing_product = orders.loc[orders["product"].isna(), ["order_id", "product"]]
present_product = orders.loc[orders["product"].notna()]
print(missing_product.to_string(index=False))

출력:

 order_id product
      105    None

텍스트 값 필터링하기

str.contains()는 벡터화된 문자열 마스크를 만듭니다. regex=False는 리터럴 검색, case=False는 대소문자 무시, na=False는 결측 텍스트를 불일치로 처리합니다.

has_desk = orders["product"].str.contains("desk", case=False, regex=False, na=False)
result = orders.loc[has_desk, ["order_id", "product"]]
print(result.to_string(index=False))

출력:

 order_id   product
      101      Desk
      103 Desk lamp
      104      Desk

읽기 쉬운 식에 DataFrame.query() 사용하기

query()에서 @는 지역 변수를 참조하고 공백이 있는 열 이름은 백틱으로 감쌉니다. 신뢰할 수 없는 입력을 식에 연결하지 마십시오. 식은 코드를 실행할 수 있습니다.

minimum_sales = 400
result = orders.query("region == 'East' and sales >= @minimum_sales")
print(result[["order_id", "region", "sales"]].to_string(index=False))

출력:

 order_id region  sales
      101   East    450
      106   East    410

.loc, 대괄호, query() 선택하기

일반적으로 .loc를 사용하고 행만 짧게 선택할 때 대괄호를 사용하며 제어된 식이 더 읽기 쉬울 때 query()를 사용합니다. 독립 결과를 수정하기 전에는 .copy()를 호출합니다.

east_orders = orders.loc[orders["region"] == "East"].copy()
east_orders["sales_with_tax"] = east_orders["sales"] * 1.2

정렬과 데이터 형식 문제 피하기

마스크는 인덱스 레이블로 정렬되므로 같은 DataFrame에서 만듭니다. 숫자나 날짜 텍스트는 비교 전에 변환하고 규칙에서 요구할 때만 텍스트를 정규화합니다.

불리언 마스크 재사용 및 디버깅하기

이름이 있는 마스크는 검사하고 개수를 세며 테스트할 수 있습니다. 일치 항목이 없어도 유효한 빈 DataFrame이 반환됩니다. DataFrame.filter()는 셀 값이 아니라 축 레이블을 선택합니다.

원본 데이터를 변경하지 않고 필터링하기

원본을 수정하려면 원본에 .loc로 할당하고 별도 작업 데이터가 필요하면 복사합니다. 이렇게 하면 연쇄 인덱싱이나 불명확한 메모리 공유에 의존하지 않습니다.

DataFrame.loc, Series.isin, Series.between, Series.str.contains, DataFrame.query