Đọc một tệp .csv lớn


107

Tôi hiện đang cố đọc dữ liệu từ các tệp .csv trong Python 2.7 với tối đa 1 triệu hàng và 200 cột (các tệp có phạm vi từ 100mb đến 1,6gb). Tôi có thể thực hiện việc này (rất chậm) đối với các tệp có dưới 300.000 hàng, nhưng khi tôi vượt lên trên, tôi gặp lỗi bộ nhớ. Mã của tôi trông như thế này:

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

Lý do cho mệnh đề else trong hàm getstuff là tất cả các phần tử phù hợp với tiêu chí sẽ được liệt kê cùng nhau trong tệp csv, vì vậy tôi để lại vòng lặp khi vượt qua chúng để tiết kiệm thời gian.

Câu hỏi của tôi là:

  1. Làm cách nào tôi có thể quản lý để điều này hoạt động với các tệp lớn hơn?

  2. Có cách nào tôi có thể làm cho nó nhanh hơn không?

Máy tính của tôi có RAM 8gb, chạy Windows 7 64bit và bộ xử lý là 3,40 GHz (không rõ bạn cần thông tin gì).


1
Tôi biết rằng có một số câu hỏi có vẻ giống nhau, nhưng không có câu hỏi nào trong số đó dường như đủ cụ thể cho vấn đề của tôi để giúp được nhiều. Xin lỗi nếu có một cái mà tôi đã bỏ lỡ.
Charles Dillon

2
Bạn nên lưu trữ dữ liệu đã đọc trong cơ sở dữ liệu (ví dụ: Sqlite) thay vì giữ trong bộ nhớ. Sau đó bạn có thể chạy tiếp tục xử lý như lọc trên db
Michael Butscher

Câu trả lời:


158

Bạn đang đọc tất cả các hàng thành một danh sách, sau đó xử lý danh sách đó. Đừng làm vậy .

Xử lý các hàng của bạn khi bạn sản xuất chúng. Nếu bạn cần lọc dữ liệu trước, hãy sử dụng hàm trình tạo:

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

Tôi cũng đã đơn giản hóa việc kiểm tra bộ lọc của bạn; logic giống nhau nhưng ngắn gọn hơn.

Bởi vì bạn chỉ khớp một chuỗi các hàng phù hợp với tiêu chí, bạn cũng có thể sử dụng:

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

Bây giờ bạn có thể lặp lại getstuff()trực tiếp. Làm tương tự trong getdata():

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

Bây giờ lặp lại trực tiếp getdata()trong mã của bạn:

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

Bây giờ bạn chỉ giữ một hàng trong bộ nhớ, thay vì hàng nghìn dòng cho mỗi tiêu chí.

yieldbiến một hàm trở thành một hàm của trình tạo , có nghĩa là nó sẽ không thực hiện bất kỳ công việc nào cho đến khi bạn bắt đầu lặp lại nó.


bạn có nhận được cùng hiệu quả bộ nhớ khi sử dụng kỹ thuật này với csv.DictReader? Bởi vì các thử nghiệm của tôi trên tệp .csv 2,5 GB cho thấy rằng việc cố gắng lặp lại từng hàng như thế này khi sử dụng thay vì csv.readerkhiến quy trình Python phát triển đến mức sử dụng bộ nhớ 2,5 GB đầy đủ.
user5359531

@ user5359531 sẽ cho biết bạn giữ các tham chiếu đến các đối tượng từ điển ở đâu đó. Bản thân DictReader không giữ lại các tham chiếu nên vấn đề nằm ở chỗ khác.
Martijn Pieters

39

Mặc dù câu trả lời của Martijin là tốt nhất. Đây là một cách trực quan hơn để xử lý các tệp csv lớn cho người mới bắt đầu. Điều này cho phép bạn xử lý các nhóm hàng hoặc nhóm cùng một lúc.

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

9
Tại sao sử dụng gấu trúc làm cho nó trực quan hơn?
wwii

25
4 dòng mã luôn tốt hơn cho người mới như tôi.
mmann1123

3
Mã Python thông thường cũng ngắn và cho phép bạn xử lý trên từng dòng. Chức năng máy phát điện chỉ ở đó để lọc nội dung; bạn sẽ làm thế nào để thực hiện cùng một bộ lọc trong Pandas?
Martijn Pieters

1
Điều này thật tuyệt! Đã giải quyết vấn đề tải và xử lý tệp csv lớn của tôi bằng gấu trúc. Cảm ơn!
Elsa Li

1
Nó hoạt động rất tốt ngay cả khi nội dung của một số hàng trải dài trên nhiều dòng!
Bán hàng Dielson

19

Tôi thực hiện một lượng lớn phân tích rung động và xem xét các tập dữ liệu lớn (hàng chục và hàng trăm triệu điểm). Thử nghiệm của tôi cho thấy pandas.read_csv () chức năng là 20 nhanh hơn numpy.genfromtxt lần (). Và hàm genfromtxt () nhanh hơn gấp 3 lần so với hàm numpy.loadtxt (). Có vẻ như bạn cần gấu trúc cho các tập dữ liệu lớn.

Tôi đã đăng mã và tập dữ liệu mà tôi đã sử dụng trong thử nghiệm này trên một blog thảo luận về MATLAB và Python để phân tích rung động .


3
Vấn đề chính của OP không phải là tốc độ, mà là sự cạn kiệt bộ nhớ. Bản thân việc sử dụng một chức năng khác để xử lý tệp không loại bỏ được nhược điểm của việc đọc nó vào danh sách hơn là sử dụng bộ xử lý luồng.
pydsigner

6

những gì đã làm việc cho tôi và là siêu nhanh là

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)

Một giải pháp làm việc khác là:

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

không những df_train=df_train.compute()dòng trong dung dịch đầu tiên của bạn tải toàn bộ dữ liệu vào bộ nhớ ... mà là những gì anh ta cố gắng không để làm gì?
Sam Dillard

3

Đối với một người đặt câu hỏi này. Sử dụng gấu trúc với ' chunksize ' và ' usecols ' đã giúp tôi đọc một tệp zip khổng lồ nhanh hơn các tùy chọn được đề xuất khác.

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

đây là một giải pháp khác cho 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

đây datareaderlà một chức năng máy phát điện.


Vì vậy, điều này hoạt động hiệu quả như giải pháp sử dụng toán tử năng suất. : xin lỗi, nó không. Lệnh gọi hàm gọi lại làm tăng thêm chi phí, đặc biệt là vì bạn phải xử lý trạng thái một cách rõ ràng và riêng biệt.
Martijn Pieters

@MartijnPieters Cảm ơn. Đã cập nhật câu trả lời.
Rishabh Agrahari

0

Nếu bạn đang sử dụng gấu trúc và có nhiều RAM (đủ để đọc toàn bộ tệp vào bộ nhớ), hãy thử sử dụng pd.read_csvvới low_memory=False, ví dụ:

import pandas as pd
data = pd.read_csv('file.csv', low_memory=False)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.