[판다스 16화] apply, map, applymap 차이점과 실무 활용법 | Python Pandas | 빅분기, 빅데이터분석기사 실기

파이썬(Python) 판다스(Pandas) 라이브러리를 활용해 데이터 전처리를 수행하다 보면, 기존에 제공되는 내장 함수만으로는 해결하기 어려운 복잡한 조건의 데이터 가공 작업을 마주하게 됩니다. 예를 들어 비즈니스 환경에서 “고객의 생년월일을 기반으로 현재 나이대 범주를 세분화”하거나, “특정 텍스트 패턴을 분석하여 맞춤형 위험 점수를 계산”하는 작업 등이 이에 해당합니다.

이처럼 사용자가 직접 정의한 커스텀 함수(사용자 정의 함수)나 람다(lambda) 함수를 데이터의 원소, 행, 열 단위에 적용하고자 할 때 판다스에서 제공하는 핵심 도구가 바로 map(), apply(), applymap()입니다. 세 함수는 데이터를 입체적으로 변환할 수 있도록 돕지만, 적용 대상(시리즈 또는 데이터프레임)과 작동 메커니즘이 다르므로 정확히 구분하여 사용해야 코드의 오류를 줄이고 연산 효율성을 높일 수 있습니다. 이번 글에서는 세 함수의 차이점과 실무 활용법을 체계적으로 정리해 보겠습니다.

1. 단일 열 전용 원소별 매핑: map() 함수

Series.map() 함수는 판다스 시리즈 객체의 각 원소(Element)를 하나씩 변환하는 데 사용하는 대표적인 매핑 메서드입니다. 최신 판다스 버전에서는 DataFrame.map()도 제공되며, 기존 applymap()의 역할을 대체합니다.

1.1. 딕셔너리를 활용한 직관적 치환

map() 함수의 가장 큰 장점 중 하나는 함수뿐만 아니라 딕셔너리(Dictionary)를 인자로 받아 문자열이나 범주형 데이터를 매핑할 수 있다는 점입니다.

import pandas as pd

# 가상의 이커머스 고객 등급 데이터 생성
customer_df = pd.DataFrame({
    '고객명': ['이민수', '김지아', '박준영', '최현우'],
    '등급코드': ['G', 'S', 'B', 'G'],
    '당월구매액': [500000, 150000, 80000, 600000]
})

# 등급 코드를 직관적인 명칭으로 변경하기 위한 딕셔너리 정의
grade_map = {'G': 'Gold', 'S': 'Silver', 'B': 'Bronze'}

# map()을 활용한 1:1 매핑 변환
customer_df['등급명'] = customer_df['등급코드'].map(grade_map)

1.2. map() 사용 시 주의사항

딕셔너리를 전달하여 map()을 실행할 때, 원본 시리즈의 값 중 딕셔너리의 키(Key)에 존재하지 않는 값이 있다면 해당 원소는 결측치인 NaN으로 자동 변환됩니다. 만약 기존 값을 보존하면서 특정 값만 치환하고 싶다면 map() 대신 replace() 함수를 사용하는 편이 구조적으로 안전합니다.

2. 행과 열 단위의 다차원 변환: apply() 함수

apply() 함수는 사용자 정의 로직을 적용할 수 있어 매우 유연한 도구이며, 기본 제공 함수만으로 해결하기 어려운 복잡한 조건 처리에서 활용도가 높습니다.

2.1. 시리즈에 apply() 적용하기

시리즈에 apply()를 사용할 때는 앞서 배운 map()과 유사하게 원소별 연산으로 작동합니다. 주로 람다 함수나 복잡한 커스텀 함수를 연결할 때 쓰입니다.

# 구매액에 따른 보너스 포인트 계산 함수
def calculate_point(amount):
    if amount >= 500000:
        return amount * 0.05
    else:
        return amount * 0.01

# 시리즈의 각 원소에 함수 적용
customer_df['적립포인트'] = customer_df['당월구매액'].apply(calculate_point)

2.2. 데이터프레임에 apply() 적용하기 (축 설정의 핵심)

apply()를 데이터프레임 단위로 호출할 때는 어느 방향으로 함수를 흘려보낼지 결정하는 axis 파라미터의 역할이 매우 중요합니다.

  • axis=0 (기본값): 열(Column) 단위로 함수를 적용합니다. 즉, 각 열(Column)이 하나의 Series 형태로 함수에 전달되며 컬럼별 평균, 최댓값 등의 요약 통계를 계산할 때 활용됩니다.
  • axis=1: 행(Row) 단위로 함수를 적용합니다. 즉, 하나의 행(Row)에 포함된 여러 컬럼 값이 Series 형태로 함수에 전달되므로 여러 컬럼을 조합한 파생변수를 생성할 때 활용됩니다.
# [실무 활용] 여러 컬럼의 값을 조합하는 행 단위(axis=1) 연산
# 등급명이 Gold이면서 구매액이 50만 원 이상인 우수 고객 판별 파생변수 생성
customer_df['우수고객여부'] = customer_df.apply(
    lambda row: 'Target' if (row['등급명'] == 'Gold' and row['당월구매액'] >= 500000) else 'Normal', 
    axis=1
)

3. 데이터프레임 전체 원소의 일괄 변경: applymap() 함수

applymap() 함수는 데이터프레임(DataFrame) 객체에서만 사용 가능하며, 데이터프레임의 모든 단일 원소(Element)에 함수를 일괄 적용하는 도구입니다.

3.1. 전체 데이터의 서식 및 자료형 정제

텍스트 데이터가 주를 이루는 데이터프레임 전체의 공백을 제거하거나, 모든 숫자 데이터를 소수점 둘째 자리 포맷의 문자열로 변환하는 등 행렬 전체의 서식을 통일할 때 유용합니다.

# 샘플 수치형 데이터프레임 생성
data_df = pd.DataFrame({
    '지점A': [12.345, 45.678],
    '지점B': [89.012, 34.567]
})

# 모든 원소를 소수점 첫째 자리 문자열 포맷으로 일괄 변경
formatted_df = data_df.applymap(lambda x: f"{x:.1f}")

판다스 버전 업데이트 변화 안내: 판다스 2.1 버전부터는 기존 DataFrame 전용 메서드였던 applymap()을 대체하기 위해 DataFrame.map()이 추가되었습니다. 기존 applymap()도 당분간 사용할 수 있지만, 최신 판다스 환경에서는 df.map() 사용을 권장하고 있습니다.

4. map, apply, applymap 핵심 차이점 요약

세 함수의 기능과 적용 방식을 표로 비교하면 다음과 같이 간결하게 정리할 수 있습니다.

함수명적용 대상입력 가능한 인자 형태주요 용도
map()시리즈 (Series) / 데이터프레임(최신 Pandas)함수, 람다, 딕셔너리, 시리즈단일 열의 1:1 값 치환 및 매핑 코드 매칭
apply()시리즈 / 데이터프레임함수, 람다행/열 단위 다중 변수 참조 파생변수 생성 및 집계
applymap()
(최신 버전은 df.map)
데이터프레임 (DataFrame)함수, 람다데이터프레임 전체 원소의 일괄 서식 변경 및 클렌징

5. 빅데이터분석기사 실기 시험 환경에서의 실전 활용 포인트

빅데이터분석기사 실기(작업형 제1유형 및 제2유형)를 풀이할 때 복잡한 조건 분기문을 피처에 반영해야 하는 상황이 주어집니다.

5.1. apply(axis=1)을 활용한 다중 조건 파생변수 생성

시험장에서 if-elif-else 형태의 연속적인 조건 처리를 수행해야 할 때, 복잡한 불리언 인덱싱을 여러 번 짜는 것보다 사용자 정의 함수를 상단에 선언한 뒤 apply(axis=1)로 한 번에 처리하는 것이 오타를 줄이는 방법이 됩니다.

# 1. 시험 조건에 맞는 분기 함수 정의
def get_age_group(age):
    if age < 20: return '미성년'
    elif age < 40: return '청년'
    elif age < 60: return '중년'
    else: return '장년'

# 2. apply를 통한 안전한 파생변수 반영
X_train['연령대'] = X_train['나이'].apply(get_age_group)

5.2. 대용량 데이터 연산 시 성능 고려

apply()와 map() 계열 함수는 사용자 정의 함수를 각 데이터 요소 또는 행 단위로 호출하는 방식으로 동작하기 때문에, 판다스의 벡터화 연산보다 상대적으로 느릴 수 있습니다.

따라서 빅분기 시험장이나 실무 환경에서 수백만 행 이상의 대규모 데이터셋을 다룰 때는 단순 사칙연산이나 내장 함수(np.where, pd.cut 등)로 해결할 수 있는 연산인지 먼저 검토하고, 그것이 불가능한 복잡한 논리 구조일 때 제한적으로 apply()를 선택하는 접근이 메모리 초과나 시간 초과 판정을 예방하는 합리적인 판단이 됩니다.

댓글 남기기