Текст
                    Сбор данных
в Интернете
на языке R

Дмитрий Храмов Сбор данных в Интернете на языке R лто in Москва, 2017
УДК 004.738.5:004.438R ББК 32.971.353 Х89 Храмов Д. Л. Х89 Сбор данных в Интернете на языке R. - М.: ДМК Пресс, 2017. - 280 с.: ил. ISBN 978-5-97060-459-5 Всё, что регистрирует человек и созданные им машины, может считаться данными. Фиксируя новое и переводя архивы в цифро- вую форму, мы с каждым днём производим всё больше данных. Но гораздо чаше случается так, что данные разбросаны по всемирной сети на многочисленных страницах онлайновых магазинов, замет- ках в социальных сетях, логах серверов и т. п. Прежде чем начать работать с такими данными, их необходимо собрать и сохранить в пригодном для анализа виде. Решению этих вопросов и посвящена данная книга. Основной материал книги разделён на две части. В первой части дано краткое введение в R - описание среды разработки, языка и основных пакетов-расширений. Вторая часть посвящена непосред- ственно сбору данных: работе с открытыми данными, извлечению данных из веб-страниц и из социальных сетей. Также рассмотре- ны необходимые технические вопросы: протокол HTTP, функции импорта данных различных форматов и регулярные выражения. Завершается рассказ созданием карт на основе собранных данных. Издание предназначено специалистам по анализу данных, а так- же программистам, интересующихся сбором данных в Интернете. УДК 004.738.5:004.438R ББК 32.971.353 ISBN 978-5-97060-459-5 © Храмов Д.А., 2016 © Оформление, издание, ДМК Пресс, 2017
Содержание Введение.................................................. 11 Кто и зачем собирает данные............................... 11 Почему R?................................................. 12 Как устроена эта книга.................................... 13 Обратная связь............................................ 13 ЧАСТЫ. ПРОГРАММИРОВАНИЕ НА R.............................. 14 Глава 1. Знакомство с R................................... 15 Установка................................................. 15 Работа в среде RGui....................................... 17 Справка................................................... 22 Глава 2. Скаляры, векторы и матрицы....................... 24 Арифметические операции и присваивание.................... 24 Имена..................................................... 25 Простые типы данных....................................... 26 Числа................................................... 26 Символьный тип.......................................... 28 Логический тип.......................................... 30 Векторы................................................... 31 Векторизация и логическая индексация...................... 36 Матрицы и массивы......................................... 39 Резюме.................................................... 41 Глава 3. Списки и таблицы................................. 42 Списки.................................................... 42 Таблицы................................................... 45 Функции, применяемые к составным данным................... 50 apply................................................... 50 lapply.................................................. 51 sapply.................................................. 52 do.call................................................. 53 Резюме.................................................... 53 Глава 4. Управление процессом вычислений.................. 54 Циклы..................................................... 54 Цикл со счётчиком....................................... 54 Цикл с предусловием..................................... 57 Условные операторы........................................ 58 Резюме.................................................... 59
4 Содержание Глава 5. Базовая графика.................................... 60 Функции низкого и высокого уровней.......................... 60 Глобальные и локальные параметры графиков................... 65 Легенда..................................................... 67 Комбинации графиков......................................... 67 Графики функций............................................. 69 Экспорт в файлы............................................. 70 Резюме и ссылки............................................. 70 Глава 6. Функции............................................ 72 Создание функций............................................ 72 Локальные и глобальные переменные. Области видимости........ 74 Диагностические сообщения................................... 76 Функции в качестве аргументов............................... 76 Функциональное программирование............................. 78 Резюме...................................................... 79 Глава 7. Факторы и даты..................................... 80 Категориальные данные....................................... 80 Дата и время................................................ 83 Резюме...................................................... 86 Глава 8. Пакеты............................................. 87 Установка и загрузка........................................ 87 Выбор пакета................................................ 89 Справка и её разновидности.................................. 89 Как самому создать пакет R?................................. 91 Пакет magrittr: конвейер операций........................... 92 Глава 9. Ввод и вывод данных. Работа с файлами.............. 94 Рабочий каталог пользователя................................ 94 Запись данных в стандартное устройство вывода............... 94 Запись в текстовые файлы.................................... 95 Таблицы................................................... 95 Строки.................................................... 97 Матрицы................................................... 97 Чтение из текстовых файлов.................................. 97 Элементы данных: scan..................................... 97 Строки: read Lines........................................ 99 Таблицы...................................................100
Содержание 5 Работа с данными в бинарном формате........................101 Управление файлами и каталогами............................102 Взаимодействие с базами данных.............................103 DBI + RSQLite...........................................103 sqldf...................................................103 Резюме.....................................................104 Ссылки к части I...........................................105 ЧАСТЬ II. СБОР ДАННЫХ......................................106 Глава 10. Открытые данные..................................107 Что это такое?.............................................107 Данные Всемирного банка....................................108 Где взять данные?..........................................113 Резюме.....................................................114 Глава 11. Протокол HTTP....................................115 Основные понятия...........................................115 Запрос.....................................................116 Ответ......................................................117 Коды состояния.............................................118 Передача параметров........................................119 HTTP в R...................................................120 Пакет httr..............................................120 Пакет RCurl.............................................122 Кириллица и кодирование URL................................123 Пример: геокодирование с помощью Google Maps Geocoding.....124 Пример: доступ к API портала открытых данных РФ............126 Ссылки.....................................................129 Глава 12. Импорт данных....................................130 Чтение файлов..............................................130 Скачивание.................................................131 Excel......................................................132 JSON.......................................................133 Пример: какой из JSON-пакетов самый популярный?............133 Google Spreadsheets........................................136 Архивы.....................................................137 Завершающий штрих: проверка типа данных....................138 Ссылки.....................................................139
Б Содержание Глава 13. Веб-скрапинг......................................140 Используйте структуру данных................................140 Элементы HTML и CSS.........................................143 div и span................................................143 Классы и идентификаторы...................................144 Путь к элементу.............................................146 XPath.....................................................146 CSS.......................................................149 Как найти путь к элементу при помощи браузера...............150 Проверка и упрощение пути. Копсоль разработчика.............153 Резюме......................................................155 Лирическое отступление: построение графов...................155 Ссылки......................................................157 Поиск в Интернете.........................................157 HTMLhCSS:.................................................158 XPath.....................................................158 Глава 14. Пакет rvest.......................................159 Пакеты для веб-скрапинга....................................159 Получение и обработка HTML-доку мента.......................160 Поиск элемента..............................................162 Разбор элемента.............................................164 Пример: получаем ссылку и скачиваем файл..................165 Таблицы.....................................................166 Пример: извлечение таблицы из Википедии...................166 Пример: разбор страницы сериала «Светлячок».................167 Пример: извлечение данных об инвестиционных фондах..........169 Работа с формами. Сессии....................................171 Пример: аутентификация на форуме..........................173 Функции навигации...........................................174 Работа с кодировками........................................175 Заключительные замечания и ссылки...........................175 Глава 15. RSelenium: управляем браузером....................177 Пример: перевод с помощью Yandex.Translate..................179 Пример: динамически генерируемая ссылка на файл.............180 Selenium и браузеры.........................................183 Резюме и ссылки.............................................183
Содержание ❖ 7 Глава 16. PhantomJS и обработка динамических веб-страниц....185 Динамические страницы: описание проблемы....................185 Установка...................................................186 Запуск......................................................186 Пример: рендеринг веб-страницы..............................187 Сохранение веб-страницы в файл..............................188 Резюме и ссылки.............................................190 Глава 17. Facebook..........................................192 Протокол авторизации OAuth 2.0..............................192 Получение маркера доступа пользователя ЛР1 Graph............193 Доступ к данным с помощью rvest и jsonlite..................196 Пакет Rfacebook и создание приложения.......................198 Глава 18. Сбор информации с помощью API ВКонтакте...........204 Создание приложения.........................................204 Регистрация приложения....................................204 Получение кода доступа....................................206 Получение данных............................................207 Реализация в R............................................208 Построение графа связей.....................................210 Получение другой информации из сети.........................212 Поиск пользователя........................................213 Ограничения.................................................214 Глава 19. Использование Twitter API.........................215 Получение доступа к Twitter API.............................215 Подключение к Twitter из R..................................215 Поиск и сохранение его результатов в базе данных............217 Фильтрация результатов поиска...............................218 Построение облака слов......................................219 Данные для анализа........................................220 Лексический корпус и терм-документная матрица.............220 Ключевые слова и их частоты...............................221 Облако слов...............................................221 Ограничения Search API......................................223 Streaming API...............................................223 Ссылки......................................................223
8 Содержание Глава 20. Регулярные выражения............................225 Символы и метасимволы.....................................225 Квантификаторы............................................227 Положение образца внутри строки...........................228 Операторы.................................................229 «Жадность» и «лень» квантификаторов.......................230 Классы символов...........................................232 Заключительные замечания..................................233 Ссылки....................................................234 Глава 21. Создание карт на основе собранных данных........235 Интерактивные карты в leaflet.............................235 Переходим к созданию карты................................239 Извлечение адресов и названий магазинов...................240 Геокодирование............................................242 Отображение на карте......................................243 Работа с шейп-файлами.....................................244 Ссылки....................................................247 Ссылки к части II.........................................249 Приложение А. Среда разработки RStudio ...................250 Создание скрипта..........................................251 Лвтодополнение имён объектов..............................252 Выполнение................................................252 Рабочее пространство......................................253 История команд............................................254 Сохранение файлов.........................................256 Кодировки файлов..........................................256 Управление файлами в рабочем каталоге.....................257 Управление пакетами.......................................257 Поиск и замена............................................258 Автоматическое создание функций...........................259 Комментирование...........................................260 Переход к определению функции.............................260 Ссылки....................................................261 Приложение Б. Языки поисковых запросов Google и Яндекс.....262 Почему важно уметь пользоваться ЯПЗ.......................263 Предотвращение перегрузок сервиса.........................263
Содержание 9 Приложение В. Введение в HTML и CSS.....................264 Веб-страница............................................264 Гиперссылки.............................................266 Шрифт...................................................267 Цвет....................................................268 Стиль...................................................268 Выравнивание............................................270 Рисунки.................................................270 Списки..................................................271 Маркированные.........................................271 Нумерованные..........................................271 Вложенные.............................................272 Таблицы.................................................272 Ссылки..................................................273 Приложение Г. Регулярные выражения......................274 Предметный указатель....................................276

Введение Всё, что регистрирует человек и созданные им машины, может считаться данными. Фиксируя новое и переводя архивы в цифровую форму, мы с каж- дым днём производим всё больше данных. Часть из них собрана в специ- альных хранилищах. Например, UN Comtrade содержит официальную ста- тистику по международной торговле. Использовать такие данные довольно легко, достаточно лишь получить доступ к их хранилищу. Но гораздо чаще случается так, что данные разбросаны по всему Интер- нету на многочисленных страницах онлайновых магазинов, заметках в со- циальных сетях, логах серверов и т. п. Прежде чем начать работать с такими данными, их необходимо собрать и сохранить в пригодном для анализа виде. Решению этих вопросов и посвящена данная книга. Кто и зачем собирает данные Круг специалистов, работа которых так или иначе связана со сбором данных, весьма обширен. Судите сами. Сбор данных является предварительным этапом в data mining - «интел- лектуальном анализе данных» или же «добыче данных». Data mining можно трактовать весьма широко - как анализ данных вообще, и более узко, как обнаружение скрытых закономерностей в (больших объемах) данных. Весьма близко к data mining находится машинное обучение (machine lear- ning). Обе дисциплины пользуются одними и теми же методами, так что чёт- кой границы между ними провести нельзя. Несколько упрощая, можно ска- зать, что data mining больше интересуется получением закономерностей, а машинное обучение - их использованием. Пользуясь собранными данны- ми, машинное обучение решает задачи классификации (например, фильтра- ции спама или выработки рекомендаций на основе мнений пользователей со сходными запросами), прогнозирования, кластеризации (например, сегмен- тации рынка), выявления аномальных наблюдений и т. п. Сбор информации о конкурентах, например мониторинг их ценовых пред- ложений, является одной из задач бизнес-аналипшки. Сходные задачи, но не- сколько в другом контексте, решает open source intelligence - разведка по от- крытым источникам данных, которая отвечает за поиск, сбор и анализ ин- формации из общедоступных источников. Ещё одна близкая область дея- тельности - business intelligence. Несмотря на наличие «intelligence» (англ. ’’разведка”) в названии, она предполагает не столько слежение за конкурен- тами, сколько за бизнес-процессами в собственной организации. В отличие от предыдущих дисциплин, где результатами анализа данных пользовались лишь отдельные организации, новое направление в журналис- тике - журналистика данных (data-driven journalism) - проводит свои ис-
12 Содержание следования в хранилищах данных для информирования публики. Одним из крупнейших поставщиков общедоступной информации явля- ются социальные сети. В них люди размещают свои анкетные данные, де- лятся новостями, личными фотографиями, вкусами (лайкая что-нибудь или вступая в какую-либо группу), кругом своих знакомств. Причём всё это де- лается по доброй воле, подчас не задумываясь о возможных последствиях. Поэтому данные из социальных сетей, добываемые с помощью social media mining, активно используются для проведения социологических и маркетин- говых исследований. Собранные сведения, имеющие географическую привязку, могут приго- диться в геоинформатике. Если раньше основными источниками данных для геоинформационныхсистем (ГИС) являлись наземная съёмка и результаты дистанционного зондирования Земли, то теперь к ним присоединилась ин- формация, поставляемая многочисленными «людьми-датчиками», отправ- ляющими со своих смартфонов фотографии в Instagram, оставляющих за- метки в Facebook, твиты в Twitter и т. п. К специалистам указанных выше профессий нужно добавить ещё специа- листов будущих - студентов, добывающих информацию для своих курсовых и дипломных работ. Всем им, а также просто любопытствующим эта книга может оказаться полезной. Почему R? Заниматься сбором данных можно и на чистом С. В конце концов, большин- ство библиотек, использующихся при сборе данных, написано именно на этом языке. В качестве примера назовём библиотеки libcurl и libxml2. Но... Если от языка программирования вам хочется большего «дружелюбия», то можно использовать Python. В нём существует множество пакетов, предназначенных как для сбора, так и для анализа данных. Поэтому, рассмат- ривая ту или иную задачу, мы будем ещё не раз упоминать о Python - как об альтернативном инструменте для её решения. Но использовать мы будем R. Если Python - это всё-таки язык общего назначения, снабжённый нужными библиотеками, благодаря чему он спо- собен трансформироваться в инструмент для анализа данных, то пакет R в буквальном смысле слова создан статистиками и для статистиков. Поэтому задачи сбора данных в этом языке реализуются наиболее прямолинейно, что позволяет быстрее добиваться результатов, не отвлекаясь на тонкости про- граммирования. Но и после того, как данные собраны, пакет R сопровождает пользовате- ля на протяжении всего цикла их анализа - от предварительной обработки данных до получения окончательных результатов и представления их на гра-
Как устроена эта книга 13 фиках - вплоть до оформления текстов статей полиграфического качества. Кстати, эти строки также набраны в R. Как устроена эта книга Основной материал книги разделён на две части. В первой части (к ней от- носятся главы 1-9) дано краткое введение в R - описание среды разработки, языка и основных пакетов-расширений. Вторая часть(главы 10-21) посвящена непосредственно сбору данных: ра- боте с открытыми данными (глава 10), извлечению данных из веб-страниц (главы 13-16) и из социальных сетей (главы 17-19). Главы 11, 12 и 20 рас- сматривают необходимые технические вопросы: протокол HTTP, функции импорта данных различных форматов и регулярные выражения. Завершает- ся рассказ в главе 21 - созданием карт на основе собранных данных. В конце каждой части приведены ссылки на литературу и веб-ресурсы. Кроме этого, в приложениях содержится: описание среды разработки RStu- dio (приложение Л), команды поисковых сервисов Google и Яндекс (прило- жение Б), введение в язык разметки HTML (приложение В) и сводка регу- лярных выражений (приложение Г). Обратная связь Ваши вопросы и конструктивную критику по содержанию книги присылай- те по электронному адресу: dkhramov@mail.ru. Новости, связанные со сбором данных, файлы примеров, список замечен- ных ошибок, а также ответы на вопросы читателей можно найти на сайте книги: http://dkhramov.dp.ua/Comp.DataGathering.
Часть I ПРОГРАММИРОВАНИЕ HAR
Глава Знакомство с R В этой главе мы установим R и научимся пользоваться средой разработки RGui. По пути решим простую, но весьма распространённую задачу - по- строим график функции. Установка Рассмотрим способ установки R, который подойдёт для любого типа опера- ционных систем, поддерживаемых пакетом: Linux, Мас или Windows. В слу- чае Windows этот путь - единственный, для Linux и Мас проще воспользо- ваться менеджером пакетов соответствующей системы. Чтобы установить R, зайдём па его официальный сайт и выберем интере- сующую версию программы. Сам R, документация к нему и дополнительные пакеты распространяются через сеть ftp- и веб-серверов, называемую CRAN (Comprehensive R Archive Network). Поэтому следующим шагом будет вы- бор одного из более чем шести десятков зеркал CRAN. После этого, указав тип операционной системы, скачиваем дистрибутив R. Запустим инсталляционный файл и будем следовать указаниям програм- мы-установщика. Единственный момент, который потребует вашего внима- ния, - выбор разрядности операционной системы (рис. 1.1). После установки запустим программу. В Linux и Мас, по умолчанию, R ра- ботает в консоли. Кроме того, вместе с пакетом поставляется графический интерфейс. В Linux он основан на связке Tcl/Tk и запускается командой R --gui=Tk. В Мас встроенный графический интерфейс для R называется R.app. Версия R для Windows поставляется с графической оболочкой RGui (рис. А.5), которую мы и рассмотрим в дальнейшем. Заметим, что работа со всеми указанными выше графическими оболочка- ми выглядит примерно одинаково.
1Б Знакомство с R Рис. 1.1 ❖ Выбор компонентов в ходе установки R под Windows Рис. 1.2 ❖ Консоль R в графической оболочке RGui (Windows)
Работа в среде RGui 17 Работа в среде RGui RGui - это стандартная графическая оболочка R под Windows, являющаяся простейшей средой разработки. Она быстро загружается и достаточно удоб- на в использовании. В RGui есть три вида окон: • консоль; • редактор скриптов; • окно графического устройства. Команды R вводятся в консоли (рис. Л.5) после приглашения пользовате- ля (значка '>') и отправляются на выполнение нажатием Enter. Управление консолью: • дополнение команды - Tab, • перемещение по истории команд - клавиши со стрелками; • прекращение выполнения команды - Esc; • переключение в другое окно - Ctrl+Tab; • очистка консоли - Ctrl+L. Для создания собственных программ (скриптов)1 удобнее использовать не консоль, а редактор (рис. 1.3). Открыть его можно в меню Файл/Новый скрипт. Первое окно открывает- ся с помощью меню, последующие - так же или комбинацией клавиш Ctrl+N. RGui (64-bit) - [Безымянный - Редактор R] Файл Правка Пакеты Окна Справка ^][й Н □ а Рис. 1.3 ❖ Интерфейс редактора кода в RGui Обратите внимание на то, как изменилась панель инструментов по срав- нению с консолью (рис. Л.5). В качестве примера построим график синусоиды: х <- seq(-pi,pi,.1) у <- sin(x) plot(x,y) В первой строке формируется одномерный массив (вектор) .г-координат, значения которых изменяются от —тг до тг с шагом 0.1. Этот массив сохраня- ется в переменной х. Комбинация символов < - обозначает операцию присваи- вания. 1 Мы используем термины «скрипт» и «программа» как синонимы.
18 Знакомство с R Во второй строке создаётся вектор у, состоящий из синусов элементов век- тора х. Наконец, в третьей строке функция plot строит требуемый график. В редакторе можно набирать команды и выполнять их как по одной, так и целыми блоками, с помощью комбинации Ctrl+R (на панели инструмен- тов также есть соответствующая кнопка). Например, можно выделить весь скрипт - Ctrl+A и отправить его на выполнение Ctrl+R (рис. 1.4). Рис. 1.4 ❖ Выделенную часть кода можно отправить на выполнение комбинацией клавиш Ctrl+R В результате получим графическое окно с построенным в нём графиком синусоиды (рис. 1.5). По терминологии R окна, в которых строятся графики, и файлы, в которых эти графики сохраняются, вместе называются графиче- скими устройствами. Рис. 1.5 ❖ Графическое устройство в RGui
Работа в среде RGui 19 Вернуться к консоли можно нажатием кнопки на панели инструментов (рис. 1.6) или при помощи Ctrl+Tab. RGui (64-Ы) - [R Graphics: Device 2 (ACTIVE)] Файл История команд Изменить размер Окна Blggjl___________________________________ [ Вернуть фокус в консоль | Рис. 1.6 ❖ Кнопка Вернуть фокус в консоль на панели инструментов графического устройства Управление окнами при помощи меню Окна показано на рис. 1.7. Л RGui (64-bit) - [R Console] Рис. 1.7 ❖ Управление окнами с помощью меню Окна Сохранить скрипт можно при помощи команд Сохранить или Сохранить как... меню Файл редактора или соответствующей кнопки на его панели уп- равления. Заметим, что скрипты R, объединённые общей темой, удобно держать в отдельном каталоге. Выйти из R можно, вызвав в консоли q() или воспользовавшись меню Файл/Выйти. При выходе из RGui среда предложит сохранить рабочее пространство (рис. 1.8).
20 Знакомство с R Рис. 1.8 ❖ Сохранение рабочего пространства по выходе из RGui Рабочее пространство (workspace) - это область оперативной памяти, в которой хранятся все созданные пользователем объекты (векторы, матрицы, таблицы, списки, функции и т. п.). Сохранение рабочего пространства в фай- ле и его последующая загрузка в новом сеансе работы с R (Файл/Загрузить рабочее пространство...) позволяют продолжить работу с того места, где она была прервана. При этом сохраняются значения всех переменных, вычисленные на прошлом сеансе работы. Просмотреть список объектов в рабочем пространстве можно при помощи функции 1s. Вызовем её в консоли > is() и получим в результате ## [1] "х" "у" Действительно, в памяти сейчас хранятся векторы координат синусоиды.
Работа в среде RGui 21 Два символа решётки (##) указывают на то, что далее идёт результат вы- полнения программы (в консоли они не отображаются, см. рис. 1.9). Смысл единицы ([1]) станет ясен в следующей главе. Другие команды управления рабочим пространством: # Сохранить рабочее пространство в файл .RData в текущем рабочем каталоге save.image() # Сохранить заданные объекты в файле save(object_list, file="myfile.RData") # Загрузить образ рабочего пространства load("myfile.RData") # Очистить рабочее пространство rm(list=ls()) Символ # открывает строку комментария. Сохранить или загрузить образ рабочего пространства можно при помощи меню Файл. Следует иметь в виду, что это меню, как и любое другое в RGui, просто запускает на выполнение соответствующие функции R. Например, команда меню Файл/Сохранить рабочее пространство... вы- зывает функцию save. image, и результат этого вызова можно увидеть в кон- соли (рис. 1.9). > х <- seq(-pi, pi, .1) > у <- sin(x) > plot(х,у) > Is О (1] "х" "у" > save . image ("С: \\Users\\AdirjLrA\Docuiner.ts\\myiniage") > I Рис. 1.9 ❖ Команда меню Файл/Сохранить рабочее пространство... представляет собой вызов функции save.image Клавиши со стрелками вверх и вниз позволяют перемещаться по списку выполненных ранее команд - истории команд. Историю команд также мож- но сохранить в файл на диске. # Вывод истории команд history() # выводит список 25 последних выполненных команд history(max.show=Inf) # выводит все выполненные в сессии команды # Сохранить историю команд savehistory(file="myflie") # по умолчанию ".Rhistory" # Загрузить историю команд loadhistory(file="myfile") # по умолчанию ".Rhistory"
22 Знакомство с R Помимо RGui, для R существуют другие, более продвинутые среды разра- ботки, например R Commander или RStudio. Так же, как и сам R, они являют- ся кросс-платформенными и распространяются свободно. Заметим, что хо- тя возможностей у этих сред гораздо больше, они дополняют возможности RGui, но не перечёркивают их. Так что навыки работы с RGui пригодятся и при работе в более совершенной среде. Справка Верхний пункт меню Справка (в консоли это Консоль, в редакторе скрип- тов, соответственно, Редактор) даёт короткую подсказку по работе с соот- ветствующим окном. Справка для редактора показана на рис. 1.10. Рис. 1.10 ♦> Справка по работе в редакторе скриптов RGui Справку по функции с именем fun можно получить, набрав в консоли ?f un. Например, для получения справки по функции создания последова- тельностей seq наберём: ?seq # или help(seq) В ответ на выполнение этой команды откроется браузер со справочной ин- формацией (рис. 1.11).
Справка 23 * —» • | о Ив) г <Я К Sequence Generation X + <• 127.0.0.120721/library/be □ С О. Помог ☆ 6 О ♦ W » = 33 Fregat - Страница по... Переводчик Google YandecTranslate § Library Genesis AvaxHome » seq {base} R Documentation Sequence Generation Description Generate regular sequences, seq is a standard generic with a default method seq.int is a primitive which can be much faster but has a few restrictions. seq_along and seq_len are very’ fast primitives for two common cases. Usage seq(...) ♦# Default S3 method: seq(from 1, to - 1, by • ((to - from)/(length.out - 1)), length.out = NULL, along.with = NULL, ...) seq.int(from, to, by, length.out, along.with, ...) seq_along(along.with) seq_len(length.out) Arguments Рис. 1.11 ❖ Справка по функции seq
Глава Скаляры, векторы и матрицы В этой главе мы научимся создавать переменные и выполнять над ними ариф- метические и логические операции. Познакомимся с основными типами дан- ных: числовыми, символьными и логическими, а затем узнаем, что все они представляют собой векторы, которые в R вовсе нс составной тип данных, а наоборот - простейший. Научимся создавать из векторов матрицы и в не- сколько строк кода вычислять интегралы. Арифметические операции и присваивание Арифметические операции в R выглядят так же, как и во многих других язы- ках программирования: 3+2 2*(4-1)/6 3Л2; sqrt(4) # ' служит для разделения выражений в строке # tf [1] 5 # # [1] 1 # # [1] 9 # # [1] 2 А вот запись операции присваивания имеет особенности. Оно может обо- значаться знаком равенства ' = ' или «стрелками», позволяющими присваи- вать как влево (а <- Ь), так и вправо (Ь -> а). а <- 2 # присвоим значение а - традиционная форма записи для R 3 -> b # присвоим значение b с = а+b # и это - то же присваивание с # напечатаем значение с ## [1] 5 Мы будем использовать обозначение ' как из соображений совмести- мости со старыми версиями пакета, так и чтобы отличать присваивание от других операций, например от задания имён столбцов в таблицах - в послед- нем случае всегда используется ' ='.
Имена 25 Имена При назначении имён переменных и функции придерживаются следующих правил: 1. Использовать для имён только латинские буквы, символ подчёркива- ния цифры и точку '.при этом имя не должно начинаться с цифры или точки: plot_new2 и plot.new - это правильно, a .plot и 2plot - нет. 2. Помнить, что R чувствителен к регистру: Var, var и VAR - это разные имена. 3. Не давать объектам имена, занятые распространёнными функциями (например, не следует создавать функцию с именем с()) или ключе- выми словами (особенно это касается Т, F, NA, NaN, Inf). Одна из особенностей R состоит в том, что в именах объектов допусти- мо использовать точку. Её нередко применяют для разделения смысловых частей имени, подобно тому как в других языках используют символ под- чёркивания (который в ранних версиях R использовать было нельзя). Так, функция install. packages () служит для установки новых пакетов расши- рений, но это именно функция, а не метод packages() объекта install. Проверить, существует ли переменная или функция с заданным именем, можно при помощи функции exists: exists('a') # переменная 'а' уже определена, # # [1] TRUE exists('d') # ...a 'd' - ещё нет # # [1] FALSE exists('all') # существует функция с именем 'all' # # [1] TRUE Параметр mode позволяет указать вид объектов, среди которых выполня- ется поиск. Так, переменная а существует, а вот функция а - нет: exists("a", mode="function") ## [1] FALSE
26 Скаляры, векторы и матрииы Простые типы данных Простые типы данных - это такие данные, которые нельзя разделить на со- ставляющие элементы. Их ещё называют скалярными данными, или скаля- рами, в отличие от составных данных - векторов, матриц, списков и т. и. В R существуют три вида простых типов данных: числовые, символьные и логические. Числа R различает три вида чисел: • numeric - вещественные; • integer - целые; • complex - комплексные. а <- 1 # или a <- 1.0 class(a) # # [1] "numeric" str(a) # # num 1 Функция class позволяет ответить на вопрос, к какому типу данных (клас- су) относится объект. Функция str возвращает структуру объекта: тип дан- ных, к которому относится объект (num - сокращение от numeric), и его зна- чение (в нашем случае - 1). Если вещественные числа создаются непосредственно, то для создания це- лых служит функция as. integer: b <- as.integer(a) class(b) # # [1] "integer" Для проверки, относится ли объект к тому или иному типу данных, ис- пользуют функции семейства is. *, например is. integer, is. numeric и т. п. is.integer(b) ## [1] TRUE
Простые типы данных is.integer(a) # # [1] FALSE is.numeric(a) # # [1] TRUE Добавляя к целому или вещественному числу мнимую часть, обозначае- мую символом i, получаем комплексное число: с <- sqrt(-l) # так не пойдёт! # # Warning in sqrt(-l): созданы NaN str(c) # # num NaN c <- sqrt(-l+0i) # правильный способ class(c) # # [1] "complex" str(c) # # cplx 0+li is.numeric(c) # # [1] FALSE is.complex(c) # # [1] TRUE Неявное приведение числовых типов данных (type coercion) выполняется по следующей схеме: integer -> numeric -> complex Это означает, например, что складывая целое и вещественное числа, мы получим в результате вещественное число, а добавляя в выражение число комплексное - получим комплексный же результат. Для введенных ранее переменных имеем:
28 Скаляры, векторы и матрииы ab <- а+Ь class(ab) # # [1] "numeric" abc <- ab/c class(abc) # # [1] "complex" Явное приведение типов данных выполняется при помощи функций се- мейства as. *: as.numeric(l.5+li) # при потере мнимой части выдается предупреждение # # Warning: мнимые части убраны при преобразовании # # [1] 1.5 dn <- as.numeric(l.5+01) str(dn) # # num 1.5 di <- as.integer(1.5+0i) str(di) # # int 1 as.complex(di) # # [1] l+0i Символьный ТИП Данные символьного типа character представляют собой строки, состоя- щие из символов, заключённых в одинарные или двойные кавычки. si <- "Text string." s2 <- 'Another string.' sl2 <- paste(sl,s2) str(sl2) # # chr "Text string. Another string." Соединение строк - конкатенация - выполняется функцией paste. Кавычки одного типа могут находиться внутри кавычек другого типа:
Простые типы данных 29 "First 'attempt'" 'Second "attempt"' Если типы внутренних и внешних кавычек совпадают, то внутренние ка- вычки нужно заэкранировать при помощи ' V (бэкслэш): "Third \"attempt\"" Иногда удобней создать строку с помощью функции sprintf, которая по- хожа на одноимённую функцию языка С: sprintf("%s was founded in AD %d.", "London", 43) # # [1] "London was founded in AD 43." Выделить подстроку позволяет функция substr): substr("abcdefg", 3, 5) # выделить подстроку с 3-го по 5-й элемент # # [1] "cde" Функции sub и gsub позволяют заменить часть строки, соответствующую заданному образцу. При этом sub заменяет только первое вхождение, a gsub выполняет глобальную замену подстроки. # что менять # на что заменить # где искать # sub ("а", "А", "rama") sub("a","A","rama") # sub = substitute # # [1] "гAma" gsub("a","A","rama") # gsub = global substitute # # [1] "rAmA" sub("a","A",c("mama","rama","banana")) # # [1] "mAma" "rAma" "bAnana" Числа преобразуются в строки «сами собой». Обратное преобразование выполняется с помощью функций as. *. paste("the value of PI is", 3.14) # # [1] "the value of PI is 3.14"
30 Скаляры, векторы и матрииы as.numeric("3.14") # # [1] 3.14 as.numeric("a") # # Warning: в результате преобразования созданы NA # # [1] NA as.character(3.14) # # [1] "3.14" Вот как можно преобразовать число с десятичной занятой из символьной формы записи ("9,1") в числовую 9.1: г <- "9,1" as.numeric( gsub(",", г) ) # # [1] 9.1 Логический тип Данные, относящиеся к логическому типу logical, имеют всего два значе- ния: TRUE («истина») и FALSE («ложь»). Чаще всего данные этого типа возникают при сравнении переменных: х = 2; у = 5 z = х > у # х больше у? z # напечатаем полученное логическое значение # # [1] FALSE class(z) # # [1] "logical" Основные логические операции: & (И), | (ИЛИ) и ! (НЕ): а = TRUE; b — FALSE # Запись можно сократить: а = Т; b = F а & b # и И v ## [1] FALSE
Векторы 31 а | b # и или v # # [1] TRUE !а # НЕ-и # # [1] FALSE Находясь в одном выражении с числами, данные логического типа приво- дятся к соответствующему числовому типу данных. При этом FALSE пред- ставляется как О, a TRUE - как 1: 1 + FALSE ## [1] 1 1 ♦ TRUE # # [1] 2 class(as.integer(1) + TRUE) # # [1] "integer" Общая схема неявного приведения типов в R выглядит так: logical -> числовые типы -> character Например: paste(FALSE, 1.0, "test") # # [1] "FALSE 1 test" Напомним, что явное приведение типов выполняется функциями семей- ства as. а проверку типа можно выполнить с помощью функции is. Векторы Основным типом данных в R является вектор. Вектор - это последователь- ность элементов одного типа: # числовой вектор х <- с(1.5, 6, 8.3, 9, 6, .6, 2е-4) # символьный вектор s <- c("s", "t", "г", "i", "n", "g", "another string") # логический вектор b <- C(TRUE, FALSE, TRUE, TRUE, FALSE, TRUE, TRUE)
32 Скаляры векторы и матрицы Функция с() служит для создания вектора. Её название происходит от английского ‘concatenate’ - собирать, то есть она как бы собирает отдельные элементы в вектор. Элементы векторов нумеруются, начиная с единицы. Чтобы выбрать тот или иной элемент вектора, нужно указать его номер в квадратных скобках: х[1] # # [1] 1.5 Векторы в R играют особую роль: из них строятся все остальные типы дан- ных. Да, выше мы изучали простые типы данных, однако технически эти ти- пы реализуются в R как векторы единичной длины. Например, число - это числовой вектор единичной длины. а = 3 а[1] # # [1] 3 # # [1] NA (NA - ‘Not Available’ - означает недоступность данных, пропуск в данных). Возможно, вас интересовало, почему выводу результатов в консоли R вся- кий раз предшествует [1]. Так вот, это номер элемента вектора, с которого начинается строка вывода: c("s"r "t", "г", "i", "n", "g", "another string") # # [1] "s" "t" "r" "1" # # [5] "n" "g" "another string" Просто до сих нор мы работали с весьма короткими векторами. Отрицательный индекс в квадратных скобках означает: выбрать все эле- менты, кроме указанных: V <- С(1.1, 2.2, 3.3, 4.4, 5.5) v[l:4] # # [1] 1.1 2.2 3.3 4.4
Векторы 33 v[-5] # # [1] 1.1 2.2 3.3 4.4 Тип данных, составляющих вектор и его структуру, как и раньше, можно определить с помощью функций class и str соотвественио: class(v) # # [1] "numeric" str(v) # # num [1:5] 1.1 2.2 3.3 4.4 5.5 Частным случаем векторов являются последовательности элементов, из- меняющихся по определённому закону. Последовательность с шагом 1 мож- но создать при помощи двоеточия ' :' v = -5:5 v # # [1] -5-4-3-2-1012345 Значительно больше возможностей даёт функция seq. Она позволяет со- здавать последовательности с заданным шагом seq(5,1,by=-.5) # # [1] 5.0 4.5 4.0 3.5 3.0 2.5 2.0 1.5 1.0 и заданной длины seq(l,10,length=6) # # [1] 1.0 2.8 4.6 6.4 8.2 10.0 Как и во многих других функциях R, в seq есть необязательные аргументы. Их можно не указывать явно, и тогда будут использованы значения, задан- ные для этих аргументов по умолчанию. Узнать, что это за аргументы и какие значения по умолчанию они принимают, можно из справки по функции seq: ?seq # help(seq) Функция rep позволяет повторить объект заданное число раз:
34 Скаляры, векторы и матрииы гер(1:3,5) # # [1] 123123123123123 Л вот вариант похитрее: гер(1:3,с(5,5,5)) # # [1] 111112222233333 Эту команду можно сократить, используя ещё один гер: гер(1:3,гер(5,3)) # # [1] 111112222233333 Арифметические операции над векторами выполняются поэлементно: и <- с(1,2,3) V <- с(4,5,6) U+V # # [1] 5 7 9 u*v # # [1] 4 10 18 u/v # # [1] 0.25 0.40 0.50 Скалярное произведение векторов записывается как U %*% V # # [,1] # # [1,] 32 Деление на ноль даёт в результате Inf - бесконечность w <- c(v[l:2],e) # добавляем элемент к фрагменту вектора v u/w # # [1] 0.25 0.40 Inf которая при последующих операциях «поглощает» все конечные значения:
Векторы 35 u+u/w # # [1] 1.25 2.40 Inf Сложим два вектора разной длины. «Будет ошибка», - скажете вы. Л вот и нет: и <- с(1,2,3) V <- с(4,5,6,7,8,9,10) u+v # # Warning in u + v: длина большего объекта не является # # произведением длины меньшего объекта # # [1] 5 7 9 8 10 12 11 Операция будет выполнена, но R предупредит, что длина векторов-опе- рандов различается. При этом сложение и другие подобные операции, требующие в «обычном» состоянии равной длины операндов, выполняются так: более короткий век- тор и повторяется столько раз, сколько нужно, чтобы его длина сравнялась с длиной v, после чего выполняется заданная операция. Фактически склады- ваются векторы: и <- с(1,2,3,1,2,3,1) V <- с(4,5,6,7,8,9,10) u+v # # [1] 5798 10 12 11 Добавление элементов в вектор осуществляется функциями с и append ("’добавить”): vec = с('а','b') vec = с(vec,'с','d') vec ## [1] "а" "Ь" "с" "d" values = c('e','f','g) vec <- append(vec, values) vec ## [1] "a" it b tt n q u tt j n tt n tt п - и Удаление элементов из вектора выполняется следующим образом:
ЗБ Скаляры, векторы и матрицы а <- sample(l:10) # генерируем случайные целые числа от 1 до 10 а remove <- с(3,5,7) # выберем для удаления 3-й, 5-й и 7-й элементы а <- а[-remove] # удалим выбранные элементы а # # [1] 5 6 4 7 3 1 8 9 10 2 # # [1] 5 6 7 1 9 10 2 Длина вектора, то есть число его элементов, вычисляется функцией length(): length(a) # # [1] 7 Указать последний элемент вектора можно так: a[length(a)] # # [1] 2 Векторизация и логическая индексаиия Векторизация - подход к программированию, позволяющий выполнять опе- рации над вектором в целом, а не над отдельными его элементами (скаляра- ми). а <- с(1,2,3); b <- с(4,5,6) cl <- vector() # создаём пустой вектор # Вместо того чтобы делать так: for (i in 1:3) { cl[i] <- a[i] + b[i] # cl обязательно нужно заранее создать } # Проще сделать так: с2 <- а + b Векторизация используется не только в R, но и в других языках програм- мирования, например в MATLAB и Python (при использовании последнего совместно с библиотекой Num Ру). Для векторизации расчётов широко используется логическая индексация: а <- с(6, -2,1,8, 0,9) ind_a <- а > 0 ind_a ## [1] TRUE FALSE TRUE TRUE FALSE TRUE
Векторизация и логическая индексация 37 Логический индекс (ind_a) - вектор, длиной равный исходному (а), эле- менты которого равны TRUE, если соответствующий элемент исходного век- тора удовлетворяет логическому условию (а > 0), и FALSE - в противном случае. Логическая индексация позволяет заменить связку «цикл + условный опе- ратор». Например, чтобы выбрать положительные элементы а, не нужно ор- ганизовывать цикл с проверкой в его теле условия а [ i] > 0, а можно посту- пить так: # Выбрать положительные элементы а а[а > 0] # или a[ind_a] # # [1] 6 1 8 9 # Подсчитать их количество... length(a[ind_a]) # # [1] 4 # ... или сумму sum(a[a > 0]) # # [1] 24 Таблица 2.1 ❖ Логические операции Обозначение Значение a == b a != b 1 &&, | | xor(az b) any(a) all(a) а равно b а не равно b И, ИЛИ (поэлементные) И, ИЛИ (сравниваются левые крайние элементы векторов) Исключающее ИЛИ TRUE, если хотя бы один из элементов а истинен TRUE, если все элементы а истинны Примеры использования логических операций: а <- с(6, -2,1,8, 0,9) а > 0 & а < 9 ## [1] TRUE FALSE TRUE TRUE FALSE FALSE
38 Скаляры, векторы и матрицы а < 2 | а > 8 # # [1] FALSE TRUE TRUE FALSE TRUE TRUE any(a>0) # # [1] TRUE all(a>0) # # [1] FALSE Если данные содержат пропуски (NA), это может повлиять на результат вычислений. Проверка таких случаев реализуется с помощью функции is. па: # Данные с пропусками: а <- С(6, -2,NA, 1,8,0,NA,9) # Их сумма даёт: sum(a) # # [1] NA # Является ли элемент пропуском в данных? is.па(а) ## [1] FALSE FALSE TRUE FALSE FALSE FALSE TRUE FALSE У Вычисление суммы с учётом проверки на пропуски в данных sum( a[’is.na(a)] ) ## [1] 22 Покажем, как с помощью векторизации можно легко вычислить опреде- лённый интеграл. Рассмотрим в качестве примера интеграл x2dx. Вычислим его прибли- жённо, воспользовавшись методами прямоугольников и трапеций (для про- верки: интеграл равен 7/3 = 2,333(3))2. а <- 1; b <- 2 # границы промежутка интегрирования п <- 1000 # число узлов интегрирования х <- seq(a,b,length.out=n) # координаты узлов сетки h <- х[2]-х[1] # шаг сетки у <- хл2 # значения подынтегральной функции в узлах сетки Значения нижней и верхней интегральных сумм дают оценки величины интеграла снизу и сверху соответственно. Любая их этих сумм даёт прибли- жённое значение интеграла, вычисленное методом прямоугольников: 2 Вспомнить их можно по книге: Турчак Л. И., Плотников 11. В. Основы численных методов. М.: Физматлит, 2003.
Матрицы и массивы 39 sd<- h*sum(y[-length(y)]) # Нижняя интегральная сумма sd ## [1] 2.331832 sue- h*sum(y[-l]) su # Верхняя интегральная сумма ## [1] 2.334835 Метод трапеций даёт более точный результат: (su+sd)/2 ## [1] 2.333334 Матрицы и массивы Матрица (matrix) в R - это специальный тип вектора, обладающий допол- нительными атрибутами, которые позволяют трактовать его как совокупность строк и столбцов. vl = 1:4 v2 = -2:1 # Создадим из элементов vl матрицу размера 2x2 ml = matrix(vl, nrow=2, ncol=2) ml ## ## [1,] ## [2,] [,1] [,2] 1 3 2 4 # Создать матрицу можно, объединяя векторы по строкам m2 = rbind(vl,v2) m2 # # [,1] [,2] [,3] [,4] # # Vl 1 2 3 4 # # V2 -2-101 # .,. или по столбцам m3 = cbind(vl,v2) m3 # # vl v2 # # [1,] 1 -2 # # [2,] 2 -1 # # [3,] 3 0 # # [4,] 4 1
40 Скаляры, векторы и матрицы При выборе элементов матрицы указывают индексы строк и столбцов: # Элемент 4-й строки и 2-го столбца матрицы m3 тЗ[4,2] ## V2 ## 1 Кроме того, у строк и столбцов матриц могут быть заданы имена. Напри- мер, у матрицы m2 имена строк (а у m3 - имена столбцов) равны vl и v2, так что к её элементам можно обратиться так: m2["v2",l] # эквивалентно m2[2,1] ## V2 ## -2 Размерность вектора и матрицы можно задать функцией dim: А <- 1:9 dim(A) <- с(3,3) # формируем из вектора матрицу размерности 3x3 А # # [,1] [,2] [,3] # # [1, ] 1 4 7 # # [2,] 2 5 8 # # [3,] 3 6 9 Транспонирование матрицы выполняется функцией t(): t(A) # # [,1] [,2] [,3] # # [1,] 1 2 3 # # [2,] 4 5 6 # # [3,] 7 8 9 Многомерные массивы в R создаются при помощи функции array. Раз- мерность массива задаётся атрибутом dim: arr <- array(l:24, dim=c(2,4,3)) arr # # , , 1 # # # # [,1] [,2] [,3] [,4] # #[!,] 1 3 5 7
Резюме 41 # # [2,] 2 4 6 8 # # # # , , 2 # # # # [,1] L2] [,3] [,4] # # [1,] 9 11 13 15 # # [2,] 10 12 14 16 # # # # , , 3 # # # # [,1] [,2] [,3] [,4] # # [1,] 17 19 21 23 # # [2,] 18 20 22 24 Резюме Синтаксис R имеет две очевидные особенности: в именах переменных мож- но использовать точку ' .', а операция присваивания обозначается стрелкой Особую роль в R играют векторы - числовые, символьное и логические. Из них строятся все остальные типы данных. Даже скаляры представляют собой лишь векторы единичной длины. Это приводит к тому, что все опера- ции осуществляются над векторами в целом, а не над отдельными их элемен- тами. В результате в R сравнительно редко используются циклы с перебором по элементам.
Глава Списки и таблицы Векторы и матрицы состоят из данных одного типа. Чтобы сохранять набо- ры данных, относящихся к различным типам, в R существуют списки и таб- лицы, с которыми мы познакомимся в этой главе. Списки Список (list) напоминает вектор, но может содержать объекты разных ти- пов. Создаётся список функцией list: 1st <- list(n=l:3, s="test", b=c(T,F,F,F,T)) n s ________ b <- c(T,F,F,F,T) 1st 1st ИЛИ <- 1:3 "test" г,-.-.”.-) <- list(n,s,b) # состоит из копий векторов n,s,b ## ## ## ## ## ## ## ## [[1]] [1] 1 2 3 [[2]] [1] "test [[3]] [1] TRUE FALSE FALSE FALSE TRUE Структура созданного списка имеет вид: str(lst) ## ## ## ## List $ : $ : $ : of 3 int [1:3] 123 chr "test" logi [1:5] TRUE FALSE FALSE FALSE TRUE Список 1st состоит из трёх элементов, каждый из которых является векто- ром: числовым, символьным и логическим соответственно. Элементы списков, так же как элементы векторов и матриц, могут иметь имена. Узнать или присвоить эти имена можно при помощи функции names:
Списки 43 names(lst) # # NULL В настоящий момент имена элементов 1st не заданы. Присвоим элементам списка 1st имена num, cha и log: names(lst) <- c("num","cha","log") и посмотрим, что получилось: str(lst) # # List of 3 # # $ num: int [1:3] 123 # # $ cha: chr "test" # # $ log: logi [1:5] TRUE FALSE FALSE FALSE TRUE Элементы списка можно выбрать одним из следующих способов: 1) с помощью квадратных скобок [,]. Полученный в результате объект, в свою очередь, будет являться списком, даже если длина его окажется единичной: 1st[1] # или 1st["пит"] # # $num # # [1] 1 2 3 class(lst[1]) # # [1] "list" 2) с помощью двойных квадратных скобок [[,]]. Возвращённый объект будет того же типа, каким он был до включения в список: 1st[[1]] # или 1st[["пит"]] ## [1] 1 2 3 class(lst[[1]]) ## [1] "integer" 3) использовать имена элементов списка и знак доллара $:
44 Списки и таблииы lst$num # # [1] 1 2 3 class(lst$num) # # [1] "integer" Удалять имена элементов можно следующим образом: names(lst) <- NULL но мы пока делать этого не будем. Добавить элемент в список можно так же, как и в вектор, - при помощи функций с() или append(): v <- 5:10 # создадим числовой вектор 11 <- с(1st,list(v)) # преобразуем его и добавим в список str(ll) # # List Of 4 # # $ num: int [1:3] 123 # # $ cha: chr "test" # # $ log: logi [1:5] TRUE FALSE FALSE FALSE TRUE # # $ : int [1:6] 5 6 7 8 9 10 Заметим, что если вектор не преобразовать при помощи list (), то к спис- ку будет добавлен не один элемент-вектор, а шесть элементов-чисел: 12 <- c(lst,v) str(12) ## List of 9 ## $ num: int [1:3] 123 ## $ cha: chr "test" ## $ log: logi [1:5] TRUE FALSE FALSE FALSE TRUE ## $ : int 5 ## $ : int 6 ## $ : int 7 ## $ : int 8 ## $ : int 9 ## $ : int 10 Функции с и append, помимо векторов, позволяют объединять и списки:
Таблицы 45 112 <- с(11,12) Удаляются элементы списка так же, как элементы вектора: remove <- с(2,3) # удалить 2-й и 3-й элементы 1st <- lst[-remove] str(lst) # # List Of 1 # # $ num: int [1:3] 123 Определим количество элементов в списке 1st: length(lst) # # [1] 1 Обратите внимание, что функция length возвращает нам число элемен- тов списка. В нашем случае такой элемент один. Другое дело, что сам этот элемент является числовым вектором. Чтобы найти длину этого последне- го, нужно применить двойные квадратные скобки length(lst[[l]]) # # [1] 3 или преобразовать список в вектор при помощи unlist (): vec_from_lst <- unlist(lst) class(vec_from_lst) # # [1] "integer" length(vec_from_lst) # # [1] 3 Таблицы Таблицы (data frames) хранят двумерные данные, но, в отличие от матриц, позволяют находиться в разных колонках данным различных типов. По свое- му внутреннему устройству таблица - это список колонок равной длины. Можно сказать и так: если вы когда-нибудь видели таблицу в Microsoft Excel, то вам уже знакомы таблицы в R. Создаются таблицы с помощью функции data. frame:
46 Списки и таблииы dfl <- data.frame(a=l:5,b=6:10,c=letters[l:5]) str(dfl) ## 'data, .frame': 5 obs. of 3 variables: ## $ a: int 1 2 3 4 5 ## $ b: int 6 7 8 9 10 ## $ c: Factor w/ 5 levels "a" ,"b","c","d", . . : 1 2 3 4 5 Таблицы можно объединять по строкам: df2 <- data.frame(a=6:10,b=ll:15,c=letters[6:10]) rbind(dfl,df2) # # a b c # # 1 16a # # 2 27b # # 3 38c # # 4 4 9 d # # 5 5 10 e # # 6 6 11 f # # 7 7 12 g # # 8 8 13 h # # 9 9 14 i # # 10 10 15 j или по столбцам: df2 <- data.frame(c=6:10,d=ll:15,e=letters[6:10]) cbind(dfl,df2) # # a b c c d e # #11 6 a 6 11 f # #22 7b 7 12 g # #33 8c 8 13 h # #44 9 d 9 14 i # # 5 5 10 e 10 15 j Ещё один способ объединения таблиц даёт в результате список: 13 <- c(dfl,df2) str(13) # # List of 6 # # $ a: int [1:5] 12345 # # $ b: int [1:5] 6 7 8 9 10
Таблицы ❖ 47 # # $ с: Factor w/ 5 levels "a", "b", "c", "d", .. : 1 2 3 4 5 # # $ c: int [1:5] 6 7 8 9 10 # # $ d: int [1:5] 11 12 13 14 15 # # $ e: Factor w/ 5 levels "f","g","h","i", ..: 12345 Если нужно объединить таблицы, имеющие общие колонки, то применяет- ся функция merge. Как правило, она используется для объединения таблиц с общими ключами: first <- data.frame(id=l:5, COL=LETTERS[1:5], col=letters[l:5]) secnd <- data.frame(id=l:5, col=letters[l:5]) # объединяем таблицы по колонке id total <- merge(first,secnd,by="id") # объединяем таблицы по колонкам id и col total <- merge(first,secnd,by=c("id","col")) Размеры таблицы определяются следующим образом: length(dfl) # длиной таблицы является длина списка, состоящего из колонок # # [1] 3 ncol(dfl) # число колонок # # [1] 3 nrow(dfl) # число строк # # [1] 5 Выбор элементов таблицы рассмотрим на примерах: df <- data.frame(a=l:5,b=6:10,c=letters[l:5]) df # вся таблица # # a b С # #11 6 a # # 2 2 7 b # #33 8c # #44 9 d # # 5 5 10 e df[1,] # 1-я строка # # a b C # # 1 1 6 a
48 Списки и таблииы df[,2] # 2-я колонка # # [1] 6789 10 df[4,3] # элемент 4-й строки 3-й колонки # # [1] d # # Levels: abode df[4,c(l,3)] # элемент 4-й строки из 1-й и 3-й колонок # # ас # # 4 4 d Иногда для выбора нужного фрагмента таблицы удобно использовать ло- гические условия: df[df$c == 'd', ] # 4-я строка # # а Ь с # # 4 4 9 d df[,df$c == 'а'] # 1-я колонка # # [1] 1 2 3 4 5 df[,df$c == 'd'] # Нет такой колонки! # # Error in '[.data.frame'(df, , df$c == "d"): undefined # # columns selected Другой способ, позволяющий выделить фрагмент таблицы, - использова- ние функции subset. Для работы с именами колонок, помимо функции names, существует от- дельная функция: colnames(df) # # [1] "а" "Ь" "с" Можно также задать имена строк таблицы. Делается это с помощью функ- ции rownames ():
Таблицы 49 rownames(df) <- LETTERS[l:nrow(df)] Теперь выбирать элемент таблицы df можно так: df["A","b"] # df[l,2] # # [1] 6 Функция View отображает таблицу в Excel-подобном виде (рис. 3.1): View(df) А В С D Е а b * с 1 6 а 2 7 b 3 8 с 4 9 d 5 10 е Рис. 3.1 ❖ Представление таблицы df с помощью функции view в среде разработки RStudio Большую таблицу отображать на экране нецелесообразно. Чтобы понять, как она устроена, достаточно рассмотреть её «голову» (head) или «хвост» (tail). Если данные в таблице упорядочены по времени, это позволит уви- деть наиболее свежие или, наоборот, самые старые данные. Число выводи- мых строк таблицы регулируется параметром п и по умолчанию равно 6: df <- data.frame(coll=l:20, col2=letters[l:20]) head(df) ## coll col2 ## 1 1 a ## 2 2 b ## 3 3 c ## 4 4 d ## 5 5 e ## 6 6 f tail(df, n=3) # # coll CO12 # # 18 18 Г # # 19 19 S # # 20 20 t
50 Списки и таблицы Функции head и tail можно также применять для вывода частей векто- ров, матриц, списков и даже функций. Функции, применяемые к составным данным Весьма распространённой причиной использования циклов является необ- ходимость применить один и тот же набор операций (тело цикла) к элемен- там вектора, списка и т. п. Для решения этой задачи в R существует множест- во функций, большинство из которых относится к семейству *арр1у (’’при- менить”). Благодаря этим функциям во многих случаях удаётся избежать использования циклов, что делает код более компактным. Мы рассмотрим три функции из семейства * apply (об остальных можно узнать в справке: ?1арр1у) и дополняющую их функцию do. call. apply apply() применяет функцию fun к строкам или столбцам матрицы и имеет вид: арр1у(матрица, размерность, fun) Например: # Создадим матрицу М размера 4x4 М <- matrix(l:16,4,4) М ## [,1] [/2] [z3] [,4] ## [1, ] 1 5 9 13 ## [2,] 2 6 10 14 ## [3,] 3 7 11 15 ## [4,] 4 8 12 16 # Найдём минимальные элементы среди строк М apply(M,l,min) ## [1] 1 2 3 4 # Найдём среднее арифметическое столбцов М арр!у(М,2,mean) ## [1] 2.5 6.5 10.5 14.5
Функции, применяемые к составным данным 51 # Проверим, являются ли столбцы М векторами apply(М,2,is.vector) ## [1] TRUE TRUE TRUE TRUE lapply lapply() (от ‘list apply’) применяет функцию fun к элементам списка или таблицы: 1арр1у(список, fun) Пример: х <- list(a = 1, b = 1:3, с = 1О:50) # Вычислим длину каждого элемента lapply(x, length) # # $а # # [1] 1 # # # # $Ь # # [1] 3 # # # # $с # # [1] 41 # ... и сумму его значений lapply(x, sum) # # $а # # [1] 1 # # # # $Ь # # [1] 6 # # # # $с # # [1] 1230 Функции можно задавать самому: 1арр1у(с(1,2,4), function(x) хЛ2) # # [[1]] # # [1] 1 # #
52 Списки и таблииы # # [[2]] # # [1] 4 # # # # [[3]] # # [1] 16 Функция fun может зависеть и от нескольких аргументов: 1арр1у(с(1,2,4), function(x,y) хл2+у, 3 # значение, передаваемое у ) # # [[1]] # # [1] 4 # # # # [[2]] # # [1] 7 # # # # [[3]] # # [1] 19 sapply Функция sapply работает аналогично lapply, но старается упростить тип результата. Например, если lapply возвращает список: 11 <- 1арр1у(с(1,2,4), function(x) хЛ2) str(li) # # List Of 3 # # $ : num 1 # # $ : num 4 # # $ : num 16 to sapply удаётся получить вектор: ve <- sapply(c(l,2,4), function(x) хЛ2) str(ve) # # num [1:3] 1 4 16 или матрицу:
Резюме 53 х <- list(l:4, 5:8) sapply(x, function(x) хл2) # # [,1] [,2] # # [1,] 1 25 # # [2,] 4 36 # # [3,] 9 49 # # [4,] 16 64 Л вот как с помощью sapply можно преобразовать хранящиеся в таблице данные к числовому типу: # Преобразуем 1-ю и 3-ю колонки таблицы data: data[, с(1,3)] <- sapply(data[, с(1,3)], as.numeric) do.call Если * apply применяют заданную функцию к каждому элементу, выполняя таким образом множество вызовов функции, то do. call применяет функ- цию один раз ко всему списку аргументов: do.call(fun, список_аргументов) Примеры: do.call(sum, list(1,2, 4,1,2)) # Без do.call понадобилось бы избавиться от списка: # 11 <- list(l, 2, 4,1,2) # sum(unllst(ll)) # # [1] 10 А <- list(l:3,4:6,7:9) do.call(rbind,A) ## [, 1] [,2] [,3] ## [1,1 1 2 3 ## [2,] 4 5 6 ## [3,] 7 8 9 Резюме Списки представляют собой одномерные наборы разнотипных данных. На их основе создаются таблицы, представляющие собой списки колонок. Как правило, данные, которые нам предстоит собирать, будут сохраняться имен- но в виде таблиц. Функции семейства * apply позволяют применить один и тот же набор операций к элементам матрицы, списка, таблицы и т. п., что позволяет во многих случаях избежать использования циклов.
Глава Управление процессом вычислений Говоря об управлении вычислительным процессом, обычно имеют в виду условные операторы и циклы. Пиклы Циклы в R используются сравнительно редко. «Винить» в этом следует век- торизацию вычислений. Тем не менее иногда именно циклы являются наи- более простым путём решения задачи. Синтаксис циклов R очень похож на синтаксис циклов С-подобных язы- ков программирования. Циклы в R могут вкладываться друг друга в соответ- ствии с обычными правилами вложения циклов. Цикл со счётчиком Цикл со счётчиком записывается следующим образом: for (i in start:stop) { стело цикла> } Его удобно применять, когда количество повторений команд, помещён- ных в тело цикла, известно заранее. Предположим, например, что в банк по- ложена сумма в 1000 долларов сроком на 5 лет с процентной ставкой 12% го- довых, и нужно узнать, сколько денег окажется на счету вкладчика по окон- чании срока вклада. sum© <- 1000 # исходная сумма rate <- .12 # процент num_years <- 5 # срок хранения sum <- sum© # текущая сумма for (i in l:num_years) { sum <- sum + rate*sum0 print(c(i,sum)) # выводим на печать номер года и накопленную сумму } ## [1] 1 1120
Пиклы 55 ## [1] 2 1240 ## [1] 3 1360 ## [1] 4 1480 ## [1] 5 1600 Заметим, что «эхо» внутри цикла не работает и для вывода промежуточ- ных данных используется функция print. Решение при помощи цикла - простое и прямолинейное, но не самое ко- роткое. Немного подумав, можно прийти к формуле: sumO*(l+num_years*rate) ## [1] 1600 Оценим время, затраченное на заполнение при помощи цикла элементов динамического массива (вектора х): п <- 50000 х <- с() system.time( for (i in l:n) { x <- c(x,i) } ) # число элементов # создадим пустой вектор # дополним вектор х очередным элементом ## user system ## 5.348 0.072 elapsed 5.426 Обратите внимание на то, как вкладывается цикл в функцию оценки вре- мени system.time. Аргумент user функции system, time возвращает процессорное время в секундах, затраченное текущим процессом (сеансом работы с R). system - это процессорное время, затраченное операционной системой на выполне- ние работ, связанных с текущим процессом (открытие файлов, операции вво- да/вывода, запуск других процессов ит. п.). Наконец, elapsed = user+system содержит полное время работы скрипта. Использованный нами способ инициализации вектора, очевидно, неэф- фективен. В первую очередь желательно зарезервировать место под элемен- ты вектора: х <- vector(length = n) # создадим вектор заданной длины system.time( for (i in l:n) { x[i] <- i } ) # # user system elapsed # # 0.096 0.008 0.103
56 Управление проиессом вычислений Экономия времени налицо. Однако лучший результат достигается при ис- пользовании векторизации: system.time(x <- l:n) ## user system elapsed ##0 0 0 Такая ситуация типична для R: циклы работают медленнее, чем вектори- зованные функции. Всё происходит так быстро, что system. time не успевает отследить изме- нения. Для более точной оценки времени выполнения скрипта используем пакет tictoc: library(tictoc) # загрузим пакет tictoc tic() # засечём время x <- l:n toc() # остановим таймер и выведем результат ## 0.004 sec elapsed О работе с пакетами расширений R мы поговорим в главе 8. Сейчас же, за- вершая разговор об оценке времени работы скрипта, покажем, как получить информацию об используемой версии R, операционной системе и загружен- ных пакетах, без которой данные о времени выполнения не имеют смысла: sessionlnfo() # # R version 3.2.3 (2015-12-10) # # Platform: i686-pc-linux-gnu (32-bit) # # Running under: Ubuntu 16.04 LTS # # # # locale: # # [1] LC_CTYPE=ru_RU.UTF-8 LC_NUMERIC=C # # [3] LC_TIME=ru_RU.UTF-8 LC_COLLATE=ru_RU.UTF-8 # # [5] LC_MONETARY=ru_RU.UTF-8 LC_MESSAGES=ru_RU.UTF-8 # # [7] LC_PAPER=ru_RU.UTF-8 LC_NAME=C # # [9] LC_ADDRESS=C LC_TELEPHONE=C # # [11] LC_MEASUREMENT=ru_RU.UTF-8 LC_IDENTIFICATION=C # # # # attached base packages: # # [1] stats graphics grDevices utils datasets methods base # # # # loaded via a namespace (and not attached): # # [1] magrittr_1.5 formatR_1.4 tools_3.2.3 htmltools_0.3.5 # # [5] yaml_2.1.13 Rcpp_0.12.5 stringi_l.1.1 rmarkdown_0.9.6 # # [9] knitr_1.13 stringr_l.O.0 digest_0.6.9 evaluate_0.9
Пиклы ❖ 57 Пикл с предусловием Цикл с предварительным условием используется тогда, когда число повто- рений тела цикла неизвестно, зато известно условие, которое должно быть выполнено в результате этих повторений. Тело цикла выполняется до тех пор, пока истинно логическое условие: while (логическое_условие) { <тело цикла> } Вновь рассмотрим задачу о банковском вкладе. Только теперь процент будет не простой, а сложный, то есть будет добавляться к исходной сумме вклада. Определим, через сколько лет сумма, накапливаемая таким спосо- бом, превысит ту, что удалось скопить с помощью простого процента (1600), и сколько именно денег удастся скопить. sum© <- 1000 rate <- .12 i <- 0 sum <- sum© while (sum < 1600) { sum <- sum ♦ rate*sum i <- i + 1 print(c(i,sum)) } ## [1] 1 1120 ## [1] 2.0 1254.4 ## [1] 3.000 1404.928 ## [1] 4.000 1573.519 ## [1] 5.000 1762.342 Естественно, что и в этом случае можно было записать формулу: sumO*(l+rate)Anum_years ## [1] 1762.342 Для преждевременного выхода из цикла используется команда break. Пос- ле неё управление передаётся во внешний цикл, команде, следующей за цик- лом, из которого мы вышли: for (i in 1:3) { for (j in 1:3) { if (j==2) break cat(sprintf(' loop: i=%d j=%d\n',i,j)) } cat(sprintf('break: i=%d j=%d\n',i,j)) }
58 Управление процессом вычислений ## loop: ## break: ## loop: ## break: ## loop: ## break: i=l j=l i=l j=2 1=2 j=l 1=2 j=2 1=3 j=l 1=3 j=2 Команда next выполняет переход к следующей итерации цикла. Условные операторы Скалярный условный оператор if, или, проще говоря, обычный if, записы- вается так: if (логическое_условие) { операторы выполняются если логическое_условие истинно } else { операторы выполняются если логическое_условие ложно } Конструкции else-if в R нет. Обратите внимание, что если в коде записано: if (condition == TRUE) х <- TRUE else x <- FALSE то R воспринимает первую строку как закончившуюся. Вторая строка начи- нается с else, который будет истолкован как самостоятельный оператор. По- скольку такого оператора не существует, то появится сообщение об ошибке: ## Error: unexpected 'else' in " else" Чтобы R интерпретировал else как часть предшествующего if’a, нужно указать ему при помощи фигурных скобок, что if ещё не закончился: if (condition == TRUE) { х <- TRUE } else {x <- FALSE} Есть в R и скалярный switch, но используется он редко. Векторный условный оператор if else выполняет проверку условия сра- зу для всего вектора и возвращает логический индекс, в соответствии с кото- рым выполняется то или иное действие:
Резюме 59 ±Те1зе(логическое_условие, выполнить_если_ИСТИНА, выполнить_если_ЛОЖЬ) Пример: х <- с(6:-4) sqrt(x) # выдаёт предупреждение из-за наличия отрицательных аргументов # # Warning in sqrt(x): созданы NaN # # [1] 2.449490 2.236068 2.000000 1.732051 1.414214 1.000000 0.000000 # # [8] NaN NaN NaN NaN NaN означает ”Not-a-Number”, то есть результат не является числом. Значения аргументов вначале необходимо отфильтровать, заменив отри- цательные числа какими-нибудь другими значениями (в нашем случае удоб- но использовать NA), и лишь затем приступать к вычислению корня. х1 <- ifelse(x>=0,х,NA) # фильтр для данных Х1 # # [1] 6 5 4 3 2 1 0 NA NA NA NA sqrt(xl) # # [1] 2.449490 2.236068 2.000000 1.732051 1.414214 1.000000 0.000000 # # [8] NA NA NA NA Однако мы снова получим предупреждение в ситуации: ifelse(x>0,sqrt(x),NA) # # Warning in sqrt(x): созданы NaN # # [1] 2.449490 2.236068 2.000000 1.732051 1.414214 1.000000 NA # # [8] NA NA NA NA - потому что, несмотря на условие, sqrt() выполняет операцию над исход- ным «нефильтрованным» вектором. Резюме Циклы в R используются сравнительно редко, поскольку они, как правило, работают существенно медленнее функций * apply, выполняющих группо- вые операции над элементами массивов, списков и таблиц. Синтаксис цик- лов R мало чем отличается от большинства С-подобных языков программи- рования. Особенностью R является наличие векторного условного оператора ifelse.
Глава Базовая графика В этой главе мы рассмотрим основы работы с базовой двумерной графикой R. «Базовой» она названа потому, что реализована в пакете graphics, кото- рый поставляется вместе с R и подключается автоматически. Графические возможности R весьма богаты, и их описанию посвящён ряд монографий. Так, в R существуют пакеты lattice и ggplot2, возможности которых даже шире, чем у graphics, и которые используют другую идеоло- гию построения графиков. Список книг с описанием графических возмож- ностей R вы найдёте в конце этой главы в разделе «Резюме и ссылки». Напомним, что графики в R выводятся в различные графические устрой- ства: окна, если вывод осуществляется на экран, или файлы, если данные сохраняются на диске. Функции низкого и высокого уровней Построим график синусоиды, координаты которой хранятся в векторах х и у: х <- seq(-pi,pi,.1) у <- sin(x) График можно составить из отдельных элементов (рис. 5.1): plot.new() plot.window(xlim = c(-pi,pi), ylim = c(-l,l)) points(x,y) axis(l) axis(2) box() title(xlab = "x") title(ylab = "y")
Функции низкого и высокого уровней 61 Рис. 5.1 ❖ График синусоиды, построенный из отдельных элементов Функции, реализующие тот или иной элемент графика, называются функ- циями низкого уровня, или низкоуровневыми. В данном случае это: • plot. new, которая создаёт новое графическое окно; • plot. window - задаёт пределы изменения х- и г/-координат на графике и другие графические параметры (те же, что и в функции раг() - см. далее); • points - строит график в виде точек; • axis - добавляет на график оси координат: 1 - снизу, 2 - слева, 3 - сверху, 4 - справа; • box - строит вокруг графика рамку; • title - выводит пояснения к графикам. Например: параметры xlab, ylab задают обозначения осей координат, a main - заголовок графика. Тот же график можно построить при помощи всего лишь одной функции: piot(x,y) plot представляет собой пример функций высокого уровня, которые на- целены на реализацию наиболее распространённых видов графиков. Как правило, в функциях высокого и низкого уровней используются одни и те же графические параметры. Вот, например, как можно оформить пояс- нения к графику при помощи параметров main, xlab, ylab функции plot (рис. 5.2): plot(x, у, main = xlab = ylab = ) "2d-Graph", "Axis X", "Axis Y"
62 Базовая графика 2d-Graph Axis X Рис. 5.2 ❖ График синусоиды, построенный функцией plot По умолчанию точки данных изображаются в виде незакрашенных круж- ков чёрного цвета. Символы для отображения точек (маркеры) настраива- ются параметром pch (plot character), цвет - параметром col. Пусть, например, точки данных будут отображаться красными «звёздоч- ками» (рис. 5.3): plot(x,y,pch="*",col="red") Маркеры можно задавать не только при помощи строк, но и по номерам. Первые двадцать видов маркеров показаны на рис. 5.4.
Функции низкого и высокого уровней ❖ 63 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Рис. 5.4 ❖ Маркеры Помимо вида маркера в целом, можно настраивать отдельно цвет заполне- ния и цвет границы маркера. Цвета можно задавать по номерам (рис. 5.5), строкам названий цветов (вро- де «red») и шестнадцатеричным строкам вида #RRGGBB (как в языке HTML). 2 3 4 5 6 7 8 Рис. 5.5 ❖ Цвета и их номера Названия цветов, поддерживаемых R, возвращает функция colors. В на- стоящее время таких цветов 657. Функция col2rgb преобразует название цвета в его RGB-представление, а функция rgb вычисляет по этому представлению строку #RRGGBB. Строить ли кривые на графике при помощи точек, линий или и так, и этак, определяет параметр type. Кроме того, для этой цели существуют специаль- ные низкоуровневые функции, например lines (рис. 5.6): х <- seq(-pi,pi, .3) у <- sin(x) plot(x,у, CO1="#00FF00", type="b" # тип отображения кривой U "р" - точки (по умолчанию), # "1" - линия, # "Ь" - точки и линия (both)) ) lines(x+.3, у, col="#0000FF")
64 Базовая графика Рис. 5.6 Тип линии (сплошная, штриховая, пунктирная и т. п.) определяется пара- метром Ity (solid, dashed, dotted... или по номерам). Параметр Iwd задет толщину линии в пунктах (рис. 5.7). х <- seq(-pi,pi,.1) у <- sin(x) plot(x,y, type="l", # линия lty="dashed", # штриховая Iwd=2 # толщиной 2 пт. ) -3-2-10 1 2 3 x Рис. 5.7 Значение параметра type="n" позволяет создать пустой график (рис. 5.8).
Глобальные и локальные параметры графиков 65 plot(x, у, type="n") Рис. 5.8 Глобальные и локальные параметры графи- ков Для глобальной настройки параметров графиков используется функция par. Вызванная без аргументов, она позволяет узнать текущие значения всех гра- фических параметров (в R версии 3.3.1 их 72). Указав в качестве аргумента строку с именем графического параметра, по- лучим значение одного этого параметра: par("col") ## [1] "black" По умолчанию для рисования используется чёрный цвет. Покажем, чем отличаются локальные параметры, которые задаются в кон- кретной функции построения графика (plot, points, lines), от глобальных параметров, устанавливаемых в par (рис. 5.9). х <- seq(-pi,pi,.3) у <- sin(x) par(col="red") plot(x,y,type="b") lines(x+.3, y) points(x-.3, y, col="blue")
66 Базовая графика Рис. 5.9 Функция par устанавливает единый для всех графиков цвет вывода кри- вых и осей координат (красный). Локальное значение параметра (синий цвет последнего графика) ожидаемо перекрывает действие глобального. Действие настроек, сделанных в par, продолжается до конца сеанса рабо- ты с R. Чтобы восстановить предыдущие значения графических параметров, используют следующий приём: par О old_par <- раг() раг(...) <plot> par(old_par) # узнать текущие настройки # сделать их копию # создать новые настройки # построить необходимые графики # восстановить прежние настройки Список наиболее употребительных графических параметров приведен в табл. 5.1. Таблица 5.1 ❖ Наиболее употребительные графические параметры Обозначение Значение main xlab,ylab xlim, ylim pch cex col Ity iwd Заголовок графика Подписи к осям х и у Диапазон изменения данных по осям х и у Маркер, которым отображается точка данных Размер маркера Цвет Тип линии (solid, dashed, dotted...) Толщина линии
Легенда 67 Легенда Функция legend добавляет к текущему графику легенду - список поясне- ний, касающихся построенных на графике кривых. Рассмотрим построение легенды к графику на следующем примере (рис. 5.10): # Строим три графика х <- seq(-pi, pi, len=20) plot(x, sin(x), type="l", ylim=c(-1.2, 2.1), col=2, lty=4, lwd=2) points(x, cos(x), pch=8, col=3) lines(x, tan(x), type="b", lty=l, pch=4, col=4) # Строим легенду legend(-1.5, 2.1, c("sin", "cos", "tan"), col = c(2, 3, 4), text.col = "green4", Ity = c(4, -1, 1), pch = c(NA, 8, 4), bg = "gray90" ) # координаты левого верхнего угла # подписи # цвет линии в легенде # цвет подписи # тип линии в легенде # маркер в легенде # цвет фона в легенде Параметр ylim в plot нужен, чтобы качественно отобразить график тан- генса. Иначе диапазон изменения ?/-координаты будет рассчитываться авто- матически по значениям синуса и график тангенса окажется сплюснут. Каждое из значений -1 и NA в параметрах Ity и pch легенды означает от- сутствие соответствующего элемента: для Ity - это отсутствие линий у гра- фика, построенного точками; для pch - отсутствие маркеров у кривой, по- строенной с помощью линий. Комбинации графиков Построим два графика в общих осях координат (рис. 5.11):
68 Базовая графика х <- seq(-pi,pi,.1) yl <- sin(x) y2 <- dnorm(x) plot(x, yl, type="l", colored") lines(x, 2*y2, col="green") grid() Рис. 5.11 Функции lines и points, в отличие от plot, не перерисовывают содержи- мое графического окна, а просто добавляют в него график. Функция grid строит на графике сетку. Построить несколько графиков в общем окне, но разных осях координат, можно при помощи параметра mf row (или mf col): mfrow = с(число_строк,число.столбцов) Расположение графиков в графическом окне можно представить в виде матрицы. Допустим, что в одной строке мы хотим разместить два графика. Выглядеть это будет так (рис. 5.12): par(mfrow=c(l,2)) plot(x,yl,mfrow=c(l,2)) plot(x,y2) X Рис. 5.12
Графики функций 69 Заметим, что поскольку mf row является глобальной настройкой, то все по- следующие графики в текущем сеансе работы будут отображаться по два в строке. Чтобы вернуться к обычному способу отображения графиков, нужно восстановить с помощью par настройки, принятые по умолчанию: par(mfrow=c(l,1)) Чтобы построить каждый график в отдельном окне, нужно перед очеред- ным вызовом plot создавать новое графическое окно (устройство). # dev.new() - при первом вызове plot создаётся новое графическое устройство plot(x,yl) # ... и дополнительно вызывать dev.new() не нужно dev.new() # Создать новое графическое устройство plot(x,y2) # Вывести в него график Кроме того: • dev. off () - закрывает текущее графическое окно; • graphics. off () - закрывает все графические окна в сессии. Г рафики функций Графики функциональных зависимостей вида у = /(т) строятся при помо- щи функции curve (рис. 5.13): curve(xA2, from=-l, to=l, main="sqr(х)", ylab="y = хЛ2") sqr(x) x Рис. 5.13
70 Базовая графика Экспорт в файлы Справка по доступным графическим устройствам: ?Devices Узнать, в каких форматах можно сохранить график, позволяет также функ- ция capabilities: capabilities() ## jpeg png tiff tcltk Xll aqua ## TRUE TRUE TRUE TRUE FALSE FALSE ## http/ftp sockets libxml fifo cledit iconv ## TRUE TRUE TRUE TRUE FALSE TRUE ## NLS profmem cairo ICU long.double libcurl ## TRUE TRUE TRUE TRUE TRUE TRUE Она, в частности, возвращает имена функций, настраивающих параметры вывода в тот или иной графический формат. Вот как происходит сохранение графика в файл формата PNG: # Описание устройства вывода png("myfile.png", width=420, height=340, units="px") # Вывод графика в устройство plot(x,yl) # Закрыть устройство dev.off() # Теперь можете посмотреть полученный файл Резюме и ссылки Мы лишь прикоснулись к графическим возможностям R. Посмотрим, что можно почитать, чтобы узнать о них больше. • Кабаков Р.И.Кв действии. Анализ и визуализация данных в програм- ме R. М.: ДМК Пресс, 2014. Глава 3 этой книги дополнит и расширит материалы главы. Тема графики развивается в главах 6 и И, а глава 17 представляет собой краткое знаком- ство с пакетами lattice и ggplot2. • Зарядов И. С. Введение в статистический пакет R: типы переменных, структуры данных, чтение и запись информации, графика. М.: Изд-во РУДН, 2010. Глава 7 посвящена базовой графике. Всего понемногу: базовая графика, lattice и ggplot2 в следующих двух книгах: • Murrel Р. R Graphics. Chapman & Hall/CRC, 2005. • HilfigerJh.J. Graphing Data with R. O'Reilly Media, 2016.
Резюме и ссылки ❖ 71 Только по lattice: • От разработчика пакета: Sarkar D. Lattice: Multivariate Data Visualiza- tion with R. Springer-Ver lag New York, 2008. По ggplot2: • От разработчика: Wickham H. ggplot2: Elegant Graphics for Data Analy- sis. Springer-Verlag New York, 2009. • Сборник рецептов: Chang W.R. R Graphics Cookbook. O’Reilly Media, 2013.
Глава Функции Функция - это фрагмент кода, предназначенный для решения какой-либо конкретной задачи. Мы уже знакомы с большим количеством готовых функ- ций R и теперь научимся создавать функции сами, чтобы расширить возмож- ности этого языка. Создание функций Синтаксис функции имеет вид: function(cnkicoK_apryMeHTOB) { тело функции } Например: function(x) { хЛ2 } или короче: function(x) хЛ2 У функции должно быть имя, чтобы её можно было вызвать: sqr <- function(x) хЛ2 sqr(2) ## [1] 4 Но можно использовать и анонимные функции: (function(x) хЛ2)(3) ## [1] 9 Обратите внимание, что присваивание имени функции выглядит так же, как присваивание значения переменной. И действительно, переменная sqr теперь относится к классу функций:
Создание функиий ❖ 73 class(sqr) ## [1] "function" Вы можете проанализировать структуру функции, используя: • имя_функции - без круглых скобок оно возвращает код функции; • body () - возвращает код, помещённый в теле функции; • f ormals() - возвращает список формальных параметров. sqr # # function(x) хЛ2 body(sqr) # # хЛ2 formals(sqr) # # $Х По умолчанию функция в R возвращает то значение, которое возвращает- ся последней строкой её кода. Но для определённости лучше использовать return(): sqr <- function(x) { return(xA2) } Список аргументов функции может быть пуст, и тогда после имени функ- ции указываются лишь круглые скобки (). В списке можно задавать значе- ния аргументов по умолчанию в виде х = а, а также формировать список аргументов переменной длины, добавляя . . . в конце списка. Таким образом, открывается широкий простор для создания функций- «обёрток». Например, нам нужна функция, строящая графики линиями крас- ного цвета. Все остальные её параметры - такие же, как у стандартной plot. Создадим функцию-обёртку над plot. То есть функцию, основу которой составляет plot, но с некоторыми доработками - в данном случае с фикси- рованным значением параметра цвета (col): plot.red <- function(x, у, ...) { plot(x, у, col = "red", ...) } Вот как это работает (рис. 6.1):
74 Функиии х <- seq(-pi,pi,.1) у <- sin(x) plot.red(x, y) x Рис. 6.1 Функции в R могут возвращать несколько объектов, относящихся к раз- ным типам данных. Для этого такие объекты нужно предварительно помес- тить в список: sqr <- function(x) { val <- хЛ2 txt <- "Squaring" ret <- list(value=val,text=txt) return(ret) } sqr(2) # # $value # # [1] 4 # # # # $text # # [1] "Squaring" Локальные и глобальные переменные. Обла- сти видимости Переменные, созданные внутри функции, являются локальными, то есть их значения доступны (видимы) только внутри данной функции или вложен- ных в неё функций. Соответственно, и функция «видит» переменные, задан- ные вне её пределов:
Локальные и глобальные переменные. Области видимости ❖ 75 а <- 5 foo <- function() { print(a) } foo() # напечатает 5 Глобальная область видимости Локальная область видимости Рис. 6.2 ❖ Глобальная и локальная области видимости переменных а <- 5 foo <- function() { b <- 10 # локальная переменная foo print(а) # foo видит а print(b) # foo видит b } foo () # # [1] 5 # # [1] 10 Хотя переменная а задана вне функции и не передаётся в неё через вход- ные параметры, проблем с ней не возникает (рис. 6.2). Л вот локальная пере- менная b в основной программе не видна: print(b) # b в основной программе не определена # # Error in print(b): объект 'b' не найден Области видимости вкладываются друг в друга (рис. 6.3). Глобальные переменные внутри функции можно задать при помощи опе- ратора присваивания «-: а <- 5 foo <- function () { b «- 10 # другой вариант: # assign("b", "new", envir = .GlobalEnv) } foo О b # теперь b видима из основной программы ## [1] 10
76 Функции Глобальная область видимости too function ) { Ла <- 16 goo functionQ { i print(a) ------- goo() }-------------------' Область видимости внешней функции Область видимости внутренней функции foo() # напечатает 16 Рис. 6.3 ❖ Вложение областей видимости Диагностические сообщения Для вывода диагностических сообщений используются функции: • message - выводит сообщение; • warning - выводит сообщение об ошибке, не останавливая выполне- ния команд; • stop - выводит сообщение об ошибке и останавливает выполнение. Функции в качестве аргументов В R функцию можно передавать в качестве аргумента в другие функции. Допустим, у вас есть функция toPercent, преобразующая вектор чисел - процентов - в вектор строк со знаком '%'. В ней используется округление, выполняемое с помощью функции round: toPercent <- function(x) { percent <- round(x * 100) paste(percent, "%", sep = "") } a <- c(0.15, 1.26, 0.98, 1.02) toPercent(a) ## [1] "15%" "126%" "98%" 102%"
Функции в качестве аргументов 77 Вдруг, неизвестно по какой причине, вам понадобилось выполнить округ- ление при помощи другой функции - signif. Эта функция позволяет ука- зывать, после какого знака следует округлять число. Что делать? Писать новую функцию toPercent не слишком разумно, так- как она будет почти точной копией предыдущей. Вместо этого слегка изме- ним toPercent: toPercent <- function(x, FUN = round, ...) { percent <- FUN(x * 100, .».) paste(percent, "%", sep = "") } Если не указывать явно аргумент FUN, то новая функция будет работать так же, как прежняя, - ведь по умолчанию используется всё тот же round. Покажем это: а <- с(0.15, 1.26, 0.98, 1.02) toPercent(a) ## [1] "15%" "126%" "98%" "102%" Вместо многоточия '.. .' можно указывать дополнительные аргументы, которые будут передаваться функции FUN. Например: toPercent(a, FUN = signif, digits = 3) ## [1] "15%" "126%" "98%" "102%" Здесь: • R присваивает код функции signif аргументу FUN, и функция FUN() становится точной копией signif (); • toPercent принимает аргумент digits (число знаков, после которых signif выполняет округление) и передает его в FUN(). Обратите внимание, что при передаче функции в качестве аргумента ис- пользуется только имя функции, но не скобки - signif, а не signif (). По- следний вариант R трактует как вызов функции, к тому же ошибочный из-за отсутствия аргументов. Функция может быть неявным аргументом другой функции. Так, если в вызове функции g () g <- function(x, FUN = NULL) { if (missing(FUN)) FUN <- function(x) хЛ2 FUN(x) } используется только первый аргумент х, без которого никак не обойтись, то «скрытая» функция FUN возводит значение х в квадрат
78 Функции g(2) # # [1] 4 missing проверяет, не пропущен ли аргумент при вызове функции, и если это так, то запускает FUN. Функцию FUN можно задать явно. Например, в виде анонимной функции: g(2, FUN = function(x) хЛ3) # # [1] 8 Функциональное программирование По сути, R является языком функционального программирования. Поэтому практически всё в нём делается при помощи функций. Например, элементы векторов выбираются при помощи функции-квадратпой скобки: х <- с(1,2,3) х[1] # # [1] 1 Такая форма записи привычна, но функцию-квадратную скобку можно пе- реписать и в более «функциональном» виде: # # [1] 1 За следующей записью: * + (1Г * *'(2,3)) кроется обычное 1+2*3 а операцию присваивания можно выполнить и так: assign("а",1) а # # [1] 1 Создадим и мы какой-нибудь оператор. Так, в R нет готового оператора +=. Ликвидируем этот пробел:
Резюме ❖ 79 /%+=%/ <- functional, о2) eval. parent(substitute(ol <- ol + о2)) х <- 1 х %+=% 2 х ## [1] 3 Резюме В R вы можете присваивать функции как значения переменных, передавать их в виде аргументов в другие функции, хранить функции в списках, созда- вать функции внутри функций и даже возвращать в качестве результата вы- полнения функции. К сожалению, рассказ об этом уведёт нас слишком далеко от темы сбора данных. Поэтому адресую вас к книге Хэдли Уикэма: • Wickam Н. Advanced R. CRC Press, 2015. Уикэм разработал для R несколько весьма популярных пакетов, в частно- сти ggplot2 и rvest. Последний мы планируем использовать для извлече- ния данных из веб-страниц. Функциональному программированию посвящена вторая часть книги, но и кроме него там есть много интересного.
Глава Факторы и даты Помимо количественных данных, мы будем иметь дело с данными иного ро- да, которые нельзя представить в виде чисел. Например, цвета светофора - «красный», «жёлтый» и «зелёный» - следуют в определённом порядке, от- личном от алфавитного3. Это сближает их с числами, однако сложение или вычитание названий цветов лишено всякого смысла. Для таких данных в R есть специальный тип данных - факторы. Кроме того, нам предстоит научиться работать с датами. Это малозамет- ный, но весьма полезный тип данных, поскольку многие ценные сведения, например финансовые отчёты и биржевые сводки, привязаны к конкретным моментам времени. Категориальные данные Большинство видов данных, которые мы рассматривали до сих пор, - чис- ловые и логические - так или иначе можно представить при помощи чисел. Числа можно упорядочивать и выполнять над ними арифметические опера- ции. Но есть данные, с которыми так поступить не получится. Рассмотрим, например, список товаров в магазине. Число наименований товаров (категорий) всегда ограничено. При этом каждый товар относится только к одной категории. Мы можем отличить "йогурт" от "велосипед", можем определить количество йогуртов и велосипедов в нашем магазине. Мы даже можем упорядочить наименования товаров по алфавиту. Но нет смысла складывать йогурты и велосипеды. И даже если каждой категории товара присвоить числовой идентификатор, то складывать такие числа всё равно будет бессмысленно. Итак, существуют данные, которые обычно представляют собой строко- вые значения из ограниченного набора категорий (названия городов, ФИО сотрудников, марки автомобилей и т. п.) и для которых нельзя ввести алгеб- раические операции. Такие данные называются категориальными данными, или факторами. Но если данные представляются в виде строк, то почему бы и не обойтись строками, не вводя нового типа данных? Посмотрим, какие преимущества 3 Строки упорядочиваются в алфавитном или лексикографическом порядке.
Категориальные данные 81 даёт нам использование факторов. Пусть у нас есть три вида фруктов и мы хотим подсчитать количество фруктов каждого вида и построить график. fruits <- c("apple", "orange", "orange”, "banana", "apple", "orange") is.charасter(fruits) # # [1] TRUE is.vector(fruits) # # [1] TRUE Пока всё идёт хорошо. Но если мы захотим узнать количество фруктов од- ного вида, то нам придётся считать количество вхождений строки с наиме- нованием этого вида фруктов в вектор fruits. Сделать это несложно, но всё же... И если понадобится построить график, например столбчатую диаграм- му «вид фруктов - количество», то нам также придётся повозиться. Л что факторы? Для начала построим из вектора fruits одноимённый фактор: fruits <- as.factor(fruits) Теперь это больше не строковый вектор - это фактор: is.character(fruits) # # [1] FALSE is.vector(fruits) # # [1] FALSE is.factor(fruits) ## [1] TRUE Создавать факторы «с нуля» можно функцией factor. Вернёмся к фруктам. Их у нас три категории: levels(fruits) ## [1] "apple" "banana" "orange"
82 Факторы и даты Функция levels позволяет узнать или установить число категорий, или уровней, данного фактора. Сделать это можно и при создании фактора, управ- ляя параметром levels в f actor (). Есть в этой функции и возможность упо- рядочить категории (ordered), а также ввести для них специальные метки (labels). «Всё это чудесно, - скажет нетерпеливый читатель, - но как насчёт реше- ния задачи?» Л она уже решена. Вот количество фруктов каждого вида: table(fruits) ## fruits ## apple banana orange ##213 Л вот и диаграмма (рис. 7.1): Рис. 7.1 Функция which. max позволяет определить уровень, встречающийся чаще всего: which.max(table(fruits)) ## orange ## 3
Дата и время ❖ 83 Дата и время Работать с датами... непривычно. Даты нельзя складывать, но их можно вы- читать, отвечая на вопрос: «Через сколько дней (часов, минут,...) после одно- го события произошло другое событие?» При этом необходимо учитывать возможные различия в часовых поясах. К датам можно добавлять и вычи- тать из них целые числа. Даты могут быть представлены в разных форма- тах... В общем, для работы с датами существует специальный тип данных - Date. Создадим переменную типа Date при помощи функции as. Date, указав в качестве даты День космонавтики: cd <- as.Date("1961-Q4-12") str(cd) ## Date[l:l], format: "1961-04-12" Какой день недели это был? weekdays(cd) ## [1] "среда" Какой месяц? months(cd) ## [1] "Апрель" Л какой квартал года? quarters(cd) ## [1] "Q2" Какая дата наступила через 7 дней? cd + 7 ## [1] "1961-04-19" Сколько дней оставалось до старта Германа Титова? as.Date("1961-08-6") - cd ## Time difference of 116 days Функция Sys. Date возвращает текущую дату, а функция Sys. time - те- кущие дату и время.
84 Факторы и даты Sys.Date() ## [1] "2016-08-12" Sys.time() ## [1] "2016-08-12 11:06:36 EEST" Здесь EEST - восточно-европейское летнее время (Eastern European Sum- mer Time, EEST). Оно соответствует времени Гринвичского меридиана (GMT) плюс 3 часа: EEST = GMT + 3. Sys. timezone() возвращает часовой пояс, установленный в локальных настройках операционной системы (локали). По умолчанию число, месяц и год разделяются в строке даты символами ' -иначе выражение не будет истолковано как дата. Формой представле- ния даты можно управлять при помощи параметра format. Виды форматов даты и времени приведены в табл. 7.1 и 7.2. Таблица 7.1 ❖ Форматы представления даты Формат 011 исан ие %у Год без указания столетия %Y Год с указанием столетия %т Месяц в виде числа (от 01 до 12) %Ь Сокращённое название месяца в соответствии с текущей ло- калью %В Полное название месяца в текущей локали %d День месяца (от 1 до 31) %а Сокращённое название дня недели, в соответствии с текущей локалью %А Полное название дня недели в текущей локали %w День недели в виде числа (от 0 до 6, где Sunday соотвествует 0) Таблица 7.2 ❖ Форматы представления времени Формат Описание %1 %Н %М Часы (от 01 до 12) Часы (от 00 до 23) Минуты (от 00 до 59)
Дата и время 85 Формат Описание %S Секунды (от 00 до 61) %р индикатор ЛМ/РМ Пусть число, месяц и год в исходных данных разделены пробелами: as.Date("12 4 1961", format="%d %m %Y") ## [1] "1961-04-12" Если бы мы не указали нужный формат, то получили бы сообщение об ошибке: as.Date("12 4 1961") # # Error in charToDate(x): character string is not in a # # standard unambiguous format Добавим к дате время запуска космического корабля «Восток-1» -9 ч. 7 мин. по московскому времени: # Запишем строку с датой и временем cd_datetime <- "12 Апрель 1961 09:07:00" # Зададим формат представления даты cd.format <- "%d %8 %Y %H:%M:%S" # Выясним название часового пояса (оно зависит от операционной системы) tz_names <- 01sonNames() # Выберем нужный часовой пояс из tz_names # Воспользуемся функцией as.POSIXct, которая похожа на as.Date, # но позволяет дополнительно задавать часовой пояс tz (time zone) cd <- as.POSIXct(cd_datetime, format = cd_format, tz = "Europe/Moscow") cd # # [1] "1961-04-12 09:07:00 MSK" Сменим формат представления даты па Число/Месяц/Год, для чего ис- пользуем функцию format: format(cd, "%d/%m/%y") # # [1] "12/04/61" Л теперь вернёмся к cd_f ormat: format(cd, cd_format) ## [1] "12 Апрель 1961 09:07:00"
86 Факторы и даты Иногда может понадобиться извлечь из даты какой-то определённый эле- мент, например минуты. В этом случае дату необходимо вначале преобразо- вать в тип данных POSIXlt, а затем извлечь из неё минуты (min): # преобразуем дату к типу POSIXlt cd <- as.POSIXlt(cd) str(cd) # # POSIXlt[1:1], format: "1961-04-12 09:07:00" # и извлечём из неё минуты cd$min # # [1] 7 Резюме Категориальные данные - это данные, значения которых делятся на катего- рии. Такие данные отображают качественную информацию об объекте, к ко- торой не применимы арифметические операции. Для хранения категориаль- ных данных в R существует специальный тип данных - факторы. R позволяет манипулировать датами, представленными в разных форма- тах, с учётом часовых поясов. Для работы с датами существуют типы данных Date, POSIXct и POSIXlt.
Глава Пакеты Привлекательной стороной R является наличие у него многочисленных па- кетов, расширяющих возможности языка. Среди этих пакетов есть как биб- лиотеки, созданные специально для R, так и интерфейсы к сторонним биб- лиотекам и сервисам (например, к API социальных сетей). Таким образом, пользователь R, занимающийся сбором и анализом данных, может, не поки- дая R, применять лучшие (или наиболее известные) программные инстру- менты, которые только существуют в этой области. При этом зачастую одна и та же задача может решаться разными способами и при помощи разных пакетов. Чтобы понять, насколько велико разнообразие средств, оказавшихся в на- шем распоряжении, достаточно сказать, что в основном хранилище R - CRAN - на 06.09.2016 г. доступно для загрузки 9096 пакетов. Оборотной стороной такого разнообразия является проблема выбора нуж- ного пакета. К счастью, в R существует несколько средств, позволяющих ре- шить и эту задачу. Установка и вагрузка Пакет (package) в R представляет собой набор функций, данных и скомпили- рованного кода, предназначенный для решения какой-то определённой зада- чи. Для установки пакета наберём в командном окне R: install.packages("имяПакета") Чтобы использовать возможности пакета, его нужно загрузить в память: library(имяПакета) В принципе, этой информации достаточно, чтобы начать пользоваться па- кетом. Но аппетит приходит во время еды, так что, скорее всего, вам понадо- бится информация о том, какие именно пакеты у вас установлены и где они находятся:
88 Пакеты library() # вывод списка установленных пакетов search() # вывод списка пакетов, загруженных в память .libPaths() # возвращает каталог, в который установлены пакеты Установлен ли интересующий вас пакет? апу(дгер1("имяПакета", installed.packages())) Возможно, потребуется установить сразу несколько пакетов: install.packages( с("пакет!","пакет2",...) ) И наверняка понадобится периодически их обновлять: update.packages() # обновляет все установленные пакеты в диалоговом режиме Устанавливать пакеты удобно при помощи графического интерфейса. Так, в RStudio команды для работы с пакетами сгруппированы в меню Tools. Од- нако даже если в своей ежедневной практике вы будете пользоваться только графическим интерфейсом, помните, что его команды реализованы при по- мощи вызовов всё тех же функций R, с которыми мы познакомились выше. Перейдём теперь к загрузке пакетов. Кроме функции library, для этого используется очень похожая на неё функция require. Отличие между ними в том, что в случае, когда загружаемый пакет не установлен, library выда- ёт сообщение об ошибке, a require возвращает FALSE, и потому её удобно использовать в условиях вида: if (!require(package)) { install.packages(package) library(package) } которые обеспечивают загрузку пакета в случае его необходимости. В ходе загрузки пакет может сообщать различную диагностическую ин- формацию. Подавить вывод этой информации можно функцией suppressPackageStartupMessages. Вот как с её помощью загрузить пакет rvest: suppressPackageStartupMessages(library(rvest)) Иногда нам нужен не весь пакет, а лишь одна функция из него. В этом случае функцию можно вызвать в виде: имя_пакета: : имя_функции (), не ис- пользуя library или require. Например, вызвать sum(), принадлежащую пакету base, можно следующим образом:
Выбор пакета 89 base::sum( с(1,2, 3) ) ## [1] 6 Другое дело, что пакет base устанавливается вместе с R и загружается в память автоматически, так что подобное обращение к sum избыточно. Выбор пакета Если вы не знаете, какой именно пакет может помочь в решении стоящей перед вами задачи, то путь ваш лежит на CRAN Task Views - это настоящий справочник по пакетам R. Вначале выберем интересующую тему. Это могут быть, в частности: • Web Technologies and Services - доступ к данным по HTTP, разбор со- держимого веб-страниц, интерфейсы к API социальных сетей и т. и.; • Open Data - пакеты для доступа к источникам открытых данных, а так- же к средствам хранения (Dropbox, Amazon Simple Storage Service и т. п.) и распространения данных (Google Sheets, Scribd,...). Прелесть Task Views состоит в том, что по каждой теме в нём приведен обзор соответствующих пакетов, короткие аннотации к ним и сравнение реа- лизаций той или иной технологии (алгоритма) в разных пакетах. Поэтому, скорее всего, вы покинете Task Views, «присмотрев» для себя один или не- сколько пакетов. Примеры использования пакетов удобно искать на R-Bloggers - агрега- торе блогов, посвящённых R. Все наиболее значимые статьи из мира R, по крайней мере на английском языке, рано или поздно оказываются на этом сайте. Для решения более узких вопросов но работе с пакетом существует сервис StackOverflow. Наконец, R Documentation представляет собой поисковик документации по пакетам и отдельным функциям R, которые хранятся как в CRAN, так и в других репозиториях. Страница пакета на CRAN находится в поисковике по запросу: CRAN имя_пакета. Посмотрим, какую информацию о пакете она может нам дать. Справка и её разновидности На странице пакета в CRAN содержится его подробное описание (рис. 8.1), с указанием названия, номера версии, пакетов, от которых зависит его работа, имён разработчиков и т. п.
90 Пакеты rvMt: Easily Harvest (Scrape) Web Papes Wrappers around the “xml/ and ’httr1 packages to make it easy to download then manipulate. HTML and XML Version: Depends: Imports; Suggests: Published: Author Maintainer: BugRaports: License: URL: 0.3.2 Rte 3.0.1). хш!2 httr (г 0.5), selr>rtr tnagntrr testthat, kmtr, png, string! (z 0.3.1), rmarkdown, covr 2016-06-17 Hadley Wickham [aut, crej, RStudio [cph] Hadley Wickham <hadley at rstudio.com> https//githubcom/hadleyA*ves (/issues GPLJ NeedsCotnpilaiion: no Materials: In views CRAN checks: RLADML bi£A¥S WebTechnologies rvest results DoMnloedt Reference manual Vignettes: Package source: Windows binaries: rvest pdt Selectorgadget rvest 0.3 Z.tar.gz r-devob iwst 0,3.2.Zip, r-reloase; rvefit O VLrip. r-okirob nest 0.3.2.ДЦ OS X Mavericks binaries: г release: rvest_0.3,2.tgz, r-oldrel: rvesl_03.2.tgz □Id sources: rvest archive «•ЛГИ MpenMnClM: Reverse depends: iNOMADS Reverse imports addmshst finreportr. googleformr. gutenbergr. MazamaSpatialUtils. nbanesA. nip. r Unemployment Data, scholar. sejmRP. traits, лvbtlitiu. vslhlpudhiUvnd Reverse suggests f uro stat fiizTyioin. lemtscdata. rrcmisc, rex, rscopus Рис. 8.1 ❖ Страница пакета rvest в CRAN С точки зрения получения информации по использованию пакета, нам наиболее интересны: его аннотация, справочное руководство (reference ma- nual) и виньетки (vignettes). Справочные руководства к пакетам R выглядят стандартно: описание па- кета, названия функций и их назначение, описания параметров, примеры применения. Другое дело - виньетки - это особый вид справки, существующий в R. Виньетка больше похожа на статью, чем на обычную справку: в ней описа- на проблема и показано, как её можно решить при помощи данного пакета. Виньетка показывает, на какие категории можно разделить функции пакета и как эти функции взаимодействуют друг с другом в ходе решения задачи. Виньетки есть у многих пакетов. Увидеть, какие виньетки установлены в данный момент па вашем компьютере, можно с помощью функции browsevignettes. Чтобы увидеть виньетки для конкретного пакета, исполь- зуйте browseVignettes("имя_пакета") (рис. 8.2).
Как самому создать пакет R? 91 Рис. 8.2 ❖ Список виньеток пакета dplyr Каждая виньетка состоит из трёх обязательных компонент: исходного фай- ла, HTML-страницы или PDF-файла, которые, собственно, и читает пользо- ватель, и файла исходного кода на R. Таким образом, можно не только позна- комиться с примером решения задачи, но и воспользоваться готовым кодом этого решения. Вы можете прочитать виньетку при помощи функции vignette и посмот- реть её код, используя edit(vignette(имя)). Как самому создать пакет R? Такой вопрос возникает рано или поздно. С ростом числа созданных вами функций R их придётся как-то упорядочивать, например размещать темати- чески связанные функции в отдельные папки. Потом в такие папки начина- ют добавляться используемые данные, потом - пояснения к функциям. На- конец, возникает мысль: не создать ли из всего этого пакет? Создание пакета - лучший способ поделиться с кем-нибудь решённой ва- ми задачей. Ведь в пакете есть код, реализующий решение; тест, поясняю- щий, как этим решением воспользоваться (виньетка и справочное руковод- ство), и данные, которые при этом использовались, - нужно только взять всё
92 Пакеты это и установить. Разумеется, для этого придётся пойти на некоторые затраты, например оформить пакет в соответствии со стандартами R. С другой стороны, задача может иметь продолжение, а в лице заинтересованных пользователей пакета вы можете получить неожиданных соратников. Наиболее ёмким руководством по разработке пакетов в R является книга уже знакомого нам Хэдли Уикэма • Wickham Н. R Packages. O’Reilly Media, 2015. и его же одноимённый сайт. И в завершение главы познакомимся с одним очень полезным пакетом. Он делает более наглядным представление операций в R. Пакет magrittr: конвейер операций Предположим, что нам нужно применить к переменной х функцию f. Всё просто: делаем f (х). Л если теперь к полученному результату применить функцию д, а потом ещё и функцию h? Получится h(g(f (х)) - выглядит уже не так просто и красиво. Теперь представим себе, что существует оператор «применить», который позволяет записать эти операции в том порядке, в котором они выполняют- ся: к х применить f а затем: к х применить f применить g применить h то есть к х применить функцию f, к полученному результату применить функ- цию g и, наконец, применить функцию h. Именно таким оператором и снабжает нас пакет magrittr. Записывается этот оператор так: %>%, напоминая этим конвейеры (pipes) в Linux. Теперь наши примеры можно оформить следующим образом: х %>% f х %>% f %>% g %>% h Итак, оператор %>% позволяет: • записывать последовательность действий слева направо (х %>% f %>% h), а не изнутри наружу (h(f (х))); • легко добавлять в последовательность (конвейер) новое действие; • избавиться от использования вложенных функций.
Пакет magrittr: конвейер операций 93 Рассмотрим два простых примера: library(magrittr) х <- seq(-pi,pi,.1) у <- x %>% sin # эквивалентно у <- sin(x) Sys.Date() %>% as.POSIXct %>% seq(by = "15 mins", length.out = 9) %>% data.frame(timestamp = .) ## timestamp ## ## ## ## ## ## ## ## ## 1 2016-09-06 03:00:00 2 2016-09-06 03:15:00 3 2016-09-06 03:30:00 4 2016-09-06 03:45:00 5 2016-09-06 04:00:00 6 2016-09-06 04:15:00 7 2016-09-06 04:30:00 8 2016-09-06 04:45:00 9 2016-09-06 05:00:00 Точка в последней строке кода (timestamp = .) является заменителем ре- зультата выполнения предыдущей операции конвейера. Кроме magrittr, сходные возможности реализованы в пакете pipeR.
Глава Ввод и вывод данных. Работа с файлами Рабочий каталог пользователя Перед тем как приступить к чтению или к записи данных, пользователь дол- жен указать имя файла, с которым ему предстоит работать. По умолчанию поиск файла происходит в текущем рабочем каталоге пользователя. Узнать текущий рабочий каталог можно с помощью функции getwd (get working directory - ’’узнать рабочий каталог”): getwd() ## [1] "С:/Users/Admin/Documents/WebMining2" а установить - при помощи setwd: setwd("/path/to/dir") # слэши '/' - как в Linux setwd("WpathWtoWdir") # или так Вначале рассмотрим функции R для записи данных. С их помощью мы создадим файлы, которые затем будем считывать. Запись данных в стандартное устройство вывода По умолчанию стандартным устройством вывода является консоль. Мы уже знакомы с одной из функций, которая выводит данные в консоль, - это функ- ция print. Но сейчас речь пойдёт о функции cat, обладающей гораздо боль- шими возможностями. Однако начнём с вывода данных в консоль: cat("Hello, \n", "world!\п") Этот код можно улучшить, поскольку cat позволяет указать символ-раз- делитель строк в параметре sep:
Запись в текстовые файлы 95 cat("Hello,", "world!", sep="\n") Вот что получится в результате: ## Hello, ## world! cat может выводить данные не только в консоль, но н в файл. Имя файла вывода служит значением параметра file. cat("Hello,", "world!", sep="\n", file="hello.txt") В этом случае данные выводятся в файл hello. txt, расположенный в ра- бочем каталоге. Направление вывода данных можно изменить при помощи функции sink. Например, перенаправим вывод из консоли в файл, а затем снова вернём его в консоль: sink("hello.txt") # Перенаправляем вывод в файл hello.txt cat("Hello,","world!",sep="\n") # Выводим данные в новую "консоль" sink() # Возвращаем вывод в обычную консоль Вались в текстовые файлы Не вдаваясь в подробности, примем, что текстовые файлы содержат текст, то есть строки символов, которые могут быть прочитаны и поняты челове- ком. Напротив, бинарные файлы содержат «нечитаемые» данные: звук, ви- део, текст в закодированном виде и т. и. Заметим, что одним из самых распространённых средств подготовки дан- ных для их последующего анализа в R является Microsoft Excel. Чтобы со- хранить таблицу Excel в виде txt- или csv-файла, в версиях Excel 2007 и бо- лее новых используют пункт Сохранить как (Save as), меню кнопки Office4. Ещё один простой способ экспорта данных из Excel: создать в редакторе Блокнот (Notepad) новый файл и перенести туда через буфер обмена всю таблицу Excel или её выделенную часть. Вопросы чтения данных с помощью R непосредственно из таблиц Excel мы рассмотрим в главе 12. Таблицы Создадим таблицу данных и запишем её в файл myf ile. txt: 1 В более ранних версиях Excel - меню Файл (File).
96 Ввод и вывод данных. Работа с файлами height = с(180.5, 163.7, 177.2) weight = с(75, 48, 72) аде = с(18, 17, 17) df <- data.frame(height,weight,age) names(df) <- c("Height","Weight ”,"Age”) row.names(df) <- c("John","Mary","Sam") write.table(df, file="myfile.txt") df # # Height Weight Age # # John 180.5 75 18 # # Mary 163.7 48 17 # # Sam 177.2 72 17 Самое важное в этом фрагменте - функция write. table, аргументами ко- торой выступают имя таблицы данных и имя файла, куда эту таблицу пред- стоит записать. write. table насчитывает более десятка параметров. Например, параметр append определяет, будет ли файл myfile. txt, если он уже существует, со- здаваться заново (теряя прежнее содержимое) или только дополняться. По умолчанию append = FALSE и файл создаётся заново. Параметр sep задаёт символ-разделитель между данными. По умолчанию разделителем служит пробел. Широко используются в качестве разделите- лей табуляция (sep="\t") и запятая (sep=", "). Последняя применяется на- столько широко, что для неё существует специальная функция-обёртка над write. table - write. csv (CSV означает comma separated values - ‘значения, разделённые запятыми’). Параметр dec устанавливает вид разделителя между целой и дробной час- тями числа. По умолчанию разделителем является точка: dec=".". Помимо точки, в этом качестве также используют запятую: dec=", ". В частности, за- пятая применяется в странах СНГ и в континентальной Европе. Для записи таблиц, содержащих числа с запятой в качестве разделителя целой и дробной частей, существует функция write. csv2 - ещё одна обёрт- ка write, table. Заметим, что если запятая используется для разделения целой и дробной частей числа, то в качестве разделителя данных в файлах CSV применяется точка с запятой ';'. Записанный нами файл myf ile. txt содержит в первой строке заголовки колонок таблицы. Точно так же поступают функции write.csv и write.csv2. Чтобы записать данные, не помещая в файл заголовки колонок, восполь- зуемся параметром col. names:
Чтение из текстовых файлов ❖ 97 write.table(df, sep=",", col.names=FALSE) Обратите внимание: мы создаём CSV-файл, используя write. table, а не write. csv, так как в последней параметра col. names просто нет. Строки Запись строк удобно выполнять функцией writeLines: writeLines(text, con="myfile.txt", sep="\n") которая в данном случае сохраняет вектор строк text в файле myfile. txt. Параметр sep задаёт строку символов, печатаемых в конце каждой строки из text. Матрииы Функция write(A, file="mymatrix.txt", ncolumns=4) сохраняет содержимое матрицы А в файле mymatrix. txt - в четырёх столб- цах, разделённых пробелами. Вывод матрицы в стандартное устройство вывода можно организовать сле- дующим образом: А <- matrix(l:10, ncol=5) write(A, file="") ##12345 # # 6 7 8 9 10 При этом данные записываются по строкам. Чтобы записать их по столб- цам, транспонируем А: write(t(A), file="") ##13579 # # 2 4 6 8 10 Чтение из текстовых файлов Элементы данных: scan Функция scan считывает данные из текстового файла и возвращает в резуль- тате вектор или список. Создадим текстовый файл следующего вида:
98 Ввод и вывод данных. Работа с файлами 24 1991 21 1993 53 1962 с помощью функции cat cat("24 1991", "21 1993", "53 1962", file = "ex.data", sep = "\n") Прочитав ex. data с помощью scan, получим вектор действительных чи- сел: data <- scan("ex.data") str(data) # # num [1:6] 24 1991 21 1993 53 ... Но ведь в файле явно хранилась таблица! Воспользуется тем, что scan воз- вращает вектор, и преобразуем этот вектор с помощью функции matrix: data <- matrix(scan("ex.data"), nrow=3, byrow=TRUE) data ## [,1] [,2] ## [1,1 24 1991 ## [2,] 21 1993 ## [3,] 53 1962 Аргумент n определяет число считываемых элементов данных. Если п = -1 (установлено по умолчанию) или равно любому отрицательному числу, то считывать данные нужно до конца файла. Считаем первое число: data <- scan("ex.data", n=l) data # # [1] 24 По умолчанию scan выводит сообщения о том, сколько элементов данных она считала. Настройка quiet=TRUE отменяет вывод этих сообщений. Если в первой строке файла находится текст заголовка, тогда как осталь- ные строки содержат числовые данные, то scan выведет сообщение об ошиб- ке. Этого можно избежать, указав, что именно (what) считывается. Напри- мер, так scan будет считывать строки:
Чтение из текстовых файлов 99 data <- scan("myfile.txt", what = character(), quiet=TRUE) str(data) ## chr [1:15] "Height" "Weight" "Age" "John" "180.5" "75" ... С другой стороны, первую строку можно при чтении пропустить. Число пропускаемых строк задаётся параметром skip. Считаем данные из файла ex.data, пропустив первую строку и сохранив результат в виде списка, со- стоящего из двух числовых векторов: data <- scan("ex.data", what = list(Age = 0, Birthyear = 0), skip=l, quiet=TRUE) Параметр what определяет, что понимается под элементом данных, и кос- венно влияет на другие настройки scan. Например, указав считать один эле- мент, мы теперь получим в результате весь список: data <- scan("ex.data", what = list(Age = 0, Birthyear = 0), n=l, quiet=TRUE) str(data) # # List of 2 # # $ Age : num [1:3] 24 21 53 # # $ Birthyear: num [1:3] 1991 1993 1962 Структура таблицы, хранящейся в myfile.txt, немного сложнее: в пер- вом столбце находятся строки, а в остальных - числовые данные. Внесём необходимые изменения в список, заданный what: data <- scan("myfile.txt", what = list(Name= "", Heigth = 0 Weight = 0 Age = 0), skip=l, quiet=TRUE) До получения таблицы остался один таг: df <- data.frame(data) Строки: readLines Для чтения строк текстового файла воспользуемся функцией readLines:
100 Ввод и вывод данных. Работа с файлами dat <- readLines("myfile.txt") str(dat) ## chr [1:4] "\"Height\" \"Weight\" V'AgeV'" "\"John\" 180.5 75 18" ... противоположной по смыслу уже знакомой нам writeLines. readLines создаёт вектор dat из строк считанного ею файла myf ile. txt. Число элементов dat равно числу прочитанных строк. В нашем случае их 4. Число считываемых строк задаётся параметром п. dat <- readLines("myfile.txt", n=l) str(dat) ## chr "\"Height\" V'WeightV' V'AgeV'" Чтение строк удобно использовать, когда данные в файле не имеют струк- туры (как, например, в hello. txt). В нашем случае такая структура - таб- лица - существует, и ею нужно воспользоваться. Таблицы Чтение таблиц выполняется функцией read.table. Среди входных пара- метров этой функции наиболее часто используются: • имя файла, из которого предстоит читать данные ("myf ile. txt"); • разделитель данных sep (в нашем случае - пробел); • параметр header, указывающий, использовать ли первую строку фай- ла в качестве заголовка таблицы (TRUE означает использовать). dat <- read.table("myfile.txt", sep=" ", header=TRUE) dat # # Height Weight Age # # John 180.5 75 18 # # Mary 163.7 48 17 # # Sam 177.2 72 17 Если в строке файла myf ile. txt обнаруживается символ комментария #, то остальная часть строки считается комментарием и при чтении игнориру- ется. Для наиболее популярных разделителей данных - запятой и табуляции - существуют специальные функции (обёртки read. table): • read.csv: sep=", " или sep="csv"; • read.delim: sep="\t".
Работа с данными в бинарном формате ❖ 101 В обеих указанных выше функциях предполагается, что символом-разде- лителем целой и дробной частей числа является точка: dec=" .". Для чтения данных, в которых целая и дробная части числа разделены за- пятой, используются функции: • read.csv2:sep=",",dec=", • read.delim2:sep="\t",dec=",". Очень полезным может оказаться аргумент fill (’’заполнить”) функции read. table3. Если файл содержит строки разной длины, что означает про- пуски в данных, то с fill = TRUE эти строки будут дополнены пробелами, а в полученной таблице на месте дополненных элементов будут стоять NA. В противном случае read. table выведет сообщение об ошибке чтения таб- лицы. В read. csv* и read. delim*, в отличие от read. table, fill = TRUE установлено по умолчанию. Следует иметь в виду, что таким способом можно корректно заполнить только отсутствие элементов, стоящих в крайних правых колонках таблицы (то есть тех, что находятся в конце строки). Пропуски элементов из глубины таблицы, дополненные fill = TRUE, приведут к путанице элементов между колонками. Отметим, что все рассмотренные нами функции чтения данных (scan, read Lines, read. *) могут считывать данные как из файлов, хранящихся на локальном компьютере, так и с интернет-ресурсов. Например, вот как мож- но получить текст заглавной страницы сервиса RDocumentation.org: rdoc <- readLines("http://www.rdocumentation.org/") Работа с данными в бинарном формате CSV и другие текстовые форматы очень удобны для хранения таблиц, но да- леко не все объекты R являются таблицами. Чтобы сохранить произвольный объект R, используется функция save. Эта функция также может сохранить набор объектов: dfl <- data.frame(a=l:5, b=6:10) df2 <- data.frame(c=ll:15, d=16:20) save(dfl, df2, file="myfile.RData") # сохраняем объекты rm(dfl, df2) # удаляем их из рабочего пространства # Проверяем: существует ли объект? dfl ## Error in eval(expr, envir, enclos): объект 'dfl' не найден 5 Есть он и у scan.
102 Ввод и вывод данных. Работа с файлами Для загрузки данных используется функция load: load("myfile.RData") dfl # # a b # #11 6 # #2 2 7 # #3 3 8 # #4 4 9 # # 5 5 10 Управление файлами и каталогами Работа с файлами и каталогами подробно описана в справке по R (?f iles и ?dir .create). Мы рассмотрим всего один пример: создадим каталог, скача- ем в него файл, проверим, что этот файл существует, а затем удалим и файл, и каталог. # Создадим каталог dir.create("test") # Сделаем его рабочим setwd("test") # Скачаем в него файл url <- "https://data.baltimorecity.gov/api/views/dz54-2aru/rows.csv? accessType=DOWNLOAD" download.file(url, destfile="./rows.csv", method="curl") % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 9294 100 9294 0 0 1435 0 0:00:06 0:00:06 1439 # Посмотрим список файлов в текущем каталоге list.files(".") [1] "rows.csv" # Удалим файл rows.csv file.remove("rows.csv") [1] TRUE # Проверим, существует ли файл? file.exists("rows.csv") [1] FALSE Ну, ещё бы.
Взаимодействие с базами данных 103 # Сделаем рабочим родительский каталог для test setwd("..") # Удалим каталог test unlink("test", recursive = TRUE) Если указано recursive = FALSE, то даже пустой каталог не будет уда- лён. Взаимодействие с базами данных Рассмотрим, как с помощью R: • создать в базе данных таблицу на основе файла с данными (.csv); • загрузить данные из таблицы базы данных в R. Для работы с базами данных в R широко используются пакеты DBI и sqldf. DBI + RSQLite Пакет DBI обеспечивает единый интерфейс доступа ко множеству систем управления базами данных (СУБД), в частности к SQLite, MySQL и PostgreSQL. Для работы с конкретной СУБД нужно установить её драйвер, реализо- ванный в виде отдельного пакета. Для SQLite это пакет RSQLite, для Post- greSQL - RPostgreSQL и т. п. Рассмотрим принципы работы с базами данных на примере SQLite. library(DBI) # Загружаем общий интерфейс и соединяемся с базой, используя её драйвер, # Если базы не существует, то она будет создана. db <- dbConnect(RSQLite::SQLite(), dbname="Test.sqlite") # Скачиваем файл из репозитория и сохраняем в таблицу (iris) fname <- "http://archive.ics.uci.edu/ml/machine-learning - databases/ iris/iris.data" iris <- read.table(file=fname, sep=",", header=FALSE) # Запишем таблицу в базу. dbWriteTable(conn = db, name = "Iris", value = iris, row.names = FALSE) # Просмотрим список существующих в базе таблиц. dbListTables(db) # Извлечём содержимое таблицы в data, frame. dfl <- dbGetQuery(db, "SELECT * FROM iris") # Теперь с ней можно работать в R. # Закрываем соединение. dbDisconnect(db) sqldf Пакет sqldf импортирует DBI, но предлагает несколько иной способ работы с базами данных. В частности, он позволяет манипулировать с таблицами R
104 Ввод и вывод данных. Работа с файлами (data, frame) при помощи языка SQL. library(sqldf) # загружает также DBI и RSQLite # Создаём новую базу данных. sqldf("attach 'Testi.sqlite' as new") # Скачиваем файл данных и сохраняем его на диск. download.file("http://archive.ics.uci.edu/ml/ machine-learning-databases/iris/iris.data", "iris.csv") # Создаём в базе таблицу и заполняем её данными из файла. read.csv.sql(file = "iris.csv", header = FALSE, sql = "CREATE TABLE Iris AS SELECT * FROM file", dbname = "Testi.sqlite" ) # Извлекаем содержимое таблицы базы данных для работы в R. df2 <- sqldf("SELECT * FROM Iris", dbname = "Testi.sqlite") Проверим идентичность созданных таблиц: identical(dfl, df2) # #[1] TRUE Резюме Функции R, предназначенные для чтения данных, позволяют считывать дан- ные, хранящиеся как на локальных компьютерах, так и на интернет-ресур- сах. Более подробно об импорте/экспорте данных в R можно узнать из руко- водства R Data Import/Export.
Ссылки к части I Наш рассказ о возможностях R далеко не полон. Восполнить этот пробел вам помогут следующие книги и ссылки. • Шипунов Л. Б. и др. Наглядная статистика. Используем R!. 2014. 296 с. {ashipunov.info/shipunov/school/hooks/rbook.pdf) Книга очень хороша для начинающих, в первую очередь тем, что в ней неформально изложены основы анализа и обработки данных. При этом, как бы невзначай, читателю даются различные задания, помогающие лучше по- нять материал. Компактно изложены основы программирования на R: всего около 30 страниц. • Кабаков Р. И. R в действии. Анализ и визуализация данных в програм- ме R. М.: ДМК Пресс, 2014. 588 с. Наиболее полная на сегодняшний день книга на русском языке по заяв- ленной теме. В ней есть почти всё, что нужно аналитику, использующему R. Кроме того, рассмотрены практики эффективного программирования и вопросы подготовки публикаций. Quick-R {www.statmethods.net/) - сайт ав- тора книги. • Мастицкий С. Э., Шитиков В. К. Статистический анализ и визуализа- ция данных с помощью R. Хайдельберг; Лондон; Тольятти, 2014. 401 с. Книгу можно использовать как в качестве введения в язык программиро- вания R, так и для ознакомления с методами статистики. Хотя изложение не столь подробно, как у Кабакова, зато дополнительно рассмотрены простран- ственный анализ и создание картограмм. Полезен будет и обширный список литературы, посвящённой R и статистическому анализу данных. На сайте R: Анализ и визуализация данных (r-analytics.blogspot.com) одно- го из автров книги - Сергея Мастицкого - регулярно публикуются новости из мира R. Здесь можно найти список русскоязычных онлайн-ресурсов по R и книги, которые можно скачать. • Зарядов И. С. Введение в статистический пакет R: типы переменных, структуры данных, чтение и запись информации, графика. М.: Изд-во РУДЯ 2010. 207 с. • Зарядов И. С. Статистический пакет R: теория вероятностей и матема- тическая статистика. М.: Изд-во РУДН, 2010. 141 с. Ещё один хороший учебник, где основы программирования на R и прило- жения этого языка к статистическим расчётам разделены на две книги. • Allerhand М. A Tiny Handbook of R. Springer, 2011. 83 p. - маленький, но симпатичный вводный учебник.
Часть 11 СБОР ДАННЫХ
Глава 10 Открытые данные Что это такое? Открытые данные - это опубликованные в Интернете данные, доступ к ко- торым открыт и бесплатен для любого заинтересованного пользователя. Наиболее важные критерии открытости данных: • машиночитаемый формат; • наличие оговоренных условий, разрешающих распространение и по- вторное использование таких данных. Машиночитаемые форматы - это форматы, позволяющие обрабатывать данные с помощью компьютера. Например: CSV, JSON, XML, XLS. Отсут- ствие машиночитаемого формата затрудняет непосредственное использова- ние данных, вынуждая проводить их предварительную обработку и приве- дение к машиночитаемому виду. Так, данные, опубликованные в человеко- читаемом формате PDF, можно отнести к категории открытых лишь услов- но. С другой стороны, распространение данных в машиночитаемом форма- те, обработать который может лишь программное обеспечение, распростра- няемое на коммерческой основе, нарушает принцип бесплатности доступа к данным. Для публикации данных на условиях, соответствующих критериям откры- тости, используются международные открытые лицензии. Существует несколько типов таких лицензий, признаваемых по всему миру Это прежде всего открытые лицензии Creative Commons и Open Data Commons. Соглас- но им, открытые данные можно воспроизводить, распространять, обрабаты- вать, комбинировать и повторно использовать без каких-либо ограничений, за исключением требования указывать источник данных. Ещё одно допусти- мое принципами открытости требование - это распространение производ- ных продуктов на тех же условиях, что и исходный. Таким образом, использовать открытые данные предельно просто. Вопро- сы возникают с тем, где и как такие данные получить. Получить открытые данные можно в хранилище (портале, хабе) откры- тых данных. Найти такие хранилища можно по запросу 'open data + тема_которая_вас_интересует' в поисковом сервисе. Как правило, хранилище открытых данных имеет API - интерфейс про- граммирования приложений, - при помощи которого эти данные можно из-
108 Открытые данные влечь. В крайнем случае, данные можно скачать как обычные файлы. Начнём с самого простого случая: хранилище имеет API, а в R существует пакет, реализующий доступ к хранилищу при помощи этого API. Список пакетов R, предназначенных для доступа к источникам открытых данных, приведен в CRAN Task View: Open Data. Рассмотрим в качестве примера работу с данными Всемирного банка, ко- торые можно получить при помощи пакета wbstats. Данные Всемирного банка Всемирный банк позволяет получить данные, характеризующие социально- экономическое развитие отдельных стран и целых регионов за последние несколько десятков лет. Каждый набор данных хранит значение какой-либо одной величины, на- зываемой индикатором (indicator). Поэтому, чтобы получить данные, вна- чале следует указать нужный индикатор. Найти индикатор позволяет функ- ция wbsearch. Поскольку данных во Всемирном банке хранится много, то для ускорения поиска вместе с пакетом wbstats поставляется кэшированная версия описа- ния этих данных, актуальная на день создания пакета. Хранится опа в списке wb_cachelist: library(wbstats) str(wb_cachelist, max.level = 1) ## List Of 7 ## $ countries :'data.frame' 264 obs . of 14 variables: ## $ indicators : : 'data.frame' 16630 obs. of 6 variables ## $ sources :'data.frame' 39 obs. of 4 variables: ## $ datacatalog: :'data.frame' 10 obs. of 25 variables: ## $ topics : :'data.frame' 21 obs. of 3 variables: ## $ income :'data.frame' 10 obs. of 2 variables: ## $ lending :'data.frame' 4 obs. of 2 variables: Описания индикаторов находятся в таблице wb_cachelist$indicators. Видно, что индикаторов в ней очень много, а именно 16 6306. Каждый индикатор характеризуется шестью полями. Первые три: иден- тификатор (indicatorlD), короткое описание (indicator) и длинное опи- сание (indicatorDesc) индикатора. Остальные три параметра относятся к источнику данных: sourceOrg, sourcelD и source. 6 К тому моменту когда вы будете читать эту книгу их, скорее всего, будет ещё больше.
Данные Всемирного банка ❖ 109 По умолчанию wbsearch() ищет соответствия в полях описаний индика- торов indicator и indicatorDesc, а возвращает данные из колонок indicatorlD и indicator: gdb_cached <- wbsearch(pattern = "GDP") head(gdb_cached) ## indicatorlD ## 681 5.51.01.10.gdp ## 683 6.0.GDP_current ## 684 6.0.GDP_growth ## 685 6.0.GDP_usd ## 686 6.0.GDPpc_constant ## 689 6.1.2_GDP.PPP ## indicator ## 681 Per capita GDP growth ## 683 GDP (current $) ## 684 GDP growth (annual %) ## 685 GDP (constant 2005 $) ## 686 GDP per capita, PPP (constant 2011 international $) ## 689 GDP (2011 USD PPP) В кэшированной версии поиск выполняется быстрее, именно так функция wbsearch настроена по умолчанию. Скачать с сайта новые описания мож- но функцией wbcache, а указать, где именно искать данные, - параметром cache функции wbsearch: new <- wbcache() gdb_new <- wbsearch(pattern = "GDP", cache = new) head(gdb_new) ## indicatorlD ## 681 5.51.01.10.gdp ## 683 6.0.GDP_current ## 684 6.0.GDP_growth ## 685 6.0.GDP_usd ## 686 6.0.GDPpc_constant ## 689 6.1.2_GDP.PPP ## indicator ## 681 Per capita GDP growth ## 683 GDP (current $) ## 684 GDP growth (annual %) ## 685 GDP (constant 2005 $) ## 686 GDP per capita, PPP (constant 2011 international $) ## 689 GDP (2011 USD PPP)
no Открытые данные Искать данные можно и по другим полям индикаторов, указывая их с по- мощью параметра fields: blmbrg_inds <- wbsearch(pattern = "Bloomberg", fields = "sourceOrg") head(blmbrg_inds) ## indicatorlD indicator ## ## 1496 BARLEY Barley, $/mt, 1789 CHICKEN Meat, chicken, cents/kg, currents currents ## 1813 CRUDE_BRENT Crude oil, Brendt, $/bbl, currents ## 1814 CRUDE_DUBAI Crude oil, Dubai, $/bbl, currents ## 1816 CRUDE_WTI Crude Oil, WTI, $/bbl, currents ## 5370 GFDD.OM.02 Stock market return (%, year -on-year) wbcache() позволяет получить описания не только на английском, но и на других языках, например new_es <- wbcache(lang = "es") что позволяет затем выполнять поиск индикаторов на этих языках. Русского среди поддерживаемых языков пока нет. Кроме того, ничто нс мешает искать нужный индикатор не средствами wbstats, а более наглядно - при помощи сайта Всемирного банка {data.worldbank.org). Наконец, нужный индикатор найден. Пусть это будет численность насе- ления. Берём его идентификатор (indicator ID) SP. POP. TOTL и указываем в функции wb, которая скачивает данные из хранилища. Параметры star tdate и enddate задают начальную и конечную даты интересующего нас интерва- ла времени. Например: pop_data <- wb(indicator = "SP.POP.TOTL", startdate = 2000, enddate = 2002) head(pop_data) # # value date indicatorlD indicator iso2c # # 1 293402563 2002 SP.POP.TOTL Population, total 1A # # 2 287291826 2001 SP.POP.TOTL Population, total 1A # # 3 281326250 2000 SP.POP.TOTL Population, total 1A # # 4 6532561 2002 SP.POP.TOTL Population, total S3 # # 5 6497461 2001 SP.POP.TOTL Population, total S3 # # 6 6454716 2000 SP.POP.TOTL Population, total S3 # # country # # 1 Arab World # # 2 Arab World # # 3 Arab World # # 4 Caribbean small states # # 5 Caribbean small states # # 6 Caribbean small states
Данные Всемирного банка ❖ 111 Указать нужные страны или регионы можно с помощью их кодов в пара- метре country7. str(wb_cachelist$countries, max.level = 1) ## 'data.frame': 264 obs. of 14 variables: ## $ iso3c : Chr "ABW" "AFG" "AFR" "AGO" ... ## $ iso2c : Chr "AW" "AF" "A9" "AO" ... ## $ country : chr "Aruba" "Afghanistan" "Africa" "Angola" . ## $ capital : chr "Oranjestad" "Kabul" NA "Luanda" ... ## $ long : Chr "-70.0167" "69.1761" NA "13.242" ... ## $ lat : chr "12.5167" "34.5228" NA "-8.81155" ... ## $ regionlD : Chr "LCN" "SAS" NA "SSF" ... ## $ region : chr "Latin America & Caribbean" ... ## $ adminlD : Chr NA "SAS" NA "SSA" ... ## $ admin : chr NA "South Asia" NA "Sub-Saharan Africa" . ## $ incomelD : Chr "NOC" "LIC" NA "UMC" ... ## $ income : chr "High income: nonOECD" "Low income" ... ## $ lendingID: Chr "LNX" "IDX" NA "IBD" ... ## $ lending : chr "Not classified" "IDA" "Aggregates" "IBRD Страны обозначаются двухбуквенными кодами (iso2c) или трёхбуквен- ными кодами (iso3c), регионы - идентификаторами regionlD, adminlD или incomelD. По умолчанию country = "all" и возвращаются данные по всем регионам и странам. Несколько стран (регионов) можно объединить в вектор: bkru_pop_data <- wb(country = с("BY","KZ","UA"), indicator = "SP.POP.TOTL", startdate = 1991, enddate = 2015) head(bkru_pop_data) # # value date indicatorlD indicator # # 1 9513000 2015 SP.POP.TOTL Population, total # # 2 9483000 2014 SP.POP.TOTL Population, total # # 3 9466000 2013 SP.POP.TOTL Population, total # # 4 9464000 2012 SP.POP.TOTL Population, total # # 5 9473000 2011 SP.POP.TOTL Population, total # # 6 9490000 2010 SP.POP.TOTL Population, total iso2c country BY Belarus BY Belarus BY Belarus BY Belarus BY Belarus BY Belarus И вроде бы всё хорошо, ио вот график изменения численности населения во времени напрямую при помощи полученной таблицы не построить. Дело в том, что даты date в этой таблице являются строками: z Здесь и далее, чтобы сэкономить место, мы приводим сокращённые результаты вызова функций.
112 Открытые данные class(bkru_pop_data$date) # # [1] "character" и нужно предварительно преобразовать их в формат дат. К счастью, созда- тели wbstats уже позаботились об этом: добавив в wb параметр POSIXct = TRUE, мы автоматически пополним полученную таблицу колонкой date_ct, которая имеет нужный формат: bkru_pop_data <- wb(country = indicator startdate POSIXct = class(bkru_pop_data$date_ct) c("BY","KZ","UA"), = "SP.POP.TOTL", = 1991, enddate = 2015, TRUE) # # [1] "Date" Теперь можно строить график (рис. 10.1): library(ggplot2) ggplot(bkru_pop_data, aes(x = date.ct, у = value, colour = country)) + geom line(size = 1) + labs(title = "Population, total", x = "Date", у = "Value") country — Belarus — Kazakhstan — Ukraine Рис. 10.1 Параметр mrv позволяет указать число наиболее свежих значений индика- тора (mrv является сокращением от most recent values). Например, вот как из- менялась численность населения Казахстана за последние 10 лет (рис. 10.2):
Где взять данные? 113 Kazakhstan population 17500000- 17000000- g 16500000- 16000000- 15500000- kzl0 <- wb(country = c("KZ"), indicator='SP.POP.TOTL', mrv=10, POSIXct=TRUE) ggplot(kzlO, aes(x = date_ct, у = value)) + geom line(size = 1) + labs(title = "Kazakhstan population", x = "Date", у = "Value") Date Рис. 10.2 Параметр freq задаёт частоту получения данных. Так, freq = "М" означа- ет, что будут получены значения индикатора по месяцам, freq = "Y" - по годам и т. д. Пример: oil_data <- wb(indicator = "CRUDE_BRENT", mrv = 6, freq = "M") head(oil_data) # # value date indicatorlD indicator iso2c # # 1 45.07 2016M07 CRUDE_BRENT Crude oil, Brendt, $/bbl, nominalS 1W # # 2 48.48 2016M06 CRUDE—BRENT Crude oil, Brendt, $/bbl, nominalS 1W # # 3 47.13 2016M05 CRUDE_BRENT Crude oil, Brendt, $/bbl, nominalS 1W # # 4 42.25 2016MO4 CRUDE_BRENT Crude oil, Brendt, $/bbl, nominalS 1W # # 5 39.07 2016M03 CRUDE_BRENT Crude oil, Brendt, $/bbl, nominalS 1W # # 6 33.20 2016M02 CRUDE-BRENT Crude oil, Brendt, $/bbl, nominalS 1W Из колонки date видно, как в wbstats можно задавать даты с точностью до месяца. Где взять данные? В качестве отправной точки для поисков хранилищ открытых данных мож- но посоветовать статью Open data в Википедии и портал открытых данных
114 Открытые данные Российской Федерации (datagov.ru). В последнем порталы открытых дан- ных показаны на карте, а в разделе Полезные ссылки приведены ссылки на российские и международные организации, занимающиеся открытыми дан- ными. Существует масса порталов открытых данных8, для подключения к ЛР1 которых в R не существует пакетов. Однако это не является существенной проблемой, так как необходимые функции нетрудно создать самому. Но для этого нужно познакомиться с протоколом передачи данных HTTP и форма- тами представления данных (чаще всего это XML и JSON). Этим мы с вами и займёмся в следующей главе, после чего вернёмся к получению открытых данных. Резюме Открытые данные - это данные в машиночитаемом формате, которые до- ступны свободно и бесплатно. В Интернете существует множество храни- лищ таких данных, подключиться к которым можно через API (интерфейс программирования приложений) соотвествующего хранилища. Мы рассмот- рели работу пакета wbstats, предоставляющего доступ к данным Всемирно- го банка. 8 И постоянно появляются новые - не забывайте заглядывать в CRAN Task View: Open Data.
Глава Протокол HTTP В этой главе мы: • познакомимся с протоколом HTTP, регламентирующим передачу дан- ных в World Wide Web; • рассмотрим пакеты R, которые работают с HTTP, то есть позволяют отправлять HTTP-запросы и обрабатывать полученные ответы; • разработаем функцию для доступа к хранилищу открытых данных по ЛР1. Основные понятия HTTP {HyperText Transfer Protocol - ’’протокол передачи гипертекста”) - про- токол передачи данных, являющийся основой World Wide Web. Этот прото- кол предполагает, что существуют потребители и поставщики инфор- мации - клиенты и серверы. Клиент (например, браузер) инициирует соеди- нение с сервером и посылает ему запрос на предоставление информации, а сервер получает этот запрос, производит необходимые действия и возвра- щает обратно клиенту сообщение с результатом. Таким образом, протокол HTTP представляет собой правила обмена данными между клиентом и сер- вером. Объектом, над которым производятся операции в HTTP, является инфор- мационный ресурс, на который указывает URI {Uniform Resource Identifier - ’’единый идентификатор ресурса”)9 в запросе клиента. Обычно такими ре- сурсами являются хранящиеся на сервере файлы: веб-страницы, файлы изоб- ражений и т.п., a URI представляет собой адрес этих файлов в Интернете. Например, URI главной страницы сервиса Mail.Ru: https://mail.ru/. Протокол HTTP позволяет указать в запросе и в ответе способ представле- ния одного и того же ресурса по различным параметрам (заголовкам): фор- мату, кодировке, языку и др. Возможности протокола можно расширять, вво- дя свои собственные заголовки. При этом сохранится совместимость с дру- гими клиентами и серверами, которые будут просто игнорировать неизвест- ные им заголовки. 9 Здесь и далее мы не делаем разницы между URI и URL {Uniform Resource Locator, единый указатель ресурса).
116 Протокол HTTP HTTP не сохраняет своё состояние в промежутке между нарами «запрос- ответ». Браузер, посылающий запросы, может отслеживать задержки отве- тов, сервер - хранить IP-адреса и заголовки запросов последних клиентов, ио сам протокол не обеспечивает храпения информации о предыдущих за- просах и ответах. После того как запрос был выполнен, следующий запрос от того же клиента сервер воспринимает безо всякой связи с предыдущим. Такой подход позволяет устранить расходы па отслеживание сеансов свя- зи, хотя иногда такое отслеживание бывает необходимым. Например, при совершении покупок в онлайновом магазине нужно следить за состоянием корзины клиента и отличать одного клиента от другого. Контролировать это приходится внешними средствами, например при помощи «куки» (cookie) на стороне клиента или сессий на стороне сервера. Запрос HTTP-запрос (request) состоит из трех частей: начальной строки запроса, раздела заголовков (header) и тела запроса (content), которого может и не быть. Начальная строка запроса составляется следующим образом: Метод URI НТТР/Версия Метод - это операция, которая выполняется над ресурсом. Обычно ме- тод представляет собой короткое английское слово, записанное заглавными буквами. Методов существует около десятка. Чаще всего применяются методы GET и POST. На них мы остановимся подробнее. Рассмотрим запрос, начинающийся как GET / HTTP/1.1 В данном случае используется метод GET, URI представляет собой слэш ' /' - такой путь ведёт на главную страницу сайта. Число 1.1 указывает, что запрос составлен в соответствии со стандартом HTTP версии 1.1. Строки заголовков, идущие после начальной строки запроса, имеют сле- дующий формат: Заголовок: значение Таким образом задаются параметры запроса. Например, имя домена, с ко- торого запрашивается ресурс, указывается в заголовке Host: Host: https://curl.haxx.se
Ответ ❖ 117 В данном случае запрашивается главная страница сайта cURL - популяр- ной программы, предназначенной для отправки HTTP-запросов. Заголовок Host в запросе обязателен. Некоторые другие заголовки запроса: • User -Agent - строка с кодовым обозначением браузера10; User-Agent: Mozilla/5.0 (Xll; Ubuntu; Linux i686; rv:47.0) Gecko/20100101 Firefox/47.0 - Mozilla Firefox 47.0 под Linux. • Accept - список поддерживаемых браузером типов содержимого в по- рядке их предпочтения данным браузером, например: Accept: text/html, text/plain, image/gif, image/jpeg • Ref erer - URI, с которого клиент перешёл на этот ресурс. Полностью наш запрос имеет вид: GET / НТТР/1.1\п Host: https://curl.haxx.se\n \n\n Каждая строка заголовка заканчивается символом перевода строки (\п), а два обязательных символа новой строки \п\п являются маркером оконча- ния заголовков запроса (или маркером окончания всего запроса, если тело запроса отсутствует, как в нашем случае). Без этого маркера сервер не будет обрабатывать запрос. Некоторые из типов содержимого, указываемых в заголовке Accept: • text/html - текст в формате HTML (веб-страница); • text/plain - простой текст; • image/jpeg - картинка в формате JPEG; • image/gif - картинка в формате GIF; • application/octet-stream - поток «октетов», то есть просто байт, для записи на диск. Ответ Начальная строка ответа (response) сервера имеет следующую структуру: HTTP/Версия Код состояния Пояснение 10 Нам пришлось разделить её на две строки.
118 Протокол HTTP Версия - это версия протокола HTTP. Код состояния (status code) - три цифры, которые определяют результат выполнения запроса. Пояснение к коду состояния - текстовое пояснение к коду ответа, предна- значенное для упрощения чтения ответа человеком. Например: НТТР/1.1 200 ОК Ответ доставлен в соответствии с протоколом HTTP версии 1.1. Запрос выполнен успешно, о чём говорит код состояния 200 и пояснение к нему - ОК. После начальной строки идут заголовки и тело ответа. Например: Server: nginx/1.2.1 Date: Tue, 27 Jul 2016 08:05:16 GMT Content-Type: application/octet-stream Content-Length: 7 Last-Modified: Sat, 08 May 2016 22:53:30 GMT Connection: keep-alive Accept-Ranges: bytes <Тело> Как и в случае запроса, тело ответа следует через два перевода строки (\п\п) после последнего заголовка. Если запрос выполнен успешно, клиенту посылаются затребованные дан- ные. Если же запрос выполнить не удалось, то клиенту передаются дополни- тельные данные, разъясняющие причины неудачи. Коды состояния Код состояния HTTP является частью первой строки ответа сервера. Он представляет собой целое число, состоящее из трех цифр. Первая из цифр ко- да указывает на класс состояния. После кода обычно следует короткая фраза на английском языке, поясняющая человеку причину именно такого ответа. В настоящее время выделено пять классов кодов состояния: • Ixx: Informational (Информационные). Информационные коды состоя- ния, сообщающие клиенту, что сервер находится в процессе обработки запроса; • 2хх: Success (Успешно). Например: 200 ОК - означает успешный за- прос ресурса. Если клиентом были запрошены какие-либо данные, то они находятся в заголовке и/или теле сообщения;
Передача параметров 119 • Зхх: Redirection (Переадресация). Коды класса Зхх сообщают клиенту, что для успешного выполнения операции необходимо сделать другой запрос (как правило, по другому UR1); • 4хх: Client Error (Ошибка клиента). Класс кодов 4хх предназначен для указания ошибок со стороны клиента. При использовании любого из методов HTTP, кроме HEAD, сервер должен вернуть в теле сообще- ния гипертекстовое пояснение для пользователя. Например: 404 Not Found означает, что сервер понял запрос, но не нашёл соответствующе- го ресурса по указанному URL • 5хх: Server Error (Ошибка сервера). Передача параметров Формы HTML часто используют для передачи HTTP-запросов с параметра- ми. Например, следующая форма предназначена для отправки запроса мето- дом GET: <form method="GET" action="foo.cgi"> <input type=text name="year"> <input type=submit name=press value="OK"> </form> Если вы откроете этот код в браузере, то увидите форму с текстовым по- лем и кнопкой с надписью «ОК» (рис. 11.1). ОК Рис. 11.1 ❖ Форма запроса В текстовом поле нужно ввести год (year). Когда вы введёте в форму, на- пример, 1990 и нажмёте «ОК», браузер создаст новый URL. Он будет состо- ять из URL веб-страницы, на которой расположена форма, и строки запроса, вроде следующей: foo. cgi?year=1990&press=OK. Так, если форма распола- галась по адресу example.com/year.html (внимание: адрес взят «с потол- ка»!), то при нажатии на кнопку «ОК» будет сформирован следующий URL: http://www.example.com/foo.cgi?year=1990&press=OK Полностью запрос выглядит так:
120 Протокол HTTP GET /foo.cgi?year=1990&press=0K HTTP/1.1 Host: example.com Метод GET устроен таким образом, что вся введённая информация отобра- жается в адресной строке браузера. Очевидно, что это не самый лучший спо- соб, когда нужно отправлять большой объём данных и/или соблюсти конфи- денциальность их передачи. Для решения этой проблемы в протоколе HTTP существует метод POST. С его помощью клиент отправляет данные отдельно от URL, помещая их в теле запроса. Поэтому в адресной строке браузера вы этих данных не увидите. Форма, генерирующая POST-запрос, отличается от предыдущей лишь ме- тодом отправки: <form method="POST" action="foo.cgi"> <input type=text name="year"> <input type=submit name=press value="OK"> </form> Сформированный POST-запрос будет иметь вид: POST /foo.cgi HTTP/1.1 Host: example.com Content-length: 18 year=1990&press=OK HTTP в R В R существуют два основных пакета для работы с протоколом HTTP: h 11 г и RCurl. Оба они выполняют запросы и обрабатывают ответы, позволяя при этом анализировать коды состояния. Оба пакета в конечном счёте основаны на библиотеке libcurl. httr обеспечивает, как представляется, более дружественный интерфейс для выполнения HTTP-запросов (функции так и называются: GET. POST, PUT, HEAD, DELETE и т. п.) и поддерживает современные протоколы авторизации, в частности OAuth 2.0. RCurl, со своей стороны, поддерживает большее число протоколов обмена данными, в частности FTP. Пакет httr Пакет httr выполняет запрос GET при помощи одноимённой функции:
HTTP в R 121 library(httr) res <- GET("https://curl.haxx.se") res # # Response [https://curl.haxx.se/] # # Date: 2016-09-07 09:18 # # Status: 200 # # Content-Type: text/html # # Size: 6.79 кВ # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://w... # # <html lang="en"> # # <head> # # <title>curl</title> # # cmeta content="text/html; charset=UTF-8" http-equiv="Content-Type"> # # clink rel="stylesheet" type="text/css" href="https://curl.haxx.se/curl.c... # # clink rel="shortcut icon" href="https://curl.haxx.se/favicon.ico"> # # clink rel="icon" href="https://curl.haxx.se/logo/curl-symbol.svg" type="... # # c/head> # # cbody bgcolor="#ffffff" text="#0O0000"> # # ... Более подробную информацию о заголовках возвращает функция headers: headers(res) # # $date # # [1] "Wed, 07 Sep 2016 09:18:38 GMT" ## # # $server # # [1] "Apache" ## # # $'x-frame-options' # # [1] "SAMEORIGIN" ## # # Supgrade # # [1] "h2" # # # # Sconnection # # [1] "Upgrade" ## # # $'last-modified' # # [1] "Wed, 07 Sep 2016 05:53:53 GMT" ## # # Setag # # [1] "\"la87-53be489c27a5e-gzip\"" ## # # S'accept-ranges' # # [1] "bytes" # # # # Svary ## [1] "Accept-Encoding"
122 Протокол HTTP ## ## $'content-encoding' ## [1] "gzip" ## ## $'strict-transport-security' ## [1] "max-age=31536000; includeSubDomains; preload" ## ## $'content-length' ## [1] "2077" ## ## $'content-type' ## [1] "text/html" ## ## attr(,"class") # # [1] "insensitive" "list" Содержимое страницы находится в теле ответа и возвращается функцией content: # Содержимое страницы page <- content(res, as = "text", encoding = "UTF-8") # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" # # <html lang="en"> # # <head> # # <title>curl</title> # # ... # # </BODY> # # </HTML> Параметр as указывает тип выдачи, в данном случае - текстовый (есть ещё raw - в виде набора байт), a encoding - кодировку текста. Полученную страницу можно сохранить на диск: # Сохранение в файл на диске. writeLines(page, con = "curl.html") Послать GET-запрос с параметрами можно двумя способами: # 1) сделать GET-запрос, добавив параметры к URL самостоятельно resl <- GET("https://www.google.com/search?&q=RCurl&btnG=Search") #2) объединить параметры (поля формы) и их значения в список, # предоставив пакету создать на этой основе URL res2 <- GET("https://www.google.com/search", query = list(q="RCurl", btnG="Search")) Пакет RCurl Запрос GET выполняется функцией get URL. При заданном значении header = TRUE, помимо содержимого страницы (тела ответа), возвращаются также заголовки ответа:
Кириллииа и кодирование URL ❖ 123 library(RCurl) # GET page = getURL("https://curl.haxx.se", header = TRUE) # # HTTP/1.1 200 OK # # Date: Wed, 07 Sep 2016 09:18:39 GMT # # Server: Apache # # X-Frame-Options: SAMEORIGIN # # Upgrade: h2 # # Connection: Upgrade # # Last-Modified: Wed, 07 Sep 2016 05:53:53 GMT # # ETag: "Ia87-53be489c27a5e" # # Accept-Ranges: bytes # # Content-Length: 6791 # # Vary: Accept-Encoding # # Strict-Transport-Security: max-age=31536000; includeSubDomains; preload # # Content-Type: text/html ## # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" ... # # ... # # </B0DY> # # </HTML> Способы работы с формой: # 1) Сделать GET-запрос, сформировав URL самостоятельно. resl <- getURL("https://www.google.com.ua/search?&q=RCurl&btnG=Search") # 2} get Form поможет составить запрос в виде: поле-значение. res2 <- getForm("https://www.google.com.ua/search", q="RCurl", btnG="Search") # 3) Все параметры можно объединить в вектор или список. res3 <- getForm("https://www.google.com.ua/search", .params = c(q="RCurl", btnG="Search")) Кириллина и кодирование URL Обработка запросов на кириллице, как обычно, имеет свои особенности. # Запрос на кириллице res <- GET("https://www.google.com.ua", path = "search", query = list(q="Be6-CKpeMni4Hr", btnG="Search")) Обратите внимание, что при выполнении запроса мы доверили дело фор- мирования URL, включающего значения параметров, самой функции GET. Дело в том, что в стандарте URL можно использовать лишь весьма ограни- ченный набор символов - US-ASCII, который даже меньше обычного ASCII. US-ASCII содержит латинские буквы, цифры и несколько знаков пунктуа- ции (' -1, , '.', ' *', ' +'). Все другие символы, включая буквы кирилли- цы, необходимо перекодировать.
124 Протокол HTTP Чтобы передать в URL символы кириллицы, их перекодируют в два этапа. На первом этапе каждый символ кодируется в Unicode (UTF-8) в последо- вательность из двух байт, на втором этапе каждый байт этой последователь- ности записывается в шестнадцатеричном представлении. Всю эту работу и выполнила за нас функция GET пакета httr. Л вот как можно выполнить перекодировку с помощью пакета RCurl: search <- "Микрокредит" # Если текущая локаль не UTF-8 if ( !110n_info()$'UTF-8' ) { # ...перекодируем текст запроса в UTF-8 codepage <- as.character(110n info()$codepage) search <- iconv(search, from = codepage, to = "UTF-8") } # Кодируем URL curlEscape(search) ## [1] "%D0%9C%D0%B8%D0%BA%D1%80%D0%BE%D0%BA%D1%80%D0%B5%D0%B4%D0%B8%D1%82" 110n_info возвращает информацию о локальных настройках, в частности текущую кодовую страницу. Но вернёмся к нашему запросу. Ответ сервера возвращает страницу в неко- торой нелатинской кодировке. Её нужно знать, иначе получим в итоге NA. Создадим маленькую функцию для извлечения кодировки из ответа сер- вера: charset <- function(response) { ct <- headers(response)$content-type' %>% strsplit(split="=") %>% unlist() ct[length(ct)] } chs <- charset(res) chs [1] "windows -1251" (вам понадобится загрузить пакет magrittr). Укажем найденную кодировку при получении содержимого страницы и выведем результаты на экран: page <- content(res, as = "text", encoding = chs) cat(page, "\n") # Вывод текста на экран Пример: геокодирование с помощью Google Maps Geocoding Геокодирование - это процесс преобразования адресов в географические ко- ординаты. В качестве примера определим координаты штаб-квартиры
Пример: геокодирование с помощью Google Maps Geocoding ❖ 125 Google Inc. (адрес: 1600 Amphitheatre Parkway, Mountain View, CA) при по- мощи API сервиса Google Maps Geocoding. Эта задача рассматривается на странице справки по API, где, помимо опи- сания форматов запроса к API и ответа, приведены координаты штаб-квар- тиры. Так что будет с чем сравнить полученные результаты. Для запросов будем использовать пакет httr. Сформируем запрос к сер- вису. Это можно сделать вручную, как показано на странице справки: library(httr) res <- GET("https://maps.googleapis.com/maps/api/geocode/json?address= 1600+Amphitheatre+Parkway,+Mountain+View,+CA") или при помощи функции GET. Последний способ, очевидно, удобнее, по- скольку не требует преобразовывать строку адреса. # Адрес головного офиса Google Inc. Google_address <- "1600 Amphitheatre Parkway, Mountain View, CA" # Запрос, сформированный при помощи GET res <- GET("https://maps.googleapis.com/maps/api/geocode/json", query = list(address = Google_address)) result <- content(res) # Широта result$results[[1]]$geometry$location$lat # # [1] 37.42234 # Долгота result$results[[l]]$geometry$location$lng # # [1] -122.0844 Можете убедиться, что полученные значения координат совпадают с теми, что приведены в справке. Как видно, в списке result содержится несколько уровней вложенных списков. Есть среди них и более полный адрес штаб-квартиры (в нашем за- просе не указан индекс): result$results[[l]]$formatted_address ## [1] "1600 Amphitheatre Pkwy, Mountain View, CA 94043, USA" Определим теперь координаты Эйфелевой башни. В прошлый раз мы по- лучили значения координат, в которых доли градуса представлены десятич- ными дробями (decimal degrees - ’десятичные градусы”). Теперь же запишем эти доли в минутах и секундах, для чего нам понадобится функция dd2dms из пакета sp (не забудьте его предварительно установить).
126 ❖ Протокол HTTP Eiffel_Tower_address <- "The Eiffel Tower, Paris, France" res <- GET("https://maps.googleapis.com/maps/api/geocode/json", query = list(address = Eiffel_Tower_address)) result <- content(res) lat <- result$results[[1]]$geometry$location$lat Ion <- result$results[[l]]$geometry$location$lng library(sp) # Классы и методы для работы с пространственными данными # Широта dd2dms(lat, NS = TRUE) ## [1] 48d51'30.132"N # Долгота dd2dms(lon) ## [1] 2dl7'40.133"E # Адрес result$results[[1]]$formatted_address ## [1] "Eiffel Tower, Champ de Mars, 5 Avenue Anatole France, 75007 Paris, France" Пример: доступ к API портала открытых данных РФ Портал открытых данных Российской Федерации (data.gov.ru) поддержива- ет доступ к данным при помощи API. Специализированного пакета R для работы с ним пока пет, однако ничто не мешает написать соответствующие функции самостоятельно. Доступ к API описан в разделе портала Сервисы > Разработчикам > Пра- вила и рекомендации. Здесь же можно скачать и полное руководство по API. Рассмотрим, как: • узнать, какие наборы данных хранятся на портале; • выбрать один из них и скачать его наиболее свежую версию. На данный момент API предлагает два формата вывода результатов запро- са: JSON и XML, причём JSON используется по умолчанию. Мы будем ис- пользовать именно этот формат. Для знакомства с ним хватит соответствую- щей статьи из Википедии. Для работы с JSON в R есть несколько пакетов. Наиболее популярны: rjson, RJSONIO и jsonlite. Мы воспользуемся последним. Для работы с помощью API нужно зарегистрироваться на портале и полу- чить ключ доступа к API (рис. 11.2). Получим перечень наборов открытых данных, хранящихся на портале.
Пример: доступ к API портала открытых данных РФ 127 API находится в стадии разработки и поэтому методы могут быть изменены без предупреждения. но с последующим информированием пользователей Для работы с API требуется получить личный ключ, который доступен после регистрации на Портале. Более подробную и детальную информацию вы можете найти в документации no API Рис. 11.2 ❖ Кнопка получения ключа к API на «Портале открытых данных Российской Федерации» library(RCurl) # getURL library(jsonlite) # fromJSON # # Параметры: # базовый URL для доступа к API URL_base <- "http://data.gov.ru/api/" # ключ для доступа к API (замените на свой) access_token <- "963ef6baab31820bdfa8a3b314fb95cc" # Получить данные по заданным параметрам getData <- function (х) { par <- paste(x, collapse = "/") url <- pastee(URL_base,par,"/?access_token-",access_token) fromJSON(getURL(url)) } # Параметры запроса: получить перечень наборов открытых данных params <- c("dataset") # Выполняем запрос datasets <- getData(params) # Сколько всего наборов данных хранится на портале nrow(datasets) # # [1] 9829 # Посмотрим заголовки первых пяти наборов head(datasets$title, п = 5) # # [1] "Список МФЦ" # # [2] "Реестр подведомственных организаций Министерства здравоохранения " # # [3] "Перечень подведомственных организаций" # # [4] "Сведения о вакантных должностях государственной гражданской служб" # # [5] "Реестр аккредитованных организаций, осуществляющих деятельность в"
128 Протокол HTTP В функции getData параметры запроса сначала объединяются в строку, разделённую слэшами ('/'). Затем полученную строку объединяют с URL API и строкой ключа доступа, окончательно формируя запрос на получение данных. После чего функция getURL выполняет запрос, a f rom JSON преобра- зует результат из формата JSON в таблицу R, которая и возвращается поль- зователю. Функция getData примитивна, и в ней не сделано никаких проверок. Мы поступили так потому, что: 1) это пример; 2) в настоящее время ЛР1 портала находится в состоянии активной разработки и может претерпевать сущест- венные изменения. Каждый набор данных характеризуется в перечне следующими свойства- ми: • identifier - уникальный идентификатор набора данных; • title - название набора; • organization - уникальный идентификатор организации, которая яв- ляется владельцем набора данных; • organization_name - название организации-владельца набора; • topic - тематическая рубрика набора открытых данных. Свойства наборов данных: • identifier - уникальный идентификатор набора открытых данных; • title - название набора открытых данных; • description - описание набора открытых данных; • creator - наименование создателя набора открытых данных; • created - дата создания набора открытых данных; • modified - дата последнего изменения набора открытых данных; • format - формат набора открытых данных; • sub j ect - ключевые слова набора открытых данных. Запросим описание какого-то конкретного набора данных: dataset_id <- "7708234640 -HDFU-1152110030001-2013-1-24-44 1" params <- c("dataset", dataset_id) dataset <- getData(params) dataset$title ## [1] "Индекс потребительских цен (Чай, кофе-к предыдущему месяцу-2013-1)" datasetSmodifled ## [1] "201307O4TOO00O0" Сколько версий данного документа существует?
Ссылки ❖ 129 params <- c(params, "version") versions <- getData(params) nrow(versions) ## [1] 3 Запросим наиболее свежую версию документа: mrv <- versionsfnrow(versions),1] params <- c(params, mrv) # Получаем содержимое (content) документа content <- c(params, "content") doc <- getData(content) head(doc) ## okato_name okato monthnum yearnum measure value ## 1 Алтайский край 1000000000 1 2013 процент 99.93 ## 2 Красноярский край 4000000000 1 2013 процент 100.31 ## 3 Ставропольский край 7000000000 1 2013 процент 99.92 ## 4 Амурская область 10000000000 1 2013 процент 99.98 ## 5 Архангельская область без ао 11001000000 1 2013 процент 100.46 ## 6 Астраханская область 12000000000 1 2013 процент 100.53 Ссылки Спецификация актуальной на сегодня версии протокола HTTP 1.1: • RFC 2616 (tools.ietf.org/html/rfc2616). Почти в каждой книге о веб-разработке есть одна или несколько глав, по- свящённых протоколу HTTP. Например: • Кошеров Д.В., Костарев Л. Ф. РНР 5. 2-е изд., перераб. и доп. СПб.: БХВ-Петербург, 2008. 1104 с. Главы 2 и 3. • Колисниченко Д. Н. РНР 5/6 и MySQL 6. Разработка Web-приложений. 2-е изд., перераб. и доп. СПб.: БХВ-Петербург, 2010. 560 с. Главы 4 и 5.
Глава 12 Импорт данных Каждое уважающее себя интернет-хранилище данных должно позаботиться о том, чтобы информация в нём находилась в структурированном виде. На- пример, в виде таблиц. Однако эти таблицы представляются в самых разных форматах (Microsoft Excel, Google Spreadsheets и т. и.) и могут быть заархи- вированы для экономии места. В этой главе мы покажем, как R импортирует данные из форматов таблич- ных процессоров, из формата JSON?, а также из архивов. Вопросы чтения данных из форматов XML и HTML мы рассмотрим в главе 13, посвящённой веб-скрапингу. Чтение файлов Большинство функций R, предназначенных для чтения данных, могут полу- чать данные из файлов, расположенных как на локальном компьютере, так и в сети. Вот как выглядит загрузка данных wine quality из репозитория данных для машинного обучения UCI (рис. 12.1). datafile <- "http://archive.ics.uci.edu/ml/ machine-learning-databases/ wine-quality/winequality-white.csv" df <- read.table(file = datafile, sep=";", header=TRUE) View(head(df)) fixed, acid ity volatile.acidity atncaod residual, sugar chlorides free.sulfur.dioxrde totaLsulfur.dioxide 1 7j0 0.27 0.36 20.7 0.045 45 17 2 63 0.30 0.34 1.6 0.049 14 13 3 81 0.28 0.40 6.9 0.050 30 9 4 12 0.23 0.32 8.5 0.058 47 13 5 12 0.23 0.32 8.5 0.058 47 18 6 81 0.28 0.40 6.9 0.050 30 Рис. 12.1 ❖ Фрагмент набора данных wine quality из репозитория UCI Обратите внимание, что хотя файл и имеет расширение . csv, но данные в нём разделены не запятыми, а точками с запятой. Поэтому используется не
Скачивание 131 read. csv. а более общая функция read. table. Скачивание Скачать веб-страницу в R можно с помощью функции download. file: # адрес документа url <- "http://curl.haxx.se/" # каталог для сохранения файлов downloads <- file.path(getwd(),"Downloads") # если его нет, то создаём if (’dir.exists(downloads)) dir.create(downloads) # имя сохраняемого файла dest_file <- file.path(downloads,"curl.html") # скачать файл download.file(url,dest_file) Функция file. path напоминает paste тем, что выполняет конкатенацию строк. Но работает она быстрее и предполагает применение слэша (/) в ка- честве разделителя. Скачать изображение или документ PDF можно аналогично: pdf_url <- "http://cran.r-project.org/web/packages/ downloader/downloader.pdf" download.file(pdf_url,"downloader.pdf", mode="wb") Обратите внимание на режим записи файла, заданный атрибутом mode, "wb" означает: записать (’’write”) файл как бинарный (’’binary”). По умолча- нию используется значение "w", и файл считается текстовым. В результате при работе под Windows будет возникать ошибка чтения файлов PDF. По- чему так происходит и о разнице между бинарными (двоичными) и тексто- выми файлами можно узнать из статьи Википедии «Текстовый файл». Если файл с заданным именем уже существует, то при скачивании в режи- мах "w" и "wb" он будет переписан, а его прежнее содержимое уничтожено. Запись в режимах "а" и "ab" означает: добавить (’’append”) скачиваемый файл к уже существующему. Удалить скачанный файл по окончании работы можно следующим обра- зом: file.remove(paste(download_folder,"downloader.pdf",sep="")) Расширить возможности R по скачиванию документов можно с помощью пакета downloader. Заданная в нём функция download имеет те же опции, что и download. file, но, в отличие от последней, позволяет скачивать доку- менты не только по протоколу HTTP, но и по HTTPS.
132 Импорт данных Excel Для работы с файлами Microsoft Excel в R существует множество пакетов. Так, readxl, как ясно из названия, позволяет читать файлы Excel - как в формате Excel ’97 (XLS), так и в более современном OOXML (Excel 2007+, XLSX). Пакеты xlsx и XLConnect дают возможность не только читать, но также создавать и записывать указанные выше типы файлов. При этом все эти пакеты работают с файлами, расположенными на локальном компьюте- ре. Пакет gdata позволяет одновременно скачивать и открывать файлы, но при этом требует установки интерпретатора Perl. В общем, мы будем поль- зоваться пакетом XLConnect как наиболее распространённым. Скачаем данные о заболеваемости ВИЧ из хранилища Gapminder и сохра- ним их в файле hiv. xlsx. Загрузить эту рабочую книгу и сохранить в табли- це df содержимое листа data помогут функции loadworkbook и readworksheet соотвественно: library('XLConnect') url_base <- "http://spreadsheets.google.com/pub" key <- "pyj6tScZqmEfbZylOqjbiRQ" url <- pastee(url_base,"?key=",key,"&output=xls") download.file(url, "hiv.xlsx", mode="wb") wb = loadWorkbook("hiv.xlsx") df = readworksheet(wb, sheet = "data") Функция readWorksheetFromFile объединяет возможности по загрузки рабочей книги и чтению нужного листа: # Название листа знать необязательно, достаточно указать его номер: df <- readWorksheetFromFile("hiv.xlsx", sheet = 1) Для более точного указания фрагмента данных служат параметры: startRow, startCol, endRow, endCol. Чтобы получить таким способом дан- ные по Аргентине за 1990-1992 гг., нужно указать: df <- readWorksheetFromFile("hiv.xlsx", sheet=l, startRow = 11, endRow = 12, startCol = 13, endCol = 15) He слишком-то удобно... Параметр region позволит указать область дан- ных в привычном для Excel стиле: df <- readWorksheetFromFile("hiv.xlsx", sheet=l, region = "Mil:012") head(df) # # Coll Col2 Col3 # # 1 0.3 0.3 0.3
JSON 133 JSON Для работы с данными, хранящимися в формате JSON (JavaScript Object Notation), чаще всего используют три пакета: rjson, RJSONIO и jsonlite. Все они обладают функциями: • fromJSON - для импорта данных JSON в R; • toJSON - для экспорта в JSON. Пакеты rjson и jsonlite импортируют данные как из локальных файлов, так и из сетевых ресурсов, заданных своим URL. RJSONIO работает только с локальными файлами, но разработчики обещают добавить возможность ра- боты с URL в самом ближайшем будущем. В остальном возможности паке- тов примерно одинаковы. В настоящее время наиболее популярен пакет j sonlite. Причём это не го- лословное утверждение: узнать, какие пакеты чаще всего скачивают пользо- ватели R, можно из данных CRAN package download logs (cran-logs.rstudio.com) Сейчас мы этим и займёмся. Пример: какой из JSON-пакетов самый популярный? Данные CRAN package download logs представляют собой логи скачиваний пакетов R, а также самого R, с одного из зеркал CRAN, поддерживаемого ком- панией RStudio. Здесь же приведен пример, как эти данные можно скачать с помощью R. Наш анализ популярности JSON-пакетов будет состоять из следующих шагов: 1) скачаем файлы логов; 2) объединим данные со всех файлов в одну большую таблицу; 3) построим графики скачиваний основных пакетов, работающих с фор- матом JSON. В результате выполнения первого шага мы получим каталог CRANlogs, со- держащий архивы с логами, созданный в рабочем каталоге пользователя. # Шаг 1: Скачаем файлы логов. # ВНИМАНИЕ! Размер скачиваемых файлов - около 360 Мб. # Составим вектор дат от начальной до конечной. from <- as.Date('2016-07-01') to <- as.Date('2016-07-30') days <- seq(from, to, by = 'day') # Выделим из даты год (годы отсчитываются от 1900 г.). year <- as.POSIXlt(days)$year + 1900 # Получим список файлов, которые нужно скачать. urls <- pastee(' http://cran-logs.rstudio.com/', year, '/', days, '.csv.gz') # Создадим каталог для хранения данных
1В4 Импорт данных dir.name <- "CRANlogs" dir.create(dir.name) # Скачаем файлы и сложим их в созданный каталог for (i in l:length(urls)) { download.file(urls[i], paste0(dir.name,7/', days[i], '.csv.gz')) } Второй шаг позволит нам получить список таблиц, импортированных из логов, а затем на их основе - одну большую таблицу. # Шаг 2: Объединим данные из логов в одну большую таблицу (data, table). # Составим список имён файлов из каталога CRANlogs. file_list <- list.files(dir.name, full.names=TRUE) # Считаем данные из файлов логов и составим из них список таблиц (data.frame). logs <- list() for (file in file_list) { print(paste("Reading", file, "...")) logs[[file]] <- read.table(file, header=TRUE, sep=",", quote =*\"", dec=".", fill=TRUE, comment.char="", as.is=TRUE) } # Загрузим пакет data, table для работы с большими таблицами library(data.table) # Объединим все таблицы списка logs в одну большую таблицу # (data, table) по строкам dt <- rbindlist(logs) # Посмотрим, что у нас получилось. str(dt) Classes "data.table7 and 7data.frame7: 15813383 obs. of 10 variables: $ date : chr "2016-07-01" "2016-07-01" "2016-07-01" "2016-07-01" ... $ time : chr "22:51:36" "22:51:37" "22:51:38" "22:51:32" ... $ size : int 1085498 106715 35218 527 1568353 432844 368679 5169 467631 $ r.version: : chr "3.3.0" "3.3.0" "3.3.1" "3.3.1" ... $ r_arch : chr "x86_64" "x86_64" "x86_64" "x86_64" ... $ r_os : chr "linux-gnu" "linux-gnu" "linux-gnu" "darwinl3.4.0" ... $ package : chr "git2r" "roxygen2" "reshape2" "ggplot2" ... $ version : chr "0.15.0" "5.0.1" "1.4.1" "2.1.0" ... $ country : chr "GB" "GB" "RO" "US" ... $ ip.id : int 1123456112 ... - attr(*, ". .internal.selfref")=<externalptr> # Список таблиц больше не нужен - удалим его. rm(logs) # Преобразуем типы данных в некоторых колонках. dt <- dt[, date:=as.Date(date)] dt <- dt[, package:=factor(package)] dt <- dt[, country:=factor(country)] # Посмотрим, как выглядит таблица теперь. str(dt) Classes "data.table7 and 7data.frame7: 15813383 obs. of 10 variables: $ date : Date, format: ""2016-07-01" *2016-07-01" ... $ time : chr "22:51:36" "22:51:37" "22:51:38" "22:51:32" ... $ Size : int 1085498 106715 35218 527 1568353 432844 368679 5169 467631 ... $ r.version: chr "3.3.0" "3.3.0" "3.3.1" "3.3.1" ...
Пример: какой ив JSON-пакетов самый популярный? 135 $ r_arch : chr "х86_64" "х86_64" "х86_64" "х86_64" ... $ r_os : chr "linux-gnu" "linux-gnu" "linux-gnu" "darwinl3.4.0" ... $ package : Factor w/ 10024 levels "A3","aaMI","abbyyR",..: 3253 7798 7394 ... $ version : chr "0.15.0" "5.0.1" "1.4.1" "2.1.0" ... $ country : Factor w/ 211 levels "Al","A2","AD",..: 71 71 167 200 99 200 ... $ ip_id : int 1123456112... - attr(*, ".internal.selfref")=<externalptr> # Необходимо для работы c data, table setkey(dt, package, date, country) Этот шаг требует некоторых пояснений: • read. table умеет читать архивы *. gz, так что дополнительных функ- ций для работы с архивами не требуется. В архивах находятся CSV- файлы. • Чтение файлов может занять несколько минут, поэтому полезно выво- дить диагностические сообщения о ходе процесса (print). • Пакет data.table позволяет создавать большие таблицы - так мы бу- дем называть тип данных data. table, который является наследником data. frame, и позволяет хранить наборы данных большого объёма, не умещающиеся целиком в оперативной памяти компьютера. • Функция data.table: : rbindlist объединяет список таблиц в боль- шую таблицу по строкам. • : = позволяет модифицировать данные по ссылке, а не по значению. От- носится к пакету data.table. • data.table:: set key - сортирует данные в заданных колонках боль- шой таблицы. Так же, как и : =, работает по ссылке. Для последующих сеансов работы с R можно сохранить объединённые данные dt в бинарном файле и загружать их при необходимости: # Сохраним данные для последующего анализа save(dt, file="CRANlogs/CRANlogs.RData") Осталось выбрать данные по интересующим пакетам и построить на их основе график. # Шаг 3: Строим графики. # Выберем данные по пакетам rjson, RJSONIO и jsonlite packages <- dt[J(c("rjson", "RJSONIO", "jsonlite")), length(unique(ip_id)), by=c("date", "package")] # Загрузим графическую библиотеку ggplot2 library(ggplot2) # Построим график зависимости числа скачивании от даты # для выбранных пакетов ggplot(packages, aes(x=date, y=Vl, color=package, group=package)) + geom_line() + ylab("Downloads") + theme_bw() Функция J относится к пакету data.table, позволяет выбрать нужные строки большой таблицы и сохранить результат в data. table.
1В6 Импорт данных Зависимость числа скачиваний JSON-пакетов от времени (июль 2016 г.) представлена на рис. 12.2. Из графика видно, что jsonlite значительно по- пулярней двух других пакетов. package jsonlite — rjson RJSONIO Рис. 12.2 ❖ Зависимость числа скачиваний пакетов rjson, RJSONIO и jsonlite от времени (июль 2016 г.) Google Spreadsheets Функция gs_read из пакета googlesheets позволяет считывать данные из таблиц и сохранять их в R. Работа с пакетом начинается с загрузки списка доступных вам таблиц11: 11 Надеюсь, вы не забыли зарегистрироваться в Google?
Архивы 137 library(googlesheets) # Загрузка списка доступных таблиц gs ls() У меня получилось вот что: Source: local data frame [6 x 10] sheet-title (chr) author (chr) perm version updated (time) (chr) (chr) 1 Список 1нструмент1в devrand r new 2016-05- 17 21:06:10 2 Новая таблица dakhramov rw new 2016-02- 26 10:24:53 3 Comments for Import exc... mart r new 2016-01-25 09:13:11 4 Программа Фестиваля урб... auldnick r new 2014-11-28 20:16:40 5 Extract Twitter Data - ... matteoduo r new 2014-03-26 17:16:40 6 hcp_20120824 dakhramov rw new 2012-09-14 17:56:25 Variables not shown: sheet. .key (chr), ws_ .feed (chr), alternate (chr), self (chr), alt_key (chr) - у вас будет что-то своё. Далее вы проходите аутентификацию (в браузере), после которой можно вернуться в R и выбрать интересующую таблицу по её названию (gs_title) или по ключу (gs_key): # Список имён таблиц gs„ls()$sheet_title # Загрузим нужную таблицу по её названию data <- gs_title("имяТаблицы") Теперь можно считывать данные: data.in.R <- gs read(data) gs_read позволяет указать номер листа в таблице (ws) и диапазон считы- ваемых ячеек (range), аналогично функциям для чтения таблиц Excel из па- кета XLConnect. Пакет googlesheet s позволяет не только импортировать данные, но и со- здавать таблицы Google, загружать их на сервер и т. п. Архивы Архивы *. z, * . gz и * . bz2 открываются функциями чтения данных, вроде read.table и read.csv, автоматически и никаких дополнительных функ- ций для этого не требуется. Несколько сложнее дела обстоят с архивами . zip. Функция unz распако- вывает одиночные файлы, хранящиеся в ZIP-архивах:
138 Импорт данных unz(zipname, filename) где zipname - имя ZIP-архива, a filename - запакованный в этот архив файл. Предположим, что на сайте (выдуманном!) http://wwwjcyz.org/data/ на- ходится архив data. zip, в котором хранится файл данных data. dat. Алго- ритм извлечения этих данных будет примерно следующим: • Создать временный файл (temp). Например, с помощью tempfile(). • Скачать download. file данные и поместить их во временный файл. • С помощью unz извлечь данные из временного файла. • Удалить временный файл (например, функцией unlink). temp <- tempfile() download.file("http://www.xyz.org/data/data.zip", temp, mode="wb") data <- read.table(unz(temp, "data.dat")) unlink(temp) Напомним, 4Tomode="wb" не окажет никакого влияния при работе в Linux, но важен при работе в Windows, которая в противном случае будет рассмат- ривать архив как текстовый файл. Функция unzip позволяет распаковать ZIP-архив в заданный каталог на диске: unzip("dataset.zip", exdir = ".") В данном случае архив dataset.zip распаковывается в текущий каталог, что соотвествует настройкам функции по умолчанию. Существует и обратная функция - zip, упаковывающая файлы в ZIP-ар- хив. Импортировать и распаковывать zip-архивы позволяет также функция getzip из пакета Hmisc. В следующем примере скачивается и распаковыва- ется архив из репозитория данных ООН (data.un.org). Затем из распакован- ного файла считываются данные, хранящиеся в формате CSV, и сохраняют- ся в таблице u n Dat а: library(Hmisc) urlUN <- "http://data.un.org/Handlers/DownloadHandler.ashx? DataFilter=group_code:101;country_code:826& DataMartId=SNA&Format=csv&c=2,3,4,6,7,8,9,10,11,12,13& s=_cr_engNameOrderBy:asc,fiscal_year:desc,_grlt_code:asc" unData <- read.csv(getZip(urlUN)) В пакете Hmisc содержится множество других полезных функций, пред- назначенных для импорта данных. Завершающий штрих: проверка типа данных После того как данные считаны и сохранены в R, стоит убедиться в том, что импорт был выполнен корректно. Например, если ожидалось получить таб-
Ссылки 139 лицу, то сохранена именно таблица, а не список. Проверьте тип полученных данных с помощью функций class или str: # Импортируем таблицу. df <- XLConnect::readWorksheetFromFile("hiv.xlsx", sheet = 1) # Проверяем тип полученного набора данных # (str здесь не подойдёт, поскольку таблица слишком большая). class(df) # # [1] "data.frame" или выведите первые строки полученных данных при помощи head: head(df, n = 2) ## Estimated.HIV.Prevalence ! Ages.15.49. X1979 X1980 X1981 X1982 X1983 ## 1 Abkhazia NA NA NA NA NA ## 2 Afghanistan NA NA NA NA NA -- Х1984 X1985 X1986 X1987 X1988 X1989 X1990 X1991 X1992 X1993 X1994 X1995 ## 1 NA NA NA NA NA NA NA NA NA NA NA NA ## 2 NA NA NA NA NA NA NA NA NA NA NA NA ## X1996 X1997 X1998 X1999 X20OO X20O1 X2002 X2003 X2004 X2005 X2006 X2007 ## 1 NA NA NA NA NA NA NA NA NA NA NA NA ## 2 NA NA NA NA NA NA NA NA NA NA NA NA ## X2008 X2009 X2010 X2011 ## 1 NA <NA> <NA> <NA> ## 2 NA 0,06 0,06 0,06 Убедитесь в том, что заголовки колонок таблицы импортированы коррект- но. Ссылки Справочник по нмпорту/экспорту данных в R - R Data Import/Export (cran.r-prpject.org/doc/manuals/R-data.pdf),
Глава IB Веб-скрапинг Зачастую полезная информация хранится не в специальном формате, а непо- средственно на веб-странице. Это могут быть сведения о товарах и ценах в интернет-магазинах, адреса организаций и т. п. Процесс добычи информа- ции, размещённой на веб-странице, называется веб-скрапинг {web-scraping буквально: ”веб-соскребание”). Для извлечения информации из веб-станиц необходимо: 1. Найти веб-страницу (или группу связанных веб-страниц) с нужными данными. 2. Определить, в каком месте страницы находятся искомые данные. 3. Разработать программу для извлечения данных. Задачу поиска информации мы будем считать решённой. Основами ис- пользования поисковых сервисов сейчас владеет практически каждый, а воз- можности создания более «тонких» запросов к ним описаны в приложении Б. Другие технологии поиска данных рассмотрены в литературе, помещённой в «Ссылки». Инструментами для извлечения данных - задачей № 3 - мы займёмся в следующей главе, а здесь сосредоточимся на определении «координат» дан- ных на веб-странице. Используйте структуру данных Пусть данные на веб-странице выглядят так, как показано на рис. 13.1. Firstname Lastname Age JU1 Smith 50 Eve Jackson 94 John Doe 80 Рис. 13.1 ❖ Таблица данных на веб-странице Чтобы их извлечь, можно использовать регулярные выражения. Но не сто- ит забывать, что эти данные имеют структуру - они сформированы в виде таблицы. Например, такой:
Используйте структуру данных ❖ 141 <!DOCTYPE html> <html> <body> <table style="width:100%"> <th>Firstname</th> <th>Lastname</th> <th>Age</th> </tr> <td>Jill</td> <td>Smith</td> <td>50</td> </tr> <td>Eve</td> <td>Jackson</td> <td>94</td> </tr> <td>John</td> <td>Doe</td> <td>80</td> </tr> </table> </body> </html> Как указать положение таблицы table па веб-странице? Она находится внутри элемента body, который, в свою очередь, находится внутри элемента html. Это можно записать так: /html/body/table что как раз и представляет собой путь к таблице, выраженный на языке XPath Есть и другие способы указания пути к нужному элементу, и вскоре мы рас- смотрим их подробнее. Что это нам даст? Экономию усилий. Не нужно задумываться о том, как составить регулярное выражение. Главное - путь к информации - мы уже знаем. Теперь решение задачи займёт лишь несколько строк кода:
142 Веб-скрапинг library(rvest) page <- "table.html" # Путь к файлу веб-страницы. hdoc <- read html(page) tnode <- html node(hdoc, df <- html_table(tnode) xpath="/html/body/table") # Обработка HTML - разметки. Поиск элемента. Преобразование таблицы HTML в data.frame. df ## Firstname Lastname Age ## 1 Jill Smith 50 ## 2 Eve Jackson 94 ## 3 John Doe 80 или всего одну, если использовать возможности magrittr: df <- read html(page) %>% html_node(xpath="/html/body/table") %>% html. table() Задачи сбора данных по степени структурированности последних можно разделить на: 1. Полностью структурированные. Данные представлены в виде таблиц, которые можно скачать при помощи read. table или извлечь, исполь- зуя rvest: : html_table, и сразу сохранить в виде таблиц data. frame или data.table. 2. Хорошо структурированные. Сайт имеет стабильную структуру, осно- ванную на шаблонах страниц. Путь к данным указывается с помощью XPath или CSS-селекторов. 3. Плохо структурированные. Представление однотипных данных изме- няется внутри страницы (например, используются разные элементы HTML-разметки). Данные извлекаются с помощью регулярных выра- жений. 4. Неструктурированные. Структура данных отсутствует. Иногда её при- ходится задавать специально. Файлы скачиваются построчно с помощью read Lines. К этой группе относится обработка текстов на естественном языке. Чем лучше структурированы данные, тем больше возможностей существу- ет для того, чтобы эти данные извлечь, и тем проще это сделать. Сейчас мы столкнулись с задачей первого типа. Её решение по уровню сложности ма- ло отличается от скачивания готового файла. Но чем хуже структурирова- ны данные, тем длинней и сложней будет код извлекающей их программы. С примерами такого рода мы ещё столкнёмся в главах 14, 15 и 21. Поэтому, чем полней вы сможете использовать структуру данных, тем луч- ше.
Элементы HTML и CSS 143 Элементы HTML и CSS Начнём с маленького теста. Создайте HTML-страницу, содержащую: • заголовок Заголовок страницы; • абзац с произвольным текстом. Внутри абзаца должна быть гиперссыл- ка на сайт Google.com; • маркированный список, содержащий пункт 1, пункт 2 и пункт 3; • таблицу, выровненную по центру страницы, с видимой границей (рис. 13.2). Row 1. Column 1 Row 1. Column 2 Row 2. Column 1 Row 2. Column 2 Рис. 13.2 ❖ Таблица Если вы справились с этим заданием в течение 10-15 минут, значит, ва- ших знаний HTML достаточно для сбора данных. Если же это не так, загля- ните в приложение В или в один из многочисленных учебников по HTML (см. «Ссылки»). Добавим к элементарным знаниям HTML и CSS несколько небольших де- талей. Нам нужно познакомиться с классами и идентификаторами элемен- тов разметки и способами группировки элементов при помощи div и span. div и span Большинство элементов HTML непосредственно направлено на оформле- ние контента: hl создаёт заголовки, р - абзацы и т. п. В отличие от них, эле- менты span и div сами по себе никак на оформление нс влияют (рис. 13.3). В то же время они широко используются совместно с технологией CSS. <!DOCTYPE HTML> <head> <title>FlpHMep страницы</1:11:1е> <meta charset="utf-8"> </head> <body> <div class="example"> <р>Пример стиля example.</p> <р>Текст примера.</p>
144 Веб-скрапинг </div> <р>Абзац текста.</р> <р class="example">Eme один example. <span id="date">DD.ММ.YYYY</span>. </р> </body> </html> Пример стиля example. Текст примера. Абзац текста. Еще один example. DD.MM.YYYY. Рис. 13.3 ❖ Сами по себе ’’span” и "div” никак не влияют на оформление веб-страниц Элементы span и div нужны для того, чтобы группировать области HTML- кода. Разница между span и div состоит в том, что span является строч- ным, то есть работает внутри абзаца, a div - блоковым и группирует HTML- разметку для нескольких абзацев. Так, в первом div-e их два: <div class="example"> <р>Пример стиля example.</р> <р>Текст примера.</р> </div> Выделенные с помощью div и span фрагменты разметки затем оформля- ются в некотором заданном стиле. Классы и идентификаторы Основу селекторов CSS составляют так называемые HTML-селекторы, име- на которых совпадают с именами элементов HTML. Например: body { font-family: arial; } Здесь имя селектора body совпадаете именем HTML-элемента body. В фи- гурных скобках указываются настройки стиля, в нашем случае: тип шрифта font-family. Теперь всё, что попадает внутрь <body>. . .</body>, т. е. всё содержимое веб-страницы, будет отображаться указанным стилем, а имен- но: тест будет отображаться шрифтом Arial.
Элементы HTML и CSS 145 Но кроме управления стилем готовых элементов мы можем создавать свои собственные селекторы. Делается это с помощью классов и идентификато- ров. Допустим, мы хотим создать отдельный стиль для текста примеров: они должны отображаться курсивом. Но стиль элемента <р> уже определен, а отдельных элементов специально для примеров в HTML не предусмотрено. Тут на помощь приходят классы (class). Рассмотрим следующий элемент таблицы стилей: .example { font-style: italic } Он определяет класс example. Имя класса начинается с точки '.'. Теперь, чтобы оформить примеры в соответствии с заданным стилем, нуж- но указать в используемых элементах атрибут класса class со значением example: class="example". Таким образом, с помощью одного класса мы можем определить стиль од- ного или нескольких элементов (в данном случае р и div). Идентификаторы похожи на классы, ио имеют другое назначение. Они ис- пользуются для динамического управления стилем элемента при помощи скрипта. Поэтому скрипт должен иметь возможность обратиться к элемен- ту по его, элемента, уникальному имени - идентификатору. Определим следующий стиль идентификатора для даты date (имя иден- тификатора начинается с решетки #): #date { font-weight: bold; } Теперь, указав в элементе HTML атрибут id="date", получим дату, отоб- ражаемую полужирным курсивом. Существенно, что имена идентификаторов и классов, в отличие от имен элементов HTML, чувствительны к регистру. Вот как в итоге будет выглядеть наша страница: <!DOCTYPE HTML> <html> <head> <title>FlpHMep CTpaHnu,bi</title> <meta charset="utf-8"> <style type="text/css"> body { font-family: arial } .example { font-style: italic } #date { font-weight: bold } </style>
14Б Веб-скрапинг </head> <body> <div class="example"> <р>Пример стиля example.</p> <р>Текст примера.</p> </div> <р>Абзац текста.</p> <p class="example">Eine один example. <span id="date">DD.MM.YYYY</span>. </p> </body> </html> Пример стиля example. Текст примера. Абзац текста. Еще один example. DD.MM.YYYY Рис. 13.4 ❖ Веб-страница (см. рис. 13.3) после применения стилей CSS Путь к элементу XPath XPath. или XML Path Language, - это язык запросов к элементам XML- и HTML-документов. Запрос нужен затем, чтобы получить из этих докумен- тов какую-то нужную нам информацию. Допустим, у нас есть веб-страница <!DOCTYPE HTML> <html> <head> <title>npnMep страницы</И1:1е> <meta charset="utf-8"> <style type="text/css"> body { font-family: arial } .abstract { font-style: italic } #date { font-weight: bold; color: navy } </style> </head>
Путь к элементу ❖ 147 <body> <Ь1>Заголовок</Ь1> <р>Обычный текст.</р> <р class="abstract">TeKCT аннотации (курсив).</р> <div class="abstract"> <р>Текст аннотации. Предложение 1.</р> <р>Текст аннотации. Предложение 2.</р> <р id="date">Ol.O1.2016.</p> </div> <div>06bi4HbM текст.</div> </body> </html> и мы хотим узнать из неё дату создания аннотации, хранящуюся в элементе <р> с идентификатором date (01.01.2016). Проследим путь к этому элементу от начала документа (рис. 13.5): Рис. 13.5 ❖ Представление HTML-документа в виде дерева элементов 1) h tml - всё содержимое страницы, в том числе и дата, находится внутри этого элемента; 2) body - сузим область поиска до «тела» страницы; 3) div - дата содержится в первом из подобных элементов; 4) р - дата находится внутри абзаца. Второй <div> сразу отпадает - в нём нет абзацев; 5) абзац с идентификатором (id) ' date'. Запишем этот путь следующим образом: /html/body/div/p[@id='date']
148 Веб-скрапинг Это и будет запрос XPath - путь к нужному нам элементу. Одиночный слеш ' /' в начале означает, что путь - абсолютный, то есть идёт от начала документа. В квадратные скобки [. . . ] заключены дополнительные условия поиска. Они нужны, так как мы ищем не все абзацы, размещённые внутри <div>, а только абзац с id=' date'. Если бы нам понадобился абзац, принадлежащий к заданном}^ классу, то условие выглядело бы так: [@с1аз5='имя_класса']. Например, путь /html/body/p[@class='abstract'] указывает на второй абзац текста. Кавычки вокруг имени класса или иден- тификатора существенны, и опускать их нельзя. Абсолютный путь можно заменить относительным, то есть путём от задан- ного места документа. Относительный путь начинается с двойного слэша ' //'. Например, //div означает путь от предка элемента <div>, то есть от <body>. В результате запроса //div будут найдены оба элемента <div>. Точнее, запрос возвратит список из двух <div>-oB. Выбрать <div>, содержащий аннотацию, можно по его номеру: он 1-ый в списке, а нумерация начинается с единицы: //div[l] С помощью относительного пути запрос, возвращающий дату, сокращает- ся до //div/p[@id='date'] Но и этот путь можно сократить, ведь нужный нам абзац обладает уни- кальным именем date: //*[@id='date'] Звёздочка * заменяет собой любой элемент разметки. Использовать её нуж- но с осторожностью, из-за «жадности»: слишком много элементов могут со- ответствовать указанному в квадратных скобках условию. Однако при ис- пользовании в качестве условия идентификаторов, применение * вполне безопасно, поскольку id по определению указывает на уникальный элемент страницы.
Путь к элементу 149 CSS Путь к элементу, содержащему дату, по CSS-селекторам очень похож на уже известный нам XPath: html>body>div>p#date Символы ' '>' заменили собой слэши, а идентификатор date обозначает- ся с префиксом ' #' - так же, как и в таблице стилей. Таким образом, ptfdate означает «абзац с идентификатором date». Класс в пути по CSS-селекторам будет, как и следовало ожидать, отображаться точкой: р.abstract указыва- ет путь к абзацу класса abstract. Самый короткий путь к дате нам уже известен: tfdate Из предыдущего примера можно сделать вывод, что путь по CSS-селекто- рам записывается короче, чем XPath. Но это бывает далеко не всегда. Так, для выбора элементов из списка в XPath есть условие, заключённое в [, ], а в CSS-селекторах - сразу два способа: : nth-child и :nth-of -type. Допустим, в следующем фрагменте нам нужно выбрать абзац, содержа- щий слово «Lamb»: <p>Little</p> <p>Lamb</p> <!-- Нужен этот элемент --> Следующие селекторы дадут один и тот же результат: • p:nth-child(2) • p:nth-of-type(2) Оба селектора выбирают заданный элемент из списка. Разница между ни- ми состоит в следующем: • :nth-child(2) означает: выбрать элемент, если: - это второй среди элементов, имеющих общего родителя; - этот элемент - абзац. • :nth-of-type(2) означает: выбрать второй абзац (элемент р) среди элементов, имеющих общего родителя. Поэтому если нам встретится следующая разметка: <hl>Head</hl> <p>Little</p> <p>Lamb</p> <!-- Нужен этот элемент -->
150 Веб-скрапинг то путь p:nth-child(2) теперь укажет на абзац с «Little» вместо «Lamb». Действительно, этот элемент выполняет оба требования: 1) это второй из элементов, имеющих общего родителя (первый - заголовок hl) и 2) это аб- зац. В то же время путь p:nth-of-type(2) продолжает оставаться коррект- ным, то есть указывать на содержащий «Lamb» абзац, поскольку это второй абзац среди элементов, имеющих общего родителя. Как найти путь к элементу при помощи браузера В браузерах, основанных на движке Blink (например, Google Chrome, Opera или Яндекс.Браузер), поиск XPath и CSS-селекторов, определяющих путь к элементу страницы, можно выполнять «из коробки». В браузерах, основанных на движке Gekko (Mozilla Firefox), для комфорт- ной работы желательно установить дополнения Firebug и Firepath. Собственно, искать вручную путь к элементу веб-страницы не придётся - помогут имеющиеся в браузере инструменты разработчика. В разных браузерах они могут называться по-разному («инструменты разра- ботки», «инструменты веб-разработки» и т. п.), но панель инструментов раз- работчика, как правило, открывается командой F12 (Ctrl+Shif t+I) или ме- ню правой кнопки Просмотреть код элемента12. На рис. 13.6 показано, как это выглядит в Opera. В Firefox и прочих Gekko после установки дополне- ний в меню правой кнопки появится пункт Inspect in FirePath - это то, что нужно. Итак, указав мышью на нужный элемент страницы - дату, выберем в меню правой кнопки Просмотреть код элемента. Браузер подсветит этот элемент веб-страницы и соответствующий ему элемент HTML во вкладке Elements (рис. 13.7). Указав на найденный элемент, с помощью меню правой кнопки Сору ско- пируем XPath (Copy Xpath) или путь по CSS-селекторахМ (Copy selector) (рис. 14.1). В нашем примере пути получились такими: • XPath: .//*[@id='date' ] • CSS: #date Тот же результат дают поиски пути в Mozilla Firefox с установленными расширениями FirePath и Firebug (рис. 13.9). Теперь у нас есть путь к элементу страницы, который мы можем исполь- зовать для извлечения содержимого этого элемента. 12 Или Просмотреть код.
Как найти путь к элементу при помоши браузера 151 Заголовок Lx fl Element! Consol- Souites Netwo< Wie Puffa » thtnl: ► heed ,-eed Обычный текст Текст аннотации (курсив) Текст аннотации Пресложение f Текст аннотации Предложение 2 ▼сосу' n1/Заголовок;/Ь1> <р/Обычгьм текст.с/р: р class- abstract текст аннотации (курсив).Гр• ▼ div class-'abstract" р Текст аннотации. Предложе-ее I. /р> о тскс- а-н:-2иги. селлохс-ие 2. л pi: date 01.01.2616. Гр ~ di ' 01.01.2 Обычны НвЗВД Вперед Перывгр/'лть M-lett Shrft ’•Backspace PS : div Обичный текст, /div: (/body > :/ntJ«l> sr Добавить на Экспресс -анель V Добавить в закладки Во весе экран Fll tyles Event Listeners DOM Breakpoints Properties Копировать адрес eaent.style { Сохранить как. Печать- CtrkS Ctrl*P ate C Просмотреть исходник текст Ctrl г U color: bold; 31splay: block; -webkit-weir-before: leir; agent stylesheet -webklt-eargln-start: epx; -webKit-margin-end: ©px; Inherited from | div, at street display font-family font-style □ Shcm «II block arlal italic Рис. 13.6 ❖ Веб-страница и панель с инструментами разработчика (вкладка Elements) в браузере Opera Заголовок Обычный текст. Текст аннотации (курсив) Текст аннотации. Предложение 1. pedatfc 368X18 Я^МШЯШции. Предложение 2. oi.oi.2oie. Обычный текст Сх 0~| Elements Console Sources Network Timelne Profies CIDOCTVPE htalx -html/ ► <пеэс >_</hMC> ▼ oody> hl.’Заголовок /hl> p Обычпий текст.-./p ср class-1abstract' Текст аннотации (курсив).</p ▼ div class- 'abstract * 'p’Текст аннетации Предложение l. /p- гр-Гекст аннотации. Предложение 2.</t» d la cate e1.oi.2e». /р == 10 ./olvx div Обычной текст, /div </body> J html Рис. 13.7 ❖ Отображение даты (01.01.2016) на веб-странице и соответствующего ей элемента HTML с id='date' на вкладке Elements панели инструментов разработчика
152 Веб-скрапинг [J |5~I Elements Console Sources Network Timeline Profiles » < htnl> ► <head _</head> ▼ <body> < hl>3аголовок</hl> < p Обычный текст.</p> < p class- 'abstracf>TeKCT аннотации (курсив).</p> ▼ <div clas5 = *‘abstracf > p Текст аннотации. Предложение 1.</р> р Текст аннотации. Предложение 2.</р' Рис. 13.8 ❖ Копирование пути к элементу с помощью меню правой кнопки Сору (Opera) Заголовок Обычный текст Текст аннотации (курсив) Текст аннотации Предложение 1 Текст аннотации. Предложение 2. Обычный текст. гоя» НТК CS5 (цеиарим (ЮМ Беш СмЫп HrrPMh ▼ I 1Ы Top Window • Highlight | cSS (X) «| |*date Б R <aoctasent> 9 «bead» cbcdy» «Ы>Яат<июаок</Ы.» <p> ObrsuisK текст. </p> <p claae-"abet: aot">Tarco auoiaiam :курега i </p> 8 «dev elass=*aB«TzaeC”» <р» Текст аняттеииг Предложение 1 с/р> «р* Таксе шмоФмзое. Предав да юса 2.</р> L Й-уДДЗЕЯ— </dAv> Рис. 13.9 ❖ Копирование CSS-пути к элементу при помощи расширений FirePath и Firebug в браузере Firefox
Проверка и упрощение пути. Консоль разработчика 153 Проверка и упрощение пути. Консоль разработчика Однако работа ещё не закончена. Дело в том, что разработчик веб-страницы может внести в неё изменения, что повлияет и на найденный нами путь. Это произойдёт, в частности, при добавлении или удалении абзацев, располагаю- щихся на странице выше, чем искомый. Возникает вопрос: как указать путь, устойчивый к изменению элементов страницы? Чтобы ответить на этот вопрос, вернёмся к примеру с поиском пути к аб- зацу с аннотацией. Вместо /html/body/p[@class='abstract'] мы могли указать другой путь, опирающийся на номер абзаца: /html/body/p[2] Если при этом в начало страницы будет добавлено несколько абзацев (на- пример, указан автор материала или подзаголовок), то наш новый путь «по- плывёт» и либо приведёт нас к другому элементу страницы, либо окажется, что такого элемента вообще не существует. В то же время первый путь останется неизменным, поскольку опирается на существенную часть структуры документа - его аннотацию. Автор стра- ницы может, конечно, изменить и её структуру, но такие правки встречаются не так часто, как простое добавление материалов. Таким образом, чтобы путь оказался устойчив к возможным изменениям страницы, нужно чтобы он опирался на существенные элементы её структу- ры. Напротив, пути, содержащие порядковые номера элементов, наиболее чувствительны к изменениям страницы. Кроме того, путь должен быть, по возможности, коротким. Чем путь коро- че, тем от меньшего числа узлов дерева элементов HTML (см. рис. 13.5) он зависит, а значит, меньше зависит и от изменений в этих узлах. Итак, устойчивый к изменениям страницы путь должен быть: • коротким; • опираться на существенные элементы структуры страницы. Для нас это означает, что скопированный из браузера путь, возможно, при- дётся скорректировать. Тогда понадобится проверить, что новый путь ведёт к тому же самому элементу веб-страницы. Поможет в этом консоль разработ- чика (рис. 13.10). Консоль разработчика позволяет выполнить запрос как по XPath, так и по CSS-селекторам и вернуть в результате элементы HTML, удовлетворяющие этому запросу. XPath-запрос выглядит так:
154 Веб-скрапинг Q, Q Elements Network Sources Timeline Profiles Resources Audits I Console О V <top frame > ▼ ) Preserve log Рис. 13.10 ❖ Консоль разработчика (Яндекс.Браузер) $х("XPath_nyTb") а запрос по CSS-селекторам имеет вид: $$("CSS_путь") Результаты выполнения XPath- и CSS-запросов для примера с датой по- казаны на рис. 13.11. Cw ffl Elements Console Sources Q Y top ▼ _ Preserve log > $x("//a[@id='date']") < [ <p id=' date“>61.61.2616.</p>] [ <p ide”date >61.61.2616.</p>] Рис. 13.11 ❖ XPath- и CSS-запросы в консоли разработчика (Opera) Одиночные кавычки вокруг имени идентификатора существенны. Их нель- зя заменить на двойные, иначе возникнет путаница с кавычками того же ти- па, ограничивающими запрос (рис. 13.12). Кавычки можно чередовать. На- пример, ограничить запрос одиночными кавычками, а в условие поместить кавычки двойные. Cw б~| Elements Console Sources Network Timeline » o i • x О V top ▼ Preserve log > $x(*//•[@id=‘date*]“) <• [ <p id date -01.61.2616. «-/p-d > Sx(*//•(^id-’date"]•) [ p id date 61.61.2816.</p>] > $x("//*(@id-“date"r) ф Uncaught SyntaxError: Missing ) after argument list VM111:1 Рис. 13.12 ❖ Использование одиночных и двойных кавычек в запросах
Резюме 155 Резюме Мы научились определять, в каком месте веб-страницы находятся искомые данные, и указывать путь к ним при помощи XPath и CSS-селекторов. Те- перь нужно передать эту информацию программе для извлечения данных. С тем, как разрабатывать такие программы средствами R, мы познакомимся в следующей главе. Лирическое отступление: построение графов Перед тем как перейти к списку литературы, отвлечёмся ненадолго на граф элементов HTML (рис. 13.5). Этот граф построен с помощью самого R, а кон- кретно - пакета Diagrammed Данный пакет поддерживает несколько спосо- бов текстового описания графов, в частности язык DOT. Описание обычного (неориентированного) графа на языке DOT начина- ется с ключевого слова graph: graph имя_графа { <описание графа> } а ориентированного - с digraph. За ним следует имя графа и в фигурных скобках - описание вершин графа и связывающих их рёбер. Комментарии к описанию делаются так же, как в языке С. Описание графа проще показать на примерах. Так, следующий код: graph mygraph { а -- b -- с; b -- d; } даёт результат, показанный на рис. 13.13. В пакете DiagrammeR описание графа на DOT просто помещается внутрь функции grViz: library(DiagrammeR) # Не забудьте загрузить пакет! grViz(" graph mygraph { а -- b -- с; b -- d; }
156 Веб-скрапинг Рис. 13.13 В ориентированном графе нужно заменить линии ' - -' на стрелки ' (рис. 13.14): grViz(" digraph mydigraph { а -> b -> с; b -> d; Рис. 13.14 Внешний вид графа можно разнообразить, задавая стили оформления вер- шин и рёбер (рис. 13.15): grViz(" digraph mydigraph { // label - видимое название вершины a [label='Foo']; // shape - форма отображения вершины b [shape=box]; // color - цвет ребра
Ссылки 157 а -> b -> с [color=blue]; // style - стиль линии ребра b -> d [style=dotted]; } ") Рис. 13.15 Наконец, уже знакомый нам граф элементов HTML (рис. 13.5) задаётся следующим образом: grViz(" digraph xpath { html -> head ->title; html -> body; body -> {hl 'p' 'p[@class=abstract]' 'div[@class=abstract]' div} 'div[@class=abstract]' -> £'p[l]' *p[2]' 'p[@id=date]'} } Всё это - лишь краткое знакомство с пакетом DiagrammeR, призванное скорее заинтриговать, чем описать его возможности (объём справочного ру- ководства по текущей версии пакета превышает 150 страниц). Ссылки Поиск в Интернете Литературы по этой теме очень много, в том числе на русском языке. Начнём с двух книг, посвящённых в основном технике поиска. • Крупник А.Б. Поиск в Интернете. 3-е изд. СПб.: Питер, 2006. Книга слегка устарела в деталях. Но: она компактна, область поиска не ограничена World Wide Web (как часто бывает), и приведены примеры ре- шения конкретных поисковых задач. В качестве более современного допол- нения к ней можно использовать:
158 Веб-скрапинг • Кутовенко А.А. Профессиональный поиск в Интернете. СПб.: Питер, 2011. Кроме основ, здесь рассмотрены вопросы поиска изображений, видео, се- мантического поиска и мн. др. Основные темы следующей книги: поиск информации о конкурентах и проведение маркетинговых исследований. • Ющук Е.Л. Интернет-разведка: руководство к действию. М.: Вершина, 2007. Много внимания уделено тому, какого рода данные можно извлечь из то- го или иного информационного ресурса. Рассмотрены вопросы поиска по «экзотическим» источникам, например по интернет-архнвам, хранящим уда- лённые веб-страницы. Если предыдущие книги описывают, как и для чего пользоваться поиско- выми технологиями, то • Levene М. Лп Introduction to Search Engines and Web navigation, John Wiley & Sons, 2010. рассказывает о том, как эти технологии устроены. HTMLnCSS: • webremeslo.ru - учебники по HTML и CSS для начинающих «с нуля». • Робсон Э., Фримен Э. Изучаем HTML, XHTML и CSS. 2-е изд. СПб.: Питер, 2014. 720 с.: ил. (Серия «Head First O’Reilly».). XPath • XPath Tutorial {zvon,org/xxl/XPath7htorial/Output_rus/examples.html) - руководство no XPath, построенное на примерах.
Глава 14 Пакет rvest Пакеты для веб-скрапинга Для того чтобы извлечь данные из веб-страницы, нужно, как минимум, две вещи: 1) получить эту страницу; 2) обработать её так, чтобы извлечь данные. С первой задачей отлично справляются пакеты RCurl и httr. Каждый из них может выполнить HTTP-запрос по заданному URL и выдать получен- ную в результате веб-страницу. Теперь эту страницу, а точнее - содержащуюся в ней разметку HTML, нужно обработать, чтобы извлечь из неё данные. Сделать это можно с по- мощью пакетов XML или хт12. Ясно, что пакеты для получения и обработки13 веб-страниц должны как- то дополнять друг друга. Так сложилось, что эти пакеты образуют пары: RCurl и XML разработаны Данканом Лэнгом (Duncan Т Lang), a httr и хт12 - Хэд- ли Уикэмом. Последний сделал и следующий шаг, разработав пакет rvest, который позволяет получать данные с помощью httr и обрабатывать их в хт12. В дальнейшем мы будем рассматривать именно rvest. Функции пакета rvest дают возможности: 1) получить HTML- или XML-файл и обработать его: read_html, reacLxml; 2) найти интересующий элемент разметки: html_node, html_nodes; 3) разобрать элемент на составные части: html_name, html_attr, html_attrs,html_text, html_children, html_table, html_form; 4) отправить запрос через форму: html_session, html_form, set_values, submit_form; 5) перемещаться по сайту: html_session, jump_to, follow_link, back, session_history; 6) определять и изменять кодировку документа: guess_encoding, repair_encoding. Рассмотрим эти функции подробнее. 13 Процесс синтаксического разбора документа часто называют «парсинг*, используя транс- литерацию с английского parsing - "обработка”, ’’синтаксический разбор".
160 Пакет rvest Получение и обработка HTML-доку мента Рассмотрим в качестве примера файл sample. html: 1 <!DOCTYPE HTML> 2 <html> з <head> 4 <title>npMMep страницы</ТК1е> s <meta charset="utf-8"> 6 <style type="text/css"> 7 body { font-family: arial } 8 .example { font-style: italic } э #date { font-weight: bold } io </style> и </head> 12 <body> в <div class="example"> и <р>Пример стиля example.</p> is </div> 16 <р>Абзац текста.</p> и <p class="example">Eme один example. is <span id="date">DD.MM.YYYY</span>. is </p> го </body> 2i </html> Считаем этот файл и обработаем его содержимое средствами пакета rvest. Занимается этим функция read_html: read_.html(html_source, ...) Её первым аргументом является источник HTML-разметки: путь к файлу на диске, URL или символьный вектор, содержащий HTML-разметку. В ре- зультате read_html, как и аналогичная функция для разбора XML-файлов - read_xml, возвращает разобранный на составные части документ XML (объ- ект типа xml_document): library(rvest) # Обрабатываем файл sample.html hdoc <- read_html("sample.html") class(hdoc) ## [1] "xml_document" "xml_node"
Получение и обработка HTML-доку мента ❖ 161 read_html("<hl>Hi!</hl>") ## {xml_document} ## <html> ## [1] <body>\n <hl>Hi’</hl>\n</body> Заметьте, что переданный read_html фрагмент разметки дополняется этой функцией до полноценного HTML-доку мента. read_html("h11p://www.google.com") ## {xml_document} ## <html itemscope="" itemtype="http://schema.org/WebPage" lang="uk"> ## [1] <head>\n <meta content="text/html; charset=UTF-8" http-equiv="Conte ... ## [2] <body bgcolor="#fff">\n <script><![CDATAf(function(){var src='/imag ... Поскольку read_html фактически возвращает дерево HTML-элементов, а не текст веб-страницы, то для того, чтобы увидеть этот текст нужно преоб- разовать xml_document в символьный вектор при помощи as. character. При этом желательно указать кодировку документа (encoding). Это по- может избежать проблем с отображением символов кириллицы в консоли R, особенно характерных для Windows-версий пакета. hdoc <- read_html("sample.html", encoding = "UTF-8") cat(as.character(hdoc)) ## <?xml version="1.0" encoding="UTF-8" standalone="yes"?> ## <!DOCTYPE HTML> ## <html><head><title>npHMep страницыс/titlexmeta charset="utf-8"/><style> ## body { font-family: arial } ## .example { font-style: italic } ## #date { font-weight: bold } ## </style></headxbody> ## <div class="example"> ## <р>Пример стиля example.</p> ## </div> ## <р>Абзац текста.</p> ## <p class="example">Eme один example. ## <span id="date">DD.MM.YYYY</span>. ## </p> ## </bodyx/html> Увидеть отдельно структуру HTML-документа можно с помощью функ- ции html_structure. Вообще-то опа относится к пакету хт12, по подгружа- ется и при загрузке rvest:
162 ❖ Пакет rvest html structure(hdoc) # # <html> # # <head> # # <title> # # {text} # # <meta [charset]> # # <style [type]> # # {cdata} # # <body> # # {text} # # <div.example> # # {text} # # <p> # # {text} # # {text} # # {text} # # <p> # # {text} # # {text} # # <p.example> # # {text} # # <span#date> # # {text} # # {text} # # {text} - вот что подразумевается под документом, разобранным на составные ча- сти. Поиск элемента Итак, файд sample.html получен и разобран. Найдём в нём пути к элемен- там, содержащим: 1) id="date" (строка 18); 2) class="example"(13-15 и 17-19) и покажем, как извлечь содержимое этих элементов при помощи функций html_nodes и html_node. С помощью браузера найдём нужный элемент и скопируем путь к нему (рис. 14.1). В первом случае это будут //* [@id="date"] (XPath) и #date (CSS), во втором - //*[@class="example"] (XPath) и .example (CSS). Функция html_nodes: html_nodes(hdoc, css, xpath) принимает на вход XML-документ hdoc, построенный функцией read_html, и путь к нужному элементу, указанный с помощью CSS-селек-
Поиск элемента ❖ 1БЗ Пример стиля example Г* П I 3eme"ts Consoe Sources Network Time-re Proxies » Абзац текста Еию один example. DD.MM. YYYY spaMoate 1»1.34-17 я'ООГТУРГ Mnlz ►<hsao - 'П5Э5 ▼ ibody> ► div class- exansle . ;diz p Абзац те«ста.</р ▼ p class= exanpia’'» 'tue cjw example. Add attribute </p> </ooav /html "tnri ppp) purple I.M.J Styles Evert Listeners DOM Brea kooirts I filter :hoJ element style { } Mate I font-wel^nt: Bold; Edit as HTML Сосу Hide element Delete element :aetive ihokier :focus : visited Scroll into view Break on... ------------1—П Copy outerHTML Copy selector Copy XPath Cut element Copy eemert Paste dement Рис. 14.1 ❖ Копирование пути к элементу <span id="date">DD.MM. YYYY</span> в браузере торов (css) или XPath (xpath). Возвращает она список элементов, располо- женных по заданному пути. # Путь указан при помощи: # CSS-селекторов (по умолчанию) html nodes(hdoc, "#date") # # {xml_nodeset (1)} # # [1] <span id="date">DD.MM.YYYY</span> # XPath html nodes(hdoc, xpath="//span[@id-'date']") # # {xml_nodeset (1)} # # [1] <span id="date">DD.MM.YYYY</span> Функция html.node принимает на вход те же аргументы, что и html_nodes, но возвращает лишь первый элемент, расположенный по заданному пути. Сравним, как работают html_nodes и html.node: htmlnodes(hdoc, ".example") # # {xml_nodeset (2)} # # [1] <div class="example">\n <р>Пример стиля example.</p>\n </div> # # [2] <p class="example">Eme один example. \n\t<span id="date">DD.мм.YYYY< ...
164 Пакет rvest html_node(hdoc, ".example") ## {xml_node} ## <div class="example"> # # [1] <р>Пример стиля example.</p> Разбор элемента В качестве примера возьмём «документ», состоящий всего из одной ссылки - HTML-элемента а. Извлечём этот элемент: simple_doc <- read_html('<а href="http://google.com" rel="nofollow"> Link to the <b>Google</bx/a>') link <- html_nodes(simple_doc, xpath="//a") link # # {xml_nodeset (1)} ## [1] <a href="http://google.com" rel="nofollow">\n и разберём его с помощью функций rvest (табл. 14.1). Таблица 14.1 ❖ Функции пакета rvest, предназначенные для разбора элемента HTML Функция Возвращает html_name html_attrs html_attr html_text html_children имя элемента атрибуты элемента отдельный атрибут, по его имени текст внутри элемента список дочерних элементов # Имя элемента html name(link) ## [1] "а" # Его атрибуты htmlattrs(link) ## [[!]] ## href ## "http://google.com" rel "nofollow"
Разбор элемента ❖ 165 # Обращение к отдельному атрибуту по его имени html attr(link, "href") # # [1] "http://google.com" # Текст внутри элемента, "как есть" html text(link) # # [1] "\n Link to the Google" # Текст с удалёнными пробелами в начале и в конце текста html_text(link, trim = TRUE) # # [1] "Link to the Google" # Список дочерних элементов html_children(link) # # {xml_nodeset (1)} # # [1] <b>Google</b> Заметьте, что при извлечении текста с помощью html_text вся разметка внутри элемента исчезает. Пример: получаем ссылку и скачиваем файл Сайт ACLED (Armed Conflict Location & Event Data Project) хранит данные по вооружённым конфликтам, актам политического насилия, протестам и терактам в развивающихся странах. Он выдаёт информацию о датах и местах проведения конкретных событий, типах этих событий, участвующих в них группах лиц, произошедших изменениях в территориальном устройстве и т. п. Предположим, что нам нужны данные за текущий год - 2016. Нет ниче- го проще, они хранятся на странице Realtime Data (2016) в файле формата XLSX. Казалось бы, скачивай и пользуйся. Проблема в том, что имя файла содержит даты начала и конца сбора данных. Данные постоянно пополняют- ся, поэтому конечная дата изменяется, а с ней изменяется и имя файла. Но хранится-то файл в одном и том же месте. Найдём его с помощью г vest, установим, как он в данный момент называется, скачаем и сохраним в виде таблицы R. Поехали: # 1.Получаем ссылку library(rvest) page <- read html("http://www.acleddata.com/data/realtime-data-2016/") link <- page %>% html„nodes("a") %>% # находим все ссылки
1ББ Пакет rvest html_attr("href") %>% # извлекаем из них URL grep("\\.xlsx",. , value=T) %>% # выбираем те, что содержат ".xlsx" .[[1]] # и из них берём первую # 2.Скачиваем файл tmpfile <- "temp.xlsx" download.file(link, tmpfile, mode = "wb") # 3.Загружаем данные из файла в data, frame library(XLConnect) df <- readWorksheetPromFile(tmpfile, sheet = 1) # Прочитать лист из файла # и сохранить в таблице. unlink(tmpfile) # Удалить временный файл. Таблицы Рассмотрим, как извлечь таблицу из HTML-документа: table <- "<table> <tr><td>a</td><td>10</td></tr> <trxtd>b</tdxtd>20</tdx/tr> doc <- read html(table) doc %>% html_table() ## [[1]] ## XI X2 ## 1 a 10 ## 2 b 20 Мы сознательно не снабдили таблицу закрывающим тегом </table>, тем не менее read_html с задачей справился. Функция html_table возвращает только текст, хранящийся в таблице. Вся- кая HTML-разметка при этом пропадает. Пример: извлечение таблицы из Википедии Нашей первой задачей будет извлечь таблицу со списком лондонских музеев и сохранить в виде таблицы. # Получить и обработать страницу page <- readhtml("https://en.wikipedia.org/wiki/List_of_museums_in..London") # Извлечь таблицу музеев tables <- page %>% html_nodes(xpath="//*[@id~'mw-content-text']/table") # Искомая таблица - 2-я в списке table <- tables[[2]] Imu <- html_table(table) # Выведем названия музеев (первые шесть) head(lmu$Name) ## [1] "2 Willow Road" ## [2] "7 Hammersmith Terrace" ## [3] "18 Stafford Terrace"
Пример: разбор страницы сериала «Светлячок» 1Б7 ## [4] "575 Wandsworth Road" ## [5] "All Hallows-by-the-Tower Crypt Museum" ## [6] "Anaesthesia Heritage Centre" Данные таблицы позволяют узнать кое-что о Лондоне: # Сколько среди них музеев искусств (Туре - Art)? lmu[lmu$Type=="Art",] %>% nrow() ## [1] 66 # Какие музеи расположены на западе Лондона (Region = West)? wlmu <- lmu$Name[lmu$Region=="West"] head(wlmu) ## [1] "7 Hammersmith Terrace" "18 Stafford Terrace" # # [3] "Baden-Powell House Exhibition" "Battle of Britain Bunker" # # [5] "Bentley Priory Museum" "Boston Manor House" Предположим, что нам нужны ссылки на страницы музеев в Википедии. Функция html_table возвращает только текст, поэтому нужно отдельно из- влечь ссылки из элемента table. К счастью, он уже извлечён и хранится в одноимённой переменной. # Первая колонка таблицы (Name) состоит из ячеек <th>. # Извлечём из неё ссылки. links <- html_nodes(table, "tr th a") # URL страниц музеев хранятся в href, а названия музеев - в title. ref <- links %>% html attr("href") name <- links %>% html_attr("title") # В колонке Name записаны только относительные ссылки. # pasteO это легко исправит. full_ref <- pastee('https://www.wikipedia.org', ref) # Сохраним результаты в таблице lmu_pages <- data.frame(Name=name, Href=full_ref) head(lmu_pages$Href) # # [1] https://www.wikipedia.org/wiki/2_Willow_Road # # [2] https://www.wikipedia.org/wiki/7_Hammersmith_Terrace # # [3] https://www.wikipedia.org/wiki/Linley_Sambourne_House # # [4] https://www.wikipedia.org/wiki/575_Wandsworth_Road # # [5] https://www.wikipedia.org/wiki/All_Hallows-by-the-Tower # # [6] https://www.wikipedia.org/wiki/Anaesthesia_Heritage_Centre # #264 Levels: https://www.wikipedia.org/wiki/2_Willow_Road ... Пример: разбор страницы сериала «Светлячок» Рассмотрим, как с помощью rvest извлечь информацию о сериале «Светля- чок» («Firefly») с сайта IMDb14. Скачаем html-страницу фильма с помощью функции read_html: 11 Internet Movie Database — ’’Интернет-база кинофильмов”.
168 Пакет rvest library(rvest) firefly <- read_html("http://www.imdb.com/title/tt0303461/") Допустим, мы хотим определить рейтинг фильма. Его можно получить из текста, расположенного в «звезде» или в строке Ratings (рис. 14.2). Firefly (2002-2003) TV Senes 44 mtn Adventure, Drama, Sci-Fi - -r IMDbPro Your rating: Ratings3 from 160 022 users Reviews: 656 user 61 critic Five hundred years in the future, a renegade crew aboard a small spacecraft tries to survive as they travel the unknown parts of the galaxy and evade warring factions as well as authority agents out to get them. Creator: Joss Whedon Stars: Nathan Allion, Gina Torres, Alan Tudyk See full cast and crew > + Watchlist Share... Рис. 14.2 ❖ Фрагмент страницы сериала «Светлячок» на сайте IMDB Пути к этим элементам по CSS-селекторам будут следующими: #overview-top > div.star-box.giga-star > div.titlePageSprite.star-box-giga-star ^overview-top > div.star-box.giga-star > div.star-box-details > strong > span Для определения элементов достаточно последней части пути. Для «звез- ды» это будет div.titlePageSprite.star-box-giga-star Вы можете проверить это, набрав $$("div.titlePageSprite.star-box-giga-star") в консоли разработчика. Для второго варианта (строки Ratings) значение рейтинга можно полу- чить следующим образом:
Пример: извлечение данных об инвестиционных фондах ❖ 1Б9 firefly %>% html node("strong > span") %>% html_text() %>% as.numeric() Функция as.numeric преобразует строку с рейтингом в числовое пред- ставление. Знак > в пути можно убрать. Вхместо CSS-селекторов можно воспользоваться путем XPath: firefly %>% html_node(xpath="//strong//span") %>% html_text() %>% as.numeric() Для извлечения списка актеров воспользуемся функцией html_nodes, ко- торая возвращает все элементы, соответствующие данному пути, то есть весь список актеров: firefly %>% html nodes("#titleCast .itemprop span") %>% html_text() [1] "Nathan Fillion" "Gina Torres" "Alan Tudyk" "Morena Baccarin" "Adam Baldwin" "Jewel Staite" "Sean Maher" "Summer Glau" "Ron Glass" Тогда как html_node возвращает лишь первый элемент: firefly %>% html node("#titleCast .itemprop span") %>% htel_text() [1] "Nathan Fillion" Получим теперь последние сообщения, появившиеся на досках объявле- ний (message boards). Они собраны в таблицу .boards. Чтобы извлечь из неё текст, используем функцию html_table: > html_table() X2 billandbonnie aradiasnight rv_jt VivienCastro koliasie SacrosanctPariah firefly %>% html nodes(".boards") %>% XI Whirring guns Joss Whedon and his fans Episode List Problem Mai, Simon or Jayne? Order of watching 2 з 4 5 6 Finally got around to watching it. Пример: извлечение данных об инвестиционных фондах Наша задача: извлечь данные о биржевых инвестиционных фондах (Exchan- ge Traded Funds, ETF), размещенные на сайте Лондонской фондовой биржи (рис. 14.3). Как видно, данные собраны в таблицу, которая на момент написа- ния этого текста располагалась на 45 страницах. Нам нужно извлечь данные из первых шести колонок этой таблицы.
170 Пакет (vest Home page » Prices & markets > ETFs > ETFs Search ETFS SEARCH SEARCH ETFS Al data delayed by at least 15 minutes as at 17 26 | Pagel of45] 11|2|3|4|5|6|7|8|9| 10|Next|> Code * Name * Cur Price ♦/- %♦/- INFU 10Y INFBRKVN U USD 99.56 *0 20a ♦0.20 /Л d t*> INFL 210Y INFBRKVN G GBX 8.425.00 ♦90 00a ♦108 d t*> BRZ AMUNDI BRAZIL USD 37.84 *1 52a ♦417 d ti> BYBU AMUNDI BYBU USD 11860 *0 44a ♦0 37 z* uu d *»> CC1U AMUNDI CHINA US USD 24382 ♦1.28a ♦0.53 z* d ti> CJ1U AMUNDI CJ1U USD 177.91 ♦243a ♦138 ой d t*> cswu AMUNDI CSWU USD 261 89 ♦1.54a ♦0.59 z* и) d ti> CWEU AMUNDI CWEU USD 28200 ♦3.52a ♦126 z* d t*> CWFU AMUNDI CWFU USD 133 57 ♦0 44a ♦0 33 d t*> Рис. 14.3 ❖ Страница Exchange Traded Funds на сайте Лондонской фондовой биржи Все страницы однотипны, поэтому для сбора данных естественно исполь- зовать цикл. Тело цикла будут составлять операции по извлечению данных с одной страницы: # # 1. Получить таблицу (тело цикла). # Получить страницу url <- "http://www.londonstockexchange.com/exchange/ prices-and-markets/ETFs/ETFs.html" page <- read_html(url) # Функция для извлечения колонок 1:6 таблицы ETF get_table <- function(doc) { doc %>% html node("table") %>% html table() %>% .[1:6]
Работа с формами. Сессии 171 } # Получить таблицу table <- get table(page) head(table) ## Code Name ## 1 INFU 10Y INFBRKVN U ## 2 INFL 210Y INFBRKVN G # # 3 BRZ AMUNDI BRAZIL # # 4 BYBU AMUNDI BYBU # # 5 CC1U AMUNDI CHINA US # # 6 CJ1U AMUNDI CJ1U Cur Price +/- %+/- USD 99.84 -0.12 -0.12 GBX 8,361.00 -16.00 -0.19 USD 38.20 0.05 0.13 USD 123.16 -0.16 -0.13 USD 264.92 1.18 0.45 USD 183.52 -0.38 -0.20 С помощью браузера находим путь к элементу, храпящему число страниц таблицы: р. floatsx. # # 2. Определить число связанных страниц (счетчик цикла). # Найти число страниц s <- раде %>% html node(”p.floatsx") %>% html text() s <- strsplit(s,"of") %>% unlist() num_pages <- s[length(s)] %>% as.numeric num_pages # # [1] 45 # # 3. Обработать таблицы на связанных страницах (цикл обработки). # http://www.londonstockexchange.com/.../ETFs.html (уже обработана) # http://www.londonstockexchange.com/.../ETFs.html?&page=2 # http://www.londonstockexchange.com/.../ETFs.html?&page-3 # # Пример составления ссылки (на страницу 2): # с("http://www.londonstockexchange.com/.../ETFs.html", # "?&page=", 2) # Цикл обработки ссылок for (р in 2:num_pages) { cur.url <- pasteO(url,"?&page=",p) cur_page <- read html(cur_url) cur_table <- get table(cur_page) table <- rbind(table,cur_table) } Работа с формами. Сессии Работа с формами в пакете rvest состоит из трёх этапов. 1. Найти форму и обработать её, то есть определить, какие параметры (по- ля) она передаёт и каковы текущие значения этих параметров. 2. Установить нужные значения параметров формы. 3. Отправить форму, то есть выполнить HTTP-запрос с заданными пара- метрами.
172 Пакет rvest За поиск и обработку форм отвечает функция html_form, принимающая на вход документ XML или элемент, полученный в результате работы read_html. Разберём простейшую форму запроса: form <- '<form name="myfоrm" action="textinput" method="GET"> Введите текст: <br> cinput type="text" name="inputbox" value=""><br> cinput type="button" name="btnl" value="Read"> cinput type="button" name="btn2" value="Write"> c/form>' hdoc <- read html(form) myform <- html_form(hdoc) myform # # [[1]] ## <form> 'myform' (GET textinput) # # <input text> 'inputbox': # # <input button> 'btnl': Read # # <input button> 'btn2': Write Данная форма имеет три поля: две кнопки (button) btnl, btn2 и тексто- вое поле inputbox. Кнопки имеют значения Read и Write соответственно, текстовое поле - пусто. Значения полей формы задаются функцией set_values. Она принимает на вход имя формы, обработанной html_form, и пары параметр=значение. Например: filled_form <- set values(myform[[1]], inputbox = "Hello!") filled_form # # <form> 'myform' (GET textinput) # # <input text> 'inputbox': Hello! # # cinput button> 'btnl': Read # # cinput button> 'btn2': Write myform представляет собой список форм, состоящий в нашем случае из одного элемента. Поэтому, чтобы указать нужную форму, мы используем myform[[1]]. Возвращает set_values заполненную форму (filled_form), которая от- правляется на сервер функцией submit_f orm. В нашем случае это выглядит так: submit_form(session, filled_form) Переменная session хранит сведения о сессии. Чтобы разобраться с нею, понадобится немного теории.
Работа с формами. Сессии ❖ 173 Дело в том, что веб-сервер не поддерживает постоянного соединения с клиентом, поэтому каждый HTTP-запрос обрабатывается сам по себе, без связи с предыдущими1’. В то же время часто возникает необходимость от- слеживать запросы от конкретного пользователя или сохранять для него зна- чения некоторых переменных. Примером первой задачи является аутенти- фикция пользователя на сайте, примером второй - формирование корзины заказов в интернет-магазине. Для решения этих задач существуют сессии - механизм, позволяющий од- нозначно идентифицировать браузер пользователя и создающий для него файл на сервере, в котором хранятся переменные сеанса. Сессию можно рас- сматривать как сеанс работы с сайтом, так, как его понимает человек: при- шёл, поработал, затем закрыл браузер - и сессия завершилась. Сессия, правда, может завершиться и без закрытия браузера. Для этого ис- пользуется тайм-аут - заранее определенное время, по истечении которого сервер считает, что пользователь ушел с сайта. В rvest существует функция html_session, принимающая на вход URL и возвращающая объект класса session, то есть сессию. Этот объект исполь- зуется в функциях, реализующих HTTP-запросы, в частности в submit_form. Он указывает серверу, что речь о конкретном сеансе работы со скриптом-клиентом. Пример: аутентификация на форуме # Создаём сессию session <- html_.session("http: //login.rutracker.org/forum/login.php") # Получаем форму для аутентификации пользователя login_form <- html_form(session)[[2]] # Заполняем форму filled_form <- set_values(login_form, "login_username" = "вашлогин", "login_password" = "вашпароль") # Отправляем её submitform(session,filled_form) Submitting with 'login' <session> http://login.rutracker.org/forum/login.php Status: 200 Type: text/html; charset=windows-1251 Size: 13399 Если нужно сохранять пользовательскую информацию на более длитель- ный срок, используется механизм кукн. Для работы с ними в пакете httr существуют функции cookies и set_cookies. 13 Таковы особенности протокола HTTP. Мы уже затрагивали этот вопрос в главе 11.
174 Пакет (vest Функции навигации Функции jump to(session, url) follow_link(session, i) обе позволяют перейти на новую веб-страницу. Разница в том, что j ump_to делает это по заданному URL, a f ollow_link - по условию (i). Условие 1 может быть: • целым числом. Тогда будет выполнен переход по /-й ссылке, найденной на странице; • строкой. Переход выполняется по первой ссылке, содержащей эту стро- ку (с учётом регистра символов). Функция back возвращает на шаг назад в списке помещённых страниц, а история посещений за сеанс возвращается функцией session_history. Пример: s <- html session("http://hadley.nz") s$url # # [1] "http://hadley.nz/" s <- s %>% follow_link("github") # # Navigating to http://github.com/hadley/ s$url # # [1] "https://github.com/hadley/" s <- s %>% back() s$url # # [1] "http://hadley.nz/" s <- s %>% jump_to("http://recipes.had.co.nz/") s$url # # [1] "http://recipes.had.co.nz/" session_history(s) # # http://hadley.nz/ # # - http://recipes.had.co.nz/
Работа с кодировками ❖ 175 Работа с кодировками Для этого в rvest существуют две функции: • guess_encoding - пытается определить, в какой кодировке записан текст; • repair_encoding - исправляет кодировку. Работают они так: url <- "https://ru.Wikipedia.org/wiki/Ершов,_Андрей_Петрович" ershov <- read_html(url) text <- ershov %>% html nodes(".note") %>% html_text() text %>% guess_encoding() ## encoding language confidence ## 1 UTF-8 1.00 ## 2 IBM420_.lt Г ar 0.38 ## 3 UTF-16BE 0.10 ## 4 UTF-16LE 0.10 ## 5 GB18030 zh 0.10 ## 6 Big5 zh 0.10 ## 7 windows-1253 el 0.01 text %>% repair encoding() %>% cat() # # Best guess: UTF-8 (100% confident) # # один из первых программистов СССР и один из пионеров российской корпусной # # лингвистики, как создатель сибирской школы программирования ... Хорошей практикой является, однако, не исправлять последствия опреде- ления кодировки, а указывать её в самом начале, при обработке веб-страницы: ershov <- read_html(url, encoding = "UTF-8") Заключительные замечания и ссылки В Python ближайшим аналогом httr является пакет Requests, a Python- овский beautifulsoup вообще послужил образцом при создании rvest. По- дробно с ними можно ознакомиться по книге: • Митчелл Р. Скрапинг веб-сайтов с помощью Python. М.: ДМК Пресс, 2016.
17Б ❖ Пакет rvest Кроме того, в книге Митчелл описаны базовые принципы обработки тек- стов на естественных языках (глава 8) и распознования изображений (гла- ва 11). Последнее нужно, в частности, для скрапинга страниц, защищённых САРТСНА-кодом. Возможности R не ограничиваются указанными в этой главе пакетами для получения и обработки веб-страниц. Не забывайте про CRAN Task View: Web Technologies and Services.
Глава 15 RSelenium: управляем браузером Пакет RSelenium позволяет использовать R для тестирования веб-приложе- пий и сбора данных с веб-страниц. RSelenium представляет собой интерфейс к Selenium Standalone Server {wwwseleniumhq.org) - инструменту для автоматизированного управления браузерами. Таким образом, при помощи RSelenium мы сможем управлять браузером с помощью команд на языке R. Для этого нужно, как обычно, установить пакет RSelenium и загрузить его в сеансе работы с R: install.packages("RSelenium") library("RSelenium") Если мы запускаем RSelenium в первый раз, то нужно установить Selenium Standalone Server - пока же мы установили только интерфейс к нему. Чтобы установить Selenium, скомандуем R: checkForServer() В результате R скачает Selenium Server и установит его в нужную папку надиске (R сообщит, в какую именно. В принципе, в эту папку можно помес- тить Selenium, скачанный вручную). Когда Selenium Server будет установлен, запустим его: startServer() Создадим объект-браузер: rd <- remoteDriver() Так мы сможем управлять браузером по умолчанию. Но можно обратить- ся и к конкретному браузеру, например к Mozilla Firefox: rd <- remoteDriver(remoteServerAddr = "localhost", port = 4444, browserName="firefox")
178 RSelenium: управляем браузером Эта возможность особенно полезна, если нужно тестировать веб-приложе- ние в различных браузерах. Чтобы теперь запустить Firefox, нужно вызвать rd$open() то есть вызвать метод open () объекта rd - экземпляра класса remoteDriver. В результате откроется окно браузера, а в окне R появится информация о версии браузера и операционной системы. Примерно такая: [1] "Connecting to remote server" SapplicationCacheEnabled [1] TRUE $rotatable [1] FALSE ShandlesAlerts [1] TRUE SdatabaseEnabled [1] TRUE $version [1] "44.0.2" $platform [1] "WINDOWS" SnativeEvents [1] FALSE SacceptSslCerts [1] TRUE Swebdriver.remote.sessionid [1] "cf3c3cdd-e912-4b5d-969e-febe4fed32ad" SwebStorageEnabled [1] TRUE SlocationContextEnabled [1] TRUE SbrowserName [1] "firefox" StakesScreenshot [1] TRUE $javascriptEnabled [1] TRUE ScssSelectorsEnabled [1] TRUE Sid [1] "cf3c3cdd-e912-4b5d-969e-febe4fed32ad" Переместимся теперь на нужную веб-страницу, например на Google. Сде- лать это можно при помощи метода navigate() объекта-браузера rd: rd$navigate("http://google.com") Введём в форму поиска фразу R Project. Но сначала нужно указать, где находится эта форма на странице Google. Поиск нужного элемента страни- цы осуществляется с помощью метода findElement(). Поиск может выпол- няться как по XPath и CSS-селекторам, так и более топко - по атрибутам id, class и name. Нам подойдёт name, так как у формы поиска очень простое имя - q.
Пример: перевод с помощью Yandex.Translate ❖ 179 webElem <- rd$findElement(using = "name", "q") Теперь в найденный элемент страницы - форму поиска - нужно записать строку R Pro j ес t и нажать клавишу Enter для выполнения запроса. Это мож- но сделать двумя последовательными командами, но можно справиться и од- ной: webElem$sendKeysToElement( list("R Project","\uE007") ) Метод sendKeysToElement () посылает текст найденному ранее элементу веб-страницы webElem. Список list содержит текст, помещаемый в форму (R Project), и код нажатия клавиши Enter (\uE0O7). Полный список подоб- ных кодов можно найти на странице WebDriver. Таким образом, весь процесс сводится к двум шагам: 1) находится нужный элемент страницы; 2) этому элементу отсылается команда. Пример: перевод с помощью Yandex.Translate Переведём названия нескольких городов на английский при помощи RSele- nium и Yandex.Translate. Поместим переводимый текст в элемент in_text и заберём результат перевода из out_text. Переведённый текст из- влечём с помощью метода getElementText() и сохраним в переменной out. library(RSelenium) startServer() rd <- remoteDriver(remoteServerAddr = "localhost", port = 4444, browserName="firefox") rd$open() rd$navigate("https://translate.yandex.com/") in_text <- rd$findElement(using = "xpath", value = "//div[@class='item item_left'] //textarea[@id='textarea']") # Переведём названия городов на английский in_text$sendKeysToElement(list("Москва, Санкт-Петербург, Саратов", "\uE007")) out_text <- rd$findElement(using = "xpath", value = ".//*[@id='translation']") out <- out_text$getElementText() Представленный выше код иллюстрирует возможности применения RSe- lenium. Для решения же практических задач лучше воспользоваться Yandex.Translate API.
180 RSelenium: управляем браузером Пример: динамически генерируемая ссылка на файл Список вакансий, размещённых на сайте Министерства труда и социальной защиты Республики Беларусь, можно скачать в файле MS Excel (рис. 15.1). Проблема в том, что ссылка на этот файл генерируется динамически, при помощи JavaScript. Л это как раз задачка для RSelenium. ЗЕПЬВЕНСКИЙ СИПИАЛ и ГРОДНЕНСКОГО ОБЛПОТРЕБОБШЕСТВА ГРОДНЕНСКАЯ ОБЛАСТЬ гл Зе льва УЛ СОВЕТСКАЯ 25 т 8 015641 2-53- 50 Кнжэнор->норгетии СПЕЦИМЫТОЕ СМЕНА ПОСТОЯННАЯ 2 400 000 УПРАВЛЕНИЕ СЕЛЬСКОГО ХОЗЯЙСТВА И ПРОДОВОЛЬСТВИЯ ГРОДНЕНСКАЯ ОБЛАСТЬ ГП Зельвд УЛ 17 СЕНТЯБРЯ 25 т. 8 01564 2 52 10 заместите л» руководите л= организации по идеолог ичесюй раОоте (Заместитель начальника отдела организации производстве сел ьсхохоз яй геенн о й продукции |агроном) ВЫСШЕЕ СМЕНА ПОСТОЯННАЯ 5 420 000 Рис. 15.1 ❖ Ссылка на вакансии, размещённые на сайте Министерства труда и социальной защиты Республики Беларусь (vacancy.mintrud.by) Делаем всё так же, как и раньше: переходим на страничку с результата- ми поиска, находим элемент, соответствующий ссылке «Excel» (его id равен ctl0O_cphMain_lnkVacancyExportXLS). Затем кликаем по этой ссылке при помощи clickElement() и закрываем браузер. library('RSelenium') startServer() rd <- remoteDriver(remoteServerAddr = "localhost", port = 4444, browserName="firefox") rd$open() rd$navigate("http://vacancy.mintrud.by/user/Pages/ Public/VacancySearchResults.aspx?") rd$screenshot(display = TRUE) # делаем снимок экрана webElem <- rd$findElement(using = 'id*,
Пример: динамически генерируемая ссылка на файл 181 value = "ctl00 cphMain InkVacancyExportXLS") webElem$clickElement() rd$close() Команда rd$screenshot делает снимок окна браузера. Сейчас она не слиш- ком полезна, но для браузеров без графического интерфейса (один из них мы рассмотрим в следующей главе) возможность сделать скриншот помога- ет контролировать поведение браузера. Например, определить, что браузер находится на искомой странице. Задача решена? Нет, есть ещё один нюанс. Firefox по команде clickElement() не сохраняет файл сразу, а спрашивает у пользователя, со- хранять ли ему файл. Это сводит на нет все успехи автоматизации. Поведение Firefox настраивается в профиле браузера (рис. 15.2). Строка browser.helperApps.neverAsk.saveToDisk должна иметь значение true. Рис. 15.2 ❖ Настройка профиля браузера Mozilla Firefox Проблема в том, что при работе с Selenium’oM Firefox каждый раз загружа- ет чистый профиль, в котором не сохраняются нужные нам настройки.
182 RSelenium: управляем браузером Значит, необходимо создать профиль с нужными настройками и запус- кать из Selenium’a Firefox именно с этим профилем. Создать профиль можно следующим образом (предположим, что мы ра- ботаем в Windows): 1. Закрыть все запущенные копии Firefox. 2. В меню Пуск выбрать Выполнить... 3. В командной строке наберём: firefox. ехе -р 4. В открывшемся окне нажать кнопку Создать... (рис. 15.3), прочитать инструкции и нажать Далее. 5. Указать имя профиля, его расположение и нажать Готово. Рис. 15.3 ♦> Создание профиля браузера Mozilla Firefox Для запуска Firefox’a с созданным профилем из Selenium’a выполняем в R: fprof <- getFirefoxProfile(../AppData/Roaming/Mozilla/ Firefox/Profiles/t4df9qj j.myproflie") rd <- remoteDriver(extraCapabilities = fprof) rd$open() Аргумент extraCapabilities указывает имя загружаемого профиля, а ар- гумент функции getFirefoxProfile() - его расположение в Windows. В Linux это было бы-/.mozilla/firefox/t4df9qj j .my_profile. Итак, мы научились создавать новый профиль Firefox и запускать браузер с этим профилем из Selenium’a. Собираем все наши результаты вместе:
Selenium и браузеры ❖ 183 library('RSelenium') startServer() fprof <- getFirefoxProfile(/AppData/Roaming/Mozilla/ Firefox/Profiles/t4df9qjj.my_profile", useBase = TRUE) rd <- remoteDriver(remoteServerAddr = "localhost", port = 4444, browserName="firefox", extraCapabilities = fprof) rd$open() rd$navigate("http://vacancy.mintrud.by/user/Pages/ Public/VacancySearchResults.aspx?") webElem <- rd$findElement(using = 'id', value = "ctlOO cphMain.lnkVacancyExportXLS") webElem$clickElement() rd$close() Selenium и браузеры Selenium, а значит, и RSelenium, устойчиво работает c Firefox - из обычных браузеров, и с PhantomJS - из браузеров без графического интерфейса (они называются «headless» - буквально: «безголовые». Ими мы займёмся в сле- дующей главе)16. Причём именно с Firefox, а не с абстрактным браузером на движке Gecko. Для остальных же браузеров, даже таких популярных, как Google Chro- me, Safari и Opera, требуется установка дополнительных драйверов. Их мож- но скачать с сайта Selenium. Проблема заключается в том, что эти драйверы далеко не всегда работают. Распространённый совет по решению этой про- блемы: нужно установить самую свежую версию драйвера. Работа с послед- ними версиями драйверов вообще является приметой Selenium’a: браузеры обновляются часто, и средства автоматизации должны отслеживать эти из- менения. Резюме и ссылки Что даёт RSelenium по сравнению с rvest: • дополняет rvest, в частности ио способам поиска элементов разметки; • предоставляет другой подход к извлечению данных, ориентированный на использование элементов интерфейса сайта («нажать кнопку» и т. п.). Иногда такой подход более удобен; • лучше «принимается» поисковыми сервисами, поскольку фактическую работу выполняет браузер (при равной частоте запросы со стороны rvest и прочих роботов могут восприниматься как вирусная атака); 1(1 Подробный обзор поддерживаемых RSelenium браузеров приведен в виньетке RSelenium: Driving OS/Browsers local and remote.
184 ❖ RSelenium: управляем браузером • позволяет извлечь динамически сформированные элементы страницы. Дополнительная информация: • В пакете RSelenium замечательные виньетки. Для быстрого старта луч- ше всего использовать RSelenium basics. • О Selenium е на русском: Selenium2.ru.
Глава PhantomJS и обработка динамических веб-страниц Динамические страницы: описание проблемы Допустим, что нам нужно извлечь из веб-страницы Techstars.com таблицу с данными. Вроде той, что показана на рис. 16.1. techstars ABOUT мжгюио UstOfComfMAfeS BLOG П CHS TAR Я. TV JOBS солист US APPIY FOR Tf CH STARS Techstars delivers the best results. Al Techstars we believe tn full transparency Here is a complete ksl of ai of the companies that we ve ever funded We've included their current status, funding raised, and more Overview Status Number of Companies Percentage Active 419 75 63% Acquired 75 13 54% Failed 62 11 19% Рис. 16.1 ♦> Веб-страница co списком компаний, финансируемых Techstars.com Но оказывается, что эти данные не хранятся на странице. Они формиру- ются на ней динамически, с помощью скриптов на JavaScript (рис. 16.2). Как говорится: «Видит око, да зуб неймёт». Вот и наш браузер «видит», но сохранить увиденную таблицу не может. Браузер сохраняет страницу в том виде, в котором получил её от сервера. Но на странице, помимо статической разметки HTML, могут содержаться скрипты - программы, которые браузер выполняет, когда демонстрирует страницу пользователю, то есть нам с вами. Нужно, чтобы браузер сохранил страницу в этом последнем виде, с уже вы- полненными скриптами. А он - неймёт.
186 PhantomJS и обработка динамических веб-странии <div olass=’’entry-conEentw> <hl>Tech5taxs delivers the best results.</hl> <p>At Tecnstars, we believe in lull transparency. Here is a complete list or all or tne conpaniei (function() { var script » document. createElement (’script ‘; script. src="httDs: //connect. techstars. ccm/vidgets/portfolio-statistics. j s' script .async = true; var entry = document. getElemerxsByTagNaiae("script ’) [0]; entry.parentNode.insettBefore(script, entry); D (): С/в a r </div><.'— .entry-content —> e/artielex/— tpost-5987 —•> <footer id="edaphon” role="conter.tinfo ’> <div id—'"sponsoxa"> <h2>A Special Thants to:</h2> Рис. 16.2 ❖ Реализация динамических таблиц Решить эту проблему поможет... браузер. Но браузер необычный. Phan- tomJS - это полноценный браузер с поддержкой JavaScript, CSS, DOM, SVG, Canvas и..., но - без графического интерфейса (рис. 16.3). PhantomJS основан на движке Webkit и, таким образом, является «род- ственником» Google Chrome и Яндекс.Браузера. Он применяется для unit- тестирования скриптов, создания скриншотов страниц и т. д. Мы же исполь- зуем его для добычи информации. Установка Скачайте PhantomJS, скопируйте его файлы в нужный вам каталог и добавь- те в PATH путь к исполняемому файлу phantomj s. ехе (мы будем ориентиро- ваться на работу в Windows). Обычно этот файл находится в подкаталоге bin/. Запуск Наберём в консоли операционной системы phantomj s, а затем, уже в самом PhantomJS, следующие команды console.log('Hello, world!'); phantom.exit(); и убедимся, что браузер работает (рис. 16.4).
Пример: рендеринг веб-странииы 187 PhantomJS is an optimal solution for HEADLESS WEBSITE TESTING Run functional tests with frameworks such as jasmine, QUnil Mocha. Capybara. WebDnver, and many others. I parn more SCREEN CAPTURE Programmatically capture web contents, including svg and Canvas. Create web site screenshots with thumbnail preview. I earn inure PAGE AUTOMATION Access and manipulate webpages with the standard DOM API. or with usual libraries like jQuery. Learn more NETWORK MONITORING Monitor page loading and export as standard HAR flies. Automate performance analysis using YSIuw and Jenkins. Learn more Рис. 16.3 ❖ Официальный сайт PhantomJS - phantomjs.org С:\Users\Admin>phantomjs phantomjs> console.logCHello, world?*> Hello, world? undef ined . phantonjs> phant ^i.exit < > ; C:\Users\Admin> Рис. 16.4 ❖ Сеанс работы c PhantomJS в консоли Можно сохранить эти команды в скрипте hello. j s и запустить этот скрипт, введя в консоли phantomjs hello.js Пример: рендеринг веб-странииы Следующий скрипт загружает главную страницу Github и сохраняет её изоб- ражение в файл github. png:
188 PhantomJS и обработка динамических веб-странии // github.js var page = require('webpage').create(); page.open('http://github.com/', function() { page.render('github.png'); phantom.exit(); }); Функция require() служит для загрузки модулей JavaScript. В данном случае загружается модуль webpage из PhantomJS, служащий для создания веб-страницы. Обработка (парсинг) содержимого веб-страницы обычно на- чинается с загрузки этого модуля. Затем вызывается функция open из webpage, которая имеет следующий формат: open(url, callback) {void} Эта функция открывает url и загружает его в созданную страницу раде. После того как страница загрузится, выполняется callback-функция. В нашем случае callback-функция создаёт изображение git hub. png и за- крывает PhantomJS. Вызов phantomjs github.js даёт нам снимок экрана, показанный на рис. 16.5. Сохранение веб-странииы в файл Вернёмся к нашей основной задаче. Для сохранения страницы с таблицей в файле HTML создадим следующий скрипт: // scrape.techstars.js var page = require('webpage').create(); var fs = require('fs'); var path = 'techstars.html' page.open('http://www.techstars.com/companies/stats/', function (status) { var content = page.content; fs.write(path,content, 'w')
Сохранение веб-страницы в файл ❖ 189 Why you'll love GitHub. PowerfK reales tc rru»« rcn celaborafea Greet c olstxrMcr 518-4 with ccnr-u'icaion The easiest way to use GitHub on Windows Рис. 16.5 ❖ Скриншот главной страницы GitHub, сделанный с помощью PhantomJS
190 PhantomJS и обработка динамических веб-странии phantom.exit(); }); Модуль f s содержит операции файлового ввода-вывода. Метод content возвращает содержимое веб-страницы в виде строки. Таким образом, в call- back-функции мы получаем содержимое искомой веб-страницы и записыва- ем его в файл techstars. html, расположенный в текущем каталоге. Путь к файлу задан в переменной path. Запуск phantomjs scrape_techstars.js даёт такую же по виду страницу, как и раныпе. Но вот исходный код у неё уже другой (рис. 16.6). <div olass="entry—content•> <bl>Techstors delivers the best results.</hl> <p>At lechscars, we believe in full transparency. Here is a ccmplete list of all of the conqpanie <p>4nbsp;</p> <pxdivxdiv alase=**reaults_seetion,,> <div alasB-nheadingw>Overview</div> stable class-"bable ---------------- <tbodyXtr> <th>St atus</th> < th > Number of Con₽anies</th> <tn>Fercentage</tn> </tr> <tr> к <td>Accive</ ctcl class=’rigncw>4i9</td> <td class=’righc’>75.63%</td> </tr> <tr> <td>Acquired</td> <td class=’rigncw>75</td> <td class=’righcw>13.54%</td> </tr> <tr> < tXD-FalletK/td> <td class="nghC’>62</td> <td class=Briahc’>11.19%</td> </tr> </tbody> </tabie> Рис. 16.6 ❖ Исходный код веб-страницы после обработки PhantomJS Теперь извлечь из таблицы данные - дело техники. Резюме и ссылки Selenium (глава 15) и PhantomJS отлично дополняют друг друга при работе с динамическими веб-страницами. Selenium удобен для отладки: вы своими глазами видите, что происходит в браузере. PhantomJS лучше подходит для
Резюме и ссылки ❖ 191 окончательной версии скрипта, когда визуализация становится не нужной, а требуется лишь извлечь данные. Дополнительная информация: • Виньетка RSelenium: Headless browsing. • Ещё одним «безголовым» браузером, поддерживаемым RSelenium, яв- ляется HtmlUnit {htmlunit.sourceforge.net).
Глава 17 Facebook В этой главе мы рассмотрим механизм регистрации по протоколу OAuth 2.0, который поддерживают крупнейшие социальные сети и другие веб-сервисы. Покажем, как это работает, на примере доступа к ЛР1 Facebook. Затем мы по- знакомимся со специализированным пакетом Rfacebook для работы с этой социальной сетью и создадим с его помощью Facebook-приложение. Протокол авторизации OAuth 2.0 Допустим, мы создали приложение, собирающее данные о друзьях пользова- телей социальной сети. Чтобы оно могло получать информацию, необходим доступ к учётным записям пользователей. Причём все права приложению не нужны - достаточно лишь доступа на чтение к списку друзей. Возможно ли предоставить приложению ограниченный доступ к учётным записям, не передавая при этом логины и пароли пользователей? Да, это возможно, и реализовано в OAuth 2.0 - открытом протоколе авто- ризации, который поддерживают крупнейшие социальные сети: Facebook, Twitter, ВКонтакте и т. п. Чтобы обеспечить приложению доступ к социальной сети или другому веб-сервису, API которого поддерживает OAuth 2.0, нам понадобится: 1) зайти на страницу сервиса, адресованную разработчикам приложений, и зарегистрировать там своё приложение; 2) указать при регистрации, какие права и па какой вид данных нужны приложению; 3) получить от сервиса специальный ключ (токен) для доступа к данным (access token) или несколько таких ключей. Технически работа с веб-сервисом будет строиться на отправке ему GET- запросов. К каждому запросу добавляется полученный токен, указывающий сервису, какие права доступа имеет данное приложение. Ответ сервиса, как правило, возвращает данные в формате JSON. Таким образом, нам понадо- бятся пакеты для работы с HTTP-запросами и с JSON.
Получение маркера доступа пользователя API Graph 193 Получение маркера доступа пользователя API Graph Зарегистрируем наше приложение на странице «Facebook for developers» (developers.facebook.com) с помощью меню Инструменты и поддержка (Tools & Support) (рис. 17.1). facebook for developers | Продукты Документы | Инструменты н поддержка | Новости Видео 31 Зарегистрироваться На связи со всем миром. Создавайте, продвигайте и монетизируйте приложения с помощью Facebook Messenger Platform Создайте скос «Вход через Facebook» создайте аккаунт Публикация материалов на Facebook Facebook Analytic* for Apps Монетизация мобильных приложений Рис. 17.1 ❖ Для регистрации приложения выберем меню Инструменты и поддержка на странице «Facebook for developers» Для получения токена доступа пли, как его называют в Facebook - марке- ра доступа, - воспользуемся ссылкой на API Graph Explorer (рис. 17.2). API Graph - это основной интерфейс для загрузки и получения данных с Face- book. Он позволяет создавать публикации, управлять рекламой, загружать фото и выполнять множество других задач. Кроме того, у API Graph есть подробная справка на русском языке. На странице «Graph API Explorer» выберем из выпадающего списка Get Token пункт Get User Access Token (рис. 17.3). Теперь нужно указать виды данных, к которым нашему приложению будет разрешён доступ (рис. 17.4). Мы позволим собирать информацию о друзьях (user_f riends), родном го- роде пользователя (user_hometown) и лайках (user_likes), то есть понра- вившихся пользователю объектах.
194 Facebook Зарегистрироваться focebook fix developers | Продукты Документы Инструменты и поддержка Новости Видео Facebook Platform is Healthy Tools & Support Go to Platform Bugs Tools Access Токи» Tool Sharing DHxjggrr Lead Ads RTU Debug Tool Object liroww API Upgrade tool Comments Moderation Tool Analytics for Apps Ads Manager JS SDK Console Cl Wftat can wi> Iwlp you with? Latest Bugs view all Feed Webhook does гкИ fire when user goes Hwe An unknown итог has ouurrcd whrvi tryng to reach recent post An unknown error occurred Insight ad IcvH Instant Article ordered list shows blank/whitespace on Android Cant retrieve stickers m RI Uhwebhooks and Graph API Some published Instant Articles appear as normal links (without Itghtning bolt) I acebook creates Status Post Instead of Link Post Рис. 17.2 ❖ Выберем ссылку Graph API Explorer на странице «Tools & Support» focebook for developers | Продукты Документы Инструменты и поддержка Новости Видео Зарегистрироваться Graph API Explorer пркпакечн! ’I Graph API Lxjrorer » Vepwp доступ ~ Get Token ▼ £2323 ro-own |- Gt UawAcow Token | CQ| GET- /g.7 -/иеПеИэ-Ш,юте “ Get*w Гг*’*п ПмрИиин «I Get Page Access Toten Рис. 17.3 ❖ Выберем из выпадающего списка Get Token пункт Get User Access Token Если до этого момента вы ещё не вошли в Facebook как пользователь, то вам предложат войти. После чего на странице «Graph API Explorer» появит- ся ваш маркер доступа (рис. 17.5).
Получение маркера доступа пользователя API Graph 195 Graph API Explorer " Graph AM tMprorer - Мв0«М ДГ .rry-ir Select Permission» (?2Д22| FQL Query EH GFT • — f-g 7 » f 1Клт Dul.i PvrinKMixr, П ema< Г1 cxjolBh_acdafti В usei_about_me ""laser brthoay d user_eduocion_h«ofy Vl user_frends d user_wmas_actwty v user_hometoAir В userjkes О userjocatori user photos □ user_posts ' u$er_retet»nshio_ derate — userjefationshbs П user_rek?ion_poltts В user su tus I- u9er_taooed_pbce$ n user-Videos В user_web$lte Г7 user_wortc.history c<«e« □ о’ттатолсе AR Graph Г rents. Groups я Pages H adsjrenaoement EdOsjead П busness^ranagement —I rrwnjgejwoei П pages'Twagecra Open Graph Actions П user_actBns.books П uses_acxo5.ftness Other П read_audience_network_insghts jjaoe5_rnanaae_instant_art)des I read _paoe_ma boxes В p^es.meisagtig E tw_event caces_mes5sqra prone number Г use- events В pages_show_fct В pubhh_pages r user_amons.mrsr ' user.actionsjiews L Lt9erjrenag«(j_oiout>s Г U5er_a<tronsvdeo Ruble prolie rcbded by detaUt В read custom fnendiEts П read_nsohts Get Access Token Отмена Рис. 17.4 ❖ В окне Select Permissions укажем виды данных, доступ к которым будет разрешён приложению facebook for developers | Продукты Документы Инструменты нподдержка Новости Видео Зарегистрироваться Graph API Explorer Grap* API Fiptorer ▼ (-1-! i.H • -• • 'me7falds=Kf,name падвобаее а еаитакеее АЯ orapr Рис. 17.5 ♦> Маркер доступа пользователя на странице «Graph API Explorer» Маркеры доступа пользователя (User Access Token) бывают двух видов: с коротким или с длительным сроками действия. Краткосрочные маркеры действуют около часа или двух. Веб-вход в Facebook, который мы использо- вали, позволяет получать только краткосрочные маркеры. Так что время от времени процесс получения маркера придётся повторять (рис. 17.6). Краткосрочные маркеры можно преобразовать в маркеры длительного дей- ствия. Этот процесс описан в справке. Действие долгосрочных маркеров про- должается около 60 дней.
196 Facebook Graph API Explorer Припожсг. e- Pi Grap*! API Explorer » joct.4 . Д EWXdEO8C0<fiAD79xUuneB5N5NZAClt8FcfWQ2aoOGE4»SBe2BrlGgA21<WrtoMaA0WOTj?1ZBEeKtMll5)W05e45maZAB7HdQ | SGelTofrw ” Refresh r>rc*O’Gei TOO»<r педюмее a синтаксисе ar oraon Рис. 17.6 ❖ «Graph API Explorer» сигнализирует о необходимости обновить маркер доступа пользователя Доступ к данным с помощью rvest и jsonlite Система представления данных на Facebook состоит из: • узлов, которые представляют собой объекты (Пользователь, Фото, Стра- ница и т. п); • границ - элементов, которые соединяют между собой объекты-узлы (например, «Фото на Странице», «Комментарии к Фото»); • полей, содержащих информацию об объектах (день рождения пользо- вателя, имя страницы и т. и.). Для того чтобы получить данные с помощью API Graph, нужно отправить HTTP-запросы GET по адресу graph. facebook. com, указав нужный объект или границу. При этом к запросам нужно добавлять полученный ранее мар- кер доступа пользователя. Выглядит запрос примерно так: library(rvest) access_token <- "ваш маркер доступа" url <- sprintf("https://graph.facebook.com/me?access_token=%s", access_token) fb_hdoc <- read_html(url) Узел /me - особый. При выполнении запроса он преобразуется в user_id - идентификатор пользователя Facebook, чей маркер доступа (User Access Token) в данный момент используется для вызовов API (или в page_id - идентификатор страницы, если доступ выполняется через Page Access Token). В данном случае запрос выполняется от имени автора текста. Отклик API на запрос состоит из пар поле-значение: { "поле": {значение}, } Что ж, информация получена и находится в f b_hdoc. Теперь её надо из- влечь.
Доступ к данным с помощью rvest и jsonlite ❖ 197 # Извлекаем из HTML-документа данные в формате JSON. fb_json <- fb_hdoc %>% html_text() # Загружаем библиотеку для работы с JSON. library(j sonlite) # Преобразуем данные из JSON в список (list). me <- fromJSON(fb_json) # Извлекаем поля из списка. me$last_name me$gender ## [1] "Khramov" ## [1] "male" Чтобы запрос к ЛР1 выполнялся быстрее, желательно уточнить его, указав интересующие поля (fields) данного узла. Для этого в запрос включают пара- метр fields. Например, запросим значения полей last_name и gender поль- зователя те: url <- sprintf( "https://graph.facebook.com/me?fields=last_name,gender&access_token=%s", access_token ) me <- url %>% read_html() %>% html_text() %>% fromJSON() str(me) ## List of 3 ## $ last_name: chr "Khramov" ## $ gender : chr "male" ## $ id : Chr "102937246821677" Теперь посмотрим, что пользователь me отметил лайком (likes): url <- sprintf("https://graph.facebook.com/me/likes?access_token=%s", access_token) fb_hdoc <- read html(url) fb.json <- fb_hdoc %>% html text() fb_data <- fromJSON(fb_json) likes <- fb_data$data likes ## name category id created_time ## 1 Marie Dorin-Habert Athlete 275689296123505 2016-08-08T06:11:53+0000 ## 2 Simon Fourcade Athlete 369856300342 2016 - 08-08T06:11:34+0000 ## 3 Biathlonworld Sports Team 111143045599740 2016-08-08T06:11:14+0000 ## 4 Dorothea Wierer Athlete 1586468241609752 2016-08-08TO6:10:59+0000 ## 5 Gabriela Koukalova Athlete 1555476481357812 2016-08-08T06:09:03+0000 За более подробной информацией о командах API обратитесь к Graph API Reference. Краткая сводка команд поиска приведена в табл. 17.1.
198 Facebook Таблица 17.1 ❖ Сводка команд поиска с помощью API Graph Объект Команда поиска человек страница событие группа место checkin search?q=ivanov&type=user search?q=latex&type=page search?q=conference&type=event search?q=programming&type=group search?q=coffee&type=place search?type=checkin Пример поиска пользователей «Ivanov»: url <- sprintf( "https://graph.facebook.com/search?q=ivanov&type=user&access_token=%s", access_token ) dat <- url %>% read html() %>% html text() %>% fromJSON() %>% .$data Можно было бы реализовать запросы при помощи пактов RCurl или htt г. Последний удобнее благодаря наличию в нём функций для работы с OAuth. Наш выбор - использовать rvest - продиктован лишь желанием полнее рас- крыть многообразие возможностей R. Теперь, когда мы узнали, как задавать запросы и обрабатывать ответы ЛР1 Graph, перейдём к готовым решениям. Разумеется, в R существует пакет для работы с Facebook - это Rf acebook. Им мы сейчас и воспользуемся. Пакет Rfacebook и создание приложения Запрос, выполненный с помощью маркера доступа пользователя и пакета Rfacebook, выглядит так: library(Rfacebook) access_token <- "ваш маркер доступа" me <- getUsers("me", access_token) me$gender ## [1] "male" Даже наши небольшие эксперименты с API Graph показывают, что мар- кер доступа пользователя удобен лишь для решения сравнительно простых задач, справиться с которыми можно за время действия маркера. Частые об- новления маркера доставляют неудобства и заставляют искать другое реше- ние. I I оно существует - это маркеры доступа приложения.
Пакет Rfacebook и создание приложения 199 Маркеры доступа приложения позволяют отправлять запросы к API Face- book от лица приложения. С их помощью можно изменять параметры при- ложения, анализировать его статистику, создавать тестовых пользователей и управлять ими. Для того чтобы получить маркер доступа приложения, нужно зарегистри- роваться в качестве разработчика Facebook (рис. 17.7). Face book Platform is Healthy Рис. 17.7 ❖ Регистрация разработчика Facebook на странице «Facebook for developers» После того как вы зарегистрируетесь и войдёте в Facebook уже в качестве разработчика, выберите в выпадающем списке Мои приложения пункт До- бавить новое приложение (рис. 17.8). Затем в появившемся окне выберите вариант Веб-сайт (рис. 17.9). Задайте имя создаваемого приложения (рис. 17.10). В результате мы попадём на страницу приложения (рис. 17.11), где в раз- деле Панель узнаем идентификатор приложения (app_id) и его «секрет» (app_secret), а также текущую версию API (рис.). Идентификатор и секрет приложения вместе служат для создания искомого маркера доступа прило- жения. Маркер доступа приложения создаётся функцией fbOAuth пакета Rfacebook: fbOAuth(app_id, app_secret, extended_permissions = FALSE) Параметр extended_permissions, установленный в TRUE, даёт доступ к некоторой личной информации пользователя, прошедшего аутентификацию (день рождения, город проживания, отношения и т. п.). При первом использовании функция fbOAuth предложит добавить па стра- ницу приложения URL веб-сайта (рис. 17.13). После сохранения этой ин- формации можно будет продолжить работу в R. Получим информацию о пользователе те теперь уже с помощью приложе- ния Facebook:
200 Facebook facebook for developers | Продукты Документы Инструменты и поддержка Новости Видео Мон приложения » «Вход через Facebook» создайте аккаузгг всего за два М1ИКЙ Е> Публикация материалов на Facebook Органическое продвижение приложений и осб-сайтиа Facebook Analytics for Apps Гюймнге iuk люди используют ваше приложение Монетизация мобильных приложений Монетизируйте мобильное прмгожем«е нг.и мобильный осб-сайт с помощью ре» гиь'ы Messenger Platform Создайте свою программу «М» •ггосы охватить ООО миллионов человек Рис. 17.8 ❖ Чтобы создать приложение Facebook, выберите в выпадающем списке Мои приложения пункт Добавить новое приложение Add a New Арр iOS Android Facebook canvas Веб-сайт basic setup Рис. 17.9 ❖ Окно выбора типа приложения
Пакет Rfacebook и создание приложения 201 Рис. 17.10 ❖ Задание имени приложения Ф Bkatast ю ПМ1Л0ЖВМИЯ. • <* View Analysts • Инс трументы и подоерхла Панель Настрой»! К>ги Предупреждения Проверы лрилмения * Добавить продукт Product Setup Вход через Facebook Ллдю-с'виуЯ" s мире продукт для входа в социальны cent Audience Network Моеетяуируа'я ваш* мобжък» п(ми>(|»ыя1> ми веФ-ctf' » cue' наг явной допами i" J мхя<швмтв решамадятепей Facebeck. Account Kit Удобное создание ежзучтое. Паролем ие требуется. Messenger Настрала рермат общаию е тод.лм в Messenger Webhooks Благодаря 'Л'бЬРыжв (ранее «Сбиаалеиие в оазтмв раргьнсга орманы.) вы макете падлисивят.еи «а ижяем>иш <лю(ъ« ен котми огон»««атъ я получать обнивпяиия в доима ревпжхт» ерямм« без П.ЭПЛЛ API Messenger Expression Биот одари вашему птжпокепис мода смогут творчеси вырвзить себя a Messenger facebook lor developer | Open Graph Благодаря API с сипвнси гилязаилей ткздм смогут п\ «ле о^ернляп: и сктлстусьеаазтъ новоста. Рис. 17.11 ❖ Страница приложения Facebook
202 Facebook VewAnavbcs * Инструменты и лоддерма liketest Данное пр«го»*н<№ t режиме <ик>»(!от|» ч wxfci нсгк in>ИМть.ч гогъсо админигтр^>.,эмл рирайотмаамв и tccw<>tuHr~»v псшонамая ;»> Версия API и Идентификатор приложения V2 7 —« -Г cetper приложения Рис. 17.12 ❖ Панель приложения Facebook Докумен Й? liketest Панель Настройки Oci«Mi.oe Дополи игв пьио Роли Предупрежден ио Проверка приложения ♦ довавлть прсдугт ю приложения vewAnaiyiKs * Инструменты и поддержка Документы Идентифнскср приложений Отображаемое машамте Домены приложений URL ajpec полиции <омф»\денциаг®но<-Тк >тачот приложения веб-сайт URL.-адрес сайта mtaffiocanostwai Сеирет прыложешя Прострхгао имен Эл адрес для связи URL-адрес Пользовательского соглашения Категорий Чпвосш • Рис. 17.13 ❖ Адрес веб-сайта, необходимый для работы fbOAuth
Пакет Rfacebook и создание приложения 203 # Создать маркер OAuth для сеанса работы в Facebook. fb_oaiith <- fbOAuth(app_id="eauj app_id", app_secret="eaw app secret") # Получить данные о пользователе. me <- getUsers("me", token=fb_oauth) # Вывести имя пользователя meSname # # [1] "Dmitry Khramov" Функция getUsers возвращает общедоступную (public) информацию об одном или нескольких пользователях Facebook. getUsers(users, token) Аргумент users содержит вектор идентификаторов пользователей, а аргу- мент token - маркер доступа, построенный функцией fbOAuth. Поиск по страницам Facebook выполняет функция searchPages. Найдём 100 общедоступных страниц, упоминающих «\vierer», и выведем имя первой страницы: pages <- searchPages( string="wierer", token=fb_oauth, n=100 ) doro <- as.vector(pages[l,"id"]) # # [1] "Dorothea Wierer" Назначение большинства функций Rfacebook, например таких, как getFriends или getPost, достаточно очевидно. Более подробную информа- цию можно получить в справочном руководстве Rfacebook и в статье «In- troducing the Rfacebook package» разработчика пакета Пабло Барбера (Pablo Barbera).
Глава Сбор информации с помошью API ВКонтакте Рассмотрим, как собирать данные о пользователях социальной сети ВКон- такте, на примере построения графа дружеских связей. Для создания приложения ВКонтакте нам понадобится доступ к этой сети в качестве пользователя. Зарегистрироваться можно по адресу сети (vk.com). Далее мы: 1) зарегистрируем приложение ВКонтакте и получим доступ к этой сети; 2) разработаем программу для сбора данных; 3) соберём данные и построим на их основе граф дружеских связей; 4) рассмотрим ещё несколько задач, например как найти конкретного поль- зователя сети; 5) узнаем об ограничениях на получение информации, накладываемых правилами и API ВКонтакте. Создание приложения Для сбора информации с помощью API ВКонтакте в первую очередь нуж- но создать приложение этой сети. В нашем случае приложением будет про- грамма, в которой содержатся запросы к API и обрабатывается полученная информация. Самого приложения у нас пока нет, но мы должны зарегистри- ровать его в сети и указать, к какого рода данным оно будет иметь права до- ступа. В ответ API сообщит нам идентификатор приложения и код доступа, под которым приложение будет входить в сеть. Регистрация приложения Инструкцию по созданию приложения можно найти в меню Документация в разделе для разработчиков ВКонтакте (vk.com/dev). Выберите пункт Другие платформы (рис. 18.1) и перейдите по ссылке «Создайте новое приложение».
Создание приложения ❖ 205 Ж Dewtopers Документация Мои приложения Поддержка Правила 3. поис Быстрый старт Мобиг.иные приложении Сайты и виджеты | лрг' « плвтформы Сообщения сообществ Callback ДИ Платежный АН Игровая платформа Работа с API SDK Roadmap Слисок методов Правила Обратная связь Другие платформы Доступ к API ВКонтакте может выть получен из приложения для любых платформ Window^ OS X. Linux Вы сможете использовать все методы API. реализуя любую функциональность, необходимую Вашему приложению Создание приложения создайте новое приложение, выбрав тип - «standalcne-прнложенне- в дальнейшей работе Вам потребуются знамения полей «ID приложения* (в документации ему соответствует параметр APl.lD. арр.И или client, kf и «Защищенный ключ* secret key с berrt. secret Способы авторизации • OAuth авторизация Клиентская или серверная авторизация на базе протокола OAuth 7.0 Прямая авторизация Дгл дохмтреиных при/южений возможен альтернат ивнь й способ - ттрнман за т ори нации т о лог ину и паролю полоэователя. Выполнение запросов к API взаимодействие с АН происходит по стандартной схеме. При атом Вы можете использовать все методы API. ЙКонцжте® 2016 о компании помощь правила рекламе разработчикам вакансии Язык Русский Рис. 18.1 ❖ Меню «Другие платформы» в разделе для разработчиков ВКонтакте Затем выберите тип создаваемого приложения (рис. 18.2) - Standalone- приложение. Потребуется ввести код подтверждения, высылаемый на мо- бильный, после чего хмы попадём на страницу управления приложением. Developers Документация Мои приложения Поддержка Правила Быстрый старт Работа с API SOK Roadmap Слисок методов Правила Обратная связь Создание приложения Название Тип « stand а lone-при поженив Вебсайт iFrame/Flash приложение Подключить приложение Рис. 18.2 ❖ Выбор типа создаваемого приложения Теперь наше приложение зарегистрировано в сети и ему присвоен уни- кальный идентификатор. Скопируем этот идентификатор («ID приложения»
206 Сбор информации с помощью API ВКонтакте на вкладке Настройки) (рис. 18.3). Он понадобится для получения кода до- ступа (access_token). Developers документация мои приложения Поддержка правила О их/-» 0 *k_get_info Настройки Информация Чжетройкм Хранимые процедуры Статистика Руководство Статус Помощи 10 приложения. VT ЧН Защищенный ключ; ЫНЛГ^И Состояние. Приложение отклнхено первый запрос к aft Рис. 18.3 ❖ Настройки приложения Получение кода доступа Подробная инструкция по получению кода доступа приведена в разделе до- кументации Работа с API. Сформируем в адресной строке браузера запрос на получение access_token. Структура запроса показана на рис. 18.4. https: //oauth. vk. com/authonze? dientjd=APP_IDS scope=PERMISSIONS^ redrecturi=REDIRECT_URIS display=DISPLAYS v=API_VERSIONS response_type=token Рис. 18.4 ❖ Структура URL на получение access_token сети ВКонтакте Здесь: • APP_ID - идентификатор приложения; • PERMISSIONS - запрашиваемые права доступа приложения; • DISPLAY - внешний вид окна авторизации (page, popup или mobile); • REDIRECT—URI - адрес, на который будет передан access_token; • API_VERSION - версия API, которую вы используете.
Получение данных 207 Идентификатор приложения мы только что получили, внешний вид окна укажем page, REDIRECT_URI равен https://oauth.vk.com/blank.htmL текущая версия API приведена в разделе справки Версии API. Права доступа приложения описаны в одноимённом разделе документа- ции, расположенном по адресу vk.com/dev/permissions. Они представляют со- бой список названий необходимых приложению прав, разделённых пробела- ми или запятыми. В нашем случае это friends, что даёт доступ к друзьям пользователя, и offline, позволяющее доступ к API в любое время. Строка запроса выглядит так: https://oauth.vk.com/authorize?client_id=IDnpMnoxeHMB&display=page &redirect_uri=https://oauth.vk.com/blank.html&scope=frlends,offline &response_type=token&v=5.45 В ответ на запрос система предложит вам подтвердить заявленные права приложения, после чего ваш браузер будет перенаправлен на REDIRECT—URI. Нужная информация найдётся в адресной строке: https://oauth.vk.com/blank.html#access_token=ACCESS_TOKEN &expires_in=0&user_id=USER_ID Скопируем полученные ACCESS_TOKEN и USER_ID. Теперь у нас есть код доступа, чтобы осуществлять запросы к API ВКонтакте. Получение данных Для получения данных от API ВКонтакте нужно осуществить POST или GET запрос по протоколу HTTPS вида: https://api.vk.com/method/METHOD_NAME?PARAMETERS &access_token=ACCESS_TOKEN где • METHOD-NAME - название метода из списка функций API. Например, метод для получения информации о друзьях пользователя называется friends.get; • PARAMETERS - параметры соответствующего метода, определяющие, что конкретно вы хотите узнать (например, контактные данные пользова- теля). Список параметров каждого метода приведен в справке. Вот он для friends.get; • ACCESS-TOKEN - ключ доступа, полученный в результате авторизации приложения. В итоге получим примерно следующее:
208 Сбор информации с помошью API ВКонтакте https://api.vk.com/method/friends.get?user_id=66748&v=5.45& access_token=533bacf01ellf55b536a565b57531acll4461ae8736d6506a3 Ответом на такой запрос будут данные в формате JSON. Таким образом, для получения данных нам понадобятся инструменты для: • отправки POST- или GET-запросов; • обработки данных в формате JSON. Реализация в R Отправку HTTP-запросов реализуем с помощью пакета RCurl, а для обра- ботки JSON воспользуемся пакетом rjson. Установим пакеты install.packages(c("RCurl","rjson")) и загрузим их: library(RCurl) library(rjson) Введём ключ доступа и номер версии API: access_token <- "ключ_доступа„вашего_приложения" ver <- "5.45" Сформируем строку запроса и выполним этот запрос: url <- sprintf( "https://api.vk.com/method/%s%s£access token=%s&v=%s", method, params, access_token, ver ) data <- getURL(url) Здесь access-token и ver выступают в роли глобальных параметров про- граммы, method - строка с именем метода, params - список параметров дан- ного метода. Например: params <- list(user_id = "...") Список параметров предварительно преобразуется в строку params <- sprintf( "?%s", paste( names(params), , unlist(params), collapse = sep = "" ) ) которая добавляется в url. Полученные данные data обрабатываются функцией from JSON () из паке- та г j son, которая возвращает список list
Получение данных ❖ 209 list <- fromJSON( data ) Из этого списка нас интересует элемент list$response. При получении данных возможны ошибки, поэтому в этом месте нужно добавить их обработку. Вот что получается в результате: vk <- function( method, params ) { if( !missing(params) ){ params <- sprintf( "?%s", paste( names(params), "=", unlist (params), collapse = sep = "" ) ) } else { params <- "" } url <- sprintf( "https://api.vk.com/method/%s%s&access_token=%s&v=%s", method, params, access_token, ver ) data <- getURL(url) tryCatch({ list <- fromJSON( data ) }, error = function (e) { print(data) stop(e) }) list$response } Функция vk() принимает имя метода и его параметры, а возвращает спи- сок с результатами запроса. С её помощью мы и будем добывать данные. Для начала получим список друзей пользователя с идентификатором uid: # Введём идентификатор пользователя uid <- "идентификатор пользователя" params <- list(user_id = uid) friendList <- vk( "friends.get", params ) При таком запросе мы получим только идентификаторы друзей. В нашей задаче этого достаточно, но если понадобится более полная информация, нуж- но использовать параметр fields. При этом обязательно возвращаются име- на (first_name) и фамилии (last_name) друзей, а также то, что конкретно указывается в fields. Например, так params <- list(user_id = uid, fields = "contacts") можно получить идентификатор, имя, фамилию и контактные данные (contacts) пользователя uid. Разумеется, мы получим только те данные, ко- торые пользователь пожелал о себе открыть. Теперь составим список друзей для каждого из друзей пользователя и до- бавим в этот список его самого:
210 Сбор информации с помощью API ВКонтакте count <- friendList$count ids <- sapply(friendList$items, function(x) x$id) # Список друзей для каждого из друзей. friendLists <- vector(length = count) for (i in l:count) { params <- list(user_id = idsfi]) list <- vk( "friends.get", params ) friendLists[i] <- list(list$items) } # Добавим пользователя в списки собственных друзей. friendLists <- c(friendLists, list(ids)) Вместо цикла можно использовать apply-функцию. Это работало бы быст- рее. Однако в данном случае скорость может навредить, так как API ВКон- такте накладывает ограничение на частоту запросов - не более 3 в секунду. Так что может даже понадобится «тормоз» - Sys. sleep. Списки друзей нам нужны, чтобы установить число общих друзей для каждой пары пользователей. Существует специальный метод friends. getMutual, возвращающий эту величину для пары пользователей. Но при его использовании количество запросов будет слишком большим: О(п2) для п друзей против О(п) в нашем случае. Вместо лишних запросов мы вычисляем количество друзей сами, на локальном компьютере. Построим матрицу инцидентности будущего графа. На пересечении i-й строки и у-го столбца этой матрицы будет стоять число взаимных друзей i-го иу-го пользователей: n <- length(friendLists) mutuals <- matrix(nrow = n, ncol = n) for (i in l:n) { for (j in l:n) { # NA не t . mutuals[i,j] <- sum(friendListsf[i]] %in% friendListsf[j]]) } } Построение графа связей Для построения графа используем пакет igraph,. Этот пакет позволяет по- строить граф непосредственно по матрице инцидентности: g <- graphfrom adjacency_matrix(mutuals, weighted=T, mode="undirected", diag=F) В нашем случае граф будет неориентированный ("undirected"), вес рё- бер учитывается (weighted=T),aдиaгoнaльныeэлeмeнты матрицы инцидент- ности игнорируются (diag = FALSE). Эти элементы нам не нужны. Они пред- ставляют собой длину списка друзей пользователя и на графе представля- лись бы в виде петель. Пусть имена вершин графа равны их номерам в списке friendLists.
Построение графа связей 211 V(g)$name <- as.character(l:n) Здесь можно было бы проявить фантазию: поместить в вершины графа фамилии пользователей или их аватарки. Мы этого не делали, чтобы обес- печить пользователям анонимность. Можно удалить из графа изолированные вершины (вершины, степень ко- торых равна нулю): isolated <- V(g)[degree(g)==0] g <- g - vertices(isolated) Хочется, чтобы пользователи, имеющие больше общих друзей, располага- лись па графе ближе, чем те, у кого общих друзей меньше. Поэтому для пред- ставления графа используем алгоритм Kamada-Kawai. В нём предполагает- ся, что рёбра графа - это пружины, жесткость которых задаётся параметром weights. Причём большим значениям соответствуют более длинные рёбра. В нашем случае в качестве весов weights удобно использовать величины, обратно пропорциональные весам из матрицы инцидентности (с небольшим смещением): egam <- max(E(g)$weight+.5) / (E(g)$weight+.5) coords <- layout„with„kk(g, weights-egam) Теперь строим граф и сохраняем его в файле (рис. 18.5): png(file = "graph.png") plot(g, layout=coords, vertex.size=15) dev.off() Рис. 18.5 ❖ Граф дружеских взаимосвязей, построенный по данным из сети ВКонтакте
212 Сбор информации с помошью API ВКонтакте Получение другой информации из сети Мы построили граф, используя в качестве характеристики близости пользо- вателей число их общих друзей. Ясно, что можно вводить и другие характе- ристики. Например: • число общих сообществ (групп и публичных страниц); • число лайков на одни и те же объекты; • взаимное общение (число заметок-комментариев). Далее укажем несколько методов, позволяющих судить об интересах и ак- тивности пользователя. Полный список методов приведен в разделе Список методов справочной системы ВКонтакте. Списки сообществ позволяют судить об интересах пользователя: • users.getSubscriptions - возвращает список идентификаторов пользователей и публичных страниц (пабликов), на которые подписан пользователь; • groups.get - возвращает список сообществ (групп и пабликов) ука- занного пользователя. Параметр f ields=description возвращает опи- сание группы. О влиятельности пользователя можно судить по списку его подписчиков: • users.getFollowers - возвращает список идентификаторов пользо- вателей, которые являются подписчиками указанного пользователя. # Список сообществ (групп и пабликов) с описанием. params <- list(user_id = uid, extended = 1, fields = "description") groups <- vk( "groups.get", params ) # Список подписок (пабликов и пользователей) с описанием. params <- list(user_id = uid, extended = 1, fields = "description") subscriptions <- vk( "users.getSubscriptions", params ) # Список подписчиков params <- list(user_id = uid, fields = "last_name") followers <- vk( "users.getFollowers", params ) # часть из них может быть забанена или деактивирована Лайки (отметки «Мне нравится») также позволяют судить о пристрасти- ях пользователя: • fave. getPhotos - возвращает фотографии, на которых текущий поль- зователь поставил отметку «Мне правится»; • fave. getPosts - возвращает посты; • fave.getvideos - видео. Об активности пользователя можно судить по числу сделанных им заме- ток: • notes. get - возвращает список заметок, созданных пользователем. Следует отметить, что в результате будут получены только те заметки, ко- торые пользователь сделал доступными для вашего приложения (точнее, для
Получение другой информации из сети 21В пользователя, на чьё имя оно зарегистрировано). Кроме того, чтобы полу- чить доступ к записям, нужно установить права доступа scope=notes при регистрации приложения. Наше приложение для построения графа таких прав не имеет. Список записей на «стене» пользователя (нужны права scope=wall при регистрации приложения): • wall. get - возвращает список записей со стены пользователя или со- общества; • wall. getReposts - возвращает список репостов указанной записи. Если вам нужно получить e-mail пользователя, то при регистрации при- ложения укажите scope=email. Но даже в этом случае вы получите e-mail, только если пользователь дал к нему доступ. Поиск пользователя Для поиска пользователей существует метод users. search, но он, как пра- вило, находит слишком много пользователей. Поэтому приходится вводить ограничения, простейшие из которых - страна и город проживания. Одна- ко использовать названия стран и городов напрямую не получится. Вместо этого нужно получить идентификаторы страны и города. Сначала нужно получить идентификатор страны, так как он является обя- зательным параметром при поиске идентификатора города. Для этого вве- дём двухбуквенный код страны (RU - Россия, UA - Украина, BY - Беларусь и т. и.): # Сначала получаем id страны. params <- list(code = "RU”) # код страны countries <- vk( "database.getCountries", params ) id <- countries$items[[l]]$id Получим список городов страны с идентификатором id: params <- list(country_id = id) cities <- vk( "database.getcities", params ) В списке cities содержатся как имена, так и идентификаторы городов. Найдём нужный город по фрагменту его имени: # Введём фрагмент названия нужного города. city_name <- "..." # Список описаний найденных городов. city <- cities$items[grep(city_name,cities$items)] # Если городов найдено несколько, возвращается первый из них. cid <- city[[l]]$id Теперь, имея возможность ограничить поиск, ищем конкретного пользо- вателя:
214 Сбор информации с помощью API ВКонтакте name <- "..." # Возможно, придётся искать на английском языке. params <- list(q = name, fields = "city", city = cid) searched_users <- vk( "users.search", params )$items Скорее всего, будет найдено несколько пользователей, так что здесь при- дётся немного поэкспериментировать. # Введём фамилию так, как её записал пользователь. user_last_name <- user <- searched_users[grep(user_last_name,searched_users)] # Если пользователей найдено несколько, возвращается первый из них. suid <- user[[l]]$id Убедимся, что найден нужный вам человек. Для этого получим о нём более подробную информацию: params <- list(user_ids = suid, fields = "photo_200_orig,education,universities,occupation") searched <- vk( "users.get", params ) Ограничения Пользователи открывают ту или иную информацию для всех пользователей, только для зарегистрированных пользователей или только для своих друзей. Следовательно, объём полученной информации зависит от уровня близости между пользователем, на которого зарегистрировано приложение, и пользо- вателем, информация о котором вас интересует. Действуют ограничения на число запросов к API. Для клиентского прило- жения лимит составляет 3 rps (раза в секунду). Для серверного приложения действует прогрессивная шкала, в зависимости от числа установок приложе- ния (rps/число установок): 5/< 10000, 8/<100000, 20/<1000000, 35/>1000000. Кроме ограничений на частоту обращений, существуют и количественные ограничения на вызов однотипных методов. Точных лимитов для таких слу- чаев разработчики ВКонтакте не сообщают. Для нас это значит, что нужно стараться уложится в лимиты и предусмотреть обработку ошибок, если ЛР1 начнёт выдавать пустые результаты.
Глава 19 Использование Twitter API В этой главе .мы создадим приложение в Twitter и решим три простые задачи: • выполним поиск по ключевым словам и сохраним его результаты в базе данных; • отфильтруем часть данных, оставив только геокодированные данные; • построим облако слов. Получение доступа к Twitter API Чтобы получить ключи доступа к Twitter Search API, у вас должен быть за- регистрирован аккаунт на Twitter и в этом аккаунте указан мобильный теле- фон. Создать такой аккаунт - дело нескольких минут. Теперь нужно создать приложение (арр) для Twitter-a. Входим в свой аккаунт и направляемся в раздел Developers, а оттуда - в Documentation, раздел Manage Му Apps (рис. 19.1). Жмём кнопку «Create New Лрр» и заполняем форму (рис. 19.2). Имя вашего приложения (Name) должно быть уникальным, иначе систе- ма не позволит его создать. Адрес веб-сайта (Website) должен быть оформ- лен по правилам оформления веб-адресов, но не обязательно должен быть реальным - можно ввести «заглушку», например http://tests.org/ - больше этот адрес нам не понадобится. После того как система подтвердит создание приложения, на вкладке Keys and Access Tokens мы найдём нужные ключи. Подключение к Twitter из R Теперь нужно установить пакет расширения R - twitteR, отвечающий за поиск данных в Twitter. Для этого вам понадобятся данные с вкладки Keys and Access Tokens (API Key, API Secret, Access Token и Access Token Secret) вашего Twitter- приложения. Их мы передадим функции setup_twitter_oauth из пакета twitteR, которая непосредственно выполняет аутентификацию.
216 ❖ Использование Twitter API / Developers / Documentation / Overview Search Q English Documentation Documentation Fabric Twitter for Websites The Twitter Platform connects your website or application with the worldwide conversation happen r g on T witter cards О Audi ф API c hangdog -=uо.. Get Juto-nolifed w*-en Twitter АЯ chenge» View Details REST APIS stream ng apis Ads APIS Fabric MoPub Best Practices Fabr t is a flexible modular set of mobile development tools ca ied "Kits" that help you make your app more stable add social features i ike sharing and login, and I л n your apo into a business with easy monetization API Overview API Status Twitter for Websites Events Case Studies Twitter cr Wet te is a suite of embeddable widgets, buttons, and chent-side scnpting tools to integrate I w tter and d sc ay Tweets on your webs te or JavaScnpt apolKation ncludingthe т л ect Button, the г oik л Button. Embedded Tweets, and Embedded Timelines Рис. 19.1 ❖ Ссылка на раздел «Manage Му Apps» Create an application Application Details Name ! TwrterAnahrss Vow wxssMr. name. 7M ш usea к arhbure Ше source of а меег ana in аее/ fauna screens 32 cnaracw? /лае. Description Analysis for Twtter data «жгалипвоос aesovoon wirn rwu oeshown fnнэем&сгода.лполгаеог»screens Bsiwean roanrf гос characters ma> Webshe vowrwieteooeiBufinc'yaccBSSiOte home Mpe wire'e irserewripotoifciis’iwtf maacuseof wftndout'norBmterrM' twiaOoirf /тот аоситаОол Th<t^t-QMf^94UfV.»MKl^eK source anriOuOon Its ftreeis ueated Or rout aopaceOoo end wrf be shown xi usei-Stcmp aumonzatarr screens. ff yoo dor I hsve л UPt yol,' us» put j pJscshoAior net Bui SMismsor to cMnt|v i >л)и> > Callback url IWrere rVionef we re&irri ate* a<rtxees'uii,' ou-fcenfcsrfuic? CAutfi Г Ла вгцвЛсв'Л rJar,i.uAi еадАЛи зрееЛ Ггег.г саиП ra'iliac* t/QL ivi <Пе гец.^вМГоАеп Лес tscudeSB vatu-e еЬеч него Го resort/juorav’c Joo iirrwosvHr ra'ibuc*e. Рис. 19.2 ❖ Окно создания приложения Twitter
Поиск и сохранение его результатов в базе данных 217 Введем в командном окне R: library(twitteR) api.key <- "ВАШ API KEY" api_secret <- "ВАШ API SECRET" access_token <- "ВАШ ACCESS TOKEN" access_token_secret <- "ВАШ ACCESS TOKEN SECRET" setup_twitter_oauth(api_key, api_secret, access_token, access_token_secret) После того как R доложит от успешном подключении, можно приступать к поиску. По окончании аутентификации может появиться вопрос, сохранить ли ко- ды доступа OAuth в локальном файле для последующего использования: [1] "Using direct authentication" Use a local file to cache OAuth access credentials between R sessions? 1: Yes 2: No Любой вариант ответа будет приемлем. Поиск и сохранение его результатов в базе данных Поиск в Twitter выполняется функцией searchTwitter с единственным обя- зательным аргументом - строкой ключевых слов. Следующий код говорит сам за себя: # Создадим базу в текущем каталоге. # (потребуется установить пакет RSQLite, если он не установлен) register sqlite backend( data mining.sqlite") # Получим твиты по заданным ключевым словам. tweets = searchTwitter("data mining") # Сохраним твиты в базе. storetweetsdb(tweets) # Загрузим твиты из базы в таблицу R. from_db = load_tweets„db(as.data.frame = TRUE) Функция searchTwitter позволяет задать количество возвращаемых тви- тов (п), их язык (lang), временной интервал (since - «от» и until - «до»), расположение автора твита (geocode) и другие параметры. Кроме того, аргу- мент retryOnRateLimit блокирует запуски на заданное время по достиже- нии лимита, установленного разработчиками API. Это несколько удлиняет время сбора информации, но избавляет от необходимости писать собствен- ные «тормоза».
218 Использование Twitter API Заметим, что количество возвращаемых твитов может быть меньше задан- ного п значения также из-за ограничений API. Об этих ограничениях мы по- говорим позднее. Фильтрация результатов поиска Составим строку поиска из запросов, разделённых знаками ' + ': search_string <- pasteG(c("новый год","#новыйгод"),collapse = "+") Если поиск ведётся на русском языке, это желательно указывать в пара- метре lang tweets <- searchTwitter(search_string, n=15000, lang="ru") иначе есть риск получить в результатах постороннюю «нелатиницу». Объект tweets относится к классу status и имеет множество методов get*(), возвращающих ту или иную часть твита. Например, функция getText возвращает текст: text <- sapply(tweets, function(x) x$getText()) Отфильтруем из полученных твитов те, что содержат данные о расположе- нии их авторов (непустые значения долготы или широты) и сохраним тек- сты твитов, имена и расположение авторов, а также время создания твита в таблице df: nonzero_geocode <- sapply(tweets, function(x) as.logical(length(x$getLatitude())) ) data <- sapply( tweets[nonzero_geocode], function(x) list(ScreenName=x$getScreenName(), Text=x$getText(), Lat=x$getLatitude(), Lon=x$getLongitude(), Date=x$getCreated()) ) col_names <- row.names(data) num_cols <- length(col_names) num_rows <- round(length(data)/num_cols) df <- data.frame( matrix(data, ncol=num_cols, nrow=num_rows, byrow=TRUE), stringsAsFactors = FALSE ) names(df) <- col_names Подробнее о функциях пакета twitteR можно прочитать в документации и небольшой вводной статье «Twitter client for R» от разработчика пакета - Джеффа Джентри (Jeff Gentry).
Построение облака слов 219 nr»<fc Inin -a trends Hni rl^ SsSSBsM“!. « I ° Я0», = average point! ^termsr* LllUnj tino । ™ ™ ™ £Z spend generated й|_^ E£_ । *" o*w । oworkingt information -= keyword — -* fikir « ti,^repo'rt"ul''*/YY рег°те negative shows Microsoft | АП table Г5. campaigns rite™! -keywords£ g new = wqrd cd found [ list IUUI s 8 campaign SgHje ямШ *£§ s site 2 -^bounce 1й Рис. 19.3 Построение облака слов Вы наверняка встречали картинки, похожие на ту, что приведена на рис. 19.3. Это - «облако слов» (word cloud). Оно позволяет наглядно представить клю- чевые слова, содержащиеся в документе, и широко используется в интеллек- туальном анализе текстов (text mining). Сейчас мы научимся строить такие облака самостоятельно. Облако слов, или, как его еще называют, облако тегов (tag cloud) - это визуальное представление списка ключевых слов какого-либо текста. Важ- ность каждого ключевого слова (частота его упоминания) обозначается в об- лаке размером шрифта и цветом. Чаще всего в виде облака изображается список категорий веб-сайта. Соб- ственно, для этого облака слов и были придуманы — впервые они появились на сервисе хранения цифровых фотографий Flickr и уже затем приобрели популярность как инструмент анализа текстов. Фактически облако слов вы- полняет ту же функцию, что и оглавление в книге, - даёт читателю возмож- ность составить общее представление о содержании текста прежде, че4м углу- биться в чтение. Для построения облака слов, помимо twitteR, нам понадобятся пакеты: tm - для анализа текста (text mining) и wordcloud - для создания облака слов. Чтобы построить облако слов, нужно:
220 Использование Twitter API 1. Получить данные для анализа. В нашем случае это будут твиты, из ко- торых выделен текст, но источником может служить и обычный тек- стовый файл. 2. Создать лексический корпус и терм-докумептную матрицу с помощью функций пакета tm. 3. Получить ключевые слова и частоту их упоминания. 4. Нарисовать облако слов. Данные для анализа Получим твиты и сохраним их в переменной tweets: tweets <- searchTwitter("data mining", n=500, lang="en") Ограничимся пока твитами на английском языке. К каждому твиту применим уже знакомую нам функцию выделения тек- ста: text = sapply(tweets, function(x) x$getText()) Лексический корпус и терм-документная матрица Вообще говоря, лексический корпус - это коллекция текстов, подлежащих анализу. В нашем случае это символьный вектор с несколькими дополни- тельными атрибутами. Терм-документная матрица описывает частоту, с которой тот или иной термин встречается в документах корпуса: строки матрицы соответствуют документам, столбцы — терминам. Значение элемента матрицы равно часто- те употребления термина в документе. Для построения лексического корпуса и терм-документной матрицы ис- пользуются функции Corpus и TermDocumentMatrix: corpus <- Corpus(VectorSource(text)) tdm <- TermDocumentMatrix(corpus, control = list(removePunctuation = TRUE, stopwords = c("data", "mining", stopwords("english")), removeNumbers = TRUE, tolower = TRUE)) С помощью параметра control мы выполняем очистку текста от симво- лов пунктуации, стои-слов (например, артиклей английского языка), чисел и переводим все буквы в строчные.
Построение облака слов 221 Ключевые слова и их частоты Итак, ключевые слова, очищенные от «мусора», содержатся в терм-документ- ной матрице. Сохраним её как обычную матрицу, отсортируем частоты в по- рядке убывания и сохраним результат в виде таблицы: m <- as.matrix(tdm) word_freqs <- sort(rowSums(m), decreasing=TRUE) dm <- data.frame(word=names(word_freqs), freq=word_freqs) Подробнее о функциях пакета tm можно узнать из документации к пакету. Облако слов Осталось лишь нарисовать облако слов. Это делается функцией wordcloud одноименного пакета: wordcloud(dm$word, dm$freq, random.order=FALSE, colors=brewer.pal(8, "Dark2")) dmSword, dm$freq обращаются к колонкам word и freq таблицы dm. brewer.pal() — функция, создающая цветовую палитру. В нашем случае она берет 8 цветов из готовой палитры Dar k2. Посмотреть все доступные па- литры можно по адресу: http://colorbrewer2.org/. В готовом скрипте всего 16 строк: library(twitteR) library(tm) library(wordcloud) api_key <- "..." api_secret <- access_token <- access_token_secret <- setuptwitter oauth(api_key, api_secret, access_token, access_token_secret) tweets <- searchTwitter("data mining", n=500, lang="en") text <- sapply(tweets, function(x) x$getText()) corpus <- Corpus(VectorSource(text)) tdm <- TermDocumentMatrix(corpus, control = list(removePunctuation = TRUE, stopwords = c("data", "mining", stopwords("english")), removeNumbers = TRUE, tolower = TRUE)) m <- as.matrix(tdm) word_freqs <- sort(rowSums(m), decreasing=TRUE) dm <- data.frame(word=names(word_freqs), freq=word_freqs)
222 Использование Twitter API wordcloud(dm$word, dm$freq, random.order=FALSE, colors=brewer.pal(8, "Dark2")) В результате получим рис. 19.4. У вас, скорее всего, получится похожее, по всё же другое облако - ведь появились новые твиты. Заметим, что если ограничиться только построением облака слов, то для этого есть более простые способы, чем использование R. Загляните, напри- мер, на http://www.wordle.net/. httptcoimnfial dataminingco workshop conference artificial executrvecityofmel bourne devices attend httptcozraoynhyqxcheck( transformation httptcoircqouzu eff|C(€nt businessmtelligence travel tno... maybe httptcocrxpbzoosl coursera httptcocskmiihtadegrees anjneyaparasnar nttptcocjccaj hmng courses httptCOUditxhksht vendorneutralbgrcssrirh ,ntei?s«n,hgosntechno'o9y ргей,с,';®а"^'с5 ^?»s0S0(t informationengineer services schools «exaiytlCS Webinar«>l»dus including pmce®*, b |earnedCharlene toolsticklaqd httptCOllditxhrql clearance httptcoygycjm9 to experts science SOCiai r 7 warehousing 11 У I ha^eappnsce help program due httPtcovbbwky time httptcozzzvtecz II f f innoechobusiness bia ro techniques^a^d output^'gw §-q securitypost machine D,9 cj Д J ? httptcopirsznjndv ^ g-httptcolirfryaservice >ljstnck|and 2 curriculum good^, iivavy VJQOI I VIM || I nui 1 1 1 vi vi 1 u’wy r~- — a Snator poll buildl"via using’s^ Т*wetecrapmg арр^||(рГ^mp _ 1ГЫdata mi rung 2 crties open^ also sbi’hli ipmivncan cxplonnggreat qampa := ZJ WGK8 C/j 4 UJUtHIJIA «3 industry theory mag ic^ JeiSE.-510 bigdata server enterprise Wflte > yntp p*. Ф- —— ‘ stocksappncations analysis . . liaiaouiui iw iom qdiecu'onS = “^T4i | >>kdnuggets £ - c ? rrt ^predictive « го® ;U internet|0ЬтаУ must securitypost macnine.-у -g Зс+ri^Honrl □ hnptcopirsznjnav (U A . CKiadnQa^ curriculum gooCisOf5 pn^ydashdb ipt -fhaimson bEg3 programming |'™SSctive ™r* t ,»m a Dj|h7aA~usina«»»«'»«~ss“a’ ^5S^X«lnSI%^onnxV-l5U «Lmn“inept "®' ”7 pnv5t0. ___wph^cranmn aPP+\ а лНлг&атр SnewPU'tVC|c^iid's beyond^uch^ facebook cloudera chns I httptcopirsznboc seminar U n?n7ellig7nce^aika^es says modeling,^! ft v software X,if 11 datascience^ вД if1 i J--- „,wl¥,wrr > wylie _______________ Obama itrtggrowth5bugs | predictive charu readchaSa₽7' | -Г. иоЬтяи know challenges sqi ^internet’ таУ companies analyt.cbndge 5 “Worthless need ibmresearch lexi"9ton I tA/phditp espnbobehavescomputerphiiekaika ? ntcoooblzoqm cloud Wcowkkksdml insights accessrapidminer ~....- h xplaimo absconsulting wting httptcohwpbpvbrur archive “Tftptcocnktpvpstt b.0datelechcons disncymtoms linkedin 9* thank past find ‘c educatinggatesf fitness § patient nuggets month — operations httptCOlirgyn decision interesting key proceedings a,vc things—analytics Website Cif>nt>°t b^iiU htEuleL ГП http,tcoP°°lzocim hriptceunurodroyps h,2'X ‘ ™ 'U dceSminer ,ns^ integrity cloudant stevecranko suyatrtech httptcoywnczoexp , m°s^t dataconomymedo httptcofxjymhhvk speakers httptconefrnaynqq 0,,httptcokabdu Prc<iictod sports rservant watson Рис. 19.4 ❖ Облако слов, созданное по запросу «data mining
Ограничения Search API 223 Ограничения Search API Ограничения по сбору данных Twitter API описаны в разделе справки: Public API/API Rate Limits. В настоящее время приложение может выполнять не более 450 запросов в течение 15 минут. Streaming API Выше мы описали работу с Search API Twitter. Этот API позволяет собирать данные из выборки твитов, опубликованных за последние 7 дней. Выборка является частью массива твитов, и существуют «свежие» твиты, которые в неё не попадают. Для доступа ко всему массиву твитов используется Twitter Streaming API. В R для работы с ним существует пакет streamR, а в Python - библиотека Tweepy. Кроме того, существует ряд пополняемых архивов данных Twitter, в част- ности Internet Archive. Данные в нём представлены в виде архивных файлов размером в несколько десятков гигабайт. Однако если только вы не покупаете данные у официального реселлера Twitter вроде Gnip, то вам всё равно будет доступна лишь ограниченная вы- борка данных. Насколько ограниченная? По некоторым оценкам, в Internet Archive (archive.org/details/twitterstream) представлен примерно 1% от всех твитов за соответствующий период времени. Поиск в архиве твитов можно выполнять с помощью сервиса BackTwe- ets.com. Относительно объема этого архива процитируем BackTweets FAQ: BackTweets searches through a tweet index spanning more than three years and reaching up to real-time. The index grows daily. Поиск может выполняться по обычному запросу, ©именипользователя, #хештегу или ссылке (как полной, так и сокращённой). Для сбора данных Twitter можно использовать NodeXL. Это плагин к MS Excel, который собирает данные и строит на их основе графы для анализа социальных сетей. Базовая версия NodeXL распространяется свободно и ис- пользует Search API Twitter. Существует версия NodeXL Pro, позволяющая собирать данные из Twitter, Facebook, YouTube, Flickr и ряда вики-сайтов, распространяемая па коммерческой основе. Ссылки Social media data collection tools {socialmediadata.wikidot.com) - боль-
224 ❖ Использование Twitter API шая подборка ссылок на инструменты для сбора данных из социаль- ных сетей, в основном из Twitter и Facebook, и на провайдеров данных. • Quora. Which tools are available to collect social network data? {www.quora.com/Which-tools-are-availahle-to-collect-social-ne^ork- data) - опять в основном про Twitter и Facebook. • Matthew A. Russell. Mining the Social Web, 2nd Edition, O’Reilly Media, 2014, 421 p. - очень полезная книга, в первую очередь для тех, кто ис- пользует Python. Посвящена сбору данных при помощи API Twitter, Facebook, LinkedIn и Google+.
Глава 20 Регулярные выражения Регулярные выражения представляют собой шаблоны для поиска строк за- данной структуры. Их удобно использовать при обработке уже собранных данных: для извлечения почтовых адресов, электронной почты, телефонов и т. п. Символы и метасимволы Регулярное выражение (regular expression, или, сокращенно, regex) - это шаб- лон (образец), который описывает некоторое множество строк общей структуры. Как же выглядит этот шаблон? Большинство букв и символов в регулярных выражениях соответствует сами себе. Предположим, нам необходимо выяснить, в какой строке текста (элементе вектора s) содержится слово text: s <- с("а letter", "a word", "first paragraph", "some text") grep("text", s) ## [1] 4 Здесь шаблоном для поиска является "text". Вектор s задаёт множество строк, на котором проводится поиск. Функция grep возвращает номер стро- ки, соответствующей данному шаблону17. Как выглядит строка, в которой содержится шаблон text? grep("text", s, value = TRUE) ## [1] "some text" # Или так: s[grep("text", s)] ## [1] "some text" lz grep расшифровывается как Globally search a Regular Expression and Print. Функция grep в R аналогична одноимённой стандартной команде Unix (появилась в 1974 г., автор - Кен Томп- сон).
226 Регулярные выражения Результаты поиска зависят от регистра символов: строки text и Text раз- личаются. Но это различие можно и проигнорировать: s <- с("а letter", "a word", "first paragraph", "some Text") grep("text", s) ## integer(O) grep("text", s, ignore.case = TRUE) ## [1] 4 В то же время некоторые символы - их называют метасимволы - имеют специальное значение и сами себе не соответствуют. Вот они: z */ +, \ $, {}, []z I, ()z \ Эти специальные символы используются для описания структуры строки, места образца в строке, числа повторений образца и т. п. Метасимволы нельзя путать с обычными символами, являющимися ча- стью текстовой строки. Например, точка ' .' на языке регулярных выражений представляет со- бой любой символ, а вовсе не только обычную точку. В результате шаблон а. возвращает строки вида аи_ещё_какой-то_символ: s <- с("а", "ab", "abc", "а.") grep("a.", s) ## [1] 2 3 4 Чтобы показать, что точка в шаблоне - обычная, нужно как-то отменить её «метасимвольиость». Для этого перед точкой (и любым другим метасимво- лом, который хотят «перевести» в обычные) ставят \ (бэкслэш). Бэкслэш на- зывается экранирующим символом. Символ, следующий за экранирующим, теряет свое специальное значение и становится обычным символом. grep("a\\.", s) ## [1] 4 Но отчего в шаблоне стоят сразу два бэкслэша? Дело в том, что бэкслэш - экранирующий символ не только в регуляр- ных выражениях, но и в строках R. Вспомните, как представляются двойные кавычки внутри строки, окружённой двойными же кавычками. Вот как: \". Л символ перевода строки? Вот он: \п. Поэтому в R для записи экранирую- щего символа регулярных выражений используется двойной бэкслэш ' \\'.
Квантификаторы 227 Правило использования экранирующего символа в R можно сформули- ровать так: если в регулярном выражении стоит бэкслеш (\), то в R нужно ставить два бэкслэша (\\). Фактически регулярные выражения представляют собой самостоятельный язык, который внутри каждого языка программирования реализуется немно- го по-своему. Особенностью реализации R является указание двух бэкслэшей \\ вместо одного \. Существует несколько групп регулярных выражений, каждая из которых имеет своё назначение. Рассмотрим их. Квантификаторы Квантификаторы ограничивают число вхождений образца в строку. • * - хотя бы 0 раз. Это означает, что образец может встречаться в строке любое число раз, а может и не встречаться в ней; • + - хотя бы 1 раз; • ? - встречается не больше одного раза (или не встречается, или встре- чается ровно один раз); • {п} - встречается ровно п раз; • {п, } - встречается п или более раз; • {п, т} - встречается от п до т раз (т > п). Пример: s <- с("а", "ab", "acb", "accb", "acccb", "accccb") # Символ 'с', предшествующий может повторяться любое число раз. grep("ac*b", s, value=T) # все, кроме 1-го ## [1] "ab" "acb" "accb" "acccb" "accccb" # Символ 'с' повторяется один или более раз. grep("ac+b", s, value=T) # с 3-го по 6-й # # [1] "acb" "accb" "acccb" "accccb" # Символ 'с' не встречается либо встречается один раз. grep("ac?b", s, value=T) # 2-й и 3-й элементы # # [1] "ab" "acb" # Символ 'с' встречается ровно два раза. grep("ac{2}b", s, value=T) # 4-й элемент ## [1] "accb"
228 Регулярные выражения # Символ 'с' встречается два и большее число раз. grep("ac{2,}b", s, value=T) # элементы с 4-го по 6-й # # [1] "accb" "acccb" "accccb" # Символ 'с' встречается два или три раза. grep("ac{2,3}b", s, value=T) # 4,5-й элементы # # [1] "accb" "acccb" Положение образца внутри строки Следующая группа регулярных выражений описывает положение образца внутри строки: • ~ - образец, следующий за данным символом, расположен в начале строки; • $ - в конце строки; • \Ь - в начале слова или строки; • \В - образец не находится в начале слова или строки (в языке регуляр- ных выражений довольно часто строчная буква означает утверждение, а прописная - отрицание этого утверждения). Напомним, что метасимволы \Ь и \В в строках R должны быть записаны как \\Ь и \\В соответственно. Пример: s <- c("abcd", "cdab", "cabd", "с abd") grep("ab", s, value=T) # возвращает все элементы # # [1] "abed" "cdab" "cabd" "c abd" grep("Aab", s, value=T) # 1-й элемент # # [1] "abed" grep("ab$", s, value=T) # 2-й элемент # # [1] "cdab" grep("\\bab", s, value=T) # 1-й и 4-й элементы # # [1] "abed" "с abd" grep("\\Bab", s, value=T) # 2-й и 3-й элементы ## [1] "cdab" "cabd"
Операторы 229 Операторы • . - любой единичный символ; • [...]- один символ из списка (порядок символов в списке не имеет значения); • любой символ, кроме тех, что содержатся в списке; • | - оператор ИЛИ: образец соотвествует одному пли другому образцу; • (...)- группировка. s <- c("*ab", "ab", "abc", "abd", "abe", "ab 12") grep("ab.", s, value=T) # 3-6-й элементы # # [1] "abc" "abd" "abe" "ab 12" grep("ab[c-e]", s, value=T) # 3-5-й элементы ## [1] "abc" "abd" "abe" grep("ab[Ac]", s , value=T) # 4-6-й элементы ## [1] "abd" "abe" "ab 12" grep("Aab", s, value=T) # все элементы, кроме 1-го ## [1] "ab" "abc" "abd" "abe" "ab 12" grep("Wab", s, value=T) # 1-й элемент ## [1] "Aab" grep("abc|d", s. value=T) # 3,4-й элементы ## [1] "abc" "abd" Группировка, как обычно, объединяет элементы в группу: # Соответствует 'ab' и любому из символов: fc', 'd' или 'е' grep("ab(c|d|е)", s, value=T) # 3-5-й элементы # # [1] "abc" "abd" "abe" # что равносильно: grep("abc|abd|abe", s, value=T) # # [1] "abc" "abd" "abe" Кроме того, группировка позволяет использовать часть регулярного вы- ражения внутри самого этого регулярного выражения. Групп может быть несколько, и к ним можно обращаться по номерам: WN. где N - номер группы. Например:
230 Регулярные выражения sub("(ab) 12", "\\1 34", s) # замена в 6-м элементе # # [1] "лаЬ" "ab" "abc" "abd" "abe" "ab 34" ИЛИ html <- с("<Ь>Текст</Ь>", "<1>Текст</1>") grep("<([a-z])>.html, value=T) # # [1] "<Ь>Текст</Ь>" "<1>Текст</1>" Символы [, ] помещаются в список [... ] первыми: gsub("[][e]", "<*>", "Regular]") # # [1] "R<*>gular<*>" # но не так: # gsub("[e] []", "<*>", "Regular]") Метасимволы теряют своё специальное значение внутри списков [...]: s <- с("а", "$а", "ab", "$ab", "b.b", "$b", "ла") grep("[a$]", s, value=T) # '$' здесь - обычный символ # # [1] "а" "$а" "ab" "$ab" "$Ь" "*a" дгер("[$л]", s, value=T) # Не перепутайте порядок следования символов! # # [1] "$а" "$ab" "$Ь" "ла" grep("[.*]", s, value=T) # # [1] "b.b" «Жадность» и «лень» квантификаторов Пусть задана строка: s <- "<em>Hello, world!</em>" Какой текст верпёт регулярное выражение <. +>? Ответ легко получить непосредственно: regexpr("<.+>", s)
«Жадность» и «лень» квантификаторов ❖ 231 ## [1] 1 ## attr (/'match, length") ## [1] 22 ## attr(,"useBytes") # # [1] TRUE Функция regexp г возвращает всю строку, начинающуюся с первого сим- вола - крайнего слева <, и заканчивающуюся крайним справа >. Длина этой строки составляет 22 символа. Но как быть, если нужен ет - то, что содер- жится между ближайшими друг к другу символами < и >? Можно ли настро- ить поведение квантификатора + так, чтобы он захватывал строку покороче? Поведение квантификаторов по умолчанию является «жадным». «Жад- ный» квантификатор стремится захватить максимально длинную строку, со- ответствующую шаблону. Напротив, «ленивый» квантификатор захватыва- ет самую короткую строку из удовлетворяющих шаблону. Вот пример «ленивого» квантификатора: regexpr("<.♦?>", s) # # [1] 1 ## attr(,"match.length") # # [1] 4 ## attr(,"useBytes") # # [1] TRUE Всё? Нет. regexpr возвращает только первое вхождение шаблона и пото- му пропускает </ет>. Нам нужен глобальный regexpr - gregexpr, возвра- щающий все вхождения шаблона: gregexpr("<.+?>", s) # # [[1]] # # [1] 1 18 ## attr(,"match.length") # # [1] 4 5 ## attr(,"useBytes") # # [1] TRUE Теперь всё в порядке. «Ленивое» поведение определяется знаком ?, располагающимся после кван- тификатора: • *? «ленивый» эквивалент *; • +? «ленивый» эквивалент +; • {п, }? «ленивый» эквивалент {п, }.
232 Регулярные выражения Классы символов Регулярные выражения позволяют выделять классы символов: буквы, циф- ры, пробелы и т. и. Есть два способа это сделать: 1) стандарт POSIX; 2) \... - Unix. Первый способ даёт более читабельные выражения, второй - более корот- кие. Оба способа можно использовать одновременно. • [: digit: ], \d - цифра от 0 до 9. Эквивалент: [0-9]. • \D - не цифра. Эквивалент: [ ~ 0 - 9 ]. • [: lower: ] - строчная буква. Эквивалент: [a-z]. • [: upper: ] - прописная буква. Эквивалент: [A-Z]. • [:alpha: ] - любая буква. Эквивалент: [A-Za-z]. • [: alnum: ] - алфавитно-цифровой символ (цифра или буква). Эквива- лент: [[:alpha:][:digit:]], [0-9A-Za-z]. • [: space: ], \s - пробел (пробел, табуляция, вертикальная табуляция, новая строка, возврат каретки). • \S - любой символ, кроме пробела. • [: punct: ] - символ пунктуации. • [: print: ] - любой символ, отображаемый на экране. Следует отметить, что конструкции вида могут встречаться толь- ко внутри списков. Поэтому, строго говоря, точным эквивалентом для [ 0 - 9 ] будет [ [: digit: ] ]. Или \d. Примеры: s <- c("12ab", "ad", "34", "\п") grep("[[:digit:]]", s, value=T) # # [1] "12ab" "34" grep("\\d", s, value=T) # # [1] "12ab" "34" grep("[[:alpha:]]", s, value=T) # # [1] "12ab" "ad" grep("[[:alnum:]]", s, value=T) ## [1] "12ab" "ad" "34"
Заключительные замечания 233 grep("[[:alpha:][idigit:]]", s, value=T) # # [1] "12ab" "ad" "34" grep("[[:puncts, value=T) # # [1] grep("\\s", s, value=T) # # [1] "\n" grep("\\S", s, value=T) # # [1] "12ab" "ad" "34" Заключительные замечания До сих пор мы использовали функции, реализованные в пакете base, входя- щем в базовую комплектацию R. Перечень этих функций, а также решаемые с их помощью задачи приведены в табл. 20.1. Среди других пакетов, работающих с регулярными выражениями, отме- тим пакет st ring г, с некоторыми функциями которого мы познакомимся в следующей главе. Краткая сводка регулярных выражений дана в приложении Г. Таблица 20.1 ❖ Задачи, решаемые с помощью регулярных выражений Задача Функция Обнаружить номер строки по образцу Извлечь строку, соответствующую образ- цу Соответствует ли строка образцу? Найти расположение подстроки, соответ- ствующей образцу Заменить строку Разделить строку на части по заданному образцу grep(..., value = FALSE) grep(..., value = TRUE) grepl regexpr, gregexpr sub, gsub strsplit
234 ❖ Регулярные выражения Ссылки Компактный учебник с большим числом примеров: • Форта Б. Освой самостоятельно регулярные выражения. 10 минут на урок. М.: Вильямс, 2005. 184 с. Для глубокого погружения в мир регулярных выражений: • Фридл Дж. Регулярные выражения. СПб.: Символ-Плюс, 2008. 608 с. Онлайн-сервисы для проверки составленных регулярных выражений: • regexr.com. • rubular.com. Сервис txt2re.com помогает не только составить регулярное выражение, но и записать его на Perl, РНР, Python и ещё десятке других языков программи- рования.
Глава Создание карт на основе собранных данных Возможность быстро отобразить полученные сведения на карте позволяет выявить ошибки и провести предварительный анализ данных (где они рас- пределены неравномерно? из какого региона данных не хватает? где образу- ются скопления? и т. п.). В этой главе мы рассмотрим два примера. Первый охватывает основные этапы сбора и предварительной обработки пространственных данных: извле- чение из веб-страницы строк адресов объектов, сопоставление этим адресам географических координат и, наконец, отображение объектов с полученны- ми координатами на карте. Второй пример посвящён работе с векторными данными, хранящимися в формате SHP, - так называемыми шейп-файлами. Построенные в обоих случаях карты будут интерактивными, то есть вы сможете управлять отображением данных (масштабом, границами, слоями и т. п.), работая непосредственно с картой, а не с подготовившей её програм- мой. Интерактивные карты в leaflet Для создания интерактивных веб-карт широко используется JavaScript-биб- лиотека Leaflet, разработчиком которой является Владимир Лгафонкин. Па- кет leaflet представляет собой интерфейс к этой библиотеке. Рассмотрим, как с его помощью создавать интерактивные карты. Для инсталляции пакета, как обычно, введём: install.packages("leaflet") Загрузим пакет: library(leaflet) Использование пакета начинается с того, что создается виджет (элемент графического интерфейса) «карта» с помощью функции leaflet. Затем на
23Б Создание карт на основе собранных данных карту добавляются слои данных, такие как листы карты (с помощью функ- ции addTiles) или маркеры объектов (addMarkers) и т. п. leaflet поддерживает оператор последовательного выполнения функций %>% из пакета magrittr. На карте, показанной на рис. 21.1, отмечено расположение статистическо- го факультета Оклендского университета (Новая Зеландия) - места, где в 1993 году появился на свет язык R. m <- leaflet() %>% addTiles() %>% # добавим листы карт OpenStreetMap addMarkers(lng=174.768, lat=-36.852, popup="The birthplace of R") m # Выведем карту на экран Рис. 21.1 ♦> Карта с отмеченным расположением статистического факультета Оклендского университета в Новой Зеландии - места, где R появился на свет Здесь параметрами Ing и lat заданы географические долгота (longitude) и широта (lattittide) объекта, popup содержит текст сообщения, которое всплы- вает при нажатии на маркер объекта. Заметим, что у вас получится интерактивная карта, тогда как мы здесь ограничимся статическими изображениями. Без использования %>% те же самые операции реализуются следующим об- разом:
Интерактивные карты в leaflet 237 m <- leaflet() m <- addTiles(m) m <- addMarkers(m, lng=174.768, lat=-36.852, popup="The birthplace of R") m В leaflet существует множество функций, позволяющих манипулиро- вать картами. Например: • setview устанавливает координаты центра карты и масштаб отображе- ния - зум (zoom). Чем зум больше, тем крупнее масштаб (рис. 21.2); • fit Bounds ограничивает отображение карты прямоугольной областью [lngl,latl] - [Ing2,lat2] (рис. 21.3). Подробную справку можно получить, набрав ?setview или ?fitBounds. m %>% setView(lng=174.768, lat=-36.852, zoom=10) Рис. 21.2 ❖ Карта из рис. 21.1, для которой задан зум: zoom=i0 m %>% fitBounds(174.767,-36.853,174.769,-36.851) По умолчанию функция addTiles использует карты OpenStrectMap. Вы- бор карт осуществляется функцией addProviderTiles. Если, например, нуж- но использовать карты Esri. WorldTopoMap, то сделать это можно так:
238 Создание карт на основе собранных данных Рис. 21.3 ❖ Карта из рис. 21.1 с заданными границами отображения: fitBounds(174.767,-36.853,174.769,-36.851) m %>% addProviderTiles("Esri.WorldTopoMap") Результат показан на рис. 21.4. Образцы поддерживаемых форматов карт (рис. 21.5) можно посмотреть на странице http://leaflet-extras.github.io/leaflet-providers/preview/index.html. Рис. 21.4 ❖ Карта из рис. 21.1 на подложке Esri.WorldTopoMap
Переходим к созданию карты 239 Рис. 21.5 ❖ Страница просмотра образцов форматов карт Leaflet-providers preview Переходим к созданию карты На этот раз мы будем извлекать из Сети не просто данные, но данные, имею- щие географическую привязку. В качестве примера рассмотрим следующую задачу: найти адреса магазинов, торгующих в Варшаве подержанными вело- сипедами, и нанести эти адреса на карту города. Создание карты на основе географических данных, извлеченных из Ин- тернета, состоит из трех этапов: 1) извлечение адресов объектов; 2) геокодироваиие\ сопоставление адресу каждого объекта его географиче- ских координат; 3) отображение на карте.
240 Создание карт на основе собранных данных Извлечение адресов и названий магазинов По идее, начать работу следует с поиска адресов магазинов. К счастью, боль- шая часть из них приведена на страницах электронного журнала Metro War- szawa. Но есть и плохая новость: данные слабо структурированы. Они распо- лагаются в тексте так, что не связаны ни с одним определённым элементом страницы и подобрать к ним единый путь по XPath или CSS-селекторам не удастся. Так что для извлечения данных придётся воспользоваться тексто- вым поиском. Извлечём содержимое страницы. library(rvest) # для извлечения данных url <- "http://metrowarszawa.gazeta.pl/metrowarszawa/ 1,141635,17512272.html" web_page <- read html(url) Выделим из веб-страницы web_page текст, а затем в нем - подстроки, со- ответствующие шаблону адреса. Но сперва подключим пакет stringr для операций со строками library(stringr) # для текстового поиска в строке Адрес начинается с ul., например: ul. Czarnomorska 13 Шаблон адреса выглядит так: ul. + ПРОБЕЛ + Прописная буква + один или более каких-то символов + ПРОБЕЛ ♦ одна или более цифр и записывается следующим образом: ul.\\s[A-Z].+?\\s[0-9]+ Напомним, что вопросительный знак ? ограничивает «жадность» операто- ров .+. Поясним, что означает «жадность» в нашем примере. Рассмотрим строку some text ul. Czarnomorska 13 some text 15 some text Шаблону поиска соответствуют следующие подстроки: ul. Czarnomorska 13 ul. Czarnomorska 13 some text 15 Какая из них будет выдана в результате поиска? По умолчанию поведение шаблона . + - «жадное»: из всех строк, соответ- ствующих ему, шаблон «захватывает» самую длинную, которую только мож- но захватить, то есть
Извлечение адресов и названий магазинов 241 ul. Czarnomorska 13 some text 15 Но нас это не устраивает. Нам, наоборот, нужна самая короткая подстрока. Чтобы указать, что поведение .+ должно быть «нежадным», к ним и добав- ляется ?. Итак, выделим текст, а из него нужные подстроки web_page %>% html text() %>% str extractall(pattern="ul.\\s[A-Z].+?\\s[0-9]+") [[1]] [1] "ul. Czarnomorska 13" "ul. Lektykarska 26" "ul. Dobra 12" "ul. Sielecka 2" "ul. Smolna 11" "ul. Zamoyskiego 26" [7] "ul. Stawki 19" "ul. Wiktorska 89" Результатом будет список: str(web_page %>% html_text() %>% str_extract_all(pattern = "ul.\\s[A-Z].+?\\s[0-9]*")) List of 1 $ : chr [1:8] "ul. Czarnomorska 13" "ul. Lektykarska 26" "ul. Dobra 12" "ul. Sielecka 2" ... Все адреса представляют собой строки, так что для их хранения список не нужен, а достаточно символьного вектора. Поэтому добавим функцию пре- образования списка в вектор - unlist: address <- web_page %>% html_text() %>% str_extract_all(pattern = "ul.\\s[A-Z].+?\\s[0-9]+") %>% unlist() address [1] "ul. Czarnomorska 13" "ul. Lektykarska 26" "ul. Dobra 12" "ul. Sielecka 2" "ul. Smolna 11" "ul. Zamoyskiego 26" [7] "ul. Stawki 19" "ul. Wiktorska 89" str(address) chr [1:8] "ul. Czarnomorska 13" "ul. Lektykarska 26" "ul. Dobra 12" "ul. Sielecka 2" "ul. Smolna 11" "ul. Zamoyskiego 26" ... В адресах нужно указать, что все они относятся к Варшаве: address <- pasteG(address, ", Warszawa") address [1] "ul. Czarnomorska 13, Warszawa" "ul. "ul. Dobra 12, Warszawa" "ul. [5] "ul. Smolna 11, Warszawa" "ul. "ul. Stawki 19, Warszawa" "ul. Lektykarska 26, Warszawa" Sielecka 2, Warszawa" Zamoyskiego 26, Warszawa" Wiktorska 89, Warszawa" Названия магазинов выделены жирным шрифтом, то есть находятся внут- ри HTML-элементов Ь. Проблема в том, что так выделяются не только они: tmp <- web_page %>% html nodes("b") %>% html text() [1] "Spodobalo ci si^? Polub nas" [4] "rower trekkingowy" [7] "Bikerstudio - Kolorowe ostre kolo" [10] "Ostre Kolo - Stwdrz swdj rower" [13] "Milou - rowerowa perelka z Mokotowa * "rower miejski" "rower gdrski" "Rowery Bajery - Szosowi potentaci" "Dwa Osiem - Sklep, serwis, kawa"
242 Создание карт на основе собранных данных Названия магазинов занимают в этом списке позиции с 6-й но 13-ю. Со- ответствующие строки содержат дефис, перед которым находится название. Используем это для выделения названий магазинов: tmp %>% str match("(.+)\\s-") [,1] [,2] [1,] NA NA [2,] NA NA [3,] NA NA [4,] NA NA [5,] NA NA [6,] "Propaganda Rowerowa "Propaganda Rowerowa [7,] "Bikerstudio •" "Bikerstudio" [8,] "Rowery Bajery -" "Rowery Bajery" [9,] "JR Concept -" "JR Concept" [10,] "Ostre Kolo -" "Ostre Kolo" [11,] "Dwa Osiem -" "Dwa Osiem" [12,] "Komis rowerowy -" "Komis rowerowy" [13,] "Milou -" "Milou" [14,] NA NA s t r_match () возвращает не только всю подстроку, удовлетворяющую шаб- лону, но и её часть, помещенную в скобки, - а это как раз то, что нам нужно. Таким образом, название магазина размещается во 2-й колонке. . Last. value означает результат работы последней команды, в нашем слу- чае - список строк, выделенных жирным шрифтом. tmp <- .Last.value %>% .[,2] tmp [1] NA NA NA NA NA "Propaganda ..." [7] "Bikerstudio" "Rowery Bajery" "JR Concept" "Ostre Kolo" "Dwa Osiem" "Komis rowerowy" [13] "Milou" NA Выделим все элементы tmp, кроме тех, что Not Available, и сохраним ре- зультат в name: tmp[’is.na(tmp)] [1] "Propaganda Rowerowa" "Bikerstudio" "Rowery Bajery" "JR Concept" "Ostre Kolo" "Dwa ..." [7] "Komis rowerowy" "Milou" name <- .Last.value Геокодирование Объединим адреса и названия магазинов в таблицу. Кроме того, поместим в эту таблицу координаты магазина - долготу и широту. Их находят при по- мощи функции geocode пакета ggmap. Сам пакет предназначен для отобра- жения объектов на Google Maps и OpenStreetMaps. Загрузим его
Отображение на карте 243 library(ggmap) и создадим таблицу: locations <- data.frame(address = address, # адрес магазина geocode(address), # долгота и широта магазина stores = name, # название stringsAsFactors = F) # сохранить символьный формат Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address= ul.+Czarnomorska+13,+Warszawa&sensor=false Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address= ul.+Lektykarska+26,+Warszawa&sensor=false Information from URL : http://maps.googleapis.com/maps/api/geocode/j son?address= ul.+Dobra+12,+Warszawa&sensor=false Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address= ul.+Sielecka+2,+Warszawa&sensor=false Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address= ul.+Smolna+ll,+Warszawa&sensor=false Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address= ul.+Zamoyskiego+26,+Warszawa&sensor=false Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address= ul.+Stawki+19,+Warszawa&sensor=false Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address= ul.+Wiktorska+89,+Warszawa&sensor=false locations address 1 ul. Czarnomorska 13, Warszawa 2 ul. Lektykarska 26, Warszawa 3 ul. Dobra 12, Warszawa 4 ul. Sielecka 2, Warszawa 5 ul. Smolna 11, Warszawa 6 ul. Zamoyskiego 26, Warszawa 7 ul. Stawki 19, Warszawa 8 ul. Wiktorska 89, Warszawa Ion lat stores 21.04576 52.17992 Propaganda Rowerowa 20.96751 52.28094 Bikerstudio 21.03030 52.23706 Rowery Bajery 21.03958 52.20277 JR Concept 21.02406 52.23293 Ostre Kolo 21.04969 52.24737 Dwa Osiem 20.98465 52.25060 Komis rowerowy 21.00736 52.19797 Milou Итак: координаты магазинов получены, данные сведены в таблицу. Пора приступать к завершающему этапу работы. Отображение на карте Этим, как мы уже знаем, занимается leaflet: library(leaflet) Сформируем текст пояснений, которые будут всплывать на карте при на- жатии на маркер объекта: store_popup <- pasteG("<b>", locationsSstores, "</b>", *<br>", locationsSaddress)
244 Создание карт на основе собранных данных В первой строке жирным шрифтом пишется название магазина, во второй - его адрес. Построим карту (рис. 21.6). Рис. 21.6 ❖ Карта Варшавы с нанесенными адресами магазинов, торгующих велосипедами m <- leaflet(data = locations) %>% addProviderTiles("Esri.WorldTopoMap") %>% addMarkers(locations$lon, locations$lat, popup = store_popup) m Функция leaflet создаёт объект-карту, addProviderTiles добавляет на неё листы карты от провайдера Esri .WorldTopoMap, наконец, addMarkers добавляет маркеры объектов с заданными долготой и широтой. Работа с шейп-файлами Рассмотрим создание карты по данным о вспышке холеры, произошедшей в 1854 г. на Брод-стрит в лондонском районе Сохо. Эти данные, собранные британским врачом Джоном Сноу, и построенная на их основе карта являют- ся одним из первых примеров использования геоинформационных техноло- гий.
Работа с шейп-файлами 245 Карту можно построить с помощью геоинформационных систем (ГИС), например коммерческой ArcGIS или свободной QGis, или любого пакета, «понимающего» формат шейп-файлов, в частности Matlab и R (рис. 21.7). Рис. 21.7 ❖ Данные Джона Сноу на карте OpenStreetMap. Красными кружками обозначены случаи заболевания холерой. Радиус кружка пропорционален числу зарегистрированных смертей. Синими кружками отмечены колонки Случаи холеры, соседствующие на карте с модными магазинами и галерея- ми современного Сохо, смотрятся несколько сюрреалистично. Поэтому ис- пользуем более нейтральную карту, благо Leaflet поддерживает более двух десятков их видов. Мы возьмём карту Hydda. Full (рис. 21.8). # Автоматическая установка пакетов из списка list.of.packages <- с("maptools", "rgdal", "leaflet") new.packages <- list.of.packagesf!(list.of.packages %in% installed.packages()[,"Package"])] if(length(new.packages)) install.packages(new.packages) suppressMessages(library('maptools')) # readShapePoints suppressMessages(library('rgdal')) # spTransform library('leaflet') # leaflet setwd('snow/') # папка для хранения shp-файлов deaths <- readShapePoints("Cholera Deaths") pumps <- readShapePoints("Pumps") snow_coords = function(obj) {
24Б Создание карт на основе собранных данных obj_coords <- data.frame(obj@coords) coordinates(obj_coords) <- -coords.xl+coords.х2 proj4string(obj_coords) <- CRS("+init=epsg:27700") obj_coords = spTransform(obj_coords,CRS("+proj=longlat +datum=WGS84")) df_coords = data.frame(obj_coords@coords) coords = data.frame(lon=df_coords$coords.xl, lat=df_coords$coords.x2) return(coords) } dec <- snow coords(deaths) puc <- snow_coords(pumps) op <- .8 m <- leaflet() %>% addProviderTiles("Hydda.Full") %>% addCircles(dec$lon, dec$lat, radius = deathsSCount, opacity = op, col = "red") %>% addCircles(puc$lon, pucSlat, radius = 3., opacity = op, col = "blue") m Рис. 21.8 ❖ Данные Джона Сноу на карте Hydda.Full Оригинальная карта Джона Сноу приведена на рис. 21.9.
Ссылки 247 Рис. 21.9 ❖ Оригинальная карта Джона Сноу Ссылки • Leaflet for R (https://rstudiogithub.io/leaflet/) - документация по паке- ту leaflet па сайте компании RStudio. Обратите внимание па возмож- ности интеграции Leaflet с Shiny, что позволяет создавать интерактив- ные веб-приложения, снабжённые картами. • Не забываем Cran Task View: в Analysis of Spatial Data много внимания уделено пакетам для сбора и обработки пространственных данных, а Analysis of Ecological and Environmental Data посвящён работе с данны- ми об окружающей среде (экологии, гидрологии, океанографии, клима- тологии и т. п.). Комбинация пакетов, имеющихся в R, позволяет использовать его в качест- ве геоинформационной системы: • Spatial data in R: Using R as a GIS (http://pakillo.github.io/R-GIS-tuto)ial/) - обзор ГИС-пакетов R от Francisco Rodriguez-Sanchez; • Lovelace R., Cheshire J. Introduction to visualising spatial data in R. Более подробно о методах геостатистики и их реализации в R можно узнать из: • Геостатистический анализ данных в экологии и природопользовании (с применением пакета R) / А. А. Савельев, С. С. Мухарамова, А. Г. Пи- люгин, Н. А. Чижикова. Казань: Казанский университет, 2012. 120 с.
248 ❖ Создание карт на основе собранных данных {gis-lab.info/docs/saveliev2012 -geostat.pdf) • Bivand R.S., Pebesma Е., G mez-Rubio V. Applied Spatial Data Analysis with R [2 ed.]. New York, Springer-Verlag, 2013. 405 p.
Ссылки к части II • Munzert 5., Rubba Ch., Mei пег P., Nyhuis D. Automated Data Collection with R A Practical Guide to Web Scraping and Text Mining. John Wiley & Sons, 2015. 452 p. Описана большая часть технологий, которые нужно знать для сбора дан- ных. В частности, описание формата JSON и работы с базами данных из R. Нет лишь самых свежих пакетов по работе с API социальных сетей. Впро- чем, для поиска наиболее свежей информации лучше использовать не книги, а Stackoverflow.com. • Nolan D., LangD. T. XML and Web Technologies for Data Sciences with R. Springer, 2014. 663 p. Подробно изложены теоретические основы обработки XML и HTML (па- кет XML), протокол HTTP (RCurl), аутентификация по OAuth (ROAuth). С точки зрения сбора данных, наиболее полезны две первые части книги. • Danneman N.t Heimann R. Social Media Mining with R. Packt Publishing, 2014. 107 p. Извлечение данных из Twitter и разбор примеров, в частности, простей- шей классификации и анализа тональности текста. • Ravindran Sh. К., Garg V. Mastering Social Media Mining with R. Packt Publishing, 2015. 225 p. По сравнению с предыдущей книгой, авторы больше сосредоточились на сборе данных. Кроме Twitter, рассматриваются также Facebook, Instagram, GitHub и другие социальные медиа.
Приложение Среда разработки RStudio Окно среды разработки RStudio (рис. АЛ) делится на две части по верти- кали. В левой половине код скриптов редактируется и отправляется на вы- полнение, а в правой отображаются результаты, рабочее пространство R и различная вспомогательная информация. Рис. А.1 ♦♦♦ Окно среды разработки RStudio Если у вас есть опыт работы с RGui, то все приобретенные при этом навы- ки пригодятся и в RStudio. Последний, однако, обладает гораздо большими возможностями, что влияет на стиль работы: вместо непосредственного вы-
Создание скрипта ❖ 251 зова функций R чаще используются команды меню18. В частности, вместо функций управления рабочим пространством R удобнее использовать окно Environment (рис. АД, вверху справа), вместо функций установки пакетов - меню Tools и т. д. Создание скрипта Новый скрипт создаётся с помощью меню File/New File или кнопки New на панели инструментов (рис. Л.2). Таким образом можно создавать также до- кументы Markdown, простые текстовые файлы, файлы исходного кода на языке C++ и ряд других файлов, работу с которыми поддерживает редак- тор кода RStudio. При этом обеспечиваются подсветка синтаксиса языка и соответствующее изменение меню редактора. R Script intro.Rmd R Markdown... Create a new R j Text File Ш С** File R Sweave Qj RHTML +r R Presentation R Documentation Рис. A.2 ❖ Создание скрипта с помощью кнопки New на панели инструментов На рис. Л.З показан внешний вид меню редактора кода при работе со скрип- том R (рис. Л.За) и с документом Markdown (рис. А.Зб). Создадим простейший скрипт и рассмотрим на его примере, чем отлича- ется работа в RStudio от работы в RGui. 18 Которые, как и в RGui, в конечном счёте реализованы при помощи функций R.
252 Среда разработки RStudio ©_ appA.rstudio.Rmd* x Qj Untitled2* x и □ Q О Source on Save X * Q *»Run *•» •* Source • © appA_rstudio.Rmd Untrtled2* х а) яП ? ’ .e KnrtPOF v ♦ Run Q Chunks* 6) Рис. A.3 ❖ Меню редактора кода при работе со скриптом (а) и с документом Markdown (б) Автодополнение имён объектов Если в RGui имя функции можно дополнить во время набора, нажав клави- шу Tab, то в RStudio возможности автодополнения гораздо шире. Редактор «угадывает» набираемую функцию (рис. А.4), причём показывает пользова- телю не только имя функции, но и её сигнатуру, а также краткую справку. Дополняются не только имена встроенных функций, но и любых других объ- ектов R, в частности переменных и пользовательских функций. Q Щ Source on Save X * _4Run Lj» Source * 1 x <- seq| Generate regular sequences, seq is a standard genenc with a default method, seq.int is a primitive which can be much faster but has a few restrictions. aeq_along and aeq_len are very fast primitives for two common cases. Press Fl for абатсг.а help Рис. A.4 ❖ Редактор кода и консоль «подсказывают» имена функций и переменных Выполнение Выделив нужные строки кода, отправим их на исполнение с помощью кноп- ки Run (рис. А.5).
Рабочее пространство 25В 0 [ Source on Save / - 1 fc seq pi,pi,.l 2 у sin x) 3 plot(x,y *un ^>4 Source ftun the current line or selection (Ctrl-Enter) Рис. A.5 ❖ Кнопка Run отправляет на исполнение выделенные строки кода При этом откроется графическое окно с построенным в нём графиком, а все выполненные команды будут отображены в консоли (рис. Л.6). Console R Markdown Markers _____________________________ > х <- seq(-pi,pi,.1) > у <- sin(x) > plot(x.y) Рис. A.6 ❖ Выполненные команды скрипта отображаются в консоли. В заголовке консоли указан текущий рабочий каталог Рабочее пространство Значения переменных, помещённых в рабочее пространство, отображаются в окне Environment (рис. Л.7). Надпись «Global Environment» в заголовке окна говорит о том, что в окне отображаются глобальные переменные. Здесь же могут отображаться локальные переменные функций, а также функции пакетов-расширений, загруженные в текущем сеансе работы. Environment History а П J* Q Import Dataset’ / ® List’ ф Global Environment’ values x num [1:63] -3.14 -3.04 -2.94 -2.84 -2.74 ... у num [1:63] -1.22e-16 -9.98e-02 -1.99e-01 -2.96e-. Рис. A.7 ❖ Значения переменных, помещённых в рабочее пространство, отображаются в окне Environment Так как в окне Environment отображается структура переменных, нет осо- бой необходимости использовать функции str и class. Кнопка с изображе- нием метлы позволяет очистить рабочее пространство, что также быстрее использования функции или меню Session/Clear Workspace....
254 Среда разработки RStudio Особенно удобно в RStudio просматривать содержимое таблиц. Создадим таблицу из значений координат: df <- data.frame(x=x,y=y) Значение таблиц помещается в отдельный раздел (Data) на вкладке Environment (рис. Л.8). Data ©df 63 obs. of 2 variables J x: num -3.14 -3.04 -2.94 -2.84 -2.74 ... y: num -1.22e-16 -9.98e-02 -1.99e-01 -2.96e-01 -3.89e-01 ... values x num [1:63] -3.14 -3.04 -2.94 -2.84 -2.74 ... у num [1:63] -1.22e-16 -9.98e-02 -1.99e-01 -2.96e-.. Рис. A.8 ❖ Отображение таблицы df в окне Environment Значок таблицы, расположенный справа в строке с именем переменной, позволяет открыть таблицу на вкладке редактора (рис. Л.9). При этом появ- ляется возможность сортировки, поиска элементов и фильтрации содержи- мого таблицы. х 1 -3.14159265 2 -3.04159265 3 -2.94159265 4 -2 84159265 5 -2.74159265 6 -2.64159265 7 -2.54159265 8 -2.44159265 9 -2.34159265 <£1 Т Fitter Q У -1.224606е-16 -9.983342е-02 -1.986693е-01 -2.9552О2е-О1 -3^94183е-01 -4.794255е-01 5 646425е-01 -6.442177е-01 -7.173561е-01 10 -2.24159265 -7333269е-01 Рис. А.9 ❖ Отображение таблицы df на вкладке редактора кода История команд Выполненные ранее команды можно использовать повторно, набрав в кон- соли Ctrl+Стрелка вверх (рис. Л. 10). Это же сочетание клавиш подходит для
История команд 255 быстрого поиска ранее вызванных функций. Например, для поиска функ- ций, в имени которых содержится plot, следует просто ввести plot и нажать Ctrl+Стрелка вверх. При этом работает и обычная для R возможность пере- мещаться по истории команд при помощи клавиш со стрелками. аррк <- угер^ «*рр._ , I I ies> chapters <- pasted pasteo("\\input{’ , files [-аррк], "}“)» collapse = "\n”) appendices <- paste( pasteO("\\input{", files[appx], ”}"), collapse = "\n" body <- paste(chapters, "Wappendi x”, "\\renewcommand{\'\thechapter}-(\\Asbuk {chapter}}", appendices, sep « "\n") index <- pasteO(header, body, footer) indexname <- paste(new, "gathering_dara.tex”. sep = "/”) writeLi res (index, indexname) source(’~/.active-rstudi o-document *) x <- seq( pi,pi,.1) 5 у <- sin(x) 1 ₽1“<x.,y) Рис. А.10 ❖ Список набранных ранее команд, показанный в консоли Ещё одним инструментом для работы с историей команд служит вкладка History (рис. Л.1, вверху справа). Команды в ней отображаются в порядке выполнения: последние выполненные находятся внизу списка. На вкладке History команды можно выделять и затем отправлять в кон- соль (кнопка То Console) или в редактор кода (То Source) (рис. Л.11). Если в редакторе при этом нет открытых документов, то будет создан новый доку- мент по имени Untitled с номером. Рис. A.11 ❖ Выбор команд на вкладке History
25Б Среда разработки RStudio Кроме этого, есть возможность очистить историю команд целиком или удалить из неё избранные команды. Сохранение файлов Файлы в RStudio при завершении работы с программой или аварийном оста- нове сохраняются автоматически. Даже если они не были сохранены обыч- ной командой сохранения (Q/7+5). Сохранить файл в заданной кодировке можно с помощью меню File/Save with Encoding... Кодировки файлов Возможность открыть файл в нужной кодировке даёт команда меню File/Reopen with Encoding.... В результате появится список вариантов коди- ровок (рис. Л. 12). Choose Encoding Рис. А.12 ❖ Выбор кодировок Особенно часто к такому «переоткрытию» файлов приходится прибегать, работая в Windows с файлами в кодировке UTF-8. Дело в том, что русская
Управление файлами в рабочем каталоге 257 кодировка в R под Windows по умолчанию устанавливается равной windows- 1251, отчего кодированные в UTF-8 файлы отображаются «кракозябрами». Чтобы задать кодировку файлов по умолчанию, воспользуемся меню Tools/Global Options..., раздел General, поле Default text encoding. Управление файлами в рабочем каталоге Вкладка Files, расположенная в нижнем правом окне среды RStudio, отобра- жает файлы, хранящиеся в рабочем каталоге пользователя (рис. Л. 13). Клик по имени файла открывает данный файл в редакторе. Здесь же вы можете создать, удалить, переименовать и проделать другие операции с файлами. Таким образом, вкладка Files представляет собой нечто вроде встроенного файлового менеджера. Files Plots Packages Help Viewer =□ Qj New Folder О Delete Rename J More* В Home ... Name Size Modified A В ♦j .gitignore 13 В May 14, 2015, 6:18 PM В .httr-oauth 36 KB Jul 31, 2016,9:00 AM □ ML) .RData 40 В Jul 14, 2016,6:41 PM s В .Rhistory 172 KB Sep 5, 2016, 2:21 PM В Lj Agot В LJ Any Video Converter □ AVERAGE RATES OF INCREASE OF POPULATION □ DURING THE LAST HALF OF THE NINETEENTH CENTURY.csv □ best_num_clust.R 263 В 13 KB Aug 28, 2016, 5:11 PM Apr 4, 2016, 5:04 PM В dustering.R 1KB Jun 19, 2016,12:39 PM Рис. А.13 ❖ Вкладка Files: список файлов рабочего каталога пользователя Управление пакетами Установка и обновление пакетов выполняются при помощи команд меню Tools или вкладки Packages (рис. Л. 14).
258 Среда разработки RStudio Files Plots Packages Help Viewer n П ОI Install ® Update @ Q Name Description Version System Library □ base64enc Tools for base64 encoding ОД-3 О П ВН Boost C++ Header Files 1.60.0-2 О в brtops Bitwise Operations 1.0-6 о □ boot Bootstrap Functions (Originally by Angelo Canty for S) 13-18 о □ caTools Tools: moving window statistics, GIF, Base64, ROC AUC, etc. 1Д7Д о □ chron Chronological Objects which can Handle Dates and Times 23-47 о □ class Functions for Classification 73-14 © в cluster ’’Finding Groups in Data”: Cluster Analysis Extended Rousseeuw et al. 2.0.4 о □ codetools Code Analysis Tools for R 02-14 о в colorspace Color Space Manipulation 12-6 о □ compiler The R Compiler Package 331 © в curl A Modern and Flexible Web Client for R 1.1 © Рис. A.14 ❖ Вкладка Packages: управление пакетами расширений R Поиск и замена Поиск и замена текста внутри файла выполняются с помощью меню Edit. Кроме того, можно воспользоваться кнопкой (с изображением лупы) на па- нели редактора кода (рис. Л. 15). Q Г Knit PDF . Q pm d/Replace fre* Al _4Run Q Chunks Replace A Q 1л selection [^] Match case Whole word J- Regex |V Wrap xw 169 Рис. A.15 ❖ Панель поиска и замены в редакторе кода Поиск в нескольких файлах осуществляется командой Edit/Find in Files...
Автоматическое создание функций 259 Автоматическое создание функций Редактор кода способен проанализировать выделенную часть кода и автома- тически преобразовать её в функцию (рис. Л. 16). Все «свободные» перемен- ные в выделенном фрагменте19 будут при этом преобразованы в аргументы функции. Q □ Source on Save Ч / ’ О h^Run 1 2 3 4 5 6 х - seqf-pi,pi,. 1 у <- sin(x) plot(x.у) Code Completion Tab Go To Help Fl <- data, frame x-x,y^ Go To Function Definition Extract Function Ctrl*Alt*X Extract Turn the cunent Ctrl-Alt*V selection into a Reflow Unction Ctrl* Shift*/ Comment/Uncomment Lines Ctrl*Shift*C Insert Roxygen Skeleton Ctrl* Shift* Alt* R Reindent Lines Reformat Code Ctrl*I Ctrl* Shift* A Show Diagnostics Рис. A.16 ❖ Автоматическое выделение функции из выбранного фрагмента кода Например, выделенный на рис. Л. 16 код plot(x,y) превратится в myplot <- function (х, у) { plot(x,y) } Имя новой функции указывается пользователем. 19 То есть объекты, на которые имеется ссылка, но которые не созданы внутри фрагмента.
260 Среда разработки RStudio Комментирование Быстрее всего закомментировать или раскомментировать фрагмент кода мож- но при помощи комбинации клавиш Ctrl+Shifts С. Кроме того, можно ис- пользовать меню Code или клавишу с изображением волшебной палочки в редакторе кода (рис. Л. 17). Рис. А.17 ❖ Комментирование выделенного фрагмента кода Переход к определению функции Функции, созданные пользователем, отображаются на вкладке Environment (рис. А. 18, раздел Function), а значок-свиток в правой части строки с именем функции позволяет посмотреть её код (рис. А. 19). Global Environment* Data Odf 63 obs. of 2 variables J values x num [1:63] -3.14 -3.04 -2.94 -2.84 -2.74 ... у num [1:63] -1.22e-16 -9.98e-02 -1.99e-01 -2.96e- Functions myplot function (x, y) fj Рис. A.18 ❖ Пользовательская функция myplot на вкладке Environment
Ссылки 261 Function: myplot ( GlobalEnv) (Read-only) 1’ function (x, y) { 2 plot(x.y) 3 } 4 Рис. A.19 ❖ Код функции myplot на вкладке редактора кода Клавиша F2 позволяет, выделив упоминание функции в коде скрипта, пе- рейти к её определению. Ссылки Подробную информацию по работе с RStudio IDE можно найти в разделе Support на официальном сайте компании RStudio (suppoit.rstudio.com).
Приложение Языки поисковых запросов Google и Яндекс Поисковые сервисы Google и Яндекс обладают развитыми языками поиско- вых запросов (ЯПЗ). Мы рассмотрим основные команды этих языков, кото- рые чаще всего используются на практике. Цитата "У лукоморья дуб зеленый" Поиск по сайту Google> site:rozetka.com.ua телефон Яндекс> телефон site:rozetka.com.ua Исключить слово телефон, но не мобильный> телефон - мобильный исключить «мобильный» и «радио»> телефон - (мобильный радио) Обязательное присутствие слова Запрос, содержащий букву пакет +г Поиск по домену Google> site:ua мобильный телефон Яндекс> мобильный телефон domain:ua 20 Указанная без знака +, буква г будет воспринята поисковым сервисохМ как стоп-слово и удалена из запроса.
Почему важно уметь пользоваться ЯПЗ 2БЗ Поиск в файлах заданного типа Google> мобильный телефон filetype:doc Яндекс> мобильный телефон mime:doc Почему важно уметь пользоваться ЯПЗ Сайтов, с которых требуется получить информацию, много. И у каждого из них свой интерфейс. Л поисковых сервисов мало. Поэтому проще извлекать информацию со страницы выдачи поисковика, чем каждый раз заново на- страивать свой скрипт для обработки очередного сайта. Поиск, реализованный внутри сайта, может иметь слишком мало возмож- ностей, или его просто может не быть. Сайт может «лежать» и не отвечать на ваши запросы, тогда как поисковый сервис с большой долей вероятности будет работать. Предотвращение перегрузок сервиса Вместе с тем чрезмерно активное использование поискового сервиса может препятствовать его работе. В этом случае поисковик будет защищаться. На- пример, Google будет возвращать ошибку 503 (Service Temporarily Unavai- lable), свидетельствующую о том, что к сервису сделано слишком большое число запросов. Поскольку вы не единственный, кто пользуется поисковиком, то старай- тесь избегать ненужных запросов, чтобы не перегружать сервис. Если проб- лема всё-таки возникла, сделайте паузу в выполнении скрипта с помощью Sys. sleep () или поставьте в промежутке между запросами функции, кото- рые будут обрабатывать результаты запросов. С точки зрения традицион- ного программирования, - это не очень хороший приём. Однако в данном случае мы таким образом одновременно и делаем паузу между запросами, и выполняем в это время полезную работу.
Приложение Введение в HTML и С55 Веб-страница Приступим к созданию веб-страницы. Возьмём простой текстовый редактор, например Блокнот из Windows, и наберём следующее: <!DOCTYPE HTML> <html> <head> <title>npo6a nepa</title> <meta charset="utf-8"> </head> <body> <И1>Добро пожаловать в HTML!</hl> <р>Это - моя первая web-страничка.</р> </body> </html> Сохраним результаты работы в файл first.html. Откроем этот файл в браузере и посмотрим, что у нас получилось: Добро пожаловать в HTML! Это - моя первая web-страничка. Только что с помощью языка HTML мы создали веб-страницу, представ- ляющую собой гипертекстовый документ, или, попросту, гипертекст. Аббревиатура HTML расшифровывается как Hypertext Markup Language - язык разметки гипертекста. Как следует из названия, предназначен этот язык для создания гипертекста. Что такое гипертекст? Это документ, перемещаться по которому можно не только последовательно (как в книге, листая страницу за страницей), но и «прыгая» в нужное место документа с помощью специальных ссылок.
Веб-странииа 2Б5 Гипертекстовый документ, по своей сути, является обычным текстом с до- бавлением специальных меток - тегов (tags), которые разбивают текст на элементы: заголовки, абзацы, таблицы и т. п. Тег - единица HTML-разметки. Напршмер, <html>, <head>, <body> и т. д. Теги, подобно скобкам, бывают открывающими и закрывающими. Обозначе- ние закрывающего тега начинается со знака '/'. Например, указанным вы- ше тегам соответствуют закрывающие: </html>, </head>, </body>. Элемент - более ёмкое понятие, чем тег. Элемент обычно состоит из па- ры тегов (открывающего и закрывающего) и расположенного между ними содержимого элемента. Например, элемент head состоит из двух тегов: от- крывающего <head> и закрывающего </head>, а его содержимым является заголовок документа HTML. Элемент может содержать простой текст или другие элементы HTML (в нашем случае элемент title). Таким образом, элемент HTML имеет вид: <имя_тега>Содержимое элемента[</имя_тега>] Большая часть тегов - парные, но есть и исключения. В частности, тег <! DOCTYPE>. Он указывает на тип документа (документ HTML). Любой ги- пертекстовый документ должен начинаться с элемента <! DOCTYPE>. Тег <html> указывает, что гипертекстовый документ начался, а соответ- ствующий ему закрывающий тег </html> означает, что гипертекстовый до- кумент завершён. Содержимое элемента html и есть гипертекстовый доку- мент. Элемент head определяет «шапку» документа. В неё входит, в частности, название документа (title), определяемое элементом title и отображаемое в браузере в строке заголовка. Сюда же могут входить элементы, содержащие информацию об авторе, кодировке и ключевых словах документа, а также о связях данного документа с другими гипертекстовыми документами. Элемент body определяет основную часть - «тело» - HTML-документа, то есть текст, заголовки, ссылки и т. п. Элемент hl - заголовок (heading) первого, то есть самого высокого уровня. Всего может быть до шести уровней заголовков. Элемент р содержит абзац (paragraph) текста. Таким образом, наша веб-страница состоит из: • указателя на тип документа: HTML; • названия документа: «Проба пера»; • заголовка 1-го уровня: «Добро пожаловать в HTML!»; • абзаца: «Это - моя первая web-страничка». Заметим, что прописные и строчные буквы в HTML не различаются. По- этому <HTML>, <html> и <HtMl> обозначает одно и то же.
266 Введение в HTML и CSS Элементом гипертекстового документа, который, собственно, и превраща- ет обычный текст в гипертекст, являются гиперссылки. Г иперссылки Гиперссылки - это ссылки на другие части данного документа или другие файлы, вовсе не обязательно гипертекстовые. Гиперссылки позволяют пе- рейти в то место документа, на которое они указывают. Вот как выглядит гиперссылка: <а href="page.html">nepenTM к новой странице</а> Гиперссылка задаётся с помощью элемента а и состоит из двух основных частей: 1) ссылки на связанный объект, в нашем примере - на файл page. html; 2) якоря (anchor) - слова, группы слов, рисунка или, вообще говоря, лю- бой области экрана - щёлкнув по которому, вы откроете связанный объект, то есть веб-страницу раде. html в окне браузера. Якорем здесь является текст «Перейти к новой странице». Путь к объекту, на который указывает ссылка, хранится в атрибуте href тега <а>. В нашем случае файл раде. html должен храниться в том же ката- логе, что и файл, из которого его вызывают. Но самое приятное в гиперссылках заключается в том, что они могут ука- зывать на любой ресурс Интернета. Таким образом, мы можем соединить в одном документе файлы, расположенные на разных компьютерах и в разных странах. Так, гиперссылка <а href="https://mail.ru/">Mail.Ru</a> указывает на главную страницу интернет-сервиса Mail.Ru. Путь к такому ре- сурсу называется URL (Uniform Resource Locator - "единый указатель ресур- са”)/ Ну а что, если нужно перейти в другой раздел того же самого документа? В первую очередь для этого необходимо сделать закладку - отметить место документа, в которое нужно попасть: <а пате="меЬраде">Веб-страница</а> Атрибут name задаёт имя закладки. В пределах одного документа имена закладок повторяться не должны. Поместим эту закладку в самом начале на- шего рассказа про HTML. Теперь в месте, из которого мы хотим перейти, поставим следующую ги- перссылку:
Шрифт 2Б7 <а href="#webpage">K разделу "Веб-страница"</а> И не забудьте про ' #'! Л сейчас перейдем к разделу ’’Веб-страница”21. Внешний вид созданной нами странички пока не впечатляет. Сейчас мы нм займёмся. Шрифт Пользуясь средствами HTML, можно задавать гарнитуру шрифта, его раз- мер, начертание и цвет. Для этого используется элемент font. Например, вот так: <font face = "Arial" size="2" color="red"> Шрифт, которым набран этот текст, имеет гарнитуру Arial, размер 2 и красный цвет. </font> Вот что из этого получится: Шрифт, которым набран этот текст, имеет гарнитуру Arial, размер 2 и красный цвет. Атрибут size задаёт размер шрифта. В HTML существует семиразряд- ная шкала размеров шрифтов - от минимального (1) до максимального (7). Конкретная величина шрифта каждого размера в пунктах (так называемый кегль) зависит от типа и настроек используемого браузера. Чаще всего базо- вым размером шрифта является 3. Значением атрибута size может быть увеличение или уменьшение разме- ра шрифта относительно базового размера: <р>Шрифт базового размера.</р> <font size="+2">Pa3Mep шрифта больше базового на 2.</font> Шрифт базового размера. Размер шрифта больше базового на 2. Шрифт можно сделать: 21 Гиперссылка сработает только в электронной версии этой книги.
268 Введение в HTML и CSS • курсивным - тегом <i>; • полужирным - <Ь>; • зачёркнутым - <strike>; • как у печатной машинки - <tt>. Л вот так можно сделать индексы: • верхнийивдекс - верхний<зир>индекс</зир>; • нижнийИ11декс - нижний<зиЬ>индекс</зиЬ>. Ивет Цвет шрифта определяется атрибутом color. Чтобы его задать, нужно пом- нить английские названия шрифтов. С другой стороны, нужный цвет можно составить самому. Как известно, каждый цвет представляет собой комбина- цию RGB-составляющих, то есть красного (Red), зелёного (Green) и синего (Blue) цветов. Цвет можно записать с помощью шестнадцатеричного числа следующим образом: #RRGGBB Представление шестнадцатеричных чисел (0,9,Л,..., F) начинается с пре- фикса #. Максимальная цифра (F) обозначает максимальное значение ярко- сти соответствующей составляющей цвета, минимальная цифра (0) - мини- мальное значение яркости. Например: #FFFFFF задаёт белый, #000000 - чёр- ный, #FF0000 - красный, #00FF00 - зелёный, #0000FF - синий цвет. Стиль Задавать шрифт и цвет для каждого абзаца неудобно и неэкономно. Гораздо лучше создать для всех абзацев единый стиль оформления. Эта идея офор- милась в виде CSS (Cascading Style Sheets) - каскадных таблиц стилей, слу- жащих для оформления элементов HTML. Каждому элементу HTML в CSS соответствует селектор — имя, присво- енное стилю. Например: Р { font-family: arial; color: navy Имя селектора p совпадает с именем HTML-элемента р. В фигурных скоб- ках указываются настройки стиля, в нашем случае тип шрифта (font-family) и его цвет (color).
Стиль ❖ 269 Добавим таблицу стилей в файл first. html и посмотрим, как будет вы- глядеть результат. <!DOCTYPE HTML> <html> <head> <title>npo6a nepa</title> <meta charset="utf-8"> <style type="text/css"> p { font -family: arial; color: navy } </style> </head> <body> <И1>Добро пожаловать в HTML!</hl> <р>Это - моя первая web-страничка.</р> </body> </html> Добро пожаловать в HTML! Это - моя первая web-страничка. Стиль (style) можно задавать не только с помощью специального элемен- та style, но и одноимённым атрибутом, который помещается в другие эле- менты HTML: <!DOCTYPE HTML> <html> <head> <title>npo6a nepa</title> cmeta charset="utf-8"> </head> <body> <И1>Добро пожаловать в HTML!</hl> <p style="font- family:arial; color:navy">3TO - моя первая web-страничка.</p> </body> </html>
270 Введение в HTML и CSS Выравнивание Надо что-то делать с текстом. Хочется видеть заголовок выровненным но центру страницы. Делается это с помощью атрибута align. Смысл значений left (принято по умолчанию), right и center очевиден, a align = "justify" означает выравнивание текста по ширине. В CSS за выравнивание текста отвечает стиль text-align с теми же зна- чениями, что и у align. Введём стиль заголовка: hl { text-align: center } и получим в результате: Добро пожаловать в HTML! Это - моя первая web-страничка. Рисунки Вставим в нашу страничку следующий код: <img src="heart.png" alt="i love you!"> Получим: Добро пожаловать в HTML! Это - моя первая web-страничка. Значением атрибута s г с является U RL рисунка. В данном случае рисунок находится в том же каталоге, что и файл с текстом веб-страницы. Другие атрибуты img: • width = Размер - ширина рисунка (в пикселях); • height = Размер - высота рисунка; • align = top | middle | bottom | left | right - выравнивание рисунка;
Списки 271 • border = Размер - ширина границы рисунка; • hspace = Размер - горизонтальный пробел; • vspace = Размер - вертикальный пробел. Заметьте, что рисунок прижат к левому краю страницы. Выровнять его по- другому можно с помощью атрибута align элемента р - так же, как и текст: <р align=center><img src="heart. png" alt="i love you’"x/p> Атрибут align элемента img задаёт выравнивание рисунка в пределах пря- моугольника со сторонами, определяемыми атрибутами width и height. Атрибут alt содержит текст, который отображается на странице в случае, когда браузер по каким-то причинам не может загрузить рисунок. Списки Маркированные Маркированный, или неупорядоченный список (unordcred list), создаётся с помощью тегов <ul>. Каждый элемент списка (list item) помещается в эле- мент li. Выглядит это следующим образом: <ul> <11>молоко;</И> <li>xne6;</li> <И>яблоки. </li> </ul> и даёт в результате: • молоко; • хлеб; • яблоки. Нумерованные Для задания упорядоченного списка (ordered list) используется тег <о1>: <о1> <11>Выбрать пункт меню "Вид".</11> <li>B появившемся меню, выбрать пункт "в виде HTML", в результате, текст страницы загружается в текстовый редактор "Блокнот".</li> <li>B случае ошибки, обратитесь к системному администратору.</li> </ol>
272 Введение в HTML и CSS На веб-странице это будет выглядеть примерно так: 1. Выбрать пункт меню ’’Вид”. 2. В появившемся меню, выбрать пункт ”В виде html”. В результате, текст страницы загружается в текстовый редактор ’’Блокнот”. 3. В случае ошибки, обратитесь к системному администратору. Вложенные Внутри списков, как маркированных, так и упорядоченных, можно созда- вать новые списки. Например, так: <ul> <11>Список первого уровня</11> <о1> <И>Список второго уровня</11> <ul> <1±>Список третьего уровня</И> </□!> </о1> </ul> что даст в итоге: • Список первого уровня 1. Список второго уровня - Список третьего уровня Таблицы В качестве примера создадим таблицу размера 2 х 2: <table style="color:#00abO0; width:10O%"> <tr> <td>(Строка!, Столбец!)</td> <td>(Строка!, Столбец2)</td> Столбец2)</td> </tr> <tr> <td>(CTpoKa2, <td>(CTpoKa2, </tr> </table> Столбец1)</!б> Столбец2)</!б> В браузере она будет выглядеть так:
Ссылки 27В (Строка 1, Столбец!) (Строка2, Столбец!) (Строка!, Столбец2) (Строка2, Столбец2) Для создания таблиц используются элементы: • table - создаёт таблицу; • t г - задает строки таблицы; • td - задаёт ячейки таблицы. Элемент table имеет массу полезных атрибутов, в частности border, ко- торый устанавливает толщину линий таблицы (в пикселах). По умолчанию: border="0". Из атрибутов ячеек можно выделить colspan и rowspan. Они определя- ют количество столбцов и, соответственно, строк, на которые простирается данная ячейка. Вот что имеется в виду: <table border=l> ctrxtd со15рап=3>0бщий 3aronoeoK</tdxtr> <tr> <td rowspan=2>HDD</td> <td>Western Digital</td><td align="right">85$</td> </tr> <tr> <td>Quantum</tdxtd align="right">110$</td> </tr> </table> Общий заголовок [Western Digital | 85$ HDD|Quantum [110$ Ссылки • W3Schools Online Web Tutorials (http://www.w3schools.com) - учебники no HTML, CSS, XML, JavaScript,... На английском. Огромное количест- во примеров. • webremeslo.ru - учебники по HTML и CSS для тех, кто начинает «с нуля». • WebReference.ru - справочники по HTML и CSS. • Робсон Э., Фримен Э. Изучаем HTML, XHTML и CSS. 2-е изд. СПб.: Питер, 2014. 720 с.
Приложение Регулярные выражения Таблица Г.1 ❖ Квантификаторы * хотя бы 0 раз + хотя бы 1 раз ? не больше 1 раза {п} ровно и раз {п,} хотя бы п раз {n,m} от и дот раз Таблица Г.2 ❖ «Жадное» и «ленивое» поведения квантификаторов «жадный» «ленивый» ★ ★ р {П,} {п,}? Таблица Г.З ❖ Положение образца внутри строки Л в начале строки $ в конце строки \Ь в начале слова или строки \В не в начале слова или строки Таблица Г.4 ❖ Классы символов [:digit:],\d \D [:lower:] [:upper:] [:alnum:] [: space:] \s \s [:punct: ] [:print:] цифры от 0 до 9: [ 0 - 9 ] не цифра: [Л0 - 9 ] строчные буквы: [a-z] прописные буквы: [A-Z] цифры и буквы: [ [: alpha: ] [:digit: ] ], [A-zQ-9] пробельные символы пробел не пробел символ пунктуации отображаемые на экране символы
❖ 275 Таблица Г.5 ❖ Операторы любой единичный символ [...] список символов, любой из которых может содержаться в строке [A...] инвертированный список оператор ИЛИ () группировка части образца
Предметный указатель \ (бэкслэш), 29, 226 ’ (кавычки одинарные), 28 ... (многоточие), 77 . (точка), 25 $ (знак доллара), 43 : (двоеточие), 33 [,] (квадратные скобки), 43 [[,]] (двойные квадратные скобки), 43 “ (кавычки двойные), 28 Б База данных (СУБД), 103 SQLite, 103 В Веб-скрапинг (web-scraping), 140 Вектор, 31-36,42 добавление элементов, 35 логический, 26 произведение скалярное, 34 символьный, 26 удаление элементов, 35 числовой, 26 Векторизация, 36, 56 ВКонтакте, социальная сеть, 204-214 ограничения API, 214 Г Геоинформатика, 12 Геоинформационная система, 12, 247 Геокодирование, 124 Граф, 155 DOT, 155 дружеских связей, 204 матрица инцидентности, 210 Графический устройство, 70 формат файла, 70 функция низкого уровня, 61 д Дата, 83 Date, 83 POSIXct, 86 POSIXlt, 86 Добыча данных из социальных сетей (social media mining), 12 Документация виньетка (vignette), 90 справочное руководство, 90 Ж Журналистика данных (data-driven journalism), 11 И Интеллектульный анализ данных (data mining), 11 К Каталог рабочий,94 создание, 102 удаление, 102 Комплексные числа, 27 Конкатенация,28 Куки (cookie), 116, 173 Л Лексический корпус, 220 Логическая индексация, 36 м Массив многомерный (array), 40 Матрица (matrix), 39, 42 вывод, 97 имена столбцов, 40
Предметный указатель 277 имена строк, 40 транспонирование, 40 Машинное обучение (machine learning), 11 О Облако слов (word cloud), 219 Открытые данные, 107 поиск, 107 форматы, 107 п Пакет, 87 использование отдельной функции, 88 Портал открытых данных РФ, 114 доступ по API, 126-129 Приведение типов данных, 31 Присваивание, операция, 24 Р Рабочее пространство (workspace), 20, 21 Регулярное выражение (regular expression), 225, 274 группировка, 229 квантификатор, 227 метасимвол, 226 экранирующий символ, 226 С Сессия, 116,172 Символ-разделитель строк (sep), 94 целой и дробной частей числа (dec), 96 Список (list), 42 выбор элементов, 43 добавление элементов, 44 преобразование в вектор, 45 удаление элементов, 45 т Таблица (data frame), 45 выбор элементов, 47 запись в файл, 95 объединение, 46 размеры, 47 фрагмент, 48 чтение из файла, 100 Терм-документная матрица, 220 У Условный оператор if, 58 ifclse, 58 switch, 58 Ф Файл CSV, 96 архив, 137 бинарный, 131 запись, 95-97 скачивание, 102, 130 удаление, 102 чтение, 97-101 Фактор (категориальные данные), 80 Функция, 72 анализ структуры, 73 анонимная, 72 глобальные переменные, 75 локальные переменные, 74 ц Цикл преждевременный выход, 57 с предусловием, 57 со счётчиком, 54 э Элемент HTML div, 144 span, 144
278 Предметный указатель идентификатор, 145 класс, 145 Я Яндекс переводчик, 179 язык поисковых запросов, 262 А ЛР1, 107 арр1у(), 50 С cat(), 94 class(), 26 CRAN (Comprehensive R Archive Network), 15 CSS, Cascading Style Sheets, Каскадные таблицы стилей, 143, 158 селектор, 149, 178 D data.frame(), 45 DBI, пакет, 103 DiagrammeR, пакет, 155 do.call(), 53 downloader, пакет, 131 E Excel, табличный процессор, 45, 95, 132 F Facebook, социальная сеть, 192-203, 249 G ggmap, пакет, 242 ggplot2, пакет, 60, 70, 79, 112, 135 Google Maps, 242 Maps Geocoding, 125 Spreadsheets, табличный процессор, 136 язык поисковых запросов, 262 googlesheets, пакет, 136, 137 grcp(), 225 н hcad(), 49 Hmisc, пакет, 138 HTML (Hypertext Markup Language, Язык разметки гипертекста), 143, 158, 264-273 гипертекст, 264 тег (tag), 265 элемент (element), 265 HTTP (HyperText Transfer Protocol, протокол передачи гипертекста), 115 заголовки, 116 код состояния, 118, 118, 119 методы, 116 передача параметров, 119 httr, пакет, 120-123, 125,159,173, 175 I igraph, пакет, 210, 211 Inf (бесконечность), 34 J JavaScript, язык программирования, 185 JSON, 192 JSON (JavaScript Object Notation), 133 jsonlite, пакет, 133-136 L lapply (), 51
Предметный указатель 279 lattice, пакет, 60, 70 leaflet, пакет, 235-247 length(), 36 lines(), 65, 68 load(), 102 м magrittr, пакет, 92, 142, 236 maptools, пакет, 245 MATLAB, язык программирования, 36 N NA (Not Available), 32 NaN (Not-a-Number), 59 О OAuth, авторизация, 120, 192,217 OpenStreetMaps, 237, 242 P par(), 65, 66 Perl, язык программирования, 132 phantomjs, пакет, 186-190 pipeR, пакет, 93 plot(), 61-69, 73 points(), 65, 68 print(), 55, 94 Python, язык программирования, 12,36, 175 R R Commander, среда разработки, 22 R.app, среда разработки, 15 RCurl, пакет, 120, 122-124, 159, 208, 249 RDocumentation.org, поиск документации по R, 101 read.table(), 100 Rfacebook, пакет, 198-203 rgdal, пакет, 245 RGui, среда разработки, 15-22 rjson, пакет, 133-136, 208 RJSONIO, пакет, 133-136 RSelenium, пакет, 177-184, 191 RStudio, среда разработки, 22, 250-261 rvest, пакет, 79, 159-176, 240 html_form(), 172 html_nodes(), 162 read_html(), 160 s sapplyO, 52 save(), 101 scan(), 97 Selenium Standalone Server, 177 seq(), 33 sp, пакет, 125 sqldf, пакет, 103 str(), 26 stringr, пакет, 233, 240 sub(), gsub(), 29 Sys. Date(), 83 Sys.sleepO, 210 system.time(), 55 Sys.time(), 83 T tail(), 49 tictoc, пакет, 56 twitteR, пакет, 215 Twitter, социальная сеть, 215-223, 249 Search API, 223 Streaming API, 215 u URI (Uniform Resource Identifier, ед11 н ый идентификатор ресурса), 115
280 ❖ Предметный указатель URL (Uniform Resource Locator, единый указатель ресурса), 115 US-ASCII, 123 w wbstats, пакет, 108-113 word c loud, пакет, 219 write.table(), 96 X XML, пакет, 159, 249 xml2, пакет, 159, 161 XPath (XML Path Language), 146, 158, 178