在包含已排序组的 pandas 数据框中创建一个计算列
Create a calculated column in pandas data frame containing sorted groups
假设有一个 pandas 数据框,其行包含一些已排序的分组数据(给定名称的所有值组彼此相邻出现),我们想介绍一个新的计算列,它根据某些列的值分配值。如果第一个值为零,则一个组的所有值都将获得第一个非零值或 nan,如果没有这样的值。否则,如果第一个值非零,则分配一个固定值,例如 -1
.
示例输入数据框:
name value
0 a 0
1 a 0
2 a 6
3 a 8
4 b 0
5 b 0
6 c 5
7 c 7
创建了 calc
列的示例输出数据框。
name value calc
0 a 0 6
1 a 0 6
2 a 6 6
3 a 8 6
4 b 0 nan
5 b 0 nan
6 c 5 -1
7 c 7 -1
我考虑的方法是创建每个组的第一个非零值的查找 table,因此对于上面的示例,它将是:
value
a 6
c 5
然后迭代输入数据框并按照上述逻辑构造值列表,然后将其分配给新列。
为了更好的性能最好不要使用 groupby
,最好是创建最终字典和 map
:
#get all names with 0
contains_zeros = df.loc[df['value'] == 0, 'name'].unique()
print (contains_zeros)
['a' 'b']
#get first non zero values only names with 0
s = df[df['name'].isin(contains_zeros) & (df['value'] != 0)].drop_duplicates('name')
print (s)
name value
2 a 6
#first non zero dictionary
d1 = s.set_index('name')['value'].to_dict()
print (d1)
{'a': 6}
#dictionary with all 0 in name
d2 = dict.fromkeys(set(contains_zeros) - set(s['name']), np.nan)
print (d2)
{'b': nan}
#all dictionary without 0
d3 = dict.fromkeys(set(df['name'].unique()) - set(contains_zeros), -1)
print (d3)
{'c': -1}
#merge all together
#
d = {**d1, **d2, **d3}
print (d)
{'a': 6, 'b': nan, 'c': -1}
df['calc'] = df['name'].map(d)
print (df)
name value calc
0 a 0 6.0
1 a 0 6.0
2 a 6 6.0
3 a 8 6.0
4 b 0 NaN
5 b 0 NaN
6 c 5 -1.0
7 c 7 -1.0
另一个较慢的解决方案 groupby
:
def f(x):
if (x== 0).all():
return np.nan
elif (x == 0).any():
return x[x != 0].iloc[0]
else:
return -1
df['calc'] = df.groupby('name')['value'].transform(f)
print (df)
name value calc
0 a 0 6.0
1 a 0 6.0
2 a 6 6.0
3 a 8 6.0
4 b 0 NaN
5 b 0 NaN
6 c 5 -1.0
7 c 7 -1.0
这是使用 groupby.transform
的一种方式。函数 return_val
中的替代项直接反映了您指定的 3 个条件,并且很容易扩展到更多条件。
def return_val(x):
vals = x.values
if 0 not in vals:
return -1
else:
return next((i for i in vals if i!=0), np.nan)
df['calc'] = df.groupby('name')['value'].transform(return_val)
print(df)
name value calc
0 a 0 6.0
1 a 0 6.0
2 a 6 6.0
3 a 8 6.0
4 b 0 NaN
5 b 0 NaN
6 c 5 -1.0
7 c 7 -1.0
假设有一个 pandas 数据框,其行包含一些已排序的分组数据(给定名称的所有值组彼此相邻出现),我们想介绍一个新的计算列,它根据某些列的值分配值。如果第一个值为零,则一个组的所有值都将获得第一个非零值或 nan,如果没有这样的值。否则,如果第一个值非零,则分配一个固定值,例如 -1
.
示例输入数据框:
name value
0 a 0
1 a 0
2 a 6
3 a 8
4 b 0
5 b 0
6 c 5
7 c 7
创建了 calc
列的示例输出数据框。
name value calc
0 a 0 6
1 a 0 6
2 a 6 6
3 a 8 6
4 b 0 nan
5 b 0 nan
6 c 5 -1
7 c 7 -1
我考虑的方法是创建每个组的第一个非零值的查找 table,因此对于上面的示例,它将是:
value
a 6
c 5
然后迭代输入数据框并按照上述逻辑构造值列表,然后将其分配给新列。
为了更好的性能最好不要使用 groupby
,最好是创建最终字典和 map
:
#get all names with 0
contains_zeros = df.loc[df['value'] == 0, 'name'].unique()
print (contains_zeros)
['a' 'b']
#get first non zero values only names with 0
s = df[df['name'].isin(contains_zeros) & (df['value'] != 0)].drop_duplicates('name')
print (s)
name value
2 a 6
#first non zero dictionary
d1 = s.set_index('name')['value'].to_dict()
print (d1)
{'a': 6}
#dictionary with all 0 in name
d2 = dict.fromkeys(set(contains_zeros) - set(s['name']), np.nan)
print (d2)
{'b': nan}
#all dictionary without 0
d3 = dict.fromkeys(set(df['name'].unique()) - set(contains_zeros), -1)
print (d3)
{'c': -1}
#merge all together
#
d = {**d1, **d2, **d3}
print (d)
{'a': 6, 'b': nan, 'c': -1}
df['calc'] = df['name'].map(d)
print (df)
name value calc
0 a 0 6.0
1 a 0 6.0
2 a 6 6.0
3 a 8 6.0
4 b 0 NaN
5 b 0 NaN
6 c 5 -1.0
7 c 7 -1.0
另一个较慢的解决方案 groupby
:
def f(x):
if (x== 0).all():
return np.nan
elif (x == 0).any():
return x[x != 0].iloc[0]
else:
return -1
df['calc'] = df.groupby('name')['value'].transform(f)
print (df)
name value calc
0 a 0 6.0
1 a 0 6.0
2 a 6 6.0
3 a 8 6.0
4 b 0 NaN
5 b 0 NaN
6 c 5 -1.0
7 c 7 -1.0
这是使用 groupby.transform
的一种方式。函数 return_val
中的替代项直接反映了您指定的 3 个条件,并且很容易扩展到更多条件。
def return_val(x):
vals = x.values
if 0 not in vals:
return -1
else:
return next((i for i in vals if i!=0), np.nan)
df['calc'] = df.groupby('name')['value'].transform(return_val)
print(df)
name value calc
0 a 0 6.0
1 a 0 6.0
2 a 6 6.0
3 a 8 6.0
4 b 0 NaN
5 b 0 NaN
6 c 5 -1.0
7 c 7 -1.0