Calcola l'intersezione sull'unione (indice di Jaccard) in pandas dataframe
Ho un dataframe come:
animal ids
cat 1,3,4
dog 1,2,4
hamster 5
dolphin 3,5
Il dataframe è abbastanza grande, con oltre 80mila righe e la colonna ids può contenere facilmente oltre migliaia, anche 10mila id separati da virgole. Gli ID in una determinata riga sarebbero univoci nella stringa separata da virgole.
Vorrei costruire un dataframe che calcola l'indice di Jaccard, cioè l'intersezione di ogni elemento nella colonna degli animali tra loro nella colonna degli ID sull'unione.
Quindi, se guardiamo gatto e cane, l'unione è 2 (id 1 e 4) e l'unione è 4 (id 1, 2, 3, 4), quindi l'indice di Jaccard è 2/4 = 0,5. Sarebbe fantastico avere il set di dati in questo formato:
cat dog hamster dolphin
cat 1 0.5 0 0.25
dog 0.5 1 0 0
hamster 0 0 1 0.5
dolphin 0.25 0 0.5 1
che significa usare l'indice di riga come nome dell'animale, in modo da poter trovare rapidamente l'indice di jaccard correlato come:
cat_dog_ji = df_new['cat']['dog']
Risposte
Puoi usare str.get_dummies
e alcuni scipy
strumenti qui.
from scipy.spatial import distance
u = df["ids"].str.get_dummies(",")
j = distance.pdist(u, "jaccard")
k = df["animal"].to_numpy()
pd.DataFrame(1 - distance.squareform(j), index=k, columns=k)
cat dog hamster dolphin
cat 1.00 0.5 0.0 0.25
dog 0.50 1.0 0.0 0.00
hamster 0.00 0.0 1.0 0.50
dolphin 0.25 0.0 0.5 1.00
Uso:
d = df.assign(key=1, ids=df['ids'].str.split(','))
d = d.merge(d, on='key', suffixes=['', '_r'])
i = [np.intersect1d(*x).size / np.union1d(*x).size for x in zip(d['ids'], d['ids_r'])]
d = pd.crosstab(d['animal'], d['animal_r'], i, aggfunc='first').rename_axis(index=None, columns=None)
Dettagli:
Utilizzare DataFrame.assignper creare una colonna temporanea key
e utilizzare Series.str.splitsu colonna ids
. Quindi utilizzare DataFrame.mergeper unire il dataframe d
con la colonna basata su se stesso key
(essenzialmente un cross join).
print(d)
animal ids key animal_r ids_r
0 cat [1, 3, 4] 1 cat [1, 3, 4]
1 cat [1, 3, 4] 1 dog [1, 2, 4]
2 cat [1, 3, 4] 1 hamster [5]
3 cat [1, 3, 4] 1 dolphin [3, 5]
4 dog [1, 2, 4] 1 cat [1, 3, 4]
5 dog [1, 2, 4] 1 dog [1, 2, 4]
6 dog [1, 2, 4] 1 hamster [5]
7 dog [1, 2, 4] 1 dolphin [3, 5]
8 hamster [5] 1 cat [1, 3, 4]
9 hamster [5] 1 dog [1, 2, 4]
10 hamster [5] 1 hamster [5]
11 hamster [5] 1 dolphin [3, 5]
12 dolphin [3, 5] 1 cat [1, 3, 4]
13 dolphin [3, 5] 1 dog [1, 2, 4]
14 dolphin [3, 5] 1 hamster [5]
15 dolphin [3, 5] 1 dolphin [3, 5]
Utilizzo np.interset1dinsieme alla np.union1dcomprensione della lista interna per calcolare l' Jaccard's
indice.
print(i)
[1.0, 0.5, 0.0, 0.25, 0.5, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.5, 0.25, 0.0, 0.5, 1.0]
Infine usiamo pd.crosstabper creare una semplice tabulazione incrociata per ottenere il risultato nel formato desiderato:
print(d)
cat dog dolphin hamster
cat 1.00 0.5 0.25 0.0
dog 0.50 1.0 0.00 0.0
dolphin 0.25 0.0 1.00 0.5
hamster 0.00 0.0 0.50 1.0