파이썬 map() & 데이터 전처리 실습 정리

syeom·2026년 4월 14일

멀티캠퍼스 데이터분석 수업 — map(), str 메서드, 파생변수 생성, 결측치 처리 정리


📌 목차

map() & 리스트 컴프리헨션
1. value_counts() — 빈도수 확인
2. 공백 제거 — for / while / map() 비교
3. Series.str — 문자열 메서드 접근
4. map() + 결측치 처리 (na_action)
5. map() 활용 — 요일 변환
6. map() vs replace()
7. 리스트 컴프리헨션

실전 데이터 전처리 — corona.csv

  1. 데이터 로드 & 기본 탐색
  2. 컬럼 제거 & 이름 변경
  3. 정렬 & 인덱스 초기화
  4. 파생변수 생성 — 일일확진자 & 일일사망자
  5. shift() & diff()
  6. 데이터 오류 수정 & 재처리

실전 데이터 전처리 — uriage.csv

  1. 데이터 정형화 — 대소문자 & 공백 제거
  2. 결측치 채우기 — 상품별 평균가격

1. value_counts() — 빈도수 확인

특정 컬럼의 값(value)별 빈도수(count)를 확인하는 함수입니다.

import pandas as pd

df = pd.DataFrame({
    'product': [' iphone ', ' iphone', 'iphone', 'galaxy ', 'galaxy']
})

df['product'].value_counts()
# 공백 차이 때문에 같은 값인데도 다르게 인식됨


2. 공백 제거 — for / while / map() 비교

Series에는 strip()이 존재하지 않아서 단일 데이터에만 바로 적용할 수 있습니다.

df['product'][0].strip()   # ✅ 단일 데이터(str 타입)이므로 가능
df['product'].strip()      # ❌ Series에는 strip() 없음 → 에러

방법 1 — for문

df2 = df.copy()   # 원본 백업

for i in range(len(df2)):
    df2.iloc[i, 0] = df2.iloc[i, 0].strip()

df2['product'].value_counts()

방법 2 — while문

df3 = df.copy()
i = 0

while i < len(df3):
    print(df3.iloc[i, 0].strip())
    i += 1

방법 3 — while True + try/except

i = 0
while True:
    try:
        print(df3.iloc[i, 0].strip())
        i += 1
    except:
        break   # 인덱스 범위 초과 시 반복 종료

방법 4 — map() ✅ 권장

def data_strip(x):
    return x.strip()

df3['product'].map(data_strip)

# lambda로 더 간결하게
df3['product'].map(lambda x: x.strip()).value_counts()

💡 map() 이란?
1차원 데이터의 각 원소에 함수를 적용하여 데이터를 수정/변환합니다.
파이썬 기본 제공 map()Series 내장 map() 두 가지가 있습니다.


3. Series.str — 문자열 메서드 접근

Series에서 문자열 함수를 사용하려면 .str을 통해 접근합니다.

# 타입 변화 흐름
# DataFrame → Series → str 메서드 → Series
df['product'].str.strip()
접근 방식타입설명
dfDataFrame2차원 데이터
df['product']Series1차원 데이터
df['product'].strStringMethods문자열 메서드 접근자
df['product'].str.strip()Series결과 반환
import numpy as np

series_1 = pd.Series(['cat', 'dog', np.nan])

series_1.str.upper()   # 결측치는 NaN 그대로 유지

4. map() + 결측치 처리 (na_action)

map()에 lambda를 쓸 때 결측치가 있으면 에러가 발생합니다.
na_action='ignore'로 결측치를 건너뛸 수 있습니다.

series_1 = pd.Series(['cat', 'dog', np.nan])

# ❌ 결측치(float 타입)에 upper()가 없어서 에러 발생
series_1.map(lambda x: x.upper())

# ✅ na_action='ignore' → 결측치는 함수 적용 없이 그대로 유지
series_1.map(lambda x: x.upper(), na_action='ignore')

💡 str.upper()na_action 없이도 결측치를 알아서 처리합니다.
lambda를 써야 할 때만 na_action='ignore'를 추가하세요.


5. map() 활용 — 요일 변환

요일 데이터는 숫자(0~6)로 저장하면 메모리를 훨씬 적게 사용합니다.
map()을 이용해 숫자를 한글 요일로 변환하는 방법 3가지입니다.

week_series = pd.Series([0, 1, 2, 3, 4, 5, 6])

방법 1 — if/elif 함수

def week_change(x):
    if x == 0:   return '일'
    elif x == 1: return '월'
    elif x == 2: return '화'
    elif x == 3: return '수'
    elif x == 4: return '목'
    elif x == 5: return '금'
    else:        return '토'

week_series.map(week_change)

방법 2 — 리스트 인덱스 활용 ✅ 간결

def week_change2(x):
    week_list = ['일', '월', '화', '수', '목', '금', '토']
    return week_list[x]   # 숫자 → 인덱스로 활용

week_series.map(week_change2)

방법 3 — 딕셔너리 활용 ✅ 권장

week_dict = {
    0: '일', 1: '월', 2: '화',
    3: '수', 4: '목', 5: '금', 6: '토'
}

week_series.map(week_dict)


6. map() vs replace()

map()은 딕셔너리와 함수를 모두 받을 수 있지만, 리스트는 받지 못합니다.
리스트 형태의 치환은 replace()를 사용하세요.

# map() — 딕셔너리 ✅, 함수 ✅, 리스트 ❌ (TypeError)
week_series.map(week_dict)

# replace() — 딕셔너리로 값 치환
week_series.replace(week_dict)

# 단순 두 값 치환 예시
gender_series = pd.Series(['f', 'm', 'f'])

gender_series.map(lambda x: 'female' if x == 'f' else 'male')

💡 한 줄 if문 (삼항 연산자)
참인 경우 if 조건식 else 거짓인 경우


7. 리스트 컴프리헨션

리스트를 간결하게 생성하는 파이썬 문법입니다.

list_1 = [1, 2, 3, 4, 5, 6]

기본 형태 — 조건 필터링

# for문
new_list = []
for i in list_1:
    if i % 2 == 0:
        new_list.append(i)

# 리스트 컴프리헨션 — 짝수만 추출
new_list = [x for x in list_1 if x % 2 == 0]
print(new_list)  # [2, 4, 6]

조건에 따라 값 변환

# 홀수 → '홀수', 짝수 → '짝수'로 변환
new_list3 = ['홀수' if x % 2 == 1 else '짝수' for x in list_1]
print(new_list3)  # ['홀수', '짝수', '홀수', '짝수', '홀수', '짝수']

조건 필터링 + 값 변환 동시에

# 3보다 큰 값만 추출 + 홀짝 변환
new_list4 = ['홀수' if x % 2 == 1 else '짝수' for x in list_1 if x > 3]
print(new_list4)  # ['홀수', '짝수', '홀수']

패턴 정리

형태설명
[x for x in list]모든 원소 그대로
[x for x in list if 조건]조건을 만족하는 원소만 추출
[식 for x in list]모든 원소에 변환 적용
[참 if 조건 else 거짓 for x in list]조건에 따라 값 변환
[참 if 조건 else 거짓 for x in list if 조건2]필터링 + 값 변환 동시에


실전 데이터 전처리 — corona.csv

8. 데이터 로드 & 기본 탐색

corona = pd.read_csv('../csv/corona.csv')

corona.head()          # 상위 5개
corona.tail()          # 하위 5개
corona.columns         # 컬럼 목록 확인
corona.info()          # 타입, 결측치 현황
corona.isna().sum()    # 결측치 개수
corona.describe()      # 수치형 통계 요약

# 문자형 컬럼 통계 요약
corona[['등록일시', '기준시간']].describe()

9. 컬럼 제거 & 이름 변경

불필요한 컬럼 제거

# 'Unnamed: 0' → 인덱스 역할이라 불필요
# 'seq' → 게시글 번호라 불필요
corona.drop(['Unnamed: 0', 'seq'], axis=1, inplace=True)

⚠️ inplace=True로 제거한 뒤 같은 셀을 다시 실행하면 에러 발생.
이미 제거된 컬럼을 또 제거하려 하기 때문입니다.

컬럼 이름 변경 — 2가지 방법

# 방법 1: rename() — 특정 컬럼만 변경할 때
corona.rename(columns={
    'createDt': '등록일시',
    'deathCnt': '총사망자',
    'decideCnt': '총확진자',
    'stateDt': '기준일',
    'stateTime': '기준시간',
    'updateDt': '수정일시',
    'accExamCnt': '누적의심자',
    'accDefRate': '누적확진율'
}, inplace=True)

# 방법 2: .columns 직접 대입 — 전체 컬럼을 한 번에 변경할 때
corona.columns = ['등록일시', '총사망자', '총확진자', '기준일',
                  '기준시간', '수정일시', '누적의심자', '누적확진율']

💡 방법 2는 컬럼 개수가 정확히 일치해야 하고, 변경 전 미리 확인이 어렵습니다.
일부만 바꿀 때는 rename(), 전체 교체 시에는 .columns 대입이 편합니다.


10. 정렬 & 인덱스 초기화

# 등록일시 기준 오름차순 정렬
corona.sort_values('등록일시', ascending=True, inplace=True)

# 인덱스 초기화 — 기존 인덱스 제거
corona.reset_index(drop=True, inplace=True)

# 원본 백업
origin_data = corona.copy()


함수설명
sort_values(컬럼, ascending=True)값 기준 오름차순 정렬
sort_values(컬럼, ascending=False)값 기준 내림차순 정렬
sort_index()인덱스 기준 정렬
reset_index(drop=True)인덱스 초기화 (기존 인덱스 제거)
reset_index(drop=False)인덱스 초기화 (기존 인덱스를 컬럼으로 유지)

11. 파생변수 생성 — 일일확진자 & 일일사망자

데이터셋에 없는 컬럼이지만 기존 데이터로 계산해서 새 컬럼을 만드는 것을 파생변수라고 합니다.

일일확진자 = 오늘의 총확진자 - 어제의 총확진자
일일사망자 = 오늘의 총사망자 - 어제의 총사망자

for문으로 직접 계산

new_list = []

for i in range(1, len(corona)):
    result = corona.loc[i, '총확진자'] - corona.loc[i-1, '총확진자']
    new_list.append(result)

# 첫날은 계산 불가 → 0으로 채우기
new_list.insert(0, 0)
# 또는: [0] + new_list

corona['일일확진자'] = new_list


12. shift() & diff()

for문보다 훨씬 간결하게 같은 결과를 낼 수 있습니다.

# shift(n) : 데이터를 n칸 아래로 이동 → 이전 행 값과 비교 가능
# 첫 번째 행은 이전 값이 없으므로 NaN 발생 → fillna(0)으로 처리

corona['일일사망자'] = (corona['총사망자'] - corona['총사망자'].shift(1)).fillna(0)

# diff(n) : n칸 이동한 값과의 차이를 한 번에 계산
# shift()를 이용한 빼기 연산과 결과 동일
corona['일일확진자'] = corona['총확진자'].diff(1).fillna(0)

💡 Series - Series.shift(1)Series.diff(1) 의 결과는 동일합니다.
diff()가 더 간결하므로 파생변수 생성 시 권장합니다.

음수 데이터 확인

# 일일확진자나 일일사망자가 음수인 행 필터링
flag = (corona[['일일사망자', '일일확진자']] < 0).any(axis=1)
corona.loc[flag, ]

13. 데이터 오류 수정 & 재처리

음수가 발생한 원인을 추적하니 445번 인덱스의 등록일시가 잘못 입력되어 있었습니다.

# 오류 데이터 주변 확인
corona.loc[444:448, ]

# 오류 수정 — 특정 위치 데이터를 직접 대입
corona.loc[445, '등록일시'] = '2021-05-31 00:00:00.000'

수정 후 재처리 순서

# 1. 등록일시 기준 오름차순 재정렬
corona.sort_values('등록일시', ascending=True, inplace=True)

# 2. 인덱스 초기화
corona.reset_index(drop=True, inplace=True)

# 3. 기존 파생변수 컬럼 제거
corona.drop(['일일사망자', '일일확진자'], axis=1, inplace=True)

# 4. 파생변수 재생성
corona['일일사망자'] = (corona['총사망자'] - corona['총사망자'].shift(1)).fillna(0)
corona['일일확진자'] = corona['총확진자'].diff(1).fillna(0)

# 5. 음수 여부 재확인
flag = (corona[['일일확진자', '일일사망자']] < 0).any(axis=1)
corona.loc[flag, ]   # 빈 결과 → 음수 없음 ✅

실전 데이터 전처리 — uriage.csv

14. 데이터 정형화 — 대소문자 & 공백 제거

uriage = pd.read_csv('../csv/uriage.csv')
uriage.info()
uriage.isna().sum()

item_name 컬럼에 두 가지 문제가 있습니다.

문제예시해결 방법
대소문자 혼재itemA, ITEMAstr.upper() 또는 str.lower()
문자 사이 공백item A, itemAstr.replace(' ', '')

⚠️ strip()문자열 앞뒤 공백만 제거합니다.
문자 사이의 공백replace(' ', '')를 사용해야 합니다.

방법 1 — for문

df = uriage.copy()

for i in range(len(df)):
    df.iloc[i, 1] = df.iloc[i, 1].replace(' ', '')

방법 2 — map() + lambda

df['item_name'] = df['item_name'].map(lambda x: x.replace(' ', ''))

방법 3 — str 메서드 체이닝 ✅ 권장

# 공백 제거 + 대문자 통일을 한 번에
uriage['item_name'] = uriage['item_name'].str.upper().str.replace(' ', '')

💡 str 메서드는 체이닝(.str.upper().str.replace())이 가능합니다.
단, .str 접근자를 각 메서드마다 붙여야 합니다.

# 정형화 결과 확인
uriage['item_name'].unique()          # 고유값 확인
uriage['item_name'].value_counts()    # 빈도수 확인


15. 결측치 채우기 — 상품별 평균가격

item_price의 결측치를 같은 상품의 평균가격으로 채웁니다.

단일 상품 처리

flag_A = uriage['item_name'] == '상품A'
flag_null = uriage['item_price'].isna()

# 상품A이고 가격이 있는 데이터의 평균
mean_price = uriage.loc[flag_A & ~flag_null, 'item_price'].mean()

# 상품A이고 가격이 결측치인 데이터에 평균 대입
uriage.loc[flag_A & flag_null, 'item_price'] = uriage.loc[
    flag_A & flag_null, 'item_price'
].fillna(mean_price)

전체 상품 반복 처리 ✅ 권장

item_list = uriage['item_name'].unique()   # 전체 상품 목록
flag_null = uriage['item_price'].isna()    # 결측치 조건 (공통)

for item_name in item_list:
    flag = uriage['item_name'] == item_name
    mean_price = uriage.loc[flag & ~flag_null, 'item_price'].mean()
    uriage.loc[flag & flag_null, 'item_price'] = mean_price

# 결측치 확인
uriage['item_price'].isna().sum()   # 0 → 결측치 없음 ✅

상품별 평균가격 확인

for i in range(len(item_list)):
    flag = uriage['item_name'] == item_list[i]
    mean_price = uriage.loc[flag, 'item_price'].mean()
    print(f'{item_list[i]} : {mean_price}')


📎 핵심 함수 요약

함수/문법설명
value_counts()값별 빈도수 확인
unique()고유값 목록 확인
Series.map(func)각 원소에 함수 적용
Series.map(dict)딕셔너리로 값 치환
Series.str.메서드Series에서 문자열 함수 접근
map(na_action='ignore')결측치는 건너뛰고 함수 적용
replace(dict)딕셔너리로 값 치환
sort_values()값 기준 정렬
reset_index(drop=True)인덱스 초기화
shift(n)데이터를 n칸 이동
diff(n)n칸 이전 값과의 차이 계산
[... for ... if ...]리스트 컴프리헨션
profile
공부 기록

0개의 댓글