使用自定义函数检查列中是否存在一组字符串
Checking if a set of strings exist in a column with a custom function
其他贡献者,
我想检查分组的 pandas DataFrame 上是否存在一组特定的关键字。我想检查的词是 start
、pending
和 finished
或 almost_finished
。我想为此定义一个自定义函数,apply
到 pandas groupby
,因为与我们解决的按行操作相比,定义一个应用于列的函数对我来说有点不清楚每一行都有 (row[colname])。
在此示例中,如果存在所需单词的序列,我希望将每个 ID
列 number
中的最后一个值复制到新列中,并且之前的其他值是否无关紧要空字符串。这是一个可重现的例子:
import pandas as pd
df = pd.DataFrame({'ID' : [1100, 1100, 1100, 1200, 1200, 1200, 1300, 1300],
'number' : ['Yes', 'No', 'No', 'Yes', 'No', 'No', 'Yes', 'No'],
'status' : ['start', 'pending', 'finished', 'start', 'pending', 'partially_finished', 'start', 'pending']})
在这种情况下,最后一组 ID == 1300
没有 return 值。
基本上我问这个问题是为了学习解决这些需要检查列中某些值的问题的最佳方法,因为我来自 R 我需要熟悉我在 [=26= 中做同样事情的方式].如果您提出任何更好的解决方案,我也将不胜感激。
非常感谢您。
df.groupby.apply 可能是您要查找的内容。您可以将一个函数应用于每个组和 return 单个值、系列或数据框。结果将汇总在一起。
例如以下函数
def return_last_num(df):
if df.status.str.contains('start').any() & df.status.str.contains('pending').any() \
& (df.status.str.contains('finished').any() | df.status.str.contains('partically_finished').any()):
df['last_number'] = df.number.values[-1]
else:
df['last_number'] = str()
return df
应用时:df.groupby('ID').apply(return_last_num)
.
Returns:
ID number status last_number
0 1100 Yes start No
1 1100 No pending No
2 1100 No finished No
3 1200 Yes start No
4 1200 No pending No
5 1200 No partially_finished No
6 1300 Yes start
7 1300 No pending
或者:return获取单个值
def return_last_num(df):
if df.status.str.contains('start').any() & df.status.str.contains('pending').any() \
& (df.status.str.contains('finished').any() | df.status.str.contains('partically_finished').any()):
return df.number.values[-1]
else:
return str()
应用时:df.groupby('ID').apply(return_last_num)
.
Returns 如果序列条件匹配,则只是每个 ID 的最后一个 'number' 值:
ID
1100 No
1200 No
1300
我在考虑矢量化方法。
首先,如果finished
和almost finished
具有相同的效果,我会将它们“合并”,并使它们成为一个易于检查的唯一编号:
>>> df['status2'] = df['status'].map({'finished':1,'partially_finished':1,'pending':10,'start':100})
>>> df
ID number status status2
0 1100 Yes start 100
1 1100 No pending 10
2 1100 No finished 1
3 1200 Yes start 100
4 1200 No pending 10
5 1200 No partially_finished 1
6 1300 Yes start 100
7 1300 No pending 10
这让我可以“提取”所需的状态 (100+10+1):
idstatus=df.groupby('ID', sort=False).sum('status2')==111
status2
ID
1100 True
1200 True
1300 False
以及实际数值:如果您不需要匹配 所有 个单词,那么仅此行就足够了。
valuenumber=df.query('status2==1').set_index('ID')
number status status2
ID
1100 No finished 1
1200 No partially_finished 1
最后合并:
idstatus.merge(valuenumber, left_index=True, right_index=True, how='left')
>>> idstatus.merge(valuenumber, left_index=True, right_index=True, how='left')
status2_x number status status2_y
ID
1100 True No finished 1.0
1200 True No partially_finished 1.0
1300 False NaN NaN NaN
可以合并较少的条目:
>>> idstatus.merge(valuenumber[['number','status']], left_index=True, right_index=True, how='left')
status2 number status
ID
1100 True No finished
1200 True No partially_finished
1300 False NaN NaN
>>>
编辑:
如果你只想得到成品的输出,你也可以用这个 oneliner 得到它:
>>> valuenumber.loc[idstatus[idstatus.status2].index][['number','status']]
number status
ID
1100 No finished
1200 No partially_finished
>>>
Edit2:刚刚进行了基准测试 有趣的是,相交解决方案更快,至少对于示例数据而言:
- groubpy-apply: 2.86 毫秒
- 聚合与合并:3.90 毫秒
- 矢量和位置:3.96 毫秒
- set-intersect:2.4 毫秒,尽管使用 lambda
Edit3: 昨天太沉了。使用Emma的“一个groupby中的两个操作”的想法,并假设项目是有序的:
df2['status'] = df2.status.map({'finished':1,'partially_finished':1,'pending':10,'start':100})
x = df2.groupby('ID').agg({'number': 'last', 'status': 'sum'})
x[x.status==111]
在 2.1 毫秒,它比带有 apply / lambda 的版本稍快
可以用set
聚合,用intersection
查。
但首先,我会将 partially_finished
或 almost_finished
映射到 finished
,如果它们应该被平等对待的话。
df['status'] = df.status.replace('partially_finished|almost_finished', 'finished', regex=True)
接下来,将 number
聚合到最后一个值,将 status
聚合到 set
,然后我使用 intersect
检查 status
中是否存在所有值.
checkcriteria = {'start', 'pending', 'finished'}
df = df.groupby('ID').agg({'number': 'last', 'status': set})
df['check'] = df.status.transform(lambda x: len(x.intersection(checkcriteria)) == 3)
这应该给出一个结果,
number status check
ID
1100 No {start, pending, finished} True
1200 No {start, pending, finished} True
1300 No {start, pending} False
您可以按 check
或 mask
过滤并删除 number
的值。
# This will only return ID == 1100, 1200
df[df.check]
# OR mask to remove the number value for when check == False
df.loc[~df.check, 'number'] = None
其他贡献者,
我想检查分组的 pandas DataFrame 上是否存在一组特定的关键字。我想检查的词是 start
、pending
和 finished
或 almost_finished
。我想为此定义一个自定义函数,apply
到 pandas groupby
,因为与我们解决的按行操作相比,定义一个应用于列的函数对我来说有点不清楚每一行都有 (row[colname])。
在此示例中,如果存在所需单词的序列,我希望将每个 ID
列 number
中的最后一个值复制到新列中,并且之前的其他值是否无关紧要空字符串。这是一个可重现的例子:
import pandas as pd
df = pd.DataFrame({'ID' : [1100, 1100, 1100, 1200, 1200, 1200, 1300, 1300],
'number' : ['Yes', 'No', 'No', 'Yes', 'No', 'No', 'Yes', 'No'],
'status' : ['start', 'pending', 'finished', 'start', 'pending', 'partially_finished', 'start', 'pending']})
在这种情况下,最后一组 ID == 1300
没有 return 值。
基本上我问这个问题是为了学习解决这些需要检查列中某些值的问题的最佳方法,因为我来自 R 我需要熟悉我在 [=26= 中做同样事情的方式].如果您提出任何更好的解决方案,我也将不胜感激。
非常感谢您。
df.groupby.apply 可能是您要查找的内容。您可以将一个函数应用于每个组和 return 单个值、系列或数据框。结果将汇总在一起。
例如以下函数
def return_last_num(df):
if df.status.str.contains('start').any() & df.status.str.contains('pending').any() \
& (df.status.str.contains('finished').any() | df.status.str.contains('partically_finished').any()):
df['last_number'] = df.number.values[-1]
else:
df['last_number'] = str()
return df
应用时:df.groupby('ID').apply(return_last_num)
.
Returns:
ID number status last_number
0 1100 Yes start No
1 1100 No pending No
2 1100 No finished No
3 1200 Yes start No
4 1200 No pending No
5 1200 No partially_finished No
6 1300 Yes start
7 1300 No pending
或者:return获取单个值
def return_last_num(df):
if df.status.str.contains('start').any() & df.status.str.contains('pending').any() \
& (df.status.str.contains('finished').any() | df.status.str.contains('partically_finished').any()):
return df.number.values[-1]
else:
return str()
应用时:df.groupby('ID').apply(return_last_num)
.
Returns 如果序列条件匹配,则只是每个 ID 的最后一个 'number' 值:
ID
1100 No
1200 No
1300
我在考虑矢量化方法。
首先,如果finished
和almost finished
具有相同的效果,我会将它们“合并”,并使它们成为一个易于检查的唯一编号:
>>> df['status2'] = df['status'].map({'finished':1,'partially_finished':1,'pending':10,'start':100})
>>> df
ID number status status2
0 1100 Yes start 100
1 1100 No pending 10
2 1100 No finished 1
3 1200 Yes start 100
4 1200 No pending 10
5 1200 No partially_finished 1
6 1300 Yes start 100
7 1300 No pending 10
这让我可以“提取”所需的状态 (100+10+1):
idstatus=df.groupby('ID', sort=False).sum('status2')==111
status2
ID
1100 True
1200 True
1300 False
以及实际数值:如果您不需要匹配 所有 个单词,那么仅此行就足够了。
valuenumber=df.query('status2==1').set_index('ID')
number status status2
ID
1100 No finished 1
1200 No partially_finished 1
最后合并:
idstatus.merge(valuenumber, left_index=True, right_index=True, how='left')
>>> idstatus.merge(valuenumber, left_index=True, right_index=True, how='left')
status2_x number status status2_y
ID
1100 True No finished 1.0
1200 True No partially_finished 1.0
1300 False NaN NaN NaN
可以合并较少的条目:
>>> idstatus.merge(valuenumber[['number','status']], left_index=True, right_index=True, how='left')
status2 number status
ID
1100 True No finished
1200 True No partially_finished
1300 False NaN NaN
>>>
编辑: 如果你只想得到成品的输出,你也可以用这个 oneliner 得到它:
>>> valuenumber.loc[idstatus[idstatus.status2].index][['number','status']]
number status
ID
1100 No finished
1200 No partially_finished
>>>
Edit2:刚刚进行了基准测试 有趣的是,相交解决方案更快,至少对于示例数据而言:
- groubpy-apply: 2.86 毫秒
- 聚合与合并:3.90 毫秒
- 矢量和位置:3.96 毫秒
- set-intersect:2.4 毫秒,尽管使用 lambda
Edit3: 昨天太沉了。使用Emma的“一个groupby中的两个操作”的想法,并假设项目是有序的:
df2['status'] = df2.status.map({'finished':1,'partially_finished':1,'pending':10,'start':100})
x = df2.groupby('ID').agg({'number': 'last', 'status': 'sum'})
x[x.status==111]
在 2.1 毫秒,它比带有 apply / lambda 的版本稍快
可以用set
聚合,用intersection
查。
但首先,我会将 partially_finished
或 almost_finished
映射到 finished
,如果它们应该被平等对待的话。
df['status'] = df.status.replace('partially_finished|almost_finished', 'finished', regex=True)
接下来,将 number
聚合到最后一个值,将 status
聚合到 set
,然后我使用 intersect
检查 status
中是否存在所有值.
checkcriteria = {'start', 'pending', 'finished'}
df = df.groupby('ID').agg({'number': 'last', 'status': set})
df['check'] = df.status.transform(lambda x: len(x.intersection(checkcriteria)) == 3)
这应该给出一个结果,
number status check
ID
1100 No {start, pending, finished} True
1200 No {start, pending, finished} True
1300 No {start, pending} False
您可以按 check
或 mask
过滤并删除 number
的值。
# This will only return ID == 1100, 1200
df[df.check]
# OR mask to remove the number value for when check == False
df.loc[~df.check, 'number'] = None