70

I have an array. The valid values are not zero (either positive or negetive). I want to find the minimum and maximum within the array which should not take zeros into account. For example if the numbers are only negative. Zeros will be problematic.

BrechtDeMan
  • 6,589
  • 4
  • 24
  • 25
Shan
  • 18,563
  • 39
  • 97
  • 132

6 Answers6

119

How about:

import numpy as np
minval = np.min(a[np.nonzero(a)])
maxval = np.max(a[np.nonzero(a)])

where a is your array.

JoshAdel
  • 66,734
  • 27
  • 141
  • 140
38

If you can choose the "invalid" value in your array, it is better to use nan instead of 0:

>>> a = numpy.array([1.0, numpy.nan, 2.0])
>>> numpy.nanmax(a)
2.0
>>> numpy.nanmin(a)
1.0

If this is not possible, you can use an array mask:

>>> a = numpy.array([1.0, 0.0, 2.0])
>>> masked_a = numpy.ma.masked_equal(a, 0.0, copy=False)
>>> masked_a.max()
2.0
>>> masked_a.min()
1.0

Compared to Josh's answer using advanced indexing, this has the advantage of avoiding to create a copy of the array.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • what is this ma... is not it another copy? – Shan Aug 23 '11 at 17:02
  • +1 Masked arrays are another nice (and often under utilized) solution – JoshAdel Aug 23 '11 at 17:05
  • 2
    @Sven: when I do `ma.base is a` I get false, so it doesn't look like `ma` is just a view of `a` and there is a copy of the memory somewhere. Or am I testing this the wrong way? – JoshAdel Aug 23 '11 at 17:12
  • 2
    @JoshAdel: by default, [np.ma.masked_equal](http://docs.scipy.org/doc/numpy/reference/generated/numpy.ma.masked_equal.html#numpy.ma.masked_equal) makes a copy of `a`. To get a view, use `ma = np.ma.masked_equal(a, 0.0, copy=False)`. – unutbu Aug 26 '11 at 13:14
  • 1
    This worked perfectly for me - I had a case where I needed to retrieve the index of the min/max nonzero value. – Wojciech Migda Sep 01 '15 at 19:45
  • I know this is a really old post, but thank you. I wanted to add that if you are working with a column from a pandas imported csv, you must convert the column to an array before the masking will work. As a total python noob, this answer really spoke to me. – Patrick Williams Apr 29 '16 at 05:12
  • I thought masked arrays were inefficient? – endolith Sep 01 '19 at 02:42
  • 1
    Just a side comment: I feel like naming the view (`ma`) same as the module used to create it (`numpy.ma`) is unnecessarily confusing for someone trying to understand this code, when it could have been named literally anything else more descriptive. Why not `a_view`, for example?? – Brunox13 Jul 17 '20 at 16:08
  • 1
    @Brunox13 It wouldn't have occurred to me that this is confusing, since it's just three lines of code, so thanks for the feedback! I went with `masked_a`. – Sven Marnach Jul 17 '20 at 18:51
  • This is a good solution if you are working with matrices. – Kalo May 09 '21 at 17:11
7

Here's another way of masking which I think is easier to remember (although it does copy the array). For the case in point, it goes like this:

>>> import numpy
>>> a = numpy.array([1.0, 0.0, 2.0])
>>> ma = a[a != 0]
>>> ma.max()
2.0
>>> ma.min()
1.0
>>> 

It generalizes to other expressions such as a > 0, numpy.isnan(a), ... And you can combine masks with standard operators (+ means OR, * means AND, - means NOT) e.g:

# Identify elements that are outside interpolation domain or NaN
outside = (xi < x[0]) + (eta < y[0]) + (xi > x[-1]) + (eta > y[-1])
outside += numpy.isnan(xi) + numpy.isnan(eta)
inside = -outside
xi = xi[inside]
eta = eta[inside]
uniomni
  • 171
  • 1
  • 2
5

Masked arrays in general are designed exactly for these kind of purposes. You can leverage masking zeros from an array (or ANY other kind of mask you desire, even masks that are more complicated than a simple equality) and do pretty much most of the stuff you do on regular arrays on your masked array. You can also specify an axis for which you wish to find the min along:

import numpy.ma as ma
mx = ma.masked_array(x, mask=x==0)
mx.min()

Example input:

x = np.array([1.0, 0.0, 2.0])

output:

1.0
Ehsan
  • 12,072
  • 2
  • 20
  • 33
4

You could use a generator expression to filter out the zeros:

array = [-2, 0, -4, 0, -3, -2]
max(x for x in array if x != 0)
Chris Pickett
  • 2,822
  • 1
  • 14
  • 7
  • 1
    I think the OP is talking about numpy arrays and not python lists. There is a difference, although your solution is correct for the later. Not going to downvote, but just so you know. – JoshAdel Aug 23 '11 at 16:39
  • Ahh, just saw array, didn't see the numpy tag. – Chris Pickett Aug 23 '11 at 16:54
  • 1
    This still works on numpy arrays, so it's a valid answer. For small arrays like this, it's significantly faster than the numpy version, too. – endolith Sep 01 '19 at 03:48
2

A simple way would be to use a list comprehension to exclude zeros.

>>> tup = (0, 1, 2, 5, 2)
>>> min([x for x in tup if x !=0])
1
Wilduck
  • 13,822
  • 10
  • 58
  • 90
  • 1
    The title of the question does say "in a numpy array (or a tuple)". – Wilduck Aug 23 '11 at 16:41
  • didn't see tuple in the title and only read the question which says array. I stand corrected +1. I jumped on you (and the other response), because people post solutions to numpy questions treating them as if they are numpy lists, which they aren't. It's a personal pet peeve, since the numpy solution is often wildly more efficient. – JoshAdel Aug 23 '11 at 16:44
  • slight correction of what I wrote above: '....treating them as if they are **python** lists...' – JoshAdel Aug 23 '11 at 16:56