Парсинг данных в R

Нужные данные часто содержатся на сайтах в неудобной форме. Это только студенты по наивности думают, что .csv или .dat файлы как булки на деревьях растут.

Загружаем две основных библиотеки для парсинга:

library(XML)  # parsing XML files
library(RCurl)  # filling forms
## Loading required package: bitops
# on Ubuntu you need to install `libcurl4-gnutls-dev` before installing
# RCurl

Парсинг готовых html-таблиц

В википедии есть данные по тому, сколько медалей получила каждая страна в Олимпийских играх. Для начала нужно поглядеть на данные за 2010 год.

Мы их попытаемся вытащить автоматом. Потом, например, можно узнать как связано количество серебряных и золотых медалей.

Находим все таблицы и смотрим, что там нашлось:

page <- "http://en.wikipedia.org/wiki/2010_Winter_Olympics_medal_table"
tables <- readHTMLTable(page)
str(tables)
## List of 7
##  $ toc : NULL
##  $ NULL:'data.frame':    2 obs. of  1 variable:
##   ..$ 2010 Winter Olympics: Factor w/ 2 levels "Bid process\nVenues\nMascots\nConcerns and controversies\nTorch relay (route)\nOpening ceremony (flag bearers)\nMedal table (me"| __truncated__,..: 1 2
##  $ NULL:'data.frame':    27 obs. of  6 variables:
##   ..$ Rank  : Factor w/ 27 levels "1","10","11",..: 1 11 17 18 19 20 21 27 22 2 ...
##   ..$ Nation: Factor w/ 26 levels "0","1","5","86",..: 8 15 26 21 24 25 9 3 6 20 ...
##   ..$ Gold  : Factor w/ 11 levels "0","1","10","14",..: 4 3 11 11 9 9 8 5 7 7 ...
##   ..$ Silver: Factor w/ 12 levels "0","1","13","15",..: 10 3 4 11 9 1 5 7 9 2 ...
##   ..$ Bronze: Factor w/ 11 levels "0","1","11","13",..: 9 11 4 10 5 7 8 3 10 7 ...
##   ..$ Total : Factor w/ 15 levels "1","11","14",..: 8 10 11 7 3 15 2 NA 5 14 ...
##  $ NULL: NULL
##  $ NULL: NULL
##  $ NULL:'data.frame':    7 obs. of  2 variables:
##   ..$ V1: Factor w/ 5 levels "","Sports\nMedal tables\nNOCs\nMedalists\nSymbols",..: 4 1 2 1 3 1 5
##   ..$ V2: Factor w/ 2 levels "1896\n1900\n1904\n1908\n1912\n1920\n1924\n1928\n1932\n1936\n1948\n1952\n1956\n1960\n1964\n1968\n1972\n1976\n1980\n1984\n1988\n1"| __truncated__,..: NA NA NA NA 1 NA 2
##  $ NULL:'data.frame':    6 obs. of  2 variables:
##   ..$ V1: Factor w/ 4 levels "","Sports\nMedal tables\nNOCs\nMedalists\nSymbols",..: 1 2 1 3 1 4
##   ..$ V2: Factor w/ 2 levels "1896\n1900\n1904\n1908\n1912\n1920\n1924\n1928\n1932\n1936\n1948\n1952\n1956\n1960\n1964\n1968\n1972\n1976\n1980\n1984\n1988\n1"| __truncated__,..: NA NA NA 1 NA 2

Судя по описанию, понимаем, что наша таблица — третья из полученных:

df <- tables[[3]]
str(df)
## 'data.frame':    27 obs. of  6 variables:
##  $ Rank  : Factor w/ 27 levels "1","10","11",..: 1 11 17 18 19 20 21 27 22 2 ...
##  $ Nation: Factor w/ 26 levels "0","1","5","86",..: 8 15 26 21 24 25 9 3 6 20 ...
##  $ Gold  : Factor w/ 11 levels "0","1","10","14",..: 4 3 11 11 9 9 8 5 7 7 ...
##  $ Silver: Factor w/ 12 levels "0","1","13","15",..: 10 3 4 11 9 1 5 7 9 2 ...
##  $ Bronze: Factor w/ 11 levels "0","1","11","13",..: 9 11 4 10 5 7 8 3 10 7 ...
##  $ Total : Factor w/ 15 levels "1","11","14",..: 8 10 11 7 3 15 2 NA 5 14 ...

Чистка таблиц из-за объединённых ячеек

Часто бывает, что в таблице были объединенные ячейки и тогда данные в некоторых строках оказываются смещены. У нас меньше 30 наблюдений и можно почистить руками, но мы потренируемся, чтобы быть готовыми к таблице с миллионом строк.

df[5:9, ]
##            Rank             Nation Gold Silver Bronze Total
## 5             5  South Korea (KOR)    6      6      2    14
## 6             6  Switzerland (SUI)    6      0      3     9
## 7             7        China (CHN)    5      2      4    11
## 8  Sweden (SWE)                  5    2      4     11  <NA>
## 9             9      Austria (AUT)    4      6      6    16

Узнаем, какие строчки сдвинуты:

to.correct <- is.na(df$Total)
to.correct
##  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
## [12] FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE  TRUE
## [23] FALSE FALSE FALSE  TRUE  TRUE

R пытается автоматом присвоить переменным правильный тип (целое число, дробное число, факторная переменная). И для избежания ошибок создаёт разные запреты: например, если у факторной переменной было заявлено два значения “да” и “нет”, то присвоить ей что-то иное не получится.

Поскольку у нас возникла путаница, то мы сначала переведем все переменные в текстовые, чтобы R не ругался на якобы ошибочные действия:

df$Rank <- as.character(df$Rank)
df$Nation <- as.character(df$Nation)
df$Gold <- as.character(df$Gold)
df$Silver <- as.character(df$Silver)
df$Bronze <- as.character(df$Bronze)
df$Total <- as.character(df$Total)

Сдвигаем нужные строки:

df[to.correct, 2:6] <- df[to.correct, 1:5]

Указываем правильные типы данных:

df$Nation <- as.character(df$Nation)
df$Gold <- as.numeric(df$Gold)
df$Silver <- as.numeric(df$Silver)
df$Bronze <- as.numeric(df$Bronze)
df$Total <- as.numeric(df$Total)

Смотрим на почти идеальную таблицку. Останется только присвоить правильный ранг там, где сейчас в графе ранг стоит название страны:

df[5:9, ]
##            Rank             Nation Gold Silver Bronze Total
## 5             5  South Korea (KOR)    6      6      2    14
## 6             6  Switzerland (SUI)    6      0      3     9
## 7             7        China (CHN)    5      2      4    11
## 8  Sweden (SWE)       Sweden (SWE)    5      2      4    11
## 9             9      Austria (AUT)    4      6      6    16
summary(df)
##      Rank              Nation               Gold           Silver     
##  Length:27          Length:27          Min.   : 0.00   Min.   : 0.00  
##  Class :character   Class :character   1st Qu.: 0.50   1st Qu.: 1.00  
##  Mode  :character   Mode  :character   Median : 2.00   Median : 2.00  
##                                        Mean   : 6.37   Mean   : 6.44  
##                                        3rd Qu.: 5.50   3rd Qu.: 5.50  
##                                        Max.   :86.00   Max.   :87.00  
##      Bronze         Total      
##  Min.   : 0.0   Min.   :  1.0  
##  1st Qu.: 1.0   1st Qu.:  3.0  
##  Median : 3.0   Median :  6.0  
##  Mean   : 6.3   Mean   : 19.1  
##  3rd Qu.: 5.5   3rd Qu.: 14.5  
##  Max.   :85.0   Max.   :258.0

Кстати, Гугл-докс тоже умеет это делать! Создаёте пустую таблицу на docs.google.com. В верхней левой ячейке набираете:

=ImportHtml("http://en.wikipedia.org/wiki/2010_Winter_Olympics_medal_table","table",3)

И наслаждаетесь тем же результатом!

Работа с текстовыми переменными

При копании в текстовых данных полезны бывают фукнции

Конвертация текстовых данных в другие форматы

Текстовые данные могут быть:

Регулярные выражения

О мощи регулярного шаманства

Найти нужные данные в XML

Есть два стратегии искать данные в XML файле. Одна удобна для небольших деревьев, другая требует больше усилий, но позволяет работать с огромнейшими XML файлами.

Отправить форму

Работа с файлами

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

Получаем список файлов с расшерением .csv в текущей папке:

file.list <- dir("*.csv")
print(file.list)
## character(0)

А дальше их можно читать в цикле:


all.data <- NULL

for (f in file.list) {
    data <- read.csv(f)  # читаем очередной файл
    cat(paste("Файл ", f, "содержит", nrow(data), "строк."))
    all.data <- rbind(all.data, data)  # дописываем его в большую табличку
}

И на выходе мы получаем большой массив данных

str(all.data)
##  NULL

Где порыться?