1. 项目概述从“取数”这件小事说起但凡开始用Python做数据分析Pandas库绝对是绕不开的第一座大山。数据清洗、转换、聚合哪一步都少不了它。而当你真正上手操作数据框DataFrame时第一个拦路虎往往就是“我到底该怎么把想要的那部分数据给‘抠’出来”这个问题看似基础却直接决定了后续所有操作的效率和代码的可读性。新手最常卡壳的地方就是loc和iloc这两个长得像双胞胎但脾气秉性完全不同的方法。我见过太多人包括早期的我自己在写df.iloc[0, ‘column_name’]时报错然后一脸懵地开始搜索浪费大量时间。今天我们就来彻底掰扯清楚loc()和iloc()这不仅是语法区别更是两种截然不同的数据索引思维。理解它们是你从“能用Pandas”到“善用Pandas”的关键一步。无论你是刚入门的数据分析新手还是偶尔需要处理表格数据的开发者掌握这两个方法的核心逻辑与使用场景都能让你的代码更加精准、高效少踩很多坑。2. 核心概念解析标签与位置的哲学在深入loc和iloc之前我们必须先建立两个最核心的概念标签索引Label-based indexing和位置索引Integer position-based indexing。这是理解二者所有区别的基石。2.1 什么是标签索引Label-based想象一下Excel表格。表格有行标题比如第1行是“姓名”、“年龄”、“城市”和列标题A列、B列、C列。在Pandas的DataFrame里行标签index和列名columns就扮演了类似“标题”的角色。标签索引就是通过这些已知的“名字”来定位数据。行标签Index默认情况下Pandas会为每一行分配一个从0开始的整数作为标签0, 1, 2…。但你可以把它设置成任何有意义的标识比如日期、用户ID、产品编号等。例如一个学生信息表行标签可以是学号[‘S001’ ‘S002’ ‘S003’]。列名Columns这个很好理解就是每一列的名字比如[‘姓名’ ‘年龄’ ‘成绩’]。标签索引的核心特点是你使用数据本身具有的、逻辑上的“标识”来进行查询。它更贴近人类的思维习惯——“我要找学号是’S002’的学生的‘成绩’”。loc就是专门为这种查询方式而生的。2.2 什么是位置索引Integer position-based位置索引则完全不管你的行和列叫什么名字。它只认一个东西从0开始的整数位置。就像在Python的列表里你用list[0]取第一个元素用list[-1]取最后一个元素一样。行位置第0行、第1行、第2行……不管它们的行标签是’S001’还是’2023-01-01’。列位置第0列、第1列、第2列……不管它们的列名是‘姓名’还是‘Age’。位置索引的核心特点是你使用数据在物理存储或显示上的“顺序位置”来进行查询。它更贴近计算机的底层思维高效且直接。iloc就是实现这种索引的工具。注意这里有一个极其关键的细节也是新手最容易混淆的点默认的整数行标签0,1,2…很容易让人误以为它就是“位置”。请务必在脑海中将它们分开index2是一个标签position2指的是第三个位置从0开始计。当行标签恰好也是0,1,2…时用loc[2]和iloc[2]可能指向同一行但这只是一种巧合它们的含义完全不同。为了直观对比我们创建一个简单的DataFrame作为贯穿全文的示例import pandas as pd # 创建一个DataFrame并指定自定义的行标签索引 data { ‘姓名’: [‘张三’ ‘李四’ ‘王五’ ‘赵六’], ‘年龄’: [25, 30, 35, 28], ‘城市’: [‘北京’ ‘上海’ ‘广州’ ‘深圳’], ‘得分’: [85, 92, 78, 88] } df pd.DataFrame(data, index[‘A’ ‘B’ ‘C’ ‘D’]) # 行标签为 ‘A’ ‘B’ ‘C’ ‘D’ print(df)输出姓名 年龄 城市 得分 A 张三 25 北京 85 B 李四 30 上海 92 C 王五 35 广州 78 D 赵六 28 深圳 88现在这个DataFrame的行标签是[‘A’ ‘B’ ‘C’ ‘D’]列名是[‘姓名’ ‘年龄’ ‘城市’ ‘得分’]。它的位置信息是行位置0对应标签’A’行位置1对应标签’B’列位置0对应’姓名’列位置1对应’年龄’以此类推。3. loc方法深度剖析基于标签的精准查询loc的全称是“location”顾名思义它通过标签来定位。它的基本语法是df.loc[row_indexer column_indexer]。3.1 基本索引与切片1. 取单个值你需要同时指定行标签和列名。# 获取行标签为‘B’列名为‘年龄’的单元格值 value df.loc[‘B’ ‘年龄’] print(value) # 输出302. 取一行数据只指定行标签列索引器用冒号:表示所有列。# 获取行标签为‘C’的整行数据 row_c df.loc[‘C’ :] # 等价于 df.loc[‘C’] print(row_c) # 输出 # 姓名 王五 # 年龄 35 # 城市 广州 # 得分 78 # Name: C dtype: object3. 取一列数据行索引器用冒号:表示所有行指定列名。# 获取‘城市’这一整列数据 city_column df.loc[: ‘城市’] print(city_column) # 输出 # A 北京 # B 上海 # C 广州 # D 深圳 # Name: 城市 dtype: object4. 取多行多列切片与列表这是loc强大之处。使用列表可以选取指定的多行或多列。# 选取行标签为‘A’和‘C’的行以及列名为‘姓名’和‘得分’的列 subset df.loc[[‘A’ ‘C’] [‘姓名’ ‘得分’]] print(subset) # 输出 # 姓名 得分 # A 张三 85 # C 王五 78使用切片重要loc的切片是包含开始和结束标签的inclusive。这与Python原生的列表切片不包含结束位置不同。# 选取行标签从‘B’到‘D’的所有行以及列从‘年龄’到‘得分’的所有列 slice_df df.loc[‘B’‘D’ ‘年龄’‘得分’] print(slice_df) # 输出 # 年龄 城市 得分 # B 30 上海 92 # C 35 广州 78 # D 28 深圳 88注意结果中包含了’B’ ‘C’ ‘D’三行以及’年龄’ ‘城市’ ‘得分’三列。‘B’‘D’和‘年龄’‘得分’都是包含两端的。3.2 布尔索引条件筛选loc结合布尔数组条件表达式进行筛选是其最常用、最强大的功能之一。你可以进行非常复杂的数据查询。# 1. 简单条件找出年龄大于28的行 condition1 df[‘年龄’] 28 print(df.loc[condition1]) # 2. 多条件组合找出年龄大于28 且 得分大于80的行 # 注意每个条件要用括号括起来逻辑运算符用 与 |或 ~非 condition2 (df[‘年龄’] 28) (df[‘得分’] 80) print(df.loc[condition2]) # 3. 选取满足条件的行并指定显示的列 print(df.loc[condition1 [‘姓名’ ‘城市’]]) # 只显示姓名和城市列 # 4. 使用字符串方法进行筛选找出城市包含‘海’的行 condition3 df[‘城市’].str.contains(‘海’) print(df.loc[condition3])3.3 赋值操作loc索引器不仅可以读取数据还可以直接修改赋值原始DataFrame。这是进行数据清洗和更新的重要手段。# 1. 修改单个值将‘B’行‘得分’列的值改为95 df.loc[‘B’ ‘得分’] 95 print(df.loc[‘B’ ‘得分’]) # 输出95 # 2. 修改整列将所有‘年龄’增加1岁 df.loc[: ‘年龄’] df[‘年龄’] 1 print(df[‘年龄’]) # 输出 # A 26 # B 31 # C 36 # D 29 # 3. 根据条件修改数据将城市为‘北京’的行的得分统一设置为90 df.loc[df[‘城市’] ‘北京’ ‘得分’] 90 print(df)实操心得在使用loc进行条件赋值时一定要确保条件表达式返回的是一个布尔序列Series并且索引与原始DataFrame对齐。Pandas的向量化运算在这里优势尽显避免了低效的循环。另外df.loc[条件 列名] 值这种写法非常直观是数据清洗中的标准操作。4. iloc方法深度剖析基于位置的高效访问iloc的全称是“integer location”它只接受整数位置0-based作为输入。它的基本语法是df.iloc[row_position column_position]。4.1 基本索引与切片1. 取单个值使用行位置和列位置。# 获取第2行位置1第3列位置2的单元格值 value_iloc df.iloc[1 2] print(value_iloc) # 输出上海 # 解析行位置1对应标签‘B’列位置2对应列名‘城市’所以值是‘上海’。2. 取一行或一列# 取第3行位置2的所有列 row_2 df.iloc[2 :] # 等价于 df.iloc[2] print(row_2) # 输出 # 姓名 王五 # 年龄 36 注意我们之前修改过年龄 # 城市 广州 # 得分 78 # 取第2列位置1的所有行 col_1 df.iloc[: 1] print(col_1) # 输出 # A 26 # B 31 # C 36 # D 29 # Name: 年龄 dtype: int643. 取多行多列切片与列表使用列表# 取第1行和第3行位置0和2第1列和第4列位置0和3 subset_iloc df.iloc[[0 2] [0 3]] print(subset_iloc) # 输出 # 姓名 得分 # A 张三 90 # C 王五 78使用切片重要iloc的切片是标准的Python切片即不包含结束位置exclusive。# 取第2行到第4行位置1到3不包含位置4第2列到最后一列位置1到末尾 slice_iloc df.iloc[1:4 1:] print(slice_iloc) # 输出 # 年龄 城市 得分 # B 31 上海 95 # C 36 广州 78 # D 29 深圳 88注意1:4只包含了位置123对应标签’B’ ‘C’ ‘D’不包含位置4。这与loc的包含性切片有本质区别。4.2 使用负数索引和Python列表一样iloc支持负数索引表示从末尾开始计数。# 取最后一行 last_row df.iloc[-1] print(last_row) # 取倒数第二行以及第1列和第3列位置0和2 subset_neg df.iloc[-2 [0 2]] print(subset_neg)4.3 iloc的赋值操作iloc同样支持赋值逻辑与loc类似只是索引方式换成了位置。# 将第1行位置0第4列位置3的值改为100 df.iloc[0 3] 100 print(df.iloc[0 3]) # 输出100 # 将前两行的‘年龄’列位置1设置为40 df.iloc[0:2 1] 40 print(df[[‘姓名’ ‘年龄’]].head(2))注意事项使用iloc进行切片赋值时要时刻牢记“左闭右开”原则。df.iloc[0:2 1] 40修改的是第0行和第1行不包括第2行。如果你想要修改前两行用0:2是正确的如果你想修改前三行需要用0:3。这个细节错误在批量操作时很容易发生。5. loc与iloc的核心区别与对比理解了各自的工作方式后我们可以从多个维度对它们进行系统性的对比。这张表格清晰地概括了核心差异特性维度lociloc索引类型标签索引 (Label-based)位置索引 (Integer position-based)索引器接受类型行/列标签整数、字符串等、标签列表、标签切片、布尔数组整数位置、整数位置列表、整数位置切片切片行为包含起始和结束标签 (‘start’‘end’包含两端)不包含结束位置 (start:end不包含end) 遵循Python惯例与DataFrame索引的关系直接使用索引index和列名columns的值忽略索引和列名只关心其整数位置0, 1, 2…常见用途按业务逻辑如ID、日期查询数据、条件筛选、基于标签的切片按顺序位置访问数据如前N行、后N行、基于固定位置的切片、当索引无规律时出错场景提供的标签在索引/列名中不存在会引发KeyError提供的位置超出范围如df.iloc[100 :]会引发IndexError场景化理解举例继续使用我们的示例DataFramedf姓名 年龄 城市 得分 A 张三 40 北京 100 B 李四 40 上海 95 C 王五 36 广州 78 D 赵六 29 深圳 88目标获取中间两行B和C的数据。用loc我知道行标签是’B’和’C’。df.loc[‘B’‘C’]可以因为切片包含两端。用iloc我知道它们是第2和第3行位置1和2。df.iloc[1:3]可以因为切片1:3包含位置1和2不包含3。df.iloc[[1 2]]也可以。目标获取‘年龄’和‘得分’列。用locdf.loc[: [‘年龄’ ‘得分’]]或df.loc[: ‘年龄’‘得分’]因为这两列在表中相邻。用ilocdf.iloc[: [1 3]]或df.iloc[: 1:4]因为‘年龄’是第2列-位置1‘得分’是第4列-位置3切片1:4包含位置123即‘年龄’ ‘城市’ ‘得分’ 这里多取了‘城市’列所以用列表[1 3]更精确。一个关键陷阱假设行标签是[10 20 30 40]。df.loc[20:30]会返回标签为20和30的两行。df.iloc[1:3]会返回位置1和2的两行即标签为20和30的行。在这个特例中结果一致但逻辑完全不同。如果行标签是[‘a’ ‘b’ ‘c’ ‘d’]df.loc[1:2]会报错因为标签1不存在而df.iloc[1:3]则能正确取出第2、3行。6. 高级技巧与混合使用场景在实际项目中数据访问需求往往更复杂需要灵活组合甚至超越loc和iloc。6.1 使用at和iat进行快速标量访问如果你只需要获取或设置一个单个单元格的值使用at基于标签和iat基于位置会比loc和iloc更快因为它们是访问标量的优化方法。# 使用 at 获取单个值 fast_value df.at[‘C’ ‘城市’] # 比 df.loc[‘C’ ‘城市’] 稍快 print(fast_value) # 输出广州 # 使用 iat 获取单个值 fast_value2 df.iat[2 2] # 第3行位置2第3列位置2 print(fast_value2) # 输出广州 # 赋值操作 df.at[‘A’ ‘年龄’] 26 df.iat[0 1] 26 # 与上一行等价实操心得在需要频繁访问单个单元格的循环中虽然应尽量避免循环但有时不可避免用at/iat替换loc/iloc能带来可观的性能提升。但在绝大多数向量化操作中感觉不到区别。6.2 处理多层索引MultiIndex当DataFrame具有多层行索引或列索引时loc和iloc依然适用但语法更丰富。# 创建一个具有多层列索引的示例 arrays [[‘A’ ‘A’ ‘B’ ‘B’] [‘one’ ‘two’ ‘one’ ‘two’]] tuples list(zip(*arrays)) index pd.MultiIndex.from_tuples(tuples names[‘first’ ‘second’]) df_multi pd.DataFrame(np.random.randn(3 4) index[‘X’ ‘Y’ ‘Z’] columnsindex) print(df_multi) # 使用 loc 访问多层索引 # 获取第一层列索引为‘A’的所有列 print(df_multi.loc[: ‘A’]) # 获取第一层列索引为‘B’第二层为‘two’的列 print(df_multi.loc[: (‘B’ ‘two’)]) # 使用切片需要指定所有层级或使用pd.IndexSlice idx pd.IndexSlice print(df_multi.loc[‘Y’ idx[: ‘two’]]) # 获取‘Y’行所有第二层为‘two’的列对于多层索引iloc的用法不变因为它只认位置。6.3 链式赋值问题与.loc的推荐用法这是一个非常重要的坑。看下面的代码# 不推荐的链式索引赋值 df[‘城市’][df[‘年龄’] 30] ‘未知’ # 可能会触发SettingWithCopyWarning警告 # 推荐的.loc单次索引赋值 df.loc[df[‘年龄’] 30 ‘城市’] ‘未知’第一种方式链式df[‘列’][条件]被称为链式赋值chained assignment。Pandas有时无法判断这是要修改原始df还是一个它的副本view因此可能只修改了副本而原始数据未变或者抛出SettingWithCopyWarning警告。最佳实践是始终使用df.loc[行条件 列] 值这种形式进行赋值它明确、高效且安全。7. 性能考量与最佳实践选择虽然对于大多数中小型数据集loc和iloc的性能差异可以忽略不计但在处理海量数据或性能关键型循环中了解其底层机制有助于写出更高效的代码。iloc通常略快于loc因为iloc直接映射到基于整数位置的底层数组NumPy访问操作更接近硬件底层。而loc需要先在索引中进行标签查找可能涉及哈希表查询再将标签转换为内部位置多了一步开销。如果索引是单调递增的整数Pandas会对loc进行优化差距会缩小。关键选择依据是场景而非微小的性能差异当你按业务键如用户ID、订单号、时间戳查询时用loc。这是它的主战场代码意图清晰与数据逻辑强相关。当你按固定顺序如前100行、每隔10行抽样、最后N行访问时用iloc。代码简洁且不受索引内容变化的影响。进行条件筛选时必须用loc或布尔索引直接作用于DataFrame。iloc无法直接接受布尔序列。当DataFrame的索引是默认的整数范围索引012…时loc[i]和iloc[i]结果相同但请根据你的意图选择如果你想表达“第i个位置”用iloc如果你想表达“标签为i的那一行”用loc。后者的意图在索引被重置或修改后可能出错。一个综合性的最佳实践示例假设我们有一个销售数据表sales_df索引是订单ID字符串列包括‘date’ ‘product’ ‘quantity’ ‘revenue’。# 场景1分析特定几个大客户的订单按业务键查询 big_clients [‘C1001’ ‘C1005’ ‘C1010’] client_orders sales_df.loc[big_clients] # 使用loc清晰表达“找这些ID的订单” # 场景2查看最近新增的50条订单按顺序位置 recent_orders sales_df.iloc[-50:] # 使用iloc表达“最后50个位置” # 场景3找出所有产品A且收入超过1000的订单条件筛选 high_value_A_orders sales_df.loc[(sales_df[‘product’] ‘A’) (sales_df[‘revenue’] 1000)] # 必须用loc配合布尔索引 # 场景4将产品B的所有订单数量增加10%条件赋值 sales_df.loc[sales_df[‘product’] ‘B’ ‘quantity’] * 1.1 # 安全且高效的单次.loc赋值 # 场景5随机抽取10%的样本按位置 sample_indices np.random.choice(sales_df.shape[0] sizeint(sales_df.shape[0]*0.1) replaceFalse) sample_df sales_df.iloc[sample_indices] # 使用iloc因为我们生成的是位置索引8. 常见问题与排查技巧实录即使理解了原理在实际编码中还是会遇到各种问题。下面是我在多年实践中总结的一些典型错误和解决方法。8.1 KeyError: ‘[label] not in index’问题使用loc时指定的行标签或列名在DataFrame中不存在。# 假设df的行标签是 [‘A’ ‘B’ ‘C’ ‘D’] try: df.loc[‘E’ :] # ‘E’不存在 except KeyError as e: print(f”KeyError: {e}“)排查与解决检查拼写和大小写这是最常见的原因。‘city’和‘City’是不同的列名。打印索引和列名确认在操作前使用print(df.index)和print(df.columns)查看当前确切的标签。使用in操作符判断if ‘E’ in df.index:。考虑使用reindex或条件判断如果你不确定标签是否存在可以先用df.reindex([‘A’ ‘E’])不存在的标签会生成NaN行。或者使用try…except块捕获异常。8.2 IndexError: single positional indexer is out-of-bounds问题使用iloc时指定的整数位置超出了DataFrame的范围。# 假设df有4行位置0123 try: df.iloc[5 :] # 位置5不存在 except IndexError as e: print(f”IndexError: {e}“)排查与解决检查DataFrame形状使用df.shape获取行数和列数。记住位置是从0到shape[0]-1和shape[1]-1。小心负数索引df.iloc[-5]在只有4行的DataFrame上也会报错因为-5表示倒数第5行不存在。在循环或动态计算位置时加入边界检查。8.3 切片结果与预期不符问题最常见的是混淆了loc包含和iloc不包含的切片语义。# 目标选取前3行 df_loc_slice df.loc[0:2] # 如果索引是0123…这会选取标签012共3行 df_iloc_slice df.iloc[0:3] # 这会选取位置012共3行 # 如果索引不是整数df.loc[0:2]可能会报错或产生意想不到的结果。解决永远清楚你用的是loc还是iloc。画个简单的数轴图有助于理解loc[‘a’‘c’]覆盖abciloc[1:4]覆盖位置123。8.4 SettingWithCopyWarning警告问题如前所述链式赋值可能引发此警告。# 可能产生警告的代码 subset df[df[‘年龄’] 30] subset[‘城市’] ‘老年组’ # 这行可能触发警告且修改可能不生效于原始df解决永远使用.loc[row_indexer col_indexer] value进行赋值。对于上面的例子正确写法是df.loc[df[‘年龄’] 30 ‘城市’] ‘老年组’如果你确实需要在一个副本上操作并避免警告可以显式创建副本subset df[df[‘年龄’] 30].copy()然后在subset上修改。8.5 布尔索引条件失效问题复杂的多条件组合时由于运算符优先级问题导致逻辑错误。# 错误示例缺少括号 condition df[‘年龄’] 28 df[‘得分’] 80 # Python会先计算 28 df[‘得分’]导致错误 # 正确示例 condition (df[‘年龄’] 28) (df[‘得分’] 80)解决给每个条件加上括号。这是必须养成的习惯。逻辑运算符|~的优先级高于比较运算符等。8.6 如何优雅地处理“找不到”的情况有时我们并不希望因为一个标签不存在就抛出异常而是希望返回一个默认值或跳过。# 方法1使用get方法仅对列有效类似字典 city df.get(‘城市’ defaultNone) # 获取‘城市’列如果不存在该列则返回None # 注意df.get()是针对列名的对行标签无效。 # 方法2使用reindex对行标签有效 # 如果你想获取多个标签并允许部分不存在 desired_labels [‘A’ ‘E’ ‘C’] result df.reindex(desired_labels) # ‘E’对应的行全为NaN print(result) # 方法3使用query方法进行安全的条件查询内部处理更优雅 # query方法可以接受字符串表达式对于复杂的列名含空格特别有用 # 假设列名是‘user age’用loc写条件很麻烦 result df.query(”user age 30“) # 使用反引号包裹含特殊字符的列名掌握loc和iloc就像是拿到了精准操作Pandas数据框的两把不同用途的“手术刀”。loc基于标签适合根据业务逻辑进行“定点解剖”iloc基于位置适合按照物理顺序进行“批量切割”。在实际工作中我几乎会在每一个涉及数据选取的脚本里同时用到它们。我的习惯是但凡涉及条件筛选、按列名选取、按业务ID查找毫不犹豫用loc但凡涉及取前N行、后N行、按固定间隔抽样、或者索引杂乱无章时就用iloc。刚开始可能会需要停下来想一想但经过一段时间的刻意练习这种选择会变成肌肉记忆你的数据分析代码会因此变得更加稳健和高效。最后再分享一个小技巧在Jupyter Notebook中你可以用df._is_view或df._is_copy来粗略判断一个操作是否可能产生视图view而引发链式赋值警告但最根本的解决方案还是牢记并践行“使用.loc进行单次赋值”这条黄金法则。
Pandas数据索引核心:loc与iloc的标签与位置索引详解
1. 项目概述从“取数”这件小事说起但凡开始用Python做数据分析Pandas库绝对是绕不开的第一座大山。数据清洗、转换、聚合哪一步都少不了它。而当你真正上手操作数据框DataFrame时第一个拦路虎往往就是“我到底该怎么把想要的那部分数据给‘抠’出来”这个问题看似基础却直接决定了后续所有操作的效率和代码的可读性。新手最常卡壳的地方就是loc和iloc这两个长得像双胞胎但脾气秉性完全不同的方法。我见过太多人包括早期的我自己在写df.iloc[0, ‘column_name’]时报错然后一脸懵地开始搜索浪费大量时间。今天我们就来彻底掰扯清楚loc()和iloc()这不仅是语法区别更是两种截然不同的数据索引思维。理解它们是你从“能用Pandas”到“善用Pandas”的关键一步。无论你是刚入门的数据分析新手还是偶尔需要处理表格数据的开发者掌握这两个方法的核心逻辑与使用场景都能让你的代码更加精准、高效少踩很多坑。2. 核心概念解析标签与位置的哲学在深入loc和iloc之前我们必须先建立两个最核心的概念标签索引Label-based indexing和位置索引Integer position-based indexing。这是理解二者所有区别的基石。2.1 什么是标签索引Label-based想象一下Excel表格。表格有行标题比如第1行是“姓名”、“年龄”、“城市”和列标题A列、B列、C列。在Pandas的DataFrame里行标签index和列名columns就扮演了类似“标题”的角色。标签索引就是通过这些已知的“名字”来定位数据。行标签Index默认情况下Pandas会为每一行分配一个从0开始的整数作为标签0, 1, 2…。但你可以把它设置成任何有意义的标识比如日期、用户ID、产品编号等。例如一个学生信息表行标签可以是学号[‘S001’ ‘S002’ ‘S003’]。列名Columns这个很好理解就是每一列的名字比如[‘姓名’ ‘年龄’ ‘成绩’]。标签索引的核心特点是你使用数据本身具有的、逻辑上的“标识”来进行查询。它更贴近人类的思维习惯——“我要找学号是’S002’的学生的‘成绩’”。loc就是专门为这种查询方式而生的。2.2 什么是位置索引Integer position-based位置索引则完全不管你的行和列叫什么名字。它只认一个东西从0开始的整数位置。就像在Python的列表里你用list[0]取第一个元素用list[-1]取最后一个元素一样。行位置第0行、第1行、第2行……不管它们的行标签是’S001’还是’2023-01-01’。列位置第0列、第1列、第2列……不管它们的列名是‘姓名’还是‘Age’。位置索引的核心特点是你使用数据在物理存储或显示上的“顺序位置”来进行查询。它更贴近计算机的底层思维高效且直接。iloc就是实现这种索引的工具。注意这里有一个极其关键的细节也是新手最容易混淆的点默认的整数行标签0,1,2…很容易让人误以为它就是“位置”。请务必在脑海中将它们分开index2是一个标签position2指的是第三个位置从0开始计。当行标签恰好也是0,1,2…时用loc[2]和iloc[2]可能指向同一行但这只是一种巧合它们的含义完全不同。为了直观对比我们创建一个简单的DataFrame作为贯穿全文的示例import pandas as pd # 创建一个DataFrame并指定自定义的行标签索引 data { ‘姓名’: [‘张三’ ‘李四’ ‘王五’ ‘赵六’], ‘年龄’: [25, 30, 35, 28], ‘城市’: [‘北京’ ‘上海’ ‘广州’ ‘深圳’], ‘得分’: [85, 92, 78, 88] } df pd.DataFrame(data, index[‘A’ ‘B’ ‘C’ ‘D’]) # 行标签为 ‘A’ ‘B’ ‘C’ ‘D’ print(df)输出姓名 年龄 城市 得分 A 张三 25 北京 85 B 李四 30 上海 92 C 王五 35 广州 78 D 赵六 28 深圳 88现在这个DataFrame的行标签是[‘A’ ‘B’ ‘C’ ‘D’]列名是[‘姓名’ ‘年龄’ ‘城市’ ‘得分’]。它的位置信息是行位置0对应标签’A’行位置1对应标签’B’列位置0对应’姓名’列位置1对应’年龄’以此类推。3. loc方法深度剖析基于标签的精准查询loc的全称是“location”顾名思义它通过标签来定位。它的基本语法是df.loc[row_indexer column_indexer]。3.1 基本索引与切片1. 取单个值你需要同时指定行标签和列名。# 获取行标签为‘B’列名为‘年龄’的单元格值 value df.loc[‘B’ ‘年龄’] print(value) # 输出302. 取一行数据只指定行标签列索引器用冒号:表示所有列。# 获取行标签为‘C’的整行数据 row_c df.loc[‘C’ :] # 等价于 df.loc[‘C’] print(row_c) # 输出 # 姓名 王五 # 年龄 35 # 城市 广州 # 得分 78 # Name: C dtype: object3. 取一列数据行索引器用冒号:表示所有行指定列名。# 获取‘城市’这一整列数据 city_column df.loc[: ‘城市’] print(city_column) # 输出 # A 北京 # B 上海 # C 广州 # D 深圳 # Name: 城市 dtype: object4. 取多行多列切片与列表这是loc强大之处。使用列表可以选取指定的多行或多列。# 选取行标签为‘A’和‘C’的行以及列名为‘姓名’和‘得分’的列 subset df.loc[[‘A’ ‘C’] [‘姓名’ ‘得分’]] print(subset) # 输出 # 姓名 得分 # A 张三 85 # C 王五 78使用切片重要loc的切片是包含开始和结束标签的inclusive。这与Python原生的列表切片不包含结束位置不同。# 选取行标签从‘B’到‘D’的所有行以及列从‘年龄’到‘得分’的所有列 slice_df df.loc[‘B’‘D’ ‘年龄’‘得分’] print(slice_df) # 输出 # 年龄 城市 得分 # B 30 上海 92 # C 35 广州 78 # D 28 深圳 88注意结果中包含了’B’ ‘C’ ‘D’三行以及’年龄’ ‘城市’ ‘得分’三列。‘B’‘D’和‘年龄’‘得分’都是包含两端的。3.2 布尔索引条件筛选loc结合布尔数组条件表达式进行筛选是其最常用、最强大的功能之一。你可以进行非常复杂的数据查询。# 1. 简单条件找出年龄大于28的行 condition1 df[‘年龄’] 28 print(df.loc[condition1]) # 2. 多条件组合找出年龄大于28 且 得分大于80的行 # 注意每个条件要用括号括起来逻辑运算符用 与 |或 ~非 condition2 (df[‘年龄’] 28) (df[‘得分’] 80) print(df.loc[condition2]) # 3. 选取满足条件的行并指定显示的列 print(df.loc[condition1 [‘姓名’ ‘城市’]]) # 只显示姓名和城市列 # 4. 使用字符串方法进行筛选找出城市包含‘海’的行 condition3 df[‘城市’].str.contains(‘海’) print(df.loc[condition3])3.3 赋值操作loc索引器不仅可以读取数据还可以直接修改赋值原始DataFrame。这是进行数据清洗和更新的重要手段。# 1. 修改单个值将‘B’行‘得分’列的值改为95 df.loc[‘B’ ‘得分’] 95 print(df.loc[‘B’ ‘得分’]) # 输出95 # 2. 修改整列将所有‘年龄’增加1岁 df.loc[: ‘年龄’] df[‘年龄’] 1 print(df[‘年龄’]) # 输出 # A 26 # B 31 # C 36 # D 29 # 3. 根据条件修改数据将城市为‘北京’的行的得分统一设置为90 df.loc[df[‘城市’] ‘北京’ ‘得分’] 90 print(df)实操心得在使用loc进行条件赋值时一定要确保条件表达式返回的是一个布尔序列Series并且索引与原始DataFrame对齐。Pandas的向量化运算在这里优势尽显避免了低效的循环。另外df.loc[条件 列名] 值这种写法非常直观是数据清洗中的标准操作。4. iloc方法深度剖析基于位置的高效访问iloc的全称是“integer location”它只接受整数位置0-based作为输入。它的基本语法是df.iloc[row_position column_position]。4.1 基本索引与切片1. 取单个值使用行位置和列位置。# 获取第2行位置1第3列位置2的单元格值 value_iloc df.iloc[1 2] print(value_iloc) # 输出上海 # 解析行位置1对应标签‘B’列位置2对应列名‘城市’所以值是‘上海’。2. 取一行或一列# 取第3行位置2的所有列 row_2 df.iloc[2 :] # 等价于 df.iloc[2] print(row_2) # 输出 # 姓名 王五 # 年龄 36 注意我们之前修改过年龄 # 城市 广州 # 得分 78 # 取第2列位置1的所有行 col_1 df.iloc[: 1] print(col_1) # 输出 # A 26 # B 31 # C 36 # D 29 # Name: 年龄 dtype: int643. 取多行多列切片与列表使用列表# 取第1行和第3行位置0和2第1列和第4列位置0和3 subset_iloc df.iloc[[0 2] [0 3]] print(subset_iloc) # 输出 # 姓名 得分 # A 张三 90 # C 王五 78使用切片重要iloc的切片是标准的Python切片即不包含结束位置exclusive。# 取第2行到第4行位置1到3不包含位置4第2列到最后一列位置1到末尾 slice_iloc df.iloc[1:4 1:] print(slice_iloc) # 输出 # 年龄 城市 得分 # B 31 上海 95 # C 36 广州 78 # D 29 深圳 88注意1:4只包含了位置123对应标签’B’ ‘C’ ‘D’不包含位置4。这与loc的包含性切片有本质区别。4.2 使用负数索引和Python列表一样iloc支持负数索引表示从末尾开始计数。# 取最后一行 last_row df.iloc[-1] print(last_row) # 取倒数第二行以及第1列和第3列位置0和2 subset_neg df.iloc[-2 [0 2]] print(subset_neg)4.3 iloc的赋值操作iloc同样支持赋值逻辑与loc类似只是索引方式换成了位置。# 将第1行位置0第4列位置3的值改为100 df.iloc[0 3] 100 print(df.iloc[0 3]) # 输出100 # 将前两行的‘年龄’列位置1设置为40 df.iloc[0:2 1] 40 print(df[[‘姓名’ ‘年龄’]].head(2))注意事项使用iloc进行切片赋值时要时刻牢记“左闭右开”原则。df.iloc[0:2 1] 40修改的是第0行和第1行不包括第2行。如果你想要修改前两行用0:2是正确的如果你想修改前三行需要用0:3。这个细节错误在批量操作时很容易发生。5. loc与iloc的核心区别与对比理解了各自的工作方式后我们可以从多个维度对它们进行系统性的对比。这张表格清晰地概括了核心差异特性维度lociloc索引类型标签索引 (Label-based)位置索引 (Integer position-based)索引器接受类型行/列标签整数、字符串等、标签列表、标签切片、布尔数组整数位置、整数位置列表、整数位置切片切片行为包含起始和结束标签 (‘start’‘end’包含两端)不包含结束位置 (start:end不包含end) 遵循Python惯例与DataFrame索引的关系直接使用索引index和列名columns的值忽略索引和列名只关心其整数位置0, 1, 2…常见用途按业务逻辑如ID、日期查询数据、条件筛选、基于标签的切片按顺序位置访问数据如前N行、后N行、基于固定位置的切片、当索引无规律时出错场景提供的标签在索引/列名中不存在会引发KeyError提供的位置超出范围如df.iloc[100 :]会引发IndexError场景化理解举例继续使用我们的示例DataFramedf姓名 年龄 城市 得分 A 张三 40 北京 100 B 李四 40 上海 95 C 王五 36 广州 78 D 赵六 29 深圳 88目标获取中间两行B和C的数据。用loc我知道行标签是’B’和’C’。df.loc[‘B’‘C’]可以因为切片包含两端。用iloc我知道它们是第2和第3行位置1和2。df.iloc[1:3]可以因为切片1:3包含位置1和2不包含3。df.iloc[[1 2]]也可以。目标获取‘年龄’和‘得分’列。用locdf.loc[: [‘年龄’ ‘得分’]]或df.loc[: ‘年龄’‘得分’]因为这两列在表中相邻。用ilocdf.iloc[: [1 3]]或df.iloc[: 1:4]因为‘年龄’是第2列-位置1‘得分’是第4列-位置3切片1:4包含位置123即‘年龄’ ‘城市’ ‘得分’ 这里多取了‘城市’列所以用列表[1 3]更精确。一个关键陷阱假设行标签是[10 20 30 40]。df.loc[20:30]会返回标签为20和30的两行。df.iloc[1:3]会返回位置1和2的两行即标签为20和30的行。在这个特例中结果一致但逻辑完全不同。如果行标签是[‘a’ ‘b’ ‘c’ ‘d’]df.loc[1:2]会报错因为标签1不存在而df.iloc[1:3]则能正确取出第2、3行。6. 高级技巧与混合使用场景在实际项目中数据访问需求往往更复杂需要灵活组合甚至超越loc和iloc。6.1 使用at和iat进行快速标量访问如果你只需要获取或设置一个单个单元格的值使用at基于标签和iat基于位置会比loc和iloc更快因为它们是访问标量的优化方法。# 使用 at 获取单个值 fast_value df.at[‘C’ ‘城市’] # 比 df.loc[‘C’ ‘城市’] 稍快 print(fast_value) # 输出广州 # 使用 iat 获取单个值 fast_value2 df.iat[2 2] # 第3行位置2第3列位置2 print(fast_value2) # 输出广州 # 赋值操作 df.at[‘A’ ‘年龄’] 26 df.iat[0 1] 26 # 与上一行等价实操心得在需要频繁访问单个单元格的循环中虽然应尽量避免循环但有时不可避免用at/iat替换loc/iloc能带来可观的性能提升。但在绝大多数向量化操作中感觉不到区别。6.2 处理多层索引MultiIndex当DataFrame具有多层行索引或列索引时loc和iloc依然适用但语法更丰富。# 创建一个具有多层列索引的示例 arrays [[‘A’ ‘A’ ‘B’ ‘B’] [‘one’ ‘two’ ‘one’ ‘two’]] tuples list(zip(*arrays)) index pd.MultiIndex.from_tuples(tuples names[‘first’ ‘second’]) df_multi pd.DataFrame(np.random.randn(3 4) index[‘X’ ‘Y’ ‘Z’] columnsindex) print(df_multi) # 使用 loc 访问多层索引 # 获取第一层列索引为‘A’的所有列 print(df_multi.loc[: ‘A’]) # 获取第一层列索引为‘B’第二层为‘two’的列 print(df_multi.loc[: (‘B’ ‘two’)]) # 使用切片需要指定所有层级或使用pd.IndexSlice idx pd.IndexSlice print(df_multi.loc[‘Y’ idx[: ‘two’]]) # 获取‘Y’行所有第二层为‘two’的列对于多层索引iloc的用法不变因为它只认位置。6.3 链式赋值问题与.loc的推荐用法这是一个非常重要的坑。看下面的代码# 不推荐的链式索引赋值 df[‘城市’][df[‘年龄’] 30] ‘未知’ # 可能会触发SettingWithCopyWarning警告 # 推荐的.loc单次索引赋值 df.loc[df[‘年龄’] 30 ‘城市’] ‘未知’第一种方式链式df[‘列’][条件]被称为链式赋值chained assignment。Pandas有时无法判断这是要修改原始df还是一个它的副本view因此可能只修改了副本而原始数据未变或者抛出SettingWithCopyWarning警告。最佳实践是始终使用df.loc[行条件 列] 值这种形式进行赋值它明确、高效且安全。7. 性能考量与最佳实践选择虽然对于大多数中小型数据集loc和iloc的性能差异可以忽略不计但在处理海量数据或性能关键型循环中了解其底层机制有助于写出更高效的代码。iloc通常略快于loc因为iloc直接映射到基于整数位置的底层数组NumPy访问操作更接近硬件底层。而loc需要先在索引中进行标签查找可能涉及哈希表查询再将标签转换为内部位置多了一步开销。如果索引是单调递增的整数Pandas会对loc进行优化差距会缩小。关键选择依据是场景而非微小的性能差异当你按业务键如用户ID、订单号、时间戳查询时用loc。这是它的主战场代码意图清晰与数据逻辑强相关。当你按固定顺序如前100行、每隔10行抽样、最后N行访问时用iloc。代码简洁且不受索引内容变化的影响。进行条件筛选时必须用loc或布尔索引直接作用于DataFrame。iloc无法直接接受布尔序列。当DataFrame的索引是默认的整数范围索引012…时loc[i]和iloc[i]结果相同但请根据你的意图选择如果你想表达“第i个位置”用iloc如果你想表达“标签为i的那一行”用loc。后者的意图在索引被重置或修改后可能出错。一个综合性的最佳实践示例假设我们有一个销售数据表sales_df索引是订单ID字符串列包括‘date’ ‘product’ ‘quantity’ ‘revenue’。# 场景1分析特定几个大客户的订单按业务键查询 big_clients [‘C1001’ ‘C1005’ ‘C1010’] client_orders sales_df.loc[big_clients] # 使用loc清晰表达“找这些ID的订单” # 场景2查看最近新增的50条订单按顺序位置 recent_orders sales_df.iloc[-50:] # 使用iloc表达“最后50个位置” # 场景3找出所有产品A且收入超过1000的订单条件筛选 high_value_A_orders sales_df.loc[(sales_df[‘product’] ‘A’) (sales_df[‘revenue’] 1000)] # 必须用loc配合布尔索引 # 场景4将产品B的所有订单数量增加10%条件赋值 sales_df.loc[sales_df[‘product’] ‘B’ ‘quantity’] * 1.1 # 安全且高效的单次.loc赋值 # 场景5随机抽取10%的样本按位置 sample_indices np.random.choice(sales_df.shape[0] sizeint(sales_df.shape[0]*0.1) replaceFalse) sample_df sales_df.iloc[sample_indices] # 使用iloc因为我们生成的是位置索引8. 常见问题与排查技巧实录即使理解了原理在实际编码中还是会遇到各种问题。下面是我在多年实践中总结的一些典型错误和解决方法。8.1 KeyError: ‘[label] not in index’问题使用loc时指定的行标签或列名在DataFrame中不存在。# 假设df的行标签是 [‘A’ ‘B’ ‘C’ ‘D’] try: df.loc[‘E’ :] # ‘E’不存在 except KeyError as e: print(f”KeyError: {e}“)排查与解决检查拼写和大小写这是最常见的原因。‘city’和‘City’是不同的列名。打印索引和列名确认在操作前使用print(df.index)和print(df.columns)查看当前确切的标签。使用in操作符判断if ‘E’ in df.index:。考虑使用reindex或条件判断如果你不确定标签是否存在可以先用df.reindex([‘A’ ‘E’])不存在的标签会生成NaN行。或者使用try…except块捕获异常。8.2 IndexError: single positional indexer is out-of-bounds问题使用iloc时指定的整数位置超出了DataFrame的范围。# 假设df有4行位置0123 try: df.iloc[5 :] # 位置5不存在 except IndexError as e: print(f”IndexError: {e}“)排查与解决检查DataFrame形状使用df.shape获取行数和列数。记住位置是从0到shape[0]-1和shape[1]-1。小心负数索引df.iloc[-5]在只有4行的DataFrame上也会报错因为-5表示倒数第5行不存在。在循环或动态计算位置时加入边界检查。8.3 切片结果与预期不符问题最常见的是混淆了loc包含和iloc不包含的切片语义。# 目标选取前3行 df_loc_slice df.loc[0:2] # 如果索引是0123…这会选取标签012共3行 df_iloc_slice df.iloc[0:3] # 这会选取位置012共3行 # 如果索引不是整数df.loc[0:2]可能会报错或产生意想不到的结果。解决永远清楚你用的是loc还是iloc。画个简单的数轴图有助于理解loc[‘a’‘c’]覆盖abciloc[1:4]覆盖位置123。8.4 SettingWithCopyWarning警告问题如前所述链式赋值可能引发此警告。# 可能产生警告的代码 subset df[df[‘年龄’] 30] subset[‘城市’] ‘老年组’ # 这行可能触发警告且修改可能不生效于原始df解决永远使用.loc[row_indexer col_indexer] value进行赋值。对于上面的例子正确写法是df.loc[df[‘年龄’] 30 ‘城市’] ‘老年组’如果你确实需要在一个副本上操作并避免警告可以显式创建副本subset df[df[‘年龄’] 30].copy()然后在subset上修改。8.5 布尔索引条件失效问题复杂的多条件组合时由于运算符优先级问题导致逻辑错误。# 错误示例缺少括号 condition df[‘年龄’] 28 df[‘得分’] 80 # Python会先计算 28 df[‘得分’]导致错误 # 正确示例 condition (df[‘年龄’] 28) (df[‘得分’] 80)解决给每个条件加上括号。这是必须养成的习惯。逻辑运算符|~的优先级高于比较运算符等。8.6 如何优雅地处理“找不到”的情况有时我们并不希望因为一个标签不存在就抛出异常而是希望返回一个默认值或跳过。# 方法1使用get方法仅对列有效类似字典 city df.get(‘城市’ defaultNone) # 获取‘城市’列如果不存在该列则返回None # 注意df.get()是针对列名的对行标签无效。 # 方法2使用reindex对行标签有效 # 如果你想获取多个标签并允许部分不存在 desired_labels [‘A’ ‘E’ ‘C’] result df.reindex(desired_labels) # ‘E’对应的行全为NaN print(result) # 方法3使用query方法进行安全的条件查询内部处理更优雅 # query方法可以接受字符串表达式对于复杂的列名含空格特别有用 # 假设列名是‘user age’用loc写条件很麻烦 result df.query(”user age 30“) # 使用反引号包裹含特殊字符的列名掌握loc和iloc就像是拿到了精准操作Pandas数据框的两把不同用途的“手术刀”。loc基于标签适合根据业务逻辑进行“定点解剖”iloc基于位置适合按照物理顺序进行“批量切割”。在实际工作中我几乎会在每一个涉及数据选取的脚本里同时用到它们。我的习惯是但凡涉及条件筛选、按列名选取、按业务ID查找毫不犹豫用loc但凡涉及取前N行、后N行、按固定间隔抽样、或者索引杂乱无章时就用iloc。刚开始可能会需要停下来想一想但经过一段时间的刻意练习这种选择会变成肌肉记忆你的数据分析代码会因此变得更加稳健和高效。最后再分享一个小技巧在Jupyter Notebook中你可以用df._is_view或df._is_copy来粗略判断一个操作是否可能产生视图view而引发链式赋值警告但最根本的解决方案还是牢记并践行“使用.loc进行单次赋值”这条黄金法则。