2010-05-30 6 views
2

J'ai un programme qui fonctionne sur un grand nombre de données expérimentales. Les données sont stockées sous forme de liste d'objets qui sont des instances d'une classe avec les attributs suivants:Python: Amélioration de la somme cumulative longue

  • time_point - le temps de l'échantillon
  • groupe - le nom du groupe de noeuds dont l'échantillon était pris
  • nœud - le nom du noeud à partir duquel l'échantillon a été prélevé
  • qty1 = la valeur de l'échantillon pour la première quantité
  • qty2 = la valeur de l'échantillon pour la deuxième quantité

J'ai besoin de dériver certaines valeurs de l'ensemble de données, regroupées de trois façons - une fois pour l'échantillon dans son ensemble, une fois pour chaque groupe de nœuds, et une fois pour chaque nœud. Les valeurs que je dois dériver dépendent des sommes cumulatives (temps triées) de qty1 et qty2: la valeur maximale de la somme par élément des sommes cumulatives de qty1 et qty2, le point temporel auquel cette valeur maximale s'est produite, et le les valeurs de qty1 et qty2 à ce moment.

je suis venu avec la solution suivante:

dataset.sort(key=operator.attrgetter('time_point')) 

# For the whole set 
sys_qty1 = 0 
sys_qty2 = 0 
sys_combo = 0 
sys_max = 0 

# For the cluster grouping 
cluster_qty1 = defaultdict(int) 
cluster_qty2 = defaultdict(int) 
cluster_combo = defaultdict(int) 
cluster_max = defaultdict(int) 
cluster_peak = defaultdict(int) 

# For the node grouping 
node_qty1 = defaultdict(int) 
node_qty2 = defaultdict(int) 
node_combo = defaultdict(int) 
node_max = defaultdict(int) 
node_peak = defaultdict(int) 

for t in dataset: 
    # For the whole system ###################################################### 
    sys_qty1 += t.qty1 
    sys_qty2 += t.qty2 
    sys_combo = sys_qty1 + sys_qty2 
    if sys_combo > sys_max: 
    sys_max = sys_combo 
    # The Peak class is to record the time point and the cumulative quantities 
    system_peak = Peak(time_point=t.time_point, 
         qty1=sys_qty1, 
         qty2=sys_qty2) 
    # For the cluster grouping ################################################## 
    cluster_qty1[t.cluster] += t.qty1 
    cluster_qty2[t.cluster] += t.qty2 
    cluster_combo[t.cluster] = cluster_qty1[t.cluster] + cluster_qty2[t.cluster] 
    if cluster_combo[t.cluster] > cluster_max[t.cluster]: 
    cluster_max[t.cluster] = cluster_combo[t.cluster] 
    cluster_peak[t.cluster] = Peak(time_point=t.time_point, 
            qty1=cluster_qty1[t.cluster], 
            qty2=cluster_qty2[t.cluster]) 
    # For the node grouping ##################################################### 
    node_qty1[t.node] += t.qty1 
    node_qty2[t.node] += t.qty2 
    node_combo[t.node] = node_qty1[t.node] + node_qty2[t.node] 
    if node_combo[t.node] > node_max[t.node]: 
    node_max[t.node] = node_combo[t.node] 
    node_peak[t.node] = Peak(time_point=t.time_point, 
          qty1=node_qty1[t.node], 
          qty2=node_qty2[t.node]) 

Cela produit la sortie correcte, mais je me demande si elle peut être plus lisible/Pythonic et/ou plus rapide/plus évolutive. Ce qui précède est attrayant en ce sens qu'il ne fait qu'une boucle dans le (grand) jeu de données une fois, mais peu attrayant du fait que j'ai essentiellement copié/collé trois copies du même algorithme.

Pour éviter les problèmes de copier/coller de ce qui précède, j'ai essayé cela aussi:

def find_peaks(level, dataset): 

    def grouping(object, attr_name): 
    if attr_name == 'system': 
     return attr_name 
    else: 
     return object.__dict__[attrname] 

    cuml_qty1 = defaultdict(int) 
    cuml_qty2 = defaultdict(int) 
    cuml_combo = defaultdict(int) 
    level_max = defaultdict(int) 
    level_peak = defaultdict(int) 

    for t in dataset: 
    cuml_qty1[grouping(t, level)] += t.qty1 
    cuml_qty2[grouping(t, level)] += t.qty2 
    cuml_combo[grouping(t, level)] = (cuml_qty1[grouping(t, level)] + 
             cuml_qty2[grouping(t, level)]) 
    if cuml_combo[grouping(t, level)] > level_max[grouping(t, level)]: 
     level_max[grouping(t, level)] = cuml_combo[grouping(t, level)] 
     level_peak[grouping(t, level)] = Peak(time_point=t.time_point, 
              qty1=node_qty1[grouping(t, level)], 
              qty2=node_qty2[grouping(t, level)]) 
    return level_peak 

system_peak = find_peaks('system', dataset) 
cluster_peak = find_peaks('cluster', dataset) 
node_peak = find_peaks('node', dataset) 

Pour les (non regroupés) calculs au niveau du système, Je suis également venu avec ce qui est assez:

dataset.sort(key=operator.attrgetter('time_point')) 

def cuml_sum(seq): 
    rseq = [] 
    t = 0 
    for i in seq: 
    t += i 
    rseq.append(t) 
    return rseq 

time_get = operator.attrgetter('time_point') 
q1_get = operator.attrgetter('qty1') 
q2_get = operator.attrgetter('qty2') 

timeline = [time_get(t) for t in dataset] 
cuml_qty1 = cuml_sum([q1_get(t) for t in dataset]) 
cuml_qty2 = cuml_sum([q2_get(t) for t in dataset]) 
cuml_combo = [q1 + q2 for q1, q2 in zip(cuml_qty1, cuml_qty2)] 

combo_max = max(cuml_combo) 
time_max = timeline.index(combo_max) 
q1_at_max = cuml_qty1.index(time_max) 
q2_at_max = cuml_qty2.index(time_max) 

Cependant, des compréhensions de liste et zip() malgré l'utilisation fraîche de cette version, il boucle dans l'ensemble de données trois fois seulement pour les calculs au niveau du système, et je ne peux pas penser à une bonne façon de faire la calaculations au niveau du cluster et du nœud sans faire quelque chose de lent comme:

timeline = defaultdict(int) 
cuml_qty1 = defaultdict(int) 
#...etc. 

for c in cluster_list: 
    timeline[c] = [time_get(t) for t in dataset if t.cluster == c] 
    cuml_qty1[c] = [q1_get(t) for t in dataset if t.cluster == c] 
    #...etc. 

Est-ce que quelqu'un ici à Stack Overflow a des suggestions d'améliorations? Le premier extrait ci-dessus fonctionne bien pour mon jeu de données initial (de l'ordre du million d'enregistrements), mais les jeux de données ultérieurs auront plus d'enregistrements et de clusters/nœuds, donc l'évolutivité est une préoccupation. Ceci est ma première utilisation non-triviale de Python, et je veux m'assurer que je profite bien du langage (ceci remplace un ensemble très compliqué de requêtes SQL, et les versions antérieures de la version Python étaient transalations droites essentiellement très inefficaces de ce que cela a fait). En général, je ne fais pas beaucoup de programmation, il me manque peut-être quelque chose d'élémentaire.

Merci beaucoup!

+0

Vous pouvez d'abord effectuer tous les calculs de nœud, puis utiliser les résultats de nœud pour calculer les résultats du cluster, puis utiliser les résultats de cluster pour calculer les résultats à l'échelle du système. Cela réduirait au moins une partie de la répétition (ajouts identiques) que vous faites actuellement. – unutbu

+0

Merci pour la suggestion. Cependant, le pic du cluster pourrait être différent de n'importe lequel des pics des nœuds individuels. Par exemple, ils peuvent tous atteindre une valeur moyenne à la fois, provoquant un pic énorme pour le cluster, mais un pic non-énorme pour un nœud individuel. – bbayles

Répondre

2

Cela semble être une opportunité classique d'appliquer une petite orientation objet. Je suggérerais de faire une classe des données dérivées et de faire abstraction du calcul de la somme cumulative en quelque chose qui fonctionne sur cette classe.

Quelque chose comme:

class DerivedData(object): 
    def __init__(self): 
     self.qty1 = 0.0 
     self.qty2 = 0.0 
     self.combo = 0.0 
     self.max = 0.0 
     self.peak = Peak(time_point=0.0, qty1=0.0, qty2=0.0) 

    def accumulate(self, data): 
     self.qty1 += data.qty1 
     self.qty2 += data.qty2 
     self.combo = self.qty1 + self.qty2 
     if self.combo > self.max: 
      self.max = self.combo 
      self.peak = Peak(time_point=data.time_point, 
          qty1=self.qty1, 
          qty2=self.qty2) 

sys = DerivedData() 
clusters = defaultdict(DerivedData) 
nodes = defaultdict(DerivedData) 

dataset.sort(key=operator.attrgetter('time_point')) 

for t in dataset: 
    sys.accumulate(t) 
    clusters[t.cluster].accumulate(t) 
    nodes[t.node].accumulate(t) 

Cette solution fait abstraction la logique pour trouver les sommets mais ne passe par l'ensemble de données une fois.

+0

Peter, merci beaucoup. Cela semble certainement beaucoup plus agréable. Je vais essayer et voir comment ça se passe. J'aurais dû mentionner que toutes les valeurs de temps et de données sont garanties être des nombres entiers (et en fait la somme de chaque quantité à chaque niveau est égale à zéro). – bbayles

Questions connexes