Python Pandas – How to Delete Row with Max/Min Values

delete-rowpandaspython

I have dataframe:

   one   N    th
0   A      5      1   
1   Z      17     0   
2   A      16     0   
3   B      9      1   
4   B      17     0   
5   B      117    1   
6   XC     35     1   
7   C      85     0    
8   Ce     965    1 

I'm looking the way to keep alternating 0101 in column three without doubling 0 or 1.
So, i want to delete row with min of values in case if i have two repeating 0 in column th and max values if i have repeating 1.

My base consis of 1000000 rows.

I expect to have dataframe like this:

   one   N    th
0   A      5      1   
1   Z      17     0   
3   B      9      1   
4   B      17     0    
6   XC     35     1   
7   C      85     0    
8   Ce     965    1 

What is the fastest way to do it. I mean vectorized way. My attempts without result.

Best Answer

using a custom groupby.idxmax

You can swap the sign if "th" is 1 (to get the max instead of min), then set up a custom grouper (with diff or shift + cumsum) and perform a groupby.idxmax to select the rows to keep:

out = df.loc[df['N'].mul(df['th'].map({0: 1, 1: -1}))
             .groupby(df['th'].ne(df['th'].shift()).cumsum())
             .idxmax()]

Variant with a different method to swap the sign and to compute the group:

out = df.loc[df['N'].mask(df['th'].eq(1), -df['N'])
             .groupby(df['th'].diff().ne(0).cumsum())
             .idxmax()]

Output:

  one    N  th
0   A    5   1
1   Z   17   0
3   B    9   1
4   B   17   0
6  XC   35   1
7   C   85   0
8  Ce  965   1

Intermediates:

  one    N  th  swap  group max
0   A    5   1    -5      1   X
1   Z   17   0    17      2   X
2   A   16   0    16      2    
3   B    9   1    -9      3   X
4   B   17   0    17      4   X
5   B  117   1  -117      5    
6  XC   35   1   -35      5   X
7   C   85   0    85      6   X
8  Ce  965   1  -965      7   X

using boolean masks

The above code works for an arbitrary number of consecutive 0s or 1s. If you know that you only have up to 2 successive ones, you could also use boolean indexing, which should be significantly faster:

# has the value higher precedence than the next?
D = df['N'].mask(df['th'].eq(1), -df['N']).diff()

# is the th different from the previous?
G = df['th'].ne(df['th'].shift(fill_value=-1))

# rule for the bottom row
m1 = D.gt(0) | G

# rule for the top row
# same rule as above but shifted up
# D is inverted
# comparison is not strict in case of equality
m2 = ( D.le(0).shift(-1, fill_value=True)
      | G.shift(-1, fill_value=True)
     )

# keep rows of interest
out = df.loc[m1&m2]

Output:

  one    N  th
0   A    5   1
1   Z   17   0
3   B    9   1
4   B   17   0
6  XC   35   1
7   C   85   0
8  Ce  965   1

Intermediates:

  one    N  th       D      G     m1     m2  m1&m2
0   A    5   1     NaN   True   True   True   True
1   Z   17   0    22.0   True   True   True   True
2   A   16   0    -1.0  False  False   True  False
3   B    9   1   -25.0   True   True   True   True
4   B   17   0    26.0   True   True   True   True
5   B  117   1  -134.0   True   True  False  False
6  XC   35   1    82.0  False   True   True   True
7   C   85   0   120.0   True   True   True   True
8  Ce  965   1 -1050.0   True   True   True   True

More complex example with equal values:

   one    N  th       D      G     m1     m2  m1&m2
0    A    5   1     NaN   True   True   True   True
1    Z   17   0    22.0   True   True   True   True
2    A   16   0    -1.0  False  False   True  False
3    B    9   1   -25.0   True   True   True   True
4    B   17   0    26.0   True   True   True   True
5    B  117   1  -134.0   True   True  False  False
6   XC   35   1    82.0  False   True   True   True
7    C   85   0   120.0   True   True   True   True
8   Ce  965   1 -1050.0   True   True   True   True
9    u  123   0  1088.0   True   True   True   True # because of D.le(0)
10   v  123   0     0.0  False  False   True  False # because or D.gt(0)

NB. in case of equality, it is possible to select the first/second row or both or none, depending on the operator used (D.le(0), D.lt(0), D.gt(0), D.ge(0)).

timings

Although limited to maximum 2 consecutive "th", the boolean mask approach is ~4-5x faster. Timed on 1M rows:

# groupby + idxmax
96.4 ms ± 6.64 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

# boolean masks
22.2 ms ± 1.48 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)