83

I am having trouble finding a way to do an efficient element-wise minimum of two Series objects in pandas. For example I can add two Series easily enough:

In [1]:
import pandas as pd
s1 = pd.Series(data=[1,1,1], index=[1,2,3])
s2 = pd.Series(data=[1,2,2,1], index=[1,2,3,4])
s1.add(s2)    
Out[1]:
1     2
2     3
3     3
4   NaN
dtype: float64

But I cannot find an efficient way to do an element-wise minimum between two Series (along with aligning the indices and handling NaN values).

Nevermind. There is an escape hatch with the combine function so you can put in any element-wise function:

In [2]:
s1 = pd.Series(data=[1,1,1], index=[1,2,3])
s2 = pd.Series(data=[1,2,2,1], index=[1,2,3,4])
s1.combine(s2, min, 0)
Out[2]:
1    1
2    1
3    1
4    0
dtype: int64
Andy Hayden
  • 359,921
  • 101
  • 625
  • 535
user2464433
  • 931
  • 2
  • 7
  • 6
  • 11
    I think you should add the bit after "nevermind" as an answer, rather than at the end of the question (I'd upvote)... Although I disagree that `min(1, NaN) == 0`.... – Andy Hayden Jun 07 '13 at 21:46
  • Yeah! You should answer your own question, so it has an accepted answer. – spen.smith Apr 28 '19 at 23:04

10 Answers10

61

I find this the simplest:

import numpy as np

smax = np.minimum(s1, s2)

Link to docs (numpy.minimum)

Jason
  • 4,346
  • 10
  • 49
  • 75
Ankur Kanoria
  • 957
  • 8
  • 9
53

The most straightforward way I can see is to make them into a DataFrame and then take the row-wise min:

>>> print pandas.concat([s1, s2], axis=1).min(axis=1)
1    1
2    1
3    1
4    1
dtype: float64
BrenBarn
  • 242,874
  • 37
  • 412
  • 384
28

Another similar way:

In [11]: pd.DataFrame([s1, s2]).min()
Out[11]:
1    1
2    1
3    1
4    1
dtype: float64
Andy Hayden
  • 359,921
  • 101
  • 625
  • 535
11
pd.Series([1,2,3,4,5]).clip(upper=pd.Series([6,5,4,3,2]))

will get you:

0    1
1    2
2    3
3    3
4    2
dtype: int64
featuredpeow
  • 2,061
  • 1
  • 19
  • 18
3

You can concat the dataframes and take the minimum, specifying level=0:

>>> s1 = pd.Series(data=[1,1,1], index=[1,2,3])
>>> s2 = pd.Series(data=[1,2,2,1], index=[1,2,3,4])
>>> pd.concat([s1, s2]).min(level=0)
1    1
2    1
3    1
4    1
dtype: int64

This approach also works on dataframes.

shaneb
  • 1,314
  • 1
  • 13
  • 18
2

You can use the combine method of a DataFrame with np.minimum as the argument. np.minimum has special handling for NaN and complex NaNs.

Indeed, the pandas docs for combine uses the np.minimum function to illustrate a "true element-wise combine":

>>> df1 = pd.DataFrame({'A': [5, 0], 'B': [2, 4]})
>>> df2 = pd.DataFrame({'A': [1, 1], 'B': [3, 3]})
>>> df1.combine(df2, np.minimum)
   A  B
0  1  2
1  0  3
shaneb
  • 1,314
  • 1
  • 13
  • 18
2

This method will do the job:

import pandas as pd


def elementwise_min(x, y):
    x[x > y] = y
    return x


a = pd.Series([1, 2, 3])
b = pd.Series([0, 2, 4])
elementwise_min(a, b)
renan-eccel
  • 181
  • 10
2

You can use clip method:

>>> s1.clip(upper=s2)
1    1
2    1
3    1
dtype: int64

>>> s2.clip(upper=s1)
1    1.0
2    1.0
3    1.0
4    NaN
dtype: float64
Bhindi
  • 1,403
  • 12
  • 16
1

Same answer as Andy Hayden, but a bit easier to read:

>>> import pandas as pd
>>> s1 = pd.Series(data=[1,2,3,4,5], index=[1,2,3,4,5])
>>> s2 = pd.Series(data=[5,1,3,5], index=[1,2,3,4])
>>> pd.DataFrame([s1, s2]).min()
1    1.0
2    1.0
3    3.0
4    4.0
5    5.0
dtype: float64
Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
1

One straight-forward way is to use Series.where function:

min_s1_s2 = s1.where(s1 < s2, s2)

where is a fundamental method in terms of which clip can be implemented, but not the other way around.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271