Pandas数据筛选实战:isin、query、contains、loc、iloc核心用法详解

📅 2026/8/3 20:32:43
Pandas数据筛选实战:isin、query、contains、loc、iloc核心用法详解
1. 项目概述数据筛选的“瑞士军刀”在数据分析的日常工作中我们面对的数据集往往不是“纯净”的里面混杂着大量无关或需要特别关注的行列。想象一下你手头有一份包含全国所有门店销售记录的Excel表格老板突然让你“只看北京、上海、广州这三个城市并且只关心上个月销售额超过10万的记录”。如果你还在手动滚动、肉眼筛选那效率就太低了。这时Python的Pandas库就成了你的得力助手而isin、query、contains、loc、iloc这几个方法就是Pandas工具箱里最锋利、最常用的几把“数据筛选手术刀”。我处理过成千上万个数据集从简单的客户名单到复杂的时序交易数据深刻体会到高效的数据筛选是数据清洗、特征工程和初步分析的基础直接决定了后续所有工作的效率和准确性。很多新手朋友拿到数据后常常用for循环去遍历不仅代码冗长速度也慢得令人发指。而一旦掌握了这几个核心的筛选方法你就能像使用数据库的SQL语句一样对DataFrame进行精准、快速的切片和切块。简单来说这几个工具各有分工isin和contains主要用于基于值的“成员资格”或“模式匹配”筛选。比如“城市是否在某个列表里”、“产品名称是否包含某个关键词”。query提供了一个类似SQL的字符串表达式接口让你能用更接近自然语言的方式编写复杂的多条件筛选逻辑代码非常简洁。loc和iloc这是Pandas数据访问的基石。loc通过“标签”label如索引名、列名来定位iloc通过“整数位置”integer location来定位。它们不仅能用于筛选行、列更是赋值、修改数据的核心入口。理解并熟练运用这五者你就能应对90%以上的数据行/列筛选场景。接下来我将结合大量实际案例为你深入拆解它们的工作原理、适用场景、性能差异以及那些官方文档里不会写的“避坑指南”。2. 核心方法深度解析与选型指南面对一个筛选需求选择哪个方法最合适这取决于你的筛选条件类型、代码可读性要求以及对性能的考量。我们不能只会用锤子就把所有问题都看成钉子。2.1isin精准的“白名单”筛选器isin方法的核心思想是判断Series或DataFrame中的每个元素是否存在于一个给定的可迭代对象如列表、元组、Series中。它返回一个布尔序列True/False这个序列可以直接用于索引操作。典型场景当你需要筛选出某个字段值属于一个已知集合的记录时。例如筛选出特定几个省份的数据、特定产品ID的记录、或者状态为“已完成”或“已取消”的订单。基本语法与示例import pandas as pd # 示例数据 df pd.DataFrame({ 城市: [北京, 上海, 广州, 深圳, 杭州, 成都], 销售额: [150, 200, 180, 220, 90, 120] }) # 定义目标城市列表 target_cities [北京, 广州, 成都] # 使用 isin 进行筛选 filtered_df df[df[城市].isin(target_cities)] print(filtered_df)输出城市 销售额 0 北京 150 2 广州 180 5 成都 120高级用法与避坑多列isin筛选如果你想对多列应用同一个条件集合或者对不同列应用不同集合需要结合其他方法。例如筛选“城市”在列表A且“品类”在列表B的记录通常使用运算符连接两个isin条件。condition df[城市].isin([北京,上海]) df[品类].isin([电子产品,图书]) result df[condition]~操作符取反~是位取反运算符在布尔序列中表示“非”。如果你想筛选不在某个列表里的数据这是最简洁的方式。other_cities_df df[~df[城市].isin(target_cities)] # 将得到深圳和杭州的数据性能陷阱当isin的查询列表非常大时例如超过数万条其性能会线性下降。对于超大型的“白名单”筛选可以考虑将数据转为set进行集合运算或者使用merge连接有时效率更高。与NaN值的交互NaNNot a Number在isin判断中永远返回False即使查询列表里有NaN也一样。因为NaN ! NaN。如果你的数据包含缺失值且需要特殊处理这一点要特别注意。2.2contains与str访问器灵活的文本模式匹配contains是Pandas字符串方法通过.str访问器调用中的一个用于检测序列中每个字符串是否包含指定的子串或匹配正则表达式。它同样返回一个布尔序列。典型场景模糊搜索、关键词过滤、日志分析中提取包含特定错误码的行。例如从商品描述中找出所有含有“限量版”字样的商品或者从日志文件中筛选出所有“ERROR”级别的记录。基本语法与示例df_log pd.DataFrame({ 日志信息: [INFO: User login successful, ERROR: Database connection failed, WARN: Disk space low, ERROR: Invalid input parameter] }) # 筛选包含“ERROR”的日志 error_logs df_log[df_log[日志信息].str.contains(ERROR)] print(error_logs)输出日志信息 1 ERROR: Database connection failed 3 ERROR: Invalid input parameter核心要点与避坑正则表达式威力contains的regex参数默认为True这意味着你可以使用强大的正则表达式进行模式匹配。例如str.contains(r‘\d{3}-\d{4}’)可以匹配电话号码模式。# 匹配以“ERR”或“WARN”开头的日志 critical_logs df_log[df_log[‘日志信息’].str.contains(r‘^(ERR|WARN)’, regexTrue)]大小写敏感默认情况下contains是大小写敏感的。如果你需要进行不区分大小写的匹配可以使用caseFalse参数或者将字符串统一转为小写再比较df[‘col’].str.lower().str.contains(‘error’)。处理非字符串和缺失值如果列中存在非字符串类型如数字或NaN直接调用.str.contains会抛出AttributeError或返回NaN。安全的做法是先确保数据类型或使用naFalse参数将缺失值视为False。# 安全的用法忽略NaN不匹配则返回False result df[‘可能非字符串列’].astype(str).str.contains(‘目标’, naFalse)性能考量对于海量文本数据的模糊匹配contains特别是使用复杂正则时可能成为性能瓶颈。如果条件固定可以考虑先将字符串特征向量化如TF-IDF再进行匹配或者使用专门的文本搜索库。2.3query优雅的表达式筛选query方法允许你使用一个字符串表达式来查询DataFrame。这个表达式会在DataFrame的命名空间中进行求值让你可以用列名直接作为变量进行运算语法非常直观。典型场景当筛选条件比较复杂涉及多个列的比较和逻辑运算时使用query可以极大提升代码的可读性。它就像在DataFrame上写一行简化的SQLWHERE子句。基本语法与示例df_sales pd.DataFrame({ ‘月份’: [‘Jan’, ‘Jan’, ‘Feb’, ‘Feb’, ‘Mar’], ‘产品’: [‘A’, ‘B’, ‘A’, ‘B’, ‘A’], ‘销售额’: [1000, 1500, 1200, 800, 2000], ‘成本’: [600, 900, 700, 500, 1100] }) # 计算利润 df_sales[‘利润’] df_sales[‘销售额’] - df_sales[‘成本’] # 使用query进行复杂筛选一月或二月且利润高于400的产品A记录 complex_filtered df_sales.query(“(月份 in [‘Jan’, ‘Feb’]) and 产品 ‘A’ and 利润 400”) print(complex_filtered)输出月份 产品 销售额 成本 利润 0 Jan A 1000 600 400 2 Feb A 1200 700 500注意第一行利润等于400不满足400所以没有被选中。这展示了query表达式计算的精确性。强大功能与注意事项引用外部变量你可以在查询字符串中使用符号来引用当前作用域中的Python变量这使得查询非常动态。min_profit 500 target_product ‘B’ result df_sales.query(“产品 target_product and 利润 min_profit”)列名中的特殊字符如果列名包含空格或运算符如col name在query中需要用反引号将其括起来。df.query(“col name 10”)性能与引擎query在底层会将表达式解析并计算。对于大型DataFrame它通常比直接使用布尔索引如df[(df.A 1) (df.B 5)]更快因为Pandas可以利用numexpr库进行优化通过设置engine‘numexpr’这是默认值。但对于非常简单的一次性条件其解析开销可能使它与直接布尔索引相差无几。与isin的融合在query表达式中可以直接使用Python的in关键字来实现isin的功能语法更自然。df.query(“城市 in [‘北京’, ‘上海’, ‘广州’]”)2.4loc与iloc数据访问的“坐标系统”这是两个最基础、最核心的数据选择方法。它们的区别是选择依据的根本不同这个区别必须刻在脑子里。loc: 基于标签label进行选择。这里的“标签”包括行索引的索引名和列的列名。它的切片是闭区间的。iloc: 基于整数位置integer location进行选择。也就是我们常说的第几行、第几列从0开始计数。它的切片是前闭后开区间与Python列表和NumPy数组一致。基本语法对比# 创建一个带自定义索引的DataFrame df pd.DataFrame({‘A’: [1,2,3,4], ‘B’: [5,6,7,8], ‘C’: [9,10,11,12]}, index[‘a’, ‘b’, ‘c’, ‘d’]) # loc 通过标签访问 print(df.loc[‘b’, ‘A’]) # 输出: 2 (索引为’b’列名为’A’的单元格) print(df.loc[‘a’:‘c’, [‘A’, ‘C’]]) # 输出第’a’到’c’行包含’c’以及’A’,’C’列的数据 # iloc 通过位置访问 print(df.iloc[1, 0]) # 输出: 2 (第1行第0列即’b’, ‘A’) print(df.iloc[0:3, [0, 2]]) # 输出第0到第2行不包含第3行以及第0和第2列的数据在条件筛选中的核心作用loc和iloc本身不直接进行复杂的条件判断但它们是与条件判断结果布尔序列结合最终取出数据的“执行者”。df[condition]这种布尔索引的写法实际上是df.loc[condition]的语法糖。更强大的筛选与赋值loc的真正威力在于它能同时接受行选择器和列选择器并进行原地赋值这是数据清洗中修改特定区域数据的标准做法。# 将‘城市’为‘上海’的记录的‘销售额’统一增加100 df.loc[df[‘城市’] ‘上海’, ‘销售额’] 100 # 同时修改多列 df.loc[df[‘利润’] 0, [‘状态’, ‘需要审核’]] [‘异常’, True]关键避坑点切片区间差异这是最容易出错的地方。loc[‘a’:‘c’]会包含索引’a’, ‘b’, ‘c’对应的所有行。而iloc[0:3]只包含第0, 1, 2行不包含第3行。务必牢记loc闭区间iloc前闭后开。整数索引的歧义如果你的DataFrame的索引是整数0,1,2…那么df.loc[1]和df.iloc[1]返回的结果可能完全不同loc[1]是选择索引标签为1的那一行而iloc[1]是选择位置顺序为1即第二行的那一行。如果索引不是连续的或不是从0开始两者结果天差地别。df2 pd.DataFrame({‘value’: [10,20,30]}, index[100, 101, 102]) print(df2.loc[101]) # 输出: 20 (索引标签为101的行) print(df2.iloc[1]) # 输出: 20 (位置为1的行巧合的是索引也是101) # 但如果索引是[100, 200, 300]df2.loc[1]会报KeyError而df2.iloc[1]会返回索引200对应的值。chained assignment问题避免使用df[‘A’][df[‘B’]0] 1这种链式赋值因为它可能无法修改原始数据或引发SettingWithCopyWarning警告。正确的做法是使用df.loc[df[‘B’]0, ‘A’] 1。3. 综合实战从复杂需求到代码实现理论讲得再多不如一个真实的案例来得透彻。假设我们拿到了一份电商平台的订单数据orders_df包含以下字段order_id订单IDcustomer_id客户IDcity城市product_category产品类别amount订单金额status状态order_date订单日期。现在我们需要完成一系列筛选任务任务1找出来自“北京”、“上海”、“广州”、“深圳”这四个一线城市且订单状态为“已发货”或“已完成”的所有订单。这是一个典型的isin多条件组合任务。# 定义条件集合 target_cities [‘北京’, ‘上海’, ‘广州’, ‘深圳’] target_statuses [‘已发货’, ‘已完成’] # 构建布尔条件 condition (orders_df[‘city’].isin(target_cities)) (orders_df[‘status’].isin(target_statuses)) # 使用loc执行筛选布尔索引的完整形式 result_task1 orders_df.loc[condition] # 等价于 orders_df[condition]思考这里为什么用而不是and因为是Pandas Series的按位与运算符用于逐元素比较两个布尔序列。而and是Python的关键字用于标量布尔值的判断直接用在Series上会引发歧义错误。任务2筛选出产品类别名称中包含“手机”或“电脑”关键词且订单金额大于5000元的订单。这里需要文本模糊匹配和数值比较相结合。contains支持正则表达式的“或”操作。# 使用正则表达式的“或” | 来匹配多个关键词 # naFalse 确保NaN值不会导致错误而是被视为False pattern r‘手机|电脑’ # 正则表达式匹配包含“手机”或“电脑”的字符串 category_condition orders_df[‘product_category’].str.contains(pattern, naFalse) amount_condition orders_df[‘amount’] 5000 result_task2 orders_df.loc[category_condition amount_condition]任务3使用query方法找出2023年第二季度4月-6月客户ID为10001、10002、10005并且订单金额介于1000到5000元之间的所有订单。这个需求条件维度多用query写出来会非常清晰。# 首先确保order_date是datetime类型 orders_df[‘order_date’] pd.to_datetime(orders_df[‘order_date’]) # 使用query。注意引用外部变量和日期处理。 customer_list [10001, 10002, 10005] start_date ‘2023-04-01’ end_date ‘2023-06-30’ result_task3 orders_df.query( “order_date start_date and order_date end_date and “ “customer_id in customer_list and “ “amount 1000 and amount 5000” )可以看到query表达式几乎就是需求描述的文字翻译可读性极佳。任务4将任务3中筛选出的数据其“状态”统一更新为“重点客户订单”并新增一列“标记”为“Q2重点”。这里就需要loc在筛选的同时进行赋值操作。# 首先获取任务3的布尔条件复用query的逻辑或直接使用result_task3的索引 # 这里我们直接基于条件构造布尔序列更高效 condition_q2 ( (orders_df[‘order_date’] start_date) (orders_df[‘order_date’] end_date) (orders_df[‘customer_id’].isin(customer_list)) (orders_df[‘amount’] 1000) (orders_df[‘amount’] 5000) ) # 使用loc进行批量赋值 orders_df.loc[condition_q2, ‘status’] ‘重点客户订单’ orders_df.loc[condition_q2, ‘标记’] ‘Q2重点’ # 新增列这个操作是原地修改orders_df高效且精准避免了循环。4. 性能对比与最佳实践选择在实际工作中尤其是处理百万、千万级别的大数据时筛选方法的性能差异会被放大。我做过一些简单的基准测试可以给大家一个直观的感受数据量越大趋势越明显。假设我们有一个100万行、10列的DataFramedf_large我们要筛选出col1值在某个包含1万个元素的列表large_list中的行。isinvsquery(within): 对于这种简单的成员检查df_large[df_large[‘col1’].isin(large_list)]通常会略快于df_large.query(‘col1 in large_list’)因为query有一个表达式解析的开销。但在条件非常复杂时query利用numexpr优化后的多条件计算可能反超。布尔索引 (loc) vsquery: 对于简单的比较如df[‘col2’] 100直接布尔索引df[df[‘col2’] 100]或df.loc[df[‘col2’] 100]通常是最快的。query在表达式复杂时其可读性优势远大于微小的性能损失。contains(regexTrue): 这是相对较慢的操作特别是正则表达式很复杂时。如果只是简单的固定字符串匹配设置regexFalse能提升性能。对于海量文本的重复匹配考虑是否可以先提取关键词或使用更高效的文本搜索工具。最佳实践总结简单精确匹配用isin当筛选条件是一个明确的集合时isin是语义最清晰、性能良好的选择。复杂逻辑组合用query当筛选条件涉及多个列、多种运算加减乘除、函数调用时query能提供无与伦比的可读性和维护性。在团队协作中这能极大降低沟通成本。文本模糊搜索用contains结合.str访问器它是处理字符串模式匹配的首选。记住处理好大小写和缺失值。一切筛选的终点和赋值操作用locloc是你的手术刀和画笔。任何基于条件的筛选结果最终都要通过loc或它的语法糖来取出数据或修改数据。对于基于整数位置的快速选择用iloc。优先使用向量化操作无论用哪个方法其底层都是向量化计算。绝对避免使用for循环遍历DataFrame的行来进行条件判断这在Pandas中是性能的“毒药”。在关键路径上考虑性能如果你的筛选操作在一个需要每秒执行成千上万次的循环或函数中那么有必要对不同的筛选方法做一个小规模的性能测试用%timeit魔法命令。5. 常见问题排查与技巧实录即使掌握了方法在实际编码中还是会遇到各种“坑”。下面是我总结的一些高频问题和解决技巧。问题1使用query时报错“name ‘xxx’ is not defined”。原因在query表达式中使用了变量但忘记加符号或者变量名确实未定义。解决检查表达式中的所有外部变量确保它们都以开头。例如df.query(‘col threshold’)。技巧对于字符串变量尤其要注意。df.query(“city ‘北京’”)是硬编码df.query(“city city_name”)才是引用变量city_name。问题2contains方法报错AttributeError: Can only use .str accessor with string values!原因调用.str.contains的列Series中并非所有元素都是字符串类型可能混入了整数、浮点数或NaN。解决最安全的方式是先用astype(str)强制转换整个列为字符串类型或者使用naFalse参数并确保非字符串值已被适当处理。# 方法1强制转换但所有值都会变成字符串 condition df[‘col’].astype(str).str.contains(‘abc’, naFalse) # 方法2仅对非NaN的字符串值操作NaN和其他类型返回False condition df[‘col’].apply(lambda x: isinstance(x, str) and ‘abc’ in x)问题3使用loc进行赋值时出现SettingWithCopyWarning警告。原因你正在操作一个可能是原始DataFrame切片副本的视图view在此视图上的赋值可能不会反映到原始数据中行为不确定。解决这是Pandas的核心陷阱之一。确保你的赋值操作直接作用于原始DataFrame或一个明确的副本。正确做法df.loc[mask, ‘column’] value直接操作原始df如果需要链式操作明确使用.copy()创建副本。df_subset df[condition].copy()然后放心修改df_subset。根本原则当你需要修改数据时尽量使用df.loc[row_indexer, col_indexer] value这种单一索引赋值语法。问题4筛选后数据为空但明明感觉条件应该能匹配上。排查步骤检查数据类型这是最常见的原因。比如你用一个字符串列表[‘1001’, ‘1002’]去isin筛选一个整数类型的列结果肯定是空的。用df[‘col’].dtype查看类型必要时用astype转换。检查空格和大小写文本数据经常首尾带有空格或者大小写不一致。使用.str.strip()去除空格用.str.lower()统一小写后再比较。打印中间布尔序列将你的筛选条件赋值给一个变量mask然后print(mask.head())或print(mask.value_counts())看看有多少True。可能条件比你想象的要严格。简化条件测试将复杂条件拆开逐个测试定位是哪个子条件出了问题。问题5在大型DataFrame上连续进行多次复杂筛选速度很慢。优化思路减少不必要的数据复制尽量使用布尔索引原地筛选而不是df df[condition1]; df df[condition2]这样链式赋值产生多个中间副本。可以合并条件一次筛选。考虑使用np.where或np.select进行条件赋值对于基于多个条件创建新列NumPy的函数通常比Pandas的逐元素操作更快。使用pd.eval()进行复杂表达式求值对于超大型DataFrame的数值运算pd.eval()可以带来性能提升。终极武器索引如果你的筛选总是基于某几列如user_id,date为这些列设置索引df.set_index([‘user_id’, ‘date’], inplaceTrue)然后使用df.loc[(specific_user, specific_date), :]速度会有数量级的提升。但这属于更高级的索引优化技术。掌握这五种筛选方法并理解它们背后的原理和适用场景你就能在数据处理的战场上从容不迫。记住没有最好的工具只有最合适的工具。多练习多思考多踩坑你就能形成自己的肌肉记忆和条件反射面对任何数据筛选需求都能快速找到最优解。