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 秒实验
- 把 shift(1) 改成 shift(2),看第一、二行是不是都空了。
- 用 shift(1) 加上原列,还原成「上一位 + 升降」,看等不等于总分。
- 想一想:如果某天缺了一行数据,shift(1) 算出来的升降会是什么?
常见误区
- 第一行的 NaN 填 0:凭空造出一个巨大涨幅。
- 方向记反:shift(-1) 当成了上一期,环比算成「下比上」。
- 以为 shift 按时间挪:缺行的时候它照样只挪一行。
- 升降和变化率混着报:一个说「涨了 12」,一个说「涨了 4.5%」,读者对不上。
自测三问(点开对答案)
1. shift(1) 和 shift(-1) 分别看哪个方向? —— shift(1) 往下挪,拿到的是上一行的值;shift(-1) 往上挪,拿到的是下一行的值。 2. 为什么第一行不能填 0? —— 0 会变成一个真实的历史值,导致第一次升降算出「从 0 涨到 265」这种假象。 3. 升降之和与首尾差是什么关系? —— 必须相等。这是 shift 运算最好的自检手段,不相等说明数据有缺行或没排序。一列里塞了两个字段怎么办?下一篇讲拆列,还有宽表和长表互转。
- ☐ (本篇完,下一篇 4.10 一列拆多列与行列转换)