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




arrays numpy (6)

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

[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 सबसे तेज़ विकल्प दिखा रहा है:

निष्कर्ष

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

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


उपाय

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

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

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

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]

आप समूह के सामान्य तत्वों और फ़िल्टर सूची का उपयोग कर सकते हैं जो कि N से लंबी हैं।

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)

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

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

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

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

दृष्टिकोण # 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: flatnonzero से सूचकांकों के बिना एक view-based विधि के साथ -

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]




binning