webentwicklung-frage-antwort-db.com.de

Ersetzen Sie Elemente in numpy-Arrays und vermeiden Sie Schleifen

Ich habe ein ziemlich großes 1d Numpy Array Xold mit vorgegebenen Werten. Diese Werte sollen nach der Regel ersetzt werden, die durch ein 2d numpy Array Y angegeben ist: Ein Beispiel wäre

Xold=np.array([0,1,2,3,4])
Y=np.array([[0,0],[1,100],[3,300],[4,400],[2,200]])

Immer wenn ein Wert in Xold mit einem Wert in Y [:, 0] identisch ist, sollte der neue Wert in Xnew der entsprechende Wert in Y [:, 1] sein. Dies wird durch zwei verschachtelte for-Schleifen erreicht:

Xnew=np.zeros(len(Xold))
for i in range(len(Xold)):
for j in range(len(Y)):
    if Xold[i]==Y[j,0]:
        Xnew[i]=Y[j,1]

Mit dem angegebenen Beispiel ergibt dies Xnew=[0,100,200,300,400]. Bei großen Datenmengen ist dieser Vorgang jedoch recht langsam. Was ist eine schnellere und elegantere Methode, um diese Aufgabe zu erfüllen?

6
goethin

AUSWAHL DER SCHNELLSTEN METHODE

Die Antworten auf diese Frage boten eine Reihe von Möglichkeiten, Elemente in Numpy-Arrays zu ersetzen. Lassen Sie uns prüfen, welches am schnellsten ist.

TL; DR: Numpy Indexierung ist der Gewinner

 def meth1(): # suggested by @Slam
    for old, new in Y:  
        Xold[Xold == old] = new

 def meth2(): # suggested by myself, convert y_dict = dict(Y) first
     [y_dict[i] if i in y_dict.keys() else i for i in Xold]

 def meth3(): # suggested by @Eelco Hoogendoom, import numpy_index as npi first
     npi.remap(Xold, keys=Y[:, 0], values=Y[:, 1])

 def meth4(): # suggested by @Brad Solomon, import pandas as pd first 
     pd.Series(Xold).map(pd.Series(Y[:, 1], index=Y[:, 0])).values

  # suggested by @jdehesa. create Xnew = Xold.copy() and index
  # idx = np.searchsorted(Xold, Y[:, 0]) first
  def meth5():             
     Xnew[idx] = Y[:, 1]

Nicht so überraschende Ergebnisse

 In [39]: timeit.timeit(meth1, number=1000000)                                                                      
 Out[39]: 12.08

 In [40]: timeit.timeit(meth2, number=1000000)                                                                      
 Out[40]: 2.87

 In [38]: timeit.timeit(meth3, number=1000000)                                                                      
 Out[38]: 55.39

 In [12]: timeit.timeit(meth4, number=1000000)                                                                                      
 Out[12]: 256.84

 In [50]: timeit.timeit(meth5, number=1000000)                                                                                      
 Out[50]: 1.12

Das gute alte Listenverständnis ist also das zweitschnellste und der gewinnbringende Ansatz ist die numpy-Indizierung in Kombination mit searchsorted().

3
Daniel Kislyuk

Wir können np.searchsorted für einen generischen Fall verwenden, wenn die Daten in der ersten Spalte von Y nicht unbedingt sortiert sind.

sidx = Y[:,0].argsort()
out = Y[sidx[np.searchsorted(Y[:,0], Xold, sorter=sidx)],1]

Probelauf -

In [53]: Xold
Out[53]: array([14, 10, 12, 13, 11])

In [54]: Y
Out[54]: 
array([[ 10,   0],
       [ 11, 100],
       [ 13, 300],
       [ 14, 400],
       [ 12, 200]])

In [55]: sidx = Y[:,0].argsort()
    ...: out = Y[sidx[np.searchsorted(Y[:,0], Xold, sorter=sidx)],1]

In [56]: out
Out[56]: array([400,   0, 200, 300, 100])

Wenn nicht für alle Elemente entsprechende Zuordnungen verfügbar sind, müssen wir ein bisschen mehr arbeiten.

sidx = Y[:,0].argsort()
sorted_indx = np.searchsorted(Y[:,0], Xold, sorter=sidx)
sorted_indx[sorted_indx==len(sidx)] = len(sidx)-1
idx_out = sidx[sorted_indx]
out = Y[idx_out,1]
out[Y[idx_out,0]!=Xold] = 0 # NA values as 0s
3
Divakar

Die erste Verbesserung, die Sie tun können, ist die Verwendung der Numpy-Indizierung, aber Sie haben immer noch eine Schleife:

for old, new in Y: 
    Xold[Xold == old] = new
2
Slam

Hier ist eine Möglichkeit:

import numpy as np

Xold = np.array([0, 1, 2, 3, 4])
Y = np.array([[0, 0], [1, 100], [3, 300], [4, 400], [2, 200]])
# Check every X value against every Y first value
m = Xold == Y[:, 0, np.newaxis]
# Check which elements in X are among Y first values
# (so values that are not in Y are not replaced)
m_X = np.any(m, axis=0)
# Compute replacement
# Xold * (1 - m_X) are the non-replaced values
# np.sum(Y[:, 1, np.newaxis] * m, axis=0) * m_X are the replaced values
Xnew = Xold * (1 - m_X) + np.sum(Y[:, 1, np.newaxis] * m, axis=0) * m_X
print(Xnew)

Ausgabe:

[  0 100 200 300 400]

Diese Methode funktioniert mehr oder weniger in jedem Fall (unsortierte Arrays, mehrfache Wiederholungen von Werten in X, Werte in X nicht ersetzt, Werte in Y ersetzen nichts in X), es sei denn, Sie geben zwei Ersetzungen für denselben Wert in Y an wäre sowieso falsch. Die zeitliche und räumliche Komplexität ergibt sich jedoch aus den Größen von X und Y. Wenn Ihr Problem zusätzliche Einschränkungen aufweist (Daten werden sortiert, keine Wiederholungen usw.), kann möglicherweise etwas Besseres erreicht werden. Wenn beispielsweise X ohne wiederholte Elemente sortiert wird und jeder Wert in Y einen Wert in X ersetzt (wie in Ihrem Beispiel), ist dies wahrscheinlich schneller:

import numpy as np

Xold = np.array([0, 1, 2, 3, 4])
Y = np.array([[0, 0], [1, 100], [3, 300], [4, 400], [2, 200]])
idx = np.searchsorted(Xold, Y[:, 0])
Xnew = Xold.copy()
Xnew[idx] = Y[:, 1]
print(Xnew)
# [  0 100 200 300 400]
2
jdehesa

Sie können slicing-Funktionen in Kombination mit der argsort-Methode verwenden.

Xnew = Y[Y[:,1].argsort()][:, 1][Xold] 

Ausgabe

array([  0, 100, 200, 300, 400])

Lösung mit pd.Series.map()

Wenn Sie bereit sind, die Pandas-Bibliothek zu verwenden, können Sie dies auch vektorisiert mit .map() tun:

>>> import pandas as pd
>>> pd.Series(Xold).map(pd.Series(Y[:, 1], index=Y[:, 0]))                                                                                                                                                                    
0      0
1    100
2    200
3    300
4    400
dtype: int64

>>> pd.Series(Xold).map(pd.Series(Y[:, 1], index=Y[:, 0])).values                                                                                                                                                            
array([  0, 100, 200, 300, 400])

Für die Signatur a.map(b) sucht a nach den entsprechenden Einträgen im Index von b und ordnet die entsprechenden Werte in b zu.

b hier ist pd.Series(Y[:, 1], index=Y[:, 0]), die die 0. Spalte als Index und die 1. Spalte als die Werte verwendet, die zugeordnet werden.


pandas.core.algorithms direkt verwenden

Under the hood , dies wird .get_indexer() und die von Cython implementierte take_1d() verwenden:

indexer = mapper.index.get_indexer(values)
new_values = algorithms.take_1d(mapper._values, indexer)

Wenn die Arrays wirklich massiv sind, können Sie den Overhead wie folgt reduzieren:

from pandas.core import algorithms

indexer = pd.Index(Y[:, 0]).get_indexer(Xold)  
mapped = algorithms.take_1d(Y[:, 1], indexer)
0
Brad Solomon

Das numpy_indexed -Paket (Haftungsausschluss; ich bin sein Autor) enthält eine effiziente vektorisierte Funktion, die das allgemeine Problem löst:

import numpy_indexed as npi
Xnew = npi.remap(Xold, keys=Y[:, 0], values=Y[:, 1])

Das heißt, dies funktioniert für jeden d-Typ oder wenn zu ersetzende Schlüssel und Werte selbst ndarrays sind und Sie ein kwarg erhalten, um anzugeben, wie auf fehlende Elemente zu reagieren ist.

Ich bin mir nicht sicher, was die Leistung von Pandas angeht. Eine der Entwurfswahlen in dieser Bibliothek ist jedoch, dass das Durchführen derartiger elementarer Operationen (oder das Durchführen von Gruppenoperationen usw.) nicht das Erstellen eines neuen Datentyps wie einer Reihe oder Tabelle beinhalten sollte, was mich immer gestört hat, Pandas für diesen Typ zu verwenden der Sache.

0

Sie können Y mit y = dict(Y) in ein Wörterbuch konvertieren und dann das folgende Listenverständnis ausführen

[y[i] if i in y.keys() else i for i in Xold]
0
Daniel Kislyuk