Mam ramkę danych, która składa się z wierszy takich jak poniżej. Moim celem jest tutaj obliczenie podobieństwa cosinusowego każdego wiersza z każdym wierszem w tej samej kategorii, tak aby otrzymać ramkę danych z 3 kolumnami: kategoria, vecs i dist, gdzie dist jest tablicą długości zawierającą odległość między każdym wierszem a każdym wierszem w tej samej kategorii.

category    vecs
0   a   [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...]
1   a   [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...]
2   b   [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...]
3   b   [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...]

Nieefektywnym rozwiązaniem, o którym myślałem, byłoby zapętlenie każdego wiersza, sprawdzenie, czy cat jest równy, a następnie obliczenie odległości i dodanie do listy w przeciwnym razie kontynuuj pętlę. To rozwiązanie byłoby jednak n ^ 2 i szukam czegoś bardziej wydajnego. Mam 8115 wierszy w tej ramce danych i szukam czegoś, co prawdopodobnie skalowałoby się do jeszcze większych zestawów danych.

Innym możliwym rozwiązaniem, które przyjrzałem się, byłoby użycie odległości parami sklearn (metryka = cosinus) i w jakiś sposób uwzględnianie tylko obliczeń z tymi samymi kategoriami, ale staram się myśleć, jak to zrobić.

Czy ktoś byłby chętny do pomocy lub zaproponował inne skuteczne rozwiązanie?

1
ben890 20 listopad 2019, 18:41

1 odpowiedź

Musisz wykonać (mniej więcej) n(n-1)/2 obliczeń.
Jest to nieredukowalne, ponieważ podobieństwa muszą być jakoś obliczone, jeśli w wektorach nie ma ukrytej struktury.

Możesz użyć scipy do obliczenia odległości parami i funkcji squareform, aby uzyskać regularną macierz symetryczną, która w przeciwnym razie byłaby trójkątną spłaszczoną:

from scipy.spatial.distance import pdist, squareform

similarities = dict()
for cat, group in df.groupby("category"):
    a = tuple(row.vecs for _, row in group.iterrows())
    b = np.array(a)
    sim_mat = squareform(1 - pdist(b, metric='cosine'))
    similarities[cat] = sim_mat

[print(k, v, sep='\n') for k, v in similarities.items()]
a
[[0. 1.]
 [1. 0.]]
b
[[0.         0.70710678]
 [0.70710678 0.        ]]
1
Horace 20 listopad 2019, 20:41
Czy miałeś na myśli, że g[i] jest grupą[i]?
 – 
ben890
20 listopad 2019, 20:36
Rzeczywiście był tam błąd, zredagowałem odpowiedź. To powinno działać teraz
 – 
Horace
20 listopad 2019, 20:43
Dziękuję Ci! Czy istnieje prosty sposób na dodanie ich z powrotem do ramki danych?
 – 
ben890
20 listopad 2019, 21:16
1
Nieważne, po prostu uporządkowałem oryginalną ramkę danych według kategorii przed utworzeniem dyktatu podobieństw. Następnie przerzuciłem wartości ze słownika na listy list, a następnie dodałem je do ramki danych, co moim zdaniem powinno działać.
 – 
ben890
20 listopad 2019, 21:48