您为使用 magrittr 管道和地图功能的第一步选择了一个具有挑战性的问题!我会尽力给你一个有用的答案,但我也建议你在练习时找到一些更容易使用的数据。了解管道的好地方%>%是 Hadley Wickham 书中的“管道”一章。关于迭代的章节也很好地介绍了这些map_*函数。一旦你对概念有了更坚定的理解,你就可以回到更复杂的问题。我认为 Hadley 比以往任何时候都更好地解释了这些工具,所以我不会在这里详细介绍它们,而是专注于解释为什么您的代码不起作用,以及为什么我的代码起作用。
分析您的代码
映射函数允许一些有用的快捷方式,您已经发现了其中一个 - 即,如果您将向量或列表作为函数参数传递,它们会自动转换为提取函数。所以,你在正确的轨道上!
要记住的是,映射函数返回一个与输入向量具有相同长度和名称的向量。您的输入向量是jsonData,其中有 5 个元素的名称[1] "copyright" "allPlays" "currentPlay" "scoringPlays" "playsByInning"。当您运行时jsonData %>% map("playEvents") %>% map("hitData"),正在提取数据,但 R 仍返回一个包含五个元素且名称与原始向量相同的向量。如果您查看以下代码,您会发现您的代码确实剥离了最上层,但长度保持不变,这不是很有帮助:
> unlist(map(jsonData, class))
copyright allPlays currentPlay scoringPlays playsByInning
"character" "data.frame" "list" "integer" "data.frame"
> unlist(map(jsonData %>% map("playEvents"), class))
copyright allPlays currentPlay scoringPlays playsByInning
"NULL" "list" "data.frame" "NULL" "NULL"
> unlist(map(jsonData %>% map("playEvents") %>% map("hitData"), class))
copyright allPlays currentPlay scoringPlays playsByInning
"NULL" "NULL" "data.frame" "NULL" "NULL"
最终输出,以及您试图与bind_rows上面的调用相结合的内容是:
> jsonData %>% map("playEvents") %>% map("hitData")
$copyright
NULL
$allPlays
NULL
$currentPlay
launchSpeed launchAngle totalDistance trajectory hardness location coordinates.coordX coordinates.coordY
1 NA NA NA <NA> <NA> <NA> NA NA
2 81.3 61.92 187.5 popup medium 6 75.78 167.97
$scoringPlays
NULL
$playsByInning
NULL
显然这不是你想要的。经过一番修修补补,我想出了以下解决方案。
我自己的策略
图书馆:
library(jsonlite)
library(purrr)
library(dplyr)
library(readr)
library(stringr)
library(magrittr)
我使用稍微不同的方法来下载和解析 JSON,因为我需要查看结构。我将它包括在内,以防您发现它有用:
url <- paste0("http://statsapi-prod-alt-968618993.us-east-1.elb.amazonaws",
".com/api/v1/game/565711/playByPlay")
url %>% read_file() %>% prettify() %>% write_file("bball.json")
jsonData <- fromJSON("bball.json")
我首先提取并清理hitData数据框。我知道它们都可以在 中找到playEvents,所以我可以使用$语法跳过几个步骤。第一次调用从列表的每个元素中map提取。数据框是嵌套的(它们包含其他数据框),因此第二次调用with将它们展平。该函数确保 R 在遇到数据框以外的内容时不会抛出错误(只有 46 个元素包含)。许多数据帧包含充满s 的行,因此第三次调用使用匿名函数(再次在 中)来摆脱这些。然后第四次调用从每个元素的hitDataplayEventshitDatamapjsonlite::flattensafelyhitDatahitDataNAmapsafelymapresult变量,它是由safely(以及error我们不需要的变量)创建的:
hitdata_list <- jsonData$allPlays$playEvents %>%
map("hitData") %>%
map(safely(jsonlite::flatten)) %>%
map(safely(~.$result[complete.cases(.$result),])) %>%
map("result")
现在我有一个数据框列表hitData。正如我上面提到的,80 个条目中只有 46 个包含hitData,所以我需要一种方法来从atBatIndex. TRUE我可以通过在元素中hitdata_list包含数据帧时生成逻辑向量来做到这一点,FALSE否则。我map_lgl用来返回一个逻辑向量而不是一个列表:
lgl_index <- map_lgl(hitdata_list, ~ !is.null(.))
atbatindex_vec <- jsonData$allPlays$atBatIndex[lgl_index]
然后我使用一个stringr函数game_pk从 URL 中获取。我不确定它是否适用于每个 URL,但在这种情况下它可以正常工作:
game_pk_vec <- str_match(url, "/(\\d+)/")[2] %>%
as.integer()
最后,我将atBatIndex和合并game_pk到一个 tibble 中,然后将该 tibble 与hitData使用bind_cols. hitData数据框仍在列表中,因此我需要先将它们与bind_rows. 该set_colnames功能来自magrittr包,并按照它所说的去做。我需要设置列名,因为在展平数据框时创建了一些复合名称hitData:
hitdata_df <- tibble(game_pk = game_pk_vec, atBatIndex = atbatindex_vec) %>%
bind_cols(bind_rows(hitdata_list)) %>%
set_colnames(str_extract(names(.), "\\w+$"))
我唯一没有做的是 extract pitchNumber。调用jsonData$allPlays$playEvents %>% map("pitchNumber")返回序列 1 到n的列表,其中每个向量的长度 > 1。我假设您只需要每个序列中的最终数字,但我不确定,所以我会不遗余力。你可以做我所做的atBatIndex来获取相关元素,然后提取你需要的东西。这是最终的数据框:
# A tibble: 46 x 10
game_pk atBatIndex launchSpeed launchAngle totalDistance trajectory hardness location coordX coordY
<chr> <int> <dbl> <dbl> <dbl> <chr> <chr> <chr> <dbl> <dbl>
1 565711 4 76.6 2.74 188. ground_ball medium 9 178. 145.
2 565711 5 101. 15.4 328. line_drive hard 8 145. 62.2
3 565711 6 103. 29.4 382. line_drive medium 9 237. 79.4
4 565711 8 109. 15.6 319. line_drive hard 9 181. 102.
5 565711 9 75.8 47.8 239. fly_ball medium 7 99.8 103.
6 565711 10 91.6 44.1 311. fly_ball medium 8 140. 69.3
7 565711 12 79.1 23.4 246. line_drive medium 7 52.3 126.
8 565711 13 67.3 -21.3 124. ground_ball medium 6 108. 156.
9 565711 14 89.9 -21.6 7.41 ground_ball medium 6 108. 152.
10 565711 15 110. 27.7 420. fly_ball medium 9 250. 69.0
# … with 36 more rows