将 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'.
您甚至不需要创建任何额外的列。
我有一个如下所示的 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'.
您甚至不需要创建任何额外的列。