尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Pandas列筛选全攻略:从基础索引到高级查询与性能优化

Pandas列筛选全攻略:从基础索引到高级查询与性能优化 1. 项目概述为什么列数据筛选是数据分析的“命门”干了这么多年数据分析我越来越觉得数据处理的核心就两件事一是把数据“拿进来”二是把数据“挑出来”。而“挑出来”这个环节尤其是针对DataFrame里特定列的数据筛选绝对是日常工作中最高频、最考验基本功的操作。你想想面对一个几十列、几万行的数据集老板让你“看看最近三个月A产品在华东区的销售明细”或者“把用户年龄在25-35岁、且消费金额大于500的记录找出来”你不会列筛选难道要手动一行行看吗Pandas作为Python数据分析的“瑞士军刀”提供了至少五六种不同的列筛选方法。新手最容易犯的错就是只知道df[‘column_name’]这一种结果遇到复杂条件就抓瞎或者写出来的代码又慢又难读。更头疼的是不同方法返回的结果类型可能不一样——有的是Series有的是DataFrame一个不小心就在后续操作里报AttributeError。我见过不少同事因为没搞清loc、iloc和直接索引的区别在数据拼接或函数应用时踩坑debug半天才发现是筛选时“丢”了DataFrame结构。所以今天我不讲什么高深算法就扎扎实实地把Pandas里列数据筛选的几种主流方法掰开揉碎了讲清楚。我会从最简单的单列提取讲到多列组合、条件过滤、以及高性能的query方法重点不止是“怎么用”更是“什么时候该用哪种”以及背后那些容易栽跟头的地方。无论你是刚入门正在啃pandas.read_csv的新手还是已经会用groupby但想优化代码的老手这篇汇总都能帮你把这块基石打得更牢。2. 核心方法全解析从基础索引到高级查询列筛选看似简单但方法多样各有各的适用场景和“脾气”。掌握它们就像木匠熟悉不同的刨子和凿子面对不同的木料数据和工艺要求分析需求能选出最顺手、最高效的那一把。2.1 基础单列与多列提取方括号[]的学问这是所有人接触Pandas学到的第一个筛选方法简单直接但细节不少。单列提取返回Series当你用单个列名字符串放入方括号Pandas返回的是一个Series对象。你可以把它理解为一维数组带了个索引标签。import pandas as pd df pd.DataFrame({‘A’: [1, 2, 3], ‘B’: [4, 5, 6], ‘C’: [7, 8, 9]}) s df[‘A’] # 提取列A print(type(s)) # 输出class ‘pandas.core.series.Series’ print(s.values) # 输出[1 2 3]这里有个关键点单列索引返回的是Series不是DataFrame。这意味着你不能再对s使用.columns属性因为它没有“列”的概念只有name本例中为’A’和index。如果你需要保持DataFrame结构比如为了后续的merge或保持统一的列操作接口需要使用双括号[[‘A’]]。多列提取返回DataFrame当你在方括号里传入一个列名的列表时Pandas会返回一个只包含这些列的新DataFrame并且列的顺序与你指定的列表顺序一致。df_subset df[[‘C’, ‘A’]] # 提取C列和A列并保持C在前A在后的顺序 print(type(df_subset)) # 输出class ‘pandas.core.frame.DataFrame’ print(df_subset.columns.tolist()) # 输出[‘C’, ‘A’]这是重新排列列顺序最常用的方法。比如原始数据列是[‘日期’ ‘销量’ ‘成本’]你想把‘成本’放到‘销量’前面一句df[[‘日期’ ‘成本’ ‘销量’]]就搞定了。注意方括号索引不能直接进行行条件筛选。df[df[‘A’] 1]这种写法是成立的但它的原理是先进行布尔索引行筛选而不是列筛选。df[[‘A’ 1]]这种写法则会直接报错因为方括号里的列表期望是列名而不是布尔表达式。2.2 标签与位置索引器loc与iloc的精准定位如果说方括号[]是“粗筛”那么loc和iloc就是“精雕”。它们都用于同时筛选行和列但依据的“坐标”不同。loc基于标签label的索引loc的输入是行标签和列标签。行标签默认是0,1,2…的索引但也可以是你在read_csv时设置的某一列如index_col‘user_id’。列标签就是列名。语法df.loc[行选择器 列选择器]列选择器可以是单个标签、标签列表、标签切片‘start_col’:‘end_col’甚至是布尔数组。# 选择特定的行和列 print(df.loc[0, ‘A’]) # 输出1 (标量) print(df.loc[[0, 2], [‘A’, ‘C’]]) # 输出一个2行2列的DataFrame # 使用切片选择连续的列 print(df.loc[:, ‘A’:‘C’]) # 选择A到C所有列包含A和C # 使用布尔数组选择列较少用但很强大 bool_mask [True, False, True] # 选择第一列和第三列 print(df.loc[:, bool_mask])loc的切片是包含末端的这与Python原生的列表切片不同需要特别注意。df.loc[:, ‘A’:‘C’]会选出A、B、C三列。iloc基于整数位置integer position的索引iloc只认数字从0开始计数。它不管你的行索引或列名具体是什么只按物理位置来。语法df.iloc[行位置选择器 列位置选择器]列选择器可以是整数、整数列表、整数切片。# 选择第一行第一列 print(df.iloc[0, 0]) # 输出1 # 选择前两行第0列和第2列 print(df.iloc[:2, [0, 2]]) # 选择所有行第1列到最后一列不包含末端 print(df.iloc[:, 1:]) # 注意iloc切片是Python风格不包含末端iloc的切片是不包含末端的和Python列表切片行为一致。df.iloc[:, 1:3]选取的是第1列和第2列因为3不被包含。locvsiloc核心抉择用loc当你的筛选逻辑是基于有业务意义的“标签”如日期‘2023-01-01’ 用户ID‘U1001’ 产品名‘Product_A’。代码意图更清晰即使数据行顺序被打乱也能准确取到目标。用iloc当你只关心数据的物理位置如前100行倒数5行每隔10行取一次。在处理顺序固定的数据或进行数学计算时更直接。但要注意如果数据行被排序或删减过位置可能已经变化iloc的结果就可能不符合预期。2.3 属性访问与点符号便捷但有限制的途径你可能会看到有人这样写df.A来访问列A。这叫做属性访问attribute access确实非常简洁。print(df.A) # 等价于 df[‘A’]但是这种方法有严格的限制列名必须是有效的Python变量名。这意味着列名不能以数字开头不能包含空格或运算符如-*。像‘2023 Sales’或‘col-name’这样的列名就无法使用点符号。列名不能与DataFrame的方法或属性名冲突。例如如果你的列名恰好是‘sum’或‘shape’那么df.sum将指向DataFrame的求和方法而不是你的数据列这会导致难以察觉的错误。只能用于单列无法进行多列选择或条件筛选。因此我的建议是仅在交互式环境如Jupyter Notebook中快速查看某一列数据时使用点符号。在任何正式的脚本或生产代码中一律使用方括号[]以避免潜在的命名冲突和语法错误。2.4 高性能条件筛选query方法详解当筛选条件变得复杂比如涉及多个列的与或非组合时传统的布尔索引写法会变得冗长且需要反复引用DataFrame本身。query方法提供了一种更优雅、有时也更高效的解决方案。传统布尔索引 vsquery# 传统写法 condition (df[‘A’] 1) (df[‘B’] 6) | (df[‘C’] 9) result df[condition] # 或者更冗长的内联写法 result df[(df[‘A’] 1) (df[‘B’] 6) | (df[‘C’] 9)] # 使用query写法 result df.query(‘A 1 and B 6 or C 9’)query方法的优势立刻显现语法简洁像写SQL的WHERE子句一样直观避免了重复书写df[]。可读性高复杂的逻辑条件一目了然。性能优势对于大型DataFramequery在底层有时会进行优化执行速度可能比布尔索引更快尤其是条件很复杂时。query使用技巧与陷阱引用外部变量在查询字符串中引用Python变量需要在变量名前加符号。threshold 5 result df.query(‘A threshold’)列名包含空格或特殊字符需要用反引号backticks将列名包裹起来。df[‘Sale Price’] [100, 200, 300] result df.query(‘Sale Price 150’) # 正确 # result df.query(‘Sale Price 150’) # 错误会解析失败query返回的是DataFrame的视图还是副本这是一个重要细节。在大多数情况下query返回的是副本copy。这意味着你对返回的result进行修改不会影响原始的df。这与loc在某些情况下的行为可能返回视图不同。如果你确认要修改原始数据需要将其赋值回原DataFrame或使用loc。实操心得对于简单的条件用布尔索引和loc就足够了。但当条件逻辑超过两个特别是涉及与、|或、~非的组合时强烈建议切换到query代码的清晰度和可维护性会提升一个档次。在Jupyter中做数据探索时query尤其好用。2.5 筛选器与正则匹配filter方法与列名模式匹配有些时候我们不是根据列的确切名字而是根据某种模式来筛选列。比如选出所有以“temp_”开头的温度传感器列或者所有以“_score”结尾的评分列。这时filter方法就派上用场了。filter方法有三个主要的参数items,like,regex用于不同的匹配场景。df pd.DataFrame({‘user_id’: [1,2,3], ‘temp_room’: [22, 23, 21], ‘temp_out’: [10, 9, 12], ‘score_math’: [90, 85, 95], ‘score_english’: [88, 92, 87]}) # 1. items: 精确匹配列名列表类似于[[]]但参数名不同 print(df.filter(items[‘user_id’, ‘score_math’])) # 2. like: 筛选列名中包含特定子串的列 print(df.filter(like‘temp’)) # 选出所有列名包含‘temp’的列 # 3. regex: 使用正则表达式进行模式匹配最强大 # 选出所有以‘score_’开头的列 print(df.filter(regex‘^score_’)) # 选出所有以‘_’结尾的列这个例子中没有仅作演示 # print(df.filter(regex‘_$’))filter的核心价值在于其声明性declarative。你告诉Pandas“我想要什么样的列”而不是“具体是哪几列”。这在处理具有规律性命名的大量列时可以极大地减少硬编码使代码更健壮。即使后续数据源增加了temp_bathroom列你的filter(like‘temp’)代码也无需修改就能自动包含它。3. 混合筛选实战结合行条件的列筛选在实际工作中纯粹的列筛选不看行场景很少更多的是“在满足某些行条件的记录中查看特定的几列”。这需要将行筛选和列筛选结合起来。3.1 链式操作先筛选行再筛选列这是最直观、也最常用的方法。先用布尔条件或loc/iloc筛选出目标行再从中选取需要的列。# 目标找出A列大于1的行并只查看这些行的B列和C列 # 方法1分两步清晰易懂 filtered_rows df[df[‘A’] 1] # 第一步行筛选 result filtered_rows[[‘B’, ‘C’]] # 第二步列筛选 # 方法2链式调用一行写完 result df[df[‘A’] 1][[‘B’, ‘C’]]链式调用非常方便但需要注意一个潜在的警告链式赋值chained assignment可能失败。# 试图修改满足条件的行的某一列值 # 以下代码可能不会报错但可能无法成功修改原始df df[df[‘A’] 1][‘B’] 999 # 不推荐可能无效上面的赋值操作可能只作用于第一步df[df[‘A’] 1]返回的一个临时副本上而不是原始的df。Pandas会发出一个著名的SettingWithCopyWarning。3.2 使用loc一次性完成行列筛选正确且推荐的做法是使用loc一次性完成条件筛选和列指定。# 正确且高效的做法 df.loc[df[‘A’] 1, [‘B’, ‘C’]] # 仅查看 df.loc[df[‘A’] 1, ‘B’] 999 # 安全地赋值修改loc的第一个参数是行选择器这里是一个布尔序列第二个参数是列选择器。这种写法不仅安全避免了SettingWithCopyWarning而且在性能上通常也更优因为Pandas内部可以对其进行优化。3.3 复杂条件与多列交互筛选当筛选条件依赖于多个列的计算结果时loc结合布尔表达式依然是最佳选择。# 筛选出A列值大于B列值的行并只显示A列和C列 result df.loc[df[‘A’] df[‘B’], [‘A’, ‘C’]] # 结合query可读性更高 result df.query(‘A B’)[[‘A’, ‘C’]] # 先query行再取列 # 或者如果列也是动态的可以这样稍显复杂 col_list [‘A’, ‘C’] result df.query(‘A B’).loc[:, col_list]对于这类复杂筛选我的经验是优先考虑使用query表达行筛选条件然后用loc或方括号选取列。这样既能保持条件表达式的清晰又能明确指定输出列代码结构最好。4. 高级技巧与性能考量掌握了基本方法后我们来看看一些能提升效率、避免坑点的高级技巧。4.1 动态列名筛选与列表推导式有时我们需要筛选的列名不是固定的而是根据某种规则动态生成的。# 场景选出所有数据类型为数值型int或float的列 numeric_cols df.select_dtypes(include[‘int64’, ‘float64’]).columns.tolist() df_numeric df[numeric_cols] # 场景选出所有列名以‘2023’开头的列假设列名是年份 year_cols [col for col in df.columns if col.startswith(‘2023’)] df_2023 df[year_cols] # 场景使用正则表达式动态匹配列名 import re pattern re.compile(r‘^user_.*_score$’) # 匹配以user_开头以_score结尾的列 matched_cols [col for col in df.columns if pattern.match(col)] df_user_scores df[matched_cols]列表推导式配合df.columns迭代是实现动态列筛选的灵活武器。df.columns本身是一个Index对象可以像列表一样进行遍历和条件判断。4.2 使用where与mask进行条件替换where和mask方法用于根据条件替换值它们返回一个与原始DataFrame形状相同的新对象但不符合条件的值会被替换默认为NaN。df.where(cond, othernan)保留cond为True处的值将False处的值替换为other。df.mask(cond, othernan)与where相反将cond为True处的值替换为other。虽然它们主要用于值替换但也可以用于一种特殊的“筛选”视角保留满足条件的原始值不满足的置为NaN。# 将A列小于等于2的值替换为NaN其他列保持不变 df_where df.where(df[‘A’] 2) print(df_where) # 输出 # A B C # 0 NaN 4.0 7.0 # 1 NaN 5.0 8.0 # 2 3.0 6.0 9.0这在数据清洗中很有用比如将超出合理范围的值标记为缺失。注意where和mask默认返回副本如需就地修改需设置inplaceTrue参数。4.3 大数据集下的性能对比与选择建议当处理数百万行甚至更大的数据集时筛选操作的性能差异会被放大。以下是一些经验性的性能排序通常情况具体取决于数据和操作iloc基于整数位置是速度最快的方法因为不涉及标签查找。loc标签索引如果索引是排序过的且是单调的Pandas可以使用快速路径速度接近iloc。否则需要进行哈希查找会慢一些。布尔索引df[condition]需要计算整个布尔序列性能取决于条件复杂度。query对于复杂表达式query的解析和优化可能使其性能优于等价的链式布尔索引尤其是当表达式涉及多个列时。filter需要遍历列名进行匹配在列数非常多时可能有开销但通常不是瓶颈。通用建议如果只按位置选取无脑用iloc。如果按业务标签选取用loc。确保索引设置正确如时间序列数据将日期设为索引可以大幅提升loc的效率。对于复杂的多条件行筛选优先尝试query代码更干净性能也可能更好。避免在循环中进行大量的df[col]单列选取尽量向量化操作或一次性选取多列。5. 常见错误排查与最佳实践即使知道了方法实际编码时还是会遇到各种问题。这里记录几个我踩过的坑和对应的解决方案。5.1KeyError与列名问题这是最常见的问题之一你请求的列名在DataFrame中不存在。# 假设df没有‘D’列 try: df[‘D’] except KeyError as e: print(f“KeyError: {e}“) # 会抛出KeyError: ‘D’排查步骤检查列名拼写和大小写‘UserID’和‘userid’是不同的。使用df.columns.tolist()打印所有列名仔细核对。检查是否有空格或不可见字符列名开头或结尾可能有空格。使用df.columns.str.strip()来清理或者用df.filter(like‘部分名’)来模糊查找。确认数据是否成功加载有时read_csv会因为编码、分隔符问题导致列名读取错误。检查df.head()和df.shape确认数据加载符合预期。5.2SettingWithCopyWarning警报这个警告是Pandas为了保护你避免无意中修改副本而设计的。当你链式索引后尝试赋值时它就会出现。# 会触发警告 df_subset df[df[‘A’] 1][[‘B’, ‘C’]] df_subset[‘B’] 100 # SettingWithCopyWarning!解决方案明确使用loc进行单步赋值这是最根本的解决方法。df.loc[df[‘A’] 1, ‘B’] 100 # 安全无警告如果确实需要副本并修改它在链式操作后显式调用.copy()。df_subset df[df[‘A’] 1][[‘B’, ‘C’]].copy() df_subset[‘B’] 100 # 安全因为操作的是明确的副本不要忽略这个警告它可能意味着你的修改没有生效到原始数据上导致后续分析基于错误的数据。5.3 筛选后索引不连续与reset_index当你筛选掉一部分行后剩余行的索引会保持原样导致索引不连续。filtered_df df[df[‘A’] 1] print(filtered_df.index) # 输出可能是 Int64Index([1, 2], dtype‘int64’)跳过了0这有时会影响一些需要连续索引的操作如某些绘图库或者导致使用iloc时定位错误。使用reset_indexfiltered_df_reset filtered_df.reset_index(dropTrue) print(filtered_df_reset.index) # 输出RangeIndex(start0, stop2, step1)dropTrue参数表示丢弃旧的索引列不将其作为新的一列加入DataFrame。如果你需要保留旧的索引作为数据列可以使用dropFalse。5.4 数据类型变更与意外情况筛选操作有时会改变列的数据类型特别是当你从包含NaN值的列中筛选出一个子集时。s pd.Series([1, 2, None, 4], dtype‘Int64’) # 可空整数类型 sub_s s[s 1] print(sub_s.dtype) # 可能变为‘float64’因为NaN是浮点数应对策略如果数据类型至关重要在筛选后可以使用.astype()强制转换回目标类型但要注意转换可能失败如浮点数NaN无法转为整数。5.5 内存与性能优化视图与副本理解Pandas何时返回视图view即原数据的一个窗口何时返回副本copy即一份新数据很重要它影响内存和性能。视图占用内存少修改视图会影响原始数据。副本占用新内存修改副本不影响原始数据。一般来说简单的单列索引df[‘col’]和loc/iloc的切片操作更可能返回视图而复杂的布尔索引、query、filter以及多列索引[[col1, col2]]几乎总是返回副本。一个简单的判断方法是看结果的_is_view属性不保证所有情况或通过修改结果看是否影响原数据来测试。但最稳妥的实践是如果你需要修改数据就使用loc/iloc进行明确赋值如果你需要一份独立的数据进行操作就显式调用.copy()。这样意图最清晰也避免了不可预知的行为。
返回列表