सुन्न 1D सरणी: मास्क तत्व जो n समय से अधिक दोहराते हैं


18

जैसे पूर्णांक की एक सरणी दी

[1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5]

मुझे ऐसे तत्वों को मुखौटा बनाने की आवश्यकता है जो Nसमय से अधिक दोहराते हैं । स्पष्ट करने के लिए: प्राथमिक लक्ष्य बूलियन मुखौटा सरणी को पुनः प्राप्त करना है, बाद में गणना के लिए इसका उपयोग करना।

मैं एक जटिल समाधान के साथ आया था

import numpy as np

bins = np.array([1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5])

N = 3
splits = np.split(bins, np.where(np.diff(bins) != 0)[0]+1)
mask = []
for s in splits:
    if s.shape[0] <= N:
        mask.append(np.ones(s.shape[0]).astype(np.bool_))
    else:
        mask.append(np.append(np.ones(N), np.zeros(s.shape[0]-N)).astype(np.bool_)) 

mask = np.concatenate(mask)

उदाहरण के लिए

bins[mask]
Out[90]: array([1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5])

क्या ऐसा करने का एक अच्छा तरीका है?

EDIT, # 2

उत्तर के लिए बहुत बहुत धन्यवाद! यहाँ MSeifert के बेंचमार्क प्लॉट का एक पतला संस्करण है। मुझे इशारा करने के लिए धन्यवाद simple_benchmark। केवल 4 सबसे तेज़ विकल्प दिखा रहा है: यहां छवि विवरण दर्ज करें

निष्कर्ष

पॉल पैंजर द्वारा संशोधित फ्लोरिअन एच द्वारा प्रस्तावित विचार इस समस्या को हल करने का एक शानदार तरीका प्रतीत होता है क्योंकि यह बहुत सीधे आगे और अकेला है। यदि आप हालांकि उपयोग करने के साथ ठीक हैं , तो MSeifert का समाधान दूसरे को बेहतर बनाता हैnumpynumba

मैंने MSeifert के उत्तर को समाधान के रूप में स्वीकार करना चुना क्योंकि यह अधिक सामान्य उत्तर है: यह लगातार दोहराए जाने वाले तत्वों के साथ (गैर-अद्वितीय) ब्लॉकों के साथ मनमाने ढंग से सरणियों को संभालता है। मामले numbaमें कोई जवाब नहीं है , दिवाकर का जवाब भी देखने लायक है!


1
क्या यह गारंटी है कि इनपुट सॉर्ट किया जाएगा?
user2357112

1
मेरे विशिष्ट मामले में, हाँ। सामान्य तौर पर मैं कहूंगा कि, एक अन इनपुट (और बार-बार तत्वों के गैर-अनूठे ब्लॉक) के मामले पर विचार करना अच्छा होगा।
MrFuppes

जवाबों:


4

मैं सुंबा का उपयोग करके एक समाधान प्रस्तुत करना चाहता हूं जिसे समझना काफी आसान होना चाहिए। मैं मानता हूं कि आप लगातार दोहराई जाने वाली वस्तुओं को "मुखौटा" करना चाहते हैं:

import numpy as np
import numba as nb

@nb.njit
def mask_more_n(arr, n):
    mask = np.ones(arr.shape, np.bool_)

    current = arr[0]
    count = 0
    for idx, item in enumerate(arr):
        if item == current:
            count += 1
        else:
            current = item
            count = 1
        mask[idx] = count <= n
    return mask

उदाहरण के लिए:

>>> bins = np.array([1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5])
>>> bins[mask_more_n(bins, 3)]
array([1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5])
>>> bins[mask_more_n(bins, 2)]
array([1, 1, 2, 2, 3, 3, 4, 4, 5, 5])

प्रदर्शन:

उपयोग करना simple_benchmark- हालाँकि मैंने सभी दृष्टिकोणों को शामिल नहीं किया है। यह एक लॉग-लॉग स्केल है:

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

ऐसा लगता है कि सुंबा समाधान पॉल पैंजर से समाधान को हरा नहीं सकता है जो कि एक बिट द्वारा बड़ी सरणियों के लिए तेज लगता है (और इसके लिए अतिरिक्त निर्भरता की आवश्यकता नहीं है)।

हालाँकि, दोनों अन्य समाधानों से बेहतर प्रदर्शन करते हैं, लेकिन वे "फ़िल्टर्ड" सरणी के बजाय एक मुखौटा लौटाते हैं।

import numpy as np
import numba as nb
from simple_benchmark import BenchmarkBuilder, MultiArgument

b = BenchmarkBuilder()

bins = np.array([1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5])

@nb.njit
def mask_more_n(arr, n):
    mask = np.ones(arr.shape, np.bool_)

    current = arr[0]
    count = 0
    for idx, item in enumerate(arr):
        if item == current:
            count += 1
        else:
            current = item
            count = 1
        mask[idx] = count <= n
    return mask

@b.add_function(warmups=True)
def MSeifert(arr, n):
    return mask_more_n(arr, n)

from scipy.ndimage.morphology import binary_dilation

@b.add_function()
def Divakar_1(a, N):
    k = np.ones(N,dtype=bool)
    m = np.r_[True,a[:-1]!=a[1:]]
    return a[binary_dilation(m,k,origin=-(N//2))]

@b.add_function()
def Divakar_2(a, N):
    k = np.ones(N,dtype=bool)
    return a[binary_dilation(np.ediff1d(a,to_begin=a[0])!=0,k,origin=-(N//2))]

@b.add_function()
def Divakar_3(a, N):
    m = np.r_[True,a[:-1]!=a[1:],True]
    idx = np.flatnonzero(m)
    c = np.diff(idx)
    return np.repeat(a[idx[:-1]],np.minimum(c,N))

from skimage.util import view_as_windows

@b.add_function()
def Divakar_4(a, N):
    m = np.r_[True,a[:-1]!=a[1:]]
    w = view_as_windows(m,N)
    idx = np.flatnonzero(m)
    v = idx<len(w)
    w[idx[v]] = 1
    if v.all()==0:
        m[idx[v.argmin()]:] = 1
    return a[m]

@b.add_function()
def Divakar_5(a, N):
    m = np.r_[True,a[:-1]!=a[1:]]
    w = view_as_windows(m,N)
    last_idx = len(a)-m[::-1].argmax()-1
    w[m[:-N+1]] = 1
    m[last_idx:last_idx+N] = 1
    return a[m]

@b.add_function()
def PaulPanzer(a,N):
    mask = np.empty(a.size,bool)
    mask[:N] = True
    np.not_equal(a[N:],a[:-N],out=mask[N:])
    return mask

import random

@b.add_arguments('array size')
def argument_provider():
    for exp in range(2, 20):
        size = 2**exp
        yield size, MultiArgument([np.array([random.randint(0, 5) for _ in range(size)]), 3])

r = b.run()
import matplotlib.pyplot as plt

plt.figure(figsize=[10, 8])
r.plot()

"ऐसा लगता है कि सुंबा समाधान पॉल पैनज़र से समाधान को हरा नहीं सकता है" यकीनन यह आकार की एक सभ्य श्रेणी के लिए तेज़ है। और यह अधिक शक्तिशाली है। मैं मेरा (अच्छा, @ फ्लोरिअन का) गैर-ब्लॉक मूल्यों के लिए काम नहीं कर सका, क्योंकि यह बहुत धीमा है। दिलचस्प रूप से, यहां तक ​​कि पाइथ्रान (जो आमतौर पर सुंबा के समान प्रदर्शन करता है) के साथ फ्लोरिअन विधि की नकल करना मैं बड़े सरणियों के लिए संख्यात्मक कार्यान्वयन से मेल नहीं खा सकता था। अजगर को outतर्क पसंद नहीं है (या शायद ऑपरेटर का कार्यात्मक रूप), इसलिए मैं उस कॉपी को नहीं बचा सका। Btw मुझे काफी पसंद है simple_benchmark
पॉल पैंजर

महान संकेत, वहाँ का उपयोग करने के लिए simple_benchmark! उस के लिए धन्यवाद और उत्तर के लिए पाठ्यक्रम का धन्यवाद। चूंकि मैं numbaअन्य चीजों के लिए भी उपयोग कर रहा हूं, इसलिए मैं भी इसका उपयोग करता हूं और इसका समाधान करता हूं। एक चट्टान और एक कठिन जगह के बीच ...
MrFuppes

7

डिस्क्लेमर: यह @ फ्लोरियनह के विचार का सिर्फ एक ध्वनि कार्यान्वयन है:

def f(a,N):
    mask = np.empty(a.size,bool)
    mask[:N] = True
    np.not_equal(a[N:],a[:-N],out=mask[N:])
    return mask

बड़े सरणियों के लिए यह एक बड़ा अंतर बनाता है:

a = np.arange(1000).repeat(np.random.randint(0,10,1000))
N = 3

print(timeit(lambda:f(a,N),number=1000)*1000,"us")
# 5.443050000394578 us

# compare to
print(timeit(lambda:[True for _ in range(N)] + list(bins[:-N] != bins[N:]),number=1000)*1000,"us")
# 76.18969900067896 us

मुझे नहीं लगता कि यह मनमाने ढंग से सरणियों के लिए सही ढंग से काम करता है: उदाहरण के लिए [1,1,1,1,2,2,1,1,2,2]
MSeifert

@MSeifert ओपी के उदाहरण से मैंने माना कि इस तरह की बात नहीं हो सकती है, लेकिन आप सही हैं कि ओपी का वास्तविक कोड इस उदाहरण को संभाल सकता है। ठीक है, केवल ओपी बता सकता है, मुझे लगता है।
पॉल पैंजर

जैसा कि मैंने user2357112 की टिप्पणी का उत्तर दिया है, मेरे विशिष्ट मामले में, इनपुट को क्रमबद्ध किया गया है और लगातार दोहराए जाने वाले तत्वों के ब्लॉक अद्वितीय हैं। हालांकि, अधिक सामान्य दृष्टिकोण से, यह बहुत उपयोगी हो सकता है यदि कोई व्यक्ति मनमानी सरणियों को संभाल सकता है।
MrFuppes

4

दृष्टिकोण # 1: यहां एक सदिश तरीका है -

from scipy.ndimage.morphology import binary_dilation

def keep_N_per_group(a, N):
    k = np.ones(N,dtype=bool)
    m = np.r_[True,a[:-1]!=a[1:]]
    return a[binary_dilation(m,k,origin=-(N//2))]

सैंपल रन -

In [42]: a
Out[42]: array([1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5])

In [43]: keep_N_per_group(a, N=3)
Out[43]: array([1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5])

दृष्टिकोण # 2: थोड़ा और अधिक कॉम्पैक्ट संस्करण -

def keep_N_per_group_v2(a, N):
    k = np.ones(N,dtype=bool)
    return a[binary_dilation(np.ediff1d(a,to_begin=a[0])!=0,k,origin=-(N//2))]

दृष्टिकोण # 3: समूहीकृत-गिनती का उपयोग करना और np.repeat(हालांकि हमें मास्क नहीं देना होगा) -

def keep_N_per_group_v3(a, N):
    m = np.r_[True,a[:-1]!=a[1:],True]
    idx = np.flatnonzero(m)
    c = np.diff(idx)
    return np.repeat(a[idx[:-1]],np.minimum(c,N))

दृष्टिकोण # 4: एक view-basedविधि के साथ -

from skimage.util import view_as_windows

def keep_N_per_group_v4(a, N):
    m = np.r_[True,a[:-1]!=a[1:]]
    w = view_as_windows(m,N)
    idx = np.flatnonzero(m)
    v = idx<len(w)
    w[idx[v]] = 1
    if v.all()==0:
        m[idx[v.argmin()]:] = 1
    return a[m]

दृष्टिकोण # 5:view-based सूचकांक के बिना एक विधि के साथ flatnonzero-

def keep_N_per_group_v5(a, N):
    m = np.r_[True,a[:-1]!=a[1:]]
    w = view_as_windows(m,N)
    last_idx = len(a)-m[::-1].argmax()-1
    w[m[:-N+1]] = 1
    m[last_idx:last_idx+N] = 1
    return a[m]

2

आप इसे अनुक्रमण के साथ कर सकते हैं। किसी भी एन के लिए कोड होगा:

N = 3
bins = np.array([1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5,6])

mask = [True for _ in range(N)] + list(bins[:-N] != bins[N:])
bins[mask]

उत्पादन:

array([1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6]

वास्तव में यह सादगी पसंद है! के रूप में अच्छी तरह से सुंदर होना चाहिए, कुछ timeitरन के साथ जाँच करेगा ।
MrFuppes

1

एक बहुत अच्छा तरीका है numpy's unique()-function ' का उपयोग करना । आपको अपने सरणी में अद्वितीय प्रविष्टियां मिलेंगी और यह भी कि वे कितनी बार दिखाई देंगी:

bins = np.array([1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5])
N = 3

unique, index,count = np.unique(bins, return_index=True, return_counts=True)
mask = np.full(bins.shape, True, dtype=bool)
for i,c in zip(index,count):
    if c>N:
        mask[i+N:i+c] = False

bins[mask]

उत्पादन:

array([1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5])

1

आप एक समय लूप का उपयोग कर सकते हैं जो यह जांचता है कि सरणी तत्व एन स्थिति वापस वर्तमान के बराबर है या नहीं। ध्यान दें कि यह समाधान मानता है कि सरणी का आदेश दिया गया है।

import numpy as np

bins = [1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5]
N = 3
counter = N

while counter < len(bins):
    drop_condition = (bins[counter] == bins[counter - N])
    if drop_condition:
        bins = np.delete(bins, counter)
    else:
        # move on to next element
        counter += 1

आप को बदलने के लिए चाहते हो सकता है len(question)के लिएlen(bins)
फ्लोरियन एच

क्षमा करें यदि मेरा प्रश्न वहाँ अस्पष्ट है; मैं तत्वों को हटाने के लिए नहीं देख रहा हूं, मुझे बस एक मुखौटा की आवश्यकता है जिसे मैं बाद में उपयोग कर सकता हूं (उदाहरण के लिए प्रति बिन समान नमूने प्राप्त करने के लिए एक आश्रित चर को मास्क करना)।
MrFuppes

0

आप इस्तेमाल कर सकते हैं grouby समूह आम तत्वों और फिल्टर सूची उससे अधिक समय हो रहे हैं एन

import numpy as np
from itertools import groupby, chain

def ifElse(condition, exec1, exec2):

    if condition : return exec1 
    else         : return exec2


def solve(bins, N = None):

    xss = groupby(bins)
    xss = map(lambda xs : list(xs[1]), xss)
    xss = map(lambda xs : ifElse(len(xs) > N, xs[:N], xs), xss)
    xs  = chain.from_iterable(xss)
    return list(xs)

bins = np.array([1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5])
solve(bins, N = 3)

0

समाधान

आप उपयोग कर सकते हैं numpy.unique। चर final_maskसे एलीगेंट तत्वों को निकालने के लिए वेरिएबल का उपयोग किया जा सकता है bins

import numpy as np

bins = np.array([1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5])
repeat_max = 3

unique, counts = np.unique(bins, return_counts=True)
mod_counts = np.array([x if x<=repeat_max else repeat_max for x in counts])
mask = np.arange(bins.size)
#final_values = np.hstack([bins[bins==value][:count] for value, count in zip(unique, mod_counts)])
final_mask = np.hstack([mask[bins==value][:count] for value, count in zip(unique, mod_counts)])
bins[final_mask]

आउटपुट :

array([1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5])

के रूप में एक ही आकार का एक मुखौटा पाने के लिए एक अतिरिक्त कदम की आवश्यकता होगी bins, है ना?
MrFuppes

सच: केवल अगर आप पहले मुखौटा प्राप्त करने में रुचि रखते हैं। यदि आप चाहते हैं final_valuesसीधे, तुम सकता है uncomment समाधान में केवल टिप्पणी की लाइन और उस मामले में आप तीन लाइनों त्यागने सकता है: mask = ..., final_mask = ...और bins[final_mask]
साइफ्रेक्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.