Calcola l'intersezione sull'unione (indice di Jaccard) in pandas dataframe

Aug 22 2020

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

5 user3483203 Aug 22 2020 at 19:04

Puoi usare str.get_dummiese alcuni scipystrumenti 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
3 ShubhamSharma Aug 22 2020 at 18:38

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 keye utilizzare Series.str.splitsu colonna ids. Quindi utilizzare DataFrame.mergeper unire il dataframe dcon 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'sindice.

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