Вы выбрали сложную задачу для своих первых шагов с помощью трубы magrittr и функций карты! Я сделаю все возможное, чтобы дать вам полезный ответ, но я бы также порекомендовал вам найти более простые данные для работы во время практики. Хорошее место, чтобы узнать о трубе %>%
- глава "Трубы" в книге Хэдли Уикхем. Глава о итерации также предлагает хорошее введение в функции map_*
. Вы можете вернуться к более сложным проблемам, если у вас есть более четкое концептуальное понимание. Я думаю, что Хэдли объясняет эти инструменты лучше, чем я когда-либо мог, поэтому я не буду вдаваться в подробности о них здесь, а вместо этого сосредоточусь на объяснении, почему ваш код не работает и почему мой.
Анализ вашего кода
Функции карты допускают несколько полезных ярлыков, один из которых вы уже обнаружили - а именно, если вы передаете векторы или списки в качестве аргумента функции, они автоматически преобразуются в функции экстрактора . Итак, вы на правильном пути!
Следует помнить, что функции карты возвращают вектор такой же длины и с теми же именами, что и входной вектор. Ваш входной вектор 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
из каждого элемента списка playEvents
. Фреймы данных hitData
являются вложенными (они содержат другие кадры данных), поэтому второй вызов map
с jsonlite::flatten
сглаживает их. Функция safely
гарантирует, что R не выдаст ошибку при обнаружении чего-либо, кроме фрейма данных (только 46 элементов содержат hitData
). Многие из hitData
фреймов данных содержат строки, заполненные NA
s, поэтому третий вызов 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
фреймов данных. Как я упоминал выше, только 46 из 80 записей содержат 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
в таблицу, затем объединяю эту таблицу с данными 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
.Вызов 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