거대한 .csv 파일 읽기


107

현재 Python 2.7의 .csv 파일에서 최대 1 백만 개의 행과 200 개의 열 (파일 범위는 100MB에서 1.6GB)로 데이터를 읽으려고합니다. 행이 300,000 개 미만인 파일에 대해 (매우 느리게) 수행 할 수 있지만 그 이상으로 이동하면 메모리 오류가 발생합니다. 내 코드는 다음과 같습니다.

def getdata(filename, criteria):
    data=[]
    for criterion in criteria:
        data.append(getstuff(filename, criteron))
    return data

def getstuff(filename, criterion):
    import csv
    data=[]
    with open(filename, "rb") as csvfile:
        datareader=csv.reader(csvfile)
        for row in datareader: 
            if row[3]=="column header":
                data.append(row)
            elif len(data)<2 and row[3]!=criterion:
                pass
            elif row[3]==criterion:
                data.append(row)
            else:
                return data

getstuff 함수에서 else 절의 이유는 기준에 맞는 모든 요소가 csv 파일에 함께 나열되므로 시간을 절약하기 위해 지나칠 때 루프를 떠나기 때문입니다.

내 질문은 다음과 같습니다.

  1. 더 큰 파일에서이 작업을 수행하려면 어떻게해야합니까?

  2. 더 빨리 만들 수있는 방법이 있습니까?

내 컴퓨터에는 64 비트 Windows 7을 실행하는 8GB RAM이 있으며 프로세서는 3.40GHz입니다 (필요한 정보가 무엇인지 확실하지 않음).


1
유사한 것처럼 보이는 질문이 여러 개 있다는 것을 알고 있지만 그중 어느 것도 내 문제에 대해 충분히 구체적으로 설명하지 않아 많은 도움이되는 것 같지는 않았습니다. 내가 놓친 것이 있다면 죄송합니다.
Charles Dillon

2
읽은 데이터는 메모리에 보관하는 대신 데이터베이스 (예 : Sqlite)에 저장해야합니다. 그런 다음 DB에 필터링과 같은 추가 처리를 실행할 수 있습니다
마이클 Butscher에게

답변:


158

모든 행을 목록으로 읽은 다음 해당 목록을 처리합니다. 그렇게하지 마십시오 .

행을 생성 할 때 처리하십시오. 데이터를 먼저 필터링해야하는 경우 생성기 함수를 사용하십시오.

import csv

def getstuff(filename, criterion):
    with open(filename, "rb") as csvfile:
        datareader = csv.reader(csvfile)
        yield next(datareader)  # yield the header row
        count = 0
        for row in datareader:
            if row[3] == criterion:
                yield row
                count += 1
            elif count:
                # done when having read a consecutive series of rows 
                return

또한 필터 테스트를 단순화했습니다. 논리는 동일하지만 더 간결합니다.

기준과 일치하는 단일 행 시퀀스 만 일치하므로 다음을 사용할 수도 있습니다.

import csv
from itertools import dropwhile, takewhile

def getstuff(filename, criterion):
    with open(filename, "rb") as csvfile:
        datareader = csv.reader(csvfile)
        yield next(datareader)  # yield the header row
        # first row, plus any subsequent rows that match, then stop
        # reading altogether
        # Python 2: use `for row in takewhile(...): yield row` instead
        # instead of `yield from takewhile(...)`.
        yield from takewhile(
            lambda r: r[3] == criterion,
            dropwhile(lambda r: r[3] != criterion, datareader))
        return

이제 getstuff()직접 반복 할 수 있습니다 . 에서 동일하게 수행하십시오 getdata().

def getdata(filename, criteria):
    for criterion in criteria:
        for row in getstuff(filename, criterion):
            yield row

이제 getdata()코드에서 직접 반복 합니다.

for row in getdata(somefilename, sequence_of_criteria):
    # process row

이제 기준 당 수천 줄 대신 메모리에 하나의 행만 보유 합니다.

yield함수를 생성기 함수로 만듭니다. 즉, 루프를 시작할 때까지 작업을 수행하지 않습니다.


이 기술을 사용할 때 동일한 메모리 효율성을 얻 csv.DictReader습니까? 2.5GB .csv 파일에 대한 내 테스트는 대신 사용할 때 이와 같이 행 단위로 반복하려고 시도 csv.reader하면 Python 프로세스가 전체 2.5GB 메모리 사용량으로 증가 한다는 것을 보여주기 때문 입니다.
user5359531

@ user5359531은 사전 객체에 대한 참조를 어딘가에 보관함을 나타냅니다. DictReader 자체 는 참조를 유지하지 않으므로 문제는 다른 곳에 있습니다.
Martijn Pieters

39

Martijin의 대답이 가장 좋습니다. 초보자를 위해 대용량 csv 파일을보다 직관적으로 처리하는 방법이 있습니다. 이를 통해 한 번에 행 그룹 또는 청크를 처리 할 수 ​​있습니다.

import pandas as pd
chunksize = 10 ** 8
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)

9
Pandas를 사용하면 더 직관적 인 이유는 무엇입니까?
wwii

25
4 줄의 코드는 저와 같은 초보자에게 항상 더 좋습니다.
mmann1123

3
일반 Python 코드도 마찬가지로 짧으며 한 줄에 처리 할 수 ​​있습니다. 제너레이터 기능은 물건을 필터링하기 위해서만 존재합니다. Pandas에서 동일한 필터링을 수행하는 방법은 무엇입니까?
Martijn Pieters

1
굉장합니다! 팬더를 사용하여 큰 CSV 파일을로드하고 처리하는 문제를 해결했습니다. 감사!
Elsa Li

1
일부 행의 내용이 여러 줄에 걸쳐있는 경우에도 매우 잘 작동합니다!
Dielson Sales

19

저는 상당한 양의 진동 분석을 수행하고 대규모 데이터 세트 (수천 및 수억 포인트)를 봅니다. 내 테스트에 따르면 pandas.read_csv () 함수 는 numpy.genfromtxt () 보다 20 배 빠릅니다. 그리고 genfromtxt () 함수는 numpy.loadtxt ()보다 3 배 빠릅니다. 큰 데이터 세트에는 팬더 가 필요한 것 같습니다 .

이 테스트에서 사용한 코드와 데이터 세트 를 진동 분석을위한 MATLAB과 Python을 논의하는 블로그에 게시했습니다 .


3
OP의 주요 문제는 속도 문제가 아니라 메모리 고갈 문제였습니다. 파일 자체를 처리하기 위해 다른 기능을 사용한다고해서 스트림 프로세서를 사용하는 것보다 목록으로 읽는 것의 단점이 제거되지는 않습니다.
pydsigner

6

나를 위해 일했고 초고속은

import pandas as pd
import dask.dataframe as dd
import time
t=time.clock()
df_train = dd.read_csv('../data/train.csv', usecols=[col1, col2])
df_train=df_train.compute()
print("load train: " , time.clock()-t)

또 다른 작업 솔루션은 다음과 같습니다.

import pandas as pd 
from tqdm import tqdm

PATH = '../data/train.csv'
chunksize = 500000 
traintypes = {
'col1':'category',
'col2':'str'}

cols = list(traintypes.keys())

df_list = [] # list to hold the batch dataframe

for df_chunk in tqdm(pd.read_csv(PATH, usecols=cols, dtype=traintypes, chunksize=chunksize)):
    # Can process each chunk of dataframe here
    # clean_data(), feature_engineer(),fit()

    # Alternatively, append the chunk to list and merge all
    df_list.append(df_chunk) 

# Merge all dataframes into one dataframe
X = pd.concat(df_list)

# Delete the dataframe list to release memory
del df_list
del df_chunk

df_train=df_train.compute()첫 번째 솔루션 의 라인이 전체 데이터 세트를 메모리에로드 하지 않습니다 ... 그가하지 않으려 고하는 것은 무엇입니까?
Sam Dillard

3

이 질문에 착수하는 사람을 위해. ' chunksize '및 ' usecols '와 함께 pandas 를 사용 하면 다른 제안 된 옵션보다 큰 zip 파일을 더 빨리 읽을 수있었습니다.

import pandas as pd

sample_cols_to_keep =['col_1', 'col_2', 'col_3', 'col_4','col_5']

# First setup dataframe iterator, ‘usecols’ parameter filters the columns, and 'chunksize' sets the number of rows per chunk in the csv. (you can change these parameters as you wish)
df_iter = pd.read_csv('../data/huge_csv_file.csv.gz', compression='gzip', chunksize=20000, usecols=sample_cols_to_keep) 

# this list will store the filtered dataframes for later concatenation 
df_lst = [] 

# Iterate over the file based on the criteria and append to the list
for df_ in df_iter: 
        tmp_df = (df_.rename(columns={col: col.lower() for col in df_.columns}) # filter eg. rows where 'col_1' value grater than one
                                  .pipe(lambda x:  x[x.col_1 > 0] ))
        df_lst += [tmp_df.copy()] 

# And finally combine filtered df_lst into the final lareger output say 'df_final' dataframe 
df_final = pd.concat(df_lst)

1

다음은 Python3에 대한 또 다른 솔루션입니다.

import csv
with open(filename, "r") as csvfile:
    datareader = csv.reader(csvfile)
    count = 0
    for row in datareader:
        if row[3] in ("column header", criterion):
            doSomething(row)
            count += 1
        elif count > 2:
            break

여기 datareader생성기 함수가 있습니다.


따라서 이것은 yield operator를 사용하는 솔루션만큼 효율적으로 작동합니다. : 미안합니다. 콜백 함수 호출은 특히 상태를 명시 적으로 개별적으로 처리해야하기 때문에 더 많은 오버 헤드를 추가합니다.
Martijn Pieters

@MartijnPieters 감사합니다. 답변을 업데이트했습니다.
Rishabh 인스 Agrahari

0

pandas를 사용하고 있고 RAM이 많은 경우 (전체 파일을 메모리로 읽을 수있을만큼) pd.read_csvwith를 사용해보십시오 low_memory=False. 예 :

import pandas as pd
data = pd.read_csv('file.csv', low_memory=False)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.