What is the neatest way to replace the dataframe of values with np.nan if it falls 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
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