将 groupby 中第一行的 NaN 值替换为包含特定值的下一行的值 - Python

Replace NaN value of first row in a groupby with value of next row which contains specific value - Python

我有一个如下所示的 DataFrame

email              month      level
jacob.a@abc.com    jan        EE2 
kylie.l@abc.com    jan        nan
jacob.a@abc.com    mar        MG1 
sumeer.b@abc.com   jan        nan
boris.k@abc.com    jan        nan
kylie.l@abc.com    jun        EE3
cinkil.m@abc.com   jan        nan
sumeer.b@abc.com   apr        PT 
cinkil.m@abc.com   jul        MG1
sumeer.b@abc.com   aug        MG1
sumeer.b@abc.com   sep        MG2 
kylie.l@abc.com    sep        MG3

我计划对每个组的第一行和最后一行进行 groupby 到 select。

但在我这样做之前,我想将每个员工的第一行“nan”替换为下一行,前提是它包含“EE”或“MG”[=18] =]

我正在考虑创建一个名为 level_new

的新专栏
email              month      level     level_new
jacob.a@abc.com    jan        EE2       EE2
kylie.l@abc.com    jan        nan       EE3
jacob.a@abc.com    mar        MG1       MG1
sumeer.b@abc.com   jan        nan       MG1
boris.k@abc.com    jan        nan       nan
kylie.l@abc.com    jun        EE3       EE3
cinkil.m@abc.com   jan        nan       MG1
sumeer.b@abc.com   apr        PT        PT
cinkil.m@abc.com   jul        MG1       MG1
sumeer.b@abc.com   aug        MG1       MG1
sumeer.b@abc.com   oct        MG2       MG2  
kylie.l@abc.com    sep        MG3       MG3

这样我就可以实现以下groupby

email              month      level     level_new
jacob.a@abc.com    jan        EE2       EE2
jacob.a@abc.com    mar        MG1       MG1
kylie.l@abc.com    jan        nan       EE3
kylie.l@abc.com    sep        MG3       MG3
sumeer.b@abc.com   jan        nan       MG1
sumeer.b@abc.com   oct        MG2       MG2  
boris.k@abc.com    jan        nan       nan
cinkil.m@abc.com   jan        nan       MG1
cinkil.m@abc.com   jul        MG1       MG1

到目前为止,我只能 select 基于分组依据的第一行和最后一行,但这仍然会 select 每个员工第一行的 nan 值。

#get the first and last row of each group
#".nth[-1]" retrieves the last row
#".nth[0]" retrieves the first row
df2 = df.groupby('email', as_index=False).nth([0,-1]) 

我们可以使用 where 将“MG”或“EE”以外的值替换为 NaN;然后 groupby + bfill + fillna 在“级别”列中填写 NaN 值,每个“电子邮件”的下一个值为“MG”或“EE”。

然后使用 groupby + 应用一个 lambda 获取每个“电子邮件”的第一个和最后一个值的索引作为列表 + explode 列表 + drop_duplicates (在如果某些电子邮件只出现一次)创建一个掩码,每个“电子邮件”的第一个和最后一个值 returns 为 True,否则为 False。然后使用这个掩码过滤相关结果:

df['level_new'] = df['level'].fillna(df['level'].where(df['level'].str.contains('MG|EE')).groupby(df['email']).bfill())
out = df.loc[df.groupby('email')['level_new'].apply(lambda x: [x.index.min(), x.index.max()]).explode().drop_duplicates()]

输出:

               email month level level_new
4    boris.k@abc.com   jan   NaN       NaN
6   cinkil.m@abc.com   jan   NaN       MG1
8   cinkil.m@abc.com   jul   MG1       MG1
0    jacob.a@abc.com   jan   EE2       EE2
2    jacob.a@abc.com   mar   MG1       MG1
1    kylie.l@abc.com   jan   NaN       EE3
11   kylie.l@abc.com   sep   MG3       MG3
3   sumeer.b@abc.com   jan   NaN       MG1
10  sumeer.b@abc.com   sep   MG2       MG2

定义以下函数来处理一个组:

def procGrp(grp):
    if grp.index.size == 1:    # single row only
        return grp
    if pd.isnull(grp.iat[0,2]):
        nxtLev = grp.iat[1,2]  # next "level"
        if ('EE' in nxtLev) or ('MG' in nxtLev):
            grp.iat[0,2] = nxtLev  # set in 1-st row
    # Return first and last row from this group
    return grp.loc[[grp.index[0], grp.index[-1]]]

然后按 email 对您的 DataFrame 进行分组并应用此函数:

result = df.groupby('email').apply(procGrp)

对于您的数据样本,结果是:

               email month level
4    boris.k@abc.com   jan   NaN
6   cinkil.m@abc.com   jan   MG1
8   cinkil.m@abc.com   jul   MG1
0    jacob.a@abc.com   jan   EE2
2    jacob.a@abc.com   mar   MG1
1    kylie.l@abc.com   jan   EE3
11   kylie.l@abc.com   sep   MG3
3   sumeer.b@abc.com   jan   NaN
10  sumeer.b@abc.com   sep   MG2

如您所见:

    boris.k@abc.com
  • 行也仍然 NaN,因为该组仅包含 一行,
  • sumeer.b@abc.com
  • 行仍然有 NaN,因为下一行有 等级 == 'PT'.

您甚至不需要创建任何额外的列。