0Pricing
Pandas & NumPy Academy · 课时

组内排名与百分位

使用 groupby().rank() 计算组内排名,并使用 pd.qcut 创建百分位分箱,以便进行相对比较。

组内排名与百分位 是 CoddyKit 上的免费 Pandas & NumPy Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Pandas & NumPy Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Pandas & NumPy Academy 课程共包含 4 节课。

为什么要在分组内排名?

原始值往往不如相对排名有意义。某位销售代表每月收入 50,000 美元时,如果平均值为 30,000 美元,他就是顶尖绩效者;但如果平均值为 80,000 美元,他的表现就较差。通过在分组内计算排名(例如在每个地区或每个季度内排名),您可以得到经过归一化的比较结果,从而考虑不同分组之间的基准差异。Pandas 通过 groupby().rank() 让分组内排名变得十分简单。

import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'rep': ['Alice', 'Bob', 'Carol', 'Dave', 'Eve',
            'Frank', 'Grace', 'Hank', 'Iris', 'Jake'],
    'region': ['East']*5 + ['West']*5,
    'sales': np.random.randint(30000, 100000, 10)
})
print(df.sort_values('region'))

Series.rank() — 基本排名

Series.rank() 会为每个值分配排名:排名 1 是最小值,排名 n 是最大值。ascending=False参数会反转方向,使排名 1 成为最大值。结果是浮点 Series,而不是整数 Series,因为默认情况下会通过对并列排名取平均值来处理并列。对于包含 5 个值的列,排名范围为 1.0 到 5.0;如果存在并列,某些值可能会共享 2.5 这样的排名。

import pandas as pd

s = pd.Series([80, 45, 90, 45, 70])

print('Values:', s.values)
print('Rank (ascending=True, default):', s.rank().values)
print('Rank (ascending=False — best=1):', s.rank(ascending=False).values)
# Note: two 45s share ranks 1 and 2 → both get 1.5

rank() 中的并列处理方法

method参数控制如何处理并列值。可选项包括:'average'(默认值——并列值共享其排名的平均值)、'min'(所有并列值都获得最低排名)、'max'(所有并列值都获得最高排名)、'first'(按出现顺序排名——不产生并列)以及 'dense'(排名中没有间隔——1、2、3、3、4 会变为 1、2、3、3、4,而不是 1、2、3、3、5)。对于百分位式排名,'dense'方法最为实用。

import pandas as pd

s = pd.Series([80, 45, 90, 45, 70])

result = pd.DataFrame({
    'value': s,
    'average': s.rank(method='average'),
    'min': s.rank(method='min'),
    'max': s.rank(method='max'),
    'first': s.rank(method='first'),
    'dense': s.rank(method='dense')
})
print(result)

使用 groupby().rank() 在分组内排名

groupby('group_col')['value_col'].rank()会独立地在每个分组内计算排名。排名会在每个分组开始时重置——因此,东部地区表现最佳者在东部排名为 1,西部地区表现最佳者在西部也排名为 1。这正是 groupby 排名适合进行公平跨组比较的原因。

import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'rep': list('ABCDEABCDE'),
    'region': ['East']*5 + ['West']*5,
    'sales': np.random.randint(30000, 100000, 10)
})

# Rank within each region (best = rank 1)
df['rank_in_region'] = df.groupby('region')['sales'].rank(ascending=False, method='min')
df_sorted = df.sort_values(['region', 'rank_in_region'])
print(df_sorted.to_string(index=False))

使用 rank(pct=True) 进行百分位排名

在 rank() 中设置 pct=True 会返回百分位排名:一个介于 0 和 1 之间的值,表示分组中小于或等于当前值的观测值所占的比例。百分位排名为 0.8 表示该值处于第 80 百分位——高于所有值中的 80%。这种归一化方式无需知道分组的实际大小,就能直接比较不同规模分组中的值。

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'name': list('ABCDEFGHIJ'),
    'region': ['East']*5 + ['West']*5,
    'score': np.random.randint(50, 100, 10)
})

# Percentile rank within region
df['pct_rank'] = df.groupby('region')['score'].rank(pct=True)
df['percentile'] = (df['pct_rank'] * 100).round(0).astype(int)
print(df.sort_values(['region', 'percentile'], ascending=[True, False]).to_string(index=False))

pd.qcut:基于分位数的分箱

pd.qcut(series, q)会将值划分为 q 个等频箱,使每个箱大约包含相同数量的观测值。与使用等宽区间的 pd.cut 不同,pd.qcut 会根据数据分布调整区间边界。这非常适合创建四分位数(q=4)、五分位数(q=5)或十分位数(q=10)绩效层级。

import pandas as pd
import numpy as np

np.random.seed(42)
sales = pd.Series(np.random.exponential(scale=50000, size=100))

# Divide into quartiles
quartiles = pd.qcut(sales, q=4, labels=['Q1', 'Q2', 'Q3', 'Q4'])

result = pd.DataFrame({'sales': sales, 'quartile': quartiles})
print('Quartile counts (should be ~25 each):')
print(result['quartile'].value_counts().sort_index())
print('\nMean sales per quartile:')
print(result.groupby('quartile')['sales'].mean().round(0))

pd.cut 与 pd.qcut

pd.cut(series, bins=n)创建等宽区间(范围相同,数量不同)。pd.qcut(series, q=n)创建等频区间(数量相同,范围不同)。对于偏态数据,pd.cut会导致尾部的箱几乎为空,而 pd.qcut 会均匀分配观测值。当区间边界具有自然的业务含义(如价格范围、年龄组)时请选择 pd.cut;如果要创建大小相等的绩效层级,请选择 pd.qcut。

import pandas as pd
import numpy as np

np.random.seed(0)
# Right-skewed data
data = pd.Series(np.random.exponential(1, 100))

cut_result = pd.cut(data, bins=5).value_counts().sort_index()
qcut_result = pd.qcut(data, q=5).value_counts().sort_index()

print('Equal-width bins (pd.cut) — uneven counts:')
print(cut_result.to_string())
print('\nEqual-frequency bins (pd.qcut) — even counts:')
print(qcut_result.to_string())

使用 pd.qcut 创建十分位标签

pd.qcut(series, q=10, labels=range(1,11))会将每个观测值分配到其十分位数(1 到 10)。这是一种常见的标准技术,应用于营销分析(客户价值十分位)、信用评分(风险十分位)和 AB 测试(用于群组分析的流量十分位)。labels参数可以是与 q 长度相同的任意列表——使用 ['Bottom 10%', ..., 'Top 10%'] 这样的字符串可以让输出更易读。

import pandas as pd
import numpy as np

np.random.seed(7)
customer_value = pd.Series(np.random.exponential(500, 1000))

# Assign to deciles 1-10
decile_labels = [f'D{i}' for i in range(1, 11)]
customer_decile = pd.qcut(
    customer_value,
    q=10,
    labels=decile_labels
)

result = pd.DataFrame({'value': customer_value, 'decile': customer_decile})
print('Mean customer value per decile:')
print(result.groupby('decile')['value'].mean().round(0))

分组内的百分位分箱

将 groupby 与 pd.qcut 结合,并使用 transform,即可在每个分组内创建百分位箱。当您想在每个地区内对客户进行排名,而不是在全局范围内排名时,这种方法非常有用——某位客户可能处于全局十分位的底部,却处于其所在地区十分位的顶部。请使用 groupby('group')['value'].transform(lambda x: pd.qcut(x, q=4, labels=['Q1','Q2','Q3','Q4']))。

import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame({
    'customer': range(20),
    'region': ['North']*10 + ['South']*10,
    'spend': np.random.randint(100, 1000, 20)
})

# Quartile within each region
def quartile_label(x):
    return pd.qcut(x, q=4, labels=['Q1','Q2','Q3','Q4'])

df['region_quartile'] = df.groupby('region')['spend'].transform(quartile_label)
print(df.sort_values(['region', 'region_quartile']).to_string(index=False))

分组内的前 N 名

一个常见的业务问题是:“每个地区表现最好的 3 个人是谁?”请使用 groupby().rank(),然后按排名筛选:df[df['rank_col'] <= 3]。这种方法适用于任意分组大小,并能妥善处理并列:如果截止位置存在并列排名,它会保留超过 3 行。另一种方法是使用 groupby().nlargest(n),它会直接返回每个分组中最大的 n 个值,而无需添加排名列。

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'rep': [f'Rep_{i}' for i in range(15)],
    'region': ['East']*5 + ['West']*5 + ['North']*5,
    'revenue': np.random.randint(40000, 120000, 15)
})

df['region_rank'] = df.groupby('region')['revenue'].rank(ascending=False, method='min')

print('Top 2 per region:')
top2 = df[df['region_rank'] <= 2].sort_values(['region', 'region_rank'])
print(top2[['rep', 'region', 'revenue', 'region_rank']].to_string(index=False))

结合 Rank 和 Transform 进行归一化

您可以将 groupby().rank(pct=True) 与 transform 结合,在原始 DataFrame 中添加百分位排名列。与原始值相比,这个归一化列通常更适合作为模型特征——它与尺度无关,并且可以在分组之间进行比较。对于机器学习,百分位排名是标准化(z 分数)的一种简单替代方案,并且对异常值更加稳健。

import pandas as pd
import numpy as np

np.random.seed(1)
df = pd.DataFrame({
    'product': np.random.choice(['A', 'B', 'C'], 30),
    'score': np.random.randint(50, 100, 30)
})

# Percentile rank within each product group
df['global_pct'] = df['score'].rank(pct=True).round(3)
df['group_pct'] = df.groupby('product')['score'].rank(pct=True).round(3)

print(df.sort_values(['product', 'score']).head(12).to_string(index=False))

快速检查

请检验您对本课所学排名和百分位操作的理解。

课程回顾

本课中您学到了:Series.rank()可以使用可配置的并列处理方式计算数值排名;groupby().rank()会在每个分组内重置排名,以便进行公平的跨组比较;pct=True会将排名转换为百分位(0 到 1);pd.qcut会创建等频箱,用于分配四分位、十分位和百分位层级。接下来,我们将探索 Pandas 性能优化技巧——性能分析、避免缓慢循环以及使用高效的数据类型。

常见问题解答

「组内排名与百分位」课时是免费的吗?

是的 — 「组内排名与百分位」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Pandas & NumPy Academy 课程的其余内容,请升级到 CoddyKit PRO。 Pandas & NumPy Academy 课程共包含 4 节课。

「组内排名与百分位」这节课中我会学到什么?

使用 groupby().rank() 计算组内排名,并使用 pd.qcut 创建百分位分箱,以便进行相对比较。 你通过在浏览器中直接运行的动手代码来练习 Pandas & NumPy Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Pandas & NumPy Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Pandas & NumPy Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「组内排名与百分位」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Pandas & NumPy Academy 课中编写并运行代码吗?

能。每节 Pandas & NumPy Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 滚动窗口
  2. 扩展窗口
  3. 指数加权移动平均
  4. 组内排名与百分位
← 返回 Pandas & NumPy Academy