numpy 1D array: องค์ประกอบหน้ากากที่ทำซ้ำได้มากกว่า n ครั้ง


18

กำหนดอาร์เรย์ของจำนวนเต็มเช่น

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

ฉันต้องการปกปิดองค์ประกอบที่ทำซ้ำหลายNครั้ง ในการชี้แจง:เป้าหมายหลักคือการดึงข้อมูลมาสก์อาร์เรย์บูลีนเพื่อใช้ในการคำนวณ binning ในภายหลัง

ฉันคิดวิธีแก้ปัญหาที่ค่อนข้างซับซ้อน

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

ขอบคุณมากสำหรับคำตอบ! นี่คือบางส่วนของพล็อตมาตรฐานของ MSeifert simple_benchmarkขอขอบคุณที่ชี้ให้ฉันไป แสดงเพียง 4 ตัวเลือกที่เร็วที่สุด: ป้อนคำอธิบายรูปภาพที่นี่

ข้อสรุป

ความคิดที่เสนอโดยFlorian H , แก้ไขโดยพอลยานเกราะน่าจะเป็นวิธีที่ดีในการแก้ปัญหานี้มันสวยตรงไปตรงมาและnumpyเท่านั้น หากคุณพอใจกับการใช้numbaงานโซลูชันของ MSeifert มีประสิทธิภาพเหนือกว่าอีกด้านหนึ่ง

ฉันเลือกที่จะยอมรับคำตอบของ MSeifert เพราะมันเป็นคำตอบทั่วไปมากขึ้น: มันจัดการกับอาร์เรย์ที่ไม่ซ้ำกันขององค์ประกอบการทำซ้ำที่ต่อเนื่องกันได้อย่างถูกต้อง ในกรณีnumbaที่ไม่ต้องเดินทางคำตอบของ Divakarก็คุ้มค่าที่จะดู!


1
รับประกันได้หรือไม่ว่าอินพุตจะถูกเรียงลำดับ?
user2357112 รองรับ Monica

1
ในกรณีเฉพาะของฉันใช่ โดยทั่วไปฉันจะบอกว่ามันเป็นการดีที่จะพิจารณากรณีของอินพุตที่ไม่เรียงลำดับ (และบล็อกที่ไม่ซ้ำกันขององค์ประกอบที่ซ้ำกัน)
MrFuppes

คำตอบ:


4

ฉันต้องการนำเสนอวิธีแก้ปัญหาโดยใช้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- แต่ฉันไม่ได้รวมวิธีการทั้งหมด มันเป็นมาตราส่วนล็อก - ล็อก:

ป้อนคำอธิบายรูปภาพที่นี่

ดูเหมือนว่าโซลูชัน numba ไม่สามารถเอาชนะโซลูชันจาก Paul Panzer ซึ่งดูเหมือนจะเร็วกว่าสำหรับอาร์เรย์ขนาดใหญ่เพียงเล็กน้อย (และไม่ต้องการการพึ่งพาเพิ่มเติม)

อย่างไรก็ตามทั้งคู่ดูเหมือนจะมีประสิทธิภาพสูงกว่าโซลูชันอื่น ๆ แต่พวกเขากลับมาสก์แทนที่จะเป็นอาร์เรย์ "กรอง"

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

"ดูเหมือนว่าโซลูชันชาจะไม่สามารถเอาชนะทางออกจาก Paul Panzer ได้"มันเร็วกว่าสำหรับขนาดที่เหมาะสม และมันก็มีพลังมากกว่า ฉันไม่สามารถทำให้ฉัน (ดี @ ของ FlorianH) ทำงานสำหรับค่าบล็อก nonunique โดยไม่ทำให้ช้าลงมาก ที่น่าสนใจแม้กระทั่งการเลียนแบบวิธีการของ Florians ด้วย pythran (ซึ่งโดยทั่วไปแล้วจะคล้ายกับ numba) ฉันไม่สามารถจับคู่ numpy สำหรับอาร์เรย์ขนาดใหญ่ได้ ไพ ธ รันไม่ชอบoutอาร์กิวเมนต์ (หรืออาจเป็นรูปแบบการทำงานของโอเปอเรเตอร์) ดังนั้นฉันจึงไม่สามารถบันทึกสำเนานั้นได้ Btw simple_benchmarkผมค่อนข้างชอบ
Paul Panzer

คำแนะนำที่ดีมีให้ใช้simple_benchmark! ขอบคุณสำหรับสิ่งนั้นและขอบคุณแน่นอนสำหรับคำตอบ เนื่องจากฉันใช้numbaเพื่อสิ่งอื่นเช่นกันฉันก็มีแนวโน้มที่จะใช้ที่นี่และทำให้เป็นวิธีแก้ปัญหา ระหว่างหินและเป็นสถานที่ที่ยากมี ...
MrFuppes

7

คำเตือน: นี่เป็นเพียงการนำความคิดของ @ FlorianH มาใช้

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 จากตัวอย่างของ OP ฉันคิดว่าสิ่งนี้ไม่สามารถเกิดขึ้นได้ แต่คุณถูกต้องในรหัสจริงของ OP ที่สามารถจัดการกับตัวอย่างของคุณได้ โอพีเท่านั้นที่สามารถบอกได้ฉันคิดว่า
Paul Panzer

ตามที่ฉันตอบความคิดเห็นของผู้ใช้ในกรณีเฉพาะของฉันการป้อนข้อมูลจะถูกจัดเรียงและบล็อกขององค์ประกอบการทำซ้ำติดต่อกันจะไม่ซ้ำกัน อย่างไรก็ตามจากมุมมองที่กว้างขึ้นมันอาจมีประโยชน์มากถ้าใครสามารถจัดการกับอาเรย์โดยพลการ
MrFuppes

4

วิธีการ # 1:นี่คือวิธี vectorized -

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 ใด ๆ จะเป็น:

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ของ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)
Florian H

ขออภัยถ้าคำถามของฉันไม่ชัดเจน ฉันไม่ต้องการลบองค์ประกอบฉันเพียงต้องการหน้ากากที่ฉันสามารถใช้ในภายหลัง (เช่นการปิดบังตัวแปรตามเพื่อให้ได้จำนวนตัวอย่างต่อถังเท่ากัน)
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สามารถใช้ในการแยกองค์ประกอบ traget 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]
CypherX
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.