W następstwie tego pytania sprzed lat, czy istnieje kanoniczna funkcja „przesunięcia” w numpy? Nie widzę nic z dokumentacji .
Oto prosta wersja tego, czego szukam:
def shift(xs, n):
if n >= 0:
return np.r_[np.full(n, np.nan), xs[:-n]]
else:
return np.r_[xs[-n:], np.full(-n, np.nan)]
Używanie tego jest jak:
In [76]: xs
Out[76]: array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
In [77]: shift(xs, 3)
Out[77]: array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])
In [78]: shift(xs, -3)
Out[78]: array([ 3., 4., 5., 6., 7., 8., 9., nan, nan, nan])
To pytanie zrodziło się z mojej wczorajszej próby napisania produktu typu fast rolling_product . Potrzebowałem sposobu na „przesunięcie” skumulowanego produktu i jedyne, o czym mogłem myśleć, to powielenie logiki np.roll()
.
Więc np.concatenate()
jest znacznie szybszy niż np.r_[]
. Ta wersja funkcji działa znacznie lepiej:
def shift(xs, n):
if n >= 0:
return np.concatenate((np.full(n, np.nan), xs[:-n]))
else:
return np.concatenate((xs[-n:], np.full(-n, np.nan)))
Jeszcze szybsza wersja po prostu wstępnie alokuje tablicę:
def shift(xs, n):
e = np.empty_like(xs)
if n >= 0:
e[:n] = np.nan
e[n:] = xs[:-n]
else:
e[n:] = np.nan
e[:n] = xs[-n:]
return e
np.r_[np.full(n, np.nan), xs[:-n]]
można by go wymienić nanp.r_[[np.nan]*n, xs[:-n]]
podobnie w innym stanie, bez potrzebynp.full
[np.nan]*n
to zwykły Python i dlatego będzie wolniejszy niżnp.full(n, np.nan)
. Nie dla małychn
, ale zostanie przekształcony w tablicę numpy przez np.r_, co odbiera przewagę.[np.nan]*n
jest szybszy niż wnp.full(n, np.nan)
przypadkun=[10,1000,10000]
. Muszę sprawdzić, czynp.r_
trafia.Odpowiedzi:
Nie numpy, ale scipy zapewnia dokładnie taką funkcjonalność zmiany, jaką chcesz,
import numpy as np from scipy.ndimage.interpolation import shift xs = np.array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]) shift(xs, 3, cval=np.NaN)
gdzie domyślnie jest wprowadzana stała wartość spoza tablicy z wartością
cval
, ustawioną tutaj nanan
. Daje to pożądaną wydajność,array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])
a ujemne przesunięcie działa podobnie,
shift(xs, -3, cval=np.NaN)
Zapewnia wyjście
array([ 3., 4., 5., 6., 7., 8., 9., nan, nan, nan])
źródło
Dla tych, którzy chcą po prostu skopiować i wkleić najszybszą implementację zmiany, jest punkt odniesienia i wniosek (patrz koniec). Dodatkowo wprowadzam parametr fill_value i poprawiam kilka błędów.
Reper
import numpy as np import timeit # enhanced from IronManMark20 version def shift1(arr, num, fill_value=np.nan): arr = np.roll(arr,num) if num < 0: arr[num:] = fill_value elif num > 0: arr[:num] = fill_value return arr # use np.roll and np.put by IronManMark20 def shift2(arr,num): arr=np.roll(arr,num) if num<0: np.put(arr,range(len(arr)+num,len(arr)),np.nan) elif num > 0: np.put(arr,range(num),np.nan) return arr # use np.pad and slice by me. def shift3(arr, num, fill_value=np.nan): l = len(arr) if num < 0: arr = np.pad(arr, (0, abs(num)), mode='constant', constant_values=(fill_value,))[:-num] elif num > 0: arr = np.pad(arr, (num, 0), mode='constant', constant_values=(fill_value,))[:-num] return arr # use np.concatenate and np.full by chrisaycock def shift4(arr, num, fill_value=np.nan): if num >= 0: return np.concatenate((np.full(num, fill_value), arr[:-num])) else: return np.concatenate((arr[-num:], np.full(-num, fill_value))) # preallocate empty array and assign slice by chrisaycock def shift5(arr, num, fill_value=np.nan): result = np.empty_like(arr) if num > 0: result[:num] = fill_value result[num:] = arr[:-num] elif num < 0: result[num:] = fill_value result[:num] = arr[-num:] else: result[:] = arr return result arr = np.arange(2000).astype(float) def benchmark_shift1(): shift1(arr, 3) def benchmark_shift2(): shift2(arr, 3) def benchmark_shift3(): shift3(arr, 3) def benchmark_shift4(): shift4(arr, 3) def benchmark_shift5(): shift5(arr, 3) benchmark_set = ['benchmark_shift1', 'benchmark_shift2', 'benchmark_shift3', 'benchmark_shift4', 'benchmark_shift5'] for x in benchmark_set: number = 10000 t = timeit.timeit('%s()' % x, 'from __main__ import %s' % x, number=number) print '%s time: %f' % (x, t)
wynik testu porównawczego:
benchmark_shift1 time: 0.265238 benchmark_shift2 time: 0.285175 benchmark_shift3 time: 0.473890 benchmark_shift4 time: 0.099049 benchmark_shift5 time: 0.052836
Wniosek
shift5 jest zwycięzcą! To trzecie rozwiązanie OP.
źródło
shift5
lepiej jest pisaćresult[:] = arr
zamiast pisaćresult = arr
, aby zachować spójność zachowania funkcji.type(np.NAN) is float
. Jeśli przesuniesz tablicę liczb całkowitych za pomocą tych funkcji, musisz określić liczbę całkowitą wartość_pełnienia.Nie ma jednej funkcji, która robi to, co chcesz. Twoja definicja zmiany jest nieco inna niż to, co robi większość ludzi. Sposoby przesuwania tablicy są częściej zapętlone:
>>>xs=np.array([1,2,3,4,5]) >>>shift(xs,3) array([3,4,5,1,2])
Możesz jednak robić, co chcesz, dzięki dwóm funkcjom.
Rozważ
a=np.array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.])
:def shift2(arr,num): arr=np.roll(arr,num) if num<0: np.put(arr,range(len(arr)+num,len(arr)),np.nan) elif num > 0: np.put(arr,range(num),np.nan) return arr >>>shift2(a,3) [ nan nan nan 0. 1. 2. 3. 4. 5. 6.] >>>shift2(a,-3) [ 3. 4. 5. 6. 7. 8. 9. nan nan nan]
Po uruchomieniu cProfile na podanej funkcji i powyższym kodzie, który podałeś, stwierdziłem, że podany kod wykonuje 42 wywołania funkcji podczas
shift2
wykonywania 14 wywołań, gdy arr jest dodatni i 16, gdy jest ujemny.Będę eksperymentować z synchronizacją, aby zobaczyć, jak każdy radzi sobie z rzeczywistymi danymi.źródło
np.roll()
; Użyłem tej techniki w linkach w moim pytaniu. Jeśli chodzi o Twoją implementację, czy jest szansa, że Twoja funkcja będzie działać dla ujemnych wartości przesunięcia?np.concatenate()
jest dużo szybszy niżnp.r_[]
. Wnp.roll()
końcu to pierwsze jest tym , czego używa.Możesz najpierw przekonwertować
ndarray
naSeries
lubDataFrame
zpandas
, a następnie możesz użyćshift
metody, jak chcesz.Przykład:
In [1]: from pandas import Series In [2]: data = np.arange(10) In [3]: data Out[3]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) In [4]: data = Series(data) In [5]: data Out[5]: 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 dtype: int64 In [6]: data = data.shift(3) In [7]: data Out[7]: 0 NaN 1 NaN 2 NaN 3 0.0 4 1.0 5 2.0 6 3.0 7 4.0 8 5.0 9 6.0 dtype: float64 In [8]: data = data.values In [9]: data Out[9]: array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])
źródło
Benchmarki i wprowadzenie Numba
1. Podsumowanie
scipy.ndimage.interpolation.shift
) jest najwolniejszym rozwiązaniem wymienionym na tej stronie.(1) długości twoich tablic
(2) ilości zmiany, którą musisz zrobić.
2. Szczegółowe testy porównawcze z najlepszymi opcjami
shift4_numba
(zdefiniowane poniżej), jeśli chcesz mieć dobry, wszechstronny3. Kod
3.1
shift4_numba
import numba @numba.njit def shift4_numba(arr, num, fill_value=np.nan): if num >= 0: return np.concatenate((np.full(num, fill_value), arr[:-num])) else: return np.concatenate((arr[-num:], np.full(-num, fill_value)))
3.2.
shift5_numba
import numba @numba.njit def shift5_numba(arr, num, fill_value=np.nan): result = np.empty_like(arr) if num > 0: result[:num] = fill_value result[num:] = arr[:-num] elif num < 0: result[num:] = fill_value result[:num] = arr[-num:] else: result[:] = arr return result
3.3.
shift5
shift5_numba
, po prostu usuń dekorator @ numba.njit.4 Dodatek
4.1 Szczegóły dotyczące zastosowanych metod
shift_scipy
:scipy.ndimage.interpolation.shift
(scipy 1.4.1) - Opcja z zaakceptowanej odpowiedzi, która jest zdecydowanie najwolniejszą alternatywą .shift1
:np.roll
orazout[:num] xnp.nan
przez IronManMark20 i gzcshift2
:np.roll
inp.put
przez IronManMark20shift3
:np.pad
islice
przez gzcshift4
:np.concatenate
inp.full
przez chrisaycockshift5
: używając dwa razyresult[slice] = x
przez chrisaycockshift#_numba
: @ numba .njit zdobione wersje poprzedniego.shift2
Ishift3
zawarte funkcje, które nie były obsługiwane przez obecną Numba (0.50.1).4.2 Inne wyniki testów
4.2.1 Względne czasy, wszystkie metody
4.2.2 Surowe czasy, wszystkie metody
4.2.3 Surowe czasy, kilka najlepszych metod
źródło
Możesz to również zrobić z Pandami:
Korzystanie z tablicy o długości 2356:
import numpy as np xs = np.array([...])
Korzystanie z Scipy:
from scipy.ndimage.interpolation import shift %timeit shift(xs, 1, cval=np.nan) # 956 µs ± 77.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Korzystanie z Pand:
import pandas as pd %timeit pd.Series(xs).shift(1).values # 377 µs ± 9.42 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
W tym przykładzie użycie Pand było około ~ 8 razy szybsze niż Scipy
źródło
Series
technika zajęła 146 nas na moim komputerze, podczas gdy moje podejście zajęło mniej niż 4 nas.Jeśli chcesz mieć jedną linijkę od numpy i nie przejmujesz się zbytnio wydajnością, spróbuj:
np.sum(np.diag(the_array,1),0)[:-1]
Wyjaśnienie:
np.diag(the_array,1)
tworzy macierz z tablicą równą przekątnej,np.sum(...,0)
sumuje macierz według kolumn i...[:-1]
pobiera elementy, które odpowiadałyby rozmiarowi oryginalnej tablicy. Zabawa z parametrami1
i:-1
as może spowodować przesunięcia w różnych kierunkach.źródło
Jednym ze sposobów na zrobienie tego bez rozlewania kodu na skrzynki
z tablicą:
def shift(arr, dx, default_value): result = np.empty_like(arr) get_neg_or_none = lambda s: s if s < 0 else None get_pos_or_none = lambda s: s if s > 0 else None result[get_neg_or_none(dx): get_pos_or_none(dx)] = default_value result[get_pos_or_none(dx): get_neg_or_none(dx)] = arr[get_pos_or_none(-dx): get_neg_or_none(-dx)] return result
z matrycą można to zrobić w następujący sposób:
def shift(image, dx, dy, default_value): res = np.full_like(image, default_value) get_neg_or_none = lambda s: s if s < 0 else None get_pos_or_none = lambda s : s if s > 0 else None res[get_pos_or_none(-dy): get_neg_or_none(-dy), get_pos_or_none(-dx): get_neg_or_none(-dx)] = \ image[get_pos_or_none(dy): get_neg_or_none(dy), get_pos_or_none(dx): get_neg_or_none(dx)] return res
źródło