2017-10-19 8 views
4

J'ai un dataframe de la forme suivantePandas: Count intersections d'intervalle de temps sur un groupe par

import pandas as pd 

Out[1]: 
df = pd.DataFrame({'id':[1,2,3,4,5], 
      'group':['A','A','A','B','B'], 
      'start':['2012-08-19','2012-08-22','2013-08-19','2012-08-19','2013-08-19'], 
      'end':['2012-08-28','2013-09-13','2013-08-19','2012-12-19','2014-08-19']}) 

    id group  start   end 
0 1  A 2012-08-19 2012-08-28 
1 2  A 2012-08-22 2013-09-13 
2 3  A 2013-08-19 2013-08-21 
3 4  B 2012-08-19 2012-12-19 
4 5  B 2013-08-19 2014-08-19 

pour la ligne donnée dans mon dataframe je voudrais compter le nombre d'éléments dans le même groupe qui ont un intervalle de temps qui se chevauche. Par exemple, dans le groupe A, id 2, du 22 août 2012 au 13 septembre 2013, le chevauchement entre id 1 (du 19 août 2012 au 28 août 2012) et id 3 (du 19 août 2013 au 21 août 2013) pour un compte de 2.

a l'inverse il n'y a pas de chevauchement entre les éléments dans le groupe B

donc, pour mon exemple ci-dessus dataframe je voudrais produire quelque chose comme

Out[2]: 
    id group  start   end count 
0 1  A 2012-08-19 2012-08-28  1 
1 2  A 2012-08-22 2013-09-13  2 
2 3  A 2013-08-19 2013-08-21  1 
3 4  B 2012-08-19 2012-12-19  0 
4 5  B 2013-08-19 2014-08-19  0 

je pouvais « brute- force "ceci mais je voudrais savoir s'il y a un Pandas plus efficace façon de faire cela.

Merci d'avance pour votre aide

+0

Pouvez-vous élaborer un peu à propos de l'intervalle de temps d'intersection. Je veux dire expliquer comment vous avez compté – Dark

+0

@Bharathshetty - J'ai élaboré sur ma question maintenant – johnaphun

Répondre

1

ish « force brute », mais fait le travail:

d'abord converti les chaînes de date à date, puis par rapport à chaque rangée contre la df avec une application.

df.start = pd.to_datetime(df.start) 
df.end = pd.to_datetime(df.end) 

df['count'] = df.apply(lambda row: len(df[ (((row.start <= df.start) & (df.start <= row.end)) \ 
              | ((df.start <= row.start) & (row.start <= df.end))) 
          & (row.id != df.id) & (row.group == df.group) ]),axis=1) 
+0

note: le to_datetime ne devrait pas être nécessaire avec votre format, c'était juste l'habitude de les convertir –

+0

Bon, même si je pensais la même chose. Bien que beaucoup de conditions – Dark

+0

Vous pouvez vous débarrasser de "& (row.id! = Df.id)" et faire "len (...) - 1", mais pas sûr comment nous pourrions réduire les conditions d'intersection dans une application –

1
import datetime 
def ol(a, b): 
    l=[] 
    for x in b: 
     l.append(max(0, int(min(a[1], x[1]) - max(a[0], x[0])>=datetime.timedelta(minutes=0)))) 
    return sum(l) 


df['New']=list(zip(df.start,df.end)) 
df['New2']=df.group.map(df.groupby('group').New.apply(list)) 
df.apply(lambda x : ol(x.New,x.New2),axis=1)-1 

Out[495]: 
0 1 
1 2 
2 1 
3 0 
4 0 
dtype: int64 

minutage

#My method 
df.apply(lambda x : ol(x.New,x.New2),axis=1)-1 

100 loops, best of 3: 5.39 ms per loop 

#@Andy's Method 
df.groupby("group").apply(count_overlaps)  
10 loops, best of 3: 23.5 ms per loop 

#@Nathan's Method 

df.apply(lambda row: len(df[ (((row.start <= df.start) & (df.start <= row.end)) \ 
         | ((df.start <= row.start) & (row.start <= df.end))) 
         & (row.id != df.id) & (row.group == df.group) ]),axis=1) 

10 loops, best of 3: 25.8 ms per loop 
+0

Vous avez pris loin. J'utilisais le zip à l'intérieur de l'application, j'ai foiré. C'est propre. – Dark

+1

@Bharathshetty quand j'utilise 'apply' je fais toujours tout le processus de perpétration en dehors du' lambda', parce que je sais que je vais foiré ... – Wen

+1

vous savez que urs est toujours le plus rapide. Vous pouvez faire ajouter des temps. Si vous utilisez numba, ce sera encore plus rapide. – Dark

2

Alors, je voudrais voir comment la force brute ... foires si elle est lente je cythonize cette logique. Ce n'est pas si grave, car si O (M^2) dans la taille du groupe, s'il y a beaucoup de petits groupes, ce ne sera peut-être pas si mal.

In [11]: def interval_overlaps(a, b): 
    ...:  return min(a["end"], b["end"]) - max(a["start"], b["start"]) > np.timedelta64(-1) 


In [12]: def count_overlaps(df1): 
    ...:  return sum(interval_overlaps(df1.iloc[i], df1.iloc[j]) for i in range(len(df1) - 1) for j in range(i, len(df1)) if i < j) 

In [13]: df.groupby("group").apply(count_overlaps) 
Out[13]: 
group 
A 2 
B 0 
dtype: int64 

Le premier est un peaufinage de this interval overlap function.


Edit: Une fois re-lecture ressemble à la count_overlaps est par ligne, plutôt que par groupe, de sorte que la fonction agg devrait être plus comme:

In [21]: def count_overlaps(df1): 
    ...:  return pd.Series([df1.apply(lambda x: interval_overlaps(x, df1.iloc[i]), axis=1).sum() - 1 for i in range(len(df1))], df1.index) 

In [22]: df.groupby("group").apply(count_overlaps) 
Out[22]: 
group 
A  0 1 
     1 2 
     2 1 
B  3 0 
     4 0 
dtype: int64 

In [22]: df["count"] = df.groupby("group").apply(count_overlaps).values 

In [23]: df 
Out[23]: 
     end group id  start count 
0 2012-08-28  A 1 2012-08-19  1 
1 2013-09-13  A 2 2012-08-22  2 
2 2013-08-19  A 3 2013-08-19  1 
3 2012-12-19  B 4 2012-08-19  0 
4 2014-08-19  B 5 2013-08-19  0 
+0

Je ne sais pas pourquoi, mais je ne pouvais pas écrire cette application comme une transformation ... (Je pense que ça ne prend pas le chemin rapide car c'est un objet python plutôt qu'un objet numpy/pandas) –

+0

Monsieur, vous aimez transformer autant? Puis-je savoir pourquoi – Dark

+1

@Bharathshetty Je pense que j'ai mal lu la question, l'exemple OP ne nécessite pas de transformation. Mais si nous comptons les chevauchements au sein de chaque groupe, cela va diffuser cette information dans le DataFrame original, qui est souvent un bon endroit pour l'avoir. –