Trovare differenze tra gli elementi di un elenco


113

Dato un elenco di numeri, come si trovano le differenze tra ogni ( i) -esimo elemento e il suo ( i+1) -esimo?

È meglio usare lambdaun'espressione o forse una comprensione di elenchi?

Per esempio:

Dato un elenco t=[1,3,6,...], l'obiettivo è quello di trovare un elenco v=[2,3,...]perché 3-1=2, 6-3=3ecc

Risposte:


154
>>> t
[1, 3, 6]
>>> [j-i for i, j in zip(t[:-1], t[1:])]  # or use itertools.izip in py2k
[2, 3]

14
Nel caso abbiate bisogno di differenze assolute, [abs(j-i) for i,j in zip(t, t[1:])]
Anil

Nel caso in cui desideri renderlo più efficiente: list(itertools.starmap(operator.sub, zip(t[1:], t)))(dopo aver importato itertoolse operator).
blhsing

3
In realtà semplicemente list(map(operator.sub, t[1:], t[:-1]))lo farà.
blhsing

Brillante! Amo molto questa risposta!
Chayim Friedman

104

Le altre risposte sono corrette, ma se stai facendo un lavoro numerico, potresti prendere in considerazione numpy. Usando numpy, la risposta è:

v = numpy.diff(t)

Molto utile! Grazie! np.diff([2,4,9])sarebbe[2,5]
TravelTrader

Sarebbe più efficiente della zipversione?
user760900

35

Se non vuoi usare numpyzip, puoi usare la seguente soluzione:

>>> t = [1, 3, 6]
>>> v = [t[i+1]-t[i] for i in range(len(t)-1)]
>>> v
[2, 3]

12

Puoi usare itertools.teee zipper costruire in modo efficiente il risultato:

from itertools import tee
# python2 only:
#from itertools import izip as zip

def differences(seq):
    iterable, copied = tee(seq)
    next(copied)
    for x, y in zip(iterable, copied):
        yield y - x

O usando itertools.isliceinvece:

from itertools import islice

def differences(seq):
    nexts = islice(seq, 1, None)
    for x, y in zip(seq, nexts):
        yield y - x

Puoi anche evitare di utilizzare il itertoolsmodulo:

def differences(seq):
    iterable = iter(seq)
    prev = next(iterable)
    for element in iterable:
        yield element - prev
        prev = element

Tutte queste soluzioni funzionano in uno spazio costante se non è necessario memorizzare tutti i risultati e supportare infiniti iterabili.


Di seguito alcuni micro-benchmark delle soluzioni:

In [12]: L = range(10**6)

In [13]: from collections import deque
In [15]: %timeit deque(differences_tee(L), maxlen=0)
10 loops, best of 3: 122 ms per loop

In [16]: %timeit deque(differences_islice(L), maxlen=0)
10 loops, best of 3: 127 ms per loop

In [17]: %timeit deque(differences_no_it(L), maxlen=0)
10 loops, best of 3: 89.9 ms per loop

E le altre soluzioni proposte:

In [18]: %timeit [x[1] - x[0] for x in zip(L[1:], L)]
10 loops, best of 3: 163 ms per loop

In [19]: %timeit [L[i+1]-L[i] for i in range(len(L)-1)]
1 loops, best of 3: 395 ms per loop

In [20]: import numpy as np

In [21]: %timeit np.diff(L)
1 loops, best of 3: 479 ms per loop

In [35]: %%timeit
    ...: res = []
    ...: for i in range(len(L) - 1):
    ...:     res.append(L[i+1] - L[i])
    ...: 
1 loops, best of 3: 234 ms per loop

Nota che:

  • zip(L[1:], L)è equivalente a zip(L[1:], L[:-1])poiché ziptermina già sull'input più breve, tuttavia evita una copia intera di L.
  • L'accesso ai singoli elementi tramite indice è molto lento perché ogni accesso all'indice è una chiamata di metodo in Python
  • numpy.diffè lento perché deve prima convertire il file listin un file ndarray. Ovviamente se inizi con un ndarraysarà molto più veloce:

    In [22]: arr = np.array(L)
    
    In [23]: %timeit np.diff(arr)
    100 loops, best of 3: 3.02 ms per loop

nella seconda soluzione, islice(seq, 1, None)invece di islice(seq, 1, len(seq))farlo funzionare con infiniti iterabili
Braham Snyder

5

Utilizzando l' :=operatore walrus disponibile in Python 3.8+:

>>> t = [1, 3, 6]
>>> prev = t[0]; [-prev + (prev := x) for x in t[1:]]
[2, 3]

5

Suggerirei di usare

v = np.diff(t)

questo è semplice e facile da leggere.

Ma se vuoi vavere la stessa lunghezza di tallora

v = np.diff([t[0]] + t) # for python 3.x

o

v = np.diff(t + [t[-1]])

FYI: questo funzionerà solo per gli elenchi.

per gli array numpy

v = np.diff(np.append(t[0], t))

bella risposta, puoi anche usare la parola chiave anteponi per garantire la stessa lunghezza, vedi la risposta sotto, che penso sia un po 'più ordinata
Adrian Tompkins

4

Un approccio funzionale:

>>> import operator
>>> a = [1,3,5,7,11,13,17,21]
>>> map(operator.sub, a[1:], a[:-1])
[2, 2, 2, 4, 2, 4, 4]

Utilizzando il generatore:

>>> import operator, itertools
>>> g1,g2 = itertools.tee((x*x for x in xrange(5)),2)
>>> list(itertools.imap(operator.sub, itertools.islice(g1,1,None), g2))
[1, 3, 5, 7]

Utilizzo degli indici:

>>> [a[i+1]-a[i] for i in xrange(len(a)-1)]
[2, 2, 2, 4, 2, 4, 4]

Il metodo dell'operatore è carino ed elegante
bcattle

3

Ok. Penso di aver trovato la soluzione giusta:

v = [x[1]-x[0] for x in zip(t[1:],t[:-1])]

2
ya è buono, ma penso che avrebbe dovuto essere v = [x [0] -x [1] per x in zip (t [1:], t [: - 1])] per l'elenco ordinato!
Amit Karnik

0

Soluzione con confini periodici

A volte con l'integrazione numerica potresti voler differenziare una lista con condizioni al contorno periodiche (quindi il primo elemento calcola la differenza fino all'ultimo. In questo caso la funzione numpy.roll è utile:

v-np.roll(v,1)

Soluzioni con zero anteposto

Un'altra soluzione numpy (solo per completezza) è usare

numpy.ediff1d(v)

Funziona come numpy.diff, ma solo su un vettore (appiattisce l'array di input). Offre la possibilità di anteporre o aggiungere numeri al vettore risultante. Ciò è utile quando si gestiscono i campi accumulati che spesso sono i flussi di caso nelle variabili meteorologiche (ad esempio pioggia, calore latente, ecc.), Poiché si desidera un elenco risultante della stessa lunghezza della variabile di input, con la prima voce intatta.

Allora scriveresti

np.ediff1d(v,to_begin=v[0])

Ovviamente puoi farlo anche con il comando np.diff, anche se in questo caso devi anteporre zero alla serie con la parola chiave prepend:

np.diff(v,prepend=0.0) 

Tutte le soluzioni precedenti restituiscono un vettore della stessa lunghezza dell'input.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.