Data Frames


***

Parte do livro Introdução à análise de dados com R.  Este trabalho está em andamento, o texto é bastante preliminar e sofrerá muitas alterações. 

Quer fazer sugestões? Deixe um comentário abaixo ou, se você sabe utilizar o github, acesse aqui.

Não copie ou reproduza este material sem autorização.

Volte para ver atualizações!

***

Data Frames: seu banco de dados no R

Por que um data.frame?

Até agora temos utilizado apenas dados de uma mesma classe, armazenados ou em um vetor ou em uma matriz. Mas uma base de dados, em geral, é feita de dados de diversas classes diferentes: no exemplo anterior, por exemplo, podemos querer ter uma coluna com os nomes dos funcionários, outra com o sexo dos funcionários, outra com valores… note que essas colunas são de classes diferentes, como textos e números. Como guardar essas informações?

A solução para isso é o data.frame. O data.frame é talvez o formato de dados mais importante do R. No data.frame cada coluna representa uma variável e cada linha uma observação. Essa é a estrutura ideal para quando você tem várias variáveis de classes diferentes em um banco de dados.

Criando um data.frame: data.frame() e as.data.frame()

É possível criar um data.frame diretamente com a função data.frame():

funcionarios <- data.frame(nome = c("João", "Maria", "José"),
                           sexo = c("M", "F", "M"),
                           salario = c(1000, 1200, 1300),
                           stringsAsFactors = FALSE)
funcionarios
##    nome sexo salario
## 1  João    M    1000
## 2 Maria    F    1200
## 3  José    M    1300

Também é coverter outros objetos em um data.frame com a função as.data.frame().

Discutiremos a opção stringsAsFactors = FALSE mais a frente.

Vejamos a estrutura do data.frame. Note que cada coluna tem sua própria classe.

str(funcionarios)
## 'data.frame':    3 obs. of  3 variables:
##  $ nome   : chr  "João" "Maria" "José"
##  $ sexo   : chr  "M" "F" "M"
##  $ salario: num  1000 1200 1300

Nomes de linhas e colunas

O data.frame sempre terá rownames e colnames.

rownames(funcionarios)
## [1] "1" "2" "3"

colnames(funcionarios)
## [1] "nome"    "sexo"    "salario"

Detalhe: a função names() no data.fram trata de suas colunas, pois os elementos fundamentais do data.frame são seus vetores coluna.

names(funcionarios)
## [1] "nome"    "sexo"    "salario"

Não parece tão diferente de uma matriz…

O que ocorreria com o data.frame funcionarios se o transformássemos em uma matriz? Vejamos:

as.matrix(funcionarios)
##      nome    sexo salario
## [1,] "João"  "M"  "1000"
## [2,] "Maria" "F"  "1200"
## [3,] "José"  "M"  "1300"

Perceba que todas as variáveis viraram character! Uma matriz aceita apenas elementos da mesma classe, e é exatamente por isso precisamos de um data.frame neste caso.

Manipulando data.frames como matrizes

Ok, temos mais um objeto do R, o data.frame … vou ter que reaprender tudo novamente? Não! Você pode manipular data.frames como se fossem matrizes!

Praticamente tudo o que vimos para selecionar e modificar elementos em matrizes funciona no data.frame. Podemos selecionar linhas e colunas do nosso data.frame como se fosse uma matriz:

## tudo menos linha 1
funcionarios[-1, ]
##    nome sexo salario
## 2 Maria    F    1200
## 3  José    M    1300

## seleciona primeira linha e primeira coluna (vetor)
funcionarios[1, 1]
## [1] "João"

## seleciona primeira linha e primeira coluna (data.frame)
funcionarios[1, 1, drop = FALSE]
##   nome
## 1 João

## seleciona linha 3, colunas "nome" e "salario"
funcionarios[3 , c("nome", "salario")]
##   nome salario
## 3 José    1300

E também alterar seus valores tal como uma matriz.

## aumento de salario para o João
funcionarios[1, "salario"] <- 1100

funcionarios
##    nome sexo salario
## 1  João    M    1100
## 2 Maria    F    1200
## 3  José    M    1300

Extra do data.frame: selecionando e modificando com $ e [[ ]]

Outras formas alternativas de selecionar colunas em um data.frame são o $ e o [[ ]]:

## Seleciona coluna nome
funcionarios$nome
## [1] "João"  "Maria" "José"

funcionarios[["nome"]]
## [1] "João"  "Maria" "José"

## Seleciona coluna salario
funcionarios$salario
## [1] 1100 1200 1300

funcionarios[["salario"]]
## [1] 1100 1200 1300

Tanto o $ quanto o [[ ]] sempre retornam um vetor como resultado.

Também é possível alterar a coluna combinando $ ou [[ ]] com <-:

## outro aumento para o João
funcionarios$salario[1] <- 1150

## equivalente
funcionarios[["salario"]][1] <- 1150
funcionarios
##    nome sexo salario
## 1  João    M    1150
## 2 Maria    F    1200
## 3  José    M    1300

Extra do data.frame: retornando sempre um data.frame com [ ]

Se você quiser garantir que o resultado da seleção será sempre um data.frame use drop = FALSE ou selecione sem a vírgula:

## Retorna data.frame
funcionarios[ ,"salario", drop = FALSE]
##   salario
## 1    1150
## 2    1200
## 3    1300

## Retorna data.frame
funcionarios["salario"]
##   salario
## 1    1150
## 2    1200
## 3    1300

Tabela resumo: selecionando uma coluna em um data.frame

Resumindo as formas de seleção de uma coluna de um data.frame.

screen-shot-2017-02-07-at-12-02-02-am

Criando colunas novas

Há diversas formas de criar uma coluna nova em um data.frame. O principal segredo é o seguinte: faça de conta que a coluna já exista, selecione ela com $, [,] ou [[]] e atribua o valor que deseja.

Para ilustrar, vamos adicionar ao nosso data.frame funcionarios mais três colunas.

Com $:

funcionarios$escolaridade <- c("Ensino Médio", "Graduação", "Mestrado")

Com [ , ]:

funcionarios[, "experiencia"] <- c(10, 12, 15)

Com [[ ]]:

funcionarios[["avaliacao_anual"]] <- c(7, 9, 10)

Uma última forma de adicionar coluna a um data.frame é, tal como uma matriz, utilizar a função cbind() (column bind).

funcionarios <- cbind(funcionarios,
                      prim_emprego = c("sim", "nao", "nao"),
                      stringsAsFactors = FALSE)

Vejamos como ficou nosso data.frame com as novas colunas:

funcionarios
##    nome sexo salario escolaridade experiencia avaliacao_anual prim_emprego
## 1  João    M    1150 Ensino Médio          10               7          sim
## 2 Maria    F    1200    Graduação          12               9          nao
## 3  José    M    1300     Mestrado          15              10          nao

E agora, temos colunas demais, como remover algumas delas?

Removendo colunas

A forma mais fácil de remover coluna de um data.fram é atribuir o valor NULL a ela:

## deleta coluna prim_emprego
funcionarios$prim_emprego <- NULL

Mas a forma mais segura e universal de remover qualquer elemento de um objeto do R é selecionar tudo exceto aquilo que você não deseja. Isto é, selecione todas colunas menos as que você não quer e atribua o resultado de volta ao seu data.frame:

## deleta colunas 4 e 6
funcionarios <- funcionarios[, c(-4, -6)]

Adicionando linhas

Uma forma simples de adicionar linhas é atribuir a nova linha com <-. Mas cuidado! O que irá acontecer com o data.frame com o código abaixo?

## CUIDADO!
funcionarios[4, ] <- c("Ana", "F", 2000,  15)

Note que nosso data.frame inteiro se transformou em texto! Você sabe explicar por que isso aconteceu? relembrar coerção

str(funcionarios)
## 'data.frame':    4 obs. of  4 variables:
##  $ nome       : chr  "João" "Maria" "José" "Ana"
##  $ sexo       : chr  "M" "F" "M" "F"
##  $ salario    : chr  "1150" "1200" "1300" "2000"
##  $ experiencia: chr  "10" "12" "15" "15"

Antes de prosseguir, transformemos as colunas salario e experiencia em números novamente:

funcionarios$salario <- as.numeric(funcionarios$salario) 

funcionarios$experiencia <- as.numeric(funcionarios$experiencia)

Se os elementos forem de classe diferente, use a função data.frame para evitar coerção:

funcionarios[4, ] <- data.frame(nome = "Ana", sexo = "F",
                                salario = 2000, experiencia = 15,
                                stringsAsFactors = FALSE)

Também é possível adicionar linhas com rbind():

rbind(funcionarios,
      data.frame(nome = "Ana", sexo = "F",
                 salario = 2000,  experiencia = 15,
                 stringsAsFactors = FALSE))

Atenção! Não fique aumentando um data.frame de tamanho adicionando linhas ou colunas. Sempre que possível pré-aloque espaço!

Removendo linhas

Para remover linhas, basta selecionar apenas aquelas linhas que você deseja manter:

## remove linha 4 do data.frame
funcionarios <- funcionarios[-4, ]
## remove linhas em que salario <= 1150
funcionarios <- funcionarios[funcionarios$salario > 1150, ]

Filtrando linhas com vetores logicos

Relembrando: se passarmos um vetor lógico na dimensão das linhas, selecionamos apenas aquelas que são TRUE. Assim, por exemplo, se quisermos selecionar aquelas linhas em que a coluna salario é maior do que um determinado valor, basta colocar esta condição como filtro das linhas:

## Apenas linhas com salario > 1000
funcionarios[funcionarios$salario > 1000, ]
##    nome sexo salario experiencia
## 2 Maria    F    1200          12
## 3  José    M    1300          15

## Apenas linhas com sexo == "F"
funcionarios[funcionarios$sexo == "F", ]
##    nome sexo salario experiencia
## 2 Maria    F    1200          12

Funções de conveniência: subset()

Uma função de conveniência para selecionar linhas e colunas de um data.frame é a função subset(), que tem a seguinte estrutura:

subset(nome_do_data_frame,
       subset = expressao_logica_para_filtrar_linhas,
       select = nomes_das_colunas,
       drop   = simplicar_para_vetor?)

Vejamos alguns exemplos:

## funcionarios[funcionarios$sexo == "F",]
subset(funcionarios, sexo == "F")
##    nome sexo salario experiencia
## 2 Maria    F    1200          12

## funcionarios[funcionarios$sexo == "M", c("nome", "salario")]
subset(funcionarios, sexo == "M", select = c("nome", "salario"))
##   nome salario
## 3 José    1300

Funções de conveniência: with

A função with() permite que façamos operações com as colunas do data.frame sem ter que ficar repetindo o nome do data.frame seguido de $ , [ , ] ou [[]] o tempo inteiro.

Para ilustrar:

## Com o with
with(funcionarios, (salario^3 - salario^2)/log(salario))
## [1] 2.4e+08 3.1e+08

## Sem o with
(funcionarios$salario^3 - funcionarios$salario^2)/log(funcionarios$salario)
## [1] 2.4e+08 3.1e+08

Quatro formas de fazer a mesma coisa (pense em outras formas possíveis):

subset(funcionarios, sexo == "M", select = "salario", drop = TRUE)
## [1] 1300

with(funcionarios, salario[sexo == "M"])
## [1] 1300

funcionarios$salario[funcionarios$sexo == "M"]
## [1] 1300

funcionarios[funcionarios$sexo == "M", "salario"]
## [1] 1300

Aplicando funções no data.frame: sapply e lapply, funções nas colunas (elementos)

Outras duas funções bastante utilizadas no R são as funções sapply() e lapply().

  • As funções sapply e lapply aplicam uma função em cada elemento de um objeto.
  • Como vimos, os elementos de um data.frame são suas colunas. Deste modo, as funções sapply e lapply aplicam uma função nas colunas de um data.frame.
  • A diferença entre uma e outra é que a primeira tenta simplificar o resultado enquanto que a segunda sempre retorna uma lista.

Testando no nosso data.frame:

sapply(funcionarios[3:4], mean)
##     salario experiencia
##        1250          14

lapply(funcionarios[3:4], mean)
## $salario
## [1] 1250
##
## $experiencia
## [1] 14

Filtrando variáveis antes de aplicar funções: filter()

Como data.frames podem ter variáveis de classe diferentes, muitas vezes é conveniente filtrar apenas aquelas colunas de determinada classe (ou que satisfaçam determinada condição). A função Filter() é uma maneira rápida de fazer isso:

# seleciona apenas colunas numéricas
Filter(is.numeric, funcionarios)
##   salario experiencia
## 2    1200          12
## 3    1300          15

# seleciona apenas colunas de texto
Filter(is.character, funcionarios)
##    nome sexo
## 2 Maria    F
## 3  José    M

Juntando filter() com sapply() você pode aplicar funções em apenas certas colunas, como por exemplo, calcular a média e máximo apenas nas colunas numéricas do nosso data.frame:

sapply(Filter(is.numeric, funcionarios), mean)
##     salario experiencia
##        1250          14

sapply(Filter(is.numeric, funcionarios), max)
##     salario experiencia
##        1300          15

Manipulando data.frames

Ainda temos muita coisa para falar de manipulação de data.framese isso merece um espaço especial. Veremos além de outras funções base do R alguns pacotes importantes como dplyr, reshape2 e tidyr em uma seção separada.

Programação no R: if(), if() else e ifelse()


***

Parte do livro Introdução à análise de dados com R.  Este trabalho está em andamento, o texto é bastante preliminar e sofrerá muitas alterações. 

Quer fazer um curso presencialmente!? Estamos com turmas abertas em Brasília e São Paulo!

Quer fazer sugestões? Deixe um comentário abaixo ou, se você sabe utilizar o github, acesse aqui.

Não copie ou reproduza este material sem autorização.

Volte para ver atualizações!

***

Há ocasiões em queremos ou precisamos executar parte do código apenas se alguma condição for atendida. O R fornece três opções básicas para estruturar seu código dessa maneira: if(), if() else e ifelse(). Vejamos cada uma delas.

O if() sozinho

A estrutura básica do if() é a seguinte:

if (condicao) {

  # comandos que
  # serao rodados
  # caso condicao = TRUE

}
  • O início do código se dá com o comando if seguido de parênteses e chaves;
  • Dentro do parênteses temos uma condição lógica, que deverá ter como resultado ou TRUE ou FALSE;
  • Dentro das chaves temos o bloco de código que será executado se – e somente se – a condição do parênteses for TRUE.

Vejamos um exemplo muito simples. Temos dois blocos de código que criam as variáveis x e y, mas eles só serão executados se as variáveis cria_x e cria_y forem TRUE, respectivamente.

# vetores de condição lógica
cria_x <- TRUE
cria_y <- FALSE

# só executa se cria_x = TRUE
if (cria_x) {
  x <- 1
}

# só executa se cria_y = TRUE
if (cria_y) {
  y <- 1
}

# note que x foi criado
exists("x")
## [1] TRUE

# note que y não foi criado
exists("y")
## [1] FALSE

Note que somente a variável x foi criada. Vamos agora rodar o mesmo bloco mas com TRUE e FALSE diferentes.

# remove x que foi criado
rm(x)

# vetores de condição lógica
cria_x <- FALSE
cria_y <- TRUE

# só executa se cria_x = TRUE
if (cria_x) {
  x <- 1
}

# só executa se cria_y = TRUE
if (cria_y) {
  y <- 1
}

# note que x não foi criado
exists("x")
## [1] FALSE

# note que y foi criado
exists("y")
## [1] TRUE

Note que agora apenas o y foi criado.

O if() com o else

Outra forma de executar códigos de maneira condicional é acrescentar ao if() o opcional else.

A estrutura básica do if() else é a seguinte:

if (condicao) {

  # comandos que
  # serao rodados
  # caso condicao = TRUE

} else {

  # comandos que
  # serao rodados
  # caso condicao = FALSE

}
  • O início do código se dá com o comando if seguido de parênteses e chaves;
  • Dentro do parênteses temos uma condição lógica, que deverá ter como resultado ou TRUE ou FALSE;
  • Dentro das chaves do if() temos um bloco de código que será executado se – e somente se – a condição do parênteses for TRUE.
  • Logo em seguida temos o else seguido de chaves;
  • Dentro das chaves do else temos um bloco de código que será executado se – e somente se – a condição do parênteses for FALSE.

Como no caso anterior, vejamos primeiramente um exemplo bastante simples.

numero <- 1

if (numero == 1) {
  cat("o numero é igual a 1")
} else {
  cat("o numero não é igual a 1")
}
## o numero é igual a 1

É possível encadear diversos if() else em sequência:

numero <- 10

if (numero == 1) {
  cat("o numero é igual a 1")
} else if (numero == 2) {
  cat("o numero é igual a 2")
} else {
  cat("o numero não é igual nem a 1 nem a 2")
}
## o numero não é igual nem a 1 nem a 2

Para fins de ilustração, vamos criar uma função que nos diga se um número é par ou ímpar. Nela vamos utilizar tanto o if() sozinho quanto o if() else.

Vale relembrar que um número (inteiro) é par se for divisível por 2 e que podemos verificar isso se o resto da divisão (operador %% no R) deste número por 2 for igual a zero.

par_ou_impar <- function(x){

  # verifica se o número é um decimal comparando o tamanho da diferença de x e round(x)
  # se for decimal retorna NA (pois par e ímpar não fazem sentido para decimais)
  if (abs(x - round(x)) > 1e-7) {
    return(NA)
  }

  # se o número for divisível por 2 (resto da divisão zero) retorna "par"
  # caso contrário, retorna "ímpar"
  if (x %% 2 == 0) {
    return("par")
  } else {
    return("impar")
  }

}

Vamos testar nossa função:

par_ou_impar(4)
## [1] "par"
par_ou_impar(5)
## [1] "impar"
par_ou_impar(2.1)
## [1] NA

Parece que está funcionando bem… só tem um pequeno problema. Se quisermos aplicar nossa função a um vetor de números, olhe o que ocorrerá:

x <- 1:5
par_ou_impar(x)
## Warning in if (abs(x - round(x)) > 1e-07) {: a condição tem comprimento > 1 e somente o primeiro
## elemento será usado
## Warning in if (x%%2 == 0) {: a condição tem comprimento > 1 e somente o primeiro elemento será usado
## [1] "impar"

Provavelmente não era isso o que esperávamos. O que está ocorrendo aqui?

A função ifelse()

Os comandos if() e if() else não são vetorizados. Uma alternativa para casos como esses é utilizar a função ifelse().

A função ifelse() tem a seguinte estrutura básica:

ifelse(vetor_de_condicoes, valor_se_TRUE, valor_se_FALSE)
  • o primeiro argumento é um vetor (ou uma expressão que retorna um vetor) com vários TRUE e FALSE;
  • o segundo argumento é o valor que será retornado quando o elemento do vetor_de_condicoes for TRUE;
  • o terceiro argumento é o valor que será retornado quando o elemento do vetor_de_condicoes for FALSE.

Primeiramente, vejamos um caso trivial, para entender melhor como funciona o ifelse():

ifelse(c(TRUE, FALSE, FALSE, TRUE), 1, -1)
## [1]  1 -1 -1  1

Note que passamos um vetor de condições com TRUE, FALSE, FALSE e TRUE. O valor para o caso TRUE é 1 e o valor para o caso FALSE é -1. Logo, o resultado é 1, -1, -1 e 1.

Façamos agora um exemplo um pouco mais elaborado. Vamos criar uma versão com ifelse da nossa função que nos diz se um número é par ou ímpar.

par_ou_impar_ifelse <- function(x){

  # se x for decimal, retorna NA, se não for, retorna ele mesmo (x)
  x <- ifelse(abs(x - round(x)) > 1e-7, NA, x)

  # se x for divisivel por 2, retorna 'par', se não for, retorna impar
  ifelse(x %% 2 == 0, "par", "impar")
}

Testemos a função com vetores. Perceba que agora funciona sem problemas!

par_ou_impar_ifelse(x)
## [1] "impar" "par"   "impar" "par"   "impar"
par_ou_impar_ifelse(c(x, 1.1))
## [1] "impar" "par"   "impar" "par"   "impar" NA

Vetorização e ifelse()

Um tema constante neste livro é fazer com que você comece a pensar em explorar a vetorização do R. Este caso não é diferente, note que poderíamos ter feito a função utilizando apenas comparações vetorizadas:

par_ou_impar_vec <- function(x){

  # transforma decimais em NA
  decimais <- abs(x - round(x)) > 1e-7
  x[decimais] <- NA

  # Cria vetor para aramazenar resultados
  res <- character(length(x))

  # verificar quem é divisível por dois
  ind <- (x %% 2) == 0

  # quem for é par
  res[ind] <- "par"

  # quem não for é ímpar
  res[!ind] <- "impar"

  # retorna resultado
  return(res)
}

Na prática, o que a função ifelse() faz é mais ou menos isso o que fizemos acima – comparações e substituições de forma vetorizada. Note que, neste caso, nossa implementação ficou inclusive um pouco mais rápida do que a solução anterior com ifelse():

library(microbenchmark)
microbenchmark(par_ou_impar_vec(1:1e3), par_ou_impar_ifelse(1:1e3))
## Unit: microseconds
##                         expr min  lq mean median  uq  max neval cld
##     par_ou_impar_vec(1:1000)  56  58   85     59  83 1428   100  a 
##  par_ou_impar_ifelse(1:1000) 322 324  411    326 414 2422   100   b