
10 个用于时间序列分析的实用 NumPy 单行代码
图片由 Editor | ChatGPT 提供
引言
处理时间序列数据通常意味着要一遍又一遍地应对相同的模式:计算移动平均值、检测峰值、为预测模型创建特征。大多数分析师发现自己会为一些操作编写冗长的循环和复杂的函数,而实际上这些操作可以用 NumPy 以单行优雅且易于维护的代码解决。
NumPy的数组操作可以帮助简化大多数常见的时间序列操作。你可以应用向量化操作一次性处理整个数据集,而不必逐步思考数据转换的过程。
本文介绍了10个NumPy单行代码,可用于你经常会遇到的时间序列分析任务。让我们开始吧!
示例数据
让我们创建真实的时间序列数据来检验我们的每一个单行代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import numpy as np import pandas as pd from datetime import datetime, timedelta # 创建示例时间序列数据 np.random.seed(42) dates = pd.date_range('2023-01-01', periods=100, freq='D') trend = np.linspace(100, 200, 100) seasonal = 20 * np.sin(2 * np.pi * np.arange(100) / 30) noise = np.random.normal(0, 5, 100) values = trend + seasonal + noise # 用于示例的附加样本数据 stock_prices = np.array([100, 102, 98, 105, 107, 103, 108, 112, 109, 115]) returns = np.array([0.02, -0.03, 0.05, 0.01, -0.02, 0.04, 0.03, -0.01, 0.02, -0.01]) volumes = np.array([1000, 1200, 800, 1500, 1100, 900, 1300, 1400, 1050, 1250]) |
生成样本数据后,让我们来看看我们的单行代码。
1. 为预测模型创建滞后特征
滞后特征通过将数值在时间上向后移动来捕捉时间依赖性。这对于自回归模型至关重要。
1 2 3 |
# 创建多个滞后特征 lags = np.column_stack([np.roll(values, i) for i in range(1, 4)]) print(lags) |
截断的输出
1 2 3 4 5 6 7 8 9 10 |
[[217.84819466 218.90590418 219.17551225] [102.48357077 217.84819466 218.90590418] [104.47701332 102.48357077 217.84819466] [113.39337757 104.47701332 102.48357077] ... ... ... [217.47142868 205.96252929 207.85185069] [219.17551225 217.47142868 205.96252929] [218.90590418 219.17551225 217.47142868]] |
这会生成一个矩阵,其中每一列分别代表移动了1、2和3个周期的值。前几行包含从序列末尾循环过来的值。
2. 计算滚动标准差
滚动标准差是衡量波动性的一个很好的指标,在风险评估中尤其有用。
1 2 3 |
# 5周期滚动标准差 rolling_std = np.array([np.std(values[max(0, i-4):i+1]) for i in range(len(values))]) print(rolling_std) |
截断的输出
1 2 |
[ 0. 0.99672128 4.7434077 7.91211311 7.617056 6.48794287 ... ... ... 6.45696044 6.19946918 5.74848214 4.99557589] |
我们得到一个数组,显示了波动性随时间的变化,其中早期的值是在较少的周期上计算的,直到完整的窗口可用。
3. 使用Z分数法检测异常值
异常值检测有助于识别由市场事件或数据质量问题引起的异常数据点。
1 2 3 |
# 识别超过2个标准差的异常值 outliers = values[np.abs((values - np.mean(values)) / np.std(values)) > 2] print(outliers) |
输出
1 |
[217.47142868 219.17551225 218.90590418 217.84819466] |
这将返回一个仅包含显著偏离均值的数组,对于标记异常时期非常有用。
4. 计算指数移动平均线
有时,你可能需要指数移动平均线(EMA)而不是常规移动平均线,EMA对最近的观测值赋予更多权重。这使其对趋势变化更为敏感。
1 2 |
ema = np.array([values[0]] + [0.3 * values[i] + 0.7 * ema[i-1] for i, ema in enumerate([values[0]] + [0] * (len(values)-1)) if i > 0][:len(values)-1]) print(ema) |
嗯,这不会按预期工作。这是因为指数移动平均线的计算本质上是递归的,并且在向量化形式下进行递归并不直接。上面的代码会引发一个 TypeError 异常。但你可以在笔记本中取消注释上面的代码单元格并亲自检查。
这是一个更简洁且可行的方案。
1 2 3 4 5 6 |
# 更易读的EMA计算 alpha = 0.3 ema = values.copy() for i in range(1, len(ema)): ema[i] = alpha * values[i] + (1 - alpha) * ema[i-1] print(ema) |
截断的输出
1 2 |
[102.48357077 103.08160353 106.17513574 111.04294223 113.04981966 ... ... ... 200.79862052 205.80046297 209.81297775 212.54085568 214.13305737] |
我们现在得到一个平滑的序列,与简单移动平均线相比,它对最近的变化反应更快。
5. 寻找局部最大值和最小值
峰值和谷值检测对于识别趋势反转以及支撑位或阻力位非常重要。现在让我们在样本数据中找到局部最大值。
1 2 3 |
# 寻找局部峰值(最大值) peaks = np.where((values[1:-1] > values[:-2]) & (values[1:-1] > values[2:]))[0] + 1 print(peaks) |
输出
1 2 |
[ 3 6 9 12 15 17 20 22 25 27 31 34 36 40 45 47 50 55 59 65 67 71 73 75 82 91 94 97] |
我们现在得到了一个局部最大值出现位置的索引数组。这有助于识别潜在的卖点或阻力位。
6. 从价格变化计算累计回报
有时将绝对价格变化转换为累计表现指标会很有帮助。
1 2 3 |
# 从每日回报计算累计回报 cumulative_returns = np.cumprod(1 + returns) - 1 print(cumulative_returns) |
输出
1 2 |
[ 0.02 -0.0106 0.03887 0.0492587 0.02827353 0.06940447 0.1014866 0.09047174 0.11228117 0.10115836] |
这显示了随时间推移的总回报,对于性能分析和投资组合跟踪至关重要。
7. 将数据归一化到0-1范围
最小-最大缩放(Min-max scaling)确保所有特征都映射到相同的[0,1]范围,避免倾斜的特征值影响分析。
1 2 3 |
# 最小-最大归一化 normalized = (values - np.min(values)) / (np.max(values) - np.min(values)) print(normalized) |
截断的输出
1 2 3 |
[0.05095609 0.06716856 0.13968446 0.21294383 0.17497438 0.20317761 ... ... ... 0.98614086 1. 0.9978073 0.98920506] |
现在,这些值都被缩放到0和1之间,同时保留了原始分布的形状,并标准化了范围。
8. 计算百分比变化
百分比变化提供了与规模无关的变动度量。
1 2 3 |
# 连续周期之间的百分比变化 pct_change = np.diff(stock_prices) / stock_prices[:-1] * 100 print(pct_change) |
输出
1 2 |
[ 2. -3.92156863 7.14285714 1.9047619 -3.73831776 4.85436893 3.7037037 -2.67857143 5.50458716] |
输出是一个数组,显示每个周期之间的百分比变动,其长度比原始序列少一个。
9. 创建二元趋势指标
有时你可能需要二元指标而不是连续值。例如,让我们将连续的价格变动转换为离散的趋势信号,用于分类模型。
1 2 3 |
# 二元趋势(1表示上升,0表示下降) trend_binary = (np.diff(values) > 0).astype(int) print(trend_binary) |
输出
1 2 3 |
[1 1 1 0 1 1 0 0 1 0 0 1 0 0 1 0 1 0 0 1 0 1 0 1 1 0 1 0 1 1 1 0 0 1 0 1 0 1 1 1 0 0 0 0 1 0 1 0 0 1 0 0 1 1 1 0 1 1 1 0 1 1 1 1 1 0 1 0 0 1 1 0 1 0 1 0 0 0 0 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 0 1 1 0 0] |
输出是一个二元数组,表示连续周期之间的向上(1)或向下(0)的变动。
10. 计算有用的相关性
我们经常需要计算变量之间的相关性,以便进行有意义的分析和解释。让我们来衡量价格变动和交易活动之间的关系。
1 2 3 |
# 用一行代码计算相关系数 price_volume_corr = np.corrcoef(stock_prices, volumes)[0, 1] print(np.round(price_volume_corr,4)) |
输出
1 |
0.5879 |
我们得到一个介于-1和1之间的单一相关系数。它表示线性关系的强度和方向。
总结
这些NumPy单行代码展示了如何使用向量化操作使时间序列任务变得更简单、更快速。它们涵盖了常见的实际问题——比如为机器学习创建滞后特征、发现异常数据点以及计算金融统计数据——同时保持代码简短清晰。
这些单行代码的真正好处不仅在于它们简短,还在于它们运行高效且易于理解。由于NumPy是为速度而构建的,这些操作能够很好地处理大型数据集,并有助于保持代码的整洁和可读性。
一旦你掌握了这些技巧,你将能够编写出既高效又易于使用的时序代码。
暂无评论。