📊 第4章 Pandas 进阶
📄 一句话概括

shift(1) 把整列往下挪一格,第一行空出来变成 NaN,这是「上一期」。 shift(-1) 往上挪,看的是「下一期」,做预测标签时常用。 原列减 shift 后的列就是升降;pct_change 直接给变化率,两个不是一回事。

📝 本章和前面几章的关系

前几篇的运算都是「同一行内部」。这一篇的运算要跨行——拿这一行和上一行比。 这是时间序列最基础的一块砖:环比、同比、增长量全靠它。

一、shift 往下挪一行

姓名 总分 上一位总分
张伟 265 nan
王芳 277 265.0
李娜 223 277.0

df['总分'].shift(1) 整列往下挪一格,第一行空出来变成 NaN。 原来第 2 行拿到的是第 1 行的值——这就是「上一期」。 第一行一定是空的。这是「没有上一期」的诚实表达,不要去填 0。

填 0 的后果:第一行的「升降」会算成「从 0 涨到 265」,凭空多出一个巨大涨幅。

二、shift(-1) 往上挪

姓名 总分 下一位总分
黄磊 197 253.0
周涛 253 256.0
吴霞 256 nan

给负数就是往上挪,最后一行空。 shift(-1) 看的是「下一期」,做预测标签的时候常用:拿今天的数据预测明天的值。 方向记牢:正数往下(看历史),负数往上(看未来)。

三、和原列一减就是升降

df['升降'] = df['总分'] - df['总分'].shift(1),得到 NaN、12、-54、29…… 第一个值是 NaN,因为「上一位」不存在——减出来还是 NaN,不会变成总分本身。 第二个 12 是 277 减 265。第三个 -54 是 223 减 277,说明降了。

pct_change() 直接给变化率:0.045、-0.195、0.130,也就是涨了 4.5%。 升降和变化率是两个东西。差值是绝对量,变化率是相对量,报告里要说清是哪个。 277 涨到 300 和 20 涨到 43,差值是 23 对 23,变化率是 8% 对 115%。

四、shift 的三个用处

场景 写法
算环比 列 减 列.shift(1)
算同比 列 减 列.shift(12)(月度数据)
造预测标签 列.shift(-1)

shift 挪的是「行」,不是「时间」。这是它最容易错的地方。 时间不连续(缺了几天、顺序乱了)的时候,shift(1) 拿到的是「上一行」而不是「昨天」。 所以用 shift 之前一定先按时间排序,并确认没有缺行。

五、动手 Demo:数据移位:shift 与升降比较

用十个总分演示 shift(1)、shift(-1) 和相减得到的升降。 最后比一比差值和变化率在同一段数据上的不同说法。

import pandas as pd

N = ["张伟", "王芳", "李娜", "刘洋", "陈静",
     "杨帆", "赵敏", "黄磊", "周涛", "吴霞"]
T = [265, 277, 223, 252, 258, 221, 274, 197, 253, 256]
D = pd.DataFrame({"姓名": N, "总分": T})

D["上一位"] = D["总分"].shift(1)
D["下一位"] = D["总分"].shift(-1)
D["升降"] = D["总分"] - D["上一位"]
D["变化率"] = D["总分"].pct_change().round(3)

print(D.head(4).to_string(index=False))
print(D.tail(2).to_string(index=False))
print("升降总和 %d,首尾差 %d" % (D["升降"].sum(), T[-1] - T[0]))

实跑输出:

--------------------------------------------------------------------
数据移位:shift 与升降比较
--------------------------------------------------------------------

  ① shift 往下挪一行
  --------------------------------------------------------------------

| 行 | 姓名 | 总分 | 上一位总分 |
|---|---:|---:|---:|
| 0 | 张伟 | 265 | nan |
| 1 | 王芳 | 277 | 265.0 |
| 2 | 李娜 | 223 | 277.0 |

  shift(1) 把整列往下挪一格,第一行空出来变成 NaN。
  原来第 2 行拿到的是第 1 行的值——这就是「上一期」。
  ! 第一行一定是空的。这是「没有上一期」的诚实表达,不要去填 0。

  ② shift(-1) 往上挪
  --------------------------------------------------------------------

| 行 | 姓名 | 总分 | 下一位总分 |
|---|---:|---:|---:|
| 7 | 黄磊 | 197 | 253.0 |
| 8 | 周涛 | 253 | 256.0 |
| 9 | 吴霞 | 256 | nan |

  给负数就是往上挪,最后一行空。
  shift(-1) 看的是「下一期」,做预测标签的时候常用。
  ! 记住方向:正数往下(看历史),负数往上(看未来)。

  ③ 和原列一减就是升降
  --------------------------------------------------------------------

| 行 | 姓名 | 总分 | 升降 |
|---|---:|---:|---:|
| 0 | 张伟 | 265 | nan |
| 1 | 王芳 | 277 | 12.0 |
| 2 | 李娜 | 223 | -54.0 |
| 3 | 刘洋 | 252 | 29.0 |

  第一个值是 NaN,因为「上一位」不存在——减出来还是 NaN,不会变成总分本身。
  pct_change() 直接给变化率:0.045、-0.195、0.130。
  ! 升降和变化率是两个东西:差值是绝对量,变化率是相对量。报告里说清是哪个。

  ④ shift 的三个用处
  --------------------------------------------------------------------

| 场景 | 写法 |
|---|---|
| 算环比 | 列减列.shift(1) |
| 算同比 | 列减列.shift(12)(月度数据) |
| 造预测标签 | 列.shift(-1) |

  shift 的参数是按「行」挪,不是按时间挪。时间不连续的时候要小心。
  ! 时间序列的环比同比都靠 shift。前提是数据已经按时间排好序。

升降.sum() 等于 256 减 265 = -9——所有小段涨跌加起来正好是从头到尾的变化。 这是个好用的自检:升降之和必须等于首尾之差。对不上就说明中间有缺行。

30 秒实验

  1. 把 shift(1) 改成 shift(2),看第一、二行是不是都空了。
  2. 用 shift(1) 加上原列,还原成「上一位 + 升降」,看等不等于总分。
  3. 想一想:如果某天缺了一行数据,shift(1) 算出来的升降会是什么?

常见误区

  1. 第一行的 NaN 填 0:凭空造出一个巨大涨幅。
  2. 方向记反:shift(-1) 当成了上一期,环比算成「下比上」。
  3. 以为 shift 按时间挪:缺行的时候它照样只挪一行。
  4. 升降和变化率混着报:一个说「涨了 12」,一个说「涨了 4.5%」,读者对不上。
自测三问(点开对答案) 1. shift(1) 和 shift(-1) 分别看哪个方向? —— shift(1) 往下挪,拿到的是上一行的值;shift(-1) 往上挪,拿到的是下一行的值。 2. 为什么第一行不能填 0? —— 0 会变成一个真实的历史值,导致第一次升降算出「从 0 涨到 265」这种假象。 3. 升降之和与首尾差是什么关系? —— 必须相等。这是 shift 运算最好的自检手段,不相等说明数据有缺行或没排序。

一列里塞了两个字段怎么办?下一篇讲拆列,还有宽表和长表互转。

相关笔记