米霖 2024
本文档的每一部分都可以在任何计算机上通过云笔记本或本地运行。
如果你的电脑上目前还没有安装 R 和 RStudio, 你可以浏览器中运行代码: https://gesis.mybinder.org/binder/v2/gh/ries9112/high-level-reprex-jupyter/0a291fe130f30281009aecb61bff202f048617f8?filepath=Tutorial-Full.ipynb
这可能需要30秒来加载,一旦加载完成,你应该会看到一个如下的页面:
从这里开始,您可以一次运行一个单元格的代码:
首先,让我们从安装 pacman包, 这个包专门用于管理R包的.
install.packages('pacman')
然后加载pacman包
我们可以使用 p _ load ()来安装本教程其余部分所需的其余软件包。使用新函数的好处是,安装将以一种“更智能”的方式进行,如果您的库中已经有了一个包,那么它将不会再次安装。
p_load('pins', 'skimr', 'DT', 'httr', 'jsonlite', # Data Exploration
'tidyverse', 'tsibble', 'anytime', # Data Prep
'ggTimeSeries', 'gifski', 'av', 'magick', 'ggthemes', 'plotly', # Visualization
'ggpubr', 'ggforce', 'gganimate', 'transformr', # Visualization continued
'caret', 'doParallel', 'parallel', 'xgboost', # Predictive Modeling
'brnn', 'party', 'deepnet', 'elasticnet', 'pls', # Predictive Modeling continued
'hydroGOF', 'formattable', 'knitr') # Evaluate Model Performance
到这里, 准备工作就差不多完成,下一步 我们进行探索数据.
我们需要做的第一件事是下载最新的数据,首先,我们需要连接到一个公共 GitHub 存储库 代码如下:
board_register(name = "pins_board",
url = "https://raw.githubusercontent.com/predictcrypto/pins/master/",
board = "datatxt")
如果是在国内, 可能无法访问对应的链接.下一步, 通过pin_get函数获取到我们需要的数据:
# cryptodata <- pin_get(name = "hitBTC_orderbook",board = "pins_board")
cryptodata <- read.csv("/Users/milin/加密货币研究/cryptodata.csv")
这样, 我们就获取到了对应的数据集. 下一步, 我们查看数据集.
pair | symbol | quote_currency | ask_1_price | ask_1_quantity | ask_2_price | ask_2_quantity | ask_3_price | ask_3_quantity | ask_4_price | ask_4_quantity | ask_5_price | ask_5_quantity | bid_1_price | bid_1_quantity | bid_2_price | bid_2_quantity | bid_3_price | bid_3_quantity | bid_4_price | bid_4_quantity | bid_5_price | bid_5_quantity | date_time_utc | date | pkDummy | pkey |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
BTCUSD | BTC | USD | 26060.39000 | 0.0200 | 26060.40000 | 0.0100 | 26060.42000 | 1.0000 | 26067.92000 | 0.0575 | 26072.30000 | 0.02229 | 26057.09000 | 0.0002 | 26051.88000 | 0.1690 | 26047.86000 | 0.0103 | 26047.44000 | 0.1000 | 26046.96000 | 0.00994 | 2023-08-26 08:00:04 | 2023-08-26 | 2023-08-26 00 | 2023-08-26 00BTC |
ETHUSD | ETH | USD | 1654.09800 | 0.1058 | 1654.62700 | 0.1579 | 1654.79300 | 0.3765 | 1654.81700 | 0.1615 | 1654.89800 | 0.80000 | 1653.01200 | 0.2471 | 1653.01100 | 0.1583 | 1652.96300 | 0.1608 | 1652.89100 | 2.7561 | 1652.85500 | 8.81850 | 2023-08-26 08:00:05 | 2023-08-26 | 2023-08-26 00 | 2023-08-26 00ETH |
EOSUSD | EOS | USD | 0.58788 | 0.1700 | 0.58794 | 467.4800 | 0.58822 | 440.4700 | 0.58839 | 1596.0400 | 0.58842 | 445.53000 | 0.58694 | 463.0500 | 0.58691 | 448.4800 | 0.58682 | 2597.9100 | 0.58677 | 1565.8600 | 0.58671 | 6993.26000 | 2023-08-26 08:00:06 | 2023-08-26 | 2023-08-26 00 | 2023-08-26 00EOS |
LTCUSD | LTC | USD | 65.16260 | 4.1270 | 65.16910 | 12.1200 | 65.18560 | 28.2040 | 65.18970 | 47.7390 | 65.19570 | 4.03200 | 65.11040 | 26.8730 | 65.09280 | 41.9150 | 65.09230 | 3.8870 | 65.08740 | 4.0630 | 65.08510 | 43.12300 | 2023-08-26 08:00:07 | 2023-08-26 | 2023-08-26 00 | 2023-08-26 00LTC |
BSVUSD | BSV | USD | 30.14320 | 52.6730 | 30.16120 | 8.3830 | 30.16720 | 29.2020 | 30.17320 | 80.2040 | 30.17920 | 118.18400 | 30.07940 | 8.1600 | 30.06900 | 28.7220 | 30.06870 | 1.2390 | 30.05740 | 74.3570 | 30.04150 | 109.62500 | 2023-08-26 08:00:12 | 2023-08-26 | 2023-08-26 00 | 2023-08-26 00BSV |
当我们谈论加密货币的价格时,有几种不同的方式来定义它,而且远不止表面上看到的那么简单。大多数人在网站上查看加密货币的“价格”,这些网站汇总了数千个交易所的数据,并且计算的是加密货币当前“价格”的全球平均数。这就是在非常流行的网站coinmarketcap.com上发生的事情,但是对于我们的用例来说这是正确的方法吗?
在我们开始编程之前,任何成功的预测建模过程的关键步骤都是以一种与我们期望采取的行动相关的方式来定义问题。如果我们希望在一个特定的交易所交易一种特定的加密货币,使用全球平均价格不会是最好的方法.我们可以采用的一个更好的替代方案是,将价格定义为我们实际上可以购买加密货币的价格。如果我们对购买加密货币感兴趣,我们应该考虑实际购买价格的数据。
加密货币交易所的运作方式是拥有一个不断演变的订单簿,交易员可以在其中发布他们想要进行的具体交易,要么出售,要么购买一种加密货币,并指明价格和数量。当某人发布一笔交易,以其他人希望以其他价格购买的价格出售一种加密货币时,双方之间的交易就会发生。
你可以在这里找到我们即将使用的交易所的实时订单簿: https://hitbtc.com/zh/btc-to-usdt
现在让我们关注一下市场深度图,这里,X轴显示加密货币的价格,左边的价格较低,右边的价格较高,而 Y 轴显示当前可以在两边触发的订单累计量(以比特币为单位)(市场的“流动性”)。
从图中可以观察到,大约1.3万美元的价位基本上让愿意购买这种加密货币,对于超过这一价位的人,他们希望出售这种资产。例如,在12,500美元的价格点附近,图表告诉我们,如果我们想以这个价格购买加密货币
BTC,在订单被触发之前,必须以更高的价格售出大约1,500
BTC。市场将完成交易,张贴到订单,并匹配买方和卖方。指令簿两端交汇的价格是我们目前可以在这个交易所交易的加密货币的价格。
因为价格是这样运作的,我们不能简单地以13000美元的价格点购买1500 BTC,因为我们会用光愿意在这个价格点出售 BTC 的人,为了完成整个订单,我们需要支付比我们认为的“价格”更多的钱,这取决于我们想要购买多少。
在市场深度图表下面,我们可以找到与上面可视化的订单簿相关的实际数据:
我们将要处理的数据是由每边订单的前5个价格点组成的。我们有机会获得5个最高的出价(在一方寻找买入) ,和5个最低的要价(从交易员寻找出售)。与上面的截图相关,我们使用的数据将只由前5行组成。
在真正开始清理数据之前,值得花时间做一些探索性数据分析(EDA) ,即在开始任何其他进程之前分析数据本身的问题。大多数数据科学和业务问题都需要你对数据集及其所有警告和问题有深入的理解,如果没有理解和解释这些基本问题,模型就没有意义。在我们的案例中,这种理解主要来自于理解加密货币的价格是如何定义的,我们在上面已经讨论过了,而且就原始数据的质量而言,我们没有太多其他需要担心的问题,但在其他案例中,进行 EDA将是一个更复杂的过程,比如将数据的不同列可视化。 可以使用skimr 包对数据进行探索性分析.
Name | cryptodata |
Number of rows | 300000 |
Number of columns | 27 |
_______________________ | |
Column type frequency: | |
character | 7 |
numeric | 20 |
________________________ | |
Group variables | None |
Data summary
Variable type: character
skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
---|---|---|---|---|---|---|---|
pair | 0 | 1 | 5 | 10 | 0 | 301 | 0 |
symbol | 0 | 1 | 1 | 6 | 0 | 301 | 0 |
quote_currency | 0 | 1 | 3 | 3 | 0 | 1 | 0 |
date_time_utc | 0 | 1 | 19 | 19 | 0 | 231716 | 0 |
date | 0 | 1 | 10 | 10 | 0 | 61 | 0 |
pkDummy | 0 | 1 | 13 | 13 | 0 | 1444 | 0 |
pkey | 0 | 1 | 14 | 19 | 0 | 300000 | 0 |
Variable type: numeric
skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
---|---|---|---|---|---|---|---|---|---|---|
ask_1_price | 0 | 1 | 3.407815e+04 | 5.773469e+06 | 0 | 0.01 | 0.17 | 1.00 | 1.000000e+09 | ▇▁▁▁▁ |
ask_1_quantity | 0 | 1 | 2.954886e+07 | 4.800398e+08 | 0 | 11.99 | 293.29 | 4408.70 | 1.258971e+10 | ▇▁▁▁▁ |
ask_2_price | 0 | 1 | 3.343888e+05 | 5.773445e+07 | 0 | 0.01 | 0.18 | 1.05 | 1.000000e+10 | ▇▁▁▁▁ |
ask_2_quantity | 0 | 1 | 3.650164e+07 | 6.052891e+08 | 0 | 17.30 | 512.12 | 5779.80 | 3.110080e+10 | ▇▁▁▁▁ |
ask_3_price | 0 | 1 | 3.337575e+06 | 5.773445e+08 | 0 | 0.01 | 0.19 | 1.10 | 1.000000e+11 | ▇▁▁▁▁ |
ask_3_quantity | 0 | 1 | 4.321217e+07 | 7.654756e+08 | 0 | 10.10 | 537.40 | 7335.50 | 3.110080e+10 | ▇▁▁▁▁ |
ask_4_price | 0 | 1 | 3.610107e+07 | 5.775795e+09 | 0 | 0.01 | 0.20 | 1.21 | 1.000000e+12 | ▇▁▁▁▁ |
ask_4_quantity | 0 | 1 | 5.654120e+07 | 9.703727e+08 | 0 | 9.26 | 602.00 | 9548.50 | 3.754972e+10 | ▇▁▁▁▁ |
ask_5_price | 0 | 1 | 1.136067e+10 | 1.050338e+12 | 0 | 0.01 | 0.20 | 1.34 | 1.000000e+14 | ▇▁▁▁▁ |
ask_5_quantity | 0 | 1 | 3.629292e+07 | 7.965629e+08 | 0 | 8.40 | 630.74 | 10490.92 | 3.210863e+10 | ▇▁▁▁▁ |
bid_1_price | 0 | 1 | 1.705300e+02 | 2.042200e+03 | 0 | 0.01 | 0.11 | 0.75 | 3.159029e+04 | ▇▁▁▁▁ |
bid_1_quantity | 0 | 1 | 2.816657e+07 | 5.182185e+08 | 0 | 15.00 | 444.10 | 6428.00 | 4.255776e+10 | ▇▁▁▁▁ |
bid_2_price | 0 | 1 | 1.700200e+02 | 2.041190e+03 | 0 | 0.01 | 0.10 | 0.75 | 3.158872e+04 | ▇▁▁▁▁ |
bid_2_quantity | 0 | 1 | 2.777513e+07 | 5.003205e+08 | 0 | 23.34 | 555.00 | 9521.90 | 7.125044e+10 | ▇▁▁▁▁ |
bid_3_price | 0 | 1 | 1.696900e+02 | 2.040950e+03 | 0 | 0.00 | 0.08 | 0.72 | 3.158871e+04 | ▇▁▁▁▁ |
bid_3_quantity | 0 | 1 | 4.862263e+07 | 7.824027e+08 | 0 | 20.00 | 720.00 | 11000.00 | 4.088310e+10 | ▇▁▁▁▁ |
bid_4_price | 0 | 1 | 1.685800e+02 | 2.039700e+03 | 0 | 0.00 | 0.07 | 0.70 | 3.158870e+04 | ▇▁▁▁▁ |
bid_4_quantity | 0 | 1 | 5.703589e+07 | 9.421117e+08 | 0 | 26.70 | 909.00 | 14385.20 | 2.332079e+10 | ▇▁▁▁▁ |
bid_5_price | 0 | 1 | 1.663800e+02 | 2.036890e+03 | 0 | 0.00 | 0.06 | 0.68 | 3.158696e+04 | ▇▁▁▁▁ |
bid_5_quantity | 0 | 1 | 6.014592e+07 | 1.199008e+09 | 0 | 32.00 | 1000.00 | 15984.00 | 1.665993e+11 | ▇▁▁▁▁ |
这个摘要帮助我们理解给定列中有多少行缺少值,或者这些值是如何分布的。
数据是通过使用HitBTC加密货币交易所提供的公共API端点而无需身份验证要求来获取的(我们正在使用的端点)。查看下面的代码,了解数据是如何被获取的实际示例,该示例在每次运行此文档时运行。以下是一个示例,获取以太坊(ETH)加密货币的数据,如果你按照设置步骤进行了设置,应该可以自行运行下面的代码来获取实时订单簿数据:
fromJSON(content(GET("https://api.hitbtc.com/api/2/public/orderbook/ETHUSD",
query=list(limit=5)),
type = "text",
encoding = "UTF-8"))
数据是由在私有RStudio服务器上运行的脚本收集的,该脚本在每小时开始时逐个迭代所有加密货币选项,从HitBTC加密货币交易所API订单簿数据(如上所述)中获取最新数据,并将最新数据追加到私有数据库中以进行长期存储。一旦数据存储在数据库中,每小时将启动一个不同的脚本,将数据库中的最新数据发布到本节开头讨论的公开可用的pins数据源中。
接下来,我们将进行一些数据清理工作,以确保我们的数据处于所需的格式。有关使用 dplyr 包进行数据准备的更温和介绍,请参阅高级版本(Wickham, François 等人 2020)。
首先,如果我们不知道数据收集的时间,我们根本无法对一行数据做任何处理。如果我们无法将特定价格与由 date_time_utc 字段给出的时间戳关联起来,那么具体价格就无关紧要。
我们可以使用 dplyr 包中的 filter() 函数排除 date_time_utc 字段具有空值(缺失值)的所有行:
此步骤在最近一次运行中(2023-08-26)移除了 0 行数据。is.na() 函数查找 date_time_utc 字段具有空值的所有情况。该函数前面带有 ! 运算符,告诉 filter() 函数排除这些行。
同样的逻辑,如果我们不知道任何行的价格,那么整行数据都是无用的,应该被移除。但我们将如何定义加密货币的价格?
在上一节中,我们讨论了加密货币价格的复杂性。我们可以通过考虑想要进行交易的人的出价和要价来复杂化我们对价格的定义,但这不是一个交易教程。相反,我们将定义加密货币的价格为我们可以购买它的价格。我们将使用 ask 方最便宜的价格计算 price_usd 字段,其中至少有价值 15 美元的加密货币被出售。
因此,让我们找出能让我们至少购买价值 15 美元加密货币的订单簿数据中最低价格。为此,对于每个要价和数量,让我们计算交易的美元价值。我们可以使用 mutate() 函数创建每个新的 trade_usd 列。trade_usd_1 应该计算为 ask_1_price 乘以 ask_1_quantity。下一个 trade_usd_2 应该考虑 ask_2_price,但乘以 ask_1_quantity 和 ask_2_quantity 的总和,因为在 ask_2_price 价格点,我们还可以购买 ask_1_price 价格点可用的数量:
cryptodata <- mutate(cryptodata,
trade_usd_1 = ask_1_price * ask_1_quantity,
trade_usd_2 = ask_2_price * (ask_1_quantity + ask_2_quantity),
trade_usd_3 = ask_3_price * (ask_1_quantity + ask_2_quantity + ask_3_quantity),
trade_usd_4 = ask_4_price * (ask_1_quantity + ask_2_quantity + ask_3_quantity + ask_4_quantity),
trade_usd_5 = ask_5_price * (ask_1_quantity + ask_2_quantity + ask_3_quantity + ask_4_quantity + ask_5_quantity))
我们可以确认 trade_usd_1 字段正在计算最低要价和数量的美元价值:
## symbol date_time_utc ask_1_price ask_1_quantity trade_usd_1
## 1 BTC 2023-08-26 08:00:04 2.606039e+04 0.0200 521.2078000
## 2 ETH 2023-08-26 08:00:05 1.654098e+03 0.1058 175.0035684
## 3 EOS 2023-08-26 08:00:06 5.878800e-01 0.1700 0.0999396
## 4 LTC 2023-08-26 08:00:07 6.516260e+01 4.1270 268.9260502
## 5 BSV 2023-08-26 08:00:12 3.014320e+01 52.6730 1587.7327736
## 6 ADA 2023-08-26 08:00:13 2.612828e-01 1937.0000 506.1047836
现在我们可以使用 mutate() 函数创建新字段 price_usd,并找出我们可以购买至少价值 15 美元的加密货币的最低价格。我们可以使用 case_when() 函数找到第一个 trade_usd 值大于或等于 15 美元,并为新列 price_usd 分配正确的 ask_price:
cryptodata <- mutate(cryptodata,
price_usd = case_when(
cryptodata$trade_usd_1 >= 15 ~ cryptodata$ask_1_price,
cryptodata$trade_usd_2 >= 15 ~ cryptodata$ask_2_price,
cryptodata$trade_usd_3 >= 15 ~ cryptodata$ask_3_price,
cryptodata$trade_usd_4 >= 15 ~ cryptodata$ask_4_price,
cryptodata$trade_usd_5 >= 15 ~ cryptodata$ask_5_price))
让我们还像之前对 date_time_utc 字段那样,移除新 price_usd 字段中具有空值的任何行。这些主要是由于通过第 5 个最低要价的交易价值低于 15 美元的行组成。
此步骤在最近一次运行中移除了 37598 行。
在本教程的高级版本中,我们只处理了一种加密货币。然而,在这个版本中,我们将为每种加密货币创建独立的模型。因此,我们不仅需要确保整体数据的质量,还需要确保与每种单独的加密货币相关的数据的质量。我们可以将数据按照单独的加密货币进行分组,然后对每个组应用转换,而不是考虑所有行。这只适用于与 dplyr 和 tidyverse 兼容的函数。
例如,我们可以通过在数据上运行 count() 函数来计算观察次数:
## n
## 1 262390
但如果我们想知道数据中与每种加密货币分别相关的观察次数呢?
我们可以使用 dplyr 包中的 group_by() 函数对数据进行分组,并按加密货币符号分组:
现在,如果我们使用 count() 函数运行相同的操作,操作将按加密货币符号分组进行:
## # A tibble: 295 × 2
## # Groups: symbol [295]
## symbol n
## <chr> <int>
## 1 1INCH 1444
## 2 AAB 719
## 3 ABBC 137
## 4 ACT 330
## 5 ADA 1444
## 6 AGIX 1443
## 7 AKRO 156
## 8 ALCX 69
## 9 ALI 266
## 10 ALICE 267
## # ℹ 285 more rows
我们可以随时通过运行 ungroup() 函数来移除分组:
## # A tibble: 1 × 1
## n
## <int>
## 1 262390
由于这个数据集
随时间变化,即使它们目前不是问题,我们也需要对可能出现的问题采取主动措施。
如果加密货币交易所新增了一种加密货币会发生什么?如果我们只有几天的数据,那不仅不足以构建有效的预测模型,而且由于数据将被进一步分割成更多组以使用交叉验证对模型的结果进行验证,我们可能会遇到实际错误,稍后将详细介绍。
为了确保我们对每种单独的加密货币有足够多的数据,让我们使用 filter() 函数过滤掉任何观察次数不足 1000 的加密货币:
cryptodata 数据集在过滤步骤之前的行数为 262402,现在为 219714。此步骤从分析中移除了 127 种没有足够观察次数的加密货币。
如果在过去 3 天内没有收集到某种加密货币的数据,让我们从数据集中排除该资产,因为我们只希望对当前正在流通的数据进行建模。如果某个资产被交易所移除(例如,如果一个项目是骗局)或不再被数据收集过程积极捕获,我们就无法为其做出新的预测,因此最好提前排除这些。
cryptodata <- filter(cryptodata, max(date) > Sys.Date()-3)
在此过滤步骤之前,cryptodata 数据集的行数为 215024,现在为 219714。
我们的目标是能够预测每种加密货币在数据收集后 24 小时内的价格。因此,我们将用作预测模型预测目标的目标变量是相对于数据收集时间 24 小时后的价格。
为此,我们可以在数据中创建一个新列,该列是 price_usd 偏移 24 行(每小时一行),但在此之前,我们需要确保数据中没有任何间隙。
我们可以使用 tsibble 包(Wang 等人 2020)填充数据中的任何间隙,该包在本教程的高级版本中有更详细的介绍。
我们使用的数据是在每小时的第 0 分钟到第 5 分钟之间收集的;它按相同的顺序每小时收集一次,以尽可能保持每种加密货币的时间一致,但节奏并不完全是每小时一次。因此,如果我们现在将数据转换为 tsibble 对象,它会将数据识别为错误的节奏。
为了解决这个问题,让我们使用 mutate() 函数创建一个名为 ts_index 的新列,该列将保留与日期和小时相关的信息,但将分钟和秒数概括为“00:00”,这将被 tsibble 包正确识别为每小时收集一次的数据。pkDummy 字段包含日期和小时,所以我们可以在该字段的末尾添加文本“:00:00”,然后使用 anytime 包(Eddelbuettel 2020)中的 anytime() 函数将新字符串转换为日期时间对象:
在我们可以将数据转换为 tsibble 并轻松填充间隙之前,我们还需要确保每种加密货币的 ts_index
列中没有重复值。不应该有重复值,但以防万一,我们可以使用 dplyr 包中的 distinct() 函数防止这个问题可能出现:
现在我们终于可以使用 tsibble 包(Wang 等人 2020)中的 as_tsibble() 函数将表格转换为 tsibble 数据类型,并为 key 参数提供 symbol 列以保留分组结构:
注意下面的数据预览与我们到目前为止看到的摘要有些不同,现在它显示“一个 tsibble”,并在表格尺寸旁边显示 [1h],表示观察结果相隔 1 小时。第二行告诉我们 tsibble 的“键”是 symbol 列
## # A tsibble: 219,766 x 34 [1h] <Asia/Shanghai>
## # Key: symbol [168]
## # Groups: symbol [168]
## pair symbol quote_currency ask_1_price ask_1_quantity ask_2_price
## <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 1INCHUSD 1INCH USD 0.320 963. 0.320
## 2 1INCHUSD 1INCH USD 0.320 963. 0.320
## 3 1INCHUSD 1INCH USD 0.324 963. 0.324
## 4 1INCHUSD 1INCH USD 0.326 0.1 0.326
## 5 1INCHUSD 1INCH USD 0.324 963. 0.324
## 6 1INCHUSD 1INCH USD 0.324 892. 0.324
## 7 1INCHUSD 1INCH USD 0.322 963. 0.322
## 8 1INCHUSD 1INCH USD 0.320 9604. 0.320
## 9 1INCHUSD 1INCH USD 0.322 963. 0.322
## 10 1INCHUSD 1INCH USD 0.320 963. 0.321
## # ℹ 219,756 more rows
## # ℹ 28 more variables: ask_2_quantity <dbl>, ask_3_price <dbl>,
## # ask_3_quantity <dbl>, ask_4_price <dbl>, ask_4_quantity <dbl>,
## # ask_5_price <dbl>, ask_5_quantity <dbl>, bid_1_price <dbl>,
## # bid_1_quantity <dbl>, bid_2_price <dbl>, bid_2_quantity <dbl>,
## # bid_3_price <dbl>, bid_3_quantity <dbl>, bid_4_price <dbl>,
## # bid_4_quantity <dbl>, bid_5_price <dbl>, bid_5_quantity <dbl>, …
现在我们可以使用 tsibble 包中的 fill_gaps() 函数填充数据中发现的任何间隙,将其隐式地视为空值。这意味着,我们将在数据中添加这些行,并将除日期时间字段之外的所有内容填充为 NA 值。这将使我们能够安全地计算每行收集的 24 小时后的目标价格。
现在再次查看数据,有 9063 个额外的行被添加为数据中隐式缺失的行:
## # A tsibble: 229,183 x 34 [1h] <Asia/Shanghai>
## # Key: symbol [168]
## # Groups: symbol [168]
## pair symbol quote_currency ask_1_price ask_1_quantity ask_2_price
## <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 1INCHUSD 1INCH USD 0.320 963. 0.320
## 2 1INCHUSD 1INCH USD 0.320 963. 0.320
## 3 1INCHUSD 1INCH USD 0.324 963. 0.324
## 4 1INCHUSD 1INCH USD 0.326 0.1 0.326
## 5 1INCHUSD 1INCH USD 0.324 963. 0.324
## 6 1INCHUSD 1INCH USD 0.324 892. 0.324
## 7 1INCHUSD 1INCH USD 0.322 963. 0.322
## 8 1INCHUSD 1INCH USD 0.320 9604. 0.320
## 9 1INCHUSD 1INCH USD 0.322 963. 0.322
## 10 1INCHUSD 1INCH USD 0.320 963. 0.321
## # ℹ 229,173 more rows
## # ℹ 28 more variables: ask_2_quantity <dbl>, ask_3_price <dbl>,
## # ask_3_quantity <dbl>, ask_4_price <dbl>, ask_4_quantity <dbl>,
## # ask_5_price <dbl>, ask_5_quantity <dbl>, bid_1_price <dbl>,
## # bid_1_quantity <dbl>, bid_2_price <dbl>, bid_2_quantity <dbl>,
## # bid_3_price <dbl>, bid_3_quantity <dbl>, bid_4_price <dbl>,
## # bid_4_quantity <dbl>, bid_5_price <dbl>, bid_5_quantity <dbl>, …
现在所有的间隙都已填充,让我们将数据转换回 tibble 的结构,这是支持我们之前讨论的分组结构的数据结构,并再次按 symbol 分组数据:
现在我们终于有了计算目标变量所需的一切,该变量包含相对于数据收集时间 24 小时后的价格。我们可以使用常规的 mutate() 函数向数据添加一个名为 target_price_24h 的新列,并使用 dplyr 中的 lead() 函数将 price_usd 列偏移 24 小时:
那么做相反的事情呢?如果我们添加了一个新列,显示 24 小时前的价格,那么在数据收集时与之前的价格之间的价格变动是否有助于我们预测下一个价格的走向?如果价格在过去 24 小时内显著下跌,那么接下来 24 小时的价格更有可能上涨还是下跌?如果价格在过去 24 小时内显著下跌,但自过去一小时以来显著上涨呢?
这些关于价格对近期价格变化的敏感性的关系可能有助于我们的
模型对未来做出更准确的预测,因此让我们继续添加一些滞后价格,使用与计算目标变量相同的方法,但这次使用 lag() 函数来获取过去的观察值,而不是之前使用的 lead() 函数:
cryptodata <- mutate(cryptodata,
lagged_price_1h = lag(price_usd, 1, order_by=ts_index),
lagged_price_2h = lag(price_usd, 2, order_by=ts_index),
lagged_price_3h = lag(price_usd, 3, order_by=ts_index),
lagged_price_6h = lag(price_usd, 6, order_by=ts_index),
lagged_price_12h = lag(price_usd, 12, order_by=ts_index),
lagged_price_24h = lag(price_usd, 24, order_by=ts_index),
lagged_price_3d = lag(price_usd, 24*3, order_by=ts_index))
这一步骤可以被认为是数据工程而不是数据清理,因为我们不是在修复问题,而是在通过可能有助于预测的列来增强数据集。
让我们查看与比特币加密货币(symbol == “BTC”)相关的最早的 30 行数据的示例。从最早的数据开始,lagged_price_1h 字段对于第一行应该有一个 NA 值,因为我们在那一点之前没有任何价格。按照相同的逻辑,lagged_price_24h 列应该缺少前 24 个值,并且显示前 6 行 price_usd 列的前 6 个值。target_price_24h 对于最早的数据应该有值,因为相反的情况是真实的,我们不知道最近 24 行数据的值:
print(select(filter(cryptodata, symbol == 'BTC'),
ts_index, price_usd, lagged_price_1h,
lagged_price_24h, target_price_24h), n=30)
## Adding missing grouping variables: `symbol`
## # A tibble: 1,454 × 6
## # Groups: symbol [1]
## symbol ts_index price_usd lagged_price_1h lagged_price_24h
## <chr> <dttm> <dbl> <dbl> <dbl>
## 1 BTC 2023-06-27 00:00:00 30276. NA NA
## 2 BTC 2023-06-27 01:00:00 30359. 30276. NA
## 3 BTC 2023-06-27 02:00:00 30374. 30359. NA
## 4 BTC 2023-06-27 03:00:00 30493. 30374. NA
## 5 BTC 2023-06-27 04:00:00 30370. 30493. NA
## 6 BTC 2023-06-27 05:00:00 30353. 30370. NA
## 7 BTC 2023-06-27 06:00:00 30320. 30353. NA
## 8 BTC 2023-06-27 07:00:00 30329. 30320. NA
## 9 BTC 2023-06-27 08:00:00 30411. 30329. NA
## 10 BTC 2023-06-27 09:00:00 30395. 30411. NA
## 11 BTC 2023-06-27 10:00:00 30382. 30395. NA
## 12 BTC 2023-06-27 11:00:00 30660. 30382. NA
## 13 BTC 2023-06-27 12:00:00 30735. 30660. NA
## 14 BTC 2023-06-27 13:00:00 30718. 30735. NA
## 15 BTC 2023-06-27 14:00:00 30612. 30718. NA
## 16 BTC 2023-06-27 15:00:00 30809. 30612. NA
## 17 BTC 2023-06-27 16:00:00 30501. 30809. NA
## 18 BTC 2023-06-27 17:00:00 30716. 30501. NA
## 19 BTC 2023-06-27 18:00:00 30737. 30716. NA
## 20 BTC 2023-06-27 19:00:00 NA 30737. NA
## 21 BTC 2023-06-27 20:00:00 NA NA NA
## 22 BTC 2023-06-27 21:00:00 NA NA NA
## 23 BTC 2023-06-27 22:00:00 NA NA NA
## 24 BTC 2023-06-27 23:00:00 NA NA NA
## 25 BTC 2023-06-28 00:00:00 30696. NA 30276.
## 26 BTC 2023-06-28 01:00:00 30613. 30696. 30359.
## 27 BTC 2023-06-28 02:00:00 30505. 30613. 30374.
## 28 BTC 2023-06-28 03:00:00 30418. 30505. 30493.
## 29 BTC 2023-06-28 04:00:00 30459. 30418. 30370.
## 30 BTC 2023-06-28 05:00:00 30489. 30459. 30353.
## # ℹ 1,424 more rows
## # ℹ 1 more variable: target_price_24h <dbl>
我们可以使用 tail() 函数将上面使用的代码包装起来,以显示最新的数据,并查看我们创建的新字段的相反动态:
print(tail(select(filter(cryptodata, symbol == 'BTC'),
ts_index, price_usd, lagged_price_24h,
target_price_24h),30), n=30)
## Adding missing grouping variables: `symbol`
## # A tibble: 30 × 5
## # Groups: symbol [1]
## symbol ts_index price_usd lagged_price_24h target_price_24h
## <chr> <dttm> <dbl> <dbl> <dbl>
## 1 BTC 2023-08-25 08:00:00 25979. 26476. 26051.
## 2 BTC 2023-08-25 09:00:00 26095. 26504. 26044.
## 3 BTC 2023-08-25 10:00:00 26077. 26443. 26014.
## 4 BTC 2023-08-25 11:00:00 26091. 26470. 26030.
## 5 BTC 2023-08-25 12:00:00 26103. 26445. 26048.
## 6 BTC 2023-08-25 13:00:00 26106. 26388. 26035.
## 7 BTC 2023-08-25 14:00:00 26068. 26361. NA
## 8 BTC 2023-08-25 15:00:00 25832. 26120. NA
## 9 BTC 2023-08-25 16:00:00 25927. 26004. NA
## 10 BTC 2023-08-25 17:00:00 25932. 26109. NA
## 11 BTC 2023-08-25 18:00:00 25897. 26065. NA
## 12 BTC 2023-08-25 19:00:00 26056. 26027. NA
## 13 BTC 2023-08-25 20:00:00 25978. 26045. NA
## 14 BTC 2023-08-25 21:00:00 26057. 26034. NA
## 15 BTC 2023-08-25 22:00:00 26081. 26096. NA
## 16 BTC 2023-08-25 23:00:00 26052. 26117. NA
## 17 BTC 2023-08-26 00:00:00 26060. 26184. NA
## 18 BTC 2023-08-26 01:00:00 26045. 26144. NA
## 19 BTC 2023-08-26 02:00:00 26050. 26153. NA
## 20 BTC 2023-08-26 03:00:00 26077. 26132. NA
## 21 BTC 2023-08-26 04:00:00 26093. 26109. NA
## 22 BTC 2023-08-26 05:00:00 26079. 26030. NA
## 23 BTC 2023-08-26 06:00:00 26060. 26056. NA
## 24 BTC 2023-08-26 07:00:00 26075. 26018. NA
## 25 BTC 2023-08-26 08:00:00 26051. 25979. NA
## 26 BTC 2023-08-26 09:00:00 26044. 26095. NA
## 27 BTC 2023-08-26 10:00:00 26014. 26077. NA
## 28 BTC 2023-08-26 11:00:00 26030. 26091. NA
## 29 BTC 2023-08-26 12:00:00 26048. 26103. NA
## 30 BTC 2023-08-26 13:00:00 26035. 26106. NA
阅读上面显示的代码不太理想。tidyverse 引入的更受欢迎的工具之一是 %>% 运算符,它的工作方式是首先从你想要修改的对象/数据开始,然后一步一步地应用每个转换步骤。它只是一种重新编写相同代码的方式,通过拆分函数的调用方式而不是将函数添加到一行中,这会变得非常难以阅读。在上面的示例中,很难跟踪事情的开始、操作顺序和与特定函数相关的参数。将其与下面的代码进行比较:
# 从要操作的对象/数据开始
cryptodata %>%
# 仅过滤 BTC 符号的数据
filter(symbol == 'BTC') %>%
# 选择要显示的列
select(ts_index, price_usd, lagged_price_24h, target_price_24h) %>%
# 显示数据的最后 30 个元素
tail(30) %>%
# 显示所有 30 个元素而不是 tibble 数据帧的默认 10 个
print(n = 30)
## Adding missing grouping variables: `symbol`
## # A tibble: 30 × 5
## # Groups: symbol [1]
## symbol ts_index price_usd lagged_price_24h target_price_24h
## <chr> <dttm> <dbl> <dbl> <dbl>
## 1 BTC 2023-08-25 08:00:00 25979. 26476. 26051.
## 2 BTC 2023-08-25 09:00:00 26095. 26504. 26044.
## 3 BTC 2023-08-25 10:00:00 26077. 26443. 26014.
## 4 BTC 2023-08-25 11:00:00 26091. 26470. 26030.
## 5 BTC 2023-08-25 12:00:00 26103. 26445. 26048.
## 6 BTC 2023-08-25 13:00:00 26106. 26388. 26035.
## 7 BTC 2023-08-25 14:00:00 26068. 26361. NA
## 8 BTC 2023-08-25 15:00:00 25832. 26120. NA
## 9 BTC 2023-08-25 16:00:00 25927. 26004. NA
## 10 BTC 2023-08-25 17:00:00 25932. 26109. NA
## 11 BTC 2023-08-25 18:00:00 25897. 26065. NA
## 12 BTC 2023-08-25 19:00:00 26056. 26027. NA
## 13 BTC 2023-08-25 20:00:00 25978. 26045. NA
## 14 BTC 2023-08-25 21:00:00 26057. 26034. NA
## 15 BTC 2023-08-25 22:00:00 26081. 26096. NA
## 16 BTC 2023-08-25 23:00:00 26052. 26117. NA
## 17 BTC 2023-08-26 00:00:00 26060. 26184. NA
## 18 BTC 2023-08-26 01:00:00 26045. 26144. NA
## 19 BTC 2023-08-26 02:00:00 26050. 26153. NA
## 20 BTC 2023-08-26 03:00:00 26077. 26132. NA
## 21 BTC 2023-08-26 04:00:00 26093. 26109. NA
## 22 BTC 2023-08-26 05:00:00 26079. 26030. NA
## 23 BTC 2023-08-26 06:00:00 26060. 26056. NA
## 24 BTC 2023-08-26 07:00:00 26075. 26018. NA
## 25 BTC 2023-08-26 08:00:00 26051. 25979. NA
## 26 BTC 2023-08-26 09:00:00 26044. 26095. NA
## 27 BTC 2023-08-26 10:00:00 26014. 26077. NA
## 28 BTC 2023-08-26 11:00:00 26030. 26091. NA
## 29 BTC 2023-08-26 12:00:00 26048. 26103. NA
## 30 BTC 2023-08-26 13:00:00 26035. 26106. NA
有几个优点让代码更整洁,但虽然有些人喜欢它,有些人讨厌它,所以我们不会强迫任何人必须了解 %>% 运算符的工作原理,我们已经避免在其余代码中使用它,但我们确实鼓励使用这个工具:https://magrittr.tidyverse.org/reference/pipe.html
如果我们不知道数据收集的时间,我们就无法对一行数据做任何事情,所以让我们通过使用 dplyr 包中的 filter() 函数排除任何 date_time_utc 列的 NA 值的行来再次确认所有行都有该字段的值:
此步骤从数据中移除了 9063 行。这一步主要帮助我们避免在下一节中以编程方式标记图表时出现问题,继续下一节 ➡️ 学习一些强大的工具来可视化数据!
使用 ggplot2 包(Wickham, Chang 等人 2020)进行可视化是 R 生态系统中最好的工具之一。ggplot2 中的 gg 代表图形的语法,这本质上是许多不同类型的图表共享相同的底层构建块,并且它们可以以不同的方式组合在一起,以制作看起来彼此非常不同的图表。用包的创建者 Hadley Wickham 的话来说,“饼图只是在极坐标中绘制的条形图”,“它们看起来非常不同,但在语法方面有很多相似之处。”
那么 ggplot2 是如何工作的呢?“…在大多数情况下,你从 ggplot() 开始,提供数据集和美学映射(使用 aes())。然后你添加图层(如 geom_point() 或 geom_histogram())、比例尺(如 scale_colour_brewer())、分面规范(如 facet_wrap())和坐标系统(如 coord_flip())。” - ggplot2.tidyverse.org/。
让我们逐步分解这个过程。
“从 ggplot() 开始,提供数据集和美学映射(使用 aes())”
使用 ggplot() 函数,我们首先提供数据集,然后定义美学映射(图表的视觉属性),将 date_time_utc 设置为 x 轴,将 price_usd 设置为 y 轴:
我们期望得到一个随时间变化的价格图表,但现在图表显示为空,因为我们需要执行额外的步骤来确定数据点在图表上的实际显示方式:“然后你添加图层(如 geom_point() 或 geom_histogram())…”
我们可以使用与上面相同的代码,添加 + geom_point() 来将数据以点的形式显示在图表上:
ggplot(data = cryptodata, aes(x = as.Date(date_time_utc), y = price_usd)) +
# 添加 geom_point():
geom_point()
价格最高的加密货币“BTC”使得查看其他任何货币都变得困难。让我们尝试只放大一个,通过对数据参数进行调整,仅显示符号为 ETH 的加密货币。
让我们将数据过滤为仅 ETH 加密货币,并创建新的数据集 eth_data:
我们现在可以使用之前相同的代码,为 data 参数提供新过滤的数据集:
这个更好一些,但 geom_point() 可能不是这个图表的最佳选择,让我们将 geom_point() 改为 geom_line(),看看效果如何:
ggplot(data = eth_data,
aes(x = date_time_utc, y = price_usd)) +
# 将 geom_point() 改为 geom_line():
geom_line()
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
让我们将结果保存为名为 crypto_chart 的对象:
我们可以通过向图表添加 stat_smooth() 来添加一条显示随时间变化的趋势线:
我们可以通过再次调用 crypto_chart 对象来显示新结果:
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
使用 ggplot 框架的一个特别好的方面是,我们可以继续向图表添加尽可能多的元素和转换,而没有任何限制。
我们这次不会保存下面显示的结果,但为了说明这一点,我们可以添加一条使用 stat_smooth(method = ‘lm’) 的线性回归拟合线穿过数据。让我们还以绿色显示单个点。我们可以根据需要继续叠加尽可能多的层:
crypto_chart +
# 添加线性回归线
stat_smooth(method = 'lm', color='red') +
# 添加点
geom_point(color='dark green', size=0.8)
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
不提供任何方法选项时,stat_smooth() 函数默认使用称为 loess 的方法,显示局部趋势,而 lm 模型则为整体数据拟合最佳线性回归线。上面显示的结果没有用来覆盖 crypto_chart 对象。
当然,添加其他组件以使可视化效果有效也很重要,让我们现在使用 xlab() 和 ylab() 以及 ggtitle() 为图表添加标签、标题和副标题:
crypto_chart <- crypto_chart +
xlab('日期时间 (UTC)') +
ylab('价格 ($)') +
ggtitle(paste('价格随时间变化 -', eth_data$symbol),
subtitle = paste('最近收集的数据时间为:',
max(eth_data$date_time_utc),
'(UTC)'))
# 显示新图表
crypto_chart
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
ggplot2 包附带了大量我们在这里没有涵盖的功能。你可以在这里找到你可以使用的函数的完整参考:
https://ggplot2.tidyverse.org/reference/
使 ggplot2 包变得更好的是,它还附带了一个框架,供任何人开发自己的扩展。这意味着社区创建了更多的功能,可以通过导入提供 ggplot 扩展的其他包来添加。
要使用扩展,我们只需要像我们对 ggplot2 和其他我们想要使用的包一样将其导入到我们的 R 会话中。我们已经在设置部分加载了 ggthemes(Arnold 2019)包,因此我们不需要运行 library(ggthemes) 来将包导入会话。
我们现在可以将主题应用到图表上,并更改其外观:
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
下面是你可以测试的主题的完整列表。如果你一直跟随到这里,尝试运行代码 crypto_chart + theme_excel() 或下面列出的任何其他选项,而不是 + theme_excel():
https://yutannihilation.github.io/allYourFigureAreBelongToUs/ggthemes/
在某些情况下,使图表对光标悬停做出响应是有帮助的。我们可以使用 plotly 包(Sievert 等人 2020),将任何 ggplot 转换为交互式图表,这非常简单!
我们已经在设置部分导入了 plotly 包,因此我们所需要做的就是将我们的图表包装在 ggplotly() 函数中:
plotly::ggplotly(crypto_chart)
标悬停在上面的图表上的特定点上。还要注意,我们没有覆盖 crypto_chart 对象,而只是显示结果。
如果你不打算将 ggplot 转换为交互式,plotly 还提供了自己的框架,用于从头开始制作图表,你可以在这里了解更多:
ggpubr 扩展(Kassambara 2020)提供了很多我们在这里不会涵盖的功能,但我们可以使用的一个函数是 stat_cor,它允许我们在图表上添加相关系数(R)和 p 值。
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
我们将在评估模型性能的部分深入探讨这些指标。
ggforce 包(Pedersen 2020)是注释图表的有用工具。我们可以例如注释异常值:
crypto_chart <- crypto_chart +
geom_mark_ellipse(aes(filter = price_usd == max(price_usd),
label = date_time_utc,
description = paste0('价格飙升至 $', price_usd))) +
# 现在对最低价格做同样的圆圈标记:
geom_mark_ellipse(aes(filter = price_usd == min(price_usd),
label = date_time_utc,
description = paste0('价格跌至 $', price_usd)))
使用 geom_mark_ellipse() 函数时,我们通过 aes() 函数传递数据参数、标签和描述。我们标记了两个点,一个是时间段内的最低价格,另一个是最高价格。对于第一个点,我们过滤数据仅为 price_usd 等于 max(price_usd) 的点,并相应添加标签。对于第二个点,做同样的操作,但显示给定日期范围内的最低价格点。
现在查看新图表:
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'
## Warning: Using the `size` aesthetic in this geom was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` in the `default_aes` field and elsewhere instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
请注意,这个图表是围绕这些点特别注释的,但我们从未指定要圈出的特定日期,而且我们总是圈出最大值和最小值,而不管特定数据。本文档的一个要点是展示这样一个观点:当涉及到数据分析、可视化和报告时,大多数工作场所的人将这些视为一次性任务,但使用合适的(开源/免费)工具,自动化和可重现性就成为了理所当然的,任何旧的分析都可以再次运行以获得完全相同的结果,或者可以使用完全相同的方法对最新视图的数据执行相同的操作。
我们还可以使用 gganimate 包(Pedersen 和 Robinson 2020)扩展 ggplot 的功能,它允许我们通过使用 transition_states() 函数在数据中的组上迭代,创建一个动画 GIF。
animated_prices <- ggplot(data = mutate(cryptodata, groups=symbol),
aes(x = date_time_utc, y = price_usd)) +
geom_line() +
theme_economist() +
transition_states(groups) +
ggtitle('价格随时间变化',subtitle = '{closest_state}') +
stat_smooth() +
view_follow() # 这会根据组调整坐标轴
# 显示动画(减慢到每秒 1 帧):
animate(animated_prices,fps=1)
## Error in `$<-.data.frame`(`*tmp*`, "group", value = ""): replacement has 1 row, data has 0
我们建议参考此文档,了解有关使用 gganimate 的简单直接示例:https://gganimate.com/articles/gganimate.html
ggTimeSeries 包(Kothari 2018)具有在绘制时间序列数据时有用的功能。我们可以使用 ggplot_calendar
_heatmap() 函数创建价格随时间的日历热图:
library(ggTimeSeries)
calendar_heatmap <- ggplot_calendar_heatmap(eth_data,'date_time_utc','price_usd') # 或者在这里做 target_percent_change?
calendar_heatmap
y 轴上的 DoW 代表一周中的哪一天
要按正确的日期顺序阅读此图表,请从左上角开始,向下工作,一旦到达列的底部,请向右移动。颜色越浅,特定日的价格越高。
前面的图表很有用,但这样的颜色刻度可能有点难以解释。我们可以使用惊人的 rayshader 包(Morgan-Wall 2020)将前面的图表转换为更容易视觉解释的 3D 图形。
本文档通过 GitHub Actions 自动运行,它没有图形环境来运行下面的代码,这阻止了它使用最新数据刷新结果。我们在下面的 rayshader 部分显示了旧结果。如果你已经到了这一点,值得在最新数据上运行下面的代码,亲自看看这个惊人的包的效果!
# 首先从图例中删除标题,以避免视觉问题
calendar_heatmap <- calendar_heatmap + theme(legend.title = element_blank())
# 添加日期到标题,以明确这些刷新两次每天
calendar_heatmap <- calendar_heatmap + ggtitle(paste0('截至:',substr(max(eth_data$date_time_utc),1,10)))
# 转换为 3D 图
plot_gg(calendar_heatmap, zoom = 0.60, phi = 35, theta = 45)
# 渲染快照
render_snapshot('rayshader_image.png')
# 关闭 RGL(在 plot_gg() 命令中在单独的窗口中打开)
rgl.close()
这是之前制作的相同的二维日历热图。
因为我们可以像上面所示编程调整相机,这意味着我们也可以创建一个快照,移动相机并拍摄另一个,继续进行,直到我们有足够的让它看起来像视频!使用 render_movie() 函数可以轻松完成这一点,该函数将在后台为之前的相同图表处理一切:
# 这次让我们删除比例尺,因为我们没有改变它:
calendar_heatmap <- calendar_heatmap + theme(legend.position = "none")
# 与之前相同的 3D 图
plot_gg(calendar_heatmap, zoom = 0.60, phi = 35, theta = 45)
# 渲染视频
render_movie('rayshader_video.mp4')
# 关闭 RGL
rgl.close()
在制作预测模型之前,我们需要仔细考虑我们将如何定义一个预测模型是“好”的还是“坏”的。在没有很好地了解我们期望模型在现实世界中的表现如何之前,我们不希望部署一个预测模型。我们可能永远不会得到一个100%准确的代表模型在现实世界中的实际表现,但有一些方法可以让我们提前了解某些事情是否有效,以及确保在训练模型时没有犯下明显的错误。
获取预测模型有效性感觉的最简单方法是取大部分数据(通常是大约80%的观测值)并将其分配为训练数据集,预测模型使用该数据集学习数据中的统计模式,然后可以用来对未来进行预测。使用尚未被统计模型看到的其余数据(称为测试数据集),我们可以评估统计模型是否按照我们基于训练数据集获得的结果所期望的方式在新数据上工作。如果两者之间的结果一致,这是一个好兆头。
如果我们多次进行这个过程(称为交叉验证),我们就有更多的信息来了解模型在预测它之前没有看到的数据时的表现如何。如果测试数据上的结果比训练数据上获得的结果差得多,这可能是过拟合的迹象,意味着模型在训练数据上过度专业化,它在预测新数据时不是很好,因为它学习了训练数据的确切模式,而不是理解不同变量之间的实际关系。总是要小心那些好得令人难以置信的结果。在这个过程中很可能犯了错误。犯错误很容易,这就是为什么我们需要一个好的系统来轻松捕捉这些错误。这是一个更长的讨论,但也很重要的是要考虑用于训练模型的数据在做出新预测的背景下将如何被利用;如果用于训练模型的变量在做出新预测时不可用,那么这根本行不通。
在我们的背景下,考虑数据的日期/时间方面也很重要。例如,如果我们将未来的数据用于训练数据集相对于测试数据,这是否会给模型提供更多实际上在进行新预测时可用的信息?绝对可以,因为具有滞后价格的列代表过去的价格,这可能以一种在进行新预测时无法利用的方式“泄露了解决方案”给测试数据。因此,当将数据分割为训练/测试数据集时,我们将使用时间感知交叉验证来跟踪数据
的收集时间。
由于上面讨论的问题,我们需要确保训练数据总是在测试数据之前收集。这就是我们所说的“时间感知”。
然后我们将使用“交叉验证”来创建5个不同的训练/测试分割来评估模型的准确性。从这5个中,我们将采用包含最新数据的测试分割,并将其视为我们的保留数据集。保留数据集代表我们可以将模型性能与之进行比较的世界的最新版本,并将为我们提供评估它们准确性的额外方式。
这将留给我们:
5个训练数据集用于构建预测模型。
4个测试数据集用于评估前4个训练模型的性能。模型是否能够准确和一致地预测在4个独立的数据子集上训练和测试的价格变动?
1个保留数据集用于评估所有5个训练模型的性能。模型在预测我们可以评估的最新数据子集时有多准确?
上面的解释非常重要要理解!然而,这一步的代码和实现并不是那么重要。重点关注上面概述的概念性理解,而不是理解下面使用的代码。
在下面的代码中,我们正在添加两个新列。首先是分割,它根据收集时间将每个观测值分配一个1到5的交叉验证分割号。然后是训练列,它标识每一行是属于给定分割的训练、测试还是保留数据。
我们将不会详细介绍下面代码的步骤,除了整个代码中留下的注释,因为我们宁愿将注意力集中在上面概述的这一步的概念性理解上。有许多进行时间感知交叉验证的方法,但没有一种特别适合我们想要概述的下一节,所以我们自己制作了一个,理解它的工作原理并不重要,但它也不是那么复杂,并且使用了到目前为止在本节中使用的相同工具。参见高级教程的这一部分,了解可以在本教程中使用的数据集之外使用的方法,并且与两个版本的预测建模部分中使用的工具兼容。
# 移除具有空的 date_time_utc 的行以排除下一步中的缺失数据
cryptodata <- drop_na(cryptodata, date_time_utc)
# 按 symbol 计数
cryptodata <- mutate(group_by(cryptodata, symbol), tot_rows = n())
# 按 symbol 添加行索引
cryptodata <- mutate(arrange(cryptodata, date_time_utc), row_id = seq_along(date_time_utc))
# 计算哪些行属于第一个分割
cryptodata <- cryptodata %>% mutate(split_rows_1 = as.integer(n()/5),
split_rows_2 = as.integer(split_rows_1*2),
split_rows_3 = as.integer(split_rows_1*3),
split_rows_4 = as.integer(split_rows_1*4),
split_rows_5 = as.integer(split_rows_1*5))
# 现在计算当前 row_id 属于哪个分割
cryptodata <- mutate(cryptodata,
split = case_when(
row_id <= split_rows_1 ~ 1,
row_id <= split_rows_2 ~ 2,
row_id <= split_rows_3 ~ 3,
row_id <= split_rows_4 ~ 4,
row_id > split_rows_4 ~ 5))
# 现在确定训练/测试组
cryptodata <- cryptodata %>% mutate(train_rows_1 = (as.integer(n()/5))*0.8,
test_rows_1
= train_rows_1 + (as.integer(n()/5))*0.2,
train_rows_2 = test_rows_1 + train_rows_1,
test_rows_2 = train_rows_2 + (as.integer(n()/5))*0.2,
train_rows_3 = test_rows_2 + train_rows_1,
test_rows_3 = train_rows_3 + (as.integer(n()/5))*0.2,
train_rows_4 = test_rows_3 + train_rows_1,
test_rows_4 = train_rows_4 + (as.integer(n()/5))*0.2,
train_rows_5 = test_rows_4 + train_rows_1,
test_rows_5 = train_rows_5 + (as.integer(n()/5))*0.2)
# 现在分配训练/测试组
cryptodata <- mutate(cryptodata,
training = case_when(
row_id <= train_rows_1 ~ 'train',
row_id <= test_rows_1 ~ 'test',
row_id <= train_rows_2 ~ 'train',
row_id <= test_rows_2 ~ 'test',
row_id <= train_rows_3 ~ 'train',
row_id <= test_rows_3 ~ 'test',
row_id <= train_rows_4 ~ 'train',
row_id <= test_rows_4 ~ 'test',
row_id <= train_rows_5 ~ 'train',
row_id > train_rows_5 ~ 'holdout'))
# 现在移除所有不再需要的列
cryptodata <- select(cryptodata, -(tot_rows:test_rows_5), -(trade_usd_1:trade_usd_5),
-(ask_1_price:bid_5_quantity), -pair, -quote_currency,
-pkDummy, -pkey, -ts_index, split)
我们的数据现在添加了新列 training(train、test 或 holdout)和 split(数字 1-5),让我们看一下新列:
## Adding missing grouping variables: `symbol`
## # A tibble: 219,766 × 3
## # Groups: symbol [168]
## symbol training split
## <chr> <chr> <dbl>
## 1 BTC train 1
## 2 ETH train 1
## 3 EOS train 1
## 4 LTC train 1
## 5 BSV train 1
## 6 ADA train 1
## 7 ZEC train 1
## 8 HT train 1
## 9 TRX train 1
## 10 KNC train 1
## # ℹ 219,756 more rows
## # A tibble: 215,024 x 3
## # Groups: symbol [164]
## symbol training split
## <chr> <chr> <dbl>
## 1 BTC train 1
## 2 ETH train 1
## 3 EOS train 1
## 4 LTC train 1
## 5 BSV train 1
## 6 ADA train 1
## 7 ZEC train 1
## 8 HT train 1
## 9 TRX train 1
## 10 KNC train 1
## # ... with 215,014 more rows
注意即使我们从选择中排除了 symbol 变量,但因为它是我们分组数据的一部分,所以它被带回来了,信息显示“添加缺失的分组变量 symbol”。数据在进行所有操作时都与其分组相关联,直到我们使用 ungroup() 来撤销它们。
让我们将新的 split 列添加到数据分组的方式中:
新字段 split 帮助我们根据日期将数据分割成 5 个不同的数据集,并包含一个从 1-5 的数字。新字段 training 将数据标记为属于训练数据集、测试数据集或保留数据集(对于第一个分割)的一部分,每个 5 个分割/数据集。
使用 tail() 再次运行相同的代码,我们可以看到与第 5 个分割的测试数据相关联的行(再次记住,每个 5 个分割都有一个训练和测试数据集):
## Adding missing grouping variables: `symbol`
## # A tibble: 6 × 3
## # Groups: symbol, split [6]
## symbol training split
## <chr> <chr> <dbl>
## 1 BTCST holdout 5
## 2 POA holdout 5
## 3 CAPS holdout 5
## 4 PAR holdout 5
## 5 DAPP holdout 5
## 6 IMX holdout 5
## # A tibble: 6 x 3
## # Groups: symbol, split [6]
## symbol training split
## <chr> <chr> <dbl>
## 1 BTCST holdout 5
## 2 PAR holdout 5
## 3 POA holdout 5
## 4 CAPS holdout 5
## 5 DAPP holdout 5
## 6 IMX holdout
理解这些分组的最简单方法是将它们可视化:
groups_chart <- ggplot(cryptodata,
aes(x = date_time_utc, y = split, color = training)) +
geom_point()
# 现在显示我们刚刚保存的图表:
groups_chart
上图看起来很奇怪,因为它包含了所有加密货币,而它们是独立处理的。我们可以通过运行相同的代码,但不是可视化整个数据集,而是使用 filter(cryptodata, symbol == “BTC”) 过滤数据,来查看仅针对 BTC 加密货币的结果,这将给我们一个更好的印象,了解已经创建的分解:
ggplot(filter(cryptodata, symbol == 'BTC'),
aes(x = date_time_utc,
y = split,
color = training)) +
geom_point()
我们可以通过为 cryptodata 对象设置动画来检查每种加密货币的分组:
animated_chart <- groups_chart +
transition_states(symbol) +
ggtitle('Now showing: {closest_state}')
# 显示新的动画图表
animate(animated_chart, fps = 2)
由于数据点最终看起来像线条,所以有点难以分辨有多少数据点。让我们将绘图改为使用 geom_jitter() 而不是 geom_point(),这将手动偏移点并给我们一个更好的印象,了解有多少数据点:
animated_chart <- animated_chart +
geom_jitter()
# 显示新的动画图表
animate(animated_chart, fps = 2)
现在我们已经将数据分割成许多不同的子集,这些子集本身可能存在阻止预测模型按预期工作的问题。
我们将在下一节中制作的第一个模型是一个简单的线性回归模型。如果数据包含任何具有零方差的列,即列的值在提供给模型的数据中从未改变,那么常规的 R 函数将无法工作。因此,在我们在下一步改变数据结构之前,让我们先修复任何与零方差列相关的问题。
首先让我们改变数据的分组。我们有兴趣根据 symbol、split 和 training 字段计算零方差:
现在让我们创建一个名为 find_zero_var 的新对象,它显示基于 symbol、split 和 train 分组的所有列的最小标准差的值:
find_zero_var <- select(mutate(cryptodata, min_sd = min(sd(price_usd, na.rm=T),
sd(target_price_24h, na.rm=T),
sd(lagged_price_1h, na.rm=T),
sd(lagged_price_2h, na.rm=T),
sd(lagged_price_3h, na.rm=T),
sd(lagged_price_6h, na.rm=T),
sd(lagged_price_12h, na.rm=T),
sd(lagged_price_24h, na.rm=T))), min_sd)
## Adding missing grouping variables: `symbol`, `split`, `training`
## # A tibble: 219,766 × 4
## # Groups: symbol, split, training [1,680]
## symbol split training min_sd
## <chr> <dbl> <chr> <dbl>
## 1 BTC 1 train 262.
## 2 ETH 1 train 38.4
## 3 EOS 1 train 0.0305
## 4 LTC 1 train 9.12
## 5 BSV 1 train 4.30
## 6 ADA 1 train 0.00751
## 7 ZEC 1 train 1.57
## 8 HT 1 train 0.0177
## 9 TRX 1 train 0.00136
## 10 KNC 1 train 0.0148
## # ℹ 219,756 more rows
## # A tibble: 215,024 x 4
## # Groups: symbol, split, training [1,640]
## symbol split training min_sd
## <chr> <dbl> <chr> <dbl>
## 1 BTC 1 train 262.
## 2 ETH 1 train 38.4
## 3 EOS 1 train 0.0306
## 4 LTC 1 train 9.18
## 5 BSV 1 train 4.32
## 6 ADA 1 train 0.00757
## 7 ZEC 1 train 1.57
## 8 HT 1 train 0.0177
## 9 TRX 1 train
## 10 KNC 1 train 0.0149
## # ... with 215,014 more rows
接下来,让我们得到一个加密货币符号列表,其中所有分割的数据的所有列的最小标准差为 0,这是我们稍后要从数据中删除的加密货币符号列表:
minimum_sd <- filter(distinct(mutate(group_by(ungroup(find_zero_var), symbol),
min_sd = min(min_sd, na.rm=T)), min_sd),min_sd < 0.0001)$symbol
# 显示结果
minimum_sd
## [1] "SEELE" "DGB" "XEM" "KMD" "MBL" "CKB" "BRD" "VIB" "OAX"
## [10] "DENT" "RCN" "LEO" "ETP" "APPC" "CND" "JST" "SWFTC" "JASMY"
## [19] "EKO" "CRO" "WMT" "GAS" "CEEK" "SPELL" "PLC" "BZRX" "NFT"
## [28] "USDD" "NXT" "AMB" "ZKS" "MTLX" "XCN" "G999" "DFC" "SBD"
## [37] "TRIBE" "SC" "USG" "CLO" "ANKR" "FTT" "DEG" "MIR" "MXC"
## [46] "ONG" "EURS" "CELR" "PLCUC" "STMX" "KLV" "BUSD" "IPL" "FLOKI"
## [55] "RSR" "BIST" "DGTX" "UTT" "BETA" "GST" "FREE" "BNK" "CW"
## [64] "SRM" "XTR" "BRG" "RARI" "GOLD" "PEARL" "STRM" "PLR" "KEY"
## [73] "SHIB" "TV" "IMPT" "BTCST" "POA" "CAPS" "DAPP" "MDX"
## [1] "SEELE" "DGB" "XEM" "KMD" "MBL" "CKB" "BRD" "VIB" "DENT"
## [10] "RCN" "LEO" "ETP" "APPC" "CND" "JST" "SWFTC" "JASMY" "CRO"
## [19] "WMT" "GAS" "CEEK" "SPELL" "PLC" "BZRX" "NFT" "USDD" "NXT"
## [28] "AMB" "ZKS" "MTLX" "XCN" "G999" "DFC" "SBD" "TRIBE" "SC"
## [37] "USG" "CLO" "ANKR" "FTT" "DEG" "MIR" "ONG" "EURS" "CELR"
## [46] "PLCUC" "STMX" "KLV" "BUSD" "IPL" "FLOKI" "RSR" "BIST" "DGTX"
## [55] "UTT" "GST" "FREE" "BNK" "CW" "SRM" "XTR" "BRG" "RARI"
## [64] "GOLD" "PEARL" "STRM" "PLR" "KEY" "SHIB" "TV" "IMPT" "BTCST"
## [73] "POA" "CAPS" "DAPP" "MDX"
现在我们可以从数据集中删除这些符号:
在上面的代码中,我们匹配所有符号是 minimum_sd 对象中要从数据中删除的加密货币符号列表的一部分的行,然后我们使用 ! 运算符来仅保留列表中未找到的符号的行。
到目前为止,我们一直使用的基本数据结构是数据框。这种数据类型支持其单元格内的许多种类的值,到目前为止,我们已经看到了数字、字符串和日期等东西,但我们也可以将整个另一个数据框作为值存储。这样做称为嵌套数据。
以下步骤以及稍后的预测建模部分采用的方法与 Hadley Wickham 发表的关于该主题的工作类似(Wickham 和 Grolemund 2017)。
下面是一个示例,展示了当我们嵌套数据时会发生什么:
## # A tibble: 880 × 4
## # Groups: symbol, split, training [880]
## symbol training split data
## <chr> <chr> <dbl> <list>
## 1 BTC train 1 <tibble [230 × 11]>
## 2 ETH train 1 <tibble [230 × 11]>
## 3 EOS train 1 <tibble [230 × 11]>
## 4 LTC train 1 <tibble [230 × 11]>
## 5 BSV train 1 <tibble [230 × 11]>
## 6 ADA train 1 <tibble [230 × 11]>
## 7 ZEC train 1 <tibble [230 × 11]>
## 8 HT train 1 <tibble [188 × 11]>
## 9 TRX train 1 <tibble [230 × 11]>
## 10 KNC train 1 <tibble [230 × 11]>
## # ℹ 870 more rows
## # A tibble: 880 x 4
## # Groups: symbol, training, split [880]
## symbol training split data
## <chr> <chr> <dbl> <list>
## 1 BTC train 1 <tibble [230 x 11]>
## 2 ETH train 1 <tibble [230 x 11]>
## 3 EOS train 1 <tibble [230 x 11]>
## 4 LTC train 1 <tibble [230 x 11]>
## 5 BSV train 1 <tibble [230 x 11]>
## 6 ADA train 1 <tibble [230 x 11]>
## 7 ZEC train 1 <tibble [230 x 11]>
## 8 HT train 1 <tibble [188 x 11]>
## 9 TRX train 1 <tibble [230 x 11]>
## 10 KNC train 1 <tibble [230 x 11]>
## # ... with 870 more rows
我们将从创建包含嵌套训练数据的新列开始。添加了一些额外的步骤来确保我们开始训练之前的数据完整性,但这些步骤在我们目前讨论的内容之外并不重要。尝试关注我们正在创建一个新数据集的概念性想法,该数据集按 symbol 和 split 列分组。作为第一步,我们正在创建一个名为 cryptodata_train 的新数据框,该数据框按 symbol 和 split 列分组,新的嵌套数据框在新的 train_data 列中:
cryptodata_train <- rename(nest(filter(cryptodata,
training=='train')),
train_data = 'data')
# 现在移除 training 列
cryptodata_train <- select(ungroup(cryptodata_train,
training),
-training)
# 修复数据组中的问题
cryptodata_train$train_data <- lapply(cryptodata_train$train_data, na.omit)
# 首先添加新列,包含训练数据集的 nrow
cryptodata_train <- group_by(ungroup(mutate(rowwise(cryptodata_train),
train_rows = nrow(train_data))),
symbol, split)
# 移除所有至少一次训练数据少于 20 行的符号
symbols_rm <- unique(filter(cryptodata_train,
split < 5, train_rows < 20)$symbol)
# 移除与上面发现的符号相关的所有数据
cryptodata_train <- filter(cryptodata_train,
! symbol %in% symbols_rm) # ! 用作 %not in% 运算符
# 删除 train_rows 列
cryptodata_train <- select(cryptodata_train, -train_rows)
# 显示结果
cryptodata_train
## # A tibble: 435 × 3
## # Groups: symbol, split [435]
## symbol split train_data
## <chr> <dbl> <list>
## 1 BTC 1 <tibble [158 × 11]>
## 2 ETH 1 <tibble [158 × 11]>
## 3 EOS 1 <tibble [158 × 11]>
## 4 LTC 1 <tibble [158 × 11]>
## 5 BSV 1 <tibble [158 × 11]>
## 6 ADA 1 <tibble [158 × 11]>
## 7 ZEC 1 <tibble [158 × 11]>
## 8 HT 1 <tibble [116 × 11]>
## 9 TRX 1 <tibble [158 × 11]>
## 10 KNC 1 <tibble [158 × 11]>
## # ℹ 425 more rows
## # A tibble: 435 x 3
## # Groups: symbol, split [435]
## symbol split train_data
## <chr> <dbl> <list>
## 1 BTC 1 <tibble [158 x 11]>
## 2 ETH 1 <tibble [158 x 11]>
## 3 EOS 1 <tibble [158 x 11]>
## 4 LTC 1 <tibble [158 x 11]>
## 5 BSV 1 <tibble [158 x 11]>
## 6 ADA 1 <tibble [158 x 11]>
## 7 ZEC 1 <tibble [158 x 11]>
## 8 HT 1 <tibble [116 x 11]>
## 9 TRX 1 <tibble [158 x 11]>
## 10 KNC 1 <tibble [158 x 11]>
## # ... with 425 more rows
现在让我们重复相同的过程,但在测试数据上创建 cryptodata_test 对象:
cryptodata_test <- select(rename(nest(filter(cryptodata,
training=='test')),
test_data = 'data'),
-training)
## Adding missing grouping variables: `training`
# 现在移除 training 列
cryptodata_test <- select(ungroup(cryptodata_test,
training),
-training)
# 显示嵌套数据
cryptodata_test
## # A tibble: 352 × 3
## # Groups: symbol, split [352]
## symbol split test_data
## <chr> <dbl> <list>
## 1 HT 1 <tibble [47 × 11]>
## 2 GMTT 1 <tibble [51 × 11]>
## 3 ETHW 1 <tibble [55 × 11]>
## 4 LAZIO 1 <tibble [55 × 11]>
## 5 ID 1 <tibble [56 × 11]>
## 6 BNT 1 <tibble [58 × 11]>
## 7 LQTY 1 <tibble [58 × 11]>
## 8 BTC 1 <tibble [58 × 11]>
## 9 ETH 1 <tibble [58 × 11]>
## 10 EOS 1 <tibble [58 × 11]>
## # ℹ 342 more rows
## # A tibble: 352 x 3
## # Groups: symbol, split [352]
## symbol split test_data
## <chr> <dbl> <list>
## 1 HT 1 <tibble [47 x 11]>
## 2 GMTT 1 <tibble [51 x 11]>
## 3 ETHW 1 <tibble [55 x 11]>
## 4 LAZIO 1 <tibble [55 x 11]>
## 5 ID 1 <tibble [56 x 11]>
## 6 BNT 1 <tibble [58 x 11]>
## 7 LQTY 1 <tibble [58 x 11]>
## 8 XDC 1 <tibble [58 x 11]>
## 9 XCH 1 <tibble [58 x 11]>
## 10 VGX 1 <tibble [58 x 11]>
## # ... with 342 more rows
以及保留数据以创建 cryptodata_holdout 对象:
cryptodata_holdout <- rename(nest(filter(cryptodata,
training=='holdout')),
holdout_data = 'data')
# 从保留中移除 split 和 training 列
cryptodata_holdout <- select(ungroup(cryptodata_holdout, split, training),
-split, -training)
# 显示嵌套数据
cryptodata_holdout
## # A tibble: 88 × 2
## # Groups: symbol [88]
## symbol holdout_data
## <chr> <list>
## 1 CTC <tibble [51 × 11]>
## 2 XMR <tibble [57 × 11]>
## 3 BTC <tibble [62 × 11]>
## 4 ETH <tibble [62 × 11]>
## 5 LTC <tibble [62 × 11]>
## 6 BSV <tibble [62 × 11]>
## 7 ADA <tibble [62 × 11]>
## 8 ZEC <tibble [62 × 11]>
## 9 TRX <tibble [62 × 11]>
## 10 KNC <tibble [62 × 11]>
## # ℹ 78 more rows
## # A tibble: 88 x 2
## # Groups: symbol [88]
## symbol holdout_data
## <chr> <list>
## 1 CTC <tibble [51 x 11]>
## 2 XMR <tibble [57 x 11]>
## 3 BTC <tibble [62 x 11]>
## 4 ETH <tibble [62 x 11]>
## 5 LTC <tibble [62 x 11]>
## 6 ADA <tibble [62 x 11]>
## 7 BSV <tibble [62 x 11]>
## 8 ZEC <tibble [62 x 11]>
## 9 TRX <tibble [62 x 11]>
## 10 KNC <tibble [62 x 11]>
## # ... with 78 more rows
现在我们可以将结果分组为每个子集 cryptodata_train、cryptodata_test 和 cryptodata_holdout,并且我们可以将结果加入到单个新数据框中,我们将其称为 cryptodata_nested,以便在一个新数据框中拥有所有三个新列 train_data、test_data 和 holdout_data:
# 加入训练和测试
cryptodata_nested <- left_join(cryptodata_train, cryptodata_test, by = c("symbol", "split"))
# 显示新数据
cryptodata_nested
## # A tibble: 435 × 4
## # Groups: symbol, split [435]
## symbol split train_data test_data
## <chr> <dbl> <list> <list>
## 1 BTC 1 <tibble [158 × 11]> <tibble [58 × 11]>
## 2 ETH 1 <tibble [158 × 11]> <tibble [58 × 11]>
## 3 EOS 1 <tibble [158 × 11]> <tibble [58 × 11]>
## 4 LTC 1 <tibble [158 × 11]> <tibble [58 × 11]>
## 5 BSV 1 <tibble [158 × 11]> <tibble [58 × 11]>
## 6 ADA 1 <tibble [158 × 11]> <tibble [58 × 11]>
## 7 ZEC 1 <tibble [158 × 11]> <tibble [58 × 11]>
## 8 HT 1 <tibble [116 × 11]> <tibble [47 × 11]>
## 9 TRX 1 <tibble [158 × 11]> <tibble [58 × 11]>
## 10 KNC 1 <tibble [158 × 11]> <tibble [58 × 11]>
## # ℹ 425 more rows
## # A tibble: 435 x 4
## # Groups: symbol, split [435]
## symbol split train_data test_data
## <chr> <dbl> <list> <list>
## 1 BTC 1 <tibble [158 x 11]> <tibble [58 x 11]>
## 2 ETH 1 <tibble [158 x 11]> <tibble [58 x 11]>
## 3 EOS 1 <tibble [158 x 11]> <tibble [58 x 11]>
## 4 LTC 1 <tibble [158 x 11]> <tibble [58 x 11]>
## 5 BSV 1 <tibble [158 x 11]> <tibble [58 x 11]>
## 6 ADA 1 <tibble [158 x 11]> <tibble [58 x 11]>
## 7 ZEC 1 <tibble [158 x 11]> <tibble [58 x 11]>
## 8 HT 1 <tibble [116 x 11]> <tibble [47 x 11]>
## 9 TRX 1 <tibble [158 x 11]> <tibble [58 x 11]>
## 10 KNC 1 <tibble [158 x 11]> <tibble [58 x 11]>
## # ... with 425 more rows
上面使用的 by 参数定义了用于连接数据的键,在这种情况下是加密货币符号以及特定的分割。
接下来,让我们将刚刚创建的新数据框 cryptodata_nested 与保留数据连接起来,并添加 holdout_data 列。在这种情况下,我们希望保持所有 5 个分割的加密货币的保留数据一致,而不是将数据与特定的分割匹配;在数据的分割 1 到 5 上训练的模型将各自有一个不同的测试数据集,但所有 5 个模型之后都将针对相同的保留数据进行测试。因此,这次
在下面执行的 join() 中,我们仅为 by 参数提供加密货币符号:
现在我们有了一个完成的数据集,它将允许我们遍历每个选项并创建许多单独的模型,正如本节讨论的那样:
## # A tibble: 435 × 5
## # Groups: symbol, split [435]
## symbol split train_data test_data holdout_data
## <chr> <dbl> <list> <list> <list>
## 1 BTC 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 2 ETH 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 3 EOS 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [61 × 11]>
## 4 LTC 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 5 BSV 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 6 ADA 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 7 ZEC 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 8 HT 1 <tibble [116 × 11]> <tibble [47 × 11]> <tibble [51 × 11]>
## 9 TRX 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 10 KNC 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## # ℹ 425 more rows
## # A tibble: 435 x 5
## # Groups: symbol, split [435]
## symbol split train_data test_data holdout_data
## <chr> <dbl> <list> <list> <list>
## 1 BTC 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 2 ETH 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 3 EOS 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [61 x 11]>
## 4 LTC 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 5 BSV 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 6 ADA 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 7 ZEC 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 8 HT 1 <tibble [116 x 11]> <tibble [47 x 11]> <tibble [51 x 11]>
## 9 TRX 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 10 KNC 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## # ... with 425 more rows
继续下一节 ➡️ 使用本节讨论的方法构建预测模型。
现在数据已经清理干净,我们已经制定了一个计划来理解模型的有效性,我们终于有了开始制作预测模型所需的一切。
我们可以从制作一个简单的线性回归模型开始:
lm(formula = target_price_24h ~ ., data = cryptodata)
##
## 调用:
## lm(formula = target_price_24h ~ ., data = cryptodata)
##
## 系数:
## (Intercept) symbolADA symbolAGIX symbolANT
## -1399.669703572 0.000727820 -0.000843216 0.043642200
## symbolAPFC symbolAPT symbolAR symbolARB
## -0.012471406 0.049727644 -0.294902923 -0.000336209
## symbolATOM symbolAVA symbolBAND symbolBAT
## 0.062588077 0.005111093 -0.316767155 -0.000247131
## symbolBCUG symbolBICO symbolBNT symbolBSV
## -0.013246274 -0.324688137 0.004045397 0.226332950
## symbolBSW symbolBTC symbolBTG symbolCELO
## -0.326170630 268.411401316 0.151465155 -0.323020575
## symbolCFX symbolCHR symbolCHZ symbolCOMP
## -0.322415484 -0.322637079 -0.001474554 0.250594689
## symbolCTC symbolCTXC symbolDAR symbolDASH
## 0.072392083 -0.321899576 -0.325939785 0.149972713
## symbolDOT symbolDYDX symbolELF symbolENJ
## 0.048712357 0.023372407 0.001437382 0.000382460
## symbolEOS symbolETH symbolETHW symbolFLUX
## 0.005354454 17.396663770 -0.041121556 -0.325259311
## symbolGAL symbolGLEEC symbolGMT symbolGMTT
## -0.313410832 -0.244946366 -0.012872750 0.124790127
## symbolGODS symbolHT symbolICP symbolID
## 0.027356616 0.159021847 -0.293334722 0.087649445
## symbolILV symbolIMX symbolIOTA symbolKNC
## 0.084052098 -0.321475802 -0.000906154 0.004603228
## symbolKSM symbolLAZIO symbolLDO symbolLOC
## -0.176790622 0.017893486 -0.313179647 -0.314199609
## symbolLQTY symbolLTC symbolMAGIC symbolMANA
## -0.030220719 0.476772549 -0.324276880 0.
000844253
## symbolMDT symbolNEXO symbolNMR symbolOMG
## -0.326042950 -0.000117699 -0.220944666 -0.320880132
## symbolONT symbolOP symbolOXT symbolPAR
## -0.296068144 -0.300901240 -0.001330831 -0.352605389
## symbolPERP symbolPORTO symbolQNT symbolRAD
## 0.001339182 -0.300053698 0.860296987 -0.316497969
## symbolRARE symbolREN symbolRIF symbolSANTOS
## -0.322000268 -0.002013056 -0.001768840 -0.293441752
## symbolSKL symbolSTETH symbolSTG symbolSTORJ
## -0.002027771 17.208506736 -0.308874167 0.037665358
## symbolSUSHI symbolTHETA symbolTOMO symbolTRU
## -0.316880083 -0.002480189 0.012059432 -0.322677782
## symbolTRX symbolVGX symbolXCH symbolXDC
## -0.001133702 0.022938520 0.323430424 -0.012710507
## symbolXLM symbolXMR symbolZEC symbolZRX
## -0.324625434 1.442381187 0.191251222 -0.000348183
## date_time_utc date price_usd lagged_price_1h
## 0.000002723 -0.163566221 0.852828758 0.051042097
## lagged_price_2h lagged_price_3h lagged_price_6h lagged_price_12h
## 0.030771219 0.062446343 0.049346762 -0.091555625
## lagged_price_24h lagged_price_3d trainingtest trainingtrain
## 0.057169582 -0.024022330 1.190654197 0.851961086
## split
## -1.636303243
我们将模型的公式定义为
target_price_24h ~ .
,这意味着我们想要对
target_price_24h
字段进行预测,并使用(~)数据中的每个其他列(.)。换句话说,我们指定了一个模型,该模型将
target_price_24h
字段作为因变量,所有其他列(.)作为自变量。意味着,我们正在寻找预测
target_price_24h
,这是唯一指向未来的列,并使用在收集其余数据时可用的所有信息来推断统计关系,这些关系可以帮助我们在新数据上预测
target_price_24h
字段的未来值,当我们想要对新数据进行新预测时,这些值仍然未知。
在上面的示例中,我们使用了包含所有非嵌套数据的
cryptodata
对象,这是对我们实际将使用的过程的一个大大简化。
从这一点开始,我们将处理新的数据集
cryptodata_nested
,如果你错过了它的创建,请回顾前一节。这里再次预览数据:
## # A tibble: 435 × 5
## # Groups: symbol, split [435]
## symbol split train_data test_data holdout_data
## <chr> <dbl> <list> <list> <list>
## 1 BTC 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 2 ETH 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 3 EOS 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [61 × 11]>
## 4 LTC 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 5 BSV 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 6 ADA 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 7 ZEC 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 8 HT 1 <tibble [116 × 11]> <tibble [47 × 11]> <tibble [51 × 11]>
## 9 TRX 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## 10 KNC 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble [62 × 11]>
## # ℹ 425 more rows
## # A tibble: 435 x 5
## # Groups: symbol, split [435]
## symbol split train_data test_data holdout_data
## <chr> <dbl> <list> <list> <list>
## 1 BTC 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 2 ETH 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 3 EOS 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [61 x 11]>
## 4 LTC 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 5 BSV 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 6 ADA 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 7 ZEC 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 8 HT 1 <tibble [116 x 11]> <tibble [47 x 11]> <tibble [51 x 11]>
## 9 TRX 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## 10 KNC 1 <tibble [158 x 11]> <tibble [58 x 11]> <tibble [62 x 11]>
## # ... with 425 more rows
因为我们现在正在处理一个嵌套的数据框,所以对单个嵌套数据集执行操作并不那么直接。我们可以使用索引提取数据中的单个元素,例如我们可以通过运行这段代码返回
train_data
列的第一个元素:
## # A tibble: 158 × 11
## date_time_utc date price_usd target_price_24h lagged_price_1h
## <chr> <chr> <dbl> <dbl> <dbl>
## 1 2023-06-30 08:00:00 2023-06-30 30459. 30477. 30422.
## 2 2023-06-30 09:00:00 2023-06-30 30442. 30473. 30459.
## 3 2023-06-30 10:00:00 2023-06-30 30387. 30449. 30442.
## 4 2023-06-30 11:00:00 2023-06-30 30667. 30427. 30387.
## 5 2023-06-30 12:00:00 2023-06-30 30747. 30395 30667.
## 6 2023-06-30 13:00:00 2023-06-30 30884. 30400. 30747.
## 7 2023-06-30 14:00:00 2023-06-30 30735 30424. 30884.
## 8 2023-06-30 15:00:00 2023-06-30 30674. 30456. 30735
## 9 2023-06-30 16:00:00 2023-06-30 30769. 30461. 30674.
## 10 2023-06-30 17:00:00 2023-06-30 30852. 30442. 30769.
## # ℹ 148 more rows
## # ℹ 6 more variables: lagged_price_2h <dbl>, lagged_price_3h <dbl>,
## # lagged_price_6h <dbl>, lagged_price_12h <dbl>, lagged_price_24h <dbl>,
## # lagged_price_3d <dbl>
## # A tibble: 158 x 11
## date_time_utc date price_usd target_price_24h lagged_price_1h
## <dttm> <date> <dbl> <dbl> <dbl>
## 1 2023-06-30 00:00:00 2023-06-30 30459. 30477. 30422.
## 2 2023-06-30 01:00:00 2023-06-30 30442. 30473. 30459.
## 3 2023-06-30 02:00:00 2023-06-30 30387. 30449. 30442.
## 4 2023-06-30 03:00:00 2023-06-30 30667. 30427. 30387.
## 5 2023-06-30 04:00:00 2023-06-30 30747. 30395 30667.
## 6 2023-06-30 05:00:00 2023-06-30 30884. 30400. 30747.
## 7 2023-06-30 06:00:00 2023-06-30 30735 30424. 30884.
## 8 2023-06-30 07:00:00 2023-06-30 30674. 30456. 30735
## 9 2023-06-30 08:00:00 2023-06-30 30769. 30461. 30674.
## 10 2023-06-30 09:00:00 2023-06-30 30852. 30442. 30769.
## # ... with 148 more rows, and 6 more variables: lagged_price_2h <dbl>,
## # lagged_price_3h <dbl>, lagged_price_6h <dbl>, lagged_price_12h <dbl>,
## # lagged_price_24h <dbl>, lagged_price_3d <dbl>
解决 2021 年 3 月 3 日出现的奇怪问题,移除 STORJ:
正如我们已经看到的,数据框作为一种数据结构非常灵活。我们可以在数据中创建一个新列来存储与数据的每一行相关联的模型本身。有几种方法可以做到这一点(这个教程本身是
为了使用三种根本不同的方法来执行相同的命令而编写的),但在这个教程中,我们将采取函数式编程的方法。这意味着我们将专注于我们想要采取的操作,这与使用
for 循环相反,后者更强调对象,使用与上面显示 train_data
列的第一个元素的示例相似的结构。
在使用函数式编程方法时,我们首先需要为我们想要执行的操作创建函数。让我们将之前用作示例的
lm()
函数包装起来,并创建一个新的自定义函数
linear_model
,它接受一个数据框作为输入(我们将为嵌套数据集的每一行提供的
train_data
),并生成一个线性回归模型:
现在我们可以使用 purrr
包中的 map()
函数与
dplyr
中的 mutate()
函数结合使用,在数据中创建一个新列,其中包含每一行
train_data
的单独线性回归模型:
## # A tibble: 430 × 6
## # Groups: symbol, split [430]
## symbol split train_data test_data holdout_data lm_model
## <chr> <dbl> <list> <list> <list> <list>
## 1 BTC 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble> <lm>
## 2 ETH 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble> <lm>
## 3 EOS 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble> <lm>
## 4 LTC 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble> <lm>
## 5 BSV 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble> <lm>
## 6 ADA 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble> <lm>
## 7 ZEC 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble> <lm>
## 8 HT 1 <tibble [116 × 11]> <tibble [47 × 11]> <tibble> <lm>
## 9 TRX 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble> <lm>
## 10 KNC 1 <tibble [158 × 11]> <tibble [58 × 11]> <tibble> <lm>
## # ℹ 420 more rows
## # A tibble: 430 x 6
## # Groups: symbol, split [430]
## symbol split train_data test_data holdout_data lm_model
## <chr> <dbl> <list> <list> <list> <list>
## 1 BTC 1 <tibble [158 x 11]> <tibble [58 x 11~ <tibble [62 x 11~ <lm>
## 2 ETH 1 <tibble [158 x 11]> <tibble [58 x 11~ <tibble [62 x 11~ <lm>
## 3 EOS 1 <tibble [158 x 11]> <tibble [58 x 11~ <tibble [61 x 11~ <lm>
## 4 LTC 1 <tibble [158 x 11]> <tibble [58 x 11~ <tibble [62 x 11~ <lm>
## 5 BSV 1 <tibble [158 x 11]> <tibble [58 x 11~ <tibble [62 x 11~ <lm>
## 6 ADA 1 <tibble [158 x 11]> <tibble [58 x 11~ <tibble [62 x 11~ <lm>
## 7 ZEC 1 <tibble [158 x 11]> <tibble [58 x 11~ <tibble [62 x 11~ <lm>
## 8 HT 1 <tibble [116 x 11]> <tibble [47 x 11~ <tibble [51 x 11~ <lm>
## 9 TRX 1 <tibble [158 x 11]> <tibble [58 x 11~ <tibble [62 x 11~ <lm>
## 10 KNC 1 <tibble [158 x 11]> <tibble [58 x 11~ <tibble [62 x 11~ <lm>
## # ... with 420 more rows
太棒了!现在我们可以使用我们在高级版本中学到的相同工具来制作更多种类的预测模型进行测试。
回顾高级版本的教程以了解关于 caret
包的解释,或查阅此文档:https://topepo.github.io/caret/index.html
R 是一个单线程应用程序,这意味着在执行操作时它一次只使用一个
CPU。下面的步骤是可选的,使用 parallel
和
doParallel
包允许 R 在创建预测模型时使用多个
CPU,这将大大加快过程:
在我们可以重复之前用于创建线性回归模型列的过程,但这次使用
caret
包创建完全相同的模型。
linear_model_caret <- function(df){
train(target_price_24h ~ . -date_time_utc -date, data = df,
method = 'lm',
trControl = trainControl(method="none"))
}
我们将方法指定为 lm
用于线性回归。参见高级版本以回顾如何使用不同的方法制作不同的模型:https://cryptocurrencyresearch.org/high-level/#/method-options。trControl
参数告诉 caret
包避免对数据进行额外的重采样。作为默认行为,caret
会对数据进行重采样并进行超参数调整以选择参数的值以获得最佳结果,但我们将在本教程中避免这个讨论。有关更多详细信息,请参阅官方
caret
文档。
以下是我们可以使用 caret
包和高级版本教程中描述的步骤制作的模型的完整列表:
现在我们可以使用我们创建的新函数 linear_model_caret
与
map()
和 mutate()
结合使用,在
cryptodata_nested
数据集中创建一个名为
lm_model
的新列,其中包含每个分割的数据(按加密货币符号和分割)的训练好的线性回归模型:
我们可以看到名为 lm_model
的新列,其中包含嵌套数据框的分组变量:
## Adding missing grouping variables: `symbol`, `split`
## # A tibble: 430 × 3
## # Groups: symbol, split [430]
## symbol split lm_model
## <chr> <dbl> <list>
## 1 BTC 1 <train>
## 2 ETH 1 <train>
## 3 EOS 1 <train>
## 4 LTC 1 <train>
## 5 BSV 1 <train>
## 6 ADA 1 <train>
## 7 ZEC 1 <train>
## 8 HT 1 <train>
## 9 TRX 1 <train>
## 10 KNC 1 <train>
## # ℹ 420 more rows
## # A tibble: 430 x 3
## # Groups: symbol, split [430]
## symbol split lm_model
## <chr> <dbl> <list>
## 1 BTC 1 <train>
## 2 ETH 1 <train>
## 3 EOS 1 <train>
## 4 LTC 1 <train>
## 5 BSV 1 <train>
## 6 ADA 1 <train>
## 7 ZEC 1 <train>
## 8 HT 1 <train>
## 9 TRX 1 <train>
## 10 KNC 1 <train>
## # ... with 420 more rows
我们可以查看第一个训练模型的摘要内容:
## Linear Regression
##
## 158 samples
## 10 predictor
##
## No pre-processing
## Resampling: None
我们可以调整我们之前为线性回归模型使用 caret
构建的函数,并添加一个参数,允许我们指定我们想要使用的方法(即使用哪个预测模型):
model_caret <- function(df, method_choice){
train(target_price_24h ~ . -date_time_utc -date, data = df,
method = method_choice,
trControl = trainControl(method="none"))
}
现在我们可以做之前为线性回归模型所做的相同事情,但使用名为
model_caret
的新函数,使用 map2()
函数同时指定模型为 xgbLinear
来创建一个 XGBoost 模型:
cryptodata_nested <- mutate(cryptodata_nested,
xgb_model = map2(train_data, "xgbLinear", model_caret))
我们不会深入讨论每个单独的模型的细节,因为正确的模型使用可能取决于许多因素,这是本教程范围之外的讨论。我们选择使用 XGBoost 模型作为示例,因为它最近作为各种问题的非常有效的框架而获得了很多人
气,并且是任何数据科学家必备的模型。
XGBoost 模型有几种可能的配置,您可以在这里找到官方文档:https://xgboost.readthedocs.io/en/latest/parameter.html
我们可以继续添加模型。正如我们所看到的,caret
允许使用超过 200 种预测模型。让我们再制作一组模型,这次将方法设置为
dnn
来创建深度神经网络:
同样,我们不会深入讨论个别模型的细节,但快速 Google 搜索将返回关于该主题的大量信息。
接下来,让我们使用方法 ctree
创建随机森林模型
对于最后一组模型,让我们使用方法 pcr
制作主成分回归模型
Caret
还提供了一些额外的选项来帮助预处理数据。当我们展示如何制作支持向量机模型时,我们在高级版本的教程中概述了一个示例,该模型需要对数据进行居中和缩放以避免遇到问题(我们不会在这里进一步讨论)。
太棒了!我们已经训练了预测模型,我们希望开始更好地了解这些模型在它们从未见过的数据上的准确性。为了进行这些比较,我们将希望对测试和保留数据集进行预测,并将这些预测与实际发生的情况进行比较。
为了进行预测,我们可以使用 predict()
函数,这里是对嵌套数据框的第一个元素的示例:
predict(object = cryptodata_nested$lm_model[[1]],
newdata = cryptodata_nested$test_data[[1]],
na.action = na.pass)
## 1 2 3 4 5 6 7 8
## 30557.13 30478.61 30423.94 30419.53 30465.80 30444.40 30409.10 30458.36
## 9 10 11 12 13 14 15 16
## 30373.08 30379.42 30375.10 30390.94 30331.10 30333.53 30344.79 30240.48
## 17 18 19 20 21 22 23 24
## 30269.10 30223.12 30323.38 30364.17 30391.42 30433.82 30430.94 30464.13
## 25 26 27 28 29 30 31 32
## 30470.69 30471.30 30550.17 30523.83 30541.54 30545.87 30586.81 30541.61
## 33 34 35 36 37 38 39 40
## 30549.07 30539.98 30533.24 30556.14 30539.80 30539.85 30576.98 30573.24
## 41 42 43 44 45 46 47 48
## 30552.60 30586.36 30602.81 30583.04 30544.57 30543.25 30556.46 30538.85
## 49 50 51 52 53 54 55 56
## 30543.22 30557.75 30566.09 30556.66 30539.34 30552.22 30538.06 30554.86
## 57 58
## 30526.89 30530.63
现在我们可以创建一个名为 make_predictions
的新自定义函数,该函数以一种方式包装此功能,我们可以使用
map()
与之结合来遍历嵌套数据框的所有选项:
make_predictions <- function(model, test){
predict(object = model, newdata = test, na.action = na.pass)
}
现在我们可以创建新列 lm_test_predictions
和
lm_holdout_predictions
,其中包含预测:
cryptodata_nested <- mutate(cryptodata_nested,
lm_test_predictions = map2(lm_model,
test_data,
make_predictions),
lm_holdout_predictions = map2(lm_model,
holdout_data,
make_predictions))
预测是使用仅见过训练数据的模型进行的,我们可以开始评估模型在测试和保留集上的好坏。让我们查看前一步的结果:
select(cryptodata_nested, lm_test_predictions, lm_holdout_predictions)
## # A tibble: 430 x 4
## # Groups: symbol, split [430]
## symbol split lm_test_predictions lm_holdout_predictions
## <chr> <dbl> <list> <list>
## 1 BTC 1 <dbl [58]> <dbl [62]>
## 2 ETH 1 <dbl [58]> <dbl [62]>
## 3 EOS 1 <dbl [58]> <dbl [61]>
## 4 LTC 1 <dbl [58]> <dbl [62]>
## 5 BSV 1 <dbl [58]> <dbl [62]>
## 6 ADA 1 <dbl [58]> <dbl [62]>
## 7 ZEC 1 <dbl [58]> <dbl [62]>
## 8 HT 1 <dbl [47]> <dbl [51]>
## 9 TRX 1 <dbl [58]> <dbl [62]>
## 10 KNC 1 <dbl [58]> <dbl [62]>
## # ... with 420 more rows
现在我们可以为其余模型做同样的事情:
cryptodata_nested <- mutate(cryptodata_nested,
# XGBoost:
xgb_test_predictions = map2(xgb_model,
test_data,
make_predictions),
# holdout
xgb_holdout_predictions = map2(xgb_model,
holdout_data,
make_predictions),
# 神经网络:
nnet_test_predictions = map2(nnet_model,
test_data,
make_predictions),
# holdout
nnet_holdout_predictions = map2(nnet_model,
holdout_data,
make_predictions),
# 随机森林:
rf_test_predictions = map2(rf_model,
test_data,
make_predictions),
# holdout
rf_holdout_predictions = map2(rf_model,
holdout_data,
make_predictions),
# PCR:
pcr_test_predictions = map2(pcr_model,
test_data,
make_predictions),
# holdout
pcr_holdout_predictions = map2(pcr_model,
holdout_data,
make_predictions))
我们已经完成了使用 caret
包的工作,可以停止并行处理集群:
stopCluster(cl)
在这个示例中,我们使用了 caret
包,因为它提供了一种直接的选项来创建多种模型,但在 R 和 Python
中都有几个很好的类似替代方案来制作多种模型。一些值得一提的是 `tidym
odels、
mlr和
scikit-learn`。
因为这个教程已经非常密集,我们将只关注我们上面创建的模型。在创建时间序列数据的预测模型时,有一些其他出色的选项可以考虑,这些选项以类似但更复杂的方式考虑了信息的收集时间,类似于我们在创建滞后变量时所做的。
继续下一节 ➡️ 评估模型的准确性,如上一节所述。
现在我们可以看到我们辛勤工作的结果了!在我们可以综合可视化结果之前,我们需要进行一些额外的数据准备步骤;如果您只是想查看显示结果的图表,请参阅下面的“可视化结果”部分。
因为我们知道我们在上一步中使用的测试数据的目标变量的真实情况,所以我们可以很好地了解模型在之前从未见过的数据集上的表现如何。我们这样做是为了避免过拟合,即模型在我们提供的训练数据上表现非常好,但在我们想要进行预测的新数据上表现不佳。如果测试集上的表现良好,那是一个好兆头。如果数据被分成几个子集,并且每个子集的训练和测试数据集的结果都是一致的,那么模型可能会按预期表现,这是一个更好的迹象。
数据的第一行是比特币(BTC)加密货币的第 1
个拆分。对于这行数据(以及所有其他行),我们使用线性回归模型对
test_data
进行了预测,并将结果保存在
lm_test_predictions
列中。模型是在 train_data
上训练的,尚未看到 test_data
的结果,那么该模型对这些数据的预测准确度如何呢?
我们可以将每个单独的预测与实际发生的情况进行比较,并为每个预测计算两者之间的误差。然后,我们可以取所有预测误差相对于实际观测值的所有值,并将性能总结为这些值的平均绝对误差(MAE),这为我们提供了一个单一的值来作为模型准确性的指标。MAE 分数越高,误差越大,这意味着模型的表现越差。
评估模型性能的常用指标是均方根误差(RMSE),它类似于 MAE,但对值进行平方然后再取平方根。这样做的一个有趣的含义是,RMSE 总是大于或等于 MAE,其中单个观测值的较大误差会被 RMSE 更多地惩罚。RMSE 值越高,模型的性能越差,范围可以从 0 到无穷大,这意味着您可能有的误差量没有定义的限制(与下一个指标不同)。
R2,也称为决定系数,是一个描述预测所做的预测与实际结果之间的相关强度的度量。值为 1.0 意味着所做的预测与实际结果完全相同。完美的分数通常是令人担忧的,因为即使是一个很好的模型也不应该完全准确,并且通常表示犯了一个错误,将结果泄露给了模型,并且在现实世界中的实际应用中表现不会那么好,但在 R2 的情况下,分数越高(从 0 到 1)越好。
我们可以使用来自 caret
包的 postResample()
函数返回比特币(BTC)加
密货币和第 1 个拆分的 RMSE 和 R2 指标:
postResample(pred = cryptodata_nested$lm_test_predictions[[1]],
obs = cryptodata_nested$test_data[[1]]$target_price_24h)
## Warning: Unknown or uninitialised column: `lm_test_predictions`.
## RMSE Rsquared MAE
## NA NA NA
我们可以提取第一个元素以返回 RMSE 指标,第二个元素用于 R
Squared(R^2)指标。我们使用 [[1]]
来提取
lm_test_predictions
和 test_data
的第一个元素,并将预测与 target_price24h
列的实际值进行比较。
这个模型使用了加密货币的最早的数据子集。当应用于数据的最新子集(保留集)时,用于预测这个较旧子集的数据的相同模型表现如何?
我们可以通过比较 lm_holdout_predictions
与
target_price_24h
列的实际发生情况来获得相同的结果摘要:
postResample(pred = cryptodata_nested$lm_holdout_predictions[[1]],
obs = cryptodata_nested$holdout_data[[1]]$target_price_24h)
## Warning: Unknown or uninitialised column: `lm_holdout_predictions`.
## RMSE Rsquared MAE
## NA NA NA
上述结果可能会显示 RMSE 指标的值为 NA。稍后我们将解释并解决这个问题。
为什么不只选择一个指标并坚持使用它呢?我们当然可以,但这两个指标相辅相成。例如,如果我们有一个总是预测时间段内 0% 价格变化的模型,该模型可能具有较低的误差,但实际上它在方向或幅度上并不非常具有信息性,并且预测和实际结果之间不会非常相关,这将导致 R2 值较低。我们使用这两个是因为它有助于更完整地描述这种情况,并且根据任务,您可能希望使用不同的指标集来评估性能。还值得一提的是,如果您预测的目标变量是 0 或 1,这将是一个分类问题,其中不同的指标更适合使用。
这些指标应该单独带着一粒盐来看待,但比较同一加密货币的许多不同模型的结果可以帮助我们确定哪些模型最适合该问题,然后比较这些结果可以帮助我们了解哪些加密货币我们可以预测最准确。
然而,在我们可以进行这些比较之前,我们需要“标准化”值以在所有数据集之间创建公平的比较。
由于加密货币的价格可能会有很大的差异,有些交易价格为数万美元,而有些交易价格不到一分钱,因此我们需要确保标准化 RMSE 列以提供公平的比较。
因此,在使用 postResample()
函数之前,让我们将预测和目标转换为 24
小时内的价格变化百分比,而不是价格变化($)。
这一步特别繁琐,但很重要。与本教程的其他部分一样,即使您觉得代码很压倒性,也要尝试理解我们在做什么以及为什么。在这个“调整价格”部分中,我们所做的只是将所有价格调整为表示为观察之间的百分比变化,这将允许我们在所有加密货币之间公平
地比较指标,这是使用价格本身无法做到的。如果您想跳过繁琐的步骤并查看模型的性能可视化,请点击此处跳到前面。
### 添加最后价格
为了将第一个预测转换为百分比,我们需要知道之前的价格,这将是训练数据中的最后一次观察。因此,让我们创建一个函数来添加
last_price_train
列并将其附加到所做的预测中,以便我们可以计算第一个元素相对于训练数据中最后一次观察的百分比变化,然后稍后删除与预测无关的值:
last_train_price <- function(train_data, predictions){
c(tail(train_data$price_usd,1), predictions)
}
我们将首先对线性回归模型执行所有步骤,以使代码更易于消化,然后我们将对其余模型执行相同的步骤。
使用上面创建的新函数覆盖前 4 个拆分的测试数据的旧预测:
cryptodata_nested <- mutate(cryptodata_nested,
lm_test_predictions = ifelse(split < 5,
map2(train_data, lm_test_predictions, last_train_price),NA))
mutate()
函数用于创建新列
lm_test_predictions
,仅为前 4
个拆分分配值,其中测试数据实际存在(第 5 个是保留集),使用
ifelse()
函数。
现在对保留集执行相同的操作。对于保留集,我们需要获取第 5 个拆分的训练数据的最后一个价格点:
cryptodata_nested_holdout <- mutate(filter(cryptodata_nested, split == 5),
lm_holdout_predictions = map2(train_data, lm_holdout_predictions, last_train_price))
现在根据加密货币符号 alone 将保留集数据与所有行连接起来:
cryptodata_nested <- left_join(cryptodata_nested,
select(cryptodata_nested_holdout, symbol, lm_holdout_predictions),
by='symbol')
# 移除不需要的列
cryptodata_nested <- select(cryptodata_nested, -lm_holdout_predictions.x, -split.y)
# 重命名保留的列
cryptodata_nested <- rename(cryptodata_nested,
lm_holdout_predictions = 'lm_holdout_predictions.y',
split = 'split.x')
# 重置正确的分组结构
cryptodata_nested <- group_by(cryptodata_nested, symbol, split)
现在我们有了我们需要的一切来准确计算观测值之间的百分比变化,包括第一个。让我们创建一个新函数来计算百分比变化:
standardize_perc_change <- function(predictions){
results <- (diff(c(lag(predictions, 1), predictions)) / lag(predictions, 1))*100
# 排除第一个元素,下一个元素将是第一个预测的百分比变化
tail(head(results, length(predictions)), length(predictions)-1)
}
用新的百分比调整后的预测覆盖旧预测:
cryptodata_nested <- mutate(cryptodata_nested,
lm_test_predictions = ifelse(split < 5,
map(lm_test_predictions, standardize_perc_change),
NA), # 保留集 - 所有拆分
lm_holdout_predictions = map(lm_holdout_predictions, standardize_perc_change))
现在对实际价格执行相同的操作。让我们创建一个新列
actuals
,其中包含真实的价格值(而不是预测的值):
actuals_create <- function(train_data, test_data){
c(tail(train_data$price_usd,1), as.numeric(unlist(select(test_data, price_usd))))
}
使用新函数创建新列 actuals
:
cryptodata_nested <- mutate(cryptodata_nested,
actuals_test = ifelse(split < 5,
map2(train_data, test_data, actuals_create),
NA))
同样,对于保留集,我们需要第 5 个拆分的训练数据的价格来执行第一个计算:
cryptodata_nested_holdout <- mutate(filter(cryptodata_nested, split == 5),
actuals_holdout = map2(train_data, holdout_data, actuals_create))
根据加密货币符号 alone 将保留集数据与所有行连接起来:
cryptodata_nested <- left_join(cryptodata_nested,
select(cryptodata_nested_holdout, symbol, actuals_holdout),
by='symbol')
# 移除不需要的列
cryptodata_nested <- select(cryptodata_nested, -split.y)
# 重命名保留的列
cryptodata_nested <- rename(cryptodata_nested, split = 'split.x')
# 重置正确的分组结构
cryptodata_nested <- group_by(cryptodata_nested, symbol, split)
现在我们可以将新的实际值转换为表示 price_usd
相对于前一个值的百分比变化,使用稍早的函数进行调整:
actuals_perc_change <- function(predictions){
results <- (diff(c(lag(predictions, 1), predictions)) / lag(predictions, 1))*100
# 排除第一个元素,下一个元素将是第一个预测的百分比变化
tail(head(results, length(predictions)), length(predictions)-1)
}
cryptodata_nested <- mutate(cryptodata_nested,
actuals_test = ifelse(split < 5,
map(actuals_test, actuals_perc_change),
NA),
actuals_holdout = map(actuals_holdout, actuals_perc_change))
现在我们将价格标准化为相对于前一时期的百分比变化,而不是美元价格,我们实际上可以比较所有加密货币的汇总统计,并进行公平比较。
让我们获得与本节开头相同的统计数据,但这次是在标准化值上。这次计算
RMSE 误差指标时,让我们使用 hydroGOF
包中的
rmse()
函数,因为它允许我们设置 na.rm = T
参数,否则一个 NA 值将导致整体 RMSE 返回 NA:
hydroGOF::rmse(cryptodata_nested$lm_test_predictions[[1]],
cryptodata_nested$actuals_test[[1]],
na.rm=T)
## [1] 0.2991226
现在我们可以使用与本节开头相同的 postResample()
函数来计算 R Squared 指标:
evaluate_preds_rsq <- function(predictions, actuals){
postResample(pred = predictions, obs = actuals)[[2]]
}
cryptodata_nested <- mutate(cryptodata_nested,
lm_rsq_test = unlist(ifelse(split < 5,
map2(lm_test_predictions, actuals_test, evaluate_preds_rsq),
NA)),
lm_rsq_holdout = unlist(map2(lm_holdout_predictions, actuals_holdout, evaluate_preds_rsq)))
查看结果:
select(cryptodata_nested, lm_rsq_test, lm_rsq_holdout)
## # A tibble: 430 x 4
## # Groups: symbol, split [430]
## symbol split lm_rsq_test lm_rsq_holdout
## <chr> <dbl> <dbl> <dbl>
## 1 BTC 1 0.000486 0.749
## 2 ETH 1 0.0584 0.773
## 3 EOS 1 0.196 0.602
## 4 LTC 1 0.0572 0.640
## 5 BSV 1 0.0402 0.728
## 6 ADA 1 0.316 0.375
## 7 ZEC 1 0.0232 0.404
## 8 HT 1 0.583 0.00919
## 9 TRX 1 0.00253 0.316
## 10 KNC 1 0.239 0.706
## # ... with 420 more rows
类似地,让我们为所有模型创建一个函数以获取 RMSE 指标:
evaluate_preds_rmse <- function(predictions, actuals){
hydroGOF::rmse(predictions, actuals, na.rm=T)
}
现在我们可以使用 map2()
函数来使用它为测试数据和保留集获取 RMSE 指标:
cryptodata_nested <- mutate(cryptodata_nested,
lm_rmse_test = unlist(ifelse(split < 5,
map2(lm_test_predictions, actuals_test, evaluate_preds_rmse),
NA)),
lm_rmse_holdout = unlist(map2(lm_holdout_predictions, actuals_holdout, evaluate_preds_rmse)))
查看结果。使用 print(n=500)
包装它们会覆盖仅显示数据预览的行为,以便我们可以查看完整结果(最多 500
个观察值)。
print(select(cryptodata_nested, lm_rmse_test, lm_rmse_holdout, lm_rsq_test, lm_rsq_holdout), n=500)
## # A tibble: 430 x 6
## # Groups: symbol, split [430]
## symbol split lm_rmse_test lm_rmse_holdout lm_rsq_test lm_rsq_holdout
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 BTC 1 0.299 0.180 0.000486 0.749
## 2 ETH 1 0.344 0.155 0.0584 0.773
## 3 EOS 1 0.758 0.277 0.196 0.602
## 4 LTC 1 0.943 0.248 0.0572 0.640
## 5 BSV 1 1.67 0.341 0.0402 0.728
## 6 ADA 1 0.511 0.507 0.316 0.375
## 7 ZEC 1 1.55 0.496 0.0232 0.404
## 8 HT 1 0.531 0.228 0.583 0.00919
## 9 TRX 1 0.559 0.247 0.00253 0.316
## 10 KNC 1 0.533 0.412 0.239 0.706
## 11 XMR 1 0.648 0.507 0.105 0.357
## 12 ZRX 1 0.927 0.293 0.0999 0.708
## 13 BAT 1 0.705 0.276 0.0343 0.758
## 14 BNT 1 0.614 0.642 0.152 0.429
## 15 MANA 1 0.658 0.393 0.158 0.566
## 16 ENJ 1 0.757 0.326 0.00459 0.477
## 17 BTG 1 1.16 1.04 0.136 0.0816
## 18 ELF 1 0.488 0.507 0.00271 0.877
## 19 NEXO 1 2.84 1.80 0.0136 0.156
## 20 CHZ 1 0.618 0.338 0.00521 0.384
## 21 AVA 1 0.954 4.80 0.00422 0.672
## 22 GMTT 1 0.869 1.35 0.272 0.168
## 23 ID 1 1.46 0.472 0.0172 0.744
## 24 DYDX 1 1.80 0.580 0.107 0.495
## 25 1INCH 1 0.737 0.413 0.00260 0.705
## 26 OXT 1 3.84 1.21 0.000507 0.0965
## 27 SKL 1 0.904 0.574 0.000522 0.284
## 28 DOT 1 0.662 0.400 0.0409 0.0108
## 29 LQTY 1 0.814 0.408 0.00167 0.578
## 30 TOMO 1 1.33 3.24 0.398 0.730
## 31 GODS 1 1.19 1.11 0.599 0.0189
## 32 IOTA 1 0.494 0.308 0.0981 0.543
## 33 PERP 1 0.685 0.287 0.0158 0.769
## 34 STETH 1 0.682 0.227 0.0633 0.807
## 35 RIF 1 1.33 0.994 0.115 0.0464
## 36 APT 1 0.790 0.497 0.105 0.553
## 37 ATOM 1 0.707 0.333 0.0193 0.653
## 38 ANT 1 1.09 0.647 0.0721 0.232
## 39 ARB 1 0.698 0.326 0.410 0.909
## 40 XDC 1 4.18 4.19 0.449 0.0308
## 41 THETA 1 0.859 0.366 0.0286 0.243
## 42 XCH 1 0.781 0.930 0.409 0.109
## 43 AGIX 1 1.21 0.507 0.0327 0.672
## 44 REN 1 0.815 0.310 0.0821 0.814
## 45 VGX 1 1.44 1.16 0.488 0.0852
## 46 GMT 1 0.979 0.370 0.0198 0.737
## 47 APFC 1 1.74 1.29 0.570 0.841
## 48 DASH 1 0.967 0.487 0.0130 0.608
## 49 ETHW 1 1.65 0.555 0.0665 0.367
## 50 LAZIO 1 0.855 0.621 0.139 0.0434
## 51 BCUG 1 1.45 0.170 0.0571 0.775
## 52 BICO 1 1.54 11.3 0.00552 0.207
## 53 OP 1 4.89 0.868 0.420 0.484
## 54 NMR 1 1.59 0.560 0.0304 0.0137
## 55 ICP 1 1.47 0.851 0.00532 0.0624
## 56 GLEEC 1 9.46 3.12 0.0111 0.000811
## 57 CFX 1 5.79 0.855 0.0734 0.476
## 58 CHR 1 1.96 0.681 0.465 0.341
## 59 LDO 1 289. 1.32 0.0542 0.0987
## 60 DAR 1 2.85 0.516 0.0732 0.0788
## 61 RAD 1 1.52 0.617 0.00301 0.00238
## 62 SUSHI 1 2.65 0.598 0.0855 0.0562
## 63 LOC 1 0.0110 1.54 0.896 0.866
## 64 SANTOS 1 0.530 0.792 0.00247 0.0645
## 65 BAND 1 3.40 1.10 0.555 0.0123
## 66 KSM 1 1.93 0.623 0.0887 0.311
## 67 MAGIC 1 2.76 0.729 0.535 0.0811
## 68 BSW 1 0.991 0.662 0.150 0.00220
## 69 CTXC 1 1.53 1.65 0.00584 0.173
## 70 QNT 1 1.15 0.806 0.00000217 0.284
## 71 RARE 1 1.62 0.622 0.0374 0.0309
## 72 ONT 1 1.03 0.615 0.332 0.126
## 73 MDT 1 1.53 0.682 0.0576 0.00672
## 74 AR 1 2.07 0.648 0.0904 0.332
## 75 STG 1 3.42 0.370 0.00641 0.251
## 76 GAL 1 1.91 0.693 0.126 0.0887
## 77 XLM 1 20.8 0.818 0.745 0.00336
## 78 TRU 1 2.22 0.717 0.244 0.602
## 79 OMG 1 4.23 0.612 0.732 0.0498
## 80 CELO 1 2.14 0.554 0.155 0.0237
## 81 PORTO 1 0.551 0.716 0.288 0.0134
## 82 COMP 1 1.63 0.955 0.0369 0.284
## 83 ILV 1 0.970 0.463 0.00973 0.575
## 84 FLUX 1 1.13 1.10 0.321 0.0700
## 85 PAR 1 0.671 1754. 0.791 0.0100
## 86 IMX 1 4.33 0.744 0.721 0.357
## 87 HT 2 0.478 0.228 0.0493 0.00919
## 88 GMTT 2 0.990 1.35 0.0462 0.168
## 89 ETHW 2 0.941 0.555 0.00480 0.367
## 90 LAZIO 2 0.508 0.621 0.0116 0.0434
## 91 ID 2 1.11 0.472 0.00361 0.744
## 92 BNT 2 0.857 0.642 0.453 0.429
## 93 LQTY 2 0.464 0.408 0.774 0.578
## 94 XDC 2 2.59 4.19 0.213 0.0308
## 95 XCH 2 0.271 0.930 0.180 0.109
## 96 VGX 2 11.3 1.16 0.0612 0.0852
## 97 BTC 2 0.414 0.180 0.00173 0.749
## 98 EOS 2 0.527 0.277 0.677 0.602
## 99 ETH 2 0.338 0.155 0.0264 0.773
## 100 LTC 2 0.737 0.248 0.284 0.640
## 101 BSV 2 0.822 0.341 0.671 0.728
## 102 ADA 2 0.745 0.507 0.496 0.375
## 103 ZEC 2 0.389 0.496 0.834 0.404
## 104 TRX 2 0.214 0.247 0.668 0.316
## 105 KNC 2 0.451 0.412 0.716 0.706
## 106 ZRX 2 1.49 0.293 0.0347 0.708
## 107 BAT 2 0.947 0.276 0.110 0.758
## 108 MANA 2 0.400 0.393 0.569 0.566
## 109 ENJ 2 0.612 0.326 0.269 0.477
## 110 ELF 2 0.300 0.507 0.206 0.877
## 111 NEXO 2 3.29 1.80 0.291 0.156
## 112 CHZ 2 0.590 0.338 0.229 0.384
## 113 AVA 2 1.45 4.80 0.293 0.672
## 114 1INCH 2 0.605 0.413 0.783 0.705
## 115 DYDX 2 0.794 0.580 0.806 0.495
## 116 OXT 2 0.492 1.21 0.218 0.0965
## 117 SKL 2 0.653 0.574 0.288 0.284
## 118 DOT 2 0.855 0.400 0.801 0.0108
## 119 TOMO 2 0.856 3.24 0.153 0.730
## 120 GODS 2 0.699 1.11 0.686 0.0189
## 121 IOTA 2 0.579 0.308 0.164 0.543
## 122 PERP 2 0.599 0.287 0.327 0.769
## 123 STETH 2 0.419 0.227 0.526 0.807
## 124 RIF 2 0.781 0.994 0.0421 0.0464
## 125 APT 2 0.413 0.497 0.570 0.553
## 126 ATOM 2 0.582 0.333 0.331 0.653
## 127 ANT 2 0.783 0.647 0.166 0.232
## 128 ARB 2 0.899 0.326 0.159 0.909
## 129 THETA 2 0.829 0.366 0.816 0.243
## 130 AGIX 2 0.562 0.507 0.480 0.672
## 131 REN 2 0.650 0.310 0.601 0.814
## 132 BCUG 2 0.157 0.170 0.950 0.775
## 133 GMT 2 1.36 0.370 0.0166 0.737
## 134 APFC 2 0.296 1.29 0.604 0.841
## 135 DASH 2 0.820 0.487 0.202 0.608
## 136 BTG 2 0.861 1.04 0.223 0.0816
## 137 XMR 2 0.618 0.507 0.0125 0.357
## 138 NMR 2 1.73 0.560 0.220 0.0137
## 139 CFX 2 1.11 0.855 0.433 0.476
## 140 STG 2 0.536 0.370 0.354 0.251
## 141 COMP 2 2.08 0.955 0.00185 0.284
## 142 PAR 2 0.0408 1754. 0.905 0.0100
## 143 BICO 2 1.15 11.3 0.323 0.207
## 144 OP 2 1.24 0.868 0.114 0.484
## 145 ICP 2 0.617 0.851 0.804 0.0624
## 146 CHR 2 1.33 0.681 0.242 0.341
## 147 LDO 2 0.523 1.32 0.622 0.0987
## 148 DAR 2 1.03 0.516 0.0780 0.0788
## 149 RAD 2 0.834 0.617 0.143 0.00238
## 150 SUSHI 2 1.49 0.598 0.169 0.0562
## 151 SANTOS 2 0.368 0.792 0.642 0.0645
## 152 KSM 2 0.570 0.623 0.513 0.311
## 153 BAND 2 1.39 1.10 0.265 0.0123
## 154 CTXC 2 3
.07 1.65 0.265 0.173
## 155 MAGIC 2 0.727 0.729 0.530 0.0811
## 156 BSW 2 0.326 0.662 0.918 0.00220
## 157 QNT 2 0.289 0.806 0.548 0.284
## 158 RARE 2 0.960 0.622 0.0827 0.0309
## 159 ONT 2 0.872 0.615 0.450 0.126
## 160 MDT 2 2.42 0.682 0.0780 0.00672
## 161 AR 2 0.573 0.648 0.825 0.332
## 162 GAL 2 0.533 0.693 0.727 0.0887
## 163 XLM 2 0.429 0.818 0.930 0.00336
## 164 TRU 2 0.687 0.717 0.569 0.602
## 165 OMG 2 0.691 0.612 0.846 0.0498
## 166 CELO 2 1.47 0.554 0.0294 0.0237
## 167 PORTO 2 0.813 0.716 0.465 0.0134
## 168 ILV 2 0.658 0.463 0.604 0.575
## 169 FLUX 2 0.848 1.10 0.245 0.0700
## 170 IMX 2 0.813 0.744 0.855 0.357
## 171 LOC 2 0.0232 1.54 0.0654 0.866
## 172 GLEEC 2 9.77 3.12 0.421 0.000811
## 173 HT 3 0.336 0.228 0.145 0.00919
## 174 GMTT 3 0.225 1.35 0.344 0.168
## 175 ETHW 3 0.466 0.555 0.569 0.367
## 176 LAZIO 3 1.16 0.621 0.278 0.0434
## 177 BNT 3 0.749 0.642 0.0890 0.429
## 178 LQTY 3 0.563 0.408 0.764 0.578
## 179 XDC 3 6.51 4.19 0.122 0.0308
## 180 XCH 3 0.216 0.930 0.750 0.109
## 181 BTC 3 0.243 0.180 0.888 0.749
## 182 ETH 3 0.331 0.155 0.0358 0.773
## 183 LTC 3 0.556 0.248 0.372 0.640
## 184 ADA 3 0.409 0.507 0.482 0.375
## 185 BSV 3 0.982 0.341 0.0524 0.728
## 186 TRX 3 1.28 0.247 0.558 0.316
## 187 ZEC 3 0.449 0.496 0.434 0.404
## 188 KNC 3 2.86 0.412 0.434 0.706
## 189 ZRX 3 0.530 0.293 0.375 0.708
## 190 BAT 3 0.547 0.276 0.357 0.758
## 191 MANA 3 0.340 0.393 0.797 0.566
## 192 ENJ 3 0.438 0.326 0.351 0.477
## 193 ELF 3 0.219 0.507 0.471 0.877
## 194 NEXO 3 2.97 1.80 0.125 0.156
## 195 CHZ 3 0.469 0.338 0.104 0.384
## 196 AVA 3 0.647 4.80 0.702 0.672
## 197 1INCH 3 0.551 0.413 0.291 0.705
## 198 DYDX 3 0.773 0.580 0.171 0.495
## 199 OXT 3 0.433 1.21 0.128 0.0965
## 200 SKL 3 0.425 0.574 0.745 0.284
## 201 TOMO 3 1.23 3.24 0.291 0.730
## 202 IOTA 3 0.340 0.308 0.578 0.543
## 203 PERP 3 0.511 0.287 0.797 0.769
## 204 STETH 3 0.310 0.227 0.307 0.807
## 205 RIF 3 0.735 0.994 0.0897 0.0464
## 206 APT 3 0.669 0.497 0.260 0.553
## 207 ATOM 3 0.458 0.333 0.198 0.653
## 208 ANT 3 1.24 0.647 0.00202 0.232
## 209 ARB 3 0.550 0.326 0.0533 0.909
## 210 THETA 3 1.14 0.366 0.279 0.243
## 211 AGIX 3 0.471 0.507 0.547 0.672
## 212 REN 3 0.247 0.310 0.874 0.814
## 213 EOS 3 0.282 0.277 0.710 0.602
## 214 DOT 3 0.396 0.400 0.192 0.0108
## 215 BCUG 3 1.91 0.170 0.546 0.775
## 216 GMT 3 0.287 0.370 0.644 0.737
## 217 APFC 3 0.218 1.29 0.840 0.841
## 218 DASH 3 0.384 0.487 0.489 0.608
## 219 BTG 3 14.1 1.04 0.000907 0.0816
## 220 GODS 3 0.710 1.11 0.372 0.0189
## 221 XMR 3 0.499 0.507 0.00384 0.357
## 222 VGX 3 1.95 1.16 0.0330 0.0852
## 223 ID 3 0.644 0.472 0.243 0.744
## 224 NMR 3 0.739 0.560 0.0000857 0.0137
## 225 CFX 3 0.573 0.855 0.338 0.476
## 226 STG 3 0.450 0.370 0.494 0.251
## 227 ONT 3 0.694 0.615 0.191 0.126
## 228 COMP 3 3.00 0.955 0.269 0.284
## 229 PAR 3 391. 1754. 0.000274 0.0100
## 230 OP 3 0.723 0.868 0.706 0.484
## 231 BICO 3 1.88 11.3 0.173 0.207
## 232 ICP 3 0.412 0.851 0.817 0.0624
## 233 CHR 3 0.512 0.681 0.192 0.341
## 234 LDO 3 0.867 1.32 0.0917 0.0987
## 235 DAR 3 0.558 0.516 0.0479 0.0788
## 236 RAD 3 0.431 0.617 0.497 0.00238
## 237 SUSHI 3 1.18 0.598 0.333 0.0562
## 238 LOC 3 21.9 1.54 0.279 0.866
## 239 SANTOS 3 0.457 0.792 0.447 0.0645
## 240 KSM 3 0.286 0.623 0.772 0.311
## 241 BAND 3 0.600 1.10 0.182 0.0123
## 242 CTXC 3 0.807 1.65 0.00267 0.173
## 243 MAGIC 3 0.870 0.729 0.0881 0.0811
## 244 BSW 3 0.538 0.662 0.213 0.00220
## 245 QNT 3 0.560 0.806 0.754 0.284
## 246 RARE 3 0.595 0.622 0.561 0.0309
## 247 MDT 3 0.488 0.682 0.0976 0.00672
## 248 AR 3 0.660 0.648 0.562 0.332
## 249 GAL 3 1.56 0.693 0.242 0.0887
## 250 XLM 3 2.00 0.818 0.169 0.00336
## 251 TRU 3 0.920 0.717 0.488 0.602
## 252 OMG 3 0.762 0.612 0.119 0.0498
## 253 CELO 3 1.37 0.554 0.000301 0.0237
## 254 PORTO 3 0.489 0.716 0.374 0.0134
## 255 ILV 3 0.553 0.463 0.646 0.575
## 256 FLUX 3 1.27 1.10 0.0127 0.0700
## 257 IMX 3 0.503 0.744 0.740 0.357
## 258 GLEEC 3 2.74 3.12 0.00148
0.000811
## 259 HT 4 0.277 0.228 0.126 0.00919
## 260 GMTT 4 88.8 1.35 0.00403 0.168
## 261 ETHW 4 0.591 0.555 0.229 0.367
## 262 LAZIO 4 0.314 0.621 0.0570 0.0434
## 263 LQTY 4 0.411 0.408 0.301 0.578
## 264 BTC 4 0.0742 0.180 0.362 0.749
## 265 ETH 4 0.116 0.155 0.197 0.773
## 266 LTC 4 0.252 0.248 0.488 0.640
## 267 BSV 4 0.347 0.341 0.00629 0.728
## 268 ADA 4 0.259 0.507 0.00171 0.375
## 269 ZEC 4 0.519 0.496 0.104 0.404
## 270 TRX 4 0.124 0.247 0.00141 0.316
## 271 KNC 4 0.593 0.412 0.905 0.706
## 272 ZRX 4 0.435 0.293 0.671 0.708
## 273 BAT 4 0.880 0.276 0.0205 0.758
## 274 MANA 4 0.342 0.393 0.00502 0.566
## 275 ENJ 4 0.338 0.326 0.236 0.477
## 276 ELF 4 0.367 0.507 0.213 0.877
## 277 CHZ 4 0.231 0.338 0.0743 0.384
## 278 AVA 4 0.383 4.80 0.147 0.672
## 279 DYDX 4 0.306 0.580 0.859 0.495
## 280 1INCH 4 0.239 0.413 0.200 0.705
## 281 OXT 4 1.59 1.21 0.614 0.0965
## 282 SKL 4 0.408 0.574 0.564 0.284
## 283 TOMO 4 0.974 3.24 0.206 0.730
## 284 IOTA 4 0.245 0.308 0.139 0.543
## 285 PERP 4 0.334 0.287 0.798 0.769
## 286 STETH 4 0.249 0.227 0.249 0.807
## 287 RIF 4 0.325 0.994 0.497 0.0464
## 288 APT 4 1.73 0.497 0.00717 0.553
## 289 ATOM 4 0.192 0.333 0.269 0.653
## 290 ANT 4 1.12 0.647 0.268 0.232
## 291 ARB 4 0.126 0.326 0.800 0.909
## 292 THETA 4 0.217 0.366 0.605 0.243
## 293 AGIX 4 0.627 0.507 0.0368 0.672
## 294 REN 4 0.570 0.310 0.101 0.814
## 295 EOS 4 0.218 0.277 0.261 0.602
## 296 BNT 4 1.09 0.642 0.503 0.429
## 297 DOT 4 0.187 0.400 0.0964 0.0108
## 298 BCUG 4 0.127 0.170 0.923 0.775
## 299 GMT 4 0.265 0.370 0.00781 0.737
## 300 APFC 4 0.471 1.29 0.467 0.841
## 301 DASH 4 0.163 0.487 0.786 0.608
## 302 BTG 4 0.581 1.04 0.206 0.0816
## 303 XCH 4 0.240 0.930 0.0825 0.109
## 304 NEXO 4 2.68 1.80 0.0500 0.156
## 305 XDC 4 3.77 4.19 0.284 0.0308
## 306 XMR 4 0.167 0.507 0.462 0.357
## 307 GODS 4 1.07 1.11 0.608 0.0189
## 308 VGX 4 2.61 1.16 0.00913 0.0852
## 309 ID 4 0.201 0.472 0.701 0.744
## 310 CFX 4 1.53 0.855 0.0245 0.476
## 311 ONT 4 1.09 0.615 0.436 0.126
## 312 STG 4 1.05 0.370 0.264 0.251
## 313 COMP 4 2.52 0.955 0.369 0.284
## 314 LOC 4 1.76 1.54 0.200 0.866
## 315 PAR 4 4.11 1754. 0.0556 0.0100
## 316 BICO 4 2.58 11.3 0.00826 0.207
## 317 OP 4 1.48 0.868 0.128 0.484
## 318 ICP 4 1.74 0.851 0.313 0.0624
## 319 CHR 4 2.53 0.681 0.0967 0.341
## 320 LDO 4 1.37 1.32 0.000493 0.0987
## 321 DAR 4 0.669 0.516 0.942 0.0788
## 322 RAD 4 1.17 0.617 0.647 0.00238
## 323 SUSHI 4 2.35 0.598 0.00863 0.0562
## 324 SANTOS 4 2.12 0.792 0.361 0.0645
## 325 KSM 4 1.68 0.623 0.0430 0.311
## 326 BAND 4 2.12 1.10 0.698 0.0123
## 327 CTXC 4 0.741 1.65 0.596 0.173
## 328 MAGIC 4 0.734 0.729 0.907 0.0811
## 329 BSW 4 5.08 0.662 0.0598 0.00220
## 330 QNT 4 0.335 0.806 0.291 0.284
## 331 RARE 4 0.670 0.622 0.844 0.0309
## 332 MDT 4 1.53 0.682 0.289 0.00672
## 333 AR 4 1.01 0.648 0.561 0.332
## 334 GAL 4 2.93 0.693 0.612 0.0887
## 335 XLM 4 0.646 0.818 0.688 0.00336
## 336 TRU 4 0.944 0.717 0.841 0.602
## 337 OMG 4 1.09 0.612 0.601 0.0498
## 338 CELO 4 1.28 0.554 0.407 0.0237
## 339 PORTO 4 0.856 0.716 0.911 0.0134
## 340 ILV 4 1.00 0.463 0.481 0.575
## 341 FLUX 4 1.55 1.10 0.0183 0.0700
## 342 IMX 4 1.08 0.744 0.491 0.357
## 343 NMR 4 1.42 0.560 0.449 0.0137
## 344 GLEEC 4 1.44 3.12 0.541 0.000811
## 345 HT 5 NA 0.228 NA 0.00919
## 346 XMR 5 NA 0.507 NA 0.357
## 347 LQTY 5 NA 0.408 NA 0.578
## 348 BTC 5 NA 0.180 NA 0.749
## 349 ETH 5 NA 0.155 NA 0.773
## 350 LTC 5 NA 0.248 NA 0.640
## 351 ADA 5 NA 0.507 NA 0.375
## 352 BSV 5 NA 0.341 NA 0.728
## 353 TRX 5 NA 0.247 NA 0.316
## 354 ZEC 5 NA 0.496 NA 0.404
## 355 KNC 5 NA 0.412 NA 0.706
## 356 ZRX 5 NA 0.293 NA 0.708
## 357 BAT 5 NA 0.276 NA 0.758
## 358 MANA 5 NA 0.393 NA 0.566
## 359 ENJ 5 NA 0.326 NA 0.477
## 360 ELF 5 NA 0.507 NA 0.877
## 361 CHZ 5 NA 0.338 NA 0.384
## 362 AVA 5 NA 4.80 NA 0.672
## 363 DYDX 5 NA 0.580 NA 0.495
## 364 1INCH 5 NA 0.413 NA 0.705
## 365 OXT 5 NA 1.21 NA 0.0965
## 366 SKL 5 NA 0.574 NA 0.284
## 367 TOMO 5 NA 3.24 NA 0.730
## 368 IOTA 5 NA 0.308 NA 0.543
## 369 PERP 5 NA 0.287 NA 0.769
## 370 STETH 5 NA 0.227 NA 0.807
## 371 APT 5 NA 0.497 NA 0.553
## 372 RIF 5 NA 0.994 NA 0.0464
## 373 ATOM 5 NA 0.333 NA 0.653
## 374 AGIX 5 NA 0.507 NA 0.672
## 375 REN 5 NA 0.310 NA 0.814
## 376 EOS 5 NA 0.277 NA 0.602
## 377 BTG 5 NA 1.04 NA 0.0816
## 378 DOT 5 NA 0.400 NA 0.0108
## 379 ANT 5 NA 0.647 NA 0.232
## 380 ARB 5 NA 0.326 NA 0.909
## 381 THETA 5 NA 0.366 NA 0.243
## 382 XCH 5 NA 0.930 NA 0.109
## 383 BNT 5 NA 0.642 NA 0.429
## 384 BCUG 5 NA 0.170 NA 0.775
## 385 GMT 5 NA 0.370 NA 0.737
## 386 APFC 5 NA 1.29 NA 0.841
## 387 DASH 5 NA 0.487 NA 0.608
## 388 NEXO 5 NA 1.80 NA 0.156
## 389 GODS 5 NA 1.11 NA 0.0189
## 390 XDC 5 NA 4.19 NA 0.0308
## 391 VGX 5 NA 1.16 NA 0.0852
## 392 ID 5 NA 0.472 NA 0.744
## 393 LAZIO 5 NA 0.621 NA 0.0434
## 394 ETHW 5 NA 0.555 NA 0.367
## 395 GMTT 5 NA 1.35 NA 0.168
## 396 CFX 5 NA 0.855 NA 0.476
## 397 ONT 5 NA 0.615 NA 0.126
## 398 STG 5 NA 0.370 NA 0.251
## 399 COMP 5 NA 0.955 NA 0.284
## 400 PAR 5 NA 1754. NA 0.0100
## 401 LOC 5 NA 1.54 NA 0.866
## 402 CHR 5 NA 0.681 NA 0.341
## 403 LDO 5 NA 1.32 NA 0.0987
## 404 SUSHI 5 NA 0.598 NA 0.0562
## 405 KSM 5 NA 0.623 NA 0.311
## 406 CTXC 5 NA 1.65 NA 0.173
## 407 QNT 5 NA 0.806 NA 0.284
## 408 RARE 5 NA 0.622 NA 0.0309
## 409 TRU 5 NA 0.717 NA 0.602
## 410 OMG 5 NA 0.612 NA 0.0498
## 411 ILV 5 NA 0.463 NA 0.575
## 412 OP 5 NA 0.868 NA 0.484
## 413 BICO 5 NA 11.3 NA 0.207
## 414 NMR 5 NA 0.560 NA 0.013
Rinker, Tyler, and Dason Kurkiewicz. 2019. Pacman: Package Management Tool. https://github.com/trinker/pacman.
Luraschi, Javier. 2020. Pins: Pin, Discover and Share Resources. https://github.com/rstudio/pins.
Waring, Elin, Michael Quinn, Amelia McNamara, Eduardo Arino de la Rubia, Hao Zhu, and Shannon Ellis. 2020. Skimr: Compact and Flexible Summaries of Data. https://CRAN.R-project.org/package=skimr.
Eddelbuettel, Dirk. 2020. Anytime: 任何东西转换为 Posixct 或 Date。http://dirk.eddelbuettel.com/code/anytime.html。
Wang, Earo, Di Cook, Rob Hyndman, 和 Mitchell O’Hara-Wild. 2020. Tsibble: 整洁的时间序列数据框架和工具。https://tsibble.tidyverts.org。
Wickham, Hadley, Romain François, Lionel Henry, 和 Kirill Müller. 2020. Dplyr: 数据操作的语法。https://CRAN.R-project.org/package=dplyr。
Arnold, Jeffrey B. 2019. Ggthemes: 为 Ggplot2 提供额外的主题、比例尺和几何图形。http://github.com/jrnold/ggthemes。
Kassambara, Alboukadel. 2020. Ggpubr: 基于 Ggplot2 的发布就绪图。https://rpkgs.datanovia.com/ggpubr/。
Kothari, Aditya. 2018. GgTimeSeries: 使用图形语法进行时间序列可视化。https://github.com/Ather-Energy/ggTimeSeries。
Morgan-Wall, Tyler. 2020. Rayshader: 在 2D 和 3D 中创建地图并可视化数据。https://github.com/tylermorganwall/rayshader。
Pedersen, Thomas Lin. 2020. Ggforce: 加速 Ggplot :https://CRAN.R-project.org/package=ggforce。
Pedersen, Thomas Lin, 和 David Robinson. 2020. Gganimate: 动画图形的语法。https://CRAN.R-project.org/package=gganimate。
Sievert, Carson, Chris Parmer, Toby Hocking, Scott Chamberlain, Karthik Ram, Marianne Corvellec, 和 Pedro Despouy. 2020. Plotly: 通过 Plotly.js 创建交互式网络图形。https://CRAN.R-project.org/package=plotly。
Wickham, Hadley, Winston Chang, Lionel Henry, Thomas Lin Pedersen, Kohske Takahashi, Claus Wilke, Kara Woo, Hiroaki Yutani, 和 Dewey Dunnington. 2020. Ggplot2: 使用图形语法创建优雅的数据可视化。https://CRAN.R-project.org/package=ggplot2。
Wickham, Hadley, 和 Garrett Grolemund. 2017. R for Data Science: Import, Tidy, Transform, Visualize, and Model Data. 第1版. O’Reilly Media, Inc.