포스트

[Python 100일 챌린지] Day 65 - Pandas 데이터 조작

[Python 100일 챌린지] Day 65 - Pandas 데이터 조작

데이터를 자유자재로 다뤄봅시다! 🔧

필터링, 정렬, 열 추가/삭제… 실무에서 매일 쓰는 데이터 조작 기법을 배워요! 이것만 알면 웬만한 데이터 전처리는 가능합니다! 💪

(30-40분 완독 ⭐⭐)

🎯 오늘의 학습 목표

📚 사전 지식


🎯 학습 목표 1: 데이터 필터링하기

💡 필터링이란?

비유: 도서관에서 “파이썬 책만 찾아줘”라고 하는 것과 같아요! 📚

전체 데이터에서 원하는 조건에 맞는 데이터만 골라내는 거예요.

1.1 조건으로 필터링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import pandas as pd

df = pd.DataFrame({
    '이름': ['철수', '영희', '민수', '지영', '현수'],
    '나이': [20, 22, 21, 23, 20],
    '점수': [85, 90, 78, 92, 88],
    '학과': ['컴퓨터', '경영', '컴퓨터', '경영', '전자']
})

# 단일 조건
high_score = df[df['점수'] >= 85]
print(high_score)

# 여러 조건 (AND)
result = df[(df['점수'] >= 85) & (df['나이'] >= 21)]
print(result)

# 여러 조건 (OR)
result = df[(df['학과'] == '컴퓨터') | (df['학과'] == '경영')]
print(result)

💡 조건 연결 기호 정리

기호 의미 예시 설명
& AND (그리고) 조건1 & 조건2 둘 다 만족해야 함
\| OR (또는) 조건1 \| 조건2 하나만 만족해도 됨
~ NOT (부정) ~조건 조건의 반대

⚠️ 주의: 여러 조건을 쓸 때는 각 조건을 괄호 ()로 감싸야 해요!

  • df[(조건1) & (조건2)]
  • df[조건1 & 조건2] ← 오류 발생!
1
2
3
# 여러 조건 예제 (참고용)
# NOT 연산: 점수가 80점 이상이 "아닌" 학생
low_score = df[~(df['점수'] >= 80)]  # 80점 미만

1.2 isin()으로 필터링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd

df = pd.DataFrame({
    '이름': ['철수', '영희', '민수', '지영', '현수'],
    '학과': ['컴퓨터', '경영', '컴퓨터', '경영', '전자']
})

# 특정 값들 중 하나와 일치하는 행
result = df[df['학과'].isin(['컴퓨터', '전자'])]
print(result)

# 포함되지 않는 행
result = df[~df['학과'].isin(['경영'])]
print(result)

1.3 문자열 조건

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas as pd

df = pd.DataFrame({
    '이름': ['김철수', '이영희', '박민수', '김지영', '최현수'],
    '이메일': ['kim@a.com', 'lee@b.com', 'park@a.com', 'kim2@c.com', 'choi@b.com']
})

# 특정 문자 포함
result = df[df['이름'].str.contains('')]
print(result)

# 특정 문자로 시작
result = df[df['이메일'].str.startswith('kim')]
print(result)

# 특정 문자로 끝남
result = df[df['이메일'].str.endswith('.com')]
print(result)

1.4 query() 메서드

💡 query()가 편한 이유?

방식 코드 특징
기본 방식 df[(df['점수'] >= 85) & (df['나이'] >= 21)] 길고 복잡함
query() df.query('점수 >= 85 and 나이 >= 21') SQL처럼 직관적!

조건이 많을수록 query()훨씬 읽기 쉬워요!

1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd

df = pd.DataFrame({
    '이름': ['철수', '영희', '민수'],
    '나이': [20, 22, 21],
    '점수': [85, 90, 78]
})

# SQL 스타일 쿼리
result = df.query('점수 >= 85')
result = df.query('나이 >= 21 and 점수 >= 80')
result = df.query('이름 == "철수"')
print(result)

💡 query() 안에서는 and, or 사용!

  • 기본 필터링: &, | (기호)
  • query(): and, or (영어 단어)

🎯 학습 목표 2: 데이터 정렬하기

💡 정렬 용어 정리

용어 영어 순서 예시
오름차순 ascending 작은 → 큰 1, 2, 3 또는 A, B, C
내림차순 descending 큰 → 작은 3, 2, 1 또는 C, B, A

기본값은 오름차순이에요!

2.1 단일 열 정렬

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd

df = pd.DataFrame({
    '이름': ['철수', '영희', '민수', '지영'],
    '점수': [85, 90, 78, 92]
})

# 오름차순 정렬
sorted_df = df.sort_values('점수')
print(sorted_df)

# 내림차순 정렬
sorted_df = df.sort_values('점수', ascending=False)
print(sorted_df)

2.2 여러 열 정렬

1
2
3
4
5
6
7
8
9
10
11
import pandas as pd

df = pd.DataFrame({
    '학과': ['컴퓨터', '경영', '컴퓨터', '경영'],
    '이름': ['철수', '영희', '민수', '지영'],
    '점수': [85, 90, 78, 92]
})

# 학과로 먼저 정렬, 같으면 점수로 정렬
sorted_df = df.sort_values(['학과', '점수'], ascending=[True, False])
print(sorted_df)

2.3 인덱스 정렬

1
2
3
4
5
6
7
8
9
import pandas as pd

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

# 인덱스 기준 정렬
sorted_df = df.sort_index()
print(sorted_df)

🎯 학습 목표 3: 열 추가/수정/삭제하기

3.1 열 추가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import pandas as pd

df = pd.DataFrame({
    '이름': ['철수', '영희', '민수'],
    '국어': [85, 90, 78],
    '영어': [90, 85, 92]
})

# 새 열 추가 (계산)
df['총점'] = df['국어'] + df['영어']
df['평균'] = df['총점'] / 2

# 조건에 따른 값
df['합격'] = df['평균'] >= 80

print(df)

3.2 열 수정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd

df = pd.DataFrame({
    '이름': ['철수', '영희', '민수'],
    '점수': [85, 90, 78]
})

# 전체 열 수정
df['점수'] = df['점수'] + 5  # 전체 5점 추가

# 조건부 수정 (loc 사용 권장!)
df.loc[df['점수'] < 80, '점수'] = 80  # 80점 미만은 80점으로

print(df)

⚠️ 조건부 수정 시 loc 사용하기: df[조건]['열'] = 값 대신 df.loc[조건, '열'] = 값을 사용하세요! 전자는 경고(SettingWithCopyWarning)가 발생할 수 있어요.

3.3 열 삭제

💡 axis란?

Pandas에서 방향을 나타내는 값이에요:

axis 의미 시각적 방향
axis=0 행 (row) ↓ 세로 방향
axis=1 열 (column) → 가로 방향

열을 삭제하려면 axis=1!

1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd

# 예제 1: 단일 열 삭제
df1 = pd.DataFrame({
    '이름': ['철수', '영희'],
    '나이': [20, 22],
    '임시': [1, 2]
})

df1 = df1.drop('임시', axis=1)
# 또는 (더 직관적인 방법)
# df1 = df1.drop(columns=['임시'])
print(df1)
1
2
3
4
5
6
7
8
9
10
11
12
import pandas as pd

# 예제 2: 여러 열 삭제
df2 = pd.DataFrame({
    '이름': ['철수', '영희'],
    '나이': [20, 22],
    '점수': [85, 90],
    '임시': [1, 2]
})

df2 = df2.drop(columns=['나이', '임시'])
print(df2)  # 이름, 점수만 남음

3.4 열 이름 변경

1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd

df = pd.DataFrame({
    'name': ['철수', '영희'],
    'age': [20, 22]
})

# 이름 변경
df = df.rename(columns={'name': '이름', 'age': '나이'})
print(df)

# 전체 열 이름 변경
df.columns = ['이름', '나이']

🎯 학습 목표 4: 결측값 처리하기

💡 결측값(Missing Value)이란?

비유: 설문조사에서 “응답 없음”과 같아요! 📋

데이터에 값이 없는 것을 말해요. Pandas에서는 NaN (Not a Number) 또는 None으로 표시돼요.

1
2
3
예: 학생 명단에서 전화번호를 안 적은 학생
이름: 철수, 전화번호: 010-1234-5678
이름: 영희, 전화번호: NaN  ← 결측값!

결측값은 분석 결과를 왜곡할 수 있어서, 적절히 처리해야 해요!

4.1 결측값 확인

1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd
import numpy as np

df = pd.DataFrame({
    '이름': ['철수', '영희', '민수', None],
    '나이': [20, np.nan, 21, 22],
    '점수': [85, 90, np.nan, 92]
})

# 결측값 확인
print(df.isnull())       # True/False 표시
print(df.isnull().sum()) # 열별 결측값 개수
print(df.isnull().sum().sum())  # 전체 결측값 개수

4.2 결측값 삭제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pandas as pd
import numpy as np

df = pd.DataFrame({
    '이름': ['철수', '영희', '민수', None],
    '나이': [20, np.nan, 21, 22],
    '점수': [85, 90, np.nan, 92]
})

# 결측값이 있는 행 삭제
df_cleaned = df.dropna()
print(df_cleaned)

# 특정 열에 결측값이 있는 행만 삭제
df_cleaned = df.dropna(subset=['이름'])

# 모든 값이 결측인 행만 삭제
df_cleaned = df.dropna(how='all')

4.3 결측값 채우기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd
import numpy as np

df = pd.DataFrame({
    '이름': ['철수', '영희', '민수'],
    '점수': [85, np.nan, 78]
})

# 특정 값으로 채우기
df_filled = df.fillna(0)

# 평균으로 채우기
df_filled = df.fillna(df['점수'].mean())

# 앞의 값으로 채우기 (forward fill)
df_filled = df.ffill()  # Pandas 2.0+ 권장 방식

# 뒤의 값으로 채우기 (backward fill)
df_filled = df.bfill()  # Pandas 2.0+ 권장 방식

print(df_filled)

💡 Pandas 2.0+ 참고: fillna(method='ffill')은 deprecated 되었어요. 대신 df.ffill(), df.bfill()을 사용하세요!

💡 결측값 처리 방법 선택 가이드

상황 권장 방법 이유
결측값이 적을 때 dropna() 데이터 손실이 적음
결측값이 많을 때 fillna(평균/중앙값) 데이터 보존
시계열 데이터 ffill() / bfill() 연속성 유지
카테고리 데이터 fillna('없음') 명시적 표시

💡 실전 팁

✅ 체이닝 (연속 작업)

💡 체이닝이란?

비유: 세탁기 → 건조기 → 옷장 순서로 한 번에 처리하는 것! 🧺

여러 작업을 점(.)으로 연결해서 한 줄에 처리하는 방식이에요.

  • 장점: 코드가 깔끔하고 중간 변수가 필요 없어요
  • 단점: 너무 길면 읽기 어려울 수 있어요
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pandas as pd

df = pd.DataFrame({
    '이름': ['철수', '영희', '민수', '지영'],
    '점수': [85, 90, 78, 92],
    '학과': ['컴퓨터', '경영', '컴퓨터', '경영']
})

# 메서드 체이닝으로 깔끔하게
result = (df
    .query('점수 >= 80')           # 1) 80점 이상만
    .sort_values('점수', ascending=False)  # 2) 점수 내림차순
    .reset_index(drop=True)         # 3) 인덱스 새로 매기기
)
print(result)

💡 reset_index(drop=True)란?

필터링/정렬 후 인덱스가 뒤죽박죽이 돼요:

1
2
원래: 0, 1, 2, 3, 4
필터 후: 0, 1, 3, 4  ← 2가 빠짐!

reset_index(drop=True)0, 1, 2, 3처럼 깔끔하게 다시 매겨요!

  • drop=True: 원래 인덱스는 버림
  • drop=False: 원래 인덱스를 새 열로 추가

🧪 연습 문제

문제: 데이터 전처리 실습

주어진 데이터에서 1) 점수가 80점 이상인 학생만 필터링, 2) 점수 기준 내림차순 정렬, 3) ‘등급’ 열 추가(90점 이상 A, 80점 이상 B)

1
2
3
4
df = pd.DataFrame({
    '이름': ['철수', '영희', '민수', '지영', '현수'],
    '점수': [85, 90, 78, 92, 88]
})
💡 힌트 보기

단계별 접근법:

  1. 80점 이상 필터링
    • df[df['점수'] >= 80] 형태로 조건 필터링
  2. 내림차순 정렬
    • sort_values('점수', ascending=False) 사용
  3. 등급 추가하기
    • 방법 1: np.where() 사용 - np.where(조건, 참일때값, 거짓일때값)
    • 방법 2: apply() + 람다 함수 사용

핵심 코드 구조:

1
2
3
# 필터링 → 정렬 → 새 열 추가
result = df[조건].sort_values(...).copy()
result['등급'] = np.where(...)
정답 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd
import numpy as np

df = pd.DataFrame({
    '이름': ['철수', '영희', '민수', '지영', '현수'],
    '점수': [85, 90, 78, 92, 88]
})

# 1. 80점 이상 필터링
result = df[df['점수'] >= 80]

# 2. 점수 내림차순 정렬
result = result.sort_values('점수', ascending=False)

# 3. 등급 추가
result['등급'] = np.where(result['점수'] >= 90, 'A', 'B')

# 인덱스 리셋
result = result.reset_index(drop=True)

print(result)

📝 오늘 배운 내용 정리

  1. 필터링: df[조건], df.query(), isin()
  2. 정렬: sort_values(), sort_index()
  3. 열 조작: 추가(df['새열']=값), 삭제(drop()), 이름변경(rename())
  4. 결측값: isnull(), dropna(), fillna()

🔗 관련 자료


📚 이전 학습

Day 64: Pandas 데이터 로딩 ⭐⭐

어제는 CSV, Excel 파일 읽기/쓰기를 배웠어요!

📚 다음 학습

Day 66: Pandas 그룹화와 집계 ⭐⭐


“늦었다고 생각할 때가 가장 빠른 때입니다. 오늘도 한 걸음 성장했어요!” 🚀

Day 65/100 Phase 7: 데이터 분석 기초 #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.