TechFeedTechFeed
Programming Languages

Python pandas 실전 튜토리얼 — CSV 로딩·DataFrame 조작·groupby·시각화 완전 정복

Python pandas를 사용한 데이터 분석 단계별 튜토리얼. CSV·JSON·엑셀 불러오기, loc/iloc 행 필터링, 결측값 dropna·fillna 처리, groupby·agg 집계, merge/concat DataFrame 병합, matplotlib·seaborn 시각화까지 실무에서 자주 쓰는 코드 패턴을 한 번에 정리합니다. NumPy, 판다스, 데이터사이언스 입문자도 바로 따라할 수 있는 실전 가이드입니다.

by

저는 지난 2년간 사이트 데이터를 매일 분석하면서 pandas를 떼놓을 수 없게 됐습니다. 구글 애널리틱스에서 내려받은 CSV, 슈퍼베이스에서 내보낸 JSON, 공공 API에서 받은 엑셀 — 어떤 형식이든 pandas를 열면 다섯 줄 안에 구조를 파악할 수 있습니다.


Python 데이터 분석을 시작하는 개발자 대부분이 설치 자체는 어렵지 않은데, DataFrame을 자유자재로 다루는 법을 익히는 데 막힌다고 합니다. 이 글은 설치부터 groupby, merge, 시각화까지 실제로 쓰이는 패턴만 단계별로 정리합니다.


설치와 개발 환경 — venv, Jupyter, VS Code

pandas 단독으로 설치해도 되지만, 데이터 분석 작업에서는 NumPy·Matplotlib·Seaborn이 세트로 필요합니다. 가상환경을 먼저 만들고 한꺼번에 설치하는 게 의존성 충돌을 피하는 가장 안전한 방법입니다.


Jupyter Notebook은 코드 실행 결과를 셀 단위로 바로 확인할 수 있어서 탐색적 데이터 분석(EDA)에 최적입니다. VS Code를 쓴다면 Jupyter 확장을 설치하면 노트북을 에디터 안에서 그대로 실행할 수 있습니다. 어떤 환경을 써도 무방하지만, 처음 배울 때는 Jupyter에서 셀별로 출력을 보면서 익히는 게 훨씬 빠릅니다.


Python 3.10 이상이면 모두 호환됩니다. 시스템 Python을 오염시키지 않기 위해 반드시 가상환경(venv 또는 conda)을 먼저 만드세요. 프로젝트마다 독립적인 환경을 유지하는 것이 실수 없이 작업하는 기본 습관입니다.


가상환경 생성 및 패키지 설치
# 가상환경 생성 python3 -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate # 핵심 패키지 설치 pip install pandas numpy matplotlib seaborn jupyter # 버전 확인 python -c "import pandas as pd; print(pd.__version__)" # 2.2.x # Jupyter 실행 jupyter notebook

DataFrame 만들기와 CSV·JSON 불러오기

DataFrame은 표(table)와 동일한 개념입니다. 행(row)과 열(column)로 구성되고, 각 열은 Series 객체입니다. 직접 딕셔너리로 만들 수도 있고, CSV·JSON·엑셀 파일을 읽어와서 만들 수도 있습니다.


실무에서 가장 많이 쓰이는 함수는 pd.read_csv()입니다. encoding 옵션에 'utf-8-sig'를 자주 넣는데, 윈도우에서 저장한 CSV가 BOM(Byte Order Mark)을 포함하는 경우가 많기 때문입니다. 한국어 데이터를 다룰 때 이 차이를 모르면 첫 번째 열 이름이 깨져서 나옵니다.


파일 크기가 크면 chunksize로 청크 단위 처리하거나, nrows로 일부만 불러와서 구조를 먼저 파악하는 습관을 들이세요. 100만 행짜리 CSV를 통째로 로딩했다가 메모리가 부족해지는 상황이 자주 생깁니다.


CSV 불러오기 & 기본 탐색
import pandas as pd # CSV 불러오기 (한국어 파일 인코딩 대응) df = pd.read_csv('sales_data.csv', encoding='utf-8-sig') # 첫 5행 확인 print(df.head()) # 구조 확인 (열 이름, 데이터 타입, 결측값 개수) df.info() # 기술 통계 요약 (count, mean, std, min, max) print(df.describe()) # JSON 불러오기 df_json = pd.read_json('data.json', orient='records') # 엑셀 불러오기 df_excel = pd.read_excel('report.xlsx', sheet_name='Sheet1') # 딕셔너리로 직접 생성 df_manual = pd.DataFrame({ 'name': ['클로드', '챗지피티', '제미나이'], 'company': ['앤트로픽', '오픈AI', '구글'], 'price': [20, 20, 20] })
판다스 DataFrame 기본 구조 설명
DataFrame은 행과 열로 구성된 2차원 데이터 구조로, NumPy ndarray를 기반으로 동작한다

열 선택, 행 필터링, loc vs iloc 완전 정리

DataFrame에서 원하는 데이터를 추출하는 방법이 몇 가지 있는데, 처음에 lociloc를 혼동해서 에러를 내는 경우가 많습니다. 차이를 한 줄로 정리하면 다음과 같습니다.


  • loc: 레이블(인덱스 이름, 열 이름) 기반 선택
  • iloc: 정수 위치 기반 선택 (0부터 시작)

조건 필터링은 불리언 마스크(Boolean mask) 방식을 씁니다. 여러 조건을 결합할 때 and 대신 &, or 대신 |를 써야 하고, 각 조건은 반드시 괄호로 감싸야 합니다. 이 두 규칙을 어기면 에러가 납니다.


query() 메서드를 쓰면 SQL에 가까운 문자열 형식으로 조건을 쓸 수 있어서 가독성이 좋아집니다. 변수를 참조할 때는 @변수명 형식을 씁니다. 복잡한 필터 조건이 많을수록 query 방식이 훨씬 읽기 편합니다.


열 선택·행 필터링·loc·iloc 예시
# 단일 열 선택 (Series 반환) prices = df['price'] # 여러 열 선택 (DataFrame 반환) sub = df[['name', 'price', 'category']] # 조건 필터링 — 가격 10만 원 이상 expensive = df[df['price'] >= 100000] # 여러 조건 결합 (&, | 사용, 괄호 필수) filtered = df[(df['price'] >= 100000) & (df['category'] == '전자제품')] # query 메서드 (가독성 향상, 변수 참조 시 @) min_price = 50000 result = df.query('price >= @min_price and category == "전자제품"') # loc: 레이블 기반 (인덱스 10~20, 특정 열) df.loc[10:20, ['name', 'price']] # iloc: 위치 기반 (처음 10행, 처음 3열) df.iloc[0:10, 0:3] # 특정 값 목록으로 필터링 selected = df[df['category'].isin(['전자제품', '가전', '컴퓨터'])]

데이터 정제 — 결측값, 중복 행, 형변환

실제 데이터는 항상 지저분합니다. 결측값(NaN)이 섞여 있거나, 같은 데이터가 중복으로 들어와 있거나, 숫자 열이 문자열로 저장된 경우가 다반사입니다. 분석 전에 이 세 가지는 반드시 처리해야 합니다.


결측값 처리는 삭제대체 중 하나를 선택합니다. 결측이 전체의 5% 미만이면 행을 삭제해도 크게 왜곡이 없습니다. 그 이상이면 평균·중앙값 대체, 또는 전후 값으로 채우는 방식(ffill, bfill)을 씁니다. 시계열 데이터라면 ffill(앞 값 채우기)이 자연스럽습니다.


숫자인데 문자로 저장된 열(예: 쉼표 포함 금액 "1,000")은 .str.replace(',', '').astype(float)로 변환합니다. 날짜 열은 pd.to_datetime()으로 변환해야 월별 집계 같은 시계열 분석이 가능합니다.


결측값·중복·형변환 처리 코드
# 결측값 현황 확인 print(df.isnull().sum()) print(df.isnull().sum() / len(df) * 100) # 비율 확인 # 결측 행 삭제 (특정 열 기준) df_clean = df.dropna(subset=['price', 'name']) # 결측값 채우기 df['price'] = df['price'].fillna(df['price'].mean()) # 평균으로 df['category'] = df['category'].fillna('미입력') # 고정값으로 df['value'] = df['value'].ffill() # 앞 값으로 (시계열) # 중복 행 제거 df = df.drop_duplicates() df = df.drop_duplicates(subset=['order_id']) # 특정 열 기준 # 문자열 숫자 → float 변환 (쉼표 제거) df['revenue'] = df['revenue'].str.replace(',', '').astype(float) # 날짜 문자열 → datetime 변환 df['order_date'] = pd.to_datetime(df['order_date'], format='%Y-%m-%d') # 날짜에서 연·월·요일 추출 df['year'] = df['order_date'].dt.year df['month'] = df['order_date'].dt.month df['weekday'] = df['order_date'].dt.day_name()

groupby로 집계 분석하기 — agg, pivot_table

groupby()는 SQL의 GROUP BY와 동일한 역할을 합니다. 카테고리별 평균 가격, 월별 매출 합계, 지역별 건수 — 이런 집계 분석의 핵심입니다.


agg()에 딕셔너리를 넘기면 열마다 다른 집계 함수를 적용할 수 있습니다. 가격은 평균, 수량은 합계, 주문 건수는 카운트를 한꺼번에 구할 수 있어서 보고용 요약 테이블을 만들 때 매우 유용합니다.


pivot_table()은 엑셀의 피벗 테이블과 같습니다. 행과 열을 각각 다른 변수로 지정해서 교차 집계를 만들 때 유용합니다. aggfunc 파라미터에 여러 함수를 리스트로 넘기면 다중 집계도 가능합니다.


groupby 결과가 Series로 나오면 .reset_index()를 써서 DataFrame으로 변환하는 게 후속 작업에 편합니다. 그냥 두면 인덱스가 중첩돼서 후속 처리가 어색해집니다.


groupby·agg·pivot_table 실전 예시
# 카테고리별 평균 가격 category_avg = df.groupby('category')['price'].mean().reset_index() # 여러 열 집계 — 열마다 다른 함수 적용 summary = df.groupby('category').agg( 평균가격=('price', 'mean'), 총수량=('quantity', 'sum'), 주문건수=('order_id', 'count') ).reset_index() # 월별·카테고리별 매출 합계 monthly = df.groupby(['year', 'month', 'category'])['revenue'].sum().reset_index() # pivot_table — 행: 월, 열: 카테고리, 값: 매출 합계 pivot = df.pivot_table( values='revenue', index='month', columns='category', aggfunc='sum', fill_value=0 # NaN → 0 ) print(pivot) # 전체 합계 열/행 추가 pivot['합계'] = pivot.sum(axis=1) pivot.loc['합계'] = pivot.sum()
pandas groupby 집계 결과 예시
groupby + agg 결과는 카테고리별 통계를 한눈에 보여준다

DataFrame 병합 — merge, concat 완전 정리

두 개 이상의 DataFrame을 합치는 방법은 크게 두 가지입니다.


  • concat: 행/열 방향으로 단순 이어 붙이기 (같은 구조의 DataFrame을 합칠 때)
  • merge: 공통 열(키)을 기준으로 결합 (SQL JOIN과 동일)

실무에서는 merge를 가장 많이 씁니다. how 파라미터로 JOIN 종류를 지정합니다. inner(교집합), left(왼쪽 기준), right(오른쪽 기준), outer(합집합) 네 가지입니다. 키 열 이름이 다르면 left_on, right_on으로 각각 지정합니다.


merge 후에 _x, _y 접미사가 붙은 열이 생기면 양쪽 DataFrame에 같은 이름의 열이 있다는 뜻입니다. suffixes 파라미터로 직접 이름을 지정하거나, 불필요한 중복 열을 미리 제거하세요. 또한 키에 중복값이 있으면 카테시안 곱이 발생해 행이 갑자기 늘어나는 문제가 생기므로, merge 전에 키 열의 중복 여부를 반드시 확인하는 습관을 들이세요.


merge, concat 실전 코드
# 행 방향 이어 붙이기 (같은 구조) df_all = pd.concat([df_2024, df_2025], ignore_index=True) # 열 방향 이어 붙이기 df_wide = pd.concat([df_features, df_labels], axis=1) # INNER JOIN (양쪽에 모두 있는 행만) merged = pd.merge(df_orders, df_customers, on='customer_id', how='inner') # LEFT JOIN (주문 기준 — 고객 정보 없어도 주문 유지) merged_left = pd.merge(df_orders, df_customers, on='customer_id', how='left') # 키 이름이 다를 때 (left_on, right_on) merged_diff = pd.merge(df_orders, df_items, left_on='item_code', right_on='product_id', how='inner') # 중복 열 접미사 지정 result = pd.merge(df_a, df_b, on='id', suffixes=('_원본', '_갱신')) # merge 전 키 중복 확인 print(df_orders['customer_id'].duplicated().sum())

matplotlib·seaborn으로 시각화하기

pandas의 .plot()은 matplotlib을 내부적으로 사용하는 편의 메서드입니다. 빠른 탐색에는 충분하지만, 보고서나 배포용 그래프는 matplotlib을 직접 다루거나 seaborn을 씁니다.


seaborn은 matplotlib 위에 얹힌 고수준 라이브러리입니다. sns.histplot(), sns.boxplot(), sns.heatmap()처럼 통계적 시각화에 특화된 함수를 제공합니다. 특히 groupby 결과를 히트맵으로 보는 패턴이 자주 쓰입니다.


한국어 폰트 깨짐은 고질적인 문제입니다. 맥에서는 AppleGothic, 윈도우에서는 Malgun Gothic을 설정하거나, 나눔고딕·노토 산스를 설치해서 matplotlib에 등록해야 합니다. 이 설정을 빠뜨리면 한글이 네모 박스로 표시됩니다.


matplotlib·seaborn 시각화 코드
import matplotlib.pyplot as plt import seaborn as sns # 한국어 폰트 설정 (Mac: AppleGothic, Windows: Malgun Gothic) plt.rcParams['font.family'] = 'AppleGothic' plt.rcParams['axes.unicode_minus'] = False # 마이너스 기호 깨짐 방지 # 카테고리별 매출 막대 그래프 category_revenue = df.groupby('category')['revenue'].sum().reset_index() fig, ax = plt.subplots(figsize=(10, 6)) ax.bar(category_revenue['category'], category_revenue['revenue'], color='steelblue') ax.set_title('카테고리별 총 매출', fontsize=14) ax.set_xlabel('카테고리') ax.set_ylabel('매출 (원)') plt.xticks(rotation=45, ha='right') plt.tight_layout() plt.savefig('revenue_by_category.png', dpi=150) plt.show() # seaborn 히트맵 (pivot_table 결과 시각화) plt.figure(figsize=(12, 8)) sns.heatmap(pivot, annot=True, fmt='.0f', cmap='YlOrRd', linewidths=0.5) plt.title('월별·카테고리별 매출 히트맵') plt.show() # 가격 분포 (histplot + KDE 곡선) sns.histplot(df['price'], bins=30, kde=True) plt.title('가격 분포') plt.show()
seaborn 히트맵 데이터 시각화 예시
seaborn 히트맵으로 월별·카테고리별 패턴을 한눈에 확인할 수 있다

대용량 데이터 처리 — 메모리 최적화와 속도 팁

수백만 행 데이터를 다루다 보면 메모리 부족(MemoryError)이나 느린 처리 속도가 발목을 잡습니다. 몇 가지 최적화 패턴을 알고 있으면 대부분 해결됩니다.


dtype 최적화가 가장 효과적입니다. 기본적으로 정수는 int64, 실수는 float64로 읽히는데, 값 범위가 작으면 int32나 float32로 바꾸면 메모리가 절반으로 줄어듭니다. 반복 문자열 열(카테고리, 지역명 등)은 category dtype으로 변환하면 더욱 극적으로 줄어듭니다.


청크(chunk) 처리는 파일 자체가 메모리에 안 들어올 때 씁니다. pd.read_csv(chunksize=100000)으로 10만 행씩 읽어서 처리한 뒤 결과만 누적합니다.


속도가 느리다면 apply() 대신 벡터화 연산을 쓰는 게 기본입니다. apply(lambda x: ...)는 파이썬 루프와 다를 게 없어서 수십 배 느립니다. NumPy 연산이나 pandas 내장 메서드로 대체할 수 있으면 무조건 그쪽을 씁니다. 1000만 행 이상의 대규모 파이프라인에서는 Polars(Rust 기반)로의 전환도 검토할 만합니다.


메모리 절약 체크리스트
① 숫자 열: int64 → int32 / float64 → float32 다운캐스팅
② 반복 문자열 열: .astype('category') 변환
③ 불필요한 열 미리 제거: pd.read_csv(usecols=[...]) 활용
④ 대용량: chunksize 청크 처리 또는 Polars 전환 검토
apply(lambda) 대신 벡터화 연산 사용

참고 자료


자주 묻는 질문 (FAQ)
Q. pandas와 NumPy의 차이는 무엇인가요?

NumPy는 수치 배열(ndarray) 연산을 위한 기저 라이브러리이고, pandas는 NumPy 위에 표(DataFrame) 구조와 레이블 기반 인덱싱, 결측값 처리 등을 추가한 고수준 라이브러리입니다. 순수 행렬 연산에는 NumPy가 빠르고, 이종 데이터 타입이 섞인 표 형식 데이터를 다루려면 pandas가 적합합니다.


Q. loc과 iloc 중 어느 것을 써야 하나요?

인덱스가 0, 1, 2... 정수 기본값이면 두 가지가 비슷해 보이지만, 인덱스를 날짜나 문자열로 재설정한 경우 차이가 납니다. 일관성을 위해 레이블로 접근할 때는 loc, 번호(위치)로 접근할 때는 iloc을 쓰는 게 명확합니다. 슬라이싱 범위도 loc는 끝값 포함, iloc는 끝값 미포함으로 다릅니다.


Q. groupby 결과에서 인덱스를 일반 열로 바꾸려면?

.reset_index()를 호출하면 그룹 기준 열이 일반 열로 내려옵니다. df.groupby('category')['price'].mean().reset_index()처럼 바로 체인하는 패턴이 가장 일반적입니다.


Q. merge 후 행이 예상보다 많아지는 이유는?

JOIN 키에 중복값이 있으면 카테시안 곱이 발생해서 행이 늘어납니다. 예를 들어 왼쪽에 같은 order_id가 2번, 오른쪽에도 2번 있으면 4행이 생깁니다. merge 전에 df['key'].duplicated().sum()으로 키 열의 중복 여부를 확인하는 습관을 들이세요.


Q. 대용량 CSV를 더 빠르게 처리하는 방법은?

1000만 행 이상의 데이터를 다루거나 실시간 처리가 필요하다면 Polars를 검토하세요. Polars는 Rust로 작성된 DataFrame 라이브러리로, pandas보다 5~10배 빠른 경우가 많습니다. API 구조가 달라 마이그레이션 비용이 들지만, 대규모 데이터 파이프라인에서는 점점 필수가 되어가고 있습니다.


pythonpandasnumpymatplotlibseaborn데이터분석데이터사이언스tutorial프로그래밍파이썬

함께 보면 좋은 문제 해결

EXPLORE / Programming Languages

이어서 읽어보기

전체 토픽 둘러보기