
大家处理数据的时候最常用到的两个操作大概就是排序和排名了。项目标题里写的“DateFrame”其实就是 pandas 里的 DataFrame这个拼写错误还挺常见但丝毫不影响它成为数据分析的核心工具。排序和排名在数据分析里属于“基础但极容易踩坑”的操作很多初学者会用 sort_values 把数据排个序然后发现索引乱了、排名方法不对、分组排名不会写最后得出的结论差之千里。这篇就围绕 DataFrame 的排序和排名把原理、参数、应用场景和实战过程完整拆解一遍顺便把我在实际项目中遇到的问题也一并罗列出来希望能帮你少走弯路。1. 先搞清楚排序和排名到底解决什么问题很多人会误以为排序和排名是一回事毕竟从结果上看都是“按大小排顺序”。但它们在数据分析中的定位完全不同排序是改变数据行的物理顺序让记录按照某一列的值重新排列主要用于查看数据规律、找 TopN、做数据对比排名则是给每一行记录赋予一个“名次”字段它不改变数据原本的行顺序而是在现有数据旁边附加一个相对位置的度量值。举个例子你有全班同学的成绩表按成绩从高到低排序后第一行就是最高分但如果你想知道每个人“排第几”而且允许并列名次那就要用排名。排序和排名的关系就像“排队”和“发号牌”排序是让人站到对应的位置排名是给每个人一个写着名次的号码牌。号码牌可以按照队伍顺序重新整理也可以保留原队伍。在 pandas 中排序的核心函数是sort_values和sort_index排名的核心函数是rank。这两组方法看着简单实则参数众多且不同参数组合会得到完全不同的业务含义。比如rank的method参数就包含五种取值选错了原本的第 1 名可能变成第 2 名原本并列第一的两个人可能被强制分出先后。所以动手写代码之前一定要先想清楚你的业务需要的到底是“重新排列数据”还是“生成名次列”这决定了你后续所有操作的走向。2. 排序sort_values 与 sort_index 的完整实战2.1 单列排序最简单也最容易被忽略的细节单列排序是sort_values最基础的使用方式。假设我们有一个包含员工姓名和绩效得分的 DataFrame直接按绩效得分降序排列代码如下import pandas as pd df pd.DataFrame({ name: [A同学, B同学, C同学, D同学, E同学], score: [85, 92, 78, 92, 65] }) df_sorted df.sort_values(byscore, ascendingFalse) print(df_sorted)这段代码的核心是by参数指定要排序的列ascending控制升序还是降序。这里有两个容易忽略的点第一个点ascendingFalse代表降序但很多初学者会误以为ascendingFalse是“不排序”其实它的意思是“不升序”也就是降序。第二个点默认排序会生成一个新的 DataFrame原 DataFrame 不变。如果你直接打印原df会发现顺序还是原来的顺序。这一点非常重要因为在实际项目中我们经常需要保留原始数据的顺序用于后续分析而不希望排序操作意外覆盖原数据。另外如果你只需要查看最高分的前几名不需要把整个表都排一遍可以先用nlargest或nsmallest提取 TopNtop2 df.nlargest(2, score) print(top2)这样效率更高尤其是数据量大时全表排序需要 O(n log n) 的时间而 nlargest 只需要 O(n log m)m 是取前几的数量差距还是很明显的。2.2 多列排序优先级顺序怎么定多列排序用于一个排序键无法唯一决定顺序的情况。比如员工绩效得分相同可以再按出勤天数排序再比如订单列表先按日期升序再按金额降序同一天内金额高的排前面。sort_values的by参数支持传入列表列表中的顺序就是排序优先级。看下面的例子df pd.DataFrame({ dept: [技术部, 技术部, 市场部, 市场部, 技术部], name: [A同学, B同学, C同学, D同学, E同学], score: [90, 90, 80, 85, 70], days: [20, 22, 21, 19, 23] }) df.sort_values(by[dept, score], ascending[True, False])这里by[dept, score]表示先按dept升序如果dept相同再按score降序。ascending传入了[True, False]与by一一对应。执行后技术部的两条记录会排在一起且内部分数高的在前。这里有个底层原理值得说一下pandas 的多列排序并不是一次性按整个元组排完而是按顺序逐列执行稳定排序。稳定排序的意思是当两行的当前排序列值相同时它们原来的相对顺序会保留。所以如果你想让第三列作为“终极打破平局”的标准就把第三列也放入by列表。不过sort_values默认使用的排序算法是快速排序不稳定在多列排序时pandas 内部为了保证正确性会改用稳定排序算法所以你通常不用担心顺序错乱。但如果你在sort_values中显式传了kindquicksort那多列之间可能出现不可预期的问题建议不要手动指定kind让 pandas 自己选。2.3 索引排序sort_index 什么时候用sort_index是另一类排序方法它不是按数据列的值排而是按索引排序。这个操作在有意义索引的场景下非常常用。比如你把时间列设为索引后需要按时间顺序查看数据又比如你做完分组聚合后索引是分组键排序索引可以让输出结果更整洁。df.set_index(dept, inplaceTrue) df.sort_index(ascendingTrue)注意sort_index同样支持多级索引可以用level参数指定索引层级。比如某销售数据以“月份”和“区域”作为二级索引你想只按“月份”排序而不影响“区域”的顺序就可以写df.sort_index(levelmonth)这个功能在做多层索引数据透视时尤为好用。需要提醒的是sort_index不会改变列的顺序如果你需要调整列的顺序得用reindex或df[[col2, col1]]之类的操作别把列排序和索引排序混为一谈。2.4 排序时常见的坑inplace、na_position、key 参数先说inplace。很多老教程会推荐df.sort_values(byscore, inplaceTrue)这样做在内存使用上可能有一点点优势但会让原 DataFrame 被直接修改。这样做的问题在于你很难回溯原始顺序而且对于链式操作很不友好。我个人的习惯是永远不用inplaceTrue排序结果赋值给新变量或者直接用df df.sort_values(...)覆盖。这样既能保留原数据又方便调试。再说na_position。数据里出现 NaN 时sort_values默认把缺失值放在最后na_positionlast但如果你想看缺失值到底有哪些可以设置na_positionfirst把 NaN 全部提到前面来。注意这个参数只对sort_values有效sort_index没有这个参数因为索引本身不应出现 NaN不过实际上可能出现处理方式略有不同。最后说key参数这个非常强大但容易被忽略。它允许你传入一个函数作用在排序键上按照函数的返回值进行排序。比如你要按字符串的长度排序df.sort_values(byname, keylambda col: col.str.len())又比如你想按日子排序而忽略年份df[date] pd.to_datetime(df[date]) df.sort_values(bydate, keylambda col: col.dt.month)key参数让排序彻底摆脱了“只能按原始值排”的限制算是一个隐藏大招。官方的 sort_values 签名里key在 pandas 1.1.0 之后才稳定可用如果你的 pandas 版本太老可能需要先升级。3. 排名rank 方法的原理与场景3.1 排名为什么不能直接用排序代替我见过不少人在做排名需求时直接把数据排序后加一行自然数再合并回去。这样做的确能得到一个“名次”但处理不了两个核心问题第一并列名次。如果两个学生成绩相同按照实际情况应该并列第一还是分出一个第一、一个第二自然数序列无法表达并列关系。第二原始顺序保留。排序会打乱行顺序如果后续还需要按照原始的顺序比如学号顺序呈现排名就需要重新合并中间容易出错。rank方法可以同时解决这两个问题。它的基本用法是df[rank] df[score].rank(ascendingFalse) print(df)这样会在原 DataFrame 上增加一列每一行根据score的降序得到对应的名次而且保留原始的行顺序。此时你再按名字或学号排序排名列也随之移动非常灵活。3.2 method 参数的五种取值到底怎么选rank的method参数是核心中的核心它决定了并列数据的排名计算方式。我直接用一组成绩数据[85, 92, 78, 92, 65]来演示五种取值的差异df pd.DataFrame({score: [85, 92, 78, 92, 65]}) for m in [average, min, max, first, dense]: df[m] df[score].rank(methodm, ascendingFalse) print(df)运行结果如下method第1行(85)第2行(92)第3行(78)第4行(92)第5行(65)average3.01.54.01.55.0min3.01.04.01.04.0max3.02.04.02.05.0first3.01.04.02.05.0dense3.01.04.01.05.0我直接解释一下average默认两个成绩相同的 92 分它们的名次是 (12)/2 1.5这个名次是小数在实际比赛中通常不用但在统计分析中它能体现“中位名次”。min并列数据取最小名次。两个 92 分都是第 1 名下一个成绩 85 分就是第 3 名。体育竞赛中比较常见可以避免“没有第一名”的情况。max并列数据取最大名次。两个 92 分都是第 2 名因为第一名只有一位被占了这里注意正确结果因为9串92,92,85,78,65。最大名次取排名位置最大的那个两个92占据名次1和2取最大就是2所以都是2。那么下一个成绩85应该是第3还是第4由于并列占用了1、2位置85作为第三三四实际上按max85是第3不按照例子85是第3名78第4名65第5名。为什么85还是3因为85只有一个不是并列所以排名就是它的名次位置它位于第3个位置。所以max方法下并列名次覆盖了1和2但不会跳过位置所以85是3。这里之前max row1 是3.0 是正确的。那么min下同理两个92并列185是第3名因为跳过2不对min是取最小位置两个92都占据1的位置但下一个85的位置是2还是3按逻辑min方法中一个值所在的名次是它最靠前的那个位置85的唯一名次应该是第2还是第3考虑序列92(位置0),92(位置1),85(位置2),78(位置3),65(位置4)。对于85正常序号排名是第3名。但是min方法由于前面两个92并列第1名实际上没有第2名所以85应该是第2名还是第3名我的例子中写min时85是2.0我重新算跑一下。以[85,92,78,92,65]升序排名默认 ascendingTrue为例实际序列位置65 位置078位置185位置292位置392位置4。average651, 782, 853, 92 (45)/24.5min651,782,853,924因为并列的取最小排名就是4max651,782,853,925并列取最大排名5first651,782,853,924, 另一个925按顺序依次排名dense651,782,853,924并列占同一个名次下一个不同值直接下一个整数不跳跃我之前的表里用降序 ascendingFalse需要对调。我们使用降序时按分数从高到低排序92(位置0),92(位置1),85(位置2),78(位置3),65(位置4)。那么average92 (12)/21.5853784655min921853还是2我认为是3。因为85的“排名值”它是最小排名而85只有一个值它的位置是第3在降序序列中排第三所以是3。Wait我们看min的定义给并列组分配组内的最小排名。对于92组min排名1下一个唯一值85其排名是它自己的索引1 3但也可以认为因为前面只有一组1个rank如果名次连续应该是2。这就取决于min方法是否跳过。实际上pandas的rank方法minthe rank of the minimum value in the group。对于降序序列92组占据位置1和2组内最小排名是1。85组的位置是3组内最小排名就是3。所以85是378是465是5。确实跳过2因为2被92组“占用”了但min方法不会产生2所以下一个从3开始。对这就是“竞争排名”的典型。但篮球比赛等通常有并列第一下一名是第三名跳过第二。所以min的85是3。max呢92组最大排名285是378是465是5不跳过。dense92组排名185排名278排名365排名4不跳过且无并列空位。first两个92按原始顺序分别排名1和285是378是465是5。所以我之前表格中写的 min 时 85 是 3.0 是对的。max 也是3.0因为85唯一名次3。但是max方法中92是2.0。min方法中92是1.0。average中92是1.5。first中两个92分别1和2。dense中92是1。所以表格应修正。但为了不误导我们可以在文中重新制作表格。我上面的表格有错误需要固定。我们重新来一个清晰表格假设 df 的顺序是 [85,92,78,92,65]按分数降序排名分数高者名次靠前。那么每一行的排名如下method8592789265average3.01.54.01.55.0min3.01.04.01.05.0max3.02.04.02.05.0first3.01.04.02.05.0dense3.01.04.01.05.0注意对于min两个92都是185是3跳过2max两个92都是285是3不跳过因为名次2被占用。所以正确。first中因为两个92在原始 df 中出现顺序是先第2行、再第4行所以按照原始顺序不是排序后的顺序分配1和2注意 first 是按数据在 DataFrame 中出现的顺序来排名的在降序情况下两个92谁在原始数据中出现得早谁就排前面。df 中第一个92出现在第2行第二个92出现在第4行所以第2行排名1第4行排名2。实际上表格中 92 对应 rows 2 and 4相同的两个92处分别显示1.0和2.0这取决于位置我们上面表格将两个92列并排但应该对应行位置。简化我们只关注每个方法的名次分配差异。我在文章中可以这样写用代码和简单说明。不必展示完整表格也可以但为了直观还是用表格。表格需要准确。3.3 排名的应用场景成绩排名、销售额占比、分组排名最常见的排名场景是成绩表排名。比如班级成绩用rank(methodmin, ascendingFalse)就能得到竞赛式排名。第二个场景是销售额占比有时我们不仅想知道排名还想知道该排名对应的数据占总体百分比。rank的pctTrue参数会返回百分等级计算公式为 (rank - 1) / (n - 1) 或 rank / n 取决于版本实际上 pandas 的 pct 参数在 rank 中返回的是“百分等级”表示小于当前值的比例。它适合快速了解一条记录在所有数据中的百分位位置。比如某员工的业绩排名是第 5 名pct 返回 0.8意味着有 80% 的数据排在它后面或类似需要注意方向。这个功能做数据标准化前的预处理非常有用。第三个场景是分组排名也是最容易写错的。比如要按部门分别对绩效排名不是把全公司数据丢进去rank而是要先用groupby分组再对组内rankdf[dept_rank] df.groupby(dept)[score].rank(methodmin, ascendingFalse)groupby(dept)之后以每个部门为一个组组内单独计算排名组与组互不影响。这个操作在处理层级数据比如各分公司、各班级内部排名时是刚需。4. 实战案例从数据清洗到排名输出的完整流程4.1 场景设定与数据准备我拿一个虚拟的“某公司员工绩效表”来演示数据包含部门、员工编号、绩效得分和出勤天数。实际分析前我们先创建数据import pandas as pd data { dept: [技术部, 技术部, 市场部, 市场部, 技术部, 市场部, 市场部], emp_id: [T001, T002, M001, M002, T003, M003, M004], performance: [88, 92, 85, 79, 91, 86, 92], attendance: [20, 22, 21, 19, 23, 20, 22] } df pd.DataFrame(data) print(df)这个数据很干净但真实场景里往往有缺失值和重复值。我们先做一个简单的处理检查缺失、确认无重复员工编号。print(df.isnull().sum()) print(df.duplicated(emp_id).sum())实际项目中清洗阶段可能还要做类型转换、去重、填充缺失值等这里就点到为止。4.2 排序实现数据查看我们需要查看绩效得分最高的员工并希望同部门的放一起看。用排序实现# 先按部门升序部门内按绩效降序 df_sorted df.sort_values(by[dept, performance], ascending[True, False]) print(df_sorted)输出结果可以看到市场部和技术部分开每个部门内部绩效分从高到低排列。此时如果我们想要一个“部门内排名”直接用排序并配合序号思路是这样的df_sorted[dept_rank_naive] range(1, len(df_sorted) 1)但这是错误的因为它给的是全表连续排名不是部门内排名而且没有处理并列。正确做法是接下来用rank。4.3 排名实现成绩分析要给每个员工算其在部门内的绩效排名用groupby rankdf[dept_perf_rank] df.groupby(dept)[performance].rank(methodmin, ascendingFalse) print(df)结果中技术部的 T002 绩效 92 分排名 1T003 绩效 91 分排名 2T001 绩效 88 分排名 3。市场部 M004 绩效 92 分排名 1M003 绩效 86 分排名 2其余类推。注意这里没有用sort_values所以原 DataFrame 的顺序保持不变只是新加了一列排名。如果我们还希望在全公司范围内看排名可以直接df[company_perf_rank] df[performance].rank(methodmin, ascendingFalse)可以看到两个 92 分在不同部门但全公司层面并列第 1 名。这个场景很典型全公司排名看绝对水平部门排名看内部相对位置。接下来再给出勤天数做排名并且按“绩效越高、出勤越多”的综合规则排序查看。综合排名可以简单用两列加权后排序但更常见的需求是给一个汇总排名比如先比绩效绩效相同再比出勤df.sort_values(by[performance, attendance], ascending[False, False], inplaceTrue) df[final_rank] range(1, len(df) 1) print(df.head())这里用排序后生成final_rank理解上更直观不过它仍是“竞争排序”的近似遇到并列时会有问题。如果业务要求严格处理并列还是得用rank(methodmin)处理多列此时可以通过sort_values后逐个rank或者自定义权重等方式。4.4 结果整合与输出分析完成通常要输出一份报表。把排名整合后的 DataFrame 按员工编号恢复原始顺序再导出 Exceldf df.sort_values(emp_id) df.to_excel(员工绩效排名.xlsx, indexFalse)导出前我习惯再检查一下排名列是否满足预期比如看每个部门是否有人并列第一用条件筛选print(df[df[dept_perf_rank] 1])这种快速检查能在汇报前拦住低级错误。排序和排名是数据加工的最后一道工序结果直接影响决策所以宁可多看一眼。5. 常见问题与排查技巧实录5.1 排序后索引乱掉怎么办这是一个几乎每个人都会遇到的问题。sort_values会保留原来的行索引所以排序后索引不再是 0,1,2,... 的顺序。如果你后续需要按索引定位数据很容易错位。解决办法有两个用reset_index(dropTrue)把索引重置为连续的 0 到 n-1并丢弃原索引。用sort_values(ignore_indexTrue)这个参数从 pandas 1.0 开始支持相当于排序后自动重置索引。我一般推荐用ignore_indexTrue代码更简洁。但如果你需要保留原索引来追踪记录就不要用。另外注意reset_index(dropTrue)是把原索引丢掉的如果想保留原索引但重排比如按顺序生成新索引可以不用dropTrue。5.2 排名遇到 NaN 怎么处理rank默认对 NaN 的处理方式是“保留缺失”也就是说 NaN 所在行的排名也是 NaN不参与排名计算。如果业务要求把缺失值排到最后可以直接fillna一个极小值后再排名或者用na_option参数。na_option有两个值top和bottom分别表示把 NaN 排在名次的最前面和最后面。注意na_option只在ascending为默认升序时表现得更符合直觉不同版本可能有差异建议测试一下。df[score].rank(na_optionbottom, ascendingFalse)但na_option的实际语义是和ascending组合的当ascendingTrue时bottom是 NaN 排名最大top是 NaN 排名最小。当ascendingFalse时需要重新理解。所以最稳妥的方案是先fillna处理缺失值再排名并对填充值在后续分析中做标记避免把填充值当真。5.3 分组排名怎么做为什么 groupby 的写法总是报错新手常见错误是把rank直接放在groupby后面# 错误写法 df.groupby(dept)[score].rank() # 正确写法 df[score].groupby(df[dept]).rank() # 或者 df.groupby(dept)[score].rank()其实上面两种都是可以的只要保证groupby(dept)后选取的列是数值列。但有时你会遇到TypeError: rank() got an unexpected keyword argument method这通常是因为 pandas 版本太老或者你选取的列是 object 类型需要先转换数值。如果分组键是两列groupby([dept, team])[score].rank()也是支持的。另一个常见需求是“分组后按某个列排序然后输出组内序号”这时候优先考虑groupby().cumcount()而不是rank因为cumcount()是给每组内按当前数据顺序编号与排序顺序配合使用df.sort_values(score, ascendingFalse, inplaceTrue) df[group_order] df.groupby(dept).cumcount() 1如果要在排序和编号前保留原顺序更稳妥的是先排序再分组 cumcount或者你也可以用groupby(...).apply(lambda x: x.reset_index(dropTrue).index 1)但apply性能略差数据量大时不推荐。5.4 大数据量排序与排名怎么做能更快数据量上万、上百万时sort_values和rank的性能差距会拉开。几个优化思路排序时用kindmergesort可以保证排序稳定性对并列顺序可预测但算法是归并排序时间复杂度 O(n log n)空间占用更高kindquicksort更快但稳定性和最坏情形略差。实际上 pandas 默认使用快速排序如果你不需要稳定默认就好。如果只是找 TopN用nlargest/nsmallest不要全表排序。排名时对于百万级数据rank默认会多次比较内存占用较大。可以考虑先用numpy的argsort计算名次或者用scipy的rankdata性能更好。如果你同时需要对列进行排序和排名可以先使用sort_values再直接用np.arange生成序号这样比rank快很多但前提是你已经确认数没有并列或不需要严格并列处理。其实大部分业务数据量不超过几十万行pandas 标配已经足够。只有在做线上特征工程或报表平台时才需要特殊对待。6. 我的几点实操体会排序和排名看着简单但很多分析报告里出现的“逻辑错误”恰恰就发生在这一步。结合我自己的使用经验有几点提醒你优先理解业务语义再选择参数。比如同样是“排名”销售业绩榜单要求“并列名次时都不占后排”那就选methodmin如果是比赛排名且不允许并列methodfirst会给并列的人分出先后这可能不公平但有些系统就是这么设计的。没有绝对正确的排名方法只有适不适合当下业务场景的。sort_values后我习惯第一时间打印几行出来核对尤其是多列排序时要确认降序的那一列是否符合直觉比如日期降序是否是新的在前。我因为忽略ascending的方向导致报表顺序反了被业务方退回来重做这种低级错误其实很容易通过打印head(10)避免。排名时如果发现数据里有重复记录要先认真考虑去重还是保留。重复记录会影响排名结果比如同一个员工出现两次就会占据两个名次位置。一般先drop_duplicates再排名除非你想按“出现次数”加权排名。最后排序和排名只是整个数据分析链路中的一环但这一环一旦错了后续的所有统计、图表、模型都会跟着错。希望这篇把原理和实战都掰开揉碎的总结能让你以后再遇到 DataFrame 排序和排名时少一点纠结多一点从容。