CoderCastrov logo
CoderCastrov
Анализ данных

Парсинг и исследование SP500 с помощью R Часть 1

Парсинг и исследование SP500 с помощью R Часть 1
просмотров
9 мин чтение
#Анализ данных
Table Of Content

Парсинг, получение данных и функциональное программирование

Сегодня я хотел бы рассмотреть быстрый пример, объединяющий парсинг, вызовы API Yahoo Finance, объединение данных и простой анализ активов с использованием концепций функционального программирования и итерации в стиле tidy.

Конечная цель:

Я хотел бы определить, у каких активов SP500 был самый высокий средний доход за последние 3 месяца.

Окончательный результат этого анализа - это этот график:

Создание этого графика, анализ активов и ответ на мой вопрос будут рассмотрены в второй части этой серии.


Введение и Tidyverse:

В сегодняшнем анализе я буду использовать язык программирования R. Если вы читали мои посты на Linkedin или Medium ранее, то, возможно, заметили, что я обычно программирую на языке Python. В целом, я предпочитаю язык программирования Python, потому что у него более простой синтаксис, широкое распространение и его легче использовать в производстве.

Экосистема R Tidyverse:

В языке программирования R существует набор пакетов, которые составляют так называемый tidyverse. Эти пакеты в основном поддерживаются инженерами и специалистами по обработке данных в Rstudio и предоставляют простой, интегрированный и унифицированный способ обработки данных в R. Tidyverse основан на идее tidy data, термин, придуманный Хэдли Уикхэмом, чтобы описать данные, где:

  • каждая переменная представлена столбцом
  • каждое наблюдение (или случай) представлено строкой

Сегодня я буду использовать несколько библиотек tidyverse. В следующем фрагменте я импортирую библиотеки, которые буду использовать.

# для парсинга
library(rvest)
# импорт основных пакетов tidyverse
library(tidyverse)
# аккуратный финансовый анализ
library(tidyquant)
# функции для очистки данных
library(janitor)

Следующее, что я сделаю, это определю переменную с текущей датой. Затем я вычту 3 месяца из текущей даты. Это возвращает другой объект даты, указывающий, какой день был 3 месяца назад. Мне это понадобится, потому что я хочу получить данные о ценах за последние 3 месяца для каждого тикера.

# сохранить текущую системную дату в переменную
today <- Sys.Date()
# вычесть 3 месяца из текущей даты
date = today %m+% months(-3)
print(date)

Один тикер

Я собираюсь использовать пакет tidyquant, чтобы получить финансовые данные для всех тикеров SP500. Основная функция пакета tidyquant - tq_get(), которую можно использовать для получения различной информации о акциях. Если я передаю строку, содержащую название тикера, в функцию tq_get(), она вернет данные об открытии, максимуме, минимуме, закрытии или OHLC. Я передаю ^GSPC, тикер SP500, в функцию tq_get().

# передаем тикер SP500 ^GSPC в функцию tq_get
one_ticker = tq_get("^GSPC", from = date)
one_ticker %>% 
  head()

Есть несколько вещей, которые следует отметить выше:

  • tq_get() возвращает аккуратный фрейм данных
  • название тикера отсутствует в фрейме данных
  • оператор %>% называется "pipe". Он передает объект, предшествующий ему, в качестве первого аргумента функции, следующей за ним.

Масштабирование часть 1 — Получение вектора всех 505 тикеров:

Я хочу получить данные OHLC для всех тикеров SP500. Для этого мне нужно сделать несколько вещей:

  • Создать вектор всех тикеров SP500
  • Пройти по этому вектору и вызвать tq_get() для каждого элемента вектора, возвращая dataframe для каждого элемента вектора
  • объединить все эти dataframe в один dataframe

Вау! Звучит немного сложно, не так ли? К счастью, с помощью R это будет довольно просто. На странице Wikipedia есть таблица со всеми 505 тикерами SP500 (некоторые компании, такие как Google, имеют несколько классов активов), которая находится по следующему URL-адресу:

https://en.wikipedia.org/wiki/List_of_S%26P_500_companies

Чтобы получить все тикеры SP500, мы собираемся спарсить эту таблицу, используя пакет rvest. Пакет rvest - это простой парсер в R, очень похожий на beautiful soup в Python. В контексте программирования парсинг определяется как программное сбор читаемого человеком контента из интернета и веб-страниц.

В приведенном ниже коде я парсю таблицу Wikipedia и создаю вектор всех тикеров SP500:

  • Сначала я присваиваю URL-адрес Wikipedia переменной
  • Читаю html с URL-адреса
  • Выбираю правильные html узлы и извлекаю html таблицу
  • Вношу небольшие изменения в названия тикеров, потому что Yahoo Finance использует символ «_» вместо «.» для некоторых названий символов

Самая сложная часть парсинга - определение xpath или css, указывающих, какие html узлы выбрать. Я не очень хорошо разбираюсь в html или css, но, используя Google Chrome, я смог найти правильный xpath (подробнее об этом ниже).

# получить URL для страницы Wikipedia со всеми символами SP500
url <- "[https://en.wikipedia.org/wiki/List_of_S%26P_500_companies](https://en.wikipedia.org/wiki/List_of_S%26P_500_companies)"
# использовать этот URL для парсинга таблицы SP500 с помощью rvest
tickers <- url %>%
  # прочитать HTML с веб-страницы
  read_html() %>%
  # один из способов получить таблицу
  #html_nodes(xpath='//*[[@id](http://twitter.com/id)="mw-content-text"]/div/table[1]') %>%
  # более простой способ получить таблицу
  html_nodes(xpath = '//*[[@id](http://twitter.com/id)="constituents"]') %>% 
  html_table()
# создать вектор тикеров
sp500tickers <- tickers[[1]]
sp500tickers = sp500tickers %>% mutate(Symbol = case_when(Symbol == "BRK.B" ~ "BRK-B",
                                           Symbol == "BF.B" ~ "BF-B",
                                            TRUE ~ as.character(Symbol)))

Как найти xpath, который я передал в функцию html_nodes, используя Google Chrome:

Step 1
  • Большая часть содержимого веб-страницы обычно находится в теле html-документа. Мы раскроем этот раздел. Это будет выглядеть примерно так:
Step 2
  • Изучив веб-страницу, я вижу, что таблица, которую я хочу, находится прямо под первым заголовком h2:
Step 3
  • После навигации по структуре страницы я нашел первый заголовок h2 и таблицу, которую я хотел ниже него
Step 4
  • Я могу щелкнуть на таблице, щелкнуть правой кнопкой мыши и скопировать xpath, который нужен для парсинга таблицы
Step 5
  • xpath = //*[@id=”constituents”], это то, что было передано в html_nodes
html_nodes(xpath = '//*[[@id](http://twitter.com/id)="constituents"]'

Масштабирование часть 2 - Purrr, итерация и функциональное программирование:

Итерация означает программное повторение шага или набора шагов определенное количество раз или до выполнения условия.

Обычно, когда мы выполняем итерацию на любом языке программирования, мы используем цикл, обычно цикл for. Мне нужно выполнить итерацию по каждому элементу вектора тикеров SP500 и передать его в функцию tq_get(). Я мог бы сделать это с помощью цикла for, но использование пакета purrr - это лучшая идея. Циклы в R медленные и сложно читаемые. Пакет purrr предоставляет набор функций для итерации и функционального программирования, которые хорошо интегрируются с остальной частью tidyverse. Основная функция в purrr - это map(). Большинство языков программирования (включая мой любимый язык Python) имеют функцию map, которая применяет функцию ко всем элементам объекта.

Функциональное программирование - это парадигма программирования, в которой функции, в отличие от классов, создают структуру и логику программ. Циклы for обычно избегаются в функциональном программировании. Вместо этого функции отображаются или применяются к спискам или другим объектам.

Как отличаются итерация в purrr и цикл for, можно увидеть на примере. Оба этих операции примерно эквивалентны:

# получить последовательность чисел от 1 до 5
numbers = seq(1:5)
print('цикл for')# цикл for
for (i in numbers){
  
  print(i)
}print('purrr :)')# функциональное программирование с purrr
list = map(numbers, print)

Несколько важных моментов:

  • как цикл for, так и функция map выполняют операцию для каждого элемента вектора
  • функция map возвращает вложенный список, где каждая запись является результатом вызова функции внутри нее для одной из записей объекта, по которому выполняется итерация. Я присваиваю это переменной list, чтобы избежать вывода на печать.
  • Использование функции map из пакета purrr занимает всего одну строку и меньше кода
  • Использование функции map из пакета purrr легче читать

Хорошо, теперь давайте продолжим, чтобы получить все 505 тикеров SP500!

Сначала мне нужно написать функцию для итерации или применения к каждому элементу вектора. Я не могу просто использовать tq_get() с map(), потому что он не возвращает имя тикера в качестве столбца фрейма данных. Чтобы получить тикер вместе с фреймом данных, я буду использовать функцию mutate() из пакета dplyr для создания нового столбца с именем тикера.

get_symbols = function(ticker = "AAPL"){
  df = tq_get(ticker, from = date) %>% mutate(symbol = rep(ticker, length(date)))
}

Затем я использую эту функцию вместе с map() для итерации по списку всех символов. Это возвращает вложенный список, содержащий фрейм данных для каждого тикера. Я использую функцию bind_rows() из пакета dplyr для объединения фреймов данных по строкам и создания одного фрейма данных со всеми тикерами SP500.

# создаем фрейм данных SP500, выполняя итерацию по списку символов и вызывая нашу функцию get_symbols каждый раз
# функция map выполняет это задание
tickers_df = map(symbols, get_symbols) %>% bind_rows()

Я также хочу, чтобы этот фрейм данных содержал информацию из таблицы википедии, в основном название компании. Это можно сделать, объединив два фрейма данных по символу.

tickers_df = tickers_df %>% 
  # объединяем с данными из википедии
  left_join(sp500tickers, by = c('symbol' = 'Symbol')) %>% 
  # приводим имена к формату R
  clean_names() %>% 
  # оставляем только нужные столбцы
  select(date:security, gics_sector, gics_sub_industry)

После объединения данных мы должны быстро проверить, чтобы убедиться, что у нас есть все 505 тикеров SP500.

tickers_df %>% 
# выбираем только столбец с символами
select(symbol)%>% 
# получаем уникальные значения
distinct()%>% 
# считаем количество уникальных значений
count() %>% 
# используем select для переименования столбцов
select("Общее количество тикеров" = n)

Наконец, мы можем проверить первые несколько строк фрейма данных, чтобы убедиться, что мы получили нужные данные:

tickers_df %>% 
  head()

Отлично! Все как ожидалось.

Заключение:

В этой первой статье мы:

  • Узнали основы экосистемы tidyverse и tidyquant
  • Узнали основы итерации и функционального программирования в tidyverse
  • Узнали, как использовать rvest и chrome для парсинга данных с википедии
  • Узнали, как перейти от чтения одного актива к всему SP500

В следующей статье:

Я отвечу на свой первоначальный вопрос:

Какие активы SP500 имели самую высокую среднюю доходность за последние 3 месяца?

Несколько замечаний:

  • Вы можете получить список всех акций SP500 намного проще с помощью tq_index("SP500"). Для этой функции требуется библиотека XLConnect. В настоящее время у меня возникают проблемы с импортом и запуском этой библиотеки на моей локальной машине.
  • tq_get() на самом деле принимает списки тикеров, поэтому использование map() необязательно
  • Я специально написал код таким образом, чтобы продемонстрировать описанные концепции

Отказ от ответственности: В университете я делал проект с данными SP500 на языке Python. Распространение этого проекта запрещено политикой университета. Хотя то, что я делаю здесь, в некотором смысле похоже на то, что было сделано ранее, я использую совершенно другой язык программирования и отвечаю на совершенно другой вопрос. Учитывая мои предыдущие заявления, я утверждаю, что это не нарушение политики того курса. Я специально использовал язык программирования R вместо Python, чтобы полностью избежать любых проблем и уважить пожелания моего бывшего преподавателя.

Код можно найти здесь

Вот еще один отличный источник о парсинге, который немного помог при написании статьи.

https://medium.com/@kyleake/wikipedia-data-scraping-with-r-rvest-in-action-3c419db9af2d


Примечание от редакторов Towards Data Science:_ Хотя мы разрешаем независимым авторам публиковать статьи в соответствии с нашими правилами и руководствами, мы не поддерживаем вклад каждого автора. Вы не должны полагаться на работы автора без поиска профессиональной консультации. См. наши Условия для читателей для получения дополнительной информации._