4

I have a dataframe with some NaNs:

hostname period Teff
51 Peg  4.2293  5773
51 Peg  4.231   NaN
51 Peg  4.23077 NaN
55 Cnc  44.3787 NaN
55 Cnc  44.373  NaN
55 Cnc  44.4175 NaN
55 Cnc  NaN 5234
61 Vir  NaN 5577
61 Vir  38.021  NaN
61 Vir  123.01  NaN

The rows with the same "hostname" all refer to the same object, but as you can see, some entries have NaNs under various columns. I'd like to merge all the rows under the same hostname such that I retain the first finite value in each column (drop the row if all values are NaN). So the result should look like this:

hostname period Teff
51 Peg  4.2293  5773
55 Cnc  44.3787 5234
61 Vir  38.021  5577

How would you go about doing this?

1
  • You can simply use df3 = df1.combine_first(df2) which is designed to do exactly this operation. Commented Mar 17, 2023 at 14:37

2 Answers 2

10

Use groupby.first; It takes the first non NA value:

df.groupby('hostname')[['period', 'Teff']].first().reset_index()
#  hostname   period  Teff
#0      Cnc  44.3787  5234
#1      Peg   4.2293  5773
#2      Vir  38.0210  5577

Or manually do this with a custom aggregation function:

df.groupby('hostname')[['period', 'Teff']].agg(lambda x: x.dropna().iat[0]).reset_index()

This requires each group has at least one non NA value.

Write your own function to handle the edge case:

def first_(g):
    non_na = g.dropna()
    return non_na.iat[0] if len(non_na) > 0 else pd.np.nan

df.groupby('hostname')[['period', 'Teff']].agg(first_).reset_index()

#  hostname   period  Teff
#0      Cnc  44.3787  5234
#1      Peg   4.2293  5773
#2      Vir  38.0210  5577
Sign up to request clarification or add additional context in comments.

3 Comments

Both of your solutions works well, except they also drop all the other columns (if my dataframe has columns in addition to those three named here).
If you want to take the first value from all columns, you should be able to simply do df.groupby('hostname').first().reset_index() without selecting columns.
I see. That does what I need it to do, and is short and simple. Thanks!
1

Is this what you need ?

pd.concat([ df1.apply(lambda x: sorted(x, key=pd.isnull)) for _, df1 in df.groupby('hostname')]).dropna()
Out[343]: 
   hostname   period    Teff
55      Cnc  44.3787  5234.0
51      Peg   4.2293  5773.0
61      Vir  38.0210  5577.0

2 Comments

Thank you. This appears to work perfectly with one small modification to only drop NaNs in selected columns: pd.concat([ df1.apply(lambda x: sorted(x, key=pd.isnull)) for _, df1 in df.groupby('hostname')]).dropna(subset=['period','Teff'])
@mcglashan aha , :-) glad it help

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.