2013-02-25 6 views
9

Das eigentliche Problem, das ich habe, ist, dass ich eine lange sortierte Liste von (float, str) Tupel im RAM speichern möchte. Eine einfache Liste passt nicht in meinen 4 GB RAM, also dachte ich, ich könnte zwei numpy.ndarray s verwenden.Wie fülle ich zwei (oder mehr) numpige Arrays aus einem einzigen iterierbaren Tupel?

Die Quelle der Daten ist eine iterierbare 2-Tupel. numpy hat eine fromiter Funktion, aber wie kann ich es verwenden? Die Anzahl der Elemente im Iterablen ist unbekannt. Ich kann es aufgrund von Speicherbeschränkungen nicht zuerst in eine Liste aufnehmen. Ich dachte an itertools.tee, aber es scheint hier viel Speicher Overhead hinzuzufügen.

Was ich denke, könnte ich den Iterator in Chunks konsumieren und diese zu den Arrays hinzufügen. Dann ist meine Frage, wie man das effizient macht? Soll ich vielleicht 2 2D-Arrays machen und Zeilen hinzufügen? (Später müsste ich sie in 1D umwandeln).

Oder vielleicht gibt es einen besseren Ansatz? Alles, was ich wirklich brauche, ist, ein Array von Strings nach dem Wert der entsprechenden Zahl in logarithmischer Zeit zu durchsuchen (deshalb möchte ich nach dem Wert von float sortieren) und es so kompakt wie möglich zu halten.

P.S. Das iterable wird nicht sortiert.

+0

Würde 'np.fromiter' verwendet, um ein einzelnes Array mit zwei Spalten zu erstellen? – unutbu

+0

@unutbu ... Ich bin mir nicht sicher, warum ich das nicht bedacht habe :) Klingt nach einer großartigen Idee. Dann sortiere ich es einfach entlang der längeren Achse und behalte es so, richtig? Du könntest es als Antwort posten, nehme ich an. –

Antwort

8

bauen vielleicht ein einziges, strukturiertes Array np.fromiter:

import numpy as np 


def gendata(): 
    # You, of course, have a different gendata... 
    for i in xrange(N): 
     yield (np.random.random(), str(i)) 

N = 100 

arr = np.fromiter(gendata(), dtype='<f8,|S20') 

es durch die erste Spaltensortierung, die zweite für Tie-Unterbrechern verwendet werden O nehmen (N log N) Zeit:

arr.sort(order=['f0','f1']) 

die Zeile durch den Wert in der ersten Spalte zu finden, kann mit searchsorted in O (log N) Zeit durchgeführt werden:

# Some pseudo-random value in arr['f0'] 
val = arr['f0'][10] 
print(arr[10]) 
# (0.049875262239617246, '46') 

idx = arr['f0'].searchsorted(val) 
print(arr[idx]) 
# (0.049875262239617246, '46') 

Sie haben viele wichtige Fragen in den Kommentaren gestellt; Lassen Sie mich versuchen, sie hier zu beantworten:

  • Der Grund dtypes ist in den numpybook erläutert. Es können ein oder zwei zusätzliche dtypes sein (wie float16 die hinzugefügt wurden, da das Buch geschrieben wurde, aber die Grundlagen sind alle dort erklärt.)

    Vielleicht eine gründlichere Diskussion ist in der online documentation. Was ist eine gute Ergänzung zu den Beispielen, die Sie erwähnten here.

  • Dtypes können verwendet werden, um strukturierte Arrays mit Spaltennamen oder mit Standardspaltennamen zu definieren. 'f0', 'f1' usw. sind Standardspalten Namen. Da ich den dtype als '<f8,|S20' definiert habe ich versäumt, Spaltennamen bereitzustellen, so nannte NumPy die erste Spalte 'f0', und die zweite 'f1'. Wenn wir

    dtype='[('fval','<f8'), ('text','|S20')] 
    

    dann die strukturierte Anordnung arr benutzt hatte, würde Spaltennamen 'fval' und 'text' haben.

  • Leider muss der dtype zu dem Zeitpunkt np.fromiter genannt werden. Sie könnten möglicherweise durch gendata laufen, sobald die maximale Länge der Saiten zu entdecken, Ihre dtype bauen und rufen dann np.fromiter (und durchlaufen gendata ein zweites Mal), aber die eher lästig ist. Es ist natürlich besser, wenn Sie in die maximale Größe der Saiten voranbringen. (|S20 definiert die Zeichenfolge Feld mit einer festen Länge von 20 Bytes.)
  • NumPy Arrays platzieren Daten einer vordefinierte Größe in Arrays einer festen Größe. Stellen Sie sich das Array (sogar multidimensionale) als einen zusammenhängenden Block eindimensionaler Speicher vor. (Das ist eine übermäßige Vereinfachung - es gibt nicht zusammenhängenden Arrays - aber wird Ihrer Phantasie für das folgende helfen.) NumPy leitet viel von seiner Geschwindigkeit ab, indem es die festen Größen nutzt (eingestellt durch dtype), um die erforderlichen Offsets schnell zu berechnen Zugriff auf Elemente im Array. Wenn die Strings variable Größen hätten, wäre es schwierig für NumPy die richtigen Offsets zu finden. Mit schwer, ich meine NumPy müsste einen Index oder irgendwie neu gestaltet werden. NumPy ist einfach nicht auf diese Weise gebaut.
  • NumPy hat einen object dtype, mit dem Sie einen 4-Byte-Zeiger auf jedes gewünschte Python-Objekt platzieren können. Auf diese Weise können Sie NumPy Arrays mit beliebigen Python-Daten haben. Leider ermöglicht die np.fromiter Funktion nicht, Arrays von dtype object zu erstellen. Ich bin nicht sicher, warum gibt es diese Einschränkung ...
  • Beachten Sie, dass np.fromiter hat eine bessere Leistung, wenn die count angegeben ist. Durch die Kenntnis der count (die Anzahl der Zeilen) und der dtype (und damit die Größe jeder Zeile) kann NumPy genau genug Speicher für das resultierende Array vor-zuweisen. Wenn Sie count nicht angeben, wird NumPy eine Schätzung für die anfängliche Größe des Arrays , und wenn zu klein, wird es versuchen, die Größe des Arrays zu ändern. Wenn der ursprüngliche Speicher erweitert werden kann, haben Sie Glück. Aber wenn NumPy ein völlig neues Stück Speicher reservieren muss, dann müssen alle alten Daten an den neuen Speicherort kopiert werden, was die Leistung erheblich verlangsamen wird.
+0

Wow, hier gibt es eine Menge neuer Sachen, z. die 'fX'-Indexierungssyntax, aber hauptsächlich der von Ihnen verwendete dtype.Erstens, sind mögliche dtypes dokumentiert? Ich fand [dies] (http://docs.scipy.org/doc/numpy/reference/generated/numpy.dtype.html), aber ich würde einige Erklärungen und nicht nur Beispiele verwenden. Muss die Größe festgelegt werden (ich denke, wenn es ein einfaches Array ist)? Weil ich in einer idealen Welt nicht möchte, dass es eine Obergrenze hat, noch möchte ich, dass kurze Strings zusätzlichen Raum einnehmen. Kann ich so etwas bekommen? –

+0

Wenn Sie "count" nicht angeben, muss "np.fromiter" nicht zuerst eine Liste aus dem Iterator erstellen und dann in ein Array konvertieren? – Jaime

+0

@Jaime: Wenn Sie 'count' nicht angeben, muss' np.fromiter' die Größe des numpy-Arrays ändern, wenn die Daten das vorbelegte Ausgabe-Array verlassen. Wenn genügend zusammenhängender Speicher vorhanden ist, müssen bei der Größenanpassung keine Daten kopiert werden, und zu keinem Zeitpunkt wird eine Python-Liste verwendet. – unutbu

1

Hier ist ein Weg N separate Arrays aus einem Generator von N -Tupel aufzubauen:

import numpy as np 
import itertools as IT 


def gendata(): 
    # You, of course, have a different gendata... 
    N = 100 
    for i in xrange(N): 
     yield (np.random.random(), str(i)) 


def fromiter(iterable, dtype, chunksize=7): 
    chunk = np.fromiter(IT.islice(iterable, chunksize), dtype=dtype) 
    result = [chunk[name].copy() for name in chunk.dtype.names] 
    size = len(chunk) 
    while True: 
     chunk = np.fromiter(IT.islice(iterable, chunksize), dtype=dtype) 
     N = len(chunk) 
     if N == 0: 
      break 
     newsize = size + N 
     for arr, name in zip(result, chunk.dtype.names): 
      col = chunk[name] 
      arr.resize(newsize, refcheck=0) 
      arr[size:] = col 
     size = newsize 
    return result 

x, y = fromiter(gendata(), '<f8,|S20') 

order = np.argsort(x) 
x = x[order] 
y = y[order] 

# Some pseudo-random value in x 
N = 10 
val = x[N] 
print(x[N], y[N]) 
# (0.049875262239617246, '46') 

idx = x.searchsorted(val) 
print(x[idx], y[idx]) 
# (0.049875262239617246, '46') 

fromiter Die Funktion des oben in iterable chunks liest (mit einer Größe chunksize). Es ruft die NumPy-Array-Methode resize auf, um die resultierenden Arrays nach Bedarf zu erweitern.

Ich habe einen kleinen Standard chunksize verwendet, seit ich diesen Code auf kleine Daten getestet habe. Sie werden natürlich entweder die Standard-Chunksize ändern oder einen chunksize-Parameter mit einem größeren Wert übergeben.

+0

Ja, das Lesen in Stücken war auch in meinen Gedanken, danke für das großartige Beispiel. Können wir 'chunksize' nicht an' np.fromiter' übergeben, um es zu beschleunigen? –

+0

Leider sehe ich keinen Weg. Wenn wir "count = chunksize" verwenden, kann der Aufruf von "np.fromiter" fehlschlagen, wenn das iterable weniger als "chunksize" Elemente enthält. Und wenn wir versuchen, das in einem "try..except" Block zu fangen, werden wir Daten verlieren, da das iterable nur für einen Durchlauf gut ist. – unutbu

Verwandte Themen