124

I have a 2D array containing integers (both positive or negative). Each row represents the values over time for a particular spatial site, whereas each column represents values for various spatial sites for a given time.

So if the array is like:

1 3 4 2 2 7
5 2 2 1 4 1
3 3 2 2 1 1

The result should be

1 3 2 2 2 1

Note that when there are multiple values for mode, any one (selected randomly) may be set as mode.

I can iterate over the columns finding mode one at a time but I was hoping numpy might have some in-built function to do that. Or if there is a trick to find that efficiently without looping.

Nik
  • 4,955
  • 13
  • 44
  • 69
  • 1
    There is http://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.mstats.mode.html and the answer here: http://stackoverflow.com/questions/6252280/find-the-most-frequent-number-in-a-numpy-vector – tom10 May 02 '13 at 05:35
  • 1
    @tom10: You mean [scipy.stats.mode()](http://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.mode.html#scipy.stats.mode), right? The other one seems to output a masked array. – fgb May 02 '13 at 05:53
  • 1
    @fgb: right, thanks for the correction (and +1 for your answer). – tom10 May 02 '13 at 19:00

10 Answers10

171

Check scipy.stats.mode() (inspired by @tom10's comment):

import numpy as np
from scipy import stats

a = np.array([[1, 3, 4, 2, 2, 7],
              [5, 2, 2, 1, 4, 1],
              [3, 3, 2, 2, 1, 1]])

m = stats.mode(a)
print(m)

Output:

ModeResult(mode=array([[1, 3, 2, 2, 1, 1]]), count=array([[1, 2, 2, 2, 1, 2]]))

As you can see, it returns both the mode as well as the counts. You can select the modes directly via m[0]:

print(m[0])

Output:

[[1 3 2 2 1 1]]
bugmenot123
  • 1,314
  • 1
  • 16
  • 29
fgb
  • 2,749
  • 1
  • 16
  • 22
  • 4
    So numpy by itself does not support any such functionality? – Nik May 02 '13 at 06:51
  • 1
    Apparently not, but [scipy's implementation relies only on numpy](http://stackoverflow.com/questions/12399107/alternative-to-scipy-mode-function-in-numpy), so you could just copy that code into your own function. – fgb May 02 '13 at 06:53
  • 15
    Just a note, for people who look at this in the future: you need to `import scipy.stats` explicitly, it is not included when you simply do an `import scipy`. – ffledgling Aug 15 '13 at 12:42
  • 1
    Can you please explain how exactly it is displaying the mode values and count ? I couldn't relate the output with the input provided. – Rahul Dec 06 '17 at 11:54
  • @Osgux: what doesn't work? I just re-run the code in the answer against python 2.7.14, numpy 1.13.3, and scipy 1.0.0 without a problem. – fgb Dec 06 '17 at 17:27
  • @Rahul: I'm not sure I follow. The output is a ModeResult object with mode and count arrays displayed above. – fgb Dec 06 '17 at 17:29
  • @fgb sorry It was a problem on my laptop because I have 2 version of python =/ – marti_ Dec 06 '17 at 21:16
  • @Osgux: no worries; glad you could resolve the issue! – fgb Dec 06 '17 at 21:18
  • @fgb: we actually think mode as the frequency of a value in a given set of values? I am not getting how output is being displayed. Let's say for example, if it is 1D-array [1,2,2,4,5] then the mode will be 2. But in 2D-array as in above example its printing "mode=array([[1, 3, 2, 2, 1, 1]])". Why these many "1s or 2s" ? How it derives the output ? – Rahul Dec 07 '17 at 05:31
  • 3
    @Rahul: you have to consider the default second argument of `axis=0`. The above code is reporting the mode per column of the input. The count is telling us how many times it has seen the reported mode in each of the columns. If you wanted the overall mode, you need to specify `axis=None`. For further info, please refer to https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.mode.html – fgb Jan 10 '18 at 22:04
  • @fgb Thanks for the explanation. Understood it clearly now. – Rahul Jan 17 '18 at 07:00
  • Until https://github.com/scipy/scipy/pull/8294, `scipy.stats.mode` was very slow for some cases, to the point that the more general `find_repeats` can be faster: https://github.com/scipy/scipy/issues/3035 – Achal Dave Oct 27 '18 at 15:09
33

Update

The scipy.stats.mode function has been significantly optimized since this post, and would be the recommended method

Old answer

This is a tricky problem, since there is not much out there to calculate mode along an axis. The solution is straight forward for 1-D arrays, where numpy.bincount is handy, along with numpy.unique with the return_counts arg as True. The most common n-dimensional function I see is scipy.stats.mode, although it is prohibitively slow- especially for large arrays with many unique values. As a solution, I've developed this function, and use it heavily:

import numpy

def mode(ndarray, axis=0):
    # Check inputs
    ndarray = numpy.asarray(ndarray)
    ndim = ndarray.ndim
    if ndarray.size == 1:
        return (ndarray[0], 1)
    elif ndarray.size == 0:
        raise Exception('Cannot compute mode on empty array')
    try:
        axis = range(ndarray.ndim)[axis]
    except:
        raise Exception('Axis "{}" incompatible with the {}-dimension array'.format(axis, ndim))

    # If array is 1-D and numpy version is > 1.9 numpy.unique will suffice
    if all([ndim == 1,
            int(numpy.__version__.split('.')[0]) >= 1,
            int(numpy.__version__.split('.')[1]) >= 9]):
        modals, counts = numpy.unique(ndarray, return_counts=True)
        index = numpy.argmax(counts)
        return modals[index], counts[index]

    # Sort array
    sort = numpy.sort(ndarray, axis=axis)
    # Create array to transpose along the axis and get padding shape
    transpose = numpy.roll(numpy.arange(ndim)[::-1], axis)
    shape = list(sort.shape)
    shape[axis] = 1
    # Create a boolean array along strides of unique values
    strides = numpy.concatenate([numpy.zeros(shape=shape, dtype='bool'),
                                 numpy.diff(sort, axis=axis) == 0,
                                 numpy.zeros(shape=shape, dtype='bool')],
                                axis=axis).transpose(transpose).ravel()
    # Count the stride lengths
    counts = numpy.cumsum(strides)
    counts[~strides] = numpy.concatenate([[0], numpy.diff(counts[~strides])])
    counts[strides] = 0
    # Get shape of padded counts and slice to return to the original shape
    shape = numpy.array(sort.shape)
    shape[axis] += 1
    shape = shape[transpose]
    slices = [slice(None)] * ndim
    slices[axis] = slice(1, None)
    # Reshape and compute final counts
    counts = counts.reshape(shape).transpose(transpose)[slices] + 1

    # Find maximum counts and return modals/counts
    slices = [slice(None, i) for i in sort.shape]
    del slices[axis]
    index = numpy.ogrid[slices]
    index.insert(axis, numpy.argmax(counts, axis=axis))
    return sort[index], counts[index]

Result:

In [2]: a = numpy.array([[1, 3, 4, 2, 2, 7],
                         [5, 2, 2, 1, 4, 1],
                         [3, 3, 2, 2, 1, 1]])

In [3]: mode(a)
Out[3]: (array([1, 3, 2, 2, 1, 1]), array([1, 2, 2, 2, 1, 2]))

Some benchmarks:

In [4]: import scipy.stats

In [5]: a = numpy.random.randint(1,10,(1000,1000))

In [6]: %timeit scipy.stats.mode(a)
10 loops, best of 3: 41.6 ms per loop

In [7]: %timeit mode(a)
10 loops, best of 3: 46.7 ms per loop

In [8]: a = numpy.random.randint(1,500,(1000,1000))

In [9]: %timeit scipy.stats.mode(a)
1 loops, best of 3: 1.01 s per loop

In [10]: %timeit mode(a)
10 loops, best of 3: 80 ms per loop

In [11]: a = numpy.random.random((200,200))

In [12]: %timeit scipy.stats.mode(a)
1 loops, best of 3: 3.26 s per loop

In [13]: %timeit mode(a)
1000 loops, best of 3: 1.75 ms per loop

EDIT: Provided more of a background and modified the approach to be more memory-efficient

Devin Cairns
  • 590
  • 5
  • 9
  • 1
    Please do contribute it to scipy's stat module so others also could benefit from it. – ARF Feb 04 '19 at 09:07
  • For higher dimensional problems with big int ndarrays, your solution seems to be still much faster than scipy.stats.mode. I had to compute the mode along the first axis of a 4x250x250x500 ndarray, and your function took 10s, while scipy.stats.mode took almost 600s. – CheshireCat Apr 29 '20 at 07:59
  • I concur with the comment above. Your function is still faster than scipy's implementation for larger matrices (though the performance I get from scipy is way better than 600s for me). – William Abma Feb 23 '21 at 05:08
  • for those who want to avoid the debug cycle triggered by the over-OOP'd return type, `scipy.stats.mode(arr).mode[0]` is the answer. That is: the mode is found with the call `mode(arr).mode[0]`, but you might have to catch `ValueError`s for zero length arrays and wrap in your own `mode` function, so you'll have to alias the scipy mode or import stats and call it from there. – Chris Dec 01 '21 at 22:09
24

If you want to use numpy only:

x = [-1, 2, 1, 3, 3]
vals,counts = np.unique(x, return_counts=True)

gives

(array([-1,  1,  2,  3]), array([1, 1, 1, 2]))

And extract it:

index = np.argmax(counts)
return vals[index]
poisonedivy
  • 359
  • 2
  • 6
17

A neat solution that only uses numpy (not scipy nor the Counter class):

A = np.array([[1,3,4,2,2,7], [5,2,2,1,4,1], [3,3,2,2,1,1]])

np.apply_along_axis(lambda x: np.bincount(x).argmax(), axis=0, arr=A)

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

Def_Os
  • 4,950
  • 5
  • 30
  • 60
  • 2
    Nice and concise, but should be used with caution if the original arrays contain a very large number because bincount will create bin arrays with len( max(A[i]) ) for each original array A[i]. – scottlittle Jan 06 '20 at 22:15
  • This is an awesome solution. There is actually a drawback in `scipy.stats.mode`. When there are multiple values having the most occurrence (multiple modes), it will throw an expectation. But this method will automatically take the "first mode". – Christopher Jul 05 '20 at 11:23
13

Expanding on this method, applied to finding the mode of the data where you may need the index of the actual array to see how far away the value is from the center of the distribution.

(_, idx, counts) = np.unique(a, return_index=True, return_counts=True)
index = idx[np.argmax(counts)]
mode = a[index]

Remember to discard the mode when len(np.argmax(counts)) > 1, also to validate if it is actually representative of the central distribution of your data you may check whether it falls inside your standard deviation interval.

Community
  • 1
  • 1
Lean Bravo
  • 361
  • 3
  • 5
4

simplest way in Python to get the mode of an list or array a

   import statistics
   print("mode = "+str(statistics.(mode(a)))

That's it

3

I think a very simple way would be to use the Counter class. You can then use the most_common() function of the Counter instance as mentioned here.

For 1-d arrays:

import numpy as np
from collections import Counter

nparr = np.arange(10) 
nparr[2] = 6 
nparr[3] = 6 #6 is now the mode
mode = Counter(nparr).most_common(1)
# mode will be [(6,3)] to give the count of the most occurring value, so ->
print(mode[0][0])    

For multiple dimensional arrays (little difference):

import numpy as np
from collections import Counter

nparr = np.arange(10) 
nparr[2] = 6 
nparr[3] = 6 
nparr = nparr.reshape((10,2,5))     #same thing but we add this to reshape into ndarray
mode = Counter(nparr.flatten()).most_common(1)  # just use .flatten() method

# mode will be [(6,3)] to give the count of the most occurring value, so ->
print(mode[0][0])

This may or may not be an efficient implementation, but it is convenient.

Stephen Rauch
  • 44,696
  • 30
  • 102
  • 125
Ali_Ayub
  • 31
  • 3
2
from collections import Counter

n = int(input())
data = sorted([int(i) for i in input().split()])

sorted(sorted(Counter(data).items()), key = lambda x: x[1], reverse = True)[0][0]

print(Mean)

The Counter(data) counts the frequency and returns a defaultdict. sorted(Counter(data).items()) sorts using the keys, not the frequency. Finally, need to sorted the frequency using another sorted with key = lambda x: x[1]. The reverse tells Python to sort the frequency from the largest to the smallest.

0

if you want to find mode as int Value here is the easiest way I was trying to find out mode of Array using Scipy Stats but the problem is that output of the code look like:

ModeResult(mode=array(2), count=array([[1, 2, 2, 2, 1, 2]])) , I only want the Integer output so if you want the same just try this

import numpy as np
from scipy import stats
numbers = list(map(int, input().split())) 
print(int(stats.mode(numbers)[0]))

Last line is enough to print Mode Value in Python: print(int(stats.mode(numbers)[0]))

Mayank
  • 29
  • 4
0

If you wish to use only numpy and do it without using the index of the array. The following implementation combining dictionaries with numpy can be used.

val,count = np.unique(x,return_counts=True)

freq = {}
for v,c in zip(val,count):
  freq[v] = c
mode = sorted(freq.items(),key =lambda kv :kv[1])[-1]