Question

Dans un tableau où la première colonne est quelques secondes après un certain point de référence et la seconde une mesure arbitraire:

6   0.738158581
21  0.801697222
39  1.797224596
49  2.77920469
54  2.839757536
79  3.832232283
91  4.676794376
97  5.18244704
100 5.521878863
118 6.316630137
131 6.778507504
147 7.020395216
157 7.331607129
176 7.637492223
202 7.848079136
223 7.989456499
251 8.76853608
278 9.092367123 
    ...

Comme vous le voyez, les mesures sont échantillonnées à des instants irréguliers. Je dois lisser les données en effectuant une moyenne des lectures jusqu'à 100 secondes avant chaque mesure (en Python). Étant donné que la table de données est énorme, une méthode basée sur un itérateur est vraiment préférable. Malheureusement, après deux heures de codage, je ne parviens pas à trouver une solution efficace et élégante.

Quelqu'un peut-il m'aider?

MODIFIER s

  1. Je veux une lecture lissée pour chaque lecture brute, et la lecture lissée doit être la moyenne arithmétique de la lecture brute et de toutes les autres valeurs des 100 dernières secondes (delta). (John, vous avez raison)

  2. Énorme ~ 1e6 - 10e6 lignes + nécessité de travailler avec une RAM serrée

  3. Les données sont approximativement aléatoires

  4. Les données sont triées

RÉSOLUTION

J'ai testé les solutions proposées par J Machin et Yairchu. Ils ont tous deux donné les mêmes résultats, cependant, sur mon jeu de données, la version de J Machin fonctionne de manière exponentielle, tandis que celle de yairchu est linéaire. Voici les temps d'exécution, mesurés par le % timeit d'IPython (en microsecondes):

data size   J Machin    yairchu
10        90.2        55.6
50          930         258
100         3080        514
500         64700       2660
1000        253000      5390
2000        952000      11500

Merci à tous pour votre aide.

Était-ce utile?

La solution

J'utilise un résultat somme auquel j'ajoute les nouveaux membres et soustrais les anciens. Cependant, de cette manière, on risque de subir des imprécisions en virgule flottante.

Par conséquent, j'implémente un " Deque " avec une liste. Et chaque fois que mon Deque réaffecte à une taille plus petite. Je recalcule la somme à la même occasion.

Je calcule également la moyenne jusqu'au point x, y compris le point x, afin qu'il y ait au moins un point d'échantillonnage à la moyenne.

def getAvgValues(data, avgSampleTime):
  lastTime = 0
  prevValsBuf = []
  prevValsStart = 0
  tot = 0
  for t, v in data:
    avgStart = t - avgSampleTime
    # remove too old values
    while prevValsStart < len(prevValsBuf):
      pt, pv = prevValsBuf[prevValsStart]
      if pt > avgStart:
        break
      tot -= pv
      prevValsStart += 1
    # add new item
    tot += v
    prevValsBuf.append((t, v))
    # yield result
    numItems = len(prevValsBuf) - prevValsStart
    yield (t, tot / numItems)
    # clean prevVals if it's time
    if prevValsStart * 2 > len(prevValsBuf):
      prevValsBuf = prevValsBuf[prevValsStart:]
      prevValsStart = 0
      # recalculate tot for not accumulating float precision error
      tot = sum(v for (t, v) in prevValsBuf)

Autres conseils

Vous n'avez pas dit exactement quand vous voulez une sortie. Je suppose que vous voulez une lecture lissée pour chaque lecture brute, et la lecture lissée doit être la moyenne arithmétique de la lecture brute et de toutes les autres valeurs des 100 dernières secondes (delta).

Réponse courte: utilisez un collections.deque ... il ne pourra jamais contenir plus de " delta " secondes de lectures. La manière dont je l'ai configurée vous permet de traiter la deque comme une liste et de calculer facilement la moyenne ou une gizmoïde sophistiquée qui donne plus de poids aux lectures récentes.

Réponse longue:

>>> the_data = [tuple(map(float, x.split())) for x in """\
... 6       0.738158581
... 21      0.801697222
[snip]
... 251     8.76853608
... 278     9.092367123""".splitlines()]
>>> import collections
>>> delta = 100.0
>>> q = collections.deque()
>>> for t, v in the_data:
...     while q and q[0][0] <= t - delta:
...         # jettison outdated readings
...         _unused = q.popleft()
...     q.append((t, v))
...     count = len(q)
...     print t, sum(item[1] for item in q) / count, count
...
...
6.0 0.738158581 1
21.0 0.7699279015 2
39.0 1.112360133 3
49.0 1.52907127225 4
54.0 1.791208525 5
79.0 2.13137915133 6
91.0 2.49500989771 7
97.0 2.8309395405 8
100.0 3.12993279856 9
118.0 3.74976297144 9
131.0 4.41385300278 9
147.0 4.99420529389 9
157.0 5.8325615685 8
176.0 6.033109419 9
202.0 7.15545189083 6
223.0 7.4342562845 6
251.0 7.9150342134 5
278.0 8.4246097095 4
>>>

Modifier

Guichet unique: obtenez votre gizmoïde sophistiqué ici. Voici le code:

numerator = sum(item[1] * upsilon ** (t - item[0]) for item in q)
denominator = sum(upsilon ** (t - item[0]) for item in q)
gizmoid = numerator / denominator

où upsilon devrait être un peu moins de 1.0 (< = zéro est illégal, juste au-dessus de zéro fait peu de lissage, on obtient la moyenne arithmétique plus le temps processeur perdu, et supérieur à un donne l'inverse de votre but).

Vos données semblent être à peu près linéaires:

Tracé de vos données http://rix0r.nl/~rix0r/share /shot-20090621.144851.gif

Quel type de lissage cherchez-vous? Un ajustement des moindres carrés d'une ligne à cet ensemble de données? Une sorte de filtre passe-bas? Ou autre chose?

Merci de nous indiquer l'application pour que nous puissions vous conseiller un peu mieux.

EDIT: par exemple, en fonction de l’application, interpoler une ligne entre le premier et le dernier point peut être suffisant pour vos besoins.

Celui-ci le rend linéaire:

def process_data(datafile):
    previous_n = 0
    previous_t = 0
    for line in datafile:
        t, number = line.strip().split()
        t = int(t)
        number = float(number)
        delta_n = number - previous_n
        delta_t = t - previous_t
        n_per_t = delta_n / delta_t
        for t0 in xrange(delta_t):
            yield previous_t + t0, previous_n + (n_per_t * t0)
        previous_n = n
        previous_t = t

f = open('datafile.dat')

for sample in process_data(f):
    print sample

O (1) mémoire si vous pouvez réitérer l'entrée plusieurs fois - vous pouvez utiliser un itérateur pour le & "gauche &"; et un pour le & "droit &";.

def getAvgValues(makeIter, avgSampleTime):
  leftIter = makeIter()
  leftT, leftV = leftIter.next()
  tot = 0
  count = 0
  for rightT, rightV in makeIter():
    tot += rightV
    count += 1
    while leftT <= rightT - avgSampleTime:
      tot -= leftV
      count -= 1
      leftT, leftV = leftIter.next()
    yield rightT, tot / count

Bien que cela donne une moyenne en déclin exponentiel, plutôt qu’une moyenne totale, je pense que vous voudrez peut-être ce que j’appelle un moyenne mobile exponentielle avec alpha variable , qui est en réalité un filtre passe-bas unipolaire. Il existe maintenant une solution à cette question, qui s'exécute dans le temps en fonction du nombre de points de données. Voyez si cela fonctionne pour vous.

Qu'en est-il de ce genre de chose, conservez les valeurs jusqu'à ce que la différence de temps avec la dernière fois soit > 100, moyenne et donne de telles valeurs par exemple

def getAvgValues(data):
    lastTime = 0
    prevValues = []
    avgSampleTime=100

    for t, v in data:
        if t - lastTime < avgSampleTime:
            prevValues.append(v)
        else:
            avgV = sum(prevValues)/len(prevValues)
            lastTime = t
            prevValues = [v]
            yield (t,avgV)

for v in getAvgValues(data):
    print v

On dirait que vous avez besoin d’une formule d’arrondi simple. Pour arrondir un nombre quelconque à un intervalle quelconque:

round (num / interval) * intervalle

Vous pouvez remplacer rond par sol ou plafond par & "; précédant &"; ou " depuis " affecte. Il peut fonctionner dans n’importe quel langage, y compris SQL.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top