How to get list of columns containing specific values corresponding to a index as a new column in pandas dataframe?

Question:

I have a pandas dataframe df which looks as follows:

A   B   C   D   E   F   G   H   I   J
Values                                      
A   NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
B   NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
C   yes NaN NaN NaN NaN NaN NaN NaN NaN NaN
D   NaN yes NaN NaN NaN NaN NaN NaN NaN NaN
E   NaN ok  ok  NaN NaN NaN NaN NaN NaN NaN
F   NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
G   NaN NaN NaN ok  NaN NaN NaN NaN NaN NaN
H   NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
I   yes NaN NaN NaN NaN NaN NaN NaN NaN NaN
J   NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

df.to_dict() is as follows:

{'A': {'A': nan,
  'B': nan,
  'C': 'yes',
  'D': nan,
  'E': nan,
  'F': nan,
  'G': nan,
  'H': nan,
  'I': 'yes',
  'J': nan},
 'B': {'A': nan,
  'B': nan,
  'C': nan,
  'D': 'yes',
  'E': 'ok',
  'F': nan,
  'G': nan,
  'H': nan,
  'I': nan,
  'J': nan},
 'C': {'A': nan,
  'B': nan,
  'C': nan,
  'D': nan,
  'E': 'ok',
  'F': nan,
  'G': nan,
  'H': nan,
  'I': nan,
  'J': nan},
 'D': {'A': nan,
  'B': nan,
  'C': nan,
  'D': nan,
  'E': nan,
  'F': nan,
  'G': 'ok',
  'H': nan,
  'I': nan,
  'J': nan},
 'E': {'A': nan,
  'B': nan,
  'C': nan,
  'D': nan,
  'E': nan,
  'F': nan,
  'G': nan,
  'H': nan,
  'I': nan,
  'J': nan},
 'F': {'A': nan,
  'B': nan,
  'C': nan,
  'D': nan,
  'E': nan,
  'F': nan,
  'G': nan,
  'H': nan,
  'I': nan,
  'J': nan},
 'G': {'A': nan,
  'B': nan,
  'C': nan,
  'D': nan,
  'E': nan,
  'F': nan,
  'G': nan,
  'H': nan,
  'I': nan,
  'J': nan},
 'H': {'A': nan,
  'B': nan,
  'C': nan,
  'D': nan,
  'E': nan,
  'F': nan,
  'G': nan,
  'H': nan,
  'I': nan,
  'J': nan},
 'I': {'A': nan,
  'B': nan,
  'C': nan,
  'D': nan,
  'E': nan,
  'F': nan,
  'G': nan,
  'H': nan,
  'I': nan,
  'J': nan},
 'J': {'A': nan,
  'B': nan,
  'C': nan,
  'D': nan,
  'E': nan,
  'F': nan,
  'G': nan,
  'H': nan,
  'I': nan,
  'J': nan},
 'To': {'A': '',
  'B': '',
  'C': 'A, ',
  'D': 'B, ',
  'E': 'B, C, ',
  'F': '',
  'G': 'D, ',
  'H': '',
  'I': 'A, ',
  'J': ''}}

I’d like to get a new column "To" which corresponding to each row which contains the list of columns having non NaN values such as "yes" or "ok".

I did it using the following code:

df["To"] = ""

for index in df.index:
    
    for column in df.columns[:-1]:
        if pd.isnull(df.loc[index, column]) == False:
    
            df.loc[index, "To"] += column + ", "
            
df

As shown, I created a new column called "To" and looped through each row and column to fill the "To" column.

The resulting dataframe looks as follows:

A   B   C   D   E   F   G   H   I   J   To
Values                                          
A   NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 
B   NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 
C   yes NaN NaN NaN NaN NaN NaN NaN NaN NaN A,
D   NaN yes NaN NaN NaN NaN NaN NaN NaN NaN B,
E   NaN ok  ok  NaN NaN NaN NaN NaN NaN NaN B, C,
F   NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 
G   NaN NaN NaN ok  NaN NaN NaN NaN NaN NaN D,
H   NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 
I   yes NaN NaN NaN NaN NaN NaN NaN NaN NaN A,
J   NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 

I think this is not an effective process and is time-consuming when the dataset is large.
Is there any shorter and more efficient way of creating this "To" column in pandas dataframe?

Asked By: hbstha123

||

Answers:

Dot product of non-NaNness and the columns (suffixed ", ") is a way of doing this:

In [242]: df.notna().dot(df.columns + ", ").str[:-2]
Out[242]:
A
B
C       A
D       B
E    B, C
F
G       D
H
I       A
J
dtype: object

What’s happening is that, df.notna() is a True/False dataframe; then we take the dot product of it with the column names (", " added). Since True is 1 and False is 0 in numeric context, the dot product behaves like a selector of column names. Then lastly we strip out the trailing ", "s.

Answered By: Mustafa Aydın

You can use stack to benefit from the default dropping of NaN values, combined with groupby.agg:

df['To'] = (df
   .stack()
   .reset_index(-1)['level_1']
   .groupby(level=0).agg(','.join)
 )

Output:

     A    B    C    D   E   F   G   H   I   J   To
A  NaN  NaN  NaN  NaN NaN NaN NaN NaN NaN NaN  NaN
B  NaN  NaN  NaN  NaN NaN NaN NaN NaN NaN NaN  NaN
C  yes  NaN  NaN  NaN NaN NaN NaN NaN NaN NaN    A
D  NaN  yes  NaN  NaN NaN NaN NaN NaN NaN NaN    B
E  NaN   ok   ok  NaN NaN NaN NaN NaN NaN NaN  B,C
F  NaN  NaN  NaN  NaN NaN NaN NaN NaN NaN NaN  NaN
G  NaN  NaN  NaN   ok NaN NaN NaN NaN NaN NaN    D
H  NaN  NaN  NaN  NaN NaN NaN NaN NaN NaN NaN  NaN
I  yes  NaN  NaN  NaN NaN NaN NaN NaN NaN NaN    A
J  NaN  NaN  NaN  NaN NaN NaN NaN NaN NaN NaN  NaN
Answered By: mozway