36、时间序列分析:从数据加载到模型构建

时间序列分析:从数据加载到模型构建

在时间序列分析中,我们常常需要处理各种类型的数据,并运用不同的方法来揭示数据中的规律和趋势。本文将介绍如何使用 Incanter 库对时间序列数据进行分析,包括数据加载、可视化、拟合曲线以及使时间序列数据平稳化等操作。

1. 数据介绍

我们将使用两个预先安装在 Incanter 中的数据集:
- Longley 数据集 :包含 1947 年至 1962 年美国的七个经济变量数据,如国内生产总值(GDP)、就业和失业人数、人口以及武装部队规模等。它是分析多重共线性的经典数据集,但我们每次只使用一个预测变量,因此多重共线性不会影响我们的分析。
- 航空公司数据集 :包含 1949 年 1 月至 1960 年 12 月的每月航空公司乘客总数。我们将在后续大部分内容中使用这个数据集。

你可以从 https://github.com/clojuredatascience/ch9-time-series 下载本文相关的源代码。

2. 加载 Longley 数据

由于 Incanter 将 Longley 数据集包含在其示例数据集库中,加载数据非常简单,只需调用 incanter.datasets/get-dataset 函数,并将 :longley 作为唯一参数。加载后,我们可以使用 incanter.core/view 函数查看数据集。

(defn ex-9-1 []
  (-> (d/get-dataset :longley)
      (i/view)))

这个数据集最初由美国国家标准与技术研究院发布,列描述可在 http://www.itl.nist.gov/div898/strd/lls/data/LINKS/i-Longley.shtml 查看。我们主要关注最后三列: x4 (武装部队规模)、 x5 (14 岁及以上的“非机构”人口)和 x6 (年份)。

3. 分析人口随时间的变化

首先,我们来看看人口随时间的变化情况。可以使用以下代码绘制人口与年份的散点图:

(defn ex-9-2 []
  (let [data (d/get-dataset :longley)]
    (-> (c/scatter-plot (i/$ :x6 data)
                        (i/$ :x5 data)
                        :x-label "Year"
                        :y-label "Population")
        (i/view))))

从绘制的图表中可以看出,人口与年份之间呈现出一种不太线性的关系,轻微的曲线表明人口增长率随着人口的增加而上升。这类似于吉布拉特法则(Gibrat’s law),即企业的增长率与其规模成正比。在分析符合吉布拉特法则的人口数据时,通常会看到类似的增长曲线。

4. 使用线性模型拟合曲线

我们已经知道如何使用 Incanter 的线性模型拟合直线。但实际上,线性模型也可以用于拟合曲线。

4.1 拟合直线

首先,回顾一下如何使用 Incanter 的 linear-model 函数拟合直线。我们需要从数据集中提取 x5 x6 列,并将它们应用到 incanter.stats/linear-model 函数中。

(defn ex-9-3 []
  (let [data  (d/get-dataset :longley)
        model (s/linear-model (i/$ :x5 data)
                              (i/$ :x6 data))]
    (println "R-square" (:r-square model))
    (-> (c/scatter-plot (i/$ :x6 data)
                        (i/$ :x5 data)
                        :x-label "Year"
                        :y-label "Population")
        (c/add-lines (i/$ :x6 data)
                     (:fitted model))
        (i/view))))

运行上述代码后,输出的 $R^2$ 值为 0.9879。虽然直线与数据拟合得比较接近,但它没有捕捉到数据的曲线特征。在图表的两端和中间,数据点与直线存在偏离,说明该模型存在较高的偏差,会根据年份系统地低估或高估人口数量。

4.2 拟合曲线

为了更好地拟合数据,我们可以向线性模型提供非线性特征。例如,除了年份,我们还可以添加年份的平方作为参数。以下代码使用 Incanter 的 bind-columns 函数创建包含这两个特征的矩阵:

(defn ex-9-4 []
  (let [data  (d/get-dataset :longley)
        x     (i/$ :x6 data)
        xs    (i/bind-columns x (i/sq x))
        model (s/linear-model (i/$ :x5 data) xs)]
    (println "R-square" (:r-square model))
    (-> (c/scatter-plot (i/$ :x6 data)
                        (i/$ :x5 data)
                        :x-label "Year"
                        :y-label "Population")
        (c/add-lines (i/$ :x6 data)
                     (:fitted model))
        (i/view))))

运行后,$R^2$ 值提高到了 0.9983,这表明拟合效果有了显著提升。我们还可以创建一个预测函数,用于对未来的人口进行预测:

(defn forecast [coefs]
  (fn [x]
    (first
     (i/mmult (i/trans coefs)
              (i/matrix [1.0 x (i/sq x)])))))

(defn ex-9-5 []
  (let [data  (d/get-dataset :longley)
        x     (i/$ :x6 data)
        xs    (i/bind-columns x (i/sq x))
        model (s/linear-model (i/$ :x5 data) xs)]
    (-> (c/scatter-plot (i/$ :x6 data)
                        (i/$ :x5 data)
                        :x-label "Year"
                        :y-label "Population")
        (c/add-function (forecast (:coefs model))
                        1947 1970)
        (i/view))))
5. 分析武装部队规模数据

接下来,我们将注意力转向 Longley 数据集中的武装部队规模列 x4 。可以使用以下代码绘制武装部队规模与年份的散点图:

(defn ex-9-6 []
  (let [data (d/get-dataset :longley)]
    (-> (c/scatter-plot (i/$ :x6 data)
                        (i/$ :x4 data)
                        :x-label "Year"
                        :y-label "Size of Armed Forces")
        (i/view))))

从图表中可以看出,这是一个更为复杂的时间序列。1950 年至 1952 年期间,武装部队规模急剧增加,随后逐渐下降。这是因为 1950 年 6 月 27 日,杜鲁门总统下令空军和海军部队协助韩国,引发了朝鲜战争。

为了拟合这些数据,我们需要生成更高阶的多项式。首先,我们构建一个 polynomial-forecast 函数,它可以根据单个 x 和最高阶多项式自动创建高阶特征:

(defn polynomial-forecast [coefs degree]
  (fn [x]
    (first
     (i/mmult (i/trans coefs)
              (for [i (range (inc degree))]
                (i/pow x i))))))

例如,我们可以使用以下代码训练一个最高到 $x^{11}$ 的模型:

(defn ex-9-7 []
  (let [data (d/get-dataset :longley)
        degree 11
        x  (s/sweep (i/$ :x6 data))
        xs (reduce i/bind-columns
                   (for [i (range (inc degree))]
                     (i/pow x i)))
        model (s/linear-model (i/$ :x4 data) xs
                              :intercept false)]
    (println "R-square" (:r-square model))
    (-> (c/scatter-plot (i/$ 1 xs) (i/$ :x4 data)
                        :x-label "Distance from Mean (Years)"
                        :y-label "Size of Armed Forces")
        (c/add-function (polynomial-forecast (:coefs model)
                                             degree)
                        -7.5 7.5)
        (i/view))))

运行后,$R^2$ 值为 0.9755,曲线与数据拟合得较好。但需要注意的是,我们可能存在过拟合的问题。如果将图表范围扩展到未来,模型可能会给出不合理的预测。例如,在最后一个测量数据点仅两年半后,模型预测军队规模将增长超过 500%,达到超过 175,000 人。

6. 时间序列分解

在对军事时间序列数据进行建模时,一个问题是数据量不足,难以建立一个通用的模型。常见的时间序列建模方法是将序列分解为几个独立的组件:
- 趋势(Trend) :序列是否随时间总体上升或下降?趋势是否像我们在人口数据中看到的那样呈指数曲线?
- 季节性(Seasonality) :序列是否在固定间隔内呈现周期性的上升和下降?对于月度数据,常见的周期是 12 个月。
- 周期性(Cycles) :数据集中是否存在跨越多个季节的长期周期?例如,在金融数据中,我们可能会观察到与经济扩张和衰退周期相对应的多年周期。

对于军事数据,由于信息不足,我们难以确定是否存在趋势,以及观察到的峰值是否是季节性或周期性模式的一部分。虽然数据看起来呈上升趋势,但也可能只是一个最终会下降到起点的周期。

接下来,我们将重点关注航空公司数据集,它是一个经典的时间序列数据集,包含 1949 - 1960 年的每月航空公司乘客数量,数据量较大且明显具有趋势和季节性成分。

7. 检查航空公司数据

航空公司数据集也是 Incanter 数据集库的一部分。我们可以使用以下代码加载并查看数据集:

(defn ex-9-9 []
  (-> (d/get-dataset :airline-passengers)
      (i/view)))

在分析时间序列时,确保数据按时间顺序排列非常重要。该数据集按年份和月份排序,但为了进一步分析,我们需要将年份和月份列转换为一个可以排序的单列。为此,我们将再次使用 clj-time 库。

8. 可视化航空公司数据

在之前的分析中,我们可能利用了 clj-time 能够理解的默认时间格式。但 clj-time 并不能自动推断所有时间表示形式,特别是美国的 mm/dd/yyyy 格式和世界其他大部分地区使用的 dd/mm/yyyy 格式之间的差异。 clj-time.format 命名空间提供了一个 parse 函数,允许我们传递一个格式字符串,指示库如何解释字符串。

我们指定时间格式为 “MMM YYYY”,即三个字符的月份和四个字符的年份。以下是相关代码:

(def time-format
  (tf/formatter "MMM YYYY"))
(defn to-time [month year]
  (tf/parse time-format (str month " " year)))

(defn airline-passengers []
  (->> (d/get-dataset :airline-passengers)
       (i/add-derived-column :time [:month :year] to-time)
       (i/$order :time :asc)
       (i/$ :passengers)))

(defn timeseries-plot [series]
  (-> (c/xy-plot (range (count series)) series
               :x-label "Time"
               :y-label "Value")
      (i/view)))

(defn ex-9-10 []
  (-> (airline-passengers)
      (timeseries-plot)))

运行上述代码后,我们可以看到数据具有明显的季节性模式(每 12 个月重复一次)、向上的趋势和轻微的增长曲线。但图表右侧的方差大于左侧,说明数据存在异方差性。我们希望去除方差的增加和向上的趋势,使时间序列数据平稳化。

9. 平稳性(Stationarity)

平稳时间序列是指其统计特性在时间上保持恒定的序列。大多数统计预测方法都假设序列已经转换为平稳序列。对于平稳时间序列,预测会变得更加容易,因为我们假设序列的统计特性在未来与过去相同。

为了去除航空公司数据中方差的增加和增长曲线,我们可以简单地对乘客数量取对数:

(defn ex-9-11 []
  (-> (airline-passengers)
      (i/log)
      (timeseries-plot)))

取对数有两个效果:一是去除了初始图表中明显的异方差性;二是将指数增长曲线简化为直线。但数据仍然存在趋势(也称为漂移),为了得到真正平稳的时间序列,我们还需要稳定均值。有几种方法可以实现这一点,接下来我们将介绍两种常见的方法:去趋势和差分。

10. 去趋势和差分
10.1 去趋势(De - trending)

去趋势的方法是对取对数后的航空公司数据拟合一个线性模型,然后绘制残差图。以下是相关代码:

(defn ex-9-12 []
  (let [data (i/log (airline-passengers))
        xs   (range (count data))
        model (s/linear-model data xs)]
    (-> (c/xy-plot xs (:residuals model)
                   :x-label "Time"
                   :y-label "Residual")
        (i/view))))

残差图显示,序列的均值比原始序列更加稳定,向上的趋势也完全被去除。但残差似乎并没有围绕新的均值呈正态分布,图表中间出现了一个“驼峰”,这表明线性模型在航空公司数据上的表现并不理想。

10.2 差分(Differencing)

差分是指从时间序列中的每个点减去其前一个点的值,得到一个新的时间序列(比原序列少一个数据点),该序列只包含连续点之间的差异。以下是差分的代码实现:

(defn difference [series]
  (map - (drop 1 series) series))

(defn ex-9-13 []
  (-> (airline-passengers)
      (i/log)
      (difference)
      (timeseries-plot)))

从图表中可以看出,向上的趋势被围绕恒定均值的波动序列所取代。均值略高于零,这对应于差异为正的倾向增加,导致我们观察到的向上趋势。

两种方法的目标都是使序列的均值保持恒定。在某些情况下,可能需要对序列进行多次差分,或者在去趋势后进行差分,以获得真正稳定均值的序列。例如,去趋势后序列仍然存在一些漂移,因此在后续内容中我们将使用差分后的数据。

11. 离散时间模型

到目前为止,我们所讨论的离散时间模型将时间划分为规则间隔的切片。为了预测未来时间切片的值,我们假设它们依赖于过去的切片。

最简单的时间序列是每个时间切片的值与其前一个切片的值相同。其预测公式为:$\hat{y}_{t + 1|t} = y_t$,即给定时间 $t$ 时,时间 $t + 1$ 的预测值等于时间 $t$ 的观测值。这个定义是递归的,因为时间 $t$ 的值依赖于时间 $t - 1$ 的值,以此类推。

我们可以在 Clojure 中使用惰性序列来模拟这个“恒定”时间序列:

(defn constant-series [y]
  (cons y (lazy-seq (constant-series y))))

(defn ex-9-14 []
  (take 5 (constant-series 42)))

运行 ex-9-14 函数将输出 (42 42 42 42 42)

如果实际观测值 $y_{t + 1}$ 与预测值 $\hat{y} {t + 1|t}$ 不同,我们可以计算预测误差:$\epsilon {t + 1} = y_{t + 1}-\hat{y} {t + 1|t}$。结合前面的公式,我们可以得到时间序列的随机模型:$y_t = y {t - 1}+\epsilon_t$,即当前时间切片的值等于前一个时间切片的值加上一些误差。

12. 随机游走(Random Walks)

随机游走是最简单的随机过程之一。我们可以将 constant-series 扩展为随机游走过程。我们希望误差服从均值为零、方差恒定的正态分布,可以使用 Incanter 的 stats/sample-normal 函数来模拟随机噪声。以下是随机游走的代码实现:

(defn random-walk [y]
  (let [e (s/sample-normal 1)
        y (+ y e)]
    (cons y (lazy-seq (random-walk y)))))

(defn ex-9-15 []
  (->> (random-walk 0)
       (take 50)
       (timeseries-plot)))

随机游走模型在金融和计量经济学中经常出现。“随机游走”一词最早由卡尔·皮尔逊(Karl Pearson)在 1905 年引入。许多过程,从波动的股票价格到分子在气体中的运动轨迹,都可以建模为简单的随机游走。1973 年,普林斯顿经济学家伯顿·戈登·马尔基尔(Burton Gordon Malkiel)在他的《漫步华尔街》( A Random Walk Down Wall Street )一书中指出,股票价格也遵循随机游走。

综上所述,时间序列分析是一个复杂而有趣的领域,通过数据加载、可视化、拟合曲线、平稳化处理以及模型构建等步骤,我们可以更好地理解和预测时间序列数据中的趋势和模式。在实际应用中,我们需要根据数据的特点选择合适的方法,并注意避免过拟合等问题。

时间序列分析:从数据加载到模型构建

13. 自回归模型(Autoregressive Models)

自回归模型是时间序列分析中常用的模型之一,它假设当前值依赖于过去的观测值。一个 $p$ 阶自回归模型,记为 $AR(p)$,可以表示为:

$y_t = c+\phi_1y_{t - 1}+\phi_2y_{t - 2}+\cdots+\phi_py_{t - p}+\epsilon_t$

其中,$c$ 是常数项,$\phi_1,\phi_2,\cdots,\phi_p$ 是自回归系数,$\epsilon_t$ 是白噪声。

在 Clojure 中,我们可以使用 Incanter 库来实现自回归模型。以下是一个简单的 $AR(1)$ 模型的示例:

(defn ar-1 [y phi c]
  (let [e (s/sample-normal 1)
        y-next (+ c (* phi y) e)]
    (cons y-next (lazy-seq (ar-1 y-next phi c)))))

(defn ex-9-16 []
  (->> (ar-1 0 0.8 0)
       (take 100)
       (timeseries-plot)))

在上述代码中, ar-1 函数实现了一个 $AR(1)$ 模型, phi 是自回归系数, c 是常数项。 ex-9-16 函数生成了一个长度为 100 的 $AR(1)$ 序列,并将其可视化。

14. 移动平均模型(Moving Average Models)

移动平均模型假设当前值依赖于过去的白噪声。一个 $q$ 阶移动平均模型,记为 $MA(q)$,可以表示为:

$y_t = \mu+\epsilon_t+\theta_1\epsilon_{t - 1}+\theta_2\epsilon_{t - 2}+\cdots+\theta_q\epsilon_{t - q}$

其中,$\mu$ 是均值,$\theta_1,\theta_2,\cdots,\theta_q$ 是移动平均系数,$\epsilon_t$ 是白噪声。

以下是一个简单的 $MA(1)$ 模型的示例:

(defn ma-1 [mu theta]
  (let [e (s/sample-normal 1)
        e-prev (s/sample-normal 1)
        y (+ mu e (* theta e-prev))]
    (cons y (lazy-seq (ma-1 mu theta)))))

(defn ex-9-17 []
  (->> (ma-1 0 0.8)
       (take 100)
       (timeseries-plot)))

在上述代码中, ma-1 函数实现了一个 $MA(1)$ 模型, theta 是移动平均系数, mu 是均值。 ex-9-17 函数生成了一个长度为 100 的 $MA(1)$ 序列,并将其可视化。

15. 自回归移动平均模型(Autoregressive Moving Average Models)

自回归移动平均模型结合了自回归模型和移动平均模型的特点。一个 $ARMA(p, q)$ 模型可以表示为:

$y_t = c+\phi_1y_{t - 1}+\phi_2y_{t - 2}+\cdots+\phi_py_{t - p}+\epsilon_t+\theta_1\epsilon_{t - 1}+\theta_2\epsilon_{t - 2}+\cdots+\theta_q\epsilon_{t - q}$

其中,$p$ 是自回归阶数,$q$ 是移动平均阶数。

在实际应用中,选择合适的 $p$ 和 $q$ 值是一个关键问题。通常可以使用信息准则(如 AIC、BIC)来选择最优的阶数。

16. 自回归积分滑动平均模型(Autoregressive Integrated Moving Average Models)

自回归积分滑动平均模型(ARIMA)是在 $ARMA$ 模型的基础上,考虑了时间序列的非平稳性。一个 $ARIMA(p, d, q)$ 模型可以表示为:

  1. 首先对原始时间序列进行 $d$ 阶差分,使其变为平稳序列。
  2. 对差分后的平稳序列建立 $ARMA(p, q)$ 模型。

以下是一个简单的 $ARIMA(1, 1, 1)$ 模型的示例:

(defn arima-1-1-1 [y phi theta c]
  (let [e (s/sample-normal 1)
        diff-y (- y (first (drop 1 y)))
        y-next (+ c (* phi diff-y) e (* theta e))]
    (cons y-next (lazy-seq (arima-1-1-1 (cons y-next y) phi theta c)))))

(defn ex-9-18 []
  (->> (arima-1-1-1 [0] 0.8 0.5 0)
       (take 100)
       (timeseries-plot)))

在上述代码中, arima-1-1-1 函数实现了一个 $ARIMA(1, 1, 1)$ 模型, phi 是自回归系数, theta 是移动平均系数, c 是常数项。 ex-9-18 函数生成了一个长度为 100 的 $ARIMA(1, 1, 1)$ 序列,并将其可视化。

17. 季节性自回归积分滑动平均模型(Seasonal Autoregressive Integrated Moving Average Models)

对于具有季节性的时间序列数据,我们可以使用季节性自回归积分滑动平均模型(SARIMA)。一个 $SARIMA(p, d, q)(P, D, Q)_s$ 模型可以表示为:

  1. 对原始时间序列进行 $d$ 阶差分和 $D$ 阶季节性差分,使其变为平稳序列。
  2. 对差分后的平稳序列建立 $ARMA(p, q)$ 模型和季节性 $ARMA(P, Q)$ 模型。

其中,$s$ 是季节周期。

以下是一个简单的 $SARIMA(1, 1, 1)(1, 1, 1)_{12}$ 模型的示例:

(defn sarima-1-1-1-1-1-1-12 [y phi theta c Phi Theta C]
  (let [e (s/sample-normal 1)
        diff-y (- y (first (drop 1 y)))
        seasonal-diff-y (- y (first (drop 12 y)))
        y-next (+ c (* phi diff-y) e (* theta e) C (* Phi seasonal-diff-y) (* Theta e))]
    (cons y-next (lazy-seq (sarima-1-1-1-1-1-1-12 (cons y-next y) phi theta c Phi Theta C)))))

(defn ex-9-19 []
  (->> (sarima-1-1-1-1-1-1-12 [0] 0.8 0.5 0 0.8 0.5 0)
       (take 120)
       (timeseries-plot)))

在上述代码中, sarima-1-1-1-1-1-1-12 函数实现了一个 $SARIMA(1, 1, 1)(1, 1, 1) {12}$ 模型, phi Phi 分别是自回归系数和季节性自回归系数, theta Theta 分别是移动平均系数和季节性移动平均系数, c C 分别是常数项和季节性常数项。 ex-9-19 函数生成了一个长度为 120 的 $SARIMA(1, 1, 1)(1, 1, 1) {12}$ 序列,并将其可视化。

18. 模型评估与选择

在时间序列分析中,选择合适的模型是非常重要的。常用的模型评估指标包括均方误差(MSE)、均方根误差(RMSE)、平均绝对误差(MAE)等。

以下是一个计算均方误差的示例:

(defn mse [y-true y-pred]
  (let [errors (map #(- %1 %2) y-true y-pred)
        squared-errors (map #(* % %) errors)]
    (/ (reduce + squared-errors) (count squared-errors))))

(defn ex-9-20 []
  (let [y-true [1 2 3 4 5]
        y-pred [1.1 2.2 3.3 4.4 5.5]]
    (println "MSE:" (mse y-true y-pred))))

在上述代码中, mse 函数计算了真实值和预测值之间的均方误差。 ex-9-20 函数演示了如何使用 mse 函数。

除了评估指标,我们还可以使用信息准则(如 AIC、BIC)来选择最优的模型。信息准则综合考虑了模型的拟合优度和复杂度,选择信息准则值最小的模型作为最优模型。

19. 预测与应用

在选择了合适的模型后,我们可以使用该模型进行预测。以下是一个使用 $ARIMA$ 模型进行预测的示例:

(defn arima-predict [y phi theta c steps]
  (let [y-pred (take steps (arima-1-1-1 y phi theta c))]
    y-pred))

(defn ex-9-21 []
  (let [y [1 2 3 4 5]
        phi 0.8
        theta 0.5
        c 0
        steps 5]
    (println "Predictions:" (arima-predict y phi theta c steps))))

在上述代码中, arima-predict 函数使用 $ARIMA(1, 1, 1)$ 模型进行预测, steps 是预测的步数。 ex-9-21 函数演示了如何使用 arima-predict 函数进行预测。

时间序列分析在许多领域都有广泛的应用,如金融、气象、交通等。通过对时间序列数据的分析和预测,我们可以更好地理解数据的规律和趋势,为决策提供支持。

总结

时间序列分析是一个复杂而强大的工具,通过数据加载、可视化、平稳化处理、模型构建和评估等步骤,我们可以揭示时间序列数据中的规律和趋势,并进行有效的预测。在实际应用中,我们需要根据数据的特点选择合适的模型,并注意避免过拟合等问题。同时,我们还可以结合其他技术,如机器学习、深度学习等,进一步提高预测的准确性。

以下是时间序列分析的主要步骤总结:
|步骤|描述|
|----|----|
|数据加载|使用 Incanter 库加载预先安装的数据集,如 Longley 数据集和航空公司数据集。|
|数据可视化|绘制散点图、折线图等,直观展示数据的特征。|
|平稳化处理|通过取对数、差分等方法使时间序列数据平稳。|
|模型构建|选择合适的模型,如自回归模型、移动平均模型、ARIMA 模型等。|
|模型评估|使用均方误差、信息准则等指标评估模型的性能。|
|预测与应用|使用训练好的模型进行预测,并将结果应用于实际问题。|

mermaid 流程图如下:

graph LR
    A[数据加载] --> B[数据可视化]
    B --> C[平稳化处理]
    C --> D[模型构建]
    D --> E[模型评估]
    E --> F{模型是否合适?}
    F -- 是 --> G[预测与应用]
    F -- 否 --> D

通过以上步骤和方法,我们可以更好地进行时间序列分析,为实际问题提供有效的解决方案。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值