mảng 1D numpy: các phần tử mặt nạ lặp lại nhiều hơn n lần


18

đưa ra một loạt các số nguyên như

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

Tôi cần phải che dấu các yếu tố lặp lại nhiều Nlần. Để làm rõ: mục tiêu chính là lấy ra mảng mặt nạ boolean, để sử dụng nó sau này để tính toán binning.

Tôi đã đưa ra một giải pháp khá phức tạp

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)

cho ví dụ

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

Có cách nào đẹp hơn để làm điều này?

EDIT, # 2

Cảm ơn rất nhiều cho câu trả lời! Đây là một phiên bản mỏng của cốt truyện chuẩn của MSeifert. Cảm ơn đã chỉ cho tôi simple_benchmark. Chỉ hiển thị 4 tùy chọn nhanh nhất: nhập mô tả hình ảnh ở đây

Phần kết luận

Ý tưởng được đề xuất bởi Florian H , được sửa đổi bởi Paul Panzer dường như là một cách tuyệt vời để giải quyết vấn đề này vì nó khá đơn giản và đơn giản numpy. numbaTuy nhiên, nếu bạn ổn với việc sử dụng , giải pháp của MSeifert vượt trội so với giải pháp khác.

Tôi đã chọn chấp nhận câu trả lời của MSeifert vì đây là câu trả lời chung chung hơn: Nó xử lý chính xác các mảng tùy ý với các khối (không duy nhất) của các phần tử lặp lại liên tiếp. Trong trường hợp numbalà không nên, câu trả lời của Divakar cũng đáng xem!


1
Có đảm bảo rằng đầu vào sẽ được sắp xếp?
user2357112 hỗ trợ Monica

1
trong trường hợp cụ thể của tôi, vâng. nói chung tôi muốn nói, sẽ tốt hơn nếu xem xét trường hợp đầu vào chưa được sắp xếp (và các khối không lặp duy nhất của các phần tử lặp lại).
MrFuppes

Câu trả lời:


4

Tôi muốn trình bày một giải pháp bằng cách sử dụng numba khá dễ hiểu. Tôi giả sử rằng bạn muốn "che dấu" các mục lặp lại liên tiếp:

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

Ví dụ:

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

Hiệu suất:

Sử dụng simple_benchmark- tuy nhiên tôi chưa bao gồm tất cả các phương pháp. Đó là thang đo log-log:

nhập mô tả hình ảnh ở đây

Có vẻ như giải pháp numba không thể đánh bại giải pháp từ Paul Panzer, dường như nhanh hơn đối với các mảng lớn một chút (và không yêu cầu phụ thuộc thêm).

Tuy nhiên cả hai dường như tốt hơn các giải pháp khác, nhưng chúng trả về một mặt nạ thay vì mảng "được lọc".

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

"Có vẻ như giải pháp numba không thể đánh bại giải pháp từ Paul Panzer" được cho là nhanh hơn đối với một loạt các kích cỡ. Và nó mạnh hơn. Tôi không thể làm cho cái của tôi (tốt, của @ FlorianH) hoạt động cho các giá trị khối nonunique mà không làm cho nó chậm hơn nhiều. Thật thú vị, ngay cả khi sao chép phương pháp Florian bằng pythran (thường thực hiện tương tự như numba), tôi không thể phù hợp với việc thực hiện numpy cho các mảng lớn. pythran không thích outđối số (hoặc có lẽ là dạng chức năng của toán tử), vì vậy tôi không thể lưu bản sao đó. Btw tôi khá thích simple_benchmark.
Paul Panzer

gợi ý tuyệt vời đó, để sử dụng simple_benchmark! cảm ơn vì điều đó và cảm ơn tất nhiên vì câu trả lời Vì tôi cũng đang sử dụng numbacho những thứ khác, nên tôi cũng có thể sử dụng nó ở đây và biến nó thành giải pháp. giữa một tảng đá và một nơi khó khăn ở đó ...
MrFuppes

7

Tuyên bố miễn trừ trách nhiệm: đây chỉ là một triển khai hợp lý hơn cho ý tưởng của @ 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

Đối với các mảng lớn hơn, điều này tạo ra một sự khác biệt rất lớn:

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

Tôi không nghĩ rằng nó hoạt động chính xác cho các mảng tùy ý: Ví dụ với [1,1,1,1,2,2,1,1,2,2].
MSeifert

@MSeifert Từ ví dụ của OP Tôi cho rằng loại điều này không thể xảy ra, nhưng bạn đúng trong mã thực tế của OP có thể xử lý ví dụ của bạn. Chà, chỉ OP mới có thể nói, tôi cho là vậy.
Paul Panzer

như tôi đã trả lời bình luận của user2357112, trong trường hợp cụ thể của tôi, đầu vào được sắp xếp và các khối của các yếu tố lặp lại liên tiếp là duy nhất. Tuy nhiên, từ góc độ tổng quát hơn, nó có thể rất hữu ích nếu người ta có thể xử lý các mảng tùy ý.
MrFuppes

4

Cách tiếp cận số 1: Đây là một cách được vector hóa -

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

Chạy mẫu -

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

Cách tiếp cận # 2: Phiên bản nhỏ gọn hơn một chút -

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

Cách tiếp cận # 3: Sử dụng số đếm được nhóm và np.repeat(sẽ không cung cấp cho chúng tôi mặt nạ) -

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

Cách tiếp cận số 4: Với view-basedphương pháp -

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]

Cách tiếp cận số 5: Với view-basedphương thức không có chỉ số từ 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

Bạn có thể làm điều này với lập chỉ mục. Đối với bất kỳ N, mã sẽ là:

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]

đầu ra:

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

thực sự thích cái đó vì nó đơn giản! cũng nên biểu diễn đẹp, sẽ kiểm tra với một số timeitlần chạy.
MrFuppes

1

Một đẹp hơn cách nhiều sẽ được sử dụng numpy's unique()-function. Bạn sẽ nhận được các mục duy nhất trong mảng của mình và cả số lần xuất hiện của chúng:

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]

đầu ra:

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

1

Bạn có thể sử dụng một vòng lặp while để kiểm tra xem phần tử N vị trí trở lại có bằng với vòng lặp hiện tại không. Lưu ý giải pháp này giả định mảng được đặt hàng.

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

Bạn có thể muốn đổi len(question)thànhlen(bins)
Florian H

xin lỗi nếu câu hỏi của tôi không rõ ràng ở đó; Tôi không tìm cách loại bỏ các phần tử, tôi chỉ cần một mặt nạ mà tôi có thể sử dụng sau này (ví dụ: che giấu một biến phụ thuộc để có được số lượng mẫu bằng nhau trên mỗi thùng).
MrFuppes

0

Bạn đã có thể sử dụng grouby đến các yếu tố chung nhóm và danh sách bộ lọc dài hơn 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)

0

Giải pháp

Bạn đã có thể sử dụng numpy.unique . Biến final_maskcó thể được sử dụng để trích xuất các phần tử traget từ mảng 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]

Đầu ra :

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

Điều đó sẽ đòi hỏi một bước bổ sung để có được một mặt nạ có hình dạng tương tự bins, phải không?
MrFuppes

Đúng: chỉ khi bạn quan tâm đến việc lấy mặt nạ trước. Nếu bạn muốn final_valuestrực tiếp, bạn có thể bỏ ghi chú dòng chỉ nhận xét trong dung dịch và trong trường hợp đó bạn có thể loại bỏ ba dòng: mask = ..., final_mask = ...bins[final_mask].
CypherX
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.