Lesen einer riesigen CSV-Datei

107

Ich versuche derzeit, Daten aus CSV-Dateien in Python 2.7 mit bis zu 1 Million Zeilen und 200 Spalten zu lesen (Dateien reichen von 100 MB bis 1,6 GB). Ich kann dies (sehr langsam) für Dateien mit weniger als 300.000 Zeilen tun, aber sobald ich darüber hinausgehe, erhalte ich Speicherfehler. Mein Code sieht folgendermaßen aus:

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

Der Grund für die else-Klausel in der Funktion getstuff ist, dass alle Elemente, die dem Kriterium entsprechen, zusammen in der CSV-Datei aufgelistet werden. Daher verlasse ich die Schleife, wenn ich an ihnen vorbeikomme, um Zeit zu sparen.

Meine Fragen sind:

  1. Wie kann ich es schaffen, dass dies mit den größeren Dateien funktioniert?

  2. Kann ich es auf irgendeine Weise schneller machen?

Mein Computer verfügt über 8 GB RAM, 64-Bit-Windows 7, und der Prozessor ist 3,40 GHz (nicht sicher, welche Informationen Sie benötigen).

Charles Dillon
quelle
1
Ich bin mir bewusst, dass es mehrere ähnlich erscheinende Fragen gibt, aber keine davon schien spezifisch genug für mein Problem zu sein, um viel zu helfen. Entschuldigung, wenn es eine gibt, die ich verpasst habe.
Charles Dillon
2
Sie sollten die gelesenen Daten in einer Datenbank (z. B. Sqlite) speichern, anstatt sie im Speicher zu behalten. Sie können dann eine weitere Verarbeitung wie das Filtern auf der Datenbank ausführen
Michael Butscher

Antworten:

158

Sie lesen alle Zeilen in eine Liste und verarbeiten diese Liste dann. Tu das nicht .

Verarbeiten Sie Ihre Zeilen, während Sie sie produzieren. Wenn Sie zuerst die Daten filtern müssen, verwenden Sie eine Generatorfunktion:

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

Ich habe auch Ihren Filtertest vereinfacht. Die Logik ist dieselbe, aber prägnanter.

Da Sie nur eine einzelne Folge von Zeilen abgleichen, die dem Kriterium entsprechen, können Sie auch Folgendes verwenden:

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

Sie können jetzt getstuff()direkt eine Schleife durchführen . Machen Sie dasselbe in getdata():

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

Schleifen Sie nun direkt getdata()in Ihrem Code:

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

Sie haben jetzt nur noch eine Zeile im Speicher, anstatt Tausende von Zeilen pro Kriterium.

yieldmacht eine Funktion zu einer Generatorfunktion , was bedeutet, dass sie keine Arbeit erledigt, bis Sie anfangen, sie zu durchlaufen.

Martijn Pieters
quelle
Erhalten Sie die gleiche Speichereffizienz, wenn Sie diese Technik mit verwenden csv.DictReader? Weil meine Tests an einer 2,5-GB-CSV-Datei zeigen, dass der Versuch, zeilenweise wie folgt zu iterieren, wenn dies verwendet wird, dazu führt, csv.readerdass der Python-Prozess auf die volle 2,5-GB-Speichernutzung anwächst.
user5359531
@ user5359531, das angibt, dass Sie irgendwo Verweise auf die Wörterbuchobjekte behalten. DictReader selbst behält keine Referenzen bei, sodass das Problem an anderer Stelle liegt.
Martijn Pieters
39

Obwohl Martijins Antwort wahrscheinlich die beste ist. Hier finden Sie eine intuitivere Möglichkeit, große CSV-Dateien für Anfänger zu verarbeiten. Auf diese Weise können Sie Gruppen von Zeilen oder Blöcken gleichzeitig verarbeiten.

import pandas as pd
chunksize = 10 ** 8
for chunk in pd.read_csv(filename, chunksize=chunksize):
    process(chunk)
mmann1123
quelle
9
Warum macht es die Verwendung von Pandas intuitiver?
wwii
25
4 Codezeilen sind für Neulinge wie mich immer besser.
mmann1123
3
Der reguläre Python-Code ist genauso kurz und ermöglicht die Verarbeitung pro Zeile. Die Generatorfunktion dient nur zum Filtern von Inhalten. Wie würden Sie die gleiche Filterung in Pandas durchführen?
Martijn Pieters
1
Das ist fantastisch! Ich habe mein Problem beim Laden und Verarbeiten großer CSV-Dateien mit Pandas gelöst. Vielen Dank!
Elsa Li
1
Es funktioniert sehr gut, auch wenn sich der Inhalt einiger Zeilen über mehrere Zeilen erstreckt!
Dielson Sales
19

Ich mache eine ganze Menge Schwingungsanalysen und schaue mir große Datenmengen an (Dutzende und Hunderte Millionen Punkte). Meine Tests haben gezeigt, dass die Funktion pandas.read_csv () 20- mal schneller ist als numpy.genfromtxt (). Und die Funktion genfromtxt () ist dreimal schneller als die Funktion numpy.loadtxt (). Es scheint, dass Sie Pandas für große Datenmengen benötigen .

Ich habe den Code und die Datensätze, die ich in diesen Tests verwendet habe, in einem Blog veröffentlicht, in dem MATLAB vs Python für die Schwingungsanalyse besprochen wurden .

Steve
quelle
3
Das Hauptproblem des OP war nicht die Geschwindigkeit, sondern die Erschöpfung des Speichers. Die Verwendung einer anderen Funktion zum Verarbeiten der Datei selbst beseitigt nicht die Nachteile des Einlesens in eine Liste, anstatt einen Stream-Prozessor zu verwenden.
Pydsigner
6

Was für mich funktioniert hat und ist superschnell ist

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)

Eine andere funktionierende Lösung ist:

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
Yury Wallet
quelle
Lädt die df_train=df_train.compute()Zeile in Ihrer ersten Lösung nicht den gesamten Datensatz in den Speicher ... was versucht er nicht zu tun?
Sam Dillard
3

Für jemanden, der auf diese Frage landet. Die Verwendung von Pandas mit ' Chunksize ' und ' Usecols ' hat mir geholfen, eine riesige Zip-Datei schneller als die anderen vorgeschlagenen Optionen zu lesen.

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)
ewalel
quelle
1

Hier ist eine andere Lösung für 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

Hier datareaderist eine Generatorfunktion.

Rishabh Agrahari
quelle
Dies funktioniert also genauso effizient wie die Lösung, die den Ertragsoperator verwendet. : Entschuldigung, das tut es nicht. Der Aufruf der Rückruffunktion erhöht den Overhead, insbesondere da Sie den Status dort explizit und separat behandeln müssen.
Martijn Pieters
@ MartinijnPieters Danke. Die Antwort wurde aktualisiert.
Rishabh Agrahari
0

Wenn Sie Pandas verwenden und über viel RAM verfügen (genug, um die gesamte Datei in den Speicher einzulesen), versuchen Sie es pd.read_csvmit low_memory=Falsez.

import pandas as pd
data = pd.read_csv('file.csv', low_memory=False)
Mike T.
quelle