Я пытаюсь очистить ежедневный прогноз от Пять тридцать восемь с помощью rvest, но мой интересующий объект, похоже, является объектом javascript, который мне даже трудно найти, где и что искать. (Я плохо разбираюсь в CSS или Javascript, хотя последние пару дней пытался самообразоваться.)
Изучив элемент веб-страницы и селектор CSS, я выяснил следующее:
Место для поиска - <div id = "polling-avg-chart">, поэтому я попробовал
library(rvest)
url <-
"https://projects.fivethirtyeight.com/election-2016/national-primary-polls/democratic/"
url %>%
read_html() %>%
html_nodes("#polling-avg-chart")
без особого успеха. Вывод просто
{xml_nodeset (1)}
[1] <\div id = "polling-avg-chart"></div>\n
Результаты отдельных опросов в точках находятся в <g style = "clip-path: url("#line-clippoll_avg");"> ... </g>, где вы видите 502 местоположения в цифрах. Я предполагаю, что мне придется перевести cx и cy каждого узла в соответствующие проценты, что делает <g class = "flag-box" transform = "translate(30, 161.44093322753096)">...</g> и так далее.
Однако я не вижу базовых данных для линии прогноза, не точек.
<line class = "hover-date-line hide-line">, и такие значения, как изменение <path class = "link" d = "M 0 171.40106812500002 C 15 171.40106812500002 15 170.94093803735575 30 170.94093803735575"></path>, и я предполагаю, что именно эти значения создают линию суточного прогноза.Я прочитал несколько других сообщений SO, таких как это, но ни один из них не казался применимым к этой конкретной проблеме. Как лучше всего получить прогнозируемые проценты в аккуратном фрейме данных?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Там диаграмма почти наверняка построена с использованием d3.js или оболочки поверх него. d3 отлично подходит для создания визуализаций данных на основе svg, потому что он помогает вам создавать шкалы для сопоставления значений (например, 40%) с местами размещения на экране (например, то, что вы видите, что-то вроде cx=100). Проблема в том, что вам нужно знать, что это за шкалы, чтобы вернуть базовые данные, и шкалы, вероятно, будут динамическими и изменяются в зависимости от размера экрана и т. д.
Вместо этого, поскольку данные находятся в таблице ниже, вы можете легко очистить их. Таблица находится внутри элемента div с идентификатором latest-polls и имеет класс t-polls.
Я использую html_node с селекторами CSS, html_table для преобразования таблицы в фрейм данных, очистки имен и превращения числовых столбцов в фактические числовые столбцы. Далее вы можете сделать еще кое-что, например отформатировать даты, но, надеюсь, это поможет вам начать.
library(tidyverse)
library(rvest)
url <- "https://projects.fivethirtyeight.com/election-2016/national-primary-polls/democratic/"
polls_df <- url %>%
read_html() %>%
html_node("#latest-polls table.t-polls") %>%
html_table() %>%
setNames(c("new", "date", "pollster", "sample_n", "sample_type", names(.)[6:10]) %>% str_remove_all("\\W")) %>%
mutate_at(vars(sample_n, Clinton, Sanders, OMalley),
function(x) str_remove_all(x, "\\D") %>% as.numeric())
head(polls_df)
#> new date pollster sample_n sample_type
#> 1 • Jun. 10-13 Selzer & Co. 486 LV
#> 2 • Jun. 26-28 Fox News 432 RV
#> 3 • Jun. 18-20 YouGov 390 LV
#> 4 • Jun. 15-20 Morning Consult 1733 RV
#> 5 • Jun. 27-Jul. 1 Ipsos, online 142 LV
#> 6 • Jun. 16-19 Opinion Research Corporation 435 RV
#> weight leader Clinton Sanders OMalley
#> 1 1.05 Clinton +2 45 43 NA
#> 2 0.91 Clinton +21 58 37 NA
#> 3 0.79 Clinton +13 55 42 NA
#> 4 0.79 Clinton +18 53 35 NA
#> 5 0.67 Clinton +41 70 29 NA
#> 6 0.66 Clinton +12 55 43 NA
Другой способ - получить ресурс напрямую.
В браузере откройте Инструменты разработчика (F12 в Chrome / Chromium), перейдите в «Сеть», обновите (F5) и найдите то, что выглядит как красиво отформатированный JSON. Найдя его, мы копируем адрес ссылки (щелкните правой кнопкой мыши ресурс> Копировать адрес ссылки).
library(httr)
library(tidyr)
library(purrr)
library(dplyr)
library(ggplot2)
url <- "https://projects.fivethirtyeight.com/election-2016/national-primary-polls/USA.json"
r <- GET(url)
Там есть все данные. Веса тоже, так что вы, вероятно, можете пересчитать эти средние значения. Данные, представленные на графике, находятся в "model":
dat <-
jsonlite::fromJSON(content(r, as = "text")) %>%
map(purrr::pluck, "model") %>%
bind_rows(.id = "party") %>%
mutate_all(readr::parse_guess)
# # A tibble: 5,288 x 5
# party candidate_name state forecastdate poll_avg
# <chr> <chr> <chr> <date> <dbl>
# 1 D Sanders USA 2016-07-01 36.5
# 2 D Clinton USA 2016-07-01 55.4
# 3 D Sanders USA 2016-06-30 37.0
# 4 D Clinton USA 2016-06-30 54.6
# 5 D Sanders USA 2016-06-29 37.0
# 6 D Clinton USA 2016-06-29 54.9
# 7 D Sanders USA 2016-06-28 37.2
# 8 D Clinton USA 2016-06-28 54.4
# 9 D Sanders USA 2016-06-27 37.4
# 10 D Clinton USA 2016-06-27 53.9
# # ... with 5,278 more rows
Воспроизвести графики:
dat %>%
filter(candidate_name %in% c("Clinton", "Kasich", "Sanders", "Trump")) %>%
ggplot(aes(forecastdate, poll_avg)) +
geom_line(aes(col = candidate_name)) +
facet_wrap(~party)
Если вы хотите интерактивности:
library(dygraphs)
library(htmltools)
foo <- dat %>%
filter(candidate_name %in% c("Clinton", "Kasich", "Sanders", "Trump")) %>%
split(.$party) %>%
map(~ {
select(.x, forecastdate, candidate_name, poll_avg) %>%
spread(candidate_name, poll_avg) %>%
{xts(.[-1], .[[1]])} %>%
dygraph(group = "poll-model") %>%
dyRangeSelector()
})
browsable(tagList(foo))
Отлично! Спасибо, это палочка-выручалочка. Только одна вещь, кажется, я получаю Error in bind_rows_(x, .id) : Argument 1 is a list, must contain atomic vectors в части bind_rows второго блока кода.
@Kim Какой у тебя packageVersion("dplyr")? Пользуюсь 0.7.4.
Хм, тот же 0.7.4. На самом деле это что-то с функцией map, потому что до третьей строки, до bind_rows, dat заканчивается как пустой список (NULL) --- этого не должно быть, верно? Версия purrr - 0.2.4. Я имею в виду, что могу обойти это, но мне просто интересно, почему я получаю другой результат.
(та же версия). Что дает x <- jsonlite::fromJSON(content(r, as = "text")) ; names(x) ; names(x$D)? А с map(`[[`, "model")?
names(x) дает [1] "D" "R", names(x$D) дает [1] "model" "polls" "distributions". Последний map( [[, "model") работал. Интересно, почему.
Ха! В pluck есть rvest. Вы случайно не загрузили rvest после purrr? А как насчет map(purrr::pluck, "model")?
Ах, действительно, это был виноват. В любом случае, спасибо за отличный ответ. С этого момента я смогу применять тот же принцип.
Понятно ... как жаль. Дело в том, что FiveThirtyEight использует свою собственную модель для оценки прогноза по этим точкам данных, что не является простым средним значением данных. Я уже соскребал базовые данные опросов, хотя ваше решение намного элегантнее.