如何在groupby之后合并列并在pandas数据框中选择其他列的第一个有效值?

时间:2018-12-07 00:22:32

标签: python pandas numpy dataframe

我有一个熊猫数据框,格式为:

df

    ID    col_1    col_2    col_3    Date
     1              20       40      1/1/2018
     1     10                        1/2/2018
     1     50                60      1/3/2018
     3     40       10       90      1/1/2018
     4              80       80      1/1/2018

问题是,我需要为每个列创建一个具有第一个有效值的新数据框,但还要从“日期”派生其他列,这与在原始数据框中匹配这些值的时间相对应。

换句话说:

new_df

    ID    first_col_1    Date_col_1    first_col_2    Date_col_2    first_col_3    Date_col_3
    1         10          1/2/2018          20         1/1/2018         40         1/1/2018 
    3         40          1/1/2018          10         1/1/2018         90         1/1/2018 
    4                     1/1/2018          80         1/1/2018         80         1/1/2018

我知道获得每个ID的每列第一个有效值很简单

df.groupby('ID').first()

但是如何为每一列提取相关的“日期”信息?

3 个答案:

答案 0 :(得分:1)

IIUC在melt之前使用groupby

newdf=df.melt(['ID','Date']).loc[lambda x : x.value!='']

newdf=  newdf.groupby(['ID','variable']).first().unstack().sort_index(level=1,axis=1)

newdf.columns=newdf.columns.map('_'.join)
newdf
   Date_col_1  value_col_1 Date_col_2  value_col_2 Date_col_3  value_col_3
ID                                                                        
1    1/2/2018         10.0   1/1/2018         20.0   1/1/2018         40.0
3    1/1/2018         40.0   1/1/2018         10.0   1/1/2018         90.0
4        None          NaN   1/1/2018         80.0   1/1/2018         80.0

答案 1 :(得分:1)

您不需要循环,但是在进行分组操作之前,您需要“融化”数据框。

所以从以下开始:

from io import StringIO
import pandas
f = StringIO("""\
ID,col_1,col_2,col_3,Date
1,,20,40,1/1/2018
1,10,,,1/2/2018
1,50,,60,1/3/2018
3,40,10,90,1/1/2018
4,,80,80,1/1/2018
""")

df = pandas.read_csv(f)

然后,您可以:

print(
    df.melt(id_vars=['ID', 'Date'], value_vars=['col_1', 'col_2', 'col_3'], value_name='first')
      .groupby(by=['ID', 'variable'])
      .first()
      .unstack(level='variable')
)

哪个给你:

              Date                     first            
variable     col_1     col_2     col_3 col_1 col_2 col_3
ID                                                      
1         1/1/2018  1/1/2018  1/1/2018  10.0  20.0  40.0
3         1/1/2018  1/1/2018  1/1/2018  40.0  10.0  90.0
4         1/1/2018  1/1/2018  1/1/2018   NaN  80.0  80.0

这些列是多层的,因此您可以根据需要对它们进行一些修饰:

def flatten_columns(df, sep='_'):
    newcols = [sep.join(_) for _ in df.columns]
    return df.set_axis(newcols, axis='columns', inplace=False)

print(
    df.melt(id_vars=['ID', 'Date'], value_vars=['col_1', 'col_2', 'col_3'], value_name='first')
      .groupby(by=['ID', 'variable'])
      .first()
      .unstack(level='variable')
      .sort_index(level='variable', axis='columns')
      .pipe(flatten_columns)
)

这为您提供了一些与示例不完全相同的列顺序,但是与我想的差不多。

   Date_col_1  first_col_1 Date_col_2  first_col_2 Date_col_3  first_col_3
ID                                                                        
1    1/1/2018         10.0   1/1/2018         20.0   1/1/2018         40.0
3    1/1/2018         40.0   1/1/2018         10.0   1/1/2018         90.0
4    1/1/2018          NaN   1/1/2018         80.0   1/1/2018         80.0

答案 2 :(得分:0)

我认为您必须遍历各列,并在连接之前为它们中的每一个提取第一个值。我看不到一种更简单的方法。

# Create a list to store the dataframes you want for each column
sub_df = [pd.DataFrame(df['ID'].unique(), columns=['ID'])]  # Init this list with IDs

for col in df.columns[1:-1]:  # loop over the columns (except ID and Date)

    # Determine the first valid rows indexes for this column (group by ID)
    valid_rows = df.groupby('ID')[col].apply(lambda sub_df: sub_df.first_valid_index())

    # Extracting the values and dates corresponding to these rows
    new_sub_df = df[[col, 'Date']].ix[valid_rows].reset_index(drop=True)

    # Append to the list of sub DataFrames
    sub_df.append(new_sub_df)

# Concatenate all these DataFrames.
new_df = pd.concat(sub_df, axis=1)