Uyuşmuş bir dizideki öğeleri kaydır


84

Yıllar önce bu sorunun ardından , uyuşuklukta kanonik bir "kaydırma" işlevi var mı? Belgelerden hiçbir şey görmüyorum .

İşte aradığım şeyin basit bir versiyonu:

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)]

Bunu kullanmak şuna benzer:

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])

Bu soru, dün hızlı yuvarlanan bir ürün yazma girişimimden geldi . Kümülatif bir ürünü "değiştirmek" için bir yola ihtiyacım vardı ve tek düşünebildiğim mantığı tekrarlamaktı np.roll().


Bu yüzden np.concatenate()çok daha hızlı np.r_[]. İşlevin bu sürümü çok daha iyi performans gösteriyor:

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)))

Daha da hızlı bir sürüm, diziyi önceden tahsis eder:

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]]np.r_[[np.nan]*n, xs[:-n]]np.full
Sıfır

2
@JohnGalt [np.nan]*ndüz bir python ve bu nedenle daha yavaş olacaktır np.full(n, np.nan). Küçük için değil n, ama np.r_ tarafından np.r_ tarafından numpy dizisine dönüştürülecek ve bu da avantajı ortadan kaldıracaktır.
swenzel

@swenzel Sadece zaman aşımına ve [np.nan]*ndaha hızlı olduğu np.full(n, np.nan)için n=[10,1000,10000]. np.r_Bir isabet alıp almadığını kontrol etmeniz gerekiyor .
Zero

Hız önemliyse, dizi boyutu en iyi algoritma için büyük bir rol oynar (aşağıya bir kıyaslama karşılaştırması eklendi). Ayrıca, günümüzde numba.njit, tekrar tekrar çağrılırsa vardiyayı daha hızlı yapmak için kullanılabilir.
np8

Yanıtlar:


101

Uyuşuk değil ama scipy tam olarak istediğiniz vardiya işlevselliğini sağlar,

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)

varsayılan değer ile dizinin dışından sabit bir değer getirmektir cval, burada olarak ayarlayın nan. Bu istenen çıktıyı verir,

array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])

ve negatif değişim benzer şekilde çalışır,

shift(xs, -3, cval=np.NaN)

Çıktı sağlar

array([  3.,   4.,   5.,   6.,   7.,   8.,   9.,  nan,  nan,  nan])

23
Scipy shift işlevi GERÇEKTEN yavaştır. Np.concatenate kullanarak kendim yaptım ve çok daha hızlıydı.
gaefan

12
numpy.roll daha hızlıdır. Pandalar da kullanıyor. github.com/pandas-dev/pandas/blob/v0.19.2/pandas/core/…
fx-kirin

Scipy.ndimage.interpolation.shift'i (scipy 1.4.1) bu sayfada listelenen diğer tüm alternatiflerle karşılaştırarak test ettim (aşağıdaki cevabıma bakın) ve bu olası en yavaş çözümdür. Yalnızca uygulamanızda hızın önemi yoksa kullanın.
np8

72

En hızlı vardiya uygulamasını kopyalayıp yapıştırmak isteyenler için bir kıyaslama ve sonuç var (sonuna bakın). Ek olarak, fill_value parametresini tanıtıyorum ve bazı hataları düzelttim.

Kıyaslama

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)

karşılaştırma sonucu:

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

Sonuç

shift5 kazanır! OP'nin üçüncü çözümü.


Karşılaştırmalar için teşekkürler. Yeni bir dizi kullanmadan bunu yapmanın en hızlı yolu nedir?
FiReTiTi

2
Son cümlede, işlev davranışını tutarlı tutmak yerine shift5yazmak daha iyidir . result[:] = arrresult = arr
avysk

2
Cevap olarak bu seçilmeli
wyx

@avysk yorumu oldukça önemlidir - lütfen shift5 yöntemini güncelleyin. Bazen bir kopya döndüren bazen de bir referans döndüren işlevler cehenneme giden yoldur.
David

2
@ Josmoor98 Bunun nedeni type(np.NAN) is float. Bu işlevleri kullanarak tamsayı dizisini kaydırırsanız, bir tamsayı doldurma_değerini belirtmeniz gerekir.
gzc

9

İstediğinizi yapan tek bir işlev yoktur. Vardiya tanımınız çoğu insanın yaptığından biraz farklı. Bir diziyi kaydırmanın yolları daha yaygın olarak döngülüdür:

>>>xs=np.array([1,2,3,4,5])
>>>shift(xs,3)
array([3,4,5,1,2])

Ancak, iki işlevle istediğinizi yapabilirsiniz.
Düşünün 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]

Verdiğiniz fonksiyon ve sağladığınız yukarıdaki kod üzerinde cProfile çalıştırdıktan sonra, sağladığınız kodun shift2arr pozitif olduğunda 14, negatif olduğunda 16 çağrı yaparken 42 fonksiyon çağrısı yaptığını buldum . Her birinin gerçek verilerle nasıl performans gösterdiğini görmek için zamanlamayı deneyeceğim.


1
Hey, şuna baktığın için teşekkürler. Biliyorum np.roll(); Sorumdaki bağlantılarda tekniği kullandım. Uygulamanıza gelince, fonksiyonunuzun negatif vardiya değerleri için çalışmasını sağlama şansınız var mı?
chrisaycock

İlginçtir, np.concatenate()çok daha hızlıdır np.r_[]. Birincisi ne de olsa np.roll()kullanır.
chrisaycock

6

Sen dönüştürebilirsiniz ndarrayiçin Seriesveya DataFramebirlikte pandasilk, o zaman kullanabilirsiniz shiftyöntemini istediğiniz kadar.

Misal:

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.])

Harika, birçok insan numpy ile birlikte panda kullanıyor ve bu çok yardımcı oluyor!
VanDavv

6

Karşılaştırmalar ve Numba'yı Tanıtma

1. Özet

  • Kabul edilen yanıt ( scipy.ndimage.interpolation.shift), bu sayfada listelenen en yavaş çözümdür.
  • Numba (@ numba.njit), dizi boyutu ~ 25.000'den küçük olduğunda bir miktar performans artışı sağlar
  • Dizi boyutu büyük olduğunda (> 250.000) "herhangi bir yöntem" eşit derecede iyidir.
  • En hızlı seçenek gerçekten
        (1) dizilerinizin uzunluğuna
        (2) Yapmanız gereken vardiya miktarına bağlıdır.
  • Aşağıda, sabit shift = 10 kullanılarak bu sayfada listelenen tüm farklı yöntemlerin zamanlamalarının resmi bulunmaktadır. en iyi yöntem.

Bağıl zamanlamalar, sabit kayma (10), tüm yöntemler

2. En iyi seçeneklerle ayrıntılı karşılaştırmalar

  • shift4_numbaHer yönüyle iyi bir şey istiyorsanız (aşağıda tanımlanmıştır) seçin

Göreceli zamanlamalar, en iyi yöntemler (Kıyaslamalar)

3. Kod

3.1 shift4_numba

  • Her yönüyle iyi; maksimum% 20 wrt. herhangi bir dizi boyutunda en iyi yönteme
  • Orta dizi boyutlarıyla en iyi yöntem: ~ 500 <N <20.000.
  • Uyarı: Numba jit (tam zamanında derleyici), yalnızca dekore edilmiş işlevi birden çok kez çağırırsanız performans artışı sağlar. İlk çağrı genellikle sonraki çağrılardan 3-4 kat daha uzun sürer.
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

  • Küçük (N <= 300 .. 1500) dizi boyutlarıyla en iyi seçenek. Eşik, gerekli vardiya miktarına bağlıdır.
  • Her dizi boyutunda iyi performans; en hızlı çözüme kıyasla maksimum +% 50.
  • Uyarı: Numba jit (tam zamanında derleyici), yalnızca dekore edilmiş işlevi birden çok kez çağırırsanız performans artışı sağlar. İlk çağrı genellikle sonraki çağrılardan 3-4 kat daha uzun sürer.
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

  • Dizi boyutları ~ 20.000 <N <250.000 olan en iyi yöntem
  • Aynı shift5_numba, @ numba.njit dekoratörünü kaldırmanız yeterlidir.

4 Ek

4.1 Kullanılan yöntemlerle ilgili ayrıntılar

  • shift_scipy: scipy.ndimage.interpolation.shift(scipy 1.4.1) - Açıkça en yavaş alternatif olan kabul edilen yanıttan gelen seçenek .
  • shift1: np.rollve IronManMark20 & gzcout[:num] xnp.nan tarafından
  • shift2: np.rollve IronManMark20np.put tarafından
  • shift3: np.padve gzcslice tarafından
  • shift4: np.concatenateve chrisaycocknp.full tarafından
  • shift5: chrisaycockresult[slice] = x tarafından iki kez kullanma
  • shift#_numba: @ numba .njit öncekinin dekore edilmiş versiyonları.

shift2Ve shift3içerdiği fonksiyonlar akım Numba (0.50.1) tarafından desteklenen olmadığını.

4.2 Diğer test sonuçları

4.2.1 Göreceli zamanlamalar, tüm yöntemler

4.2.2 Ham zamanlamalar, tüm yöntemler

4.2.3 Ham zamanlamalar, birkaç en iyi yöntem


4

Bunu Pandalar ile de yapabilirsiniz:

2356 uzunluğunda bir dizi kullanarak:

import numpy as np

xs = np.array([...])

Scipy kullanarak:

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)

Pandaların Kullanımı:

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)

Bu örnekte, Pandalar kullanmak Scipy'den yaklaşık 8 kat daha hızlıydı


2
En hızlı yöntem, sorumun sonunda yayınladığım ön tahsis. Kişisel Seriesyaklaşımım 4 yaş altı götürdüler oysa tekniği, bilgisayarımda 146 götürdüler.
chrisaycock

0

Uyuşuktan tek satırlık bir şey istiyorsanız ve performans konusunda çok endişelenmiyorsanız, deneyin:

np.sum(np.diag(the_array,1),0)[:-1]

Açıklama: np.diag(the_array,1)Diziniz bir defalık köşegen ile bir matris oluşturur np.sum(...,0), matrisi sütun olarak toplar ve ...[:-1]orijinal dizinin boyutuna karşılık gelen öğeleri alır. Parametrelerle 1ve :-1olarak oynamak size farklı yönlerde geçişler sağlayabilir.


-2

Kodu kasalara dökmeden yapmanın bir yolu

dizi ile:

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

matris ile şu şekilde yapılabilir:

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

Bu ne temiz ne de hızlı.
chrisaycock
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.