Je travaille en Python 2.7, et j'ai des horodatages et des valeurs correspondantes. Je veux définir ces valeurs sur une base de temps d'une valeur pour chaque seconde, à savoir la dernière valeur mesurée. Donc:

[[1,  4,  6],
 [15, 17, 12]]

À:

[[1,  2,  3,  4,  4,  6],
 [15, 15, 15, 17, 17, 12]]

J'ai trouvé ça, qui fait ce que je veux, mais il doit y avoir une manière plus élégante. Quelqu'un en connaît-il un?

import numpy as np

#Example data:
origdata= {}
origdata['time'] = [4, 26, 37, 51, 59, 71, 93]
origdata['vals'] = [17, 5, 43, 21, 14, 8, np.NaN]

extratime = [t-1 for t in origdata['time']]
data={}
data['time'] = np.concatenate((origdata['time'][:-1], extratime[1:]), axis=0)
data['vals'] = np.concatenate((origdata['vals'][:-1], origdata['vals'][:-1]), axis=0)

sorter = data['time'].argsort()
data['time'] = data['time'][sorter]
data['vals'] = data['vals'][sorter]

filledOutData = {}
filledOutData['time'] = range(data['time'][0], data['time'][-1])
filledOutData['vals'] = np.interp(filledOutData['time'], data['time'], data['vals'])

Le traçage des données d'origine et du résultat souhaité avec le code suivant donne l'image ci-dessous:

import matplotlib.pyplot as plt
plt.plot(origdata['time'], origdata['vals'], '-o', filledOutData['time'], filledOutData['vals'], '.-')
plt.legend(['original', 'desired result'])
plt.show

An illustration of what I want

2
Swier 8 mars 2016 à 18:46

5 réponses

Meilleure réponse

Il s'avère que cela s'appelle une interpolation constante par morceaux qui peut être effectuée avec le paquet scipy:

import scipy as sp

interpolator = sp.interpolate.interp1d(origdata['time'], origdata['vals'], kind='zero')

filledOutData2 = {}
filledOutData2['time'] = range(origdata['time'][0],origdata['time'][-1])
filledOutData2['vals'] = interpolator(filledOutData2['time'])

Modifier: Ou en fonction:

def interp_piecewise_constant(times,values):
  interpolator = sp.interpolate.interp1d(times, values, kind='zero')
  newTimes = range(times[0], times[-1])
  return(newTimes, interpolator(newTimes))
0
Swier 8 mars 2016 à 17:27

Essaye ça:

data = {}
times = [4, 26, 37, 51, 59, 71, 93]
vals = [17, 5, 43, 21, 14, 8, float('nan')]
# i don't have numpy so i had to change to nan

for i in range(times[0], times[-1]+1):
    if i in times:
        v = vals[times.index(i)]
    data.setdefault('time', []).append(i)
    data.setdefault('vals', []).append(v)

print data['time']

[4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28 , 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53 , 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78 , 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93]

print data['vals']

[17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 5, 5, 5 , 5, 5, 5, 5, 5, 5, 5, 5, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 43, 21, 21, 21 , 21, 21, 21, 21, 21, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 8, 8, 8, 8, 8, 8, 8, 8 , 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, nan]

1
gkusner 8 mars 2016 à 16:09

Et un générateur?

>>> def fill(times, vals):
...     lt, lv = times[0], vals[0]
...     for nt, nv in zip(times[1:], vals[1:]):
...         while lt < nt:
...             yield lt, lv
...             lt += 1
...         lt, lv = nt, nv
...     yield nt, nv
...
>>> times = [4, 26, 37, 51, 59, 71, 93]
... vals = [17, 5, 43, 21, 14, 8, float('nan')]
...
>>> list(fill(times, vals))
[(4, 17),
(5, 17),
(6, 17),
...
(91, 8),
(92, 8),
(93, nan)]

Si vous avez besoin de listes séparées, vous pouvez décompresser les tuples générés de cette manière:

>>> from itertools import tee
>>> filled_times, filled_vals = [list((t[idx] for t in it)) 
...                              for idx, it in enumerate(tee(fill(times, vals)))]
0
Michael Amrhein 8 mars 2016 à 21:36

J'utiliserais un générateur pour créer les valeurs de données intermédiaires:

def piecewise(x_vals,y_vals,offset=0.1):
    iter_x = iter(x_vals)
    iter_y = iter(y_vals) #use iterators
    y = next(iter_y)#get first y value
    yield next(iter_x),y #first pair
    while True:
        x = next(iter_x)
        yield x-offset, y #gives the x value - offset with previous y value
        y = next(iter_y)
        yield x,y        #actual data points

Essentiellement pour les listes de [x1, x2, x3 ...] et [y1, y2, y3 ...] cela génère la sortie:

(x1,y1), (x2-1,y1), (x2,y2), (x3-1,y2), (x3,y3)...

Il ne crée donc que deux fois la quantité de points de données d'origine au lieu de toutes les autres solutions qui créent tout le possible entre les points avec des nombres entiers. (cela fonctionnerait aussi bien avec de petites valeurs décimales)

De cette façon, vous pouvez créer les données remplies comme ceci:

filledOutData= {'time':[],'vals':[]}
for x,y in piecewise(origdata['time'],origdata['vals']):
    filledOutData['time'].append(x)
    filledOutData['vals'].append(y)

Ou plus court avec zip et affectation simultanée:

filledOutData= {}
filledOutData['time'],filledOutData['vals'] = zip(*piecewise(origdata['time'],origdata['vals']))

diagram of output

Notez que cela ne fonctionne probablement que si la liste horaire d'origine est triée correctement.

0
Tadhg McDonald-Jensen 8 mars 2016 à 17:29

Cela devrait bien fonctionner et a une structure logique assez simple.

def interpolate(data):
    times, values = data
    output = []

    time, end, index = times[0], times[-1], 0
    while time <= end:
        if time == times[index + 1]:
            index += 1
        output.append((time, values[index]))
        time += 1

    return zip(*output)

print interpolate([[1, 4, 6], [15, 17, 12]])
# [(1, 2, 3, 4, 5, 6), (15, 15, 15, 17, 17, 12)]
0
Jared Goguen 8 mars 2016 à 21:51