it-swarm.com.de

Wie kann ich das Lesen mehrerer Dateien beschleunigen und die Daten in einen Datenrahmen einfügen?

Ich habe eine Reihe von Textdateien, beispielsweise 50, die ich in ein massives Datenframe einlesen muss. Im Moment verwende ich die folgenden Schritte.

  1. Lesen Sie jede Datei und prüfen Sie, was die Etiketten sind. Die Informationen, die ich brauche, sind oft in den ersten Zeilen enthalten. Dieselben Bezeichnungen werden für den Rest der Datei wiederholt, wobei jeweils unterschiedliche Datentypen aufgeführt werden.
  2. Erstellen Sie einen Datenrahmen mit diesen Beschriftungen.
  3. Lesen Sie die Datei erneut und füllen Sie den Datenrahmen mit Werten.
  4. Verketten Sie diesen Datenrahmen mit einem Master-Datenrahmen.

Dies funktioniert ziemlich gut für Dateien mit der Größe von 100 KB - einige Minuten, aber bei 50 MB dauert es nur Stunden und ist nicht praktisch. 

Wie kann ich meinen Code optimieren? Im Speziellen -

  1. Wie kann ich feststellen, welche Funktionen die meiste Zeit in Anspruch nehmen, die ich optimieren muss? Ist es das Lesen der Datei? Ist es das Schreiben an den Datenrahmen? Wo verbringe ich mein Programm?
  2. Sollte ich Multithreading oder Multiprocessing in Betracht ziehen?
  3. Kann ich den Algorithmus verbessern?
    • Lesen Sie die gesamte Datei auf einmal in eine Liste ein, anstatt Zeile für Zeile,
    • Analysieren Sie die Daten in Abschnitten/ganzen Dateien und nicht Zeile für Zeile.
    • Weisen Sie dem Datenrahmen Daten in Abschnitten/auf einmal zu, anstatt Zeile für Zeile.
  4. Gibt es noch etwas, was ich tun kann, um meinen Code schneller auszuführen?

Hier ist ein Beispielcode. Mein eigener Code ist etwas komplexer, da die Textdateien komplexer sind, so dass ich etwa 10 reguläre Ausdrücke und mehrere while-Schleifen verwenden muss, um die Daten einzulesen und sie der richtigen Stelle im richtigen Array zuzuordnen. Um die MWE einfach zu halten, habe ich in den Eingabedateien für die MWE auch keine sich wiederholenden Beschriftungen verwendet. Daher möchte ich die Datei zweimal ohne Grund lesen. Ich hoffe das ergibt Sinn!

import re
import pandas as pd

df = pd.DataFrame()
paths = ["../gitignore/test1.txt", "../gitignore/test2.txt"]
reg_ex = re.compile('^(.+) (.+)\n')
# read all files to determine what indices are available
for path in paths:
    file_obj = open(path, 'r')
    print file_obj.readlines()

['a 1\n', 'b 2\n', 'end']
['c 3\n', 'd 4\n', 'end']

indices = []
for path in paths:
    index = []
    with open(path, 'r') as file_obj:
        line = True
        while line:
            try:
                line = file_obj.readline()
                match = reg_ex.match(line)
                index += match.group(1)
            except AttributeError:
                pass
    indices.append(index)
# read files again and put data into a master dataframe
for path, index in Zip(paths, indices):
    subset_df = pd.DataFrame(index=index, columns=["Number"])
    with open(path, 'r') as file_obj:
        line = True
        while line:
            try:
                line = file_obj.readline()
                match = reg_ex.match(line)
                subset_df.loc[[match.group(1)]] = match.group(2)
            except AttributeError:
                pass
    df = pd.concat([df, subset_df]).sort_index()
print df

  Number
a      1
b      2
c      3
d      4

Meine Eingabedateien:

test1.txt

a 1
b 2
end

test2.txt

c 3
d 4
end
36
bluprince13

Es stellt sich heraus, dass das Erstellen eines leeren DataFrame zuerst, das Durchsuchen des Index nach dem richtigen Ort für eine Datenzeile und dann das Aktualisieren nur einer Zeile des DataFrames ein dumm zeitaufwändiger Prozess ist.

Eine viel schnellere Methode ist das Lesen des Inhalts der Eingabedatei in eine primitive Datenstruktur, z. B. eine Liste von Listen oder eine Liste von Diktaten, und das Konvertieren in einen DataFrame.

Verwenden Sie Listen, wenn sich alle Daten, die Sie lesen, in denselben Spalten befinden. Andernfalls verwenden Sie diktiken, um explizit anzugeben, in welche Spalte jedes Datenbit gehen soll.

Update 18. Januar: Dies ist verlinkt mit Wie parkt man komplexe Textdateien mit Python? Ich habe auch einen Blogartikel geschrieben, in dem erklärt wird, wie komplexe Dateien für Anfänger analysiert werden. .

1
bluprince13

Bevor Sie den Multiprocessing-Hammer herausziehen, sollten Sie zunächst ein Profil erstellen. Mit cProfile können Sie schnell nachsehen, welche Funktionen lange dauern. Wenn sich Ihre Leitungen alle in einem einzigen Funktionsaufruf befinden, werden sie als Bibliotheksaufrufe angezeigt. line_profiler ist besser, aber es dauert etwas mehr Zeit für das Setup. 

HINWEIS. Wenn Sie ipython verwenden, können Sie% timeit (magischer Befehl für das Timeit-Modul) und% prun (magischer Befehl für das Profilmodul) verwenden, um sowohl Ihre Anweisungen als auch die Funktionen zeitlich zu steuern. Bei einer Google-Suche werden einige Handbücher angezeigt.

Pandas ist eine wundervolle Bibliothek, aber ich war gelegentlich ein Opfer, weil ich sie schlecht benutzt hatte, mit abscheulichen Ergebnissen. Seien Sie insbesondere vorsichtig mit den Operationen append ()/concat (). Das könnte Ihr Engpass sein, aber Sie sollten ein Profil erstellen, um sicher zu sein. Normalerweise sind die Operationen numpy.vstack () und numpy.hstack () schneller, wenn Sie keine Index-/Spaltenausrichtung durchführen müssen. In Ihrem Fall sieht es so aus, als könnten Sie mit Serien- oder 1-D-numpy-Narrays auskommen, was Zeit sparen kann.

Übrigens, ein try-Block in Python ist viel langsamer, oft 10x oder mehr, als nach einer ungültigen Bedingung zu suchen. Stellen Sie also sicher, dass Sie ihn unbedingt benötigen, wenn Sie ihn für jede einzelne Zeile in eine Schleife einfügen. Dies ist wahrscheinlich der andere Hacker der Zeit; Ich kann mir vorstellen, dass Sie den try-Block blockiert haben, um nach AttributeError zu suchen, falls match.group (1) ausfällt. Ich würde zuerst nach einem gültigen Treffer suchen. 

Selbst diese kleinen Modifikationen sollten ausreichen, damit Ihr Programm deutlich schneller ausgeführt werden kann, bevor Sie drastische Schritte wie Multiprocessing versuchen. Diese Python-Bibliotheken sind großartig, bringen jedoch neue Herausforderungen mit sich.

15
clocker

Ich habe dies oft verwendet, da es eine besonders einfache Implementierung von Multiprocessing ist. 

import pandas as pd
from multiprocessing import Pool

def reader(filename):
    return pd.read_Excel(filename)

def main():
    pool = Pool(4) # number of cores you want to use
    file_list = [file1.xlsx, file2.xlsx, file3.xlsx, ...]
    df_list = pool.map(reader, file_list) #creates a list of the loaded df's
    df = pd.concat(df_list) # concatenates all the df's into a single df

if __== '__main__':
    main()

Damit sollten Sie in der Lage sein, die Geschwindigkeit Ihres Programms ohne allzu großen Arbeitsaufwand erheblich zu steigern. Wenn Sie nicht wissen, über wie viele Prozessoren Sie verfügen, können Sie dies überprüfen, indem Sie Ihre Shell hochziehen und tippen

echo %NUMBER_OF_PROCESSORS%

BEARBEITEN: Um dies noch schneller zu machen, sollten Sie Ihre Dateien in CSV-Dateien ändern und die Pandas-Funktion verwenden. pandas.read_csv

11
Некто

Vor allem, wenn Sie die Datei mehrmals lesen, scheint dies der Engpass zu sein. Lesen Sie die Datei in ein String-Objekt und verwenden Sie dann mehrmals cStringIO

Zweitens haben Sie keinen Grund gezeigt, die Indizes zu erstellen, bevor Sie alle Dateien einlesen. Warum verwenden Sie Pandas für IO? Es scheint, als könnten Sie es in regulären Python-Datenstrukturen aufbauen (möglicherweise mit __slots__) und es dann in den Master-Datenrahmen einfügen. Wenn Sie den Datei-X-Index nicht benötigen, bevor Sie die Datei Y lesen (wie die 2. Schleife anscheinend vorschlägt), müssen Sie die Dateien nur einmal durchlaufen.

Drittens können Sie entweder einfache split/strip für die Zeichenfolgen verwenden, um die durch Leerzeichen getrennten Token herauszuziehen, oder, wenn es komplizierter ist (es gibt String-Anführungszeichen und dergleichen), das CSV-Modul aus der Python-Standardbibliothek verwenden. Bis Sie zeigen, wie Sie Ihre Daten tatsächlich aufbauen, ist es schwierig, eine entsprechende Korrektur vorzuschlagen.

Was Sie bisher gezeigt haben, lässt sich mit dem Einfachen ziemlich schnell erledigen 

for path in paths:
    data = []
    with open(path, 'r') as file_obj:
        for line in file_obj:
            try:
                d1, d2 = line.strip().split()
            except ValueError:
                pass
            data.append(d1, int(d2)))
    index, values = Zip(*data)
    subset_df = pd.DataFrame({"Number": pd.Series(values, index=index)})

Der Unterschied bei den Timings ist, wenn ich auf einer virtuellen Maschine mit nicht belegtem Speicherplatz laufe (die generierten Dateien sind ungefähr 24 MB groß):

import pandas as pd
from random import randint
from itertools import combinations
from posix import fsync


outfile = "indexValueInput"

for suffix in ('1', '2'):
    with open(outfile+"_" + suffix, 'w') as f:
        for i, label in enumerate(combinations([chr(i) for i in range(ord('a'), ord('z')+1)], 8)) :
            val = randint(1, 1000000)
            print >>f, "%s %d" % (''.join(label), val)
            if i > 3999999:
                break
        print >>f, "end"
        fsync(f.fileno())

def readWithPandas():
    data = []
    with open(outfile + "_2", 'r') as file_obj:
        for line in file_obj:
            try:
                d1, d2 = str.split(line.strip())
            except ValueError:
                pass
            data.append((d1, int(d2)))
    index, values = Zip(*data)
    subset_df = pd.DataFrame({"Numbers": pd.Series(values, index=index)})

def readWithoutPandas():
    data = []
    with open(outfile+"_1", 'r') as file_obj:
        for line in file_obj:
            try:
                d1, d2 = str.split(line.strip())
            except ValueError:
                pass
            data.append((d1, int(d2)))
    index, values = Zip(*data)

def time_func(func, *args):
    import time
    print "timing function", str(func.func_name)
    tStart = time.clock()
    func(*args)
    tEnd = time.clock()
    print "%f seconds " % (tEnd - tStart)

time_func(readWithoutPandas)
time_func(readWithPandas)

Die resultierenden Zeiten sind:

timing function readWithoutPandas
4.616853 seconds 
timing function readWithPandas
4.931765 seconds 

Sie können diese Funktionen mit Ihrem Indexaufbau ausprobieren und sehen, wie der Zeitunterschied aussehen würde. Es ist fast sicher, dass die Verlangsamung durch das Lesen mehrerer Festplatten erfolgt. Und da Pandas keine Zeit braucht, um Ihr Datenframe aus einem Wörterbuch aufzubauen, sollten Sie besser herausfinden, wie Sie Ihren Index in Python aufbauen, bevor Sie die Daten an Pandas weitergeben. Führen Sie jedoch sowohl das Lesen der Daten als auch den Aufbau des Index auf einer Festplatte aus.

Ich denke, ein weiterer Nachteil besteht darin, dass Sie, wenn Sie aus Ihrem Code heraus drucken, erwarten, dass dies sehr viel Zeit in Anspruch nimmt. Die Zeit, die benötigt wird, um Klartext auf einen Zwerg zu schreiben, die Zeit, die benötigt wird, um auf die Festplatte zu schreiben.

3

Allgemeine Überlegungen zum Python:

Vor allem zur Zeitmessung können Sie einen solchen Ausschnitt verwenden:

from time import time, sleep


class Timer(object):
    def __init__(self):
        self.last = time()


    def __call__(self):
        old = self.last
        self.last = time()
        return self.last - old

    @property
    def elapsed(self):
        return time() - self.last



timer = Timer()

sleep(2)
print timer.elapsed
print timer()
sleep(1)
print timer()

Dann könnten Sie den Laufcode viele Male vergleichen und nach dem Unterschied suchen.

Dazu kommentiere ich inline:

with open(path, 'r') as file_obj:
    line = True
    while line: #iterate on realdines instead.
        try:
            line = file_obj.readline()
            match = reg_ex.match(line)
            index += match.group(1)
            #if match:
            #    index.extend(match.group(1)) # or extend

        except AttributeError:
            pass

Ihr vorheriger Code war nicht wirklich Pythonic, Sie sollten/mit Ausnahme von ... probieren. Versuchen Sie dann nur, die minimal möglichen Zeilen einzugeben.

Die gleichen Hinweise gelten für den zweiten Codeblock.

Wenn Sie dieselben Dateien mehrmals lesen müssen. Sie können sie unter Verwendung von StringIO in RAM speichern, oder Sie behalten ein {path: content} -Dikt, das Sie nur einmal lesen.

Python-Regex ist bekanntermaßen langsam, Ihre Daten scheinen ziemlich einfach zu sein. Sie können die Verwendung von Split- und Strip-Methoden für Ihre Eingabezeilen in Betracht ziehen.

 striped=[l.split() for l in [c.strip() for c in file_desc.readlines()] if l] 

Ich empfehle Ihnen, dies zu lesen: https://Gist.github.com/JeffPaine/6213790 das entsprechende Video ist hier https://www.youtube.com/watch?v=OSGv2VnC0go

2
cgte

Sie können das Multiprocessing-Modell importieren und einen Pool von Arbeitsprozessen verwenden, um mehrere Dateien gleichzeitig als Dateiobjekte zu öffnen, wodurch der Ladeabschnitt des Codes beschleunigt wird. Um die Zeit zu testen, importieren Sie entweder die Funktion datetime und verwenden Sie den folgenden Code:

import datetime
start=datetime.datetime.now()

#part of your code goes here

execTime1=datetime.datetime.now()
print(execTime1-start)

#the next part of your code goes here

execTime2=datetime.datetime.now()
print(execTime2-execTime1)

Wenn Sie jede Datei nur einmal lesen möchten, sollten Sie in Erwägung ziehen, ein anderes Multiprocessing-Skript zu verwenden, um eine Liste von Zeilen in jeder Datei zu erstellen, sodass Sie ohne eine Datei-E/A-Operation nach einer Übereinstimmung suchen können.

1
Ron Distante

Lesen Sie die Dateien mit pd.read_csv direkt in einen Pandas-Datenrahmen. So erstellen Sie Ihr subset_df. Verwenden Sie Methoden wie "Skipfooter", um die Zeilen am Ende der Datei zu überspringen, die Sie nicht benötigen. Es gibt viele weitere Methoden, die einige der von Ihnen verwendeten Regex-Schleifenfunktionen ersetzen können, z. B. error_bad_lines und skip_blank_lines.

Verwenden Sie anschließend von Pandas bereitgestellte Tools, um die nicht benötigten Daten zu entfernen.

Dadurch können Sie die geöffnete Datei nur einmal lesen.

1
blindChicken

Ihr Code macht nicht das, was Sie beschreiben. 

Frage : 1. Lesen Sie jede Datei und prüfen Sie, was die Etiketten sind. Die Informationen, die ich brauche, sind oft in den ersten Zeilen enthalten. 

Aber Sie lesen die Datei ganze , nicht nur ein paar Zeilen. Dies führt dazu, dass die Dateien zweimal gelesen werden !

Frage : 2. Lesen Sie die Datei erneut und füllen Sie den Datenrahmen mit Werten. 

Sie überschreiben df['a'|'b'|'c'|'d'] immer wieder in der Schleife, was nutzlos ist
Ich glaube, das ist nicht das, was Sie wollen.
Dies funktioniert für die in Question angegebenen Daten, nicht jedoch, wenn Sie mit n Werten arbeiten müssen. 


Vorschlag mit einer anderen Logik:

data = {}
for path in paths:
    with open(path, 'r') as file_obj:
        line = True
        while line:
            try:
                line = file_obj.readline()
                match = reg_ex.match(line)
                if match.group(1) not in data:
                    data[ match.group(1) ] = []

                data[match.group(1)].append( match.group(2) )
            except AttributeError:
                pass

print('data=%s' % data)
df = pd.DataFrame.from_dict(data, orient='index').sort_index()
df.rename(index=str, columns={0: "Number"}, inplace=True)  

Ausgabe

data={'b': ['2'], 'a': ['1'], 'd': ['4'], 'c': ['3']}
<class 'pandas.core.frame.DataFrame'>
Index: 4 entries, a to d
Data columns (total 1 columns):
Number    4 non-null object
dtypes: object(1)
memory usage: 32.0+ bytes
  Number
a      1
b      2
c      3
d      4  

Zeitplan :

             Code from Q:   to_dict_from_dict
    4 values 0:00:00.033071 0:00:00.022146
 1000 values 0:00:08.267750 0:00:05.536500
10000 values 0:01:22.677500 0:00:55.365000

Mit Python getestet: 3.4.2 - Pandas: 0.19.2 - Re: 2.2.1

1
stovfl

1 Erstellen Sie eine Ausgabevorlage für Dateien (wie der Ergebnisdatenrahmen sollte Spalte A, B C aufweisen)

2 Lesen Sie jede Datei, wandeln Sie sie in eine Ausgabevorlage um (die in Schritt 1 erstellt wurde) und speichern Sie die Datei wie temp_idxx.csv. Dies kann parallel erfolgen :)

3 Verketten Sie diese temp_idxx.csv-Dateien in einer einzigen Datei und löschen Sie die temporären Dateien

vorteile dieser Prozedur ist, dass sie parallel ausgeführt werden kann und nicht den gesamten Arbeitsspeicher beansprucht

1
quester

Verwenden Sie zunächst einen Profiler für Ihr Skript ( siehe diese Frage) . Analysieren Sie genau, welcher Teil mehr Zeit benötigt. Sehen Sie, ob Sie es optimieren können.

Zweitens glaube ich, dass das Lesen der E/A-Vorgangsdatei am ehesten der Engpass ist. Es kann durch gleichzeitigen Ansatz optimiert werden. Ich würde vorschlagen, Dateien gleichzeitig lesen und Datenrahmen erstellen. Jeder Thread kann neu erstellte Datenframes in eine Warteschlange verschieben. Eine Haupt-Thread-Überwachungswarteschlange kann Datenrahmen aus der Warteschlange aufnehmen und mit dem Master-Datenrahmen zusammenführen.

Hoffe das hilft. 

1
EngineeredBrain