Pandas lambda内使用isin传字符串报错解决及索引匹配实现
问题说明
现有测试数据如下:
import pandas as pd import numpy as np Open_Time_s=['2022-04-30 11:05:00+03:00','2022-04-30 11:10:00+03:00', np.nan, np.nan] intersect=[np.nan,np.nan,'intersect_2022-04-30 11:05:00+03:00','intersect_2022-04-30 11:10:00+03:00'] df = pd.DataFrame.from_dict({'Open Time':Open_Time_s,'intersect':intersect}) df['LEVEL']=np.nan
需求为逐行处理intersect列,去除前缀intersect_后匹配Open Time列的对应值,将匹配到的行索引写入LEVEL列,预期输出如下:
Open Time intersect LEVEL 0 2022-04-30 11:05:00+03:00 NaN NaN 1 2022-04-30 11:10:00+03:00 NaN NaN 2 NaN intersect_2022-04-30 11:05:00+03:00 0.0 3 NaN intersect_2022-04-30 11:10:00+03:00 1.0
初始尝试代码:
df['LEVEL']=df['intersect'].loc[df['intersect'].isna()==0].apply(lambda x: df[df['Open Time'].isin(x.replace(r'intersect_', ''))].index)
触发报错only list-like objects are allowed to be passed to isin(), you passed a [str],原因是isin()方法仅接受可迭代的类列表对象作为入参,逐行apply时传入的是单个字符串,不符合参数要求。
解决方案
方法1:lambda逐行实现(符合要求)
逐行处理时不需要使用isin(),直接做等值判断即可。提前构建时间值到索引的映射字典可以避免每次apply都遍历全表,性能更好:
# 构建Open Time列非空值到行索引的映射 time_to_idx = {time: idx for idx, time in df['Open Time'].dropna().items()} df['LEVEL'] = df['intersect'].apply( lambda val: time_to_idx.get(val.removeprefix('intersect_')) if pd.notna(val) else np.nan )
如果不想提前构建映射,也可以直接在lambda中写匹配逻辑,仅适合小数据量场景:
df['LEVEL'] = df['intersect'].apply( lambda val: df.index[df['Open Time'] == val.removeprefix('intersect_')][0] if pd.notna(val) and (df['Open Time'] == val.removeprefix('intersect_')).any() else np.nan )
注:Python 3.9+、Pandas 1.4.0+支持
removeprefix()方法专门用于去除固定前缀,比replace更精准,不会误替换字符串中间的相同内容;低版本可以替换为val.replace('intersect_', '', 1),指定只替换1次匹配内容。
方法2:向量化实现(性能最优)
如果不强制要求使用lambda,优先选择pandas向量化操作,比逐行apply性能高3~10倍:
extracted_time = df['intersect'].str.removeprefix('intersect_') time_to_idx = {time: idx for idx, time in df['Open Time'].dropna().items()} df['LEVEL'] = extracted_time.map(time_to_idx)
运行上述任意一段代码,都可以得到预期的输出结果。
内容的提问来源于stack exchange,提问作者максим ильин
相关产品推荐
相关产品推荐

