मौजूदा मूल्य से अधिक मूल्य की पहली घटना


144

मेरे पास संख्यात्मक में 1D सरणी है और मैं सूचकांक की स्थिति को ढूंढना चाहता हूं जहां मूल्य एक मान से अधिक होता है।

उदाहरण के लिए

aa = range(-10,10)

उस स्थिति में खोजें aaजहाँ, मान 5पार हो जाता है।


2
एक स्पष्ट होना चाहिए कि क्या कोई समाधान नहीं हो सकता है (चूंकि उदाहरण के लिए argmax उत्तर उस स्थिति में काम नहीं करेगा (अधिकतम (0,0,0,0) = 0) जैसा कि एम्ब्रस ने टिप्पणी की है
seanv507

जवाबों:


199

यह थोड़ा तेज है (और अच्छा लग रहा है)

np.argmax(aa>5)

चूंकि argmaxपहली बार बंद हो जाएगा True("अधिकतम मूल्यों की कई घटनाओं के मामले में, पहली घटना के अनुरूप सूचकांक वापस आ जाते हैं।") और दूसरी सूची को नहीं बचाता है।

In [2]: N = 10000

In [3]: aa = np.arange(-N,N)

In [4]: timeit np.argmax(aa>N/2)
100000 loops, best of 3: 52.3 us per loop

In [5]: timeit np.where(aa>N/2)[0][0]
10000 loops, best of 3: 141 us per loop

In [6]: timeit np.nonzero(aa>N/2)[0][0]
10000 loops, best of 3: 142 us per loop

103
बस सावधानी का एक शब्द: यदि इसके इनपुट ऐरे में कोई ट्रू वैल्यू नहीं है, तो np.argmax खुशी से 0 लौटा देगा (जो कि इस मामले में आपको नहीं चाहिए)।
ambrus

8
परिणाम सही हैं, लेकिन मुझे स्पष्टीकरण थोड़ा संदिग्ध लगा। argmaxपहली बार में बंद नहीं लगता है True। ( Trueविभिन्न पदों पर एकल के साथ बूलियन सरणियों का निर्माण करके इसका परीक्षण किया जा सकता है ।) गति को संभवतः इस तथ्य से समझाया गया है कि argmaxआउटपुट सूची बनाने की आवश्यकता नहीं है।
DrV

1
मुझे लगता है कि तुम सही हो, @DrV। मेरा स्पष्टीकरण इस बारे में होना था कि यह मूल इरादे के बावजूद सही परिणाम क्यों देता है, वास्तव में अधिकतम की मांग नहीं करता है, न कि यह तेजी से क्योंकि मैं आंतरिक विवरण को समझने का दावा नहीं कर सकता argmax
पूछते हैं

1
@ जॉर्ज, मुझे डर है कि मैं नहीं जानता कि वास्तव में क्यों। मैं केवल यह कह सकता हूं कि यह मेरे द्वारा दिखाए गए विशेष उदाहरण में तेज है, इसलिए मैं इसे आम तौर पर बिना तेजी से विचार नहीं करूंगा (i) यह जानकर कि यह क्यों है (देखें @ DrV की टिप्पणी) या (ii) अधिक मामलों का परीक्षण (उदाहरण के लिए, क्या aaसॉर्ट किया गया है) @ माइकल के जवाब में)।
Askewchan

3
@ डीआरवी, मैं केवल argmax10 मिलियन-एलिमेंट बूलियन एरेज़ पर एक ही Trueनंबर पर अलग-अलग स्थानों पर न्यूमपी 1.11.2 का उपयोग करके चला गया, और मामले की स्थिति True। तो 1.11.2 argmaxबूलियन सरणियों पर "शॉर्ट-सर्किट" लगता है।
उलरिच स्टर्न

96

आपके सरणी की क्रमबद्ध सामग्री को देखते हुए, एक और भी तेज़ तरीका है: खोजा हुआ

import time
N = 10000
aa = np.arange(-N,N)
%timeit np.searchsorted(aa, N/2)+1
%timeit np.argmax(aa>N/2)
%timeit np.where(aa>N/2)[0][0]
%timeit np.nonzero(aa>N/2)[0][0]

# Output
100000 loops, best of 3: 5.97 µs per loop
10000 loops, best of 3: 46.3 µs per loop
10000 loops, best of 3: 154 µs per loop
10000 loops, best of 3: 154 µs per loop

19
यह वास्तव में सबसे अच्छा उत्तर है मान लें कि सरणी को सॉर्ट किया गया है (जो वास्तव में प्रश्न में निर्दिष्ट नहीं है)। आप +1np.searchsorted(..., side='right')
14

3
मुझे लगता है कि sideतर्क केवल तभी फर्क करता है जब सॉर्ट किए गए सरणी में दोहराया मान हो। यह लौटे इंडेक्स के अर्थ को नहीं बदलता है, जो हमेशा वह इंडेक्स होता है जिस पर आप क्वेरी मान सम्मिलित कर सकते हैं, सभी प्रविष्टियों को दाईं ओर शिफ्ट कर सकते हैं, और एक सॉर्ट किए गए सरणी को बनाए रख सकते हैं।
गस

@Gus, sideका प्रभाव तब होता है जब समान मान और सम्मिलित सरणी दोनों में होता है, चाहे किसी भी मान को दोहराया जाए। सॉर्ट किए गए सरणी में बार-बार मान केवल प्रभाव को अतिरंजित करते हैं (पक्षों के बीच का अंतर सॉर्ट किए गए सरणी में डाला जा रहा मूल्य की संख्या की संख्या है)। side करता है , वापस आ सूचकांक का अर्थ बदल हालांकि यह उन सूचकांक पर क्रमबद्ध सरणी में मानों डालने से उत्पन्न सरणी नहीं बदलता है। एक सूक्ष्म लेकिन महत्वपूर्ण अंतर; वास्तव में यह उत्तर गलत इंडेक्स देता है यदि N/2वह अंदर नहीं है aa
Askewchan

जैसा कि उपरोक्त टिप्पणी में संकेत दिया गया है, यह उत्तर एक में से एक है यदि N/2नहीं है aa। सही रूप np.searchsorted(aa, N/2, side='right')(बिना +1) होगा। दोनों रूप अन्यथा एक ही सूचकांक देते हैं। Nविषम होने के परीक्षण के मामले पर विचार करें (और N/2.0यदि पायथन 2 का उपयोग करने के लिए फ्लोट को मजबूर करने के लिए)।
Askewchan

21

मुझे भी इसमें दिलचस्पी थी और मैंने परफ्लोट के साथ सभी सुझाए गए उत्तरों की तुलना की है । (अस्वीकरण: मैं perfplot के लेखक हूँ।)

यदि आप जानते हैं कि जिस सरणी को आप देख रहे हैं वह पहले से ही हल है , तो

numpy.searchsorted(a, alpha)

आप के लिए है। यह एक निरंतर-समय ऑपरेशन है, अर्थात, गति सरणी के आकार पर निर्भर नहीं करती है। आप उससे तीव्र नहीं हो सकते।

यदि आपको अपनी सरणी के बारे में कुछ नहीं पता है, तो आप गलत नहीं होंगे

numpy.argmax(a > alpha)

पहले से ही क्रमबद्ध:

यहां छवि विवरण दर्ज करें

अवर्गीकृत:

यहां छवि विवरण दर्ज करें

प्लॉट को फिर से तैयार करने के लिए कोड:

import numpy
import perfplot


alpha = 0.5

def argmax(data):
    return numpy.argmax(data > alpha)

def where(data):
    return numpy.where(data > alpha)[0][0]

def nonzero(data):
    return numpy.nonzero(data > alpha)[0][0]

def searchsorted(data):
    return numpy.searchsorted(data, alpha)

out = perfplot.show(
    # setup=numpy.random.rand,
    setup=lambda n: numpy.sort(numpy.random.rand(n)),
    kernels=[
        argmax, where,
        nonzero,
        searchsorted
        ],
    n_range=[2**k for k in range(2, 20)],
    logx=True,
    logy=True,
    xlabel='len(array)'
    )

4
np.searchsortedनिरंतर समय नहीं है। यह वास्तव में है O(log(n))। लेकिन आपका परीक्षण मामला वास्तव में searchsorted(जो है O(1)) के सर्वश्रेष्ठ मामले को मापता है ।
MSeifert

@MSeert आपको O (लॉग (n)) को देखने के लिए किस प्रकार के इनपुट ऐरे / अल्फ़ा की आवश्यकता है?
निको श्लोमर

1
सूचकांक sqrt (लंबाई) में आइटम प्राप्त करने से बहुत खराब प्रदर्शन हुआ। मैंने उस बेंचमार्क सहित यहां एक उत्तर भी लिखा था ।
MSeifert

मुझे संदेह है searchsorted(या कोई एल्गोरिथ्म) O(log(n))एक बाइनरी खोज को समान रूप से वितरित डेटा के लिए हरा सकता है । संपादित करें: searchsorted है एक द्विआधारी खोज।
मतीन उलहक 5

16
In [34]: a=np.arange(-10,10)

In [35]: a
Out[35]:
array([-10,  -9,  -8,  -7,  -6,  -5,  -4,  -3,  -2,  -1,   0,   1,   2,
         3,   4,   5,   6,   7,   8,   9])

In [36]: np.where(a>5)
Out[36]: (array([16, 17, 18, 19]),)

In [37]: np.where(a>5)[0][0]
Out[37]: 16

8

तत्वों के बीच एक स्थिर कदम है कि गिरफ्तार करता है

rangeया किसी अन्य रेखीय रूप से बढ़ती हुई सरणी के मामले में आप बस सूचकांक को प्रोग्रामेटिक रूप से गणना कर सकते हैं, वास्तव में सरणी पर इस पर पुनरावृति करने की कोई आवश्यकता नहीं है:

def first_index_calculate_range_like(val, arr):
    if len(arr) == 0:
        raise ValueError('no value greater than {}'.format(val))
    elif len(arr) == 1:
        if arr[0] > val:
            return 0
        else:
            raise ValueError('no value greater than {}'.format(val))

    first_value = arr[0]
    step = arr[1] - first_value
    # For linearly decreasing arrays or constant arrays we only need to check
    # the first element, because if that does not satisfy the condition
    # no other element will.
    if step <= 0:
        if first_value > val:
            return 0
        else:
            raise ValueError('no value greater than {}'.format(val))

    calculated_position = (val - first_value) / step

    if calculated_position < 0:
        return 0
    elif calculated_position > len(arr) - 1:
        raise ValueError('no value greater than {}'.format(val))

    return int(calculated_position) + 1

शायद वह थोड़ा सुधार कर सके। मुझे यकीन है कि यह कुछ नमूना सरणियों और मूल्यों के लिए सही ढंग से काम करता है, लेकिन इसका मतलब यह नहीं है कि वहाँ गलतियाँ नहीं हो सकती हैं, खासकर यह देखते हुए कि यह तैरता नहीं है ...

>>> import numpy as np
>>> first_index_calculate_range_like(5, np.arange(-10, 10))
16
>>> np.arange(-10, 10)[16]  # double check
6

>>> first_index_calculate_range_like(4.8, np.arange(-10, 10))
15

यह देखते हुए कि यह किसी भी पुनरावृत्ति के बिना स्थिति की गणना कर सकता है यह निरंतर समय ( O(1)) होगा और संभवतः अन्य सभी उल्लिखित दृष्टिकोणों को हरा सकता है। हालाँकि इसके लिए सरणी में एक स्थिर कदम की आवश्यकता होती है, अन्यथा यह गलत परिणाम देगा।

सुंबा का उपयोग कर सामान्य समाधान

एक अधिक सामान्य दृष्टिकोण एक सुन्न समारोह का उपयोग किया जाएगा:

@nb.njit
def first_index_numba(val, arr):
    for idx in range(len(arr)):
        if arr[idx] > val:
            return idx
    return -1

यह किसी भी एरे के लिए काम करेगा लेकिन इसे एरे पर चलना होगा, इसलिए औसत स्थिति में यह होगा O(n):

>>> first_index_numba(4.8, np.arange(-10, 10))
15
>>> first_index_numba(5, np.arange(-10, 10))
16

बेंचमार्क

भले ही निको श्लोमर ने पहले से ही कुछ बेंचमार्क प्रदान किए हों, मुझे लगा कि यह मेरे नए समाधानों को शामिल करने और विभिन्न "मूल्यों" के लिए परीक्षण करने के लिए उपयोगी हो सकता है।

परीक्षण सेटअप:

import numpy as np
import math
import numba as nb

def first_index_using_argmax(val, arr):
    return np.argmax(arr > val)

def first_index_using_where(val, arr):
    return np.where(arr > val)[0][0]

def first_index_using_nonzero(val, arr):
    return np.nonzero(arr > val)[0][0]

def first_index_using_searchsorted(val, arr):
    return np.searchsorted(arr, val) + 1

def first_index_using_min(val, arr):
    return np.min(np.where(arr > val))

def first_index_calculate_range_like(val, arr):
    if len(arr) == 0:
        raise ValueError('empty array')
    elif len(arr) == 1:
        if arr[0] > val:
            return 0
        else:
            raise ValueError('no value greater than {}'.format(val))

    first_value = arr[0]
    step = arr[1] - first_value
    if step <= 0:
        if first_value > val:
            return 0
        else:
            raise ValueError('no value greater than {}'.format(val))

    calculated_position = (val - first_value) / step

    if calculated_position < 0:
        return 0
    elif calculated_position > len(arr) - 1:
        raise ValueError('no value greater than {}'.format(val))

    return int(calculated_position) + 1

@nb.njit
def first_index_numba(val, arr):
    for idx in range(len(arr)):
        if arr[idx] > val:
            return idx
    return -1

funcs = [
    first_index_using_argmax, 
    first_index_using_min, 
    first_index_using_nonzero,
    first_index_calculate_range_like, 
    first_index_numba, 
    first_index_using_searchsorted, 
    first_index_using_where
]

from simple_benchmark import benchmark, MultiArgument

और भूखंडों का उपयोग कर उत्पन्न किया गया था:

%matplotlib notebook
b.plot()

आइटम शुरुआत में है

b = benchmark(
    funcs,
    {2**i: MultiArgument([0, np.arange(2**i)]) for i in range(2, 20)},
    argument_name="array size")

यहां छवि विवरण दर्ज करें

सुन्न समारोह और समारोह खोज समारोह के बाद सबसे अच्छा प्रदर्शन करता है। अन्य समाधान बहुत खराब प्रदर्शन करते हैं।

आइटम अंत में है

b = benchmark(
    funcs,
    {2**i: MultiArgument([2**i-2, np.arange(2**i)]) for i in range(2, 20)},
    argument_name="array size")

यहां छवि विवरण दर्ज करें

छोटे सरणियों के लिए सुंबा फ़ंक्शन आश्चर्यजनक तेजी से प्रदर्शन करता है, हालांकि बड़ी सरणियों के लिए यह गणना-फ़ंक्शन और खोज किए गए फ़ंक्शन द्वारा बेहतर प्रदर्शन करता है।

आइटम sqrt (लेन) पर है

b = benchmark(
    funcs,
    {2**i: MultiArgument([np.sqrt(2**i), np.arange(2**i)]) for i in range(2, 20)},
    argument_name="array size")

यहां छवि विवरण दर्ज करें

यह अधिक दिलचस्प है। फिर से सुब्बा और गणना फ़ंक्शन बहुत अच्छा प्रदर्शन करते हैं, हालांकि यह वास्तव में खोज के सबसे खराब मामले को ट्रिगर कर रहा है जो वास्तव में इस मामले में अच्छी तरह से काम नहीं करता है।

जब कोई मूल्य शर्त को पूरा नहीं करता है तो कार्यों की तुलना

एक और दिलचस्प बात यह है कि ये फ़ंक्शन व्यवहार करता है यदि कोई मूल्य नहीं है जिसका सूचकांक वापस आ जाना चाहिए:

arr = np.ones(100)
value = 2

for func in funcs:
    print(func.__name__)
    try:
        print('-->', func(value, arr))
    except Exception as e:
        print('-->', e)

इस परिणाम के साथ:

first_index_using_argmax
--> 0
first_index_using_min
--> zero-size array to reduction operation minimum which has no identity
first_index_using_nonzero
--> index 0 is out of bounds for axis 0 with size 0
first_index_calculate_range_like
--> no value greater than 2
first_index_numba
--> -1
first_index_using_searchsorted
--> 101
first_index_using_where
--> index 0 is out of bounds for axis 0 with size 0

खोजे गए, argmax, और सुंबा बस एक गलत मान लौटाते हैं। हालाँकि searchsortedऔर numbaएक इंडेक्स लौटाते हैं जो कि ऐरे के लिए मान्य इंडेक्स नहीं है।

कार्यों where, min, nonzeroऔर calculateएक अपवाद फेंक देते हैं। हालाँकि calculateवास्तव में मददगार के लिए केवल अपवाद ही कुछ कहता है।

इसका मतलब है कि एक व्यक्ति को वास्तव में इन कॉल को एक उचित आवरण फ़ंक्शन में लपेटना पड़ता है जो अपवाद या अमान्य रिटर्न मान को पकड़ता है और उचित रूप से संभालता है, कम से कम यदि आप सुनिश्चित नहीं हैं कि मान सरणी में हो सकता है।


नोट: गणना और searchsortedविकल्प केवल विशेष परिस्थितियों में काम करते हैं। "गणना" फ़ंक्शन को एक स्थिर कदम की आवश्यकता होती है और खोजे जाने वाले सरणी को क्रमबद्ध करने की आवश्यकता होती है। तो ये सही परिस्थितियों में उपयोगी हो सकते हैं लेकिन इस समस्या के लिए सामान्य समाधान नहीं हैं । मामले आप साथ काम कर रहे में हल कर अजगर सूचियों आप पर एक नज़र लेने के लिए चाहते हो सकता है द्विविभाजित बजाय Numpys searchsorted का उपयोग करने का मॉड्यूल।


3

मैं प्रपोज करना चाहूंगा

np.min(np.append(np.where(aa>5)[0],np.inf))

यह सबसे छोटा सूचकांक लौटाएगा जहां स्थिति पूरी की जाती है, जबकि अनंत की वापसी अगर स्थिति कभी पूरी नहीं होती है (और whereखाली सरणी देता है)।


1

मैं साथ जाता

i = np.min(np.where(V >= x))

जहां Vवेक्टर (1d सरणी) है, xवह मूल्य है और iपरिणामी सूचकांक है।

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