pd.notna will give you a series of boolean values indicating whether the argument is not-NaN, so you have True for not-NaN and False for NaN.
You can do this for all the columns you care about, and then use np.logical_and.reduce
with axis=1 to find which rows satisfy this condition for all columns you care about.
pd.isna will give you the inverse. To enforce the "all other columns are NaN" condition, you can apply this similar treatment to the columns you are not interested in.
Finally, .sum() on a boolean array will give the number of elements that are True, which is what we want.
import numpy as np
def count_not_na_rows(df: pd.DataFrame, columns_with_values: list) -> int:
nan_columns = list(set(df.columns) - set(columns_with_values))
not_nan_rows = np.logical_and.reduce(pd.notna(df.loc[:, columns_with_values]), axis=1)
nan_rows = np.logical_and.reduce(pd.isna(df.loc[:, nan_columns]), axis=1)
result = np.logical_and(not_nan_rows, nan_rows)
return result.sum()
To test this:
df = pd.DataFrame([[2, 1, np.nan],
[1, np.nan, np.nan],
[np.nan, np.nan, np.nan]], columns=["varA", "varB", "varC"])
count_not_na_rows(df, ["varA"]) # 1 -> only one row has varA but NaN for everything else
count_not_na_rows(df, ["varB"]) # 0 -> No rows have varB but NaN for everything else
count_not_na_rows(df, ["varC"]) # 0
count_not_na_rows(df, ["varA", "varB"]) # 1 -> only one row has varA and varB but NaN for everything else