티스토리 뷰

파이썬 how to

CSV 파일 다루기

2017. 10. 10. 19:39
반응형


CSV 파일은 단순한 텍스트 파일을 데이터 테이블 형식으로 사용하는 포맷이다. 기본적으로 한 행을 1개의 레코드로 사용하며, 개별 필드를 컴마 (혹은 정하기에 따라서는 탭 문자나 임의의 구분자를 사용할 수 있다.)로 구분한다. csv는 기본적으로 플레인 텍스트 파일이기 때문에 텍스트 파일을 읽고 구분자 단위로 쪼개어 각 레코드/필드를 액세스하는 방식으로 처리하는 것도 가능하지만, 이는 제법 귀찮은 여러 작업들을 동반하기 때문에 파이썬 기본 라이브러리에는 csv 파일로부터 필드를 구분하여 읽고 쓸 수 있게 해주는 csv 모듈이 제공된다.


참고로 `csv` 모듈은 텍스트 파일의 내용을 구분자에 따라서 잘라서 제공해주는 것 이상의 역할을 하지 않는다. 각 필드의 값은 모두 문자열이며, 정수 및 실수 값으로 변환해서 처리해야 한다면, 이러한 값의 타입 변환의 책임은 항상 프로그래머에게 달려있다. 

Reader/Writer

csv.Reader/csv.Writer는 csv 파일을 각 레코드 단위로 읽고 쓰게 해주는 클래스이다. 특이함 점으로 파일 경로를 받아 특정한 파일을 바로 열어서 처리하지 않고 파일핸들러를 인자로 받아 이를 래핑하는 형태로 동작한다. (이는 sqlite3 모듈에서 파일 경로를 받아서 connection 객체를 만드는 방식과 좀 다르다.) 참고로 csv에서 기본적으로 제공하는 포맷형식은 엑셀에서 사용하는 기준을 디폴트로 사용한다. (컴마와 개행으로 구분)

import csv
## csv 파일을 읽고 레코드별 각 필드를 컴마로 연결하여 출력한다.
with open('eggs.csv') as inFile:
  reader = csv.Reader(inFile)
  for row in reader:
    print(', '.join(row))

csv.Reader는 텍스트 파일을 읽어서 생성할 때 정의한 포맷에 따라 한줄씩 파싱하는 역할을 수행한다. 따라서 이터레이터의 역할만을 수행하며, 특정한 라인의 레코드를 직접적으로 액세스할 수 없다. (이러한 액세스가 필요하다면 리더를 list()에 전달해서 리스트로 변환해야 한다.)

또한, csv 파일을 읽는 작업은 문자열 데이터를 정해진 한정자 단위로 끊어서 구분하는 것외에 특별한 의미는 없기 때문에 유의미한 데이터 포맷으로 바꾸는 일은 프로그래머에게 달려있다. (즉 숫자로 들어가있는 데이터를 알아서 정수나 실수 타입으로 바꿔주지 않는다.)

기본적인 Reader/Writer는 한 레코드를 각 필드가 순서대로 연결되어 있는 리스트로 간주한다. 따라서 csv.Writer의 경우 writerow() 메소드에 값의 리스트를 전달하여 한 레코드씩 기록한다.

with open('newEggs.csv', 'w') as outFile:
  writer = csv.Writer(outFile, delimiter='\t')
  for record in data:
    writer.writerow(record)

열 이름이 있는 CSV 파일 다루기

가끔 첫행에 각 필드의 이름이 기재된 csv 파일들이 있다. 주로 엑셀등에서 만든 데이터가 이렇게 구성된다. 각 필드에 이름이 있는 csv 파일은 OrderedDict 타입의 형태로 각 필드에 이름을 붙여서 액세스할 수 있게끔 읽어들일 수 있다. 이 때 사용되는 클래스는 csv.DictReader/csv.DictWriter이다.

사용법은 csv.Readercsv.Writer하고 대동소이하다. 다만 이는 각 칼럼에 이름을 부여하는 역할을 담당하기 떄문에 실제로 열 머리글행이 없는 csv 파일을 읽어서 별도의 칼럼 이름을 부여하는 것이 가능하다. 열 이름은 문자열의 리스트로 부여하는데, 이를 생략하면 첫 행을 읽어서 파싱할 결과를 사용한다.

그리고 개별적으로 읽은 행은 OrderedDict 포맷으로 취급된다.

csv.Writer는 생성할 때 파일 핸들러와 필드 이름을 받는다. (이때는 반드시 받아야 한다.) 만약 이미 필드 이름을 가지고 있는 DictReader 인스턴스가 있다면, 해당 리더의 fileldnames 속성을 이용해서 동일한 필드 구성을 부여할 수 있다.

다음 코드는 비어있거나, 정수를 포함하는 일련의 데이터를 읽어와서 빈 값은 0으로 만들고 모두 실수형태로 변환해서 저장하는 코드를 담는다. 이 때 원본 파일은 열 이름을 포함하고 있고, 이 구성을 그대로 유지하도록 한다.

with open('A.csv') as inFile:
  reader = csv.DictReader(infile)
  with open('B.csv', 'w') as outFile:
    writer = csv.DictWriter(outFile, reader.filednames)
    writer.writeheader()
    for row in reader:
      d = dict()
      for field in reader.fieldnames:
        d[field] = float(row[field]) if row[field] else 0.0
      writer.writerow(d)

'방언'의 사용

앞서 언급했듯이 CSV는 엄격하게 통제된 규격이 아니다. 따라서 쓰이는 플랫폼이나 프로그램에 따라서 개별적인 방언으로서의 규칙이 있다. csv에서는 몇 가지 포맷 규칙을 방언(csv.Dialect)라는 형식으로 만들어두고 있다. 여기에는 몇 가지 미리 만들어진 인스턴스들이 있다.

  • csv.excel : 기본적으로 쓰이는 포맷팅 규칙으로 엑셀에서 생성한 csv 파일을 읽고 쓸 떄 사용한다.
  • csv.excel_tab : 엑셀에서 '탭으로 구분' 옵션으로 만든 csv 파일
  • csv.unix_dialect : \n을 개행으로 쓰고 모든 필드에 따옴표를 넣는다.

스니퍼

다루려는 csv 파일이 정확하게 어떤 방식으로 만들어져 있는지 알 수 없는 경우, '방언'을 추측해내기 위한 도구로 제공된다. 파일의 처음 일부분을 읽어서 방언을 추측하고 이를 사용할 수 있다.

with open('example.csv') as cFile:
  ## 파일의 앞 1kb 읽어서 어떤 포맷을 사용하는지 알아낸다.
  dialect = csv.Sniffer().sniff(cfile.read(1024))
  reader = csv.Reader(cFile, dialect)
  ...

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함