如何根据多个条件按变量组高效更新数据帧值?



我有一个数据帧,如下所示:

input_df:

name  name_group  value
foo1          a       2
foo2          a       2
foo3          a       2
foo4          a       2
bar1          b
bar2          b
bar3          b
buzz1         c       6
buzz2         c       6
buzz3         c       6
buzz4         c       6
buzz5         c       6

其中每个name_group中的每一行都具有相同的"值",因此在这种情况下,name_group"a"中的每个名称都具有相同的"值","b"name_group中的每个名称都具有相同的"值",依此类推。

我想创建一个新列"new_vals",它等于该"name_group"、"值"列中行的 min(#)。如果任何"name_group"的"value"列中缺少值,则应仅使用该"name_group"中的#行。对于我的示例数据帧,所需的输出为:

output_df:

name  name_group  value   new_vals
foo1          a       2          2
foo2          a       2          2
foo3          a       2          2
foo4          a       2          2
bar1          b                  3
bar2          b                  3
bar3          b                  3
buzz1         c       6          5
buzz2         c       6          5
buzz3         c       6          5
buzz4         c       6          5
buzz5         c       6          5

目前,我实现这一目标的方法是遍历"name_group"列中的每个唯一值,找到该name_group中的 # 行,将其与"value"列中的值进行比较,然后根据两者之间的最小值设置"new_val"列的值。每个"name_group"的结果都连接到另一个数据帧,直到我得到最终输出。

虽然这种方法有效,但我觉得必须有一种更有效的方法来做到这一点,而不是获取起始数据帧的子集,分别处理每个"name_group",然后将所有内容重新组合在一起。有没有人有更pythonic/更有效的方式来重现此功能?

以下是一些反映我当前流程的代码:

output_df = pd.DataFrame()
for name_group in input_df['name_group'].unique():
# process the data one name group at a time
temp_df = input_df.loc[input_df['name_group'] == name_group]
max_val = temp_df['value'].max()
name_group_cnt = temp_df.shape[0]
# if the "value" column is empty, set new_val equal to the number of
# rows in that name_group
if max_val == '':
new_val = name_group_cnt
else:
new_val = min(max_val, name_group_cnt)
temp_df['new_val'] = new_val
output_df = pd.concat([output_df, temp_df])

我将使用transformnp.where

s=df.groupby('name_group').name_group.transform('count')
df['New']=np.where(s>df.value,df.value,s)
df
Out[13]: 
name name_group  value  New
0    foo1          a    2.0  2.0
1    foo2          a    2.0  2.0
2    foo3          a    2.0  2.0
3    foo4          a    2.0  2.0
4    bar1          b    NaN  3.0
5    bar2          b    NaN  3.0
6    bar3          b    NaN  3.0
7   buzz1          c    6.0  5.0
8   buzz2          c    6.0  5.0
9   buzz3          c    6.0  5.0
10  buzz4          c    6.0  5.0
11  buzz5          c    6.0  5.0

一种解决方案是创建一个临时count,然后比较值。

vs = df.groupby("new_group").size().to_dict()
# vs =  {'a': 4, 'c': 5, 'b': 3}
df["count"] = df["new_group"].apply(lambda k: vs[k])
def comp(row):
if row["value"] is np.nan: return row["count"]
return min(row["value"], row["count"])
df["new_vals"] = df.apply(comp, 1)
# equivalent to 
# df = df[['value', 'count']].min(axis=1)

输出:

new_group  value  count  new_vals
0   a    2.0      4       2.0
1   a    2.0      4       2.0
2   a    2.0      4       2.0
3   a    2.0      4       2.0
4   b    NaN      3       3,0
5   b    NaN      3       3.0
6   b    NaN      3       3.0
7   c    6.0      5       5.0
8   c    6.0      5       5.0       
9   c    6.0      5       5.0
10  c    6.0      5       5.0
11  c    6.0      5       5.0

这里有一种方法可以做到这一点。这个想法是计算每个name_group的行数,并将其合并到数据帧中作为新列(称为count)。然后new_vals将仅计算为value列和count列的最小值:

df = df.merge(df.groupby('name_group').size().reset_index(name='count'), on='name_group')
df['new_vals'] = df[['value', 'count']].min(axis=1)

然后,如果需要,您可以使用df.drop(columns='count', inplace=True)删除count列,以给出:

name name_group  value  new_vals
0    foo1          a    2.0       2.0
1    foo2          a    2.0       2.0
2    foo3          a    2.0       2.0
3    foo4          a    2.0       2.0
4    bar1          b    NaN       3.0
5    bar2          b    NaN       3.0
6    bar3          b    NaN       3.0
7   buzz1          c    6.0       5.0
8   buzz2          c    6.0       5.0
9   buzz3          c    6.0       5.0
10  buzz4          c    6.0       5.0
11  buzz5          c    6.0       5.0

最新更新