Skip to content

Pandas DataFrame을 CSV로 변환: to_csv() 완벽 가이드

Updated on

데이터를 정리하고, 모든 열을 변환하고, 분석을 실행했습니다. 이제 결과를 공유해야 합니다 -- 그런데 내보낸 CSV 파일이 Excel에서 깨진 문자로 열리거나, 원치 않는 인덱스 열이 포함되어 있거나, 4GB 파일이 동료의 노트북을 멈추게 합니다. Pandas DataFrame을 CSV로 내보내는 것은 인코딩, 구분자, 압축, 대용량 파일 성능이 한 줄의 코드를 디버깅 세션으로 바꿀 때까지 간단하게 들립니다.

이러한 문제가 흔한 이유는 to_csv()에 20개 이상의 매개변수가 있고, 기본값이 하류 소비자가 기대하는 것과 항상 일치하지 않기 때문입니다. 잘못된 인코딩은 비ASCII 텍스트를 깨뜨립니다. 누락된 index=False는 미스터리 열을 추가합니다. 압축 없이는 적절한 크기의 데이터셋이 거대한 파일 첨부가 됩니다.

DataFrame.to_csv() 메서드는 어떤 매개변수를 설정해야 하는지 알면 이 모든 경우를 처리합니다. 이 가이드는 기본 내보내기부터 수백만 행 DataFrame의 청크 쓰기까지 모든 실용적인 시나리오를 다루므로, 첫 번째 시도에서 데이터를 올바르게 내보낼 수 있습니다.

📚

기본 사용법

가장 간단한 호출은 현재 작업 디렉토리에 DataFrame을 CSV 파일로 씁니다:

import pandas as pd
 
df = pd.DataFrame({
    'name': ['Alice', 'Bob', 'Charlie'],
    'age': [28, 35, 42],
    'salary': [72000, 88000, 95000]
})
 
df.to_csv('employees.csv')

이렇게 하면 다음 내용으로 employees.csv가 생성됩니다:

,name,age,salary
0,Alice,28,72000
1,Bob,35,88000
2,Charlie,42,95000

첫 번째 열에 주목하세요 -- 이것은 DataFrame 인덱스입니다. 대부분의 경우 이것을 원하지 않습니다. index=False로 수정합니다:

df.to_csv('employees.csv', index=False)

출력:

name,age,salary
Alice,28,72000
Bob,35,88000
Charlie,42,95000

전체 메서드 시그니처

가장 자주 사용하는 매개변수가 포함된 to_csv()의 전체 시그니처입니다:

DataFrame.to_csv(
    path_or_buf=None,   # 파일 경로 또는 버퍼
    sep=',',            # 구분자
    na_rep='',          # 결측값을 위한 문자열
    float_format=None,  # 부동소수점 포맷 문자열
    columns=None,       # 쓸 열의 하위 집합
    header=True,        # 열 이름 쓰기
    index=True,         # 행 인덱스 쓰기
    index_label=None,   # 인덱스의 열 레이블
    mode='w',           # 쓰기 모드 ('w', 'a', 'x')
    encoding=None,      # 파일 인코딩 (기본값 utf-8)
    compression='infer',# 압축 유형
    quoting=None,       # csv.QUOTE_* 상수
    lineterminator=None,# 줄바꿈 문자
    chunksize=None,     # 청크당 행 수
    date_format=None,   # 날짜/시간 포맷 문자열
    errors='strict',    # 인코딩 오류 처리
)

주요 매개변수 설명

sep -- 사용자 정의 구분자

모든 시스템이 쉼표를 읽는 것은 아닙니다. sep을 사용하여 구분자를 변경합니다:

import pandas as pd
 
df = pd.DataFrame({
    'product': ['Widget A', 'Widget B'],
    'price': [19.99, 29.99]
})
 
# 탭으로 구분
df.to_csv('products.tsv', sep='\t', index=False)
 
# 세미콜론으로 구분 (유럽 지역 설정에서 일반적)
df.to_csv('products_eu.csv', sep=';', index=False)
 
# 파이프로 구분
df.to_csv('products.txt', sep='|', index=False)

columns -- 하위 집합 내보내기

전체 DataFrame 대신 특정 열만 씁니다:

import pandas as pd
 
df = pd.DataFrame({
    'id': [1, 2, 3],
    'name': ['Alice', 'Bob', 'Charlie'],
    'email': ['a@x.com', 'b@x.com', 'c@x.com'],
    'internal_score': [0.87, 0.92, 0.78]
})
 
# 클라이언트가 필요한 열만 내보내기
df.to_csv('export.csv', columns=['id', 'name', 'email'], index=False)

header -- 열 이름 제어

import pandas as pd
 
df = pd.DataFrame({
    'name': ['Alice', 'Bob'],
    'score': [85, 92]
})
 
# 헤더 행 없음
df.to_csv('no_header.csv', header=False, index=False)
 
# 사용자 정의 헤더 이름
df.to_csv('custom_header.csv', header=['전체 이름', '시험 점수'], index=False)

na_rep -- 결측값 표현

기본적으로 NaN 값은 빈 문자열이 됩니다. na_rep을 사용하여 명시적으로 만듭니다:

import pandas as pd
import numpy as np
 
df = pd.DataFrame({
    'sensor': ['A', 'B', 'C'],
    'reading': [23.5, np.nan, 18.2]
})
 
# 기본값: NaN에 대해 빈 문자열
df.to_csv('readings_default.csv', index=False)
# sensor,reading
# A,23.5
# B,
# C,18.2
 
# 명시적 결측값 마커
df.to_csv('readings_marked.csv', na_rep='NULL', index=False)
# sensor,reading
# A,23.5
# B,NULL
# C,18.2

float_format -- 소수점 정밀도 제어

import pandas as pd
 
df = pd.DataFrame({
    'item': ['Widget', 'Gadget'],
    'price': [19.999999, 42.123456]
})
 
# 소수점 두 자리
df.to_csv('prices.csv', float_format='%.2f', index=False)
# item,price
# Widget,20.00
# Gadget,42.12

date_format -- 날짜/시간 열 포맷

import pandas as pd
 
df = pd.DataFrame({
    'event': ['Launch', 'Review'],
    'date': pd.to_datetime(['2026-03-15 14:30:00', '2026-04-01 09:00:00'])
})
 
# ISO 날짜만 (시간 없음)
df.to_csv('events.csv', date_format='%Y-%m-%d', index=False)
# event,date
# Launch,2026-03-15
# Review,2026-04-01

인코딩 및 특수 문자 처리

인코딩은 깨진 CSV 파일의 가장 큰 원인입니다. to_csv()의 기본 인코딩은 utf-8로, 대부분의 시스템에서 작동합니다. 하지만 Windows의 Excel은 비ASCII 문자를 올바르게 표시하기 위해 BOM(바이트 순서 표시)을 필요로 합니다.

UTF-8 (기본값)

import pandas as pd
 
df = pd.DataFrame({
    'city': ['취리히', '뮌헨', '도쿄'],
    'greeting': ['안녕하세요', '안녕', '곤니치와']
})
 
# 표준 UTF-8
df.to_csv('cities.csv', index=False, encoding='utf-8')

Excel용 UTF-8(BOM 포함)

CSV에 악센트 문자, CJK 텍스트 또는 특수 기호가 포함되어 있고 Microsoft Excel에서 올바르게 열어야 하는 경우:

import pandas as pd
 
df = pd.DataFrame({
    'city': ['취리히', '뮌헨', '상파울루', '베이징'],
    'population': [434000, 1472000, 12330000, 21540000]
})
 
# utf-8-sig는 Excel이 인식하는 BOM을 추가합니다
df.to_csv('cities_excel.csv', index=False, encoding='utf-8-sig')

기타 인코딩

import pandas as pd
 
df = pd.DataFrame({'col': ['data']})
 
# 레거시 서유럽 시스템용 Latin-1
df.to_csv('legacy.csv', index=False, encoding='latin-1')
 
# 레거시 일본어 시스템용 Shift-JIS
df.to_csv('japanese.csv', index=False, encoding='shift_jis')
인코딩사용 시기Excel 호환?
utf-8대부분의 시스템, API, 데이터베이스 기본값부분적 (BOM 없음)
utf-8-sig비ASCII 텍스트가 있는 Windows Excel
latin-1 / iso-8859-1레거시 서유럽 시스템
shift_jis레거시 일본어 시스템
cp1252Windows 서유럽

대용량 DataFrame: 압축과 청킹

압축

압축은 데이터 손실 없이 파일 크기를 60-90% 줄입니다. Pandas는 여러 형식을 지원합니다:

import pandas as pd
import numpy as np
 
# 대용량 DataFrame 생성
df = pd.DataFrame({
    'id': range(1_000_000),
    'value': np.random.randn(1_000_000),
    'category': np.random.choice(['A', 'B', 'C', 'D'], 1_000_000)
})
 
# gzip 압축 (가장 일반적)
df.to_csv('large_data.csv.gz', index=False, compression='gzip')
 
# zip 압축
df.to_csv('large_data.zip', index=False, compression='zip')
 
# bz2 압축 (더 작지만 느림)
df.to_csv('large_data.csv.bz2', index=False, compression='bz2')
 
# Zstandard (빠르고 좋은 비율 -- zstandard 패키지 필요)
df.to_csv('large_data.csv.zst', index=False, compression='zstd')
압축파일 확장자속도크기 감소다시 읽기
없음.csv가장 빠른 쓰기0%pd.read_csv('f.csv')
gzip.csv.gz보통70-85%pd.read_csv('f.csv.gz')
zip.zip보통70-85%pd.read_csv('f.zip')
bz2.csv.bz2느림75-90%pd.read_csv('f.csv.bz2')
zstd.csv.zst빠름70-85%pd.read_csv('f.csv.zst')

Pandas는 파일 확장자에서 압축 형식을 자동으로 추론하므로, compression='infer'(기본값)이 일반적으로 작동합니다.

Chunksize -- 배치로 쓰기

메모리를 압박하는 매우 큰 DataFrame의 경우, 청크로 씁니다:

import pandas as pd
import numpy as np
 
df = pd.DataFrame({
    'id': range(5_000_000),
    'value': np.random.randn(5_000_000)
})
 
# 한 번에 500,000행 쓰기
df.to_csv('huge_data.csv', index=False, chunksize=500_000)

chunksize 매개변수는 최종 파일 크기를 줄이지 않습니다 -- 한 번에 디스크에 플러시되는 행 수를 제어합니다. 이를 통해 쓰기 작업 중 최대 메모리 사용량을 줄일 수 있습니다.

문자열 또는 버퍼에 쓰기

항상 파일에 쓰는 것은 아닙니다. 때로는 API 호출, 데이터베이스 삽입 또는 메모리 내 처리를 위해 CSV를 문자열로 필요합니다:

import pandas as pd
 
df = pd.DataFrame({
    'name': ['Alice', 'Bob'],
    'score': [85, 92]
})
 
# CSV를 문자열로 가져오기
csv_string = df.to_csv(index=False)
print(csv_string)
# name,score
# Alice,85
# Bob,92

StringIO 또는 BytesIO에 쓰기

import pandas as pd
from io import StringIO, BytesIO
 
df = pd.DataFrame({
    'x': [1, 2, 3],
    'y': [4, 5, 6]
})
 
# StringIO 버퍼
buffer = StringIO()
df.to_csv(buffer, index=False)
csv_text = buffer.getvalue()
 
# BytesIO 버퍼 (HTTP 응답, S3 업로드에 유용)
byte_buffer = BytesIO()
df.to_csv(byte_buffer, index=False, encoding='utf-8')
csv_bytes = byte_buffer.getvalue()

이 패턴은 로컬 파일을 생성하지 않고 CSV 데이터를 클라우드 스토리지(S3, GCS)에 업로드할 때 일반적입니다.

기존 CSV에 추가

기존 파일을 덮어쓰지 않고 행을 추가하려면 mode='a'header=False를 사용합니다:

import pandas as pd
 
# 첫 번째 배치 -- 헤더와 함께 쓰기
df1 = pd.DataFrame({'name': ['Alice'], 'score': [85]})
df1.to_csv('results.csv', index=False)
 
# 두 번째 배치 -- 헤더 중복 없이 추가
df2 = pd.DataFrame({'name': ['Bob', 'Charlie'], 'score': [92, 78]})
df2.to_csv('results.csv', mode='a', header=False, index=False)

결과 파일은 하나의 헤더 행에 이어 세 개의 데이터 행이 있습니다. header=False를 생략하면 데이터 중간에 두 번째 헤더 행이 삽입됩니다.

인용 및 이스케이프

구분자, 줄바꿈 또는 따옴표를 포함하는 필드는 적절한 인용이 필요합니다. Pandas는 이를 자동으로 처리하지만, 동작을 제어할 수 있습니다:

import pandas as pd
import csv
 
df = pd.DataFrame({
    'name': ['O\'Brien', 'Smith, Jr.', 'Alice "Ace" Wong'],
    'notes': ['아포스트로피 포함', '쉼표 포함', '따옴표 포함']
})
 
# 기본값: 필요한 경우에만 인용 (QUOTE_MINIMAL)
df.to_csv('minimal.csv', index=False)
 
# 모든 필드 인용
df.to_csv('all_quoted.csv', index=False, quoting=csv.QUOTE_ALL)
 
# 비숫자 필드만 인용
df.to_csv('nonnumeric.csv', index=False, quoting=csv.QUOTE_NONNUMERIC)

내보내기 형식 비교: to_csv() vs to_excel() vs to_parquet()

올바른 내보내기 형식을 선택하는 것은 누가 파일을 사용하고 얼마나 큰지에 달려 있습니다:

특성to_csv()to_excel()to_parquet()
파일 형식일반 텍스트바이너리 (xlsx)바이너리 (열 지향)
사람이 읽을 수 있음Excel/Sheets를 통해아니오
파일 크기 (100만 행)~50-100 MB~30-60 MB~5-15 MB
쓰기 속도빠름느림빠름
읽기 속도보통느림매우 빠름
타입 보존없음 (모두 문자열)부분적완전
추가 라이브러리 필요없음openpyxlpyarrow / fastparquet
압축 지원gzip, zip, bz2, zstd내장내장 (snappy, gzip)
최적 용도상호 운용성, API, 빠른 공유비즈니스 사용자, Excel 사용자분석 파이프라인, 빅데이터

경험 법칙: 비기술 사용자나 외부 시스템과 데이터를 공유하려면 CSV를 사용합니다. 속도와 타입 충실도가 중요한 내부 파이프라인에는 Parquet을 사용합니다. 수신자가 서식이나 여러 시트가 필요한 경우 Excel을 사용합니다.

일반적인 함정과 피하는 방법

1. 원치 않는 인덱스 열

가장 흔한 불만입니다. 인덱스에 의미 있는 데이터가 없다면 항상 index=False를 전달하세요:

# 잘못됨 -- 다시 읽을 때 미스터리 "Unnamed: 0" 열 추가
df.to_csv('data.csv')
 
# 올바름
df.to_csv('data.csv', index=False)

2. 다시 읽을 때 이름 없는 열

인덱스와 함께 내보내고 파일을 다시 읽으면 Unnamed: 0 열이 나타납니다:

import pandas as pd
 
# 인덱스와 함께 내보낸 CSV 다시 읽기
df = pd.read_csv('data.csv', index_col=0)  # read_csv에 어떤 열이 인덱스인지 알려주기

3. 날짜/시간 정밀도 손실

date_format 없이는 날짜/시간 열이 마이크로초를 포함한 전체 타임스탬프로 내보내집니다. 이를 명시적으로 제어하세요:

import pandas as pd
 
df = pd.DataFrame({
    'ts': pd.to_datetime(['2026-01-15 08:30:00.123456'])
})
 
df.to_csv('ts.csv', index=False, date_format='%Y-%m-%d %H:%M:%S')

4. 부동소수점 반올림 문제

부동소수점 숫자는 예상치 못한 후행 자릿수를 생성할 수 있습니다:

import pandas as pd
 
df = pd.DataFrame({'val': [0.1 + 0.2]})
df.to_csv('float.csv', index=False)
# val
# 0.30000000000000004
 
df.to_csv('float_clean.csv', index=False, float_format='%.2f')
# val
# 0.30

5. 열 내 혼합 타입

열에 혼합 타입(정수와 문자열)이 있으면 to_csv()는 모든 것을 문자열로 변환합니다. 내보내기 전에 데이터 타입을 확인하세요:

import pandas as pd
 
df = pd.DataFrame({'id': [1, 2, 'three']})
print(df.dtypes)
# id    object  <-- 혼합 타입
 
# 내보내기 전 정리
df['id'] = pd.to_numeric(df['id'], errors='coerce')
df.to_csv('clean.csv', index=False)

내보내기 전 PyGWalker로 데이터 시각화

DataFrame을 CSV로 내보내기 전에 데이터가 올바른지 확인하는 것이 도움됩니다. PyGWalker (opens in a new tab)는 Jupyter Notebook 내에서 모든 Pandas DataFrame을 인터랙티브한 시각적 인터페이스로 변환합니다 -- 플롯 코드를 작성하지 않고 열을 드래그 앤 드롭하여 차트를 만들 수 있습니다:

import pandas as pd
import pygwalker as pyg
 
df = pd.read_csv('raw_data.csv')
 
# 내보내기 전 시각적으로 탐색
walker = pyg.walk(df)
 
# 만족하면 내보내기
df.to_csv('verified_export.csv', index=False, encoding='utf-8-sig')

이 워크플로우는 예기치 않은 null 값, 이상치 또는 잘못된 데이터 타입과 같은 문제를 CSV 소비자에게 도달하기 전에 감지합니다.

FAQ

인덱스 없이 Pandas DataFrame을 CSV로 내보내려면?

to_csv()index=False를 전달합니다: df.to_csv('file.csv', index=False). 이렇게 하면 행 인덱스가 첫 번째 열로 기록되는 것을 방지합니다. 파일을 다시 읽을 때 추가 Unnamed: 0 열이 표시되지 않습니다.

CSV 내보내기 시 특수 문자와 인코딩을 어떻게 처리하나요?

비ASCII 문자(악센트 문자, CJK 텍스트)가 포함된 CSV를 Microsoft Excel에서 올바르게 열어야 하는 경우 encoding='utf-8-sig'를 사용합니다. 표준 시스템과 API에는 기본 encoding='utf-8'이 작동합니다. 레거시 시스템에는 필요한 특정 인코딩(latin-1, shift_jis 등)을 사용하세요.

Pandas에서 내보낼 때 큰 CSV 파일을 어떻게 압축하나요?

파일 이름에 압축 확장자를 추가하면 Pandas가 나머지를 처리합니다: df.to_csv('data.csv.gz', index=False)는 gzip 압축 파일을 생성합니다. compression='gzip', 'zip', 'bz2' 또는 'zstd'를 명시적으로 설정할 수도 있습니다. 압축 파일은 일반적으로 70-90% 작으며 pd.read_csv()로 직접 다시 읽을 수 있습니다.

기존 CSV 파일에 덮어쓰지 않고 데이터를 추가할 수 있나요?

예. mode='a'header=False를 사용합니다: df.to_csv('file.csv', mode='a', header=False, index=False). mode='a'는 파일을 추가 모드로 열고, header=False는 파일 중간에 중복 헤더 행이 기록되는 것을 방지합니다.

Pandas에서 to_csv()와 to_parquet()의 차이점은?

to_csv()는 사람이 읽을 수 있는 텍스트 파일을 생성하지만 타입 정보가 손실됩니다(모든 것이 문자열이 됩니다). to_parquet()는 데이터 타입(정수, 부동소수점, 날짜/시간)을 보존하는 컴팩트한 바이너리 파일을 생성하며, 5-10배 빠르게 읽히고 5-10배 작습니다. 비기술 사용자나 외부 시스템과의 공유에는 CSV를 사용하고, 성능과 타입 충실도가 중요한 내부 데이터 파이프라인에는 Parquet를 사용하세요.

결론

DataFrame.to_csv()는 Pandas 데이터를 CSV 파일로 내보내는 표준 방법입니다. 깨끗한 내보내기를 위해 항상 index=False를 전달하세요. Excel 호환성이 중요한 경우 encoding='utf-8-sig'를 사용합니다. gzip 또는 zstd로 큰 파일을 압축하여 크기를 70-90% 줄입니다. float_format으로 소수 정밀도를 제어하고, na_rep으로 결측값을 처리하고, mode='a'로 기존 파일에 추가합니다. 타입 보존과 속도가 중요한 데이터 파이프라인에는 대신 to_parquet()를 고려하세요. 내보내기 전에 PyGWalker와 같은 도구로 데이터를 시각적으로 확인하여 문제를 조기에 발견하세요.

📚