तत्वों को एक संख्यात्मक सरणी में बदलें


84

बाद-अप से इस सवाल का साल पहले, वहाँ numpy में एक प्रामाणिक "बदलाव" समारोह है? मैं प्रलेखन से कुछ भी नहीं देख रहा हूँ ।

यहाँ मैं क्या देख रहा हूँ का एक सरल संस्करण है:

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

इसका उपयोग करना इस प्रकार है:

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

यह प्रश्न कल एक तेज़ रोलिंग_प्रोडक्ट लिखने के मेरे प्रयास से आया था । मुझे एक संचयी उत्पाद को "शिफ्ट" करने के लिए एक तरीका चाहिए था और मुझे लगता है कि तर्क को दोहराने के लिए सभी सोच सकते थे np.roll()


की np.concatenate()तुलना में बहुत तेज है np.r_[]। फ़ंक्शन का यह संस्करण बहुत बेहतर प्रदर्शन करता है:

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

एक और भी तेज संस्करण केवल पूर्व-आवंटित करता है:

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
शून्य

2
@ जॉन्गल्ट [np.nan]*nसादा अजगर है और इसलिए की तुलना में धीमा होगा np.full(n, np.nan)। छोटे के लिए नहीं n, लेकिन यह np.r_ द्वारा सुन्न सरणी में तब्दील हो जाएगा जो फायदा उठाता है।
स्वेंजेल

@swenzel बस इसे समय पर और के [np.nan]*nलिए तेजी से np.full(n, np.nan)है n=[10,1000,10000]। अगर np.r_कोई हिट लेता है, तो जांच करने की आवश्यकता है ।
शून्य

यदि गति चिंता का विषय है, तो सरणी आकार सबसे अच्छा एल्गोरिथ्म के लिए एक बड़ी भूमिका निभाता है (नीचे एक बेंचमार्क तुलना जोड़ा गया)। इसके अलावा, आजकल numba.njit का उपयोग बार-बार कॉल करने पर शिफ्ट को तेज़ बनाने के लिए किया जा सकता है।
np8

जवाबों:


101

सुन्न नहीं है, लेकिन डरपोक आप चाहते हैं बिल्कुल पारी कार्यक्षमता प्रदान करता है,

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)

जहाँ डिफ़ॉल्ट को मान के साथ सरणी के बाहर से लगातार मान में लाना है cval, यहाँ पर सेट करें nan। यह वांछित आउटपुट देता है,

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

और नकारात्मक बदलाव इसी तरह काम करता है,

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

आउटपुट प्रदान करता है

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

23
Scipy शिफ्ट फ़ंक्शन वास्तव में धीमा है। मैंने np.concatenate का उपयोग करके अपना रोल किया और यह बहुत तेज था।
गफान

12
numpy.roll तेज है। पांडा भी इसका इस्तेमाल करते हैं। github.com/pandas-dev/pandas/blob/v0.19.2/pandas/core/…
fx-kirin

बस इस पृष्ठ पर सूचीबद्ध अन्य विकल्पों के खिलाफ scipy.ndimage.interpolation.shift (scipy 1.4.1) का परीक्षण किया (नीचे मेरा उत्तर देखें), और यह सबसे धीमा संभव समाधान है। यदि आपके आवेदन में गति का कोई महत्व नहीं है, तो ही उपयोग करें।
np8

72

उन लोगों के लिए जो शिफ्ट के सबसे तेज़ कार्यान्वयन को बस कॉपी और पेस्ट करना चाहते हैं, एक बेंचमार्क और निष्कर्ष है (अंत देखें)। इसके अलावा, मैं fill_value पैरामीटर पेश करता हूं और कुछ बग्स को ठीक करता हूं।

बेंचमार्क

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)

बेंचमार्क परिणाम:

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

निष्कर्ष

shift5 विजेता है! यह ओपी का तीसरा उपाय है।


तुलना के लिए धन्यवाद। किसी भी विचार एक नए सरणी का उपयोग किए बिना इसे करने का सबसे तेज़ तरीका क्या है?
FiReTiTi

2
फ़ंक्शन व्यवहार को सुसंगत रखने के लिए इसके बजाय अंतिम खंड में shift5लिखना बेहतर है । result[:] = arrresult = arr
avysk

2
इसे उत्तर के रूप में चुना जाना चाहिए
wyx

@avysk टिप्पणी बहुत महत्वपूर्ण है - कृपया शिफ्ट 5 विधि को अपडेट करें। ऐसे कार्य जो कभी-कभी एक प्रति लौटाते हैं और कभी-कभी एक संदर्भ लौटाते हैं, नरक का रास्ता है।
डेविड

2
@ जोसमूर 98 इसलिए type(np.NAN) is float। यदि आप इन कार्यों का उपयोग करके पूर्णांक सरणी को स्थानांतरित करते हैं, तो आपको पूर्णांक fill_value निर्दिष्ट करना होगा।
gzc

9

कोई एकल फ़ंक्शन नहीं है जो आपको चाहिए। आपकी शिफ्ट की परिभाषा ज्यादातर लोगों की तुलना में थोड़ी अलग है। किसी सरणी को शिफ्ट करने के तरीके आमतौर पर लूप किए जाते हैं:

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

हालाँकि, आप वह कर सकते हैं जो आप दो कार्यों के साथ चाहते हैं।
विचार करें 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]

आपके दिए गए फ़ंक्शन और आपके द्वारा दिए गए उपरोक्त कोड पर cProfile चलाने के बाद, मैंने पाया कि आपके द्वारा प्रदान किया गया कोड 42 फ़ंक्शन कॉल करता है जबकि shift214 कॉल किए गए हैं जब गिरफ्तारी सकारात्मक है और 16 नकारात्मक है। मैं यह देखने के लिए समय के साथ प्रयोग करूंगा कि प्रत्येक वास्तविक डेटा के साथ कैसा प्रदर्शन करता है।


1
अरे, इस पर एक नज़र लेने के लिए धन्यवाद। मुझे मालूम है np.roll(); मैंने अपने प्रश्न में लिंक में तकनीक का उपयोग किया। अपने कार्यान्वयन के लिए, नकारात्मक पारी मूल्यों के लिए काम करने का कोई भी मौका आपको मिल सकता है?
क्रिसकॉक

दिलचस्प है, np.concatenate()की तुलना में बहुत तेज है np.r_[]। पूर्व क्या np.roll()उपयोग करता है, सब के बाद।
क्रिसकॉक

6

आप परिवर्तित कर सकते हैं ndarrayकरने के लिए Seriesया DataFrameके साथ pandasपहले, तो आप उपयोग कर सकते हैंshift विधि आप चाहते हैं।

उदाहरण:

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

महान, कई लोग सुन्न के साथ पांडा का उपयोग कर रहे हैं, और यह बहुत मददगार है!
वनदेव

6

बेंचमार्क और Numba की शुरुआत

1. सारांश

  • स्वीकृत उत्तर ( scipy.ndimage.interpolation.shift) सबसे धीमा है इस पृष्ठ में सूचीबद्ध समाधान है।
  • Numba (@ numba.njit) कुछ प्रदर्शन को बढ़ावा देता है जब सरणी आकार ~ 25.000 से छोटा होता है
  • "कोई भी विधि" समान रूप से अच्छी है जब सरणी का आकार बड़ा (> 250.000)।
  • सबसे तेज़ विकल्प वास्तव में
        (1) आपके सरणियों की लंबाई
        (2) पर निर्भर करता है जो आपको करने की आवश्यकता है।
  • नीचे इस पृष्ठ पर सूचीबद्ध सभी अलग-अलग तरीकों के समय की तस्वीर है (2020-07-11), निरंतर बदलाव = 10. का उपयोग करते हुए, जैसा कि कोई भी देख सकता है, छोटे सरणी आकारों के साथ कुछ विधियों का उपयोग + 2000% समय से अधिक है सबसे अच्छी विधि।

सापेक्ष समय, निरंतर बदलाव (10), सभी विधियाँ

2. सबसे अच्छे विकल्पों के साथ विस्तृत बेंचमार्क

  • shift4_numbaयदि आप अच्छे ऑलराउंडर चाहते हैं, तो (नीचे परिभाषित) चुनें

सापेक्ष समय, सर्वोत्तम विधियाँ (बेंचमार्क)

3. कोड

3.1 shift4_numba

  • अच्छे ऑलराउंडर; अधिकतम 20% wrt। किसी भी सरणी आकार के साथ सबसे अच्छी विधि
  • मध्यम सरणी आकार के साथ सबसे अच्छी विधि: ~ 500 <एन <20.000।
  • कैविएट: नुम्बा जित (केवल समय संकलक में) प्रदर्शन को बढ़ावा देगा, यदि आप सजाए गए फ़ंक्शन को एक से अधिक बार बुला रहे हैं। पहली कॉल आमतौर पर बाद की कॉल की तुलना में 3-4 गुना अधिक समय लेती है।
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

  • छोटे (एन <= 300 .. 1500) सरणी आकार के साथ सबसे अच्छा विकल्प। ट्रेशोल्ड शिफ्ट की आवश्यक मात्रा पर निर्भर करता है।
  • किसी भी सरणी आकार पर अच्छा प्रदर्शन; सबसे तेज समाधान की तुलना में अधिकतम + 50%।
  • कैविएट: नुम्बा जित (केवल समय संकलक में) प्रदर्शन को बढ़ावा देगा, यदि आप सजाए गए फ़ंक्शन को एक से अधिक बार बुला रहे हैं। पहली कॉल आमतौर पर बाद की कॉल की तुलना में 3-4 गुना अधिक समय लेती है।
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

  • सरणी आकार के साथ सबसे अच्छा तरीका ~ 20.000 <एन <250.000
  • के रूप में ही shift5_numba, बस @ numba.njit डेकोरेटर को हटा दें।

4 परिशिष्ट

4.1 प्रयुक्त विधियों के बारे में विवरण

  • shift_scipy: scipy.ndimage.interpolation.shift(scipy 1.4.1) - स्वीकृत उत्तर से विकल्प, जो स्पष्ट रूप से सबसे धीमा विकल्प है
  • shift1: np.rollऔर IronManMark20 और gzcout[:num] xnp.nan द्वारा
  • shift2: np.rollऔर आयरनमैनमार्क 20np.put द्वारा
  • shift3: np.padऔर gzcslice द्वारा
  • shift4: np.concatenateऔर क्रिसकॉकnp.full द्वारा
  • shift5: क्रिसकॉकresult[slice] = x द्वारा दो बार उपयोग करना
  • shift#_numba: @ सुंबा .njit पिछले के सजाए गए संस्करण।

shift2और shift3निहित कार्यों वर्तमान Numba (0.50.1) द्वारा समर्थित नहीं किया गया था।

4.2 अन्य परीक्षण के परिणाम

4.2.1 सापेक्ष समय, सभी विधियाँ

4.2.2 कच्चे समय, सभी विधियाँ

4.2.3 कच्चे समय, कुछ सर्वोत्तम तरीके


4

आप पंडों के साथ भी ऐसा कर सकते हैं:

2356-लंबी सरणी का उपयोग करना:

import numpy as np

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

का उपयोग कर 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)

पंडों का उपयोग करना:

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)

इस उदाहरण में, पंडों का उपयोग स्किप की तुलना में ~ 8 गुना तेज था


2
सबसे तेज़ विधि पूर्व-आवंटन है जिसे मैंने अपने प्रश्न के अंत में पोस्ट किया है। आपकी Seriesतकनीक ने मुझे हमारे कंप्यूटर पर १४६ पर ले लिया, जबकि मेरे दृष्टिकोण ने हमें ४ के नीचे ले लिया।
चिरसायकॉक

0

यदि आप सुन्न से एक-लाइनर चाहते हैं और प्रदर्शन के बारे में बहुत चिंतित नहीं हैं, तो कोशिश करें:

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

व्याख्या: np.diag(the_array,1)अपने सरणी को विकर्ण से एक-एक मैट्रिक्स बनाता है, np.sum(...,0)मैट्रिक्स कॉलम-वार को सॉम्प करता है, और ...[:-1]उन तत्वों को लेता है जो मूल सरणी के आकार के अनुरूप होंगे। के साथ 1और :-1मापदंडों के रूप में खेलना आपको विभिन्न दिशाओं में बदलाव दे सकता है।


-2

मामलों में कोड को गिराए बिना इसे करने का एक तरीका

सरणी के साथ:

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

मैट्रिक्स के साथ इसे इस तरह किया जा सकता है:

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

यह न तो साफ है और न ही तेज है।
चिरसायकॉक
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.