quickly drop dataframe columns with only one distinct value

Question:

Is there a faster way to drop columns that only contain one distinct value than the code below?

cols=df.columns.tolist()
for col in cols:
    if len(set(df[col].tolist()))<2:
        df=df.drop(col, axis=1)

This is really quite slow for large dataframes. Logically, this counts the number of values in each column when in fact it could just stop counting after reaching 2 different values.

Asked By: Alexis Eggermont

||

Answers:

You can use Series.unique() method to find out all the unique elements in a column, and for columns whose .unique() returns only 1 element, you can drop that. Example –

for col in df.columns:
    if len(df[col].unique()) == 1:
        df.drop(col,inplace=True,axis=1)

A method that does not do inplace dropping –

res = df
for col in df.columns:
    if len(df[col].unique()) == 1:
        res = res.drop(col,axis=1)

Demo –

In [154]: df = pd.DataFrame([[1,2,3],[1,3,3],[1,2,3]])

In [155]: for col in df.columns:
   .....:     if len(df[col].unique()) == 1:
   .....:         df.drop(col,inplace=True,axis=1)
   .....:

In [156]: df
Out[156]:
   1
0  2
1  3
2  2

Timing results –

In [166]: %paste
def func1(df):
        res = df
        for col in df.columns:
                if len(df[col].unique()) == 1:
                        res = res.drop(col,axis=1)
        return res

## -- End pasted text --

In [172]: df = pd.DataFrame({'a':1, 'b':np.arange(5), 'c':[0,0,2,2,2]})

In [178]: %timeit func1(df)
1000 loops, best of 3: 1.05 ms per loop

In [180]: %timeit df[df.apply(pd.Series.value_counts).dropna(thresh=2, axis=1).columns]
100 loops, best of 3: 8.81 ms per loop

In [181]: %timeit df.apply(pd.Series.value_counts).dropna(thresh=2, axis=1)
100 loops, best of 3: 5.81 ms per loop

The fastest method still seems to be the method using unique and looping through the columns.

Answered By: Anand S Kumar

You can create a mask of your df by calling apply and call value_counts, this will produce NaN for all rows except one, you can then call dropna column-wise and pass param thresh=2 so that there must be 2 or more non-NaN values:

In [329]:   
df = pd.DataFrame({'a':1, 'b':np.arange(5), 'c':[0,0,2,2,2]})
df

Out[329]:
   a  b  c
0  1  0  0
1  1  1  0
2  1  2  2
3  1  3  2
4  1  4  2

In [342]:
df[df.apply(pd.Series.value_counts).dropna(thresh=2, axis=1).columns]

Out[342]:
   b  c
0  0  0
1  1  0
2  2  2
3  3  2
4  4  2

Output from the boolean conditions:

In [344]:
df.apply(pd.Series.value_counts)

Out[344]:
    a  b   c
0 NaN  1   2
1   5  1 NaN
2 NaN  1   3
3 NaN  1 NaN
4 NaN  1 NaN

In [345]:
df.apply(pd.Series.value_counts).dropna(thresh=2, axis=1)

Out[345]:
   b   c
0  1   2
1  1 NaN
2  1   3
3  1 NaN
4  1 NaN
Answered By: EdChum

One step:

df = df[[c for c
        in list(df)
        if len(df[c].unique()) > 1]]

Two steps:

Create a list of column names that have more than 1 distinct value.

keep = [c for c
        in list(df)
        if len(df[c].unique()) > 1]

Drop the columns that are not in ‘keep’

df = df[keep]

Note: this step can also be done using a list of columns to drop:

drop_cols = [c for c
             in list(df)
             if df[c].nunique() <= 1]
df = df.drop(columns=drop_cols)
Answered By: kait
df.loc[:,df.apply(pd.Series.nunique) != 1]

For example

In:
df = pd.DataFrame({'A': [10, 20, np.nan, 30], 'B': [10, np.nan, 10, 10]})
df.loc[:,df.apply(pd.Series.nunique) != 1]

Out:
   A
0  10
1  20
2  NaN
3  30
Answered By: jz0410

None of the solutions worked in my use-case because I got this error: (my dataframe contains list item).

TypeError: unhashable type: ‘list’

The solution that worked for me is this:

ndf = df.describe(include="all").T
new_cols = set(df.columns) - set(ndf[ndf.unique == 1].index)
df = df[list(new_cols)]  
Answered By: shantanuo

Most ‘pythonic’ way of doing it I could find:

df = df.loc[:, (df != df.iloc[0]).any()]
Answered By: amalik2205

Many examples in thread and this thread does not worked for my df. Those worked:

# from: https://stackoverflow.com/questions/33144813/quickly-drop-dataframe-columns-with-only-one-distinct-value
# from: https://stackoverflow.com/questions/20209600/pandas-dataframe-remove-constant-column

import pandas as pd
import numpy as np


data = {'var1': [1,2,3,4,5,np.nan,7,8,9],
       'var2':['Order',np.nan,'Inv','Order','Order','Shp','Order', 'Order','Inv'],
       'var3':[101,101,101,102,102,102,103,103,np.nan], 
       'var4':[np.nan,1,1,1,1,1,1,1,1],
       'var5':[1,1,1,1,1,1,1,1,1],
       'var6':[np.nan,np.nan,np.nan,np.nan,np.nan,np.nan,np.nan,np.nan,np.nan],
       'var7':["a","a","a","a","a","a","a","a","a"],
       'var8': [1,2,3,4,5,6,7,8,9]}


df = pd.DataFrame(data)
df_original = df.copy()



#-------------------------------------------------------------------------------------------------


df2 = df[[c for c
        in list(df)
        if len(df[c].unique()) > 1]]


#-------------------------------------------------------------------------------------------------


keep = [c for c
        in list(df)
        if len(df[c].unique()) > 1]

df3 = df[keep]



#-------------------------------------------------------------------------------------------------



keep_columns = [col for col in df.columns if len(df[col].unique()) > 1]

df5 = df[keep_columns].copy()



#-------------------------------------------------------------------------------------------------



for col in df.columns:
     if len(df[col].unique()) == 1:
         df.drop(col,inplace=True,axis=1)
Answered By: vasili111

Two simple one-liners for either returning a view (shorter version of jz0410’s answer)

df.loc[:,df.nunique()!=1]

or dropping inplace (via drop())

df.drop(columns=df.columns[df.nunique()==1], inplace=True)
Answered By: Ben JW

I would like to throw in:
pandas 1.0.3

ids = df.nunique().values>1
df.loc[:,ids]

not that slow:

2.81 ms ± 115 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Answered By: smurfit89

One line

df=df[[i for i in df if len(set(df[i]))>1]]
Answered By: Elvin Aghammadzada
df=df.loc[:,df.nunique()!=Numberofvalues]
Answered By: waqas ahmed

One of the solutions with pipe (convenient if used often):

def drop_unique_value_col(df):
    return df.loc[:,df.apply(pd.Series.nunique) != 1]

df.pipe(drop_unique_value_col)
Answered By: Adrien Pacifico

This will drop all the columns with only one distinct value.

for col in Dataframe.columns:
    
    if len(Dataframe[col].value_counts()) == 1:

        Dataframe.drop([col], axis=1, inplace=True)
Answered By: Adeel Afzal
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.