Contando o número de elementos não NaN em um ndarray entorpecido em Python

87

Preciso calcular o número de elementos não NaN em uma matriz ndarray numpy. Como alguém faria isso com eficiência em Python? Aqui está meu código simples para conseguir isso:

import numpy as np

def numberOfNonNans(data):
    count = 0
    for i in data:
        if not np.isnan(i):
            count += 1
    return count 

Existe uma função interna para isso no numpy? A eficiência é importante porque estou fazendo análises de Big Data.

Thnx por qualquer ajuda!

jjepsuomi
fonte
2
Esta pergunta parece estar fora do tópico porque pertence a codereview.stackexchange.com
jonrsharpe
1
Você quer dizer eficiente em termos de memória?
Ashwini Chaudhary
1 Eu estava pensando no tempo de CPU, mas sim, por que não memória também. Quanto mais rápido e mais barato, melhor =)
jjepsuomi
3
@jjepsuomi Uma versão com uso eficiente de memória será sum(not np.isnan(x) for x in a), mas em termos de velocidade é lenta comparada à versão numpy do @M4rtini.
Ashwini Chaudhary
@AshwiniChaudhary Muito obrigado! Preciso ver qual é o mais importante em meu aplicativo =)
jjepsuomi

Respostas:

161
np.count_nonzero(~np.isnan(data))

~inverte a matriz booleana de onde retornou np.isnan.

np.count_nonzeroconta valores que não são 0 \ false. .sumdeve dar o mesmo resultado. Mas talvez de forma mais clara para usarcount_nonzero

Velocidade de teste:

In [23]: data = np.random.random((10000,10000))

In [24]: data[[np.random.random_integers(0,10000, 100)],:][:, [np.random.random_integers(0,99, 100)]] = np.nan

In [25]: %timeit data.size - np.count_nonzero(np.isnan(data))
1 loops, best of 3: 309 ms per loop

In [26]: %timeit np.count_nonzero(~np.isnan(data))
1 loops, best of 3: 345 ms per loop

In [27]: %timeit data.size - np.isnan(data).sum()
1 loops, best of 3: 339 ms per loop

data.size - np.count_nonzero(np.isnan(data))parece quase não ser o mais rápido aqui. outros dados podem fornecer resultados de velocidade relativa diferentes.

M4rtini
fonte
+1 @ M4rtini obrigado novamente! Você é ótimo! ; DI aceitará sua resposta assim que eu puder :)
jjepsuomi
3
Talvez até numpy.isnan(array).sum()? Não sou muito proficiente com entorpecimento.
msvalkon
2
@msvalkon, contará o número de elementos NaN, enquanto OP deseja o número de elementos não NaN.
falsetru
2
@goncalopp stackoverflow.com/questions/8305199/… =)
jjepsuomi
5
Uma extensão da resposta @msvalkon: data.size - np.isnan(data).sum()será um pouco mais eficiente.
Daniel
10

Alternativa rápida de escrever

Embora não seja a escolha mais rápida, se o desempenho não for um problema, você pode usar:

sum(~np.isnan(data)).

Atuação:

In [7]: %timeit data.size - np.count_nonzero(np.isnan(data))
10 loops, best of 3: 67.5 ms per loop

In [8]: %timeit sum(~np.isnan(data))
10 loops, best of 3: 154 ms per loop

In [9]: %timeit np.sum(~np.isnan(data))
10 loops, best of 3: 140 ms per loop
GM
fonte
Esta resposta fornece a soma que não é o mesmo que contar o número de elementos ... Você deve usar em seu lenlugar.
BenT
@BenT a soma dos elementos de um array bool que atendem a uma determinada condição é a mesma, fornecendo ao comprimento de um array de subconjunto os elementos que atendem a uma determinada condição. Você pode esclarecer onde isso está errado?
GM
1
Erro meu: esqueci que um booleano tem retorno.
BenT
3

Para determinar se a matriz é esparsa, pode ajudar obter uma proporção dos valores nan

np.isnan(ndarr).sum() / ndarr.size

Se essa proporção exceder um limite, use uma matriz esparsa, por exemplo - https://sparse.pydata.org/en/latest/

Darren Weber
fonte
2

Uma alternativa, mas um pouco mais lenta, é fazer isso sobre a indexação.

np.isnan(data)[np.isnan(data) == False].size

In [30]: %timeit np.isnan(data)[np.isnan(data) == False].size
1 loops, best of 3: 498 ms per loop 

O uso duplo de np.isnan(data)e do ==operador pode ser um pouco exagerado, então publiquei a resposta apenas para completar.

Manuel
fonte