Python 복잡한 리스트 필터링: filter vs 리스트 컴프리헨션, 무엇이 더 나을까?

데이터 전처리나 리스트에서 특정 조건에 맞는 요소를 추출하는 작업은 Python에서 자주 필요한 작업 중 하나입니다. 이런 경우, filter() 함수와 리스트 컴프리헨션(List Comprehension)을 사용할 수 있습니다. 하지만 둘 중 어떤 방법이 더 적합할까요? 이번 글에서는 두 방법을 비교하며 실제 데이터 처리에서 유용한 코드 예제와 함께 이해하기 쉽게 설명하겠습니다.


1. filter와 리스트 컴프리헨션의 기본 개념

filter() 함수

  • filter() 함수는 조건에 맞는 요소만 걸러내는 내장 함수입니다.
  • 첫 번째 인자로 필터링 조건을 정의하는 함수, 두 번째 인자로 필터링 대상 데이터를 받습니다.
  • 결과는 이터레이터 형태로 반환됩니다.
nums = [1, 2, 3, 4, 5]
filtered = filter(lambda x: x % 2 == 0, nums)  # 짝수만 필터링
print(list(filtered))  # [2, 4]

리스트 컴프리헨션

  • 리스트 컴프리헨션은 리스트를 생성하면서 특정 조건에 맞는 요소만 포함할 수 있습니다.
  • 더 간결한 구문으로 조건을 표현할 수 있습니다.
nums = [1, 2, 3, 4, 5]
filtered = [x for x in nums if x % 2 == 0]  # 짝수만 필터링
print(filtered)  # [2, 4]

2. 실제 데이터 처리에서 두 방법 비교

상황: 딥러닝 데이터 전처리

문제: 딥러닝 모델을 학습시키기 전에, 결측값이 있는 데이터를 제거하고, 입력 크기를 기준으로 데이터 필터링을 진행해야 합니다.

filter 사용

# 데이터 예시
data = [
    {"id": 1, "input": [0.1, 0.2], "label": 1},
    {"id": 2, "input": [0.3, None], "label": 0},
    {"id": 3, "input": [0.5, 0.7], "label": 1},
]

# 결측값이 없는 데이터만 필터링
def is_valid(sample):
    return None not in sample["input"]

filtered_data = filter(is_valid, data)
print(list(filtered_data))

결과

[{'id': 1, 'input': [0.1, 0.2], 'label': 1}, {'id': 3, 'input': [0.5, 0.7], 'label': 1}]

리스트 컴프리헨션 사용

# 결측값이 없는 데이터만 필터링
filtered_data = [sample for sample in data if None not in sample["input"]]
print(filtered_data)

결과

[{'id': 1, 'input': [0.1, 0.2], 'label': 1}, {'id': 3, 'input': [0.5, 0.7], 'label': 1}]

비교

  • 가독성: 리스트 컴프리헨션은 코드가 더 짧고 명확합니다.
  • 함수 재사용성: filteris_valid와 같은 함수를 정의하여 조건을 독립적으로 관리할 수 있어 재사용성이 높습니다.

3. 성능 비교: 언제 무엇을 써야 할까?

리스트 크기가 클 때

filter와 리스트 컴프리헨션 모두 큰 리스트를 처리할 수 있지만, filter는 지연 평가(Lazy Evaluation)를 사용해 조건에 맞는 요소를 하나씩 처리합니다. 반면, 리스트 컴프리헨션은 한 번에 모든 데이터를 처리하여 새 리스트를 생성합니다.

예제: 메모리 효율 테스트

import sys

nums = range(1, 1000000)  # 100만 개의 숫자
filtered_with_filter = filter(lambda x: x % 2 == 0, nums)
filtered_with_comprehension = [x for x in nums if x % 2 == 0]

print(sys.getsizeof(filtered_with_filter))  # 매우 작은 크기 (이터레이터)
print(sys.getsizeof(filtered_with_comprehension))  # 메모리 크기가 커짐 (리스트)

결과

112
84488

메모리 효율이 중요한 경우

  • filter는 이터레이터를 반환하기 때문에 메모리를 덜 사용합니다.
  • 리스트 컴프리헨션은 새로운 리스트를 생성하므로 더 많은 메모리를 사용합니다.

4. 복잡한 조건 필터링: 다중 조건 적용하기

상황: 딥러닝 라벨 기준으로 데이터 필터링

라벨이 1이고, 입력 데이터의 길이가 2 이상인 샘플만 필터링합니다.

filter 사용

# 다중 조건을 적용한 필터링 함수
def is_valid(sample):
    return sample["label"] == 1 and len(sample["input"]) >= 2

filtered_data = filter(is_valid, data)
print(list(filtered_data))

결과

[{'id': 1, 'input': [0.1, 0.2], 'label': 1}, {'id': 3, 'input': [0.5, 0.7], 'label': 1}]

리스트 컴프리헨션 사용

filtered_data = [
    sample for sample in data 
    if sample["label"] == 1 and len(sample["input"]) >= 2
]
print(filtered_data)

결과

[{'id': 1, 'input': [0.1, 0.2], 'label': 1}, {'id': 3, 'input': [0.5, 0.7], 'label': 1}]

비교

  • 가독성: 리스트 컴프리헨션은 조건을 함수로 분리하지 않아 간단한 조건에서는 더 직관적입니다.
  • 유연성: filter는 조건을 함수로 정의하므로 조건이 복잡할 경우 유지보수가 쉬워집니다.

5. 결론: 언제 어떤 방법을 사용할까?

filter를 사용하는 경우

  • 조건을 함수로 정의하여 재사용성과 확장성을 높이고 싶을 때
  • 메모리 사용량을 최소화해야 할 때 (지연 평가 덕분에 큰 데이터를 처리할 때 유리)

리스트 컴프리헨션을 사용하는 경우

  • 필터링 조건이 간단하고 가독성이 중요한 경우
  • 한 번에 처리할 데이터 크기가 크지 않고, 리스트 생성이 필요한 경우

요약: filter vs 리스트 컴프리헨션

특성 filter 리스트 컴프리헨션
가독성 함수 기반으로 조건 분리, 유지보수 용이 간결한 구문으로 직관적 표현 가능
메모리 사용 지연 평가로 메모리 효율적 새 리스트 생성으로 메모리 사용 증가
조건의 복잡성 복잡한 조건에서 함수로 표현 가능 간단한 조건에 적합
속도 큰 차이 없음 (조건에 따라 다름) 큰 리스트의 경우 메모리 부담 발생 가능

filter와 리스트 컴프리헨션 모두 각각의 장점이 있으므로, 데이터 크기와 조건의 복잡성을 고려해 상황에 맞게 선택하세요! 🎯

+ Recent posts