การอ่านไฟล์. csv ขนาดใหญ่


107

ฉันกำลังพยายามอ่านข้อมูลจากไฟล์. csv ใน Python 2.7 ที่มีมากถึง 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 คือองค์ประกอบทั้งหมดที่เหมาะสมกับเกณฑ์จะแสดงรายการร่วมกันในไฟล์ csv ดังนั้นฉันจึงออกจากลูปเมื่อฉันผ่านไปเพื่อประหยัดเวลา

คำถามของฉันคือ:

  1. ฉันจะจัดการเพื่อให้สิ่งนี้ทำงานกับไฟล์ที่ใหญ่กว่าได้อย่างไร

  2. มีวิธีใดบ้างที่ฉันสามารถทำให้เร็วขึ้นได้?

คอมพิวเตอร์ของฉันมี RAM 8GB ใช้ Windows 7 64 บิตและโปรเซสเซอร์คือ 3.40 GHz (ไม่แน่ใจว่าคุณต้องการข้อมูลอะไร)


1
ฉันทราบว่ามีคำถามที่ดูเหมือนคล้ายกันหลายข้อ แต่ดูเหมือนจะไม่มีคำถามใดที่เจาะจงเพียงพอสำหรับปัญหาของฉันที่จะช่วยได้มาก ขออภัยหากมีสิ่งที่ฉันพลาดไป
Charles Dillon

2
คุณควรเก็บข้อมูลที่อ่านไว้ในฐานข้อมูล (เช่น Sqlite) แทนที่จะเก็บไว้ในหน่วยความจำ จากนั้นคุณสามารถเรียกใช้การประมวลผลเพิ่มเติมเช่นการกรองบนฐานข้อมูล
Michael Butscher

คำตอบ:


159

คุณกำลังอ่านแถวทั้งหมดในรายการจากนั้นประมวลผลรายการนั้น อย่าทำอย่างนั้น

ประมวลผลแถวของคุณเมื่อคุณสร้างมัน หากคุณต้องการกรองข้อมูลก่อนให้ใช้ฟังก์ชันเครื่องกำเนิดไฟฟ้า:

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

40

แม้ว่าคำตอบของ Martijin จะดีที่สุด นี่คือวิธีที่ง่ายกว่าในการประมวลผลไฟล์ csv ขนาดใหญ่สำหรับผู้เริ่มต้น สิ่งนี้ช่วยให้คุณสามารถประมวลผลกลุ่มของแถวหรือทีละกลุ่มได้

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

10
เหตุใดการใช้แพนด้าจึงทำให้ใช้งานง่ายขึ้น
wwii

26
โค้ด 4 บรรทัดดีกว่าเสมอสำหรับมือใหม่อย่างผมเอง
mmann1123

3
รหัส Python ปกติสั้นพอ ๆ กันและให้คุณประมวลผลต่อบรรทัด ฟังก์ชันเครื่องกำเนิดไฟฟ้ามีไว้เพื่อกรองสิ่งต่างๆเท่านั้น คุณจะทำอย่างไรในการกรองแบบเดียวกันใน Pandas?
Martijn Pieters

1
นี่มันเจ๋งมาก! แก้ไขปัญหาของฉันในการโหลดและประมวลผลไฟล์ csv ขนาดใหญ่โดยใช้แพนด้า ขอบคุณ!
Elsa Li

1
ทำงานได้ดีมากแม้ว่าเนื้อหาของบางแถวจะมีหลายบรรทัดก็ตาม!
Dielson Sales

19

ฉันทำการวิเคราะห์การสั่นสะเทือนในปริมาณพอสมควรและดูชุดข้อมูลขนาดใหญ่ (หลายสิบและหลายร้อยล้านจุด) การทดสอบแสดงให้เห็นว่าฉันpandas.read_csv ()ฟังก์ชั่นที่จะเป็น20ครั้งเร็วกว่า numpy.genfromtxt () และฟังก์ชัน genfromtxt () เร็วกว่า numpy.loadtxt () 3 เท่า ดูเหมือนว่าคุณต้องการหมีแพนด้าสำหรับชุดข้อมูลขนาดใหญ่

ผมโพสต์รหัสและข้อมูลชุดที่ผมใช้ในการทดสอบนี้ในบล็อกถกMATLAB VS งูหลามวิเคราะห์การสั่นสะเทือน


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 ' ช่วยให้ฉันอ่านไฟล์ 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คือฟังก์ชันเครื่องกำเนิดไฟฟ้า


ดังนั้นสิ่งนี้จึงทำงานได้อย่างมีประสิทธิภาพเช่นเดียวกับโซลูชันที่ใช้ตัวดำเนินการผลตอบแทน : ขอโทษมันไม่ได้ การเรียกใช้ฟังก์ชันการโทรกลับจะเพิ่มค่าใช้จ่ายมากขึ้นโดยเฉพาะอย่างยิ่งเมื่อคุณต้องจัดการกับสถานะอย่างชัดเจนและแยกกัน
Martijn Pieters

@MartijnPieters ขอบคุณ อัปเดตคำตอบ
Rishabh Agrahari

0

หากคุณใช้แพนด้าและมี RAM จำนวนมาก (เพียงพอที่จะอ่านไฟล์ทั้งหมดลงในหน่วยความจำ) ให้ลองใช้pd.read_csvด้วย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.