3
votes

Comment compter les chevauchements et trouver des partenaires qui se chevauchent pour les pandas?

Je ne sais pas comment faire cela le plus efficacement possible avec pandas .

J'ai les pandas suivants DataFrame , qui contient actuellement deux colonnes commence et se termine , représentant les intervalles [1, 10] , [5, 15] et [3, 8] .

  start  end  total  interval
0      0   1   0     []
1      1   3   1     [[1, 10]] 
2      3   5   2     [[1, 10], [3, 8]]
3      5   8   3     [[1, 10], [3, 8], [5, 15]]
4      8   10  2     [[1, 10], [5, 15]]
5      10  15  1     [[5, 15]]
6      15  75  0     []

À partir de 0, je veux calculer comment les intervalles se chevauchent. Voici la structure de fusion correcte (sans trop vous soucier des intervalles fermés / ouverts):

L'intervalle [0, 1] n'a pas d'intervalle, [1,3] a 1 intervalle (de [1, 10] ), [3, 5] a deux intervalles (la paire [1, 10] et [3, 8] ), l'intervalle [5, 8] a trois intervalles ( [1, 10], [3, 8], [5, 15] ), [8, 10] a deux intervalles ( [1, 10], [5, 15] ), etc.

En résumant les résultats sous forme de tableau, le résultat attendu serait:

import pandas as pd

dict1 = {'start': [1, 5, 3], 'end': [10, 15, 8]}

df = pd.DataFrame(dict1)
print(df)
   start  end
0      1   10
1      5   15
2      3    8

La colonne des intervalles étant actuellement un liste de listes contenant chaque liste d'intervalles. (J'ai inclus un entier supérieur à 15 pour montrer qu'il n'y a rien là-bas; 75 est arbitraire)

Comment dois-je accomplir ce qui précède avec les pandas? Les trois étapes semblent être:

(1) déconstruire les intervalles en sections en fonction de l'union de tous les autres intervalles

(2) compter les intervalles qui se chevauchent

( 3) stocker les intervalles pour une récupération ultérieure

pandas est-il même équipé pour cette opération?


2 commentaires

Je ne reçois pas la logique de la colonne total


@ U9-Forward J'aurais dû mieux nommer la colonne. Cela signifie le nombre d'intervalles, ou la longueur de la liste dans la colonne intervalle


3 Réponses :


1
votes

J'utilise numpy boardcast

s1=df1.end.values
s2=df1.start.values
s3=df2.end.values
s4=df2.start.values
f=pd.DataFrame(((s1[:,None]>=s3)&(s2[:,None]<=s4)).T,index=df2.index)
df2['total']=f.sum(1)
df2['interval']=[(df1.values[x]).tolist() for x in f.values]
df2
Out[289]: 
   start  end  total                    interval
0      0    1      0                          []
1      1    3      1                   [[1, 10]]
2      3    5      2           [[1, 10], [3, 8]]
3      5    8      3  [[1, 10], [5, 15], [3, 8]]
4      8   10      2          [[1, 10], [5, 15]]
5     10   15      1                   [[5, 15]]
6     15   75      0                          []


0 commentaires

3
votes

Depuis pandas 0.24.0 , on peut utiliser pd.Interval.overlaps:

+----+--------+------+-----------+-------+
|    | start  | end  |   intv    | total |
+----+--------+------+-----------+-------+
| 0  |     0  |   1  | (0, 1]    |     0 |
| 1  |     1  |   3  | (1, 3]    |     1 |
| 2  |     3  |   5  | (3, 5]    |     2 |
| 3  |     5  |   8  | (5, 8]    |     3 |
| 4  |     8  |  10  | (8, 10]   |     2 |
| 5  |    10  |  15  | (10, 15]  |     1 |
+----+--------+------+-----------+-------+

Sortie:

endpoints = df.stack().sort_values().reset_index(drop=True)
intervals = pd.DataFrame({'start':endpoints.shift().fillna(0), 
                          'end':endpoints}).astype(int)
# construct the list of intervals from the endpoints
intervals['intv'] = [pd.Interval(a,b) for a,b in zip(intervals.start, intervals.end)]

# these are the original intervals
orig_invt = pd.arrays.IntervalArray([pd.Interval(a,b) for a,b in zip(df.start, df.end)])

# walk through the intervals and compute the intersections
intervals['total'] = intervals.intv.apply(lambda x: org_intv.overlaps(x).sum())


0 commentaires

2
votes

Utilisation de l'approche standard pour la boucle:

bounds = np.unique(df)
if 0 not in bounds: bounds = np.insert(bounds, 0, 0)

end = 75
bounds = np.append(bounds, end)

total = []
interval = []
for i in range(len(bounds)-1):
    # Find which intervals fit
    ix = (df['start'] <= bounds[i]) & (df['end'] >= bounds[i+1])

    total.append(np.sum(ix))
    interval.append(df[ix].values.tolist())

pd.DataFrame({'start': bounds[:-1], 'end': bounds[1:], 'total': total, 'interval': interval})


2 commentaires

Ces approches sont souvent beaucoup plus intuitives, même si je suppose que les boucles for ont un impact négatif sur les performances. Merci pour cela!


Je suis d'accord. La lisibilité vaut quelque chose. En fonction de la charge de calcul de votre tâche, vous pouvez facilement l'utiliser sans remarquer de différence dans le temps de calcul. Si ce n'est pas le cas, d'autres articles pourraient vous permettre de gagner du temps de calcul.