set_index 把一列变成行标签,reset_index 再把它变回来。 sort_values 排序不改行标签,所以排完之后按标签取还是对的。 rank 有三种并列处理法:min 取最小名次、dense 紧凑、average 取平均。
这一篇收第 3 章的尾。三件事:换行标签、排序、排名。都是「让表好看也好用」的活。 第 4 章会把这些动作串起来做分组和合并,行标签用得好,后面省一半力气。
一、把一列设成行标签:set_index
df.set_index('姓名') 之后,行标签是姓名,列少一列。
shape 从 (10, 6) 变成 (10, 5),因为姓名从列搬到了行标签上。
取张伟那行只需 d.loc['张伟'],不用再写条件筛。
set_index 默认把这列从列里删掉。要两边都留着就写 drop=False。
行标签理想上应该是唯一的。有重名的时候 loc 会返回多行,得留神。
二、换回来:reset_index
reset_index() 把行标签变回一列,行标签恢复成 0 到 9。
删过行之后标签不连续,用 reset_index(drop=True) 直接丢掉旧标签重排。
drop=True 的意思是「旧标签不要了」。不写的话它会变成一列名叫 index 的列。
传不传 drop,差别就是多一列还是不多一列,这个列名还常常叫 index,很容易混进数据。
三、排序:sort_values
df.sort_values('总分', ascending=False),从大到小。默认是从小到大。
| 姓名 | 总分 |
|---|---|
| 王芳 | 277 |
| 赵敏 | 274 |
| 张伟 | 265 |
| 陈静 | 258 |
| 吴霞 | 256 |
排序不改行标签。张伟排到第三位,他的标签还是 0。
这正是标签比位置可靠的地方:位置会变,标签不会。
多列排序传列表:sort_values(['班级', '总分']),先按班级,班内再按总分。
两边顺序不同就传两个布尔值:sort_values(['班级','总分'], ascending=[True, False])。
四、排名:rank
df['总分'].rank(ascending=False, method='min') 给出名次。
| 姓名 | 总分 | 名次 |
|---|---|---|
| 张伟 | 265 | 3 |
| 王芳 | 277 | 1 |
| 李娜 | 223 | 8 |
| 刘洋 | 252 | 7 |
| 陈静 | 258 | 4 |
rank 返回的是小数,就算结果全是整数也是 1.0、2.0——因为平均名次可能是 1.5。
method='min' 是同分取最小名次:两人并列第一,下一个就是第三名。
method='dense' 并列后紧跟着排(1、1、2),method='average' 取平均名次(1.5、1.5、3)。
这张成绩表里没有同分,三种写法算出来一样。所以 Demo 末尾造了三个人的小表来验。 排名方式不同,结果就不同。报排名必须说清用的哪一种,不然没法对账。
五、排完想回原顺序:sort_index
sort_values 之后想回到原来的行顺序,一行 sort_index() 就行。
前提是行标签还留着原来的顺序信息——这又回到「排序不改标签」那条。
标签是身份,顺序是外观。两者分开,才谈得上「排了还能回来」。
六、第 3 章到这里就齐了
回头看这十三篇:读进来、看属性、抽行列、加改删、清洗、排序。 这些动作单独看都简单,组合起来才是日常:读一份 csv,清一遍,筛出想要的,排个序。 第 4 章开始把这些动作拼成真正的分析:分组、合并、时间序列、透视表。
如果这一章还有哪个动作需要翻回去查,先把它练熟再往下走。第 4 章默认这些都会。
七、动手 Demo:索引设置与排序排名
先把姓名设成行标签,再 reset 回来,然后按总分排序、算名次。 最后用多种 method 各算一遍名次,看并列时差在哪。
import pandas as pd
DF = pd.DataFrame({
"姓名": ["张伟", "王芳", "李娜", "刘洋", "陈静",
"杨帆", "赵敏", "黄磊", "周涛", "吴霞"],
"班级": ["一班"] * 5 + ["二班"] * 5,
"语文": [88, 92, 75, 85, 90, 68, 95, 72, 83, 79],
"数学": [92, 95, 68, 88, 73, 82, 91, 65, 86, 90],
"英语": [85, 90, 80, 79, 95, 71, 88, 60, 84, 87],
})
DF["总分"] = DF["语文"] + DF["数学"] + DF["英语"]
D = DF.set_index("姓名")
print("设标签 %s → %s,取张伟 %d" % (DF.shape, D.shape, D.loc["张伟", "总分"]))
print("reset 回来 %s" % (D.reset_index().shape,))
TOP = DF.sort_values("总分", ascending=False)
print(TOP[ ["姓名", "总分"]].head(5).to_string(index=False))
DF["名次"] = DF["总分"].rank(ascending=False, method="min").astype(int)
print(DF.loc[ [0, 1, 2, 3, 4], ["姓名", "总分", "名次"]].to_string(index=False))
TIE = pd.DataFrame({"姓名": ["甲", "乙", "丙"], "分": [100, 100, 90]})
for m in ["min", "dense", "average"]:
r = TIE["分"].rank(ascending=False, method=m).tolist()
print("%-8s %s" % (m, r))
实跑输出:
--------------------------------------------------------------------
索引设置与排序排名
--------------------------------------------------------------------
① 把一列设成行标签
--------------------------------------------------------------------
set_index 之后,行标签是姓名,列少了一列。shape 从 (10, 6) 变成 (10, 5)。
取张伟那行:d.loc['张伟'] 就行,不用再筛条件。
! set_index 默认把这列从列里删掉。要保留就写 drop=False。
② 换回来:reset_index
--------------------------------------------------------------------
reset_index 把行标签变回一列,行标签恢复成 0 到 9。
删过行之后行标签会不连续,用 reset_index(drop=True) 直接丢掉旧标签重排。
! drop=True 的意思是「旧标签不要了」。不写的话它会变成一列名叫 index 的列。
③ 排序:sort_values
--------------------------------------------------------------------
| 行 | 姓名 | 总分 |
|---|---:|---:|
| 1 | 王芳 | 277 |
| 6 | 赵敏 | 274 |
| 0 | 张伟 | 265 |
| 4 | 陈静 | 258 |
| 9 | 吴霞 | 256 |
ascending=False 是从大到小。默认是从小到大。
多列排序传列表:sort_values(['班级','总分']),先按班级排,班内再按总分排。
| 行 | 班级 | 姓名 | 总分 |
|---|---:|---:|---:|
| 1 | 一班 | 王芳 | 277 |
| 0 | 一班 | 张伟 | 265 |
| 4 | 一班 | 陈静 | 258 |
| 3 | 一班 | 刘洋 | 252 |
! 排序不改行标签。张伟排第几,他的标签还是 0——这正是标签比位置可靠的原因。
④ 排名:rank
--------------------------------------------------------------------
| 行 | 姓名 | 总分 | 总分排名 |
|---|---:|---:|---:|
| 0 | 张伟 | 265 | 3 |
| 1 | 王芳 | 277 | 1 |
| 2 | 李娜 | 223 | 8 |
| 3 | 刘洋 | 252 | 7 |
| 4 | 陈静 | 258 | 4 |
method='min' 是同分取最小名次。三个人并列第一,下一个就是第四名。
method='dense' 是并列之后紧跟着排(1、1、2),method='average' 取平均名次。
! 排名方式不同,结果就不同。报排名的时候要说清用的是哪一种,不然没法对账。
⑤ 索引与排序的配合
--------------------------------------------------------------------
| 要做的事 | 写法 |
|---|---|
| 按姓名查行 | set_index('姓名') 之后 loc |
| 删行后重排行号 | reset_index(drop=True) |
| 按某列排序 | sort_values('列名', ascending=False) |
| 算名次 | rank(ascending=False, method='min') |
| 排完仍按原顺序看 | sort_index() |
最后一条常常用得上:排完序想回到原来的顺序,sort_index 一下就行。
! 第 3 章到此结束。第 4 章把这些动作组合起来:分组、合并、时间序列。
三种 method 在「两人并列第一」时给的名次不一样:min 是 1、1、3,dense 是 1、1、2。
sort_values 之后打出来的行标签还是 1、6、0,顺序变了、标签没变,这就是证据。
30 秒实验
- 把 method 换成 'first',看并列两人谁排前面。
- 给表加一列班级,用两个布尔值做多列排序,看班内顺序对不对。
- 想一想:
set_index之后原表会不会少一列?想两边都留要加什么参数?
常见误区
- set_index 之后以为原列还在:列少了一列,后面按列名取会报 KeyError。
- reset_index 不写 drop:多出一列名叫 index 的列,混进数据里。
- 以为 sort_values 会重排行号:行标签原样不动,按位置取还是老的。
- 报排名不说 method:和别人的名次对不上,谁也说不清谁错。
自测三问(点开对答案)
1. 为什么 sort_values 之后还能按标签取到原来那行? —— 因为排序只改行的先后,不改行标签。标签是身份,位置才是顺序。 2. method='min' 和 'dense' 在两人并列第一时分别给什么名次? —— min 给 1、1、3(跳号),dense 给 1、1、2(不跳号)。 3. reset_index(drop=True) 里的 drop 在丢什么? —— 丢旧的索引值本身,让它不变成一列。不写就会多出一列叫 index 的列。第 3 章的十三篇到此结束。第 4 章开始把这些动作组合成真正的分析:分组、合并、时间序列。
- ☐ (本篇完,下一篇 4.1 求和、均值、最大最小)