2016-04-19 12 views
2

Ich versuche eine Teilmenge aus einem numpy ndarray für mein kollaboratives Filterprojekt auszuwählen.Numpy WHERE IN gleich

Mein Array hat diese Form:

ratings = np.array(
    [ 
     (1, 2, 3.0), 
     (2, 2, 3.0), 
     (4, 1, 2.0), 
     (1, 2, 1.0), 
    ], 
    dtype=[ 
     ('user_id', np.uint32), 
     ('item_id', np.uint32), 
     ('score', np.float32) 
    ] 
) 

Nun, ich möchte eine Teilmenge der Bewertungen auszuwählen, in dem user_id auf ein Array gehört. Ähnlich wie SQL 'WHERE IN' Funktionalität.

Ich konnte dies np.in1d ​​mit erreichen:

subset_of_users = [1, 2] 
ratings[np.in1d(ratings['user_id'], subset_of_users)] 

Meine Profilierung zeigen immer, dass die in1d mit Blöcken der langsamste sind, und es hat mir, dass vielleicht denken, es gibt eine schnellere Alternative, dies zu tun.

Vielen Dank für Ihre Zeit.

Antwort

0

Wenn Ihre maximale user_id nicht zu groß ist, können Sie eine Lookup-Tabelle verwenden:

mask_of_users = np.zeros(shape=max(ratings['user_id'])+1, dtype=bool) 
mask_of_users[subset_of_users] = True 
selected_ratings = ratings[mask_of_users[ratings['user_id']]] 
+0

Danke für Ihre Antwort. max (user_id) ist ~ 150k. Mit diesem Code ging meine Funktion von 0,04s auf 0,17s :-( – fedeisas

0

Es scheint der Engpass dort np.in1d ist, so wollen wir versuchen, diesen Teil zu beschleunigen. Nun, aus meiner bisherigen NumPy-Erfahrung, stieß ich auf eine Alternative mit np.searchsorted, die als Ersatz für np.in1d für den Fall verwendet werden könnte, wenn das zweite Array ein sortiertes und eindeutiges Array oder eine Liste ist und mit den Elementen im ersten verglichen werden soll Array. Nachfolgend finden Sie die Umsetzung -

def in1d_replacement(A,B): 
    """ in1d replacement using searchsorted with optional 'left', 'right' args. 
    """ 
    # Get left and right sorted indices for A in B 
    idx_l = np.searchsorted(B,A,'left') 
    idx_r = np.searchsorted(B,A,'right') 

    # Look for switching indices between the left and right ones 
    # indicating the matches 
    return idx_l != idx_r 

Runtime Test -

In [195]: # Random arrays of decent sizes 
    ...: nA = 10000 
    ...: nB = 1000 
    ...: max_num = 100000 
    ...: A = np.random.randint(0,max_num,(nA)) 
    ...: B = np.unique(np.random.randint(0,max_num,(nB))) 
    ...: 

In [196]: np.allclose(np.in1d(A,B),in1d_replacement(A,B)) 
Out[196]: True 

In [197]: %timeit np.in1d(A,B) 
100 loops, best of 3: 2.73 ms per loop 

In [198]: %timeit in1d_replacement(A,B) 
100 loops, best of 3: 2.14 ms per loop 

So gibt es einige Performance-Schub, wenn nicht eine riesige ein.

1

Sie könnten versuchen, das numpy_indexed Paket zu verwenden (Disclaimer: Ich bin sein Autor).

Ihre Frage inspiriert, ich habe die API ein wenig, in einer Weise verändert, so dass wir von wiederholten Abfragen profitieren:

import numpy_indexed as npi 
npi.contains(subset_of_users, ratings['user_id']) 

die sich von links nach rechts gelesen werden sollte; 'Untergruppe enthält Elemente von user_id' und gibt die Indizes von 'user_id' zurück, die in der Untergruppe vorhanden sind.

jedoch der teuerste Teil der Berechnung baut den ‚Index‘ für den Satz von Benutzer-ID und das vorberechnet werden kann:

index = npi.as_index(ratings['user_id']) 
npi.contains(subset_of_users, index) 
npi.contains(some_other_subset_of_users, index) 

Was erwarte ich ziemlich viel schneller zu sein, auf einem pro Anfrage.

Ive auch eine npi.in_ Funktion integriert, inspiriert von Divakar Antwort, die Sie schreiben können, npi.in_ (Bewertungen ['user_id'], subset_of_users), die wieder von links nach rechts liest; die Elemente von user_id, die in der Teilmenge vorhanden sind. Aber ich erwarte, dass es etwas weniger effizient ist als die Verwendung von Contains. Das ist alles Vermutung; wäre schön, einige Vergleiche zu aktuellen Daten zu sehen!