Análise de Dados em R (FNDE) - Módulo 2
Allan Vieira
março de 2018
“The tidyverse is a set of packages that work with harmony because they share common data representtions and API design. The tidyverse package is designed to make it easy to install and load core packages from the tidyverse in a single command.” * Hadley Wickham *
Podemos definir o Tidyverse, então, como um meta-package que congrega uma coleção de diversos outros pacotes de R voltados para importação, exploração, manipulação e visualização de dados. Vão desde pacotes para manipulação de strings, expressões regulares, datas, passando por pacotes de leitura e importação, manipulação e visualização de dados, até a geração de relatórios e criação de páginas web, dentre outras coisas.
Existem quatro famílias principais de funções no pacote stringr:
# carregamento
library(stringr)
str_length("abc")
[1] 3
x <- c("abcdef", "ghifjk")
# The 3rd letter
str_sub(x, 3, 3)
[1] "c" "i"
# The 2nd to 2nd-to-last character
str_sub(x, 2, -2)
[1] "bcde" "hifj"
str_sub() também pode ser usada para modificar strings:
str_sub(x, 3, 3) <- "X"
x
[1] "abXdef" "ghXfjk"
Para duplicar strings individuais, use str_dup():
str_dup(x, c(2, 3))
[1] "abXdefabXdef" "ghXfjkghXfjkghXfjk"
As funções a seguir adicionam, removem ou modificanm espaços em branco existentes nas strings.
I)
x <- c("abc", "defghi")
str_pad(x, 10)
[1] " abc" " defghi"
str_pad(x, 10, "right")
[1] "abc " "defghi "
str_pad(x, 10, "both")
[1] " abc " " defghi "
str_pad(x, 10, "both", pad="@")
[1] "@@@abc@@@@" "@@defghi@@"
str_pad(x, 4)
[1] " abc" "defghi"
str_pad()
com str_trunc():x <- c("Short", "This is a long string")
str_trunc(x, 10)
[1] "Short" "This is..."
str_pad(x, 10)
[1] " Short" "This is a long string"
# x %>%
# str_trunc(10) %>%
# str_pad(10, "right")
II)
x <- c(" a ", "b ", " c")
str_trim(x)
[1] "a" "b" "c"
str_trim(x, "left")
[1] "a " "b " "c"
III)
jabberwocky <- str_c(
"`Twas brillig, and the slithy toves ",
"did gyre and gimble in the wabe: ",
"All mimsy were the borogoves, ",
"and the mome raths outgrabe. "
)
# uma alternativa a c() para strings
cat(str_wrap(jabberwocky, width = 40))
`Twas brillig, and the slithy toves did
gyre and gimble in the wabe: All mimsy
were the borogoves, and the mome raths
outgrabe.
# cat0 é uma alternativa a print
Uma boa parte das funções do pacote stringr são locale sensitive: elas vão performar de diferente dependendo do país/região em que o usuário se encontra.
Veja exemplos de funções que transformam letras de caixa baixa para caixa alta e vice-versa:
x <- "I like horses."
str_to_upper(x)
[1] "I LIKE HORSES."
str_to_title(x)
[1] "I Like Horses."
str_to_lower(x)
[1] "i like horses."
# Veja o caso do idioma turco em que há dois tipos de i: um com "ponto" e outro sem
str_to_lower(x, "tr")
[1] "ı like horses."
Ordenando strings e seus índices:
x <- c("y", "i", "k")
str_order(x) # índices
[1] 2 3 1
str_sort(x)
[1] "i" "k" "y"
# No idioma da Lituânia, y está entre as letras i and k
str_sort(x, locale = "lt")
[1] "i" "y" "k"
Um aspecto importante é que a configuração default de stringr sempre vem com o idioma em inglês para garantir um comportamento idêntico em qualquer sistema ao se utilizar as funções genéricas. Isso é diferente do que normalmente ocorre com R base, onde a opção global locale normalmente varia com a versão regional do sistema operacional e gera bastante confusão quando vamos desenvolver programas.
Para obtermos uma lista das abreviações e regiões disponíveis, é só rodar stringi::stri_locale_list()
.
A vasta maioria das funções de stringr funciona com padrões. Estas funções são parametrizadas pelo tipo de taefa que elas performam e para quais padrões elas fazem o matching ou pareamento.
Tasks
Cada função de matching possui os mesmos dois primeiros argumentos: um vetor de strings para processar e um padrão para fazer o matching. O pacote stringr disponibiliza função de pattern matching para detectar, localizar, extrair, parear, substituir e separar strings.
Veja um exemplo com algumas strings e uma Expressão Regular para encontrar números de telefone (dos EUA):
strings <- c(
"apple",
"219 733 8965",
"329-293-8753",
"Work: 579-499-7527; Home: 543.355.3679"
)
phone <- "([2-9][0-9]{2})[- .]([0-9]{3})[- .]([0-9]{4})"
# Which strings contain phone numbers?
str_detect(strings, phone)
[1] FALSE TRUE TRUE TRUE
str_subset(strings, phone)
[1] "219 733 8965"
[2] "329-293-8753"
[3] "Work: 579-499-7527; Home: 543.355.3679"
# How many phone numbers in each string?
str_count(strings, phone)
[1] 0 1 1 2
# Where in the string is the phone number located?
(loc <- str_locate(strings, phone))
start end
[1,] NA NA
[2,] 1 12
[3,] 1 12
[4,] 7 18
str_locate_all(strings, phone)
[[1]]
start end
[[2]]
start end
[1,] 1 12
[[3]]
start end
[1,] 1 12
[[4]]
start end
[1,] 7 18
[2,] 27 38
# What are the phone numbers?
str_extract(strings, phone)
[1] NA "219 733 8965" "329-293-8753" "579-499-7527"
str_extract_all(strings, phone)
[[1]]
character(0)
[[2]]
[1] "219 733 8965"
[[3]]
[1] "329-293-8753"
[[4]]
[1] "579-499-7527" "543.355.3679"
str_extract_all(strings, phone, simplify = TRUE)
[,1] [,2]
[1,] "" ""
[2,] "219 733 8965" ""
[3,] "329-293-8753" ""
[4,] "579-499-7527" "543.355.3679"
()
apenas para o primeiro match.# Pull out the three components of the match
str_match(strings, phone)
[,1] [,2] [,3] [,4]
[1,] NA NA NA NA
[2,] "219 733 8965" "219" "733" "8965"
[3,] "329-293-8753" "329" "293" "8753"
[4,] "579-499-7527" "579" "499" "7527"
str_match_all(strings, phone)
[[1]]
[,1] [,2] [,3] [,4]
[[2]]
[,1] [,2] [,3] [,4]
[1,] "219 733 8965" "219" "733" "8965"
[[3]]
[,1] [,2] [,3] [,4]
[1,] "329-293-8753" "329" "293" "8753"
[[4]]
[,1] [,2] [,3] [,4]
[1,] "579-499-7527" "579" "499" "7527"
[2,] "543.355.3679" "543" "355" "3679"
# caso desejássemos esconder os telefones (mto util para CPF's)
str_replace(strings, phone, "XXX-XXX-XXXX")
[1] "apple"
[2] "XXX-XXX-XXXX"
[3] "XXX-XXX-XXXX"
[4] "Work: XXX-XXX-XXXX; Home: 543.355.3679"
str_replace_all(strings, phone, "XXX-XXX-XXXX")
[1] "apple"
[2] "XXX-XXX-XXXX"
[3] "XXX-XXX-XXXX"
[4] "Work: XXX-XXX-XXXX; Home: XXX-XXX-XXXX"
str_split("a-b-c", "-")
[[1]]
[1] "a" "b" "c"
str_split_fixed("a-b-c", "-", n = 2)
[,1] [,2]
[1,] "a" "b-c"
Obtendo índices do matching:
x[str_detect(x, pattern)]
.fruit <- c("apple", "banana", "pear", "pinapple")
str_subset(fruit, "a")
[1] "apple" "banana" "pear" "pinapple"
which(str_detect(x, pattern))
.str_which(LETTERS, "F")
[1] 6
# str_which(LETTERS, "F|Y")
Expessões Regulares são formas concisas e flexíveis para descrever padrões (que estamos buscando) em strings;
Expressões regulares nos permitem descrever em termos mais gerais o que desejamos buscar em uma string;
Normalmente, é mais eficiente usarmos expressões regulares, porque se buscarmos caracteres simples, só obteremos o matching exato do caractere. Já pensou em como faríamos para buscar apenas números de telefone, endereços de e-mail, CNPJ's ou CPF's no meio de dados (por exemplo páginas da internet) que não estão exatamente organizados em colunas?
expressões regulares são a principal engine de matching no pacote stringr;
é uma técnica usada praticamente em todo o mundo da programação. Não é uma particularidade somente do R.
As expressões regulares (regex ou regexps), em geral, são construídas da combinação de 3 componentes:
1) literal characters: que somente vão sofrer matching se houver nos dados caracteres literais idênticos;
2) character classes: que permitem o matching por múltiplos um caractere - são compostas de caracteres dentro de dois colchetes [ ];
3) modifiers ou anchors: que vão operar nos caracteres, nas classes e em combinações dos dois.
Correspondências literais (basic match/ literal characters)
O padrão mais simples para fazer a matching é:
x <- c("apple", "banana", "pear")
str_extract(x, "an")
[1] NA "an" NA
Podemos ignorar letras maiúsculas e minúsculas com ignore_case = TRUE:
bananas <- c("banana", "Banana", "BANANA")
str_detect(bananas, "banana")
[1] TRUE FALSE FALSE
str_detect(bananas, regex("banana", ignore_case = TRUE))
[1] TRUE TRUE TRUE
Seguindo com os exemplos, o próximo passo é conhecermos o papel desempenhado por ., cuja função é buscar qualquer caractere com exceção de uma nova linha (\n):
str_extract(x, ".a.")
[1] NA "ban" "ear"
Mas se você quiser encontrar até mesmo caracteres que indicam uma nova linha, pode “setar” dotall = TRUE
dentro da função responsável pelo mecanismo de matching, que nesse caso é regex()
(visto no item anterior):
str_detect("\nX\n", ".X.")
[1] FALSE
str_detect("\nX\n", regex(".X.", dotall = TRUE))
[1] TRUE
Se “ . ” faz o match de qualquer caractere, como podemos fazer o match literal de um ponto “ . ” ?
Teremos que usar um caractere de escape para dizer ao R que queremos um matching literal e não usar o comportamento especial de “ . ”
Em expressões regulares (regexps) se usa uma barra invertida \ para tal fim
Então, para buscarmos um simples . nos dados, usamos “\ .”
Mas…
como você já viu no Módulo 1, a barra invertida também é um caractere especial no R usada para strings
Portanto, teríamos que acrescentar outra barra invertida formando a string “\ \ .” e fazermos o matching com um simples ponto nos dados.
Basicamente, a regra é: em cada par de barras invertidas, temos uma barra literal
# To create the regular expression, we need \\
dot <- "\\."
# But the expression itself only contains one:
writeLines(dot)
\.
# And this tells R to look for an explicit .
str_extract(c("abc", "a.c", "bef"), "a\\.c")
[1] NA "a.c" NA
Facilita pensar que uma regexp é um tipo de string, ou seja regexps é um conjunto que está contido no conjunto de objetos que são strings.
Temos que pensar primeiro em como deve chegar a string dentro do mecanismo de regexp.
Como faríamos então para fazer o match literal de uma barra invertida “ \ ” ?
Basta seguirmos o mesmo raciocínio das duas instâncias:
Como deve chegar literalmente a string no mecanismo de regexp (após todas as deduções de caracteres de escape)?
Deve chegar como “ \ \ ”
Então, devemos digitar “ \ \ \ \ ”, onde cada par de barras equivale a uma literal que chega ao mecanismo de regex.
Quando formos nos referir às expressões regulares, usaremos somente o núcleo delas, exemplo “ \ ”
Quando formos nos referir as strings, usaremos “\ \”;
Lembre-se que devemos acrescentar os caracteres de escape usando os raciocínios apresentados anteriormente para que se chegue ao “padrão final” de uma regexp.
x <- "a\\b"
writeLines(x)
a\b
str_extract(x, "\\\\")
[1] "\\"
Primeiro, veremos alguns atalhos para classes de caracteres:
atalhos | classes |
---|---|
\w | alfanuméricos e _ (qualquer palavra) |
\W | não alfanuméricos (qualquer coisa diferente de palavras e _) |
\d | digitos |
\D | não dígitos |
\s | espaço |
\S | não espaço |
Passemos aos exemplos:
str_extract_all("Don't eat that!", "\\w+")[[1]]
[1] "Don" "t" "eat" "that"
str_split("Don't eat that!", "\\W")[[1]]
[1] "Don" "t" "eat" "that" ""
str_extract_all("1 + 2 = 3", "\\d+")[[1]]
[1] "1" "2" "3"
(text <- "Some \t badly\n\t\tspaced \f text")
[1] "Some \t badly\n\t\tspaced \f text"
str_replace_all(text, "\\s+", " ")
[1] "Some badly spaced text"
Um outro atalho interessante é “\ b” que busca por limites/bordas de palavras, ou seja, transições entre o caracteres de palavras e caracteres de não-palavras. “\ B” faz o oposto.
str_replace_all("The quick brown fox", "\\b", "_")
[1] "_The_ _quick_ _brown_ _fox_"
str_replace_all("The quick brown fox", "\\B", "_")
[1] "T_h_e q_u_i_c_k b_r_o_w_n f_o_x"
Outros atalhos interessantes estão em http://stringr.tidyverse.org/articles/regular-expressions.html.
Há a possibilidade, ainda, de criarmos nossas próprias classes usando []:
As expressões anteriores vão dentro de outros colchetes:
y <- c(1234, "R", "FNDE", " ", "Olá, tudo bem ?")
str_extract_all(y, "[[:digit:]]")
[[1]]
[1] "1" "2" "3" "4"
[[2]]
character(0)
[[3]]
character(0)
[[4]]
character(0)
[[5]]
character(0)
str_extract_all(y, "[[:digit:]á]")
[[1]]
[1] "1" "2" "3" "4"
[[2]]
character(0)
[[3]]
character(0)
[[4]]
character(0)
[[5]]
[1] "á"
str_extract_all(y, "[[:digit:]td]")
[[1]]
[1] "1" "2" "3" "4"
[[2]]
character(0)
[[3]]
character(0)
[[4]]
character(0)
[[5]]
[1] "t" "d"
str_extract_all(y, "[b[:upper:]m]")
[[1]]
character(0)
[[2]]
[1] "R"
[[3]]
[1] "F" "N" "D" "E"
[[4]]
character(0)
[[5]]
[1] "O" "b" "m"
Há ainda diversas classes pré-construídas que podemos usar com os colchetes:
-[:punct:]: pontuação.
-[:alpha:]: letras.
-[:lower:]: letras minúsculas.
-[:upper:]: LETRAS MAIÚSCULAS.
-[:digit:]: digitos.
-[:alnum:]: letras e números.
-[:cntrl:]: caracteres de controle.
-[:graph:]: letras, números e pontuação.
-[:print:]: letras, números, pontuação e whitespace.
-[:space:]: caracteres de espaço (equivalente a “\ s”).
-[:blank:]: espaço e tab.
As expressões vão dentro de outros colchetes:
y <- c(1234, "R", "FNDE", " ", "Olá, tudo bem ?")
str_extract_all(y, "[[:digit:]]")
[[1]]
[1] "1" "2" "3" "4"
[[2]]
character(0)
[[3]]
character(0)
[[4]]
character(0)
[[5]]
character(0)
str_extract_all(y, "[[:digit:]á]")
[[1]]
[1] "1" "2" "3" "4"
[[2]]
character(0)
[[3]]
character(0)
[[4]]
character(0)
[[5]]
[1] "á"
str_extract_all(y, "[[:digit:]td]")
[[1]]
[1] "1" "2" "3" "4"
[[2]]
character(0)
[[3]]
character(0)
[[4]]
character(0)
[[5]]
[1] "t" "d"
str_extract_all(y, "[b[:upper:]m]")
[[1]]
character(0)
[[2]]
[1] "R"
[[3]]
[1] "F" "N" "D" "E"
[[4]]
character(0)
[[5]]
[1] "O" "b" "m"
“ | ” é o operador de alternância que permite escolher entre uma ou mais correspondências possíveis.
abc|def vai fazer o matching com abc OU def.
str_detect(c("abc", "def", "ghi"), "abc|def")
[1] TRUE TRUE FALSE
Parenteses podem servir para alterar regras de precedência ou formar grupos.
O mesmo que vimos no módulo 1 para alterar regras de precedência serve para regexp:
str_extract(c("grey", "gray"), "gre|ay")
[1] "gre" "ay"
str_extract(c("grey", "gray"), "gr(e|a)y")
[1] "grey" "gray"
Parênteses definem grupos e podemos retroreferenciar esses grupos indicando que eles aparecem mais uma, duas ou mais vezes. No exemplo a seguir, buscamos os pares de caracteres que aparecem duas vezes.
fruit
[1] "apple" "banana" "pear" "pinapple"
pattern <- "(..)\\1"
fruit %>%
str_subset(pattern)
[1] "banana"
# (..) = qualquer par de dois caracteres
# \1 = backreference to the first group - repeticao
fruit %>%
str_subset(pattern) %>%
str_match(pattern)
[,1] [,2]
[1,] "anan" "an"
# help de str_match: "....First column is the complete match, followed by one column for each capture group."
Ancorar significa estabelecer um padrão para o início ou final da string que estamos buscando.
x <- c("apple", "banana", "pear")
str_extract(x, "^a")
[1] "a" NA NA
str_extract(x, "a$")
[1] NA "a" NA
Pode-se controlar quantas vezes um padrão aparece em determinada parte da string com:
x <- "1888 is the longest year in Roman numerals: MDCCCLXXXVIII"
str_extract(x, "CC?")
[1] "CC"
str_extract(x, "CC+")
[1] "CCC"
str_extract(x, 'C[LX]+')
[1] "CLXXX"
str_match("banana", '(na)+')
[,1] [,2]
[1,] "nana" "na"
Podemos especificar o número exato de repetições que esperamos com:
str_extract(x, "C{2}")
[1] "CC"
str_extract(x, "C{2,}")
[1] "CCC"
str_extract(x, "C{2,3}")
[1] "CCC"
Há diversos outros padrões e expressões que podem ser usadas. Busque ??stringi_search-regex
ou visite o site de stringr.
OBRIGADO!!