python - remover - Devolve eficientemente o índice da condição satisfatória do primeiro valor no array



remover linhas duplicadas pandas (1)

numba

Com numba é possível otimizar ambos os cenários. Sintaticamente, você precisa apenas construir uma função com um loop for simples:

from numba import njit

@njit
def get_first_index_nb(A, k):
    for i in range(len(A)):
        if A[i] > k:
            return i
    return -1

idx = get_first_index_nb(A, 0.9)

O Numba melhora o desempenho por JIT ("Just In Time") compilando código e aproveitando as otimizações no nível da CPU . Um loop for regular sem o @njit decorator normalmente seria mais lento do que os métodos que você já tentou para o caso em que a condição é atendida tardiamente.

Para uma série numérica de Pandas df['data'] , você pode simplesmente alimentar a representação NumPy à função JIT-compilada:

idx = get_first_index_nb(df['data'].values, 0.9)

Generalização

Uma vez que numba permite funções como argumentos , e assumindo que a função passada também pode ser compilada por JIT, você pode chegar a um método para calcular o enésimo índice onde uma condição é satisfeita para uma func arbitrária.

@njit
def get_nth_index_count(A, func, count):
    c = 0
    for i in range(len(A)):
        if func(A[i]):
            c += 1
            if c == count:
                return i
    return -1

@njit
def func(val):
    return val > 0.9

# get index of 3rd value where func evaluates to True
idx = get_nth_index_count(arr, func, 3)

Para o 3º último valor, você pode alimentar o reverso, arr[::-1] , e negar o resultado de len(arr) - 1 , o - 1 necessário para considerar a indexação 0.

Benchmarking de desempenho

# Python 3.6.5, NumPy 1.14.3, Numba 0.38.0

np.random.seed(0)
arr = np.random.rand(10**7)
m = 0.9
n = 0.999999

@njit
def get_first_index_nb(A, k):
    for i in range(len(A)):
        if A[i] > k:
            return i
    return -1

def get_first_index_np(A, k):
    for i in range(len(A)):
        if A[i] > k:
            return i
    return -1

%timeit get_first_index_nb(arr, m)                                 # 375 ns
%timeit get_first_index_np(arr, m)                                 # 2.71 µs
%timeit next(iter(np.where(arr > m)[0]), -1)                       # 43.5 ms
%timeit next((idx for idx, val in enumerate(arr) if val > m), -1)  # 2.5 µs

%timeit get_first_index_nb(arr, n)                                 # 204 µs
%timeit get_first_index_np(arr, n)                                 # 44.8 ms
%timeit next(iter(np.where(arr > n)[0]), -1)                       # 21.4 ms
%timeit next((idx for idx, val in enumerate(arr) if val > n), -1)  # 39.2 ms

Eu preciso encontrar o índice do primeiro valor em um array NumPy 1d, ou séries numéricas Pandas, satisfazendo uma condição. A matriz é grande e o índice pode estar próximo do início ou fim da matriz ou a condição pode não ser atendida. Eu não posso dizer com antecedência o que é mais provável. Se a condição não for atendida, o valor de retorno deve ser -1 . Eu considerei algumas abordagens.

Tentativa 1

# func(arr) returns a Boolean array
idx = next(iter(np.where(func(arr))[0]), -1)

Mas isso geralmente é muito lento, pois func(arr) aplica uma função vetorizada em toda a matriz, em vez de parar quando a condição é atendida. Especificamente, é caro quando a condição é satisfeita perto do início da matriz.

Tentativa 2

np.argmax é um pouco mais rápido, mas não consegue identificar quando uma condição nunca é satisfeita:

np.random.seed(0)
arr = np.random.rand(10**7)

assert next(iter(np.where(arr > 0.999999)[0]), -1) == np.argmax(arr > 0.999999)

%timeit next(iter(np.where(arr > 0.999999)[0]), -1)  # 21.2 ms
%timeit np.argmax(arr > 0.999999)                    # 17.7 ms

np.argmax(arr > 1.0) retorna 0 , isto é, uma instância quando a condição não é satisfeita.

Tentativa 3

# func(arr) returns a Boolean scalar
idx = next((idx for idx, val in enumerate(arr) if func(arr)), -1)

Mas isso é muito lento quando a condição é satisfeita perto do final da matriz. Presumivelmente, isso ocorre porque a expressão do gerador tem uma sobrecarga cara de um grande número de chamadas __next__ .

Isso é sempre um compromisso ou existe uma maneira, para func genéricas, de extrair o primeiro índice de forma eficiente?

avaliação comparativa

Para benchmarking, suponha que func encontre o índice quando um valor for maior que uma dada constante:

# Python 3.6.5, NumPy 1.14.3, Numba 0.38.0
import numpy as np

np.random.seed(0)
arr = np.random.rand(10**7)
m = 0.9
n = 0.999999

# Start of array benchmark
%timeit next(iter(np.where(arr > m)[0]), -1)                       # 43.5 ms
%timeit next((idx for idx, val in enumerate(arr) if val > m), -1)  # 2.5 µs

# End of array benchmark
%timeit next(iter(np.where(arr > n)[0]), -1)                       # 21.4 ms
%timeit next((idx for idx, val in enumerate(arr) if val > n), -1)  # 39.2 ms




numpy