2017-01-04 1 views
1

Je souhaite calculer la similarité entre les éléments (0,1,2,3 ..) en fonction de leurs informations temporelles. L'information temporelle peut être un instant (startdate), un intervalle de temps (startdate, enddate) ou null (NaT); voir un exemple de données (df_for) ci-dessous.Python - Calcul des similitudes entre les éléments en fonction des informations temporelles (instantané, intervalle)

enter image description here

  1. {instant, instant}: si sim égale = 1, autre sim = 0
  2. {instant, null} ou vice versa, sim = 0
  3. {instant, intervalle} : si instant dans intervalle, sim = 1 ou si un intervalle contient un instant, sim = 1
  4. {intervalle, intervalle}: si les intervalles se chevauchent, sim = intersection des deux intervalles/union des deux intervalles
  5. {intervalle, intervalle}: si un dans terval en contient un autre, puis sim = 1

Le code python suivant obtient des informations temporelles à partir de la trame de données et remplit les conditions ci-dessus (1-5). Le code est verbeux, je me demande s'il existe une méthode intelligente/lib pour calculer la similarité entre les périodes et les instants en utilisant python.

m, k = df_for.shape 
sim = np.zeros((m, m)) 
data = df_for.values 
for i in range(m): 
    for j in range(m): 
     if i != j: 
      st1 = data[i][0] 
      ed1 = data[i][1] 
      st2 = data[j][0] 
      ed2 = data[j][1] 
      #both items are null values 
      if pd.isnull(st1) and pd.isnull(ed1) and pd.isnull(st2) and pd.isnull(ed2): 
       sim[i][j] = 0. 
      # {instant, instant} => equal, not equal 
      if pd.notnull(st1) and pd.isnull(ed1) and pd.notnull(st2) and pd.isnull(ed2): 
       if st1 == st2: 
        sim[i][j] = 1. 
       else: 
        sim[i][j] = 0. 
      # {instant, null} => sim is 0 
      if pd.notnull(st1) and pd.isnull(ed1) and pd.isnull(st2) and pd.isnull(ed2): 
       sim[i][j] = 0. 
      # {instant, interval} => meets, during 
      if pd.notnull(st1) and pd.isnull(ed1) and pd.notnull(st2) and pd.notnull(ed2): 
        if(st2 <= st1 <= ed2): 
         sim[i][j] = 1. #a time is between two other times 
        else: 
         sim[i][j] = 0. 
      # {interval, instant} => meets, contains 
      if pd.notnull(st1) and pd.notnull(ed1) and pd.notnull(st2) and pd.isnull(ed2): 
        if(st1 <= st2 <= ed1): 
         sim[i][j] = 1. #a time is between two other times 
        else: 
         sim[i][j] = 0. 
      # {interval, interval} => equal, overlaps, not overlaps 
      if pd.notnull(st1) and pd.notnull(ed1) and pd.notnull(st2) and pd.notnull(ed2): 
       if (st1 <= st2 <= ed1) or (st2 <= st1 <= ed2): 
        intersect = min(ed1,ed2)- max(st1,st2) # earliestend-lateststart 
        union = max(st1,st2,ed1,ed2) - min(ed1,ed2,st1,st2) 
        overlaps = intersect/union 
        #print(intersect/np.timedelta64(1, 'D'),union/np.timedelta64(1, 'D')) 
        if (st1 > st2 and ed1 < ed2) or (st1 < st2 and ed1 > ed2): # contains, during 
         overlaps = 1.0 
        sim[i][j]=overlaps 
       else: 
        sim[i][j] = 0. 
     else: 
      sim[i][j] = 1. 

Répondre

2

Je peux voir plusieurs façons de simplifier le code.

  1. D'abord, je vous suggère de déplacer le code de comparaison avec une fonction distincte qui prend st1, ed1, st2 et ed2 comme arguments. (Comme une note de côté, pourquoi st (début temps) mais ed (fin Date) Noms uniformes pourraient bien.) Vous seriez en mesure de return votre résultat au code d'appel, qui serait chargé de faire l'affectation dans le tableau des résultats. En parlant de ce code d'appel ... Il n'est pas nécessaire d'appeler la fonction de comparaison pour chaque paire de plages de temps. Les résultats doivent toujours être symétriques (par exemple compare(data[i][0], data[i][1], data[j][0], data[j][1]) va retourner la même chose que compare(data[j][0], data[j][1], data[i][0], data[i][1])). Il suffit donc d'appeler l'un de ceux-ci et d'affecter les résultats à sim[i][j] et sim[j][i].

  2. Plutôt que beaucoup de if some_test_expression: return 1; else: return 0 blocs, il suffit de retourner les résultats de la comparaison: return some_test_expression.Le type bool de Python est une sous-classe de int, donc ceux-ci doivent être automatiquement convertis en nombres lorsque vous les attribuez à un tableau numpy (vous pouvez également transtyper en float explicitement si vous voulez que la fonction renvoie toujours le même type).

  3. Gérez tous les cas avec des zéros en haut. C'est un cas facile, et si vous les traitez d'abord (et revenez, donc le reste de la fonction ne s'exécute pas), vous n'avez pas besoin de vérifier autant de choses pour null plus tard. Vous n'avez pas besoin de gérer null/null, null/instantand/null/interval séparément, renvoyer juste zéro si l'une des valeurs de départ est null: if isnull(st1) or isnull(st2): return 0.

  4. Les comparaisons instant/intervalle sont symétriques, il suffit d'écrire la logique dans un sens et retourner les paramètres s'ils sont au début dans le mauvais ordre: if isnull(ed2): st1, ed1, st2, ed2 = st2, ed2, st1, ed1

  5. Je ne vois pas trop qui peut être amélioré avec les implémentations spécifiques de vos comparaisons entre les instants et les intervalles ou entre deux intervalles (autres que le choses générales déjà mentionnées ci-dessus). Cependant, je voudrais dire que votre formule pour les intervalles qui se chevauchent aura une grande discontinuité dans la similarité car un petit intervalle chevauche lentement plus avec un plus grand, puis devient entièrement contenu. Par exemple, un intervalle d'une seconde qui commence juste une milliseconde avant un intervalle d'une heure se traduira par une valeur de similarité entre les deux 0.000277 (0.999/(3600 + 0.001)), mais celui qui commence en même temps que l'intervalle le plus grand aura une similarité de 1.0 (une grosse différence). Une mesure de similarité plus continuellement changeante proviendrait de la division de la quantité de chevauchement par la taille de l'intervalle le plus petit (plutôt que la taille de l'union). Cette formule n'a pas besoin d'un cas particulier pour un intervalle contenant entièrement un autre car le calcul par défaut donnera déjà une similarité de 1.0 (le chevauchement sera la taille de l'intervalle le plus petit, donc vous le diviserez par lui-même).

Donc, mettre tout cela ensemble, voici comment je réécris les choses:

def compare_intervals(st1, et1, st2, et2): # note I've renamed the ed's to et 
    # all nulls 
    if pd.isnull(st1) or pd.isnull(st2): 
     return 0. 

    # {instant, instant} => equal, not equal 
    if pd.isnull(et1) and pd.isnull(et2): 
     return float(st1 == st2) 

    # {instant, interval} => flip params (to be handled in next block) 
    if pd.isnull(et1): 
     st1, et1, st2, et2 = st2, et2, st1, et1 

    # {interval, instant} => meets, contains 
    if pd.isnull(et2): 
     return float(st1 <= st2 <= et1) 

    # {interval, interval} => equal, overlaps, not overlaps 
    if (st1 <= st2 <= et1) or (st2 <= st1 <= et2): 
     intersect = min(et1, et2) - max(st1, st2) 
     min_interval = min(et1 - st1, et2 - st2) # using minimum instead of union 
     return intersect/min_interval 

    return 0. # intervals didn't overlap 

m, k = df_for.shape 
sim = np.zeros((m, m)) 
data = df_for.values 
for i in range(m): 
    for j in range(i, m): # we only iterate on j values >= i 
     if i == j: 
      sim[i,j] = 1. 
     else: 
      sim[i,j] = sim[j,i] = compare_intervals(data[i][0], data[i][1], 
                data[j][0], data[j][1]) 

Quelques notes que je ne pouvais pas entrer dans les commentaires dans le code:

  1. Aucun test pour les valeurs étant des intervalles n'est nécessaire à la dernière étape de la fonction compare_interval, car tous les autres cas (impliquant des instants et des valeurs NULL) ont déjà été traités. J'ai modifié les affectations au tableau sim pour utiliser les index de tuple, car cela est plus naturel que les index imbriqués pour les tableaux de numpy multidimensionnels (les tranches ne fonctionnent pas si vous utilisez des index imbriqués). Vous pourriez être en mesure de faire la même chose avec les recherches data, mais je ne les ai pas changé depuis que je ne connais pas les pandas presque aussi bien que je sais numpy. En parlant de mon manque de connaissance des pandas, il y a probablement un moyen d'appliquer la fonction de comparaison directement à une "jointure" de la trame de données avec elle-même. Je ne sais pas comment faire ça. S'il existe, il sera probablement plus rapide que les boucles imbriquées que j'ai laissées pour la plupart inchangées dans le code appelant (même si cela n'optimise pas les cas symétriques).

+0

Merci d'avoir amélioré le code. est le min_interval se référant à «la taille de l'intervalle plus petit (plutôt que la taille de l'union)»? J'ai mis à jour ed1, ed2 dans votre code à et1, et2 .. – kitchenprinzessin

+0

Ouais, c'est ce que cette partie du code est destiné à faire. J'ai manqué quelques variables 'ed', donc j'ai modifié pour les corriger. – Blckknght

+0

merci.Je me demande si ce cas (discontinuité dans la similarité) pourrait s'appliquer à {instant, instant} aussi. – kitchenprinzessin

0

N'essayant pas de donner une réponse complète ici. Je créer une classe pour recevoir l'heure de début et de fin, puis effectuer toutes les vérifications null null sur le constructeur. Plus tard dans cette classe je vais surcharger les opérateurs de comparaison. Comme eq pour == et ne pour! = Sur les surcharges, vous pouvez essayer de renvoyer des valeurs entières au lieu de True et False. Et avec tout cet ensemble, je vais réécrire peut-être comme Instant (données [i] [0], données [i] [1]) == Instant (données [j] [0], données [j] [1])

et d'en tirer ce que vous avez besoin de là