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?
CodePudding user response:
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.
CodePudding user response:
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