使用purrr函数和%>%

时间:2019-05-11 13:29:56

标签: r dplyr purrr jsonlite

我正在尝试建立与此类似的表格(这只是几行,但是我试图从游戏列表中获取所有点击量):

game_pk   atBatIndex  pitchNumber   hardness launchAngle  launchSpeed  location  totalDistance  trajectory   coordX   coordY
565711    4           3             medium   2.74         76.62        9         188.03         ground_ball  177.88   145.11
565711    5           3             hard     15.42        101.26       8         328.08         line_drive   144.79   62.25

我要提取的大部分内容都可以在hitData中找到,它位于列表playEvents中的80个元素中,但不是全部,这些元素本身位于数据框{{1 }}。您可以使用allPlays来查看示例。

这是我正在使用的代码:

jsonData$allPlays$playEvents[[80]]$hitData

不幸的是,它返回错误:

  

错误:参数7不能是包含数据框的列表

我很难想出一种方法来处理JSON中的列表,数据框,嵌套数据框和向量的集合。

除了library(jsonlite) library(purrr) library(dplyr) url <- "http://statsapi-prod-alt-968618993.us-east-1.elb.amazonaws.com/api/v1/game/565711/playByPlay" jsonData <- fromJSON(url) hitDataDF <- data.frame(jsonData %>% map("playEvents") %>% map("hitData") %>% map_df(bind_rows)) 之外,我还需要来自hitData(在atBatIndex(也在jsonData$allPlays$about)和jsonData$allPlays中找到的数字矢量的数据,它们可以与pitchNumber位于同一级别。

我要从URL中获取hitData号565711,并使用以下代码将其添加到数据框中:

game_pk

我是R的新手,我想使用hitDataDF$game_pk = str_match(url, '([^/]+)(?:/[^/]+){1}$')[,2] %>%编写代码。这是我的第一次尝试,不确定我是否完全理解此方法。如果您有解决方案,请尝试解释一下,以便我可以更好地了解正在发生的事情,并希望在提取相似数据的情况下将其应用于其他代码?

非常感谢您的帮助!

谢谢!

2 个答案:

答案 0 :(得分:2)

在使用magrittr管道和地图功能的第一步中,您选择了一个具有挑战性的问题!我会尽力为您提供有用的答案,但我也建议您在练习时找到一些更容易使用的数据。 Hadley Wickham的书中的"Pipes"一章是了解管道%>%的好地方。 iteration的章节还对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的第一次调用从列表hitData的每个元素中提取playEventshitData数据帧是嵌套的(它们包含其他数据帧),因此使用mapjsonlite::flatten的第二次调用将其展平。函数safely确保在遇到数据帧以外的内容时,R不会引发错误(仅46个元素包含hitData)。许多hitData数据帧都包含充满NA的行,因此对map的第三次调用使用匿名函数(同样在safely中)来消除这些行。然后,对map的第四次调用从每个元素的result变量中提取数据帧,该变量是由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] 函数从URL中获取stringr。我不确定它是否适用于每个URL,但是在这种情况下可以正常工作:

game_pk

最后,我将game_pk_vec <- str_match(url, "/(\\d+)/")[2] %>% as.integer() atBatIndex合并为小标题,然后使用game_pk将该小标题与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+$")) 。调用pitchNumber会返回序列1到 n 的列表,其中每个向量的长度均大于1。我假设您只需要每个序列中的最终数字,但是我不确定,所以我会尽力而为。您可以执行与jsonData$allPlays$playEvents %>% map("pitchNumber")相同的操作来获取相关元素,然后提取所需的内容。这是最终的数据框:

atBatIndex

答案 1 :(得分:-1)

尝试一点点“取消列出”。我设法获得了一个无名的数据框-从列表中删除名称似乎很复杂。希望这会有所帮助:

hitData = jsonData %>%
      map("playEvents") %>%
      map("hitData") %>%
      unlist(recursive = F)

numRows = lapply(hitData,length) %>% unique %>% unlist

hitDataFrame = unlist(hitData) %>% matrix(nrow = numRows) %>% as.data.frame