4. Pandas 데이터 조작
4. Pandas 데이터 조작¶
개요¶
Pandas의 핵심 데이터 조작 기법인 필터링, 정렬, 그룹화, 병합에 대해 다룹니다. 이 기법들은 실무 데이터 분석에서 가장 많이 사용됩니다.
1. 데이터 필터링¶
1.1 조건 필터링¶
import pandas as pd
import numpy as np
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve'],
'age': [25, 30, 35, 28, 32],
'department': ['Sales', 'IT', 'IT', 'HR', 'Sales'],
'salary': [50000, 60000, 70000, 55000, 65000]
})
# 단일 조건
print(df[df['age'] > 30])
print(df[df['department'] == 'IT'])
# 복합 조건 (AND: &, OR: |)
print(df[(df['age'] > 25) & (df['salary'] >= 60000)])
print(df[(df['department'] == 'IT') | (df['department'] == 'Sales')])
# NOT 조건
print(df[~(df['department'] == 'HR')])
# 범위 조건
print(df[df['age'].between(25, 30)]) # 25 <= age <= 30
1.2 isin을 이용한 필터링¶
# 여러 값 중 하나와 일치
departments = ['IT', 'Sales']
print(df[df['department'].isin(departments)])
# 일치하지 않는 경우
print(df[~df['department'].isin(departments)])
1.3 문자열 조건¶
# 문자열 포함
print(df[df['name'].str.contains('a', case=False)])
# 시작/끝 문자열
print(df[df['name'].str.startswith('A')])
print(df[df['name'].str.endswith('e')])
# 정규 표현식
print(df[df['name'].str.match(r'^[A-C]')]) # A, B, C로 시작
1.4 query 메서드¶
# SQL 스타일 쿼리
print(df.query('age > 30'))
print(df.query('department == "IT"'))
print(df.query('age > 25 and salary >= 60000'))
# 변수 사용
min_age = 30
print(df.query('age >= @min_age'))
# 인덱스 참조
df_indexed = df.set_index('name')
print(df_indexed.query('index == "Alice"'))
1.5 결측값 필터링¶
df_with_na = pd.DataFrame({
'A': [1, 2, None, 4],
'B': [None, 2, 3, 4],
'C': [1, 2, 3, None]
})
# 결측값이 있는 행
print(df_with_na[df_with_na['A'].isna()])
# 결측값이 없는 행
print(df_with_na[df_with_na['A'].notna()])
# 모든 열에 결측값이 없는 행
print(df_with_na.dropna())
# 특정 열에 결측값이 없는 행
print(df_with_na.dropna(subset=['A', 'B']))
2. 정렬¶
2.1 값 기준 정렬¶
df = pd.DataFrame({
'name': ['Charlie', 'Alice', 'Bob', 'Diana'],
'age': [35, 25, 30, 25],
'score': [85, 95, 75, 90]
})
# 단일 열 정렬
print(df.sort_values('age'))
print(df.sort_values('age', ascending=False))
# 여러 열 정렬
print(df.sort_values(['age', 'score']))
print(df.sort_values(['age', 'score'], ascending=[True, False]))
# 결측값 위치
df_na = df.copy()
df_na.loc[0, 'age'] = None
print(df_na.sort_values('age', na_position='first')) # 결측값 맨 앞
print(df_na.sort_values('age', na_position='last')) # 결측값 맨 뒤
# inplace 정렬
df.sort_values('age', inplace=True)
2.2 인덱스 정렬¶
df = pd.DataFrame({
'value': [10, 20, 30, 40]
}, index=['d', 'b', 'c', 'a'])
# 인덱스 오름차순
print(df.sort_index())
# 인덱스 내림차순
print(df.sort_index(ascending=False))
# 열 인덱스 정렬
df_wide = pd.DataFrame({
'C': [1, 2],
'A': [3, 4],
'B': [5, 6]
})
print(df_wide.sort_index(axis=1))
2.3 순위 매기기¶
df = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie', 'Diana'],
'score': [85, 95, 85, 90]
})
# 기본 순위 (동점 시 평균)
df['rank'] = df['score'].rank(ascending=False)
print(df)
# 순위 방법
# method='average': 평균 순위 (기본값)
# method='min': 최소 순위
# method='max': 최대 순위
# method='first': 먼저 나온 순서대로
# method='dense': 밀집 순위 (간격 없음)
df['rank_min'] = df['score'].rank(ascending=False, method='min')
df['rank_dense'] = df['score'].rank(ascending=False, method='dense')
print(df)
3. 그룹화 (GroupBy)¶
3.1 기본 그룹화¶
df = pd.DataFrame({
'department': ['Sales', 'IT', 'IT', 'HR', 'Sales', 'HR'],
'name': ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank'],
'salary': [50000, 60000, 70000, 55000, 65000, 52000],
'bonus': [5000, 8000, 10000, 6000, 7000, 5500]
})
# 그룹화
grouped = df.groupby('department')
# 그룹 확인
print(grouped.groups)
print(grouped.ngroups) # 그룹 수
# 특정 그룹 가져오기
print(grouped.get_group('IT'))
3.2 집계 함수¶
# 단일 집계
print(df.groupby('department')['salary'].mean())
print(df.groupby('department')['salary'].sum())
# 여러 열 집계
print(df.groupby('department')[['salary', 'bonus']].mean())
# 여러 집계 함수
print(df.groupby('department')['salary'].agg(['mean', 'sum', 'count']))
# 사용자 정의 함수
print(df.groupby('department')['salary'].agg(lambda x: x.max() - x.min()))
3.3 agg 메서드¶
# 열마다 다른 집계
agg_result = df.groupby('department').agg({
'salary': ['mean', 'max'],
'bonus': 'sum',
'name': 'count'
})
print(agg_result)
# 이름 지정
agg_result = df.groupby('department').agg(
avg_salary=('salary', 'mean'),
max_salary=('salary', 'max'),
total_bonus=('bonus', 'sum'),
employee_count=('name', 'count')
)
print(agg_result)
3.4 transform과 apply¶
# transform: 원본과 같은 크기의 결과
df['dept_avg_salary'] = df.groupby('department')['salary'].transform('mean')
print(df)
# 그룹 내 정규화
df['salary_normalized'] = df.groupby('department')['salary'].transform(
lambda x: (x - x.mean()) / x.std()
)
# apply: 유연한 그룹 연산
def top_n(group, n=2, column='salary'):
return group.nlargest(n, column)
print(df.groupby('department').apply(top_n, n=1))
3.5 여러 열로 그룹화¶
df = pd.DataFrame({
'year': [2022, 2022, 2023, 2023, 2022, 2023],
'quarter': ['Q1', 'Q2', 'Q1', 'Q2', 'Q1', 'Q1'],
'sales': [100, 150, 120, 180, 110, 130]
})
# 다중 열 그룹화
print(df.groupby(['year', 'quarter'])['sales'].sum())
# 결과를 DataFrame으로
print(df.groupby(['year', 'quarter'])['sales'].sum().reset_index())
# 언스택
print(df.groupby(['year', 'quarter'])['sales'].sum().unstack())
3.6 필터링¶
df = pd.DataFrame({
'department': ['Sales', 'IT', 'IT', 'HR', 'Sales'],
'salary': [50000, 60000, 70000, 55000, 65000]
})
# 조건을 만족하는 그룹만 필터링
# 평균 급여가 55000 이상인 부서
result = df.groupby('department').filter(lambda x: x['salary'].mean() >= 55000)
print(result)
4. 데이터 병합¶
4.1 merge (SQL 스타일 조인)¶
# 예제 데이터
employees = pd.DataFrame({
'emp_id': [1, 2, 3, 4],
'name': ['Alice', 'Bob', 'Charlie', 'Diana'],
'dept_id': [10, 20, 10, 30]
})
departments = pd.DataFrame({
'dept_id': [10, 20, 40],
'dept_name': ['Sales', 'IT', 'Marketing']
})
# 내부 조인 (기본값)
result = pd.merge(employees, departments, on='dept_id')
print(result)
# 왼쪽 조인
result = pd.merge(employees, departments, on='dept_id', how='left')
print(result)
# 오른쪽 조인
result = pd.merge(employees, departments, on='dept_id', how='right')
print(result)
# 외부 조인
result = pd.merge(employees, departments, on='dept_id', how='outer')
print(result)
4.2 다른 열 이름으로 조인¶
employees = pd.DataFrame({
'emp_id': [1, 2, 3],
'name': ['Alice', 'Bob', 'Charlie'],
'department': [10, 20, 10]
})
departments = pd.DataFrame({
'id': [10, 20],
'dept_name': ['Sales', 'IT']
})
# 다른 열 이름으로 조인
result = pd.merge(employees, departments,
left_on='department', right_on='id')
print(result)
4.3 인덱스 기반 조인¶
employees = pd.DataFrame({
'name': ['Alice', 'Bob', 'Charlie'],
'salary': [50000, 60000, 70000]
}, index=[1, 2, 3])
bonuses = pd.DataFrame({
'bonus': [5000, 8000, 10000]
}, index=[1, 2, 4])
# 인덱스로 조인
result = pd.merge(employees, bonuses, left_index=True, right_index=True, how='outer')
print(result)
# join 메서드 (인덱스 기반)
result = employees.join(bonuses, how='outer')
print(result)
4.4 concat (연결)¶
df1 = pd.DataFrame({
'A': [1, 2],
'B': [3, 4]
})
df2 = pd.DataFrame({
'A': [5, 6],
'B': [7, 8]
})
# 수직 연결 (행 방향)
result = pd.concat([df1, df2])
print(result)
# 인덱스 재설정
result = pd.concat([df1, df2], ignore_index=True)
print(result)
# 수평 연결 (열 방향)
result = pd.concat([df1, df2], axis=1)
print(result)
# 키 추가
result = pd.concat([df1, df2], keys=['first', 'second'])
print(result)
4.5 append (행 추가) - deprecated in pandas 2.0¶
# concat 사용 권장
new_row = pd.DataFrame({'A': [9], 'B': [10]})
result = pd.concat([df1, new_row], ignore_index=True)
5. 피벗과 멜트¶
5.1 pivot¶
df = pd.DataFrame({
'date': ['2023-01', '2023-01', '2023-02', '2023-02'],
'city': ['Seoul', 'Busan', 'Seoul', 'Busan'],
'sales': [100, 80, 120, 90]
})
# 피벗 테이블
pivot = df.pivot(index='date', columns='city', values='sales')
print(pivot)
# Busan Seoul
# date
# 2023-01 80 100
# 2023-02 90 120
5.2 pivot_table¶
df = pd.DataFrame({
'date': ['2023-01', '2023-01', '2023-01', '2023-02'],
'city': ['Seoul', 'Seoul', 'Busan', 'Seoul'],
'category': ['A', 'B', 'A', 'A'],
'sales': [100, 150, 80, 120]
})
# 집계 함수 적용
pivot = pd.pivot_table(df, values='sales', index='date',
columns='city', aggfunc='sum')
print(pivot)
# 여러 집계 함수
pivot = pd.pivot_table(df, values='sales', index='date',
columns='city', aggfunc=['sum', 'mean'])
print(pivot)
# 여러 인덱스
pivot = pd.pivot_table(df, values='sales',
index=['date', 'category'],
columns='city',
aggfunc='sum',
fill_value=0)
print(pivot)
# 마진 추가
pivot = pd.pivot_table(df, values='sales', index='date',
columns='city', aggfunc='sum', margins=True)
print(pivot)
5.3 melt (언피벗)¶
df_wide = pd.DataFrame({
'name': ['Alice', 'Bob'],
'math': [90, 85],
'english': [80, 95],
'science': [85, 90]
})
# Wide → Long 변환
df_long = pd.melt(df_wide,
id_vars=['name'],
value_vars=['math', 'english', 'science'],
var_name='subject',
value_name='score')
print(df_long)
# name subject score
# 0 Alice math 90
# 1 Bob math 85
# 2 Alice english 80
# 3 Bob english 95
# 4 Alice science 85
# 5 Bob science 90
5.4 stack과 unstack¶
df = pd.DataFrame({
'A': [1, 2],
'B': [3, 4]
}, index=['x', 'y'])
# stack: 열을 행으로
stacked = df.stack()
print(stacked)
# x A 1
# B 3
# y A 2
# B 4
# unstack: 행을 열로
unstacked = stacked.unstack()
print(unstacked)
6. 중복 처리¶
df = pd.DataFrame({
'A': [1, 1, 2, 2, 3],
'B': ['a', 'a', 'b', 'c', 'c'],
'C': [10, 10, 20, 30, 40]
})
# 중복 확인
print(df.duplicated())
print(df.duplicated(subset=['A', 'B']))
print(df[df.duplicated(keep=False)]) # 모든 중복 행
# 중복 개수
print(df.duplicated().sum())
# 중복 제거
print(df.drop_duplicates())
print(df.drop_duplicates(subset=['A']))
print(df.drop_duplicates(subset=['A'], keep='last')) # 마지막 유지
print(df.drop_duplicates(subset=['A'], keep=False)) # 모든 중복 제거
7. 교차 테이블¶
df = pd.DataFrame({
'gender': ['M', 'F', 'M', 'F', 'M', 'F'],
'department': ['Sales', 'IT', 'IT', 'Sales', 'HR', 'IT'],
'salary': [50000, 60000, 55000, 58000, 52000, 62000]
})
# 빈도 교차 테이블
ct = pd.crosstab(df['gender'], df['department'])
print(ct)
# 마진 추가
ct = pd.crosstab(df['gender'], df['department'], margins=True)
print(ct)
# 정규화
ct = pd.crosstab(df['gender'], df['department'], normalize=True)
print(ct)
# 집계 함수 적용
ct = pd.crosstab(df['gender'], df['department'],
values=df['salary'], aggfunc='mean')
print(ct)
연습 문제¶
문제 1: 그룹별 통계¶
부서별 평균 급여와 직원 수를 구하세요.
df = pd.DataFrame({
'department': ['Sales', 'IT', 'IT', 'HR', 'Sales'],
'name': ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve'],
'salary': [50000, 60000, 70000, 55000, 65000]
})
# 풀이
result = df.groupby('department').agg(
avg_salary=('salary', 'mean'),
count=('name', 'count')
)
print(result)
문제 2: 데이터 병합¶
두 DataFrame을 조인하여 직원의 부서명을 포함하세요.
employees = pd.DataFrame({
'emp_id': [1, 2, 3],
'name': ['Alice', 'Bob', 'Charlie'],
'dept_id': [10, 20, 10]
})
departments = pd.DataFrame({
'dept_id': [10, 20],
'dept_name': ['Sales', 'IT']
})
# 풀이
result = pd.merge(employees, departments, on='dept_id')
print(result)
문제 3: 피벗 테이블¶
월별, 카테고리별 매출 합계를 피벗 테이블로 만드세요.
sales = pd.DataFrame({
'month': ['Jan', 'Jan', 'Feb', 'Feb', 'Jan', 'Feb'],
'category': ['A', 'B', 'A', 'B', 'A', 'A'],
'amount': [100, 150, 120, 180, 110, 130]
})
# 풀이
pivot = pd.pivot_table(sales, values='amount',
index='month', columns='category',
aggfunc='sum')
print(pivot)
요약¶
| 기능 | 함수/메서드 |
|---|---|
| 필터링 | df[condition], query(), isin() |
| 정렬 | sort_values(), sort_index(), rank() |
| 그룹화 | groupby(), agg(), transform(), apply() |
| 병합 | merge(), join(), concat() |
| 피벗 | pivot(), pivot_table(), melt(), stack(), unstack() |
| 중복 | duplicated(), drop_duplicates() |
| 교차표 | crosstab() |