📊 第3章 Pandas 入门
📄 一句话概括

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 秒实验

  1. 把 method 换成 'first',看并列两人谁排前面。
  2. 给表加一列班级,用两个布尔值做多列排序,看班内顺序对不对。
  3. 想一想:set_index 之后原表会不会少一列?想两边都留要加什么参数?

常见误区

  1. set_index 之后以为原列还在:列少了一列,后面按列名取会报 KeyError。
  2. reset_index 不写 drop:多出一列名叫 index 的列,混进数据里。
  3. 以为 sort_values 会重排行号:行标签原样不动,按位置取还是老的。
  4. 报排名不说 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 章开始把这些动作组合成真正的分析:分组、合并、时间序列。

相关笔记