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

এটি করার কোন সুন্দর উপায় আছে?

সম্পাদনা, # 2

উত্তরের জন্য অনেক ধন্যবাদ! এখানে এমসিফার্টের বেঞ্চমার্ক প্লটের একটি পাতলা সংস্করণ। আমাকে নির্দেশ করার জন্য ধন্যবাদ simple_benchmark। কেবলমাত্র দ্রুততম 4 টি বিকল্প দেখাচ্ছে: এখানে চিত্র বর্ণনা লিখুন

উপসংহার

ফ্লোরিয়ান এইচ প্রস্তাবিত , পল পাঞ্জার দ্বারা সংশোধিত এই সমস্যাটি সমাধানের এক দুর্দান্ত উপায় বলে মনে হচ্ছে এটি পুরোপুরি সোজা এবং numpyএকা হয়ে গেছে। আপনি যদি ব্যবহার করে ঠিক থাকেন numbaতবে এমসিফের্টের সমাধান ছাড়িয়ে যায়।

আমি এমসিফার্টের উত্তরটিকে আরও সাধারণ উত্তর হিসাবে সমাধান হিসাবে গ্রহণ করতে বেছে নিয়েছি: এটি ক্রমাগত পুনরাবৃত্তিকারী উপাদানগুলির (অ-অনন্য) ব্লকের সাথে স্বেচ্ছাসেবী অ্যারেগুলি সঠিকভাবে পরিচালনা করে। যদি না যায় numbaতবে দিবাকরের উত্তরটিও দেখার মতো!


1
এটি কি গ্যারান্টিযুক্ত যে ইনপুটটি বাছাই করা হবে?
ব্যবহারকারী 2357112 22:43

1
আমার নির্দিষ্ট ক্ষেত্রে, হ্যাঁ সাধারণভাবে আমি বলব, একটি অরসেটেড ইনপুট (এবং পুনরাবৃত্ত উপাদানগুলির অ-অনন্য ব্লক) এর ক্ষেত্রে বিবেচনা করা ভাল।
মিঃফুপেস

উত্তর:


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

"মনে হচ্ছে নাম্বার দ্রবণটি পল পানজারের কাছ থেকে সমাধানটি হারাতে পারে না" যুক্তিযুক্তভাবে এটি আকারের শালীন পরিসরের জন্য দ্রুত is এবং এটি আরও শক্তিশালী। আমি আমার (ভাল, @ ফ্লোরিয়ানএইচ) এর কাজটি খুব ধীর না করেই ননুনিক ব্লক মানগুলির জন্য করতে পারি না। মজার বিষয় হল, এমনকি পাইথ্রান (যা সাধারণত নাম্বার সাথে একইভাবে সঞ্চালিত হয়) দিয়ে ফ্লোরিয়ানদের পদ্ধতির অনুলিপি করা আমি বড় অ্যারেগুলির জন্য নির্লজ্জ প্রয়োগের সাথে মেলে না। পাইথ্রান outআর্গুমেন্টটি পছন্দ করে না (বা সম্ভবত অপারেটরের কার্যকরী ফর্ম), সুতরাং আমি সেই অনুলিপিটি সংরক্ষণ করতে পারিনি। BTW আমি বেশ পছন্দ simple_benchmark
পল

দুর্দান্ত ইঙ্গিত, ব্যবহার করার জন্য simple_benchmark! তার জন্য ধন্যবাদ এবং উত্তরের জন্য অবশ্যই ধন্যবাদ। যেহেতু আমি numbaঅন্যান্য জিনিসগুলির জন্যও ব্যবহার করছি, তাই আমি প্রবণতাও এখানে ব্যবহার করি এবং এটি সমাধানটি তৈরি করি। সেখানে একটি শিলা এবং একটি শক্ত জায়গার মধ্যে ...
মিঃফুপ্পস

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]
এমসিফার্ট

@ সাইফার্ট ওপির উদাহরণ থেকে আমি ধরে নিয়েছিলাম যে এই জাতীয় জিনিসটি ঘটতে পারে না তবে আপনি ঠিক বলেছেন যে ওপির আসল কোডটি আপনার উদাহরণটি পরিচালনা করতে পারে। ঠিক আছে, কেবল ওপি বলতে পারে, আমি মনে করি।
পল প্যানজার

আমি যেমন ব্যবহারকারী 2357112 এর মন্তব্যে জবাব দিয়েছি, আমার নির্দিষ্ট ক্ষেত্রে, ইনপুটটি সাজানো হয়েছে এবং পরপর পুনরাবৃত্তি করার উপাদানগুলির ব্লকগুলি অনন্য। তবে, আরও সাধারণ দৃষ্টিকোণ থেকে, যদি কেউ স্বেচ্ছাচারী অ্যারেগুলি পরিচালনা করতে পারে তবে এটি খুব কার্যকর হতে পারে।
মিঃফুপেস

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রান পরীক্ষা করা উচিত ।
মিঃফুপেস

1

এর খুব সুন্দর উপায় হ'ল numpy'এর unique()ফাংশনটি ব্যবহার করা । আপনি আপনার অ্যারেতে অনন্য এন্ট্রি পাবেন এবং কত ঘন ঘন তারা উপস্থিত হয় তা গণনা:

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

আপনি কিছুক্ষণ লুপ ব্যবহার করতে পারেন যা অ্যারে এলিমেন্ট N অবস্থানগুলি বর্তমানের সমান কিনা তা পরীক্ষা করে। এই দ্রষ্টব্যটি নোটটি অ্যারের অর্ডার করা হয়েছে বলে ধরে নিন।

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)
ফ্লোরিয়ান এইচ

দুঃখিত যদি আমার প্রশ্নটি এখানে অস্পষ্ট থাকে; আমি উপাদানগুলি সরাতে চাইছি না, আমার কেবল একটি মুখোশ দরকার যা পরে আমি ব্যবহার করতে পারি (যেমন প্রতি বিনের সমান সংখ্যক নমুনা পাওয়ার জন্য নির্ভরশীল ভেরিয়েবলটি মাস্কিং)।
মিঃফুপ্পেস

0

আপনি গ্রেবিবি ব্যবহার করতে পারেন সাধারণ উপাদান এবং ফিল্টার তালিকার জন্য যা এন এর চেয়ে দীর্ঘ ।

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?
মিঃফুপেস

সত্য: আপনি যদি প্রথমে মুখোশটি পেতে আগ্রহী হন। আপনি যদি চান final_valuesসরাসরি, আপনি পারে uncomment সমাধান একমাত্র মন্তব্য লাইন এবং যে ক্ষেত্রে আপনি তিন লাইন পরিত্যাগ পারে: mask = ..., final_mask = ...এবং bins[final_mask]
সাইফারেক্স
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.