One way to do it is using np.isnan
and create a boolean and then argsort them which will all NaN
to the end while maintaining the original order non-nan values. Then drop column whose all column values are NaN
idx = np.isnan(df.values).argsort(axis=1)
df = pd.DataFrame(
df.values[np.arange(df.shape[0])[:, None], idx],
index=df.index,
columns=df.columns,
).dropna(how="all", axis=1)
print(df)
0 1 2 3 4 5 6 7
0 1.0 3.0 4.0 5.0 6.0 8.0 9.0 NaN
1 11.0 12.0 15.0 16.0 17.0 19.0 20.0 NaN
2 22.0 23.0 24.0 25.0 27.0 28.0 29.0 30.0
Details
np.isnan(df.values)
# non-nan val nan value
# | |
# array([[False, True, False, False, False, False, True, False, False, True],
# [False, False, True, True, False, False, False, True, False, False],
# [ True, False, False, False, False, True, False, False, False, False]])
# False ⟶ 0 True ⟶ 1
# When sorted all True values i.e nan are pushed to the right.
idx = np.isnan(df.values).argsort(axis=1)
# array([[0, 2, 3, 4, 5, 7, 8, 1, 6, 9],
# [0, 1, 4, 5, 6, 8, 9, 2, 3, 7],
# [1, 2, 3, 4, 6, 7, 8, 9, 0, 5]], dtype=int64)
# Now, indexing `df.values` using `idx`
pd.DataFrame(
df.values[np.arange(df.shape[0])[:, None], idx],
index=df.index, # This is important if you have custom index like say a, b, c...
columns=df.columns # If you custom column names
)
# 0 1 2 3 4 5 6 7 8 9
# 0 1.0 3.0 4.0 5.0 6.0 8.0 9.0 NaN NaN NaN
# 1 11.0 12.0 15.0 16.0 17.0 19.0 20.0 NaN NaN NaN
# 2 22.0 23.0 24.0 25.0 27.0 28.0 29.0 30.0 NaN NaN
# ⤓ ⤓
# `_.dropna(how='all', axis=1)` All Nans in column
# so drop them