Home > Net >  Replace the dataframe of values with np.nan if the fall outside of the lower and upper limit
Replace the dataframe of values with np.nan if the fall outside of the lower and upper limit

Time:09-01

What is the neatest way to replace the dataframe of values with np.nan if the fall outside of the lower and upper limit?

Values: A,B,C,D
Limits: lower,upper

df1 = pd.DataFrame(np.random.randint(50,300,size=(100, 4)), columns=list('ABCD')) # Generate Random Dataframe
df2 = pd.DataFrame(np.random.randint(150,180,size=(100, 1)), columns=['lower'])
df3 = pd.DataFrame(np.random.randint(180,200,size=(100, 1)), columns=['upper'])
df = pd.concat([df1,df2,df3], axis=1)
df

enter image description here

CodePudding user response:

Use DataFrame.mask with conditions by DataFrame.lt and DataFrame.gt chained by | for bitwise OR:

c = list('ABCD')
df[c] = df[c].mask(df[c].lt(df['lower'], axis=0) | df[c].gt(df['upper'], axis=0))
print (df)
     A      B      C      D  lower  upper
0  NaN    NaN    NaN    NaN    178    180
1  NaN    NaN  164.0    NaN    161    187
2  NaN    NaN    NaN    NaN    169    181
3  NaN    NaN    NaN  187.0    174    198
4  NaN    NaN    NaN    NaN    163    186
..  ..    ...    ...    ...    ...    ...
95 NaN    NaN    NaN    NaN    177    181
96 NaN    NaN    NaN    NaN    158    192
97 NaN  168.0    NaN  183.0    150    198
98 NaN    NaN    NaN    NaN    167    186
99 NaN    NaN    NaN    NaN    166    190
    

CodePudding user response:

I would use between. It seems to be descriptive and very clear for your task. Please see the below example:

import numpy as np

cols = ['A','B','C','D']
for col in cols:
    df[col] = np.where(df[col].between(df['lower'],df['upper']),df[col],np.nan)

print:

     A      B      C      D  lower  upper
0  NaN    NaN    NaN    NaN    152    198
1  NaN    NaN    NaN    NaN    162    183
2  NaN    NaN    NaN    NaN    163    193
3  NaN    NaN  183.0  196.0    174    196
4  NaN    NaN    NaN    NaN    164    183
..  ..    ...    ...    ...    ...    ...
95 NaN    NaN    NaN    NaN    151    193
96 NaN  160.0    NaN    NaN    159    199
97 NaN    NaN    NaN    NaN    178    193
98 NaN    NaN    NaN    NaN    155    180
99 NaN    NaN    NaN    NaN    159    193

CodePudding user response:

Another approach with np:

df_slice = df[list('ABCD')]
x, y = df_slice.shape[1], df.lower.shape[0]
lower = np.repeat(df.lower.values,x).reshape(y, x)
upper = np.repeat(df.upper.values,x).reshape(y, x)

df.loc[:,list('ABCD')] = df_slice[(df_slice>=lower) & (df_slice<=upper)]

There's some perfomance gain. Comparison with answer by @jezrael:

import timeit

mysetup = """
import pandas as pd
import numpy as np

df1 = pd.DataFrame(np.random.randint(50,300,size=(100, 4)), columns=list('ABCD')) # Generate Random Dataframe
df2 = pd.DataFrame(np.random.randint(150,180,size=(100, 1)), columns=['lower'])
df3 = pd.DataFrame(np.random.randint(180,200,size=(100, 1)), columns=['upper'])
df = pd.concat([df1,df2,df3], axis=1)
"""

repeat = """
df_slice = df[list('ABCD')]
x, y = df_slice.shape[1], df.lower.shape[0]
lower = np.repeat(df.lower.values,x).reshape(y, x)
upper = np.repeat(df.upper.values,x).reshape(y, x)

df.loc[:,list('ABCD')] = df_slice[(df_slice>=lower) & (df_slice<=upper)]
"""

masking = """
c = list('ABCD')
df[c] = df[c].mask(df[c].lt(df['lower'], axis=0) | df[c].gt(df['upper'], axis=0))
"""

print(f'repeat: {timeit.timeit(setup = mysetup,stmt = repeat, number = 10000)} seconds')
print (f'masking: {timeit.timeit(setup = mysetup,stmt = masking, number = 10000)} seconds')

# repeat: 25.68002800000022 seconds
# masking: 36.715671100000236 seconds

CodePudding user response:

Another possible solution, using broadcasted comparison:

v1, v2 = [np.array(df[x]) for x in ['lower','upper']]
c = ['A', 'B', 'C', 'D']

df[c] = df[c].where((df[c] >= v1[:, None]) & (df[c] <= v2[:, None]))

Output:

        A      B      C      D  lower  upper
0   167.0    NaN    NaN    NaN    150    194
1   179.0  195.0    NaN    NaN    161    196
2     NaN    NaN    NaN  185.0    176    187
3     NaN    NaN    NaN    NaN    163    182
4     NaN    NaN    NaN  168.0    153    185
..    ...    ...    ...    ...    ...    ...
95    NaN  182.0    NaN    NaN    158    183
96    NaN    NaN  192.0    NaN    152    193
97  170.0    NaN  168.0    NaN    161    190
98    NaN  173.0    NaN    NaN    154    187
99  175.0  159.0    NaN    NaN    151    183
  • Related