데이터 분석을 시작하려고 Pandas를 처음 봤을 때, DataFrame이 뭔지도 모르고 인덱싱 방법도 헷갈려서 한참 고생했던 기억이 있습니다. 엑셀은 쉬운데 왜 코드로는 이렇게 어려울까 싶었죠. 실제로 데이터 분석 입문자의 76%가 Pandas 문법에서 첫 번째 장벽을 느낀다는 조사 결과가 있습니다.
하지만 Pandas를 마스터하면 엑셀로 몇 시간 걸리던 작업을 몇 분 만에 끝낼 수 있습니다. 이 글에서는 Pandas의 모든 것을 실전 예제와 함께 완벽 정리했습니다.
Pandas가 뭐길래? 3분 만에 이해하기
Pandas는 데이터 분석을 위한 파이썬 라이브러리입니다. 엑셀처럼 테이블 형태로 데이터를 다루지만, 훨씬 강력하고 빠릅니다.
Pandas를 배워야 하는 이유
- 엑셀보다 100배 빠름: 백만 행도 1초 만에 처리
- 자동화 가능: 매일 반복하는 작업을 코드 한 줄로
- 데이터 과학의 필수: 머신러닝, AI의 시작점
- 취업 경쟁력: 데이터 직군 필수 스킬
설치하기
pip install pandas numpy matplotlib
numpy와 matplotlib도 함께 설치하면 데이터 분석 환경이 완성됩니다.
Series와 DataFrame 이해하기
Series: 1차원 데이터
import pandas as pd
# 리스트에서 생성
s = pd.Series([10, 20, 30, 40, 50])
print(s)
# 0 10
# 1 20
# 2 30
# 3 40
# 4 50
# 인덱스 지정
s = pd.Series([10, 20, 30], index=['a', 'b', 'c'])
print(s['b']) # 20
Series는 딕셔너리와 비슷하지만 더 강력합니다.
DataFrame: 2차원 테이블
# 딕셔너리에서 생성
data = {
'이름': ['홍길동', '김철수', '이영희'],
'나이': [25, 30, 28],
'직업': ['개발자', '디자이너', '기획자']
}
df = pd.DataFrame(data)
print(df)
# 이름 나이 직업
# 0 홍길동 25 개발자
# 1 김철수 30 디자이너
# 2 이영희 28 기획자
엑셀 시트를 코드로 옮겨놓은 것과 같습니다.
데이터 불러오기와 저장하기
CSV 파일 읽기
# 기본 읽기
df = pd.read_csv('data.csv')
# 인코딩 지정 (한글 깨짐 방지)
df = pd.read_csv('data.csv', encoding='utf-8-sig')
# 특정 열만 읽기
df = pd.read_csv('data.csv', usecols=['이름', '나이'])
# 첫 1000줄만 읽기
df = pd.read_csv('data.csv', nrows=1000)
엑셀 파일 읽기
# 엑셀 읽기 (openpyxl 필요: pip install openpyxl)
df = pd.read_excel('data.xlsx')
# 특정 시트 읽기
df = pd.read_excel('data.xlsx', sheet_name='Sheet2')
# 여러 시트 읽기
dfs = pd.read_excel('data.xlsx', sheet_name=None) # 딕셔너리로 반환
파일 저장하기
# CSV로 저장
df.to_csv('output.csv', index=False, encoding='utf-8-sig')
# 엑셀로 저장
df.to_excel('output.xlsx', index=False)
# JSON으로 저장
df.to_json('output.json', orient='records', force_ascii=False)
꿀팁: index=False를 쓰면 불필요한 인덱스 열이 저장되지 않습니다.
데이터 둘러보기 (탐색적 데이터 분석)
기본 정보 확인
# 처음 5줄
df.head()
# 마지막 5줄
df.tail()
# 데이터 크기
df.shape # (행 수, 열 수)
# 컬럼 이름
df.columns
# 데이터 타입
df.dtypes
# 기본 통계
df.describe()
# 전체 정보
df.info()
데이터를 받으면 제일 먼저 하는 작업들입니다.
빠른 통계 확인
# 평균
df['나이'].mean()
# 중앙값
df['나이'].median()
# 최댓값, 최솟값
df['나이'].max()
df['나이'].min()
# 표준편차
df['나이'].std()
# 각 값의 개수
df['직업'].value_counts()
데이터 선택하기 (인덱싱과 슬라이싱)
열 선택
# 한 개 열
df['이름']
# 여러 열
df[['이름', '나이']]
# 조건부 선택
df[df['나이'] > 25]
# 여러 조건
df[(df['나이'] > 25) & (df['직업'] == '개발자')]
loc와 iloc
# loc: 라벨 기반 인덱싱
df.loc[0] # 첫 번째 행
df.loc[0:2] # 0~2행 (2 포함!)
df.loc[df['나이'] > 25, ['이름', '나이']] # 조건 + 열 선택
# iloc: 위치 기반 인덱싱
df.iloc[0] # 첫 번째 행
df.iloc[0:2] # 0~1행 (2 미포함)
df.iloc[0:3, 0:2] # 0~2행, 0~1열
헷갈리는 부분: loc는 끝 인덱스 포함, iloc는 미포함!
조건부 필터링 실전
# 나이가 25 이상 30 이하
df[(df['나이'] >= 25) & (df['나이'] <= 30)]
# 또는 between 사용
df[df['나이'].between(25, 30)]
# 특정 값 포함
df[df['직업'].isin(['개발자', '디자이너'])]
# 문자열 포함
df[df['이름'].str.contains('김')]
# NULL이 아닌 행
df[df['나이'].notna()]
데이터 정렬하기
# 나이 오름차순
df.sort_values('나이')
# 나이 내림차순
df.sort_values('나이', ascending=False)
# 여러 열 정렬
df.sort_values(['직업', '나이'], ascending=[True, False])
# 인덱스로 정렬
df.sort_index()
새로운 열 추가하고 수정하기
열 추가
# 단순 추가
df['부서'] = '개발팀'
# 계산해서 추가
df['내년나이'] = df['나이'] + 1
# 조건부 값 할당
df['시니어여부'] = df['나이'] > 30
# apply 사용
df['나이대'] = df['나이'].apply(lambda x: f"{x//10}0대")
열 이름 변경
# 특정 열 이름 변경
df.rename(columns={'이름': 'name', '나이': 'age'}, inplace=True)
# 모든 열 이름 변경
df.columns = ['name', 'age', 'job']
열 삭제
# 열 삭제
df.drop('직업', axis=1, inplace=True)
# 여러 열 삭제
df.drop(['직업', '부서'], axis=1, inplace=True)
주의: inplace=True를 쓰면 원본이 변경됩니다!
결측치(NaN) 처리하기
결측치 확인
# 결측치 개수
df.isnull().sum()
# 결측치 비율
df.isnull().mean() * 100
# 결측치 시각화
import matplotlib.pyplot as plt
df.isnull().sum().plot(kind='bar')
plt.show()
결측치 처리
# 결측치 제거
df.dropna() # 하나라도 NaN이 있는 행 제거
df.dropna(subset=['나이']) # 특정 열의 NaN만 제거
# 결측치 채우기
df.fillna(0) # 0으로 채우기
df.fillna(df.mean()) # 평균으로 채우기
df['나이'].fillna(df['나이'].median(), inplace=True) # 중앙값
# 앞/뒤 값으로 채우기
df.fillna(method='ffill') # 앞 값으로
df.fillna(method='bfill') # 뒤 값으로
그룹화와 집계 (GroupBy)
기본 그룹화
# 직업별 평균 나이
df.groupby('직업')['나이'].mean()
# 여러 통계량
df.groupby('직업')['나이'].agg(['mean', 'min', 'max', 'count'])
# 여러 열 그룹화
df.groupby(['부서', '직업'])['나이'].mean()
실전 예제: 매출 분석
# 샘플 데이터
sales = pd.DataFrame({
'날짜': pd.date_range('2025-01-01', periods=100),
'제품': ['A', 'B', 'C'] * 33 + ['A'],
'매출': np.random.randint(100, 1000, 100),
'지역': ['서울', '부산'] * 50
})
# 제품별 총 매출
sales.groupby('제품')['매출'].sum()
# 지역별, 제품별 평균 매출
sales.groupby(['지역', '제품'])['매출'].mean()
# 여러 집계 함수 동시 적용
sales.groupby('제품').agg({
'매출': ['sum', 'mean', 'count'],
'지역': 'nunique' # 고유값 개수
})
데이터 병합하기 (Merge, Join, Concat)
merge: SQL의 JOIN과 유사
# 두 데이터프레임
df1 = pd.DataFrame({
'사번': [1, 2, 3],
'이름': ['홍길동', '김철수', '이영희']
})
df2 = pd.DataFrame({
'사번': [1, 2, 4],
'부서': ['개발팀', '디자인팀', '기획팀']
})
# INNER JOIN (기본)
pd.merge(df1, df2, on='사번')
# LEFT JOIN
pd.merge(df1, df2, on='사번', how='left')
# RIGHT JOIN
pd.merge(df1, df2, on='사번', how='right')
# OUTER JOIN
pd.merge(df1, df2, on='사번', how='outer')
concat: 데이터 이어붙이기
# 세로로 합치기
df_combined = pd.concat([df1, df2], axis=0, ignore_index=True)
# 가로로 합치기
df_combined = pd.concat([df1, df2], axis=1)
Pivot Table로 데이터 요약하기
# 샘플 데이터
df = pd.DataFrame({
'날짜': ['2025-01', '2025-01', '2025-02', '2025-02'],
'제품': ['A', 'B', 'A', 'B'],
'매출': [100, 150, 200, 250],
'수량': [10, 15, 20, 25]
})
# 피벗 테이블
pivot = df.pivot_table(
values='매출',
index='날짜',
columns='제품',
aggfunc='sum',
fill_value=0 # NaN을 0으로
)
print(pivot)
# 제품 A B
# 날짜
# 2025-01 100 150
# 2025-02 200 250
엑셀 피벗 테이블과 똑같지만 코드로 자동화할 수 있습니다!
시계열 데이터 다루기
날짜 변환
# 문자열을 날짜로
df['날짜'] = pd.to_datetime(df['날짜'])
# 날짜 생성
dates = pd.date_range('2025-01-01', periods=10, freq='D')
# 연, 월, 일 추출
df['년'] = df['날짜'].dt.year
df['월'] = df['날짜'].dt.month
df['요일'] = df['날짜'].dt.day_name()
시계열 집계
# 월별 집계
df.set_index('날짜').resample('M').sum()
# 주별 평균
df.set_index('날짜').resample('W').mean()
# 이동 평균 (7일)
df['매출'].rolling(window=7).mean()
데이터 시각화 (Matplotlib 연동)
import matplotlib.pyplot as plt
# 한글 폰트 설정 (Windows)
plt.rcParams['font.family'] = 'Malgun Gothic'
plt.rcParams['axes.unicode_minus'] = False
# 선 그래프
df['매출'].plot(kind='line', title='일별 매출 추이')
plt.show()
# 막대 그래프
df.groupby('제품')['매출'].sum().plot(kind='bar')
plt.title('제품별 총 매출')
plt.show()
# 히스토그램
df['나이'].plot(kind='hist', bins=10)
plt.title('나이 분포')
plt.show()
# 박스플롯
df.boxplot(column='매출', by='지역')
plt.show()
# 산점도
df.plot(kind='scatter', x='나이', y='연봉')
plt.show()
실전 프로젝트: 고객 데이터 분석
import pandas as pd
import numpy as np
# 샘플 데이터 생성
np.random.seed(42)
customers = pd.DataFrame({
'고객ID': range(1, 1001),
'나이': np.random.randint(20, 70, 1000),
'성별': np.random.choice(['남', '여'], 1000),
'지역': np.random.choice(['서울', '경기', '부산', '대구'], 1000),
'구매액': np.random.randint(10000, 1000000, 1000),
'방문횟수': np.random.randint(1, 50, 1000)
})
# 1. 기본 통계
print("=== 기본 통계 ===")
print(customers.describe())
# 2. 나이대별 분석
customers['나이대'] = (customers['나이'] // 10) * 10
age_analysis = customers.groupby('나이대').agg({
'구매액': ['mean', 'sum', 'count'],
'방문횟수': 'mean'
}).round(0)
print("\\n=== 나이대별 분석 ===")
print(age_analysis)
# 3. 지역별 매출
region_sales = customers.groupby('지역')['구매액'].sum().sort_values(ascending=False)
print("\\n=== 지역별 총 매출 ===")
print(region_sales)
# 4. 고가치 고객 (상위 10%)
high_value = customers.nlargest(100, '구매액')
print(f"\\n=== 고가치 고객 평균 ===")
print(f"평균 구매액: {high_value['구매액'].mean():,.0f}원")
print(f"평균 방문횟수: {high_value['방문횟수'].mean():.1f}회")
# 5. 성별 차이 분석
gender_analysis = customers.groupby('성별').agg({
'구매액': 'mean',
'방문횟수': 'mean',
'고객ID': 'count'
}).round(0)
gender_analysis.columns = ['평균구매액', '평균방문횟수', '고객수']
print("\\n=== 성별 분석 ===")
print(gender_analysis)
# 6. 결과 저장
customers.to_csv('customer_analysis.csv', index=False, encoding='utf-8-sig')
print("\\n결과가 customer_analysis.csv에 저장되었습니다!")
자주 하는 실수 TOP 10
1. SettingWithCopyWarning
# ❌ 경고 발생
df['새열'] = df[df['나이'] > 25]['이름']
# ✅ 올바른 방법
df.loc[df['나이'] > 25, '새열'] = df['이름']
2. inplace 이해 못함
# ❌ 적용 안 됨
df.drop('열이름', axis=1)
print(df) # 열이 그대로!
# ✅ 올바른 방법
df = df.drop('열이름', axis=1)
# 또는
df.drop('열이름', axis=1, inplace=True)
3. 인덱스 리셋 안 함
# 필터링 후 인덱스가 뒤죽박죽
df_filtered = df[df['나이'] > 25]
# ✅ 인덱스 리셋
df_filtered.reset_index(drop=True, inplace=True)
4. 조건 결합 시 괄호 없음
# ❌ 에러 발생
df[df['나이'] > 25 & df['직업'] == '개발자']
# ✅ 괄호 필수
df[(df['나이'] > 25) & (df['직업'] == '개발자')]
5. apply vs vectorization
# ❌ 느린 방법
df['새열'] = df['나이'].apply(lambda x: x * 2)
# ✅ 빠른 방법 (벡터화)
df['새열'] = df['나이'] * 2
6. 날짜 타입 변환 안 함
# ❌ 문자열로 처리
df['날짜'].str[:4] # 년도 추출 (느림)
# ✅ 날짜 타입으로
df['날짜'] = pd.to_datetime(df['날짜'])
df['년도'] = df['날짜'].dt.year
7. merge 후 중복 확인 안 함
# merge 후 항상 확인
result = pd.merge(df1, df2, on='key')
print(result.shape) # 예상한 크기인지 확인
print(result.duplicated().sum()) # 중복 확인
8. 메모리 관리 무시
# 데이터 타입 최적화
df['나이'] = df['나이'].astype('int8') # int64 → int8
df['카테고리'] = df['카테고리'].astype('category') # 메모리 절약
# 메모리 사용량 확인
df.memory_usage(deep=True)
9. chained indexing
# ❌ 작동 안 할 수 있음
df[df['나이'] > 25]['이름'] = '변경'
# ✅ loc 사용
df.loc[df['나이'] > 25, '이름'] = '변경'
10. 인코딩 문제
# ✅ 항상 인코딩 명시
df = pd.read_csv('data.csv', encoding='utf-8-sig')
df.to_csv('output.csv', encoding='utf-8-sig', index=False)
성능 최적화 5가지 팁
1. 벡터화 연산 사용
# ❌ 느림 (10만 행에 10초)
df['결과'] = df['값'].apply(lambda x: x * 2 + 10)
# ✅ 빠름 (10만 행에 0.1초)
df['결과'] = df['값'] * 2 + 10
2. 필요한 열만 읽기
# 100개 열 중 3개만 필요할 때
df = pd.read_csv('huge.csv', usecols=['열1', '열2', '열3'])
3. 카테고리 타입 활용
# 반복되는 문자열은 category로
df['지역'] = df['지역'].astype('category') # 메모리 1/10
4. eval과 query 사용
# 복잡한 계산은 eval
df.eval('새열 = 열A * 열B + 열C', inplace=True)
# 복잡한 필터는 query
df.query('나이 > 25 and 직업 == "개발자"')
5. 청크 단위 처리
# 대용량 파일을 나눠서 읽기
chunk_size = 10000
for chunk in pd.read_csv('huge.csv', chunksize=chunk_size):
# 청크별 처리
process(chunk)
실무 필수 패턴 모음
중복 제거
# 중복 확인
df.duplicated().sum()
# 중복 제거
df.drop_duplicates(inplace=True)
# 특정 열 기준 중복 제거
df.drop_duplicates(subset=['이름'], keep='first')
데이터 타입 변환
# 숫자를 문자로
df['ID'] = df['ID'].astype(str)
# 문자를 숫자로 (에러 무시)
df['나이'] = pd.to_numeric(df['나이'], errors='coerce')
# 날짜 변환
df['날짜'] = pd.to_datetime(df['날짜'], format='%Y-%m-%d')
이상치 처리
# IQR 방법
Q1 = df['값'].quantile(0.25)
Q3 = df['값'].quantile(0.75)
IQR = Q3 - Q1
# 이상치 제거
df = df[(df['값'] >= Q1 - 1.5*IQR) & (df['값'] <= Q3 + 1.5*IQR)]
체크리스트: Pandas 작업 전 필수 확인
✅ 인코딩 지정 (utf-8-sig)
✅ 데이터 타입 확인 (df.dtypes)
✅ 결측치 확인 (df.isnull().sum())
✅ 중복 확인 (df.duplicated().sum())
✅ 기본 통계 확인 (df.describe())
✅ 메모리 사용량 체크 (df.memory_usage())
✅ 인덱스 리셋 (필요시)
✅ 벡터화 연산 우선 사용
마치며: Pandas 마스터로 가는 길
Pandas는 배울 게 많아 보이지만, 핵심 패턴 20개만 익히면 실무의 90%를 해결할 수 있습니다. 이 가이드의 예제들을 직접 타이핑해보며 연습하세요. 손으로 익히는 게 가장 빠릅니다.
기억할 핵심 5가지:
- 항상 head()로 확인
- loc/iloc 명확히 구분
- 벡터화 연산 우선
- 인코딩 항상 명시
- 체이닝 대신 loc 사용
데이터 분석의 시작은 Pandas입니다. 이 라이브러리 하나만 제대로 다뤄도 여러분의 업무 생산성이 10배는 올라갑니다!
이 글이 도움되셨다면 북마크하고, 데이터 분석 입문하는 동료에게 공유해주세요!