Dịch chuyển các phần tử trong một mảng phức tạp


83

Tiếp theo câu hỏi này nhiều năm trước, có một chức năng "thay đổi" kinh điển trong numpy không? Tôi không thấy bất cứ điều gì từ tài liệu .

Đây là một phiên bản đơn giản của những gì tôi đang tìm kiếm:

def shift(xs, n):
    if n >= 0:
        return np.r_[np.full(n, np.nan), xs[:-n]]
    else:
        return np.r_[xs[-n:], np.full(-n, np.nan)]

Sử dụng cái này giống như:

In [76]: xs
Out[76]: array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.])

In [77]: shift(xs, 3)
Out[77]: array([ nan,  nan,  nan,   0.,   1.,   2.,   3.,   4.,   5.,   6.])

In [78]: shift(xs, -3)
Out[78]: array([  3.,   4.,   5.,   6.,   7.,   8.,   9.,  nan,  nan,  nan])

Câu hỏi này đến từ nỗ lực của tôi để viết một sản phẩm Roll_product nhanh ngày hôm qua. Tôi cần một cách để "thay đổi" một sản phẩm tích lũy và tất cả những gì tôi có thể nghĩ là tái tạo logic trong đó np.roll().


Vì vậy, np.concatenate()là nhanh hơn nhiều so với np.r_[]. Phiên bản này của hàm hoạt động tốt hơn rất nhiều:

def shift(xs, n):
    if n >= 0:
        return np.concatenate((np.full(n, np.nan), xs[:-n]))
    else:
        return np.concatenate((xs[-n:], np.full(-n, np.nan)))

Một phiên bản thậm chí còn nhanh hơn chỉ cần phân bổ trước mảng:

def shift(xs, n):
    e = np.empty_like(xs)
    if n >= 0:
        e[:n] = np.nan
        e[n:] = xs[:-n]
    else:
        e[n:] = np.nan
        e[:n] = xs[-n:]
    return e

tự hỏi nếu np.r_[np.full(n, np.nan), xs[:-n]]có thể được thay thế bằng np.r_[[np.nan]*n, xs[:-n]]tương tự cho điều kiện khác, mà không cầnnp.full
Không

2
@JohnGalt [np.nan]*nlà python đơn giản và do đó sẽ chậm hơn np.full(n, np.nan). Không phải cho nhỏ n, nhưng nó sẽ được chuyển đổi thành mảng numpy bởi np.r_, điều này sẽ làm mất đi lợi thế.
swenzel

@swenzel Chỉ cần hẹn giờ và [np.nan]*nnhanh hơn np.full(n, np.nan)cho n=[10,1000,10000]. Cần phải kiểm tra nếu np.r_có một hit.
Không

Nếu tốc độ là vấn đề đáng quan tâm, thì kích thước mảng đóng một vai trò rất lớn đối với thuật toán tốt nhất (đã thêm một so sánh điểm chuẩn bên dưới). Ngoài ra, ngày nay numba.njit có thể được sử dụng để chuyển đổi nhanh hơn nếu được gọi nhiều lần.
np8

Câu trả lời:


99

Không phức tạp nhưng scipy cung cấp chính xác chức năng thay đổi mà bạn muốn,

import numpy as np
from scipy.ndimage.interpolation import shift

xs = np.array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.])

shift(xs, 3, cval=np.NaN)

trong đó mặc định là mang vào một giá trị không đổi từ bên ngoài mảng với giá trị cval, đặt ở đây thành nan. Điều này cho kết quả mong muốn,

array([ nan, nan, nan, 0., 1., 2., 3., 4., 5., 6.])

và sự thay đổi tiêu cực hoạt động tương tự,

shift(xs, -3, cval=np.NaN)

Cung cấp đầu ra

array([  3.,   4.,   5.,   6.,   7.,   8.,   9.,  nan,  nan,  nan])

23
Chức năng thay đổi scipy thực sự chậm. Tôi đã tự cuộn bằng cách sử dụng np.concatenate và nó nhanh hơn nhiều.
gaefan

12
numpy.roll nhanh hơn. gấu trúc cũng sử dụng nó. github.com/pandas-dev/pandas/blob/v0.19.2/pandas/core/…
fx-kirin

Vừa thử nghiệm scipy.ndimage.interpolation.shift (scipy 1.4.1) so với tất cả các lựa chọn thay thế khác được liệt kê trên trang này (xem câu trả lời của tôi bên dưới) và đây là giải pháp chậm nhất có thể. Chỉ sử dụng nếu tốc độ không quan trọng trong ứng dụng của bạn.
np8

70

Đối với những người muốn chỉ sao chép và dán việc thực hiện ca nhanh nhất, có một điểm chuẩn và kết luận (xem phần cuối). Ngoài ra, tôi giới thiệu tham số fill_value và sửa một số lỗi.

Điểm chuẩn

import numpy as np
import timeit

# enhanced from IronManMark20 version
def shift1(arr, num, fill_value=np.nan):
    arr = np.roll(arr,num)
    if num < 0:
        arr[num:] = fill_value
    elif num > 0:
        arr[:num] = fill_value
    return arr

# use np.roll and np.put by IronManMark20
def shift2(arr,num):
    arr=np.roll(arr,num)
    if num<0:
         np.put(arr,range(len(arr)+num,len(arr)),np.nan)
    elif num > 0:
         np.put(arr,range(num),np.nan)
    return arr

# use np.pad and slice by me.
def shift3(arr, num, fill_value=np.nan):
    l = len(arr)
    if num < 0:
        arr = np.pad(arr, (0, abs(num)), mode='constant', constant_values=(fill_value,))[:-num]
    elif num > 0:
        arr = np.pad(arr, (num, 0), mode='constant', constant_values=(fill_value,))[:-num]

    return arr

# use np.concatenate and np.full by chrisaycock
def shift4(arr, num, fill_value=np.nan):
    if num >= 0:
        return np.concatenate((np.full(num, fill_value), arr[:-num]))
    else:
        return np.concatenate((arr[-num:], np.full(-num, fill_value)))

# preallocate empty array and assign slice by chrisaycock
def shift5(arr, num, fill_value=np.nan):
    result = np.empty_like(arr)
    if num > 0:
        result[:num] = fill_value
        result[num:] = arr[:-num]
    elif num < 0:
        result[num:] = fill_value
        result[:num] = arr[-num:]
    else:
        result[:] = arr
    return result

arr = np.arange(2000).astype(float)

def benchmark_shift1():
    shift1(arr, 3)

def benchmark_shift2():
    shift2(arr, 3)

def benchmark_shift3():
    shift3(arr, 3)

def benchmark_shift4():
    shift4(arr, 3)

def benchmark_shift5():
    shift5(arr, 3)

benchmark_set = ['benchmark_shift1', 'benchmark_shift2', 'benchmark_shift3', 'benchmark_shift4', 'benchmark_shift5']

for x in benchmark_set:
    number = 10000
    t = timeit.timeit('%s()' % x, 'from __main__ import %s' % x, number=number)
    print '%s time: %f' % (x, t)

kết quả điểm chuẩn:

benchmark_shift1 time: 0.265238
benchmark_shift2 time: 0.285175
benchmark_shift3 time: 0.473890
benchmark_shift4 time: 0.099049
benchmark_shift5 time: 0.052836

Phần kết luận

shift5 là người chiến thắng! Đó là giải pháp thứ ba của OP.


Cảm ơn vì những so sánh. Bất kỳ ý tưởng nào là cách nhanh nhất để làm điều đó mà không cần sử dụng một mảng mới?
FiReTiTi

2
Trong mệnh đề cuối cùng của shift5nó, tốt hơn là viết result[:] = arrthay vì result = arr, để giữ cho hành vi của hàm nhất quán.
avysk

2
Đây nên được chọn làm câu trả lời
wyx 13/03/18

Nhận xét @avysk khá quan trọng - vui lòng cập nhật phương thức shift5. Các hàm đôi khi trả về một bản sao và đôi khi trả về một tham chiếu là con đường dẫn đến địa ngục.
David

2
@ Josmoor98 Đó là bởi vì type(np.NAN) is float. Nếu bạn thay đổi mảng số nguyên bằng cách sử dụng các hàm này, bạn cần chỉ định giá trị điền vào số nguyên.
gzc

8

Không có chức năng duy nhất làm những gì bạn muốn. Định nghĩa của bạn về sự thay đổi hơi khác so với những gì hầu hết mọi người đang làm. Các cách để chuyển một mảng thường được lặp lại:

>>>xs=np.array([1,2,3,4,5])
>>>shift(xs,3)
array([3,4,5,1,2])

Tuy nhiên, bạn có thể làm những gì bạn muốn với hai chức năng.
Hãy xem xét a=np.array([ 0., 1., 2., 3., 4., 5., 6., 7., 8., 9.]):

def shift2(arr,num):
    arr=np.roll(arr,num)
    if num<0:
         np.put(arr,range(len(arr)+num,len(arr)),np.nan)
    elif num > 0:
         np.put(arr,range(num),np.nan)
    return arr
>>>shift2(a,3)
[ nan  nan  nan   0.   1.   2.   3.   4.   5.   6.]
>>>shift2(a,-3)
[  3.   4.   5.   6.   7.   8.   9.  nan  nan  nan]

Sau khi chạy cProfile trên hàm đã cho của bạn và mã ở trên mà bạn cung cấp, tôi thấy rằng mã bạn cung cấp thực hiện 42 lệnh gọi hàm trong khi shift2thực hiện 14 lệnh gọi khi arr là dương và 16 khi là âm. Tôi sẽ thử nghiệm thời gian để xem từng hoạt động như thế nào với dữ liệu thực.


1
Này, cảm ơn vì đã xem qua. Tôi biết về np.roll(); Tôi đã sử dụng kỹ thuật này trong các liên kết trong câu hỏi của mình. Đối với việc triển khai của bạn, bất kỳ cơ hội nào bạn có thể làm cho chức năng của mình hoạt động cho các giá trị dịch chuyển âm?
chrisaycock

Điều thú vị np.concatenate()là nhanh hơn rất nhiều np.r_[]. Rốt cuộc thì cái trước là cái gì np.roll()sử dụng.
chrisaycock

5

Bạn có thể chuyển đổi ndarraysang Serieshoặc DataFramevới pandastrước, sau đó bạn có thể sử dụng shiftphương thức tùy thích.

Thí dụ:

In [1]: from pandas import Series

In [2]: data = np.arange(10)

In [3]: data
Out[3]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

In [4]: data = Series(data)

In [5]: data
Out[5]: 
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9
dtype: int64

In [6]: data = data.shift(3)

In [7]: data
Out[7]: 
0    NaN
1    NaN
2    NaN
3    0.0
4    1.0
5    2.0
6    3.0
7    4.0
8    5.0
9    6.0
dtype: float64

In [8]: data = data.values

In [9]: data
Out[9]: array([ nan,  nan,  nan,   0.,   1.,   2.,   3.,   4.,   5.,   6.])

Tuyệt vời, nhiều người đang sử dụng gấu trúc cùng với numpy, và điều này rất hữu ích!
VanDavv

5

Điểm chuẩn và giới thiệu Numba

1. Tóm tắt

  • Câu trả lời được chấp nhận ( scipy.ndimage.interpolation.shift) là giải pháp chậm nhất được liệt kê trong trang này.
  • Numba (@ numba.njit) tăng hiệu suất khi kích thước mảng nhỏ hơn ~ 25.000
  • "Bất kỳ phương pháp nào" tốt như nhau khi kích thước mảng lớn (> 250.000).
  • Lựa chọn nhanh nhất thực sự phụ thuộc vào
        (1) Độ dài các mảng của bạn
        (2) Số lượng dịch chuyển bạn cần làm.
  • Dưới đây là hình ảnh về thời gian của tất cả các phương pháp khác nhau được liệt kê trên trang này (2020-07-11), sử dụng hằng số shift = 10. Như người ta có thể thấy, với kích thước mảng nhỏ, một số phương pháp sử dụng hơn + 2000% thời gian so với phương pháp tốt nhất.

Thời gian tương đối, dịch chuyển không đổi (10), tất cả các phương pháp

2. Điểm chuẩn chi tiết với các tùy chọn tốt nhất

  • Chọn shift4_numba(được định nghĩa bên dưới) nếu bạn muốn có một người giỏi toàn diện

Thời gian tương đối, phương pháp tốt nhất (Điểm chuẩn)

3. Mã

3.1 shift4_numba

  • Tốt tất cả xung quanh; tối đa 20% wrt. sang phương pháp tốt nhất với bất kỳ kích thước mảng nào
  • Phương pháp tốt nhất với kích thước mảng trung bình: ~ 500 <N <20.000.
  • Lưu ý: Numba jit (trình biên dịch đúng lúc) sẽ chỉ tăng hiệu suất nếu bạn đang gọi hàm được trang trí nhiều lần. Cuộc gọi đầu tiên thường lâu hơn 3-4 lần so với các cuộc gọi tiếp theo.
import numba

@numba.njit
def shift4_numba(arr, num, fill_value=np.nan):
    if num >= 0:
        return np.concatenate((np.full(num, fill_value), arr[:-num]))
    else:
        return np.concatenate((arr[-num:], np.full(-num, fill_value)))

3.2. shift5_numba

  • Tùy chọn tốt nhất với kích thước mảng nhỏ (N <= 300 .. 1500). Ngưỡng phụ thuộc vào lượng dịch chuyển cần thiết.
  • Hiệu suất tốt trên mọi kích thước mảng; tối đa + 50% so với giải pháp nhanh nhất.
  • Lưu ý: Numba jit (trình biên dịch đúng lúc) sẽ chỉ tăng hiệu suất nếu bạn đang gọi hàm được trang trí nhiều lần. Cuộc gọi đầu tiên thường lâu hơn 3-4 lần so với các cuộc gọi tiếp theo.
import numba

@numba.njit
def shift5_numba(arr, num, fill_value=np.nan):
    result = np.empty_like(arr)
    if num > 0:
        result[:num] = fill_value
        result[num:] = arr[:-num]
    elif num < 0:
        result[num:] = fill_value
        result[:num] = arr[-num:]
    else:
        result[:] = arr
    return result

3.3. shift5

  • Phương pháp tốt nhất với kích thước mảng ~ 20.000 <N <250.000
  • Tương tự như shift5_numba, chỉ cần xóa trình trang trí @ numba.njit.

4 Phụ lục

4.1 Chi tiết về các phương pháp đã sử dụng

  • shift_scipy: scipy.ndimage.interpolation.shift(scipy 1.4.1) - Tùy chọn từ câu trả lời được chấp nhận, rõ ràng là phương án thay thế chậm nhất .
  • shift1: np.rollout[:num] xnp.nanbởi IronManMark20 & gzc
  • shift2: np.rollnp.putbởi IronManMark20
  • shift3: np.padslicebởi gzc
  • shift4: np.concatenatenp.fullbởi chrisaycock
  • shift5: sử dụng hai lần result[slice] = xbởi chrisaycock
  • shift#_numba: @ numba .njit được trang trí các phiên bản trước đó.

Các shift2shift3chứa các hàm không được hỗ trợ bởi numba hiện tại (0.50.1).

4.2 Các kết quả thử nghiệm khác

4.2.1 Thời gian tương đối, tất cả các phương pháp

4.2.2 Thời gian thô, tất cả các phương pháp

4.2.3 Thời gian thô, một số phương pháp tốt nhất


4

Bạn cũng có thể làm điều này với Pandas:

Sử dụng mảng dài 2356:

import numpy as np

xs = np.array([...])

Sử dụng scipy:

from scipy.ndimage.interpolation import shift

%timeit shift(xs, 1, cval=np.nan)
# 956 µs ± 77.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Sử dụng gấu trúc:

import pandas as pd

%timeit pd.Series(xs).shift(1).values
# 377 µs ± 9.42 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Trong ví dụ này, sử dụng Pandas nhanh hơn Scipy khoảng 8 lần


2
Phương pháp nhanh nhất là phân bổ trước mà tôi đã đăng ở cuối câu hỏi của mình. SeriesKỹ thuật của bạn đã thực hiện 146 chúng tôi trên máy tính của tôi, trong khi phương pháp của tôi mất dưới 4 chúng tôi.
chrisaycock

0

Nếu bạn muốn một lớp lót từ numpy và không quá quan tâm đến hiệu suất, hãy thử:

np.sum(np.diag(the_array,1),0)[:-1]

Giải thích: np.diag(the_array,1)tạo một ma trận với mảng của bạn một lần theo đường chéo, tính np.sum(...,0)tổng cột ma trận và ...[:-1]lấy các phần tử sẽ tương ứng với kích thước của mảng ban đầu. Chơi xung quanh 1:-1các tham số có thể cung cấp cho bạn sự thay đổi theo các hướng khác nhau.


-2

Một cách để làm điều đó mà không làm tràn mã vào các trường hợp

với mảng:

def shift(arr, dx, default_value):
    result = np.empty_like(arr)
    get_neg_or_none = lambda s: s if s < 0 else None
    get_pos_or_none = lambda s: s if s > 0 else None
    result[get_neg_or_none(dx): get_pos_or_none(dx)] = default_value
    result[get_pos_or_none(dx): get_neg_or_none(dx)] = arr[get_pos_or_none(-dx): get_neg_or_none(-dx)]     
    return result

với ma trận, nó có thể được thực hiện như thế này:

def shift(image, dx, dy, default_value):
    res = np.full_like(image, default_value)

    get_neg_or_none = lambda s: s if s < 0 else None
    get_pos_or_none = lambda s : s if s > 0 else None

    res[get_pos_or_none(-dy): get_neg_or_none(-dy), get_pos_or_none(-dx): get_neg_or_none(-dx)] = \
        image[get_pos_or_none(dy): get_neg_or_none(dy), get_pos_or_none(dx): get_neg_or_none(dx)]
    return res

Điều này không sạch và cũng không nhanh.
chrisaycock
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.