Programação com R

Esta é uma seção mais técnica que pode ser lida por último. Nesse caso pule para a seção Aquisição de Dados.

Esta seção apresenta uma formalização um pouco mais rigorosa dos conceitos do R como linguagem de programação. Em uma primeira leitura, para aqueles que ainda estão se familiarizando com a linguagem, ela pode ser pulada e lida mais tarde. Ela contém um pouco de repetição do material já visto, para fins de completeza.

Objetos

R é uma linguagem de programação de array, funcional e orientada a objeto. Todos os elementos de R, variáveis de dados e funções, são objetos. Não se pode acessar locais da memória diretamente e todos os objetos usados na execução de um programa são armazenados em memória RAM. Isso acaba tendo um peso importante quando se processa um grande volume de dados.

Linguagens de programação de arrays (também chamadas de linguagens vetoriais ou multidimensionais) são linguagens onde operações sobre objetos multidimensionais (vetores, matrizes, etc.) generalizam as operações sobre escalares de forma transparente. Elas permitem um código mais conciso e legível.

Todos os objetos possuem atributos que são meta-dados descrevendo suas características. Estes atributos podem ser listados com a função attributes() e definidos com a função attr(). Um desses atributos, bastante importante, é a classe de um objeto pois as funções de R usam essa informação para determinar como o objeto deve ser manipulado. A classe de um objeto pode ser lida ou alterada com a função class().

Existem os seguintes tipos de dados: Lógico ou booleano (logic), numérico (numeric), inteiro (integer), complexo (complex), caracter (character) e Raw.

Estes dados podem ser agrupados em estruturas de dados. Existem dois tipos fundamentais de estruturas: vetores atômicos e vetores genéricos. Vetores atômicos são matrizes de qualquer dimensão contendo um único tipo de dados. Vetores genéricos são também chamados de listas e são compostas por vetores atômicos. Listas são recursivas, no sentido de que podem conter outras listas.

Uma variável não precisa ser inicializada nem seu tipo declarado, sendo determinado implicitamente a partir do conteúdo do objeto. Seu tamanho é alterado dinamicamente.

Não existe o tipo “escalar” em R. Um escalar é simplesmente um vetor com um único elemento. Portanto a atribuição u <- 1 é apenas um atalho para u <- c(1).

Uma matriz é um vetor atômico acrescentado de um atributo dim com dois elementos (o número de linhas e de colunas). No exemplo seguinte, um vetor é transformado em uma matriz e depois recuperado como vetor:

> v <- 1:12
> print(v)
 [1]  1  2  3  4  5  6  7  8  9 10 11 12
> class(v)
[1] "integer"
> x <- c(1,2,3,4,5,6,7,8)
> class(x)
[1] "numeric"
> attr(v, "dim") <- c(2,6)
> print(v)
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    3    5    7    9   11
[2,]    2    4    6    8   10   12
> class(v)
[1] "matrix"
> # Um atributo arbitrário pode ser incluído
> attr(v, "nome") <- "minha matriz"

> attributes(v)
$dim
[1] 2 6
$nome
[1] "minha matriz"

> attr(v, "nome") <- NULL   # o atributo é removido
> attributes(v)
$dim
[1] 2 6
> # Um atributo pode ser alterado
> dim(v) <- c(3,4)
> print(v)
     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12
> # Removido o atributo "dim" v volta a ser um vetor
> attr(v, "dim") <- NULL
> v
 [1]  1  2  3  4  5  6  7  8  9 10 11 12

A atribuição v <- 1:4 é idêntica à v <- c(1:4) e análoga, mas não idêntica à v <- c(1, 2, 3, 4). Nos dois primeiros casos o resultado é um vetor de inteiros. No terceiro temos um vetor numérico (de ponto flutuante).

Existem funções para a marcação de atributos: dim(), dimnames(), names(), row.names(), class() e tsp() (usado para a criação de séries temporais). Estas funções são preferíveis à simplesmente usar attr(vetor, "atributo") porque fazem um tratamento e análise dos parâmetros usados, emitindo notificações de erros mais detalhadas.

A igualdade entre objetos atômicos pode ser testada com o uso do operador ==, que verifica recursivamente a identidade de cada um dos elementos dos objetos comparados, ou da função identical(), que verifica a igualdade completa entre os dois objetos.

> a <- c(1,3,5); b <- c(1,3,5); c <- c(1,2,5)
> a==b
[1] TRUE TRUE TRUE
> a==c
[1]  TRUE FALSE  TRUE
> identical(a,b)
[1] TRUE
> identical(a,c)
[1] FALSE

Listas e Data Frames

As listas são coleções de vetores atômicos, não necessariamente de mesmo tipo. Elas são recursivas no sentido de que podem ter outras listas como seus elementos. Data frames são listas onde todos os vetores possuem o mesmo comprimento. Muitas funções recebem listas como argumentos ou retornam listas.

Para exemplificar vamos usar uma lista contendo as 5 primeiras observações do data frame warpbreaks, com 3 variáveis.

> quebras <- head(warpbreaks, n=5)
> quebras
  breaks wool tension
1     26    A       L
2     30    A       L
3     54    A       L
4     25    A       L
5     70    A       L

> # Usamos unclass() para ver seus componentes
> unclass(quebras)
$breaks
[1] 26 30 54 25 70

$wool
[1] A A A A A
Levels: A B

$tension
[1] L L L L L
Levels: L M H

attr(,"row.names")
[1] 1 2 3 4 5

> # Usamos attributes() para ver seus atributos
> attributes(quebras)
$names
[1] "breaks"  "wool"    "tension"

$row.names
[1] 1 2 3 4 5

$class
[1] "data.frame"

A igualdade entre objetos não atômicos não é implementado com o operador ==. Neste caso é necessário usar a função identical(), que verifica a igualdade completa entre os dois objetos.

> u <- list(v1=1,v2=2); v <- u
> v==u
Error in v == u : comparison of these types is not implemented
> identical(u,v)
[1] TRUE

> # identical testa a identidade entre quaisquer dois objetos:
> f <- function(x,y) x+y; g <- function(x,y) x+y
> identical(f,g)
[1] TRUE

A função unclass() retorna uma cópia de seu argumento sem seus atributos de classe. attributes() retorna uma lista com os atributos de seu argumento.

Selecionar partes de uma lista é uma operação importante em R. Para ilustrar algumas operações vamos usar o data frame iris que é uma lista contendo 5 vetores atômicos. Ela contém os campos (ou observações) Sepal.Length, Sepal.Width, Petal.Length, Petal.Width e Species. Relembrando, a função unclass(iris) exibe todos os valores em cada campo e seus atributos, separadamente. A função attributes(iris) exibe apenas os atributos. No exemplo abaixo aplicamos o agrupamento K-means usando a função kmeans(). Em seguida exploramos o objeto retornado que é uma lista.

> head(iris, n=2)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa

> # Para selecionar apenas os 4 primeiros campos usamos iris[1:4]

> kGrupo <- kmeans(iris[1:4],3)
> typeof(kGrupo)    # para ver de que tipo é o objeto
[1] "list"
> length(kGrupo)    # kGrupo é uma lista com 9 elementos
[1] 9
> print(kGrupo)     # para listar todos os elementos do objeto
K-means clustering with 3 clusters of sizes 50, 38, 62

Cluster means:
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1     5.006000    3.428000     1.462000    0.246000
2     6.850000    3.073684     5.742105    2.071053
3     5.901613    2.748387     4.393548    1.433871

Clustering vector:
  [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 [31] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 2 3 3 3 3 3 3 3
 [61] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 3 3 3 3 3 3 3 3 3 3 3 3
 [91] 3 3 3 3 3 3 3 3 3 3 2 3 2 2 2 2 3 2 2 2 2 2 2 3 3 2 2 2 2 3
[121] 2 3 2 3 2 2 3 3 2 2 2 2 2 3 2 2 2 2 3 2 2 2 3 2 2 2 3 2 2 3

Within cluster sum of squares by cluster:
[1] 15.15100 23.87947 39.82097
 (between_SS / total_SS =  88.4 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss" "betweenss"
[7] "size"         "iter"         "ifault"

> str(kGrupo)
List of 9
 $ cluster     : int [1:150] 1 1 1 1 1 1 1 1 1 1 ...
 $ centers     : num [1:3, 1:4] 5.01 6.85 5.9 3.43 3.07 ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : chr [1:3] "1" "2" "3"
  .. ..$ : chr [1:4] "Sepal.Length" "Sepal.Width" "Petal.Length" "Petal.Width"
 $ totss       : num 681
 $ withinss    : num [1:3] 15.2 23.9 39.8
 $ tot.withinss: num 78.9
 $ betweenss   : num 603
 $ size        : int [1:3] 50 38 62
 $ iter        : int 2
 $ ifault      : int 0
 - attr(*, "class")= chr "kmeans"

> # A lista contém os seguintes atributos
> attributes(kGrupo)
$names
[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
[6] "betweenss"    "size"         "iter"         "ifault"

$class
[1] "kmeans"

> # sapply(objeto, class) exibe a classe de cada elemento na lista
> # A função sapply é tratada com maiores detalhes na próxima seção

> sapply(kGrupo, class)
     cluster      centers        totss     withinss tot.withinss    betweenss
   "integer"     "matrix"    "numeric"    "numeric"    "numeric"    "numeric"
        size         iter       ifault
   "integer"    "integer"    "integer"

> # Podemos visualizar simultaneamente o segundo elemento, "centers"
> # que fornece uma matriz com os valores do centro de cada agrupamento
> e size, 7º elemento, com o número de pontos em cada grupo

> kGrupo[c(2,7)]
$centers
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1     5.006000    3.428000     1.462000    0.246000
2     6.850000    3.073684     5.742105    2.071053
3     5.901613    2.748387     4.393548    1.433871

$size
[1] 50 38 62

> # Para visualizar o segundo componente da lista kGrupo,
> # que é uma matriz, usamos
> kGrupo[2]
$centers
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1     5.006000    3.428000     1.462000    0.246000
2     6.850000    3.073684     5.742105    2.071053
3     5.901613    2.748387     4.393548    1.433871

> # Para ver apenas os componentes desta matriz:
> kGrupo[[2]]
  Sepal.Length Sepal.Width Petal.Length Petal.Width
1     5.006000    3.428000     1.462000    0.246000
2     6.850000    3.073684     5.742105    2.071053
3     5.901613    2.748387     4.393548    1.433871

> # O mesmo resultado seria obtido por kGrupo$centers
> # Para listar a primeira linha da matriz
> kGrupo[[2]][,1]
       1        2        3
5.006000 6.850000 5.901613
> # Para listar a primeira coluna da matriz
> kGrupo[[2]][1,]
Sepal.Length  Sepal.Width Petal.Length  Petal.Width
       5.006        3.428        1.462        0.246
> # Para listar o primeiro elemento da primeira linha
> kGrupo[[2]][1,1]
[1] 5.006
> # que é o mesmo que kGrupo$centers[1,1]

Funções em R

Quase tudo em R é uma função. Até os operadores comuns são funções. A declaração 2 + 3 é, na verdade, uma forma sintética para "+"(2, 3).

> '+'(2,3)
[1] 5
> "*"(13, 9)
[1] 117

Em funções, parâmetros são passados por valor e não por referência. Isso significa que um objeto passado como parâmetro é copiado e a cópia passada para a função. O objeto original não é alterado. Além disso variáveis definidas no corpo de funções são locais e não podem ser usadas fora dela. Para tornar globa uma variável usada dentro de uma função podemos usar o operador de "super atribuição" <<-. Considere, por exemplo, o código abaixo.

> f <- function(x) x <- x^2
> u <- c(1,2,3)
> v <- f(u)
> v
[1] 1 4 9
> u
[1] 1 2 3

> #  x é local à função
> print(x)
Error: object 'x' not found

> # Se necessário tornar x global fazemos

> f <- function(x) x <<- x^2
> v <- f(x)
> x
[1] 1 4 9

Funções podem ser usadas recursivamente (ou sejam, podem fazer chamadas a si mesmas). Dois exemplos são mostrados abaixo: o primeiro calcula o fatorial de um inteiro, o segundo exibe a sequência de Fibonacci com n elementos.

> fatorial <- function(x) {
     if (x == 0) return (1)
     else return (x * fatorial(x-1))
 }
> fatorial(0)
[1] 1
> fatorial(6)
[1] 720

> # Obs.: a mesma função poderia ser definida em forma mais compacta como
> fatorial <- function(x) ifelse (x == 0, 1, x * fatorial(x-1))

> # A sequência de Fibonacci:
> fibonacci <- function(n) {
     if(n <= 0)  return("Nada")
     fib <- function(m) ifelse(m <= 1, m, fib(m-1) + fib(m-2))
     seq <- c(0)
     if (n>1) { for(i in 1:(n-1)) seq[i+1] <- fib(i) }
     print("Sequência de Fibonacci:")
     print(seq)
 }
 > fibonacci(9)
[1] "Sequência de Fibonacci:"
[1]  0  1  1  2  3  5  8  13  21

Funções apply(), lapply(), sapply(), tapply()

A função apply() recebe como argumentos uma coleção de objetos (data frame, lista, vetor, etc.), o parâmetro MARGIN (que informa onde será aplicada a função) e uma função (qualquer função pode ser usada). Ela serve para executar alguma operação sobre essa coleção. Seu objetivo é principalmente o de evitar o uso de laços ou loops. Ela tem a seguinte estrutura:

apply(X, MARGIN, FUN)
onde:
    x: uma matriz ou array
    MARGIN=n :  onde n = 1 ou 2, definindo onde a função será aplicada:
        se n=1: a função será aplicada nas linhas
        se n=2: função aplicada nas colunas
        se n=c(1,2): função aplicada nas linhas e colunas
    FUN: define função a ser usada.
         Podem ser funções internas (mean, median, sum, min, max, ...)
         ou definidas pelo usuário
> # Usando a matriz v, já definida:
> print(v)
     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12
> # A média das colunas
> apply(v,2,mean)
[1]  2  5  8 11
> # A soma das colunas
> apply(v,2,sum)
[1]  6 15 24 33
> minimosNasLinhas <- apply(v, 1, min)
> print(minimosNasLinhas)
[1] 1 2 3

A função lapply() recebe como argumentos uma coleção de objetos (data frame, lista, etc.) e uma função. Ela executa a função sobre todos os elementos da coleçao e retorna um objeto do tipo lista:

lapply(X, FUN)
onde:
    X: vetor, lista, data frame, ...
    FUN: Função a ser aplicada a cada elemento de X
         Podem ser funções internas ou definidas pelo usuário

Para exemplificar aplicaremos a função tolower() para reduzir a letras minúsculas todas as palavras de um vetor de caracteres:

> partes <- c("RODAS","MOTOR","CARBURADOR","PNEUS")
> partesMinuscula <- lapply(partes, tolower)
> print(partesMinuscula)
[[1]]
[1] "rodas"
[[2]]
[1] "motor"
[[3]]
[1] "carburador"
[[4]]
[1] "pneus"

> # Esta lista pode ser convertida em um vetor usando-se unlist:
> partesMinuscula <- unlist(partesMinuscula)
> print(partesMinuscula)
[1] "rodas"      "motor"      "carburador" "pneus"

A função sapply() recebe como argumentos uma coleção de objetos (data frame, lista, etc.) e uma função. Ela age da mesma forma que lapply() mas retorna um vetor ou uma matriz:

sapply(X, FUN)
onde:
    X: vetor, lista, data frame, ...
    FUN: Função a ser aplicada a cada elemento de X
         Podem ser funções internas ou definidas pelo usuário

Usaremos a função sapply() com o data frame cars que traz uma coleção de observações sobre velocidades e distâncias percorridas até repouso em cada velocidade em automóveis (em 1920) para encontrar os valores mínimos em cada coluna:

> # para ver a estrutura do data frame:
> str(cars)
 'data.frame':	50 obs. of  2 variables:
  $ speed: num  4 4 7 7 8 9 10 10 10 11 ...
  $ dist : num  2 10 4 22 16 10 18 26 34 17 ...
> lMinimos <- lapply(cars, max)
> sMinimos <- sapply(cars, max)
> print(lMinimos)
$speed
[1] 25
$dist
[1] 120

> print(sMinimos)
speed  dist
   25   120

O exemplo abaixo mostra o uso de lapply() e sapply() junto com uma função do usuário. Ela retorna os valores do data frame que estão abaixo da média em cada coluna. Neste caso elas retornam valores iguais, como se pode ver com o uso de identical():

> abaixoDaMedia <- function(x) {
                   media <- mean(x)
                   return(x[x < media])
                   }
> abaixoDaMedia(c(1,2,3,40,50))
 [1] 1 2 3
> minSapply <- sapply(cars, abaixoDaMedia)
> minLapply <- lapply(cars, abaixoDaMedia)
[1] TRUE
> minSapply
$speed
 [1]  4  4  7  7  8  9 10 10 10 11 11 12 12 12 12 13 13 13 13 14 14 14 14 15
[25] 15 15
$dist
 [1]  2 10  4 22 16 10 18 26 34 17 28 14 20 24 28 26 34 34 26 36 20 26 32 40
[25] 32 40 42 36 32

> # Os valores retornados são iguais (embora em objetos distintos):
> identical(minSapply, minLapply)
 [1] TRUE

A função tapply() calcula um valor usando uma função (mean, median, min, max, ...) sobre os dados de um objeto agrupados para cada valor de uma variável de fator dada.

tapply(X, INDEX, FUN = NULL)
onde:
    X: um objeto, geralmente um vetor
    INDEX: uma lista contendo fatores
    FUN: a função a ser aplicada sobre os elementos de X

Para ilustrar o uso desta função vamos usar o data frame irisCalculamos primeiro a média dos comprimentos de sépalas para todas as espécies. Depois calculamos as médias para cada espécie em separado, setosa, versicolor, virginica.

Em seguida usamos o data frame mtcars para calcular o consumo médio dos carros, agrupados por número de cilindros (cyl = 4, 6, 8) e tipos de transmissão, am = 0 (automático), 1 = (manual).

> attach(iris)
> # O comprimento médio de todas as sépalas é
> mean(Sepal.Length)
[1] 5.843333
> # O comprimento médio das sépalas agrupadas por espécie:
> tapply(Sepal.Length, Species, mean)
    setosa versicolor  virginica
     5.006      5.936      6.588
> detach(iris)
> # Usando mtcars:
> attach(mtcars)
> # O consumo médio para todos os carros é
> mean(mtcars$mpg)
[1] 20.09062
> # O consumo médio dos carros, agrupados por cilindros e tipo de transmissão
> tapply(mpg, list(cyl, am), mean)
       0        1
4 22.900 28.07500
6 19.125 20.56667
8 15.050 15.40000

> # Para efeito de conferência, calculamos a media de mpg para am=0 e cyl=8
> L <- mtcars[cyl==8 & am==0,]
> # L contém apenas carros com  am=0 e cyl=8
> mean(L$mpg)
[1] 15.05
> detach(mtcars)

Lembramos que em R os índices começam em 1 e não 0, como em muitas outras linguagens.

Ambientes (environments) e escopo

R armazena seus objetos em memória RAM dentro de ambientes ou environments. Um environment fica definido por uma lista que associa os nomes dos objetos nele carregados com seus valores. Eles existem principalmente para organizar estes objetos e a forma como R os encontra. Cada ambiente está ligado a um pai (um parent environment) fazendo com que os ambientes formem uma estrutura de árvore que termina no ambiente de nível mais alto que se chama R_EmptyEnv. Quando se inicia uma sessão o R se encontra no ambiente global, global environment, denominado R_GlobalEnv, também chamado de área de trabalho do usuário. Quando o nome de um objeto é invocado em código o interpretador de R busca na lista do ambiente atual, que pode ser visto com a função environment(). Se não encontrado o nome é procurado no ambiente pai, e assim sucessivamente, até que o último é alcançado.

Um novo ambiente pode ser criado com a função new.env() e objetos dentro deste ambiente com a função assign(). Estes objetos podem ser recuperados através da função get() ou da notação ambiente$variavel. A função exists("variavel", envir = ambiente) verifica a existência de variavel no ambiente, enquanto os objetos em um ambiente são listados com ls(ambiente), como se ilustra abaixo:

> environment()                            # exibe ambiente atual
<environment: R_GlobalEnv>
> var <- "este objeto está em Global Env"  # cria objeto em Global_Env
> novoEnv <- new.env()
> assign(var, "novo objeto em novoEnv", env=novoEnv)
> ls()
 [1] "novoEnv" "var"
> var
[1] "este objeto está em Global Env"
> get(var, env=novoEnv)
[1] "novo objeto em novoEnv"

> # A notação de "$" pode ser usada:
> novoEnv$var <- " outro valor para objeto em novoEnv"
> var
[1] " este objeto está em Global_env"
> novoEnv$var
[1] " outro valor para objeto em novoEnv"

> cat("var em global_env -->", var, "\nvar em novoEnv -->", novoEnv$var)
var em global_env --> este objeto está em Global Env
var em novoEnv --> novo objeto em novoEnv

> # Para ver o ambiente pai de novoEnv
> parent.env(novoEnv)
<environment: R_GlobalEnv>
> novoEnv$x <- 1   $ insere nova variável no ambiente
> ls(envir=novoEnv)
[1] "var" "x"
> exists("x", envir = novoEnv)
[1] TRUE

> # Um ambiente pode ser criado como filho de qualquer outro ambiente
> e2 <- new.env(parent = outroEnv)
> parent.env(e2)

> # Uma variável será criada em e2
> e2$teste <- 123
> # e2 é filho de novoEnv que está em R_GlobalEnv
> # A variável teste não será encontrada em R_GlobalEnv (pois reside em um nível abaixo)
> teste
Error: object 'teste' not found
> # O objeto está no ambiente e2
> e2$teste
[1] 123
> # Para testar se um objeto é um ambiente
> is.environment(e2)
[1] TRUE

> # Observe que a variável que contém o ambiente global é .GlobalEnv
> is.environment(.GlobalEnv)
[1] TRUE

> # Seu atributo name é "R_GlobalEnv"
> environmentName(environment())
[1] "R_GlobalEnv"

A função abaixo percorre os ambientes de modo hierárquico à partir de R_GlobalEnv subindo para os pais até o último ambiente, R_EmptyEnv que é o último ambiente, sem pai. A função search() exibe os ambientes na ordem hierárquica, a mesma ordem usada para a procura de um objeto.

> exibirArvore <- function() {
      a <- environment()
      repeat {        
          print(environmentName(a))
          if (environmentName(a) == "R_EmptyEnv") break
          a <- parent.env(a)
     }
 }
> exibirArvore()
[1] ""
[1] "R_GlobalEnv"
[1] "tools:rstudio"
[1] "package:stats"
[1] "package:graphics"
[1] "package:grDevices"
[1] "package:utils"
[1] "package:datasets"
[1] "package:methods"
[1] "Autoloads"
[1] "base"
[1] "R_EmptyEnv"

> # A função environment() permite descobrir em que
> # ambiente está uma função:
> environment(exibirArvore)
<environment: R_GlobalEnv>

> search()
 [1] ".GlobalEnv"        "tools:rstudio"     "package:stats"     "package:graphics" 
 [5] "package:grDevices" "package:utils"     "package:datasets"  "package:methods"  
 [9] "Autoloads"         "package:base" 	

Observe que a primeira chamada à função environmentName(a) retorna um string vazio, que é o nome do ambiente interno à função. Quando uma função é criada ela gera a criação de um ambiente próprio onde ficam as variáveis definidas em seu corpo. Para exemplificar a existência deste ambiente dedicado à definição da função criamos abaixo a função minhaFuncao(x) que retorna outra função que soma x ao seu argumento. O valor de x passado na definição da função não é alterado com uma definição de seu valor fora do corpo da função.

> f <- function() {
          x <- 1
          print(environment())
          print(parent.env(environment()))
 }
> f()
<environment: 0xd31ee80>
<environment: R_GlobalEnv>

> minhaFuncao <- function(x) { function(y) x+y }
> h <- minhaFuncao(100)
> h(10)
[1] 110
> x <- 3
> h(2)
[1] 102
> # Internamente ao ambiente de h, x = 100
> # h vive no ambiente environment(h).
> # Neste ambiente existe apenas a variável:
> ls(environment(h))
[1] "x"

No caso acima o R criou o ambiente "0xd31ee80" que é filho de R_GlobalEnv. A variável x só existe dentro do ambiente da função.

Uma função (e seu ambiente) podem ser colocados em qualquer outro ambiente usando-se a função environment(funcao ) <- outroAmbiente. No exemplo abaixo a variável anos é definida em .GlobalEnv e dentro do corpo da função quantosAnos. Três outras funções são definidas dentro desta primeira: anosLocal (que usa a variável local, a=10), anosGlobal (que usa a variável em globalEnv, a=10). Na execução da função semBusca a variável está localmente definida, a=1 e nenhuma busca é necessária para a sua execução.

> anos <- 100
> quantosAnos <- function() { 
      anos <- 10
      anosLocal <- function() { print( anos + 5 ) } 
      anosGlobal <- function() { print( anos + 5 ) } 
      semBusca <-  function() { anos <- 1; print( anos + 5 ) } 
      environment( anosGlobal ) <- .GlobalEnv 
      anosLocal() 
      anosGlobal()
      semBusca() 
  }
> quantosAnos()
[1] 15
[1] 105
[1] 6

O exemplo abaixo mostra que quando a função f1 é gerada seu ambiente foi armazenado junto com ela. Nele estão as variáveis a=2, b=3. Este ambiente fica inalterado mesmo depois que a variável global a foi alterado.

> funcaoSoma <- function(a, b) {
     f <- function(x) return( x + a + b )
     return( f )
 }
> a <- 2; b <- 3
> f1 <- funcaoSoma(a, b)
> f1(3)
[1] 8
> a <- 4
> f2 <- criarFuncao(a, b)
> f2(3)
[1] 10
> f1(3)
[1] 8
> # Para forçar a permanência de uma variável após
> # a conclusão da função usamos a atribuição "<<-"
> f <- function(){w<-13}
> f()  # não há retorno
> w
Error: object 'w' not found
> f <- function(){w<<-13}
> f()
> w
[1] 13

Para alocar explicitamente variáveis para um determinado ambiente, além da notação de "$" pode ser usado:

> ls(outroEnv) # o ambiente está vazio
character(0)
> with(outroEnv, {a <- 1; txt <- "texto" })
> ls(outroEnv)
[1] "a"   "txt"
> with(outroEnv, {print(a); print(txt)})
[1] 1
[1] "texto"
> # Alternativamente,
> outroEnv$a; outroEnv$txt
[1] 1
[1] "texto"

Se o nome da biblioteca onde está uma função é previamente conhecido é possível evitar a busca pela definição de uma função usando o operador ::. O mesmo procedimento pode ser usado para forçar o uso de uma função específica quando existem outras de mesmo nome definidas. Se o pacote não está carregado o operador ::: pode ser usado.

> x <- c(123, 234, 345, 242, 34, 100)
> stats::sd(x)
[1] 113.9731

> Wilks
Error: object 'Wilks' not found
> stats:::Wilks
> # ... A definição da função é exibida

> # Para verificar o que significa o operador :::
> `:::`
function (pkg, name) 
{
    pkg <- as.character(substitute(pkg))
    name <- as.character(substitute(name))
    get(name, envir = asNamespace(pkg), inherits = FALSE)
}


Decorre do que foi dito que o escopo de uma variável em R é o seguinte: a variável deve estar definida no ambiente local em que é usada ou em algum ambiente pai. Se variáveis com o mesmo nome estão definidas dentro da hierarquia de ambientes será usada aquela que for encontrada primeiro, ou seja, no ambiente de menor posição.

Muitas linguagens de programação não permitem (ou dsencorajam) o uso de variáveis globais pois elas podem tornar tornar o código mais frágil, sujeito a erros. No R elas podem ser usadas e funções podem acessar variáveis em ambientes acima delas. Mas essa não é sempre uma boa prática. Para projetos com algum nível de complexidade é recomendado que se passe todas as variáveis necessárias na definição da função ou se faça uma verificação rigorosa de escopos, oferecendo alternativas para o caso em que essas variáveios estão ausentes ou tenham tipos não apropriados. Caso as variáveis globais sejam usadas é uma boa prática dar a elas nomes identificadores tais como global.var para evitar que conflituem com outras definidas localmente.

Otimização e Pesquisa de Erros (debugging)

É possível pré-compilar uma função usando a biblioteca compiler (e sua função cmpfun()) que gera uma versão em byte-code. Nas linhas abaixo, fizemos uma medida dos tempos gastos nas funções f e sua versão pré-compilada g.

Algumas práticas podem ser aplicadas se um código estiver demorando muito para ser executado. Uma delas consiste em envolver o código a ser verificado com os comandos Rprof() e Rprof(NULL) e depois executar a função summaryRprof() para ver um resumo dos tempos gastos na execução de cada funcão.

> library(compiler)
> f <- function(n, x) { for (i in 1:n) x <- x + (1 + x)^(-1)}
> g <- cmpfun(f)
> medirTempos <- function() {
      Rprof()
      inicio <- Sys.time()
      f(10000000,1)
      duracao1 <- Sys.time() - inicio
      print(duracao1)
      inicio <- Sys.time()
      g(10000000,1)
      duracao2 <- Sys.time() - inicio
      print(duracao2)
      print(duracao1 - duracao2)
      Rprof(NULL)
      summaryRprof()
  }

> # Executamos a função para medir os tempos gastos  
> medirTempos()
Time difference of 1.003972 secs
Time difference of 0.9667881 secs
Time difference of 0.03718424 secs
$by.self
    self.time self.pct total.time total.pct
"f"      1.00    51.02       1.00     51.02
"g"      0.96    48.98       0.96     48.98

$by.total
              total.time total.pct self.time self.pct
"medirTempos"       1.96    100.00      0.00     0.00
"f"                 1.00     51.02      1.00    51.02
"g"                 0.96     48.98      0.96    48.98

$sample.interval
[1] 0.02

$sampling.time
[1] 1.96

A função compilada g é um pouco mais rápida que sua original. Em blocos maiores e mais demorados de código a diferença pode ser significativa.

Quando dados são importados para uma sessão de R sempre é uma boa prática ler apenas os campos necessários. Por exemplo, suponha que se deseje importar dados de uma tabela contido em um arquivo de texto arquivo.txt que contém 5 variáveis, a primeira de caracter e as 4 demais numéricas, mas apenas as duas primeiras serão usadas. A importação seletiva pode ser obtida usando-se o parâmetro colClasses. Colunas associadas com NULL serão ignoradas:

> # Para importar todos os dados usamos:
> dfLeitura <- read.table(arquivo.txt, header=TRUE, sep=',')
> # Seria mais eficiente e rápido selecionar apenas os campos desejados:
> dfLeitura <- read.table(arquivo.txt, header=TRUE, sep=',',
               colClasses=c("character", "numeric", NULL, NULL, NULL))

A execução de uma operação vetorializada é mais ágil do que percorrer um laço sobre os elementos de um vetor ou matriz. Isso é obtido com o uso de funções projetadas para lidar com vetores de forma otimizada. Alguns exemplo na instalação básica são as funções colSums(), colMeans(), rowSums(),
e rowMeans()
. O pacote matrixStats, plyr, dplyr, reshape2, data.table também incluem diversas funções otimizadas para esse tipo de operação.

Para mostrar isso usamos, desta vez, a função system.time(operação) que mede o tempo de execução da operação.

> partes <- 1:100000000
> soma1 <- function(x) print(sum(x))
> soma2 <- function(x) {
     s <- 0
     for (u in x) s <- s + u
     print(s)
 }

> system.time(soma1(partes))
[1] 5e+15
   user  system elapsed 
      0       0       0 
> system.time(soma2(partes))
[1] 5e+15
   user  system elapsed 
  4.775   0.000   4.775 

Em outro exemplo fazemos a soma dos elementos de uma matriz com 1000 colunas e 1000 linhas (portanto com 1 milhão de elementos).

> set.seed(1234)
> # Cria uma matriz 10000 x 10000
> matriz <- matrix(rnorm(100000000), ncol=10000)
> # Cria função para somar elementos de cada coluna
> somar <- function(x) {
           somando <- numeric(ncol(x))
           for (i in 1:ncol(x)) {
               for (k in 1:nrow(x)) {
                   somando[i] <- somando[i] + x[k,i]
               }
           }
  }
> # Executa a função e mede o tempo gasto
> system.time(somar(matriz))
   user  system elapsed 
 17.231   0.000  17.230
> # mede o tempo de execução de colSums  
> system.time(colSums(matriz))
   user  system elapsed 
  0.108   0.000   0.107 

Como vimos o cálculo é realizado aproximadamente 160 vezes mais rapidamente pela função vetorializada. Essa diferença pode ser muito maior, dependendo da situação analisada.

Sempre é mais eficiente inicializar um objeto em seu tamanho final e depois preenchê-lo de que partir de um objeto vazio e ajustar seu tamanho progressivamente.

> set.seed(1234)
> u <- rnorm(1000000)
> uQuadrado <- 0
> system.time(for (i in 1:length(u)) uQuadrado[i] <- u[i]^2)
   user  system elapsed 
  0.361   0.000   0.361

> # Tempo de execução para a mesma operação com
> # a variável inicializada em seu tamenho final   
> rm(uQuadrado)
> uQuadrado <- numeric(length=1000000)
> system.time(for (i in 1:length(u)) uQuadrado[i] <- u[i]^2)
   user  system elapsed 
   0.11    0.00    0.11

> # Usando a função vetorializada
> uQuadrado <- numeric(length=1000000)
> system.time(uQuadrado <- u^2)
   user  system elapsed 
  0.002   0.000   0.001     

A operação é muito mais rápida quando se usa a função vetorializada. Além da exponenciação, as funções adição, multiplicação e outras operações binárias do tipo são todas vetorializadas.

Gerenciamento de memória

Como já mencionado, R mantém em memória RAM todos os seus objetos em uso, o que pode introduzir lentidão ou mesmo a impossibilidade de realizar alguma operação. Mensagens de erro sobre a insuficiência de espaço de memória indicam que o limite foi excedido. Este limite depende, é claro, do hardware usado, do sistema operacional e da compilação de R (a versão de 64 bits é mais eficiente). Para grandes volumes de dados é preciso procurar escrever um código eficiente para acelerar a execução com o eventual armazenando dados em meio externo para diminuir a sobrecarga na memória RAM e através do uso de rotinas estatísticas especializadas, escritas para maximar a eficiência no manipulação de dados.

Para uma programação mais eficiente é recomendável aplicar operações sobre vetores sempre que possível. As funções internas para manipulação vetores, matrizes e listas (tais como ifelse, colMeans e rowSums) são mais eficientes que loops (for e while). Matrizes usam menos recursos que data frames. No uso de read.table() para carregar dados externos para um data frame especifique as opções colClasses e nrows explicitamente, defina comment.char = "" e marque como NULL as colunas não necessárias. Ao ler dados externos para uma matriz, use a função scan().

Como mencionado, sempre que possível crie objetos com seu tamanho final ao invés de aumentar seu tamanho gradualmente, inserindo valores. Teste seu código usando uma amostra de dados menor para otimizá-lo e remover erros. Exclua objetos temporários ou desnecessários usando rm(objeto). Após a remoção use gc() para iniciar a coleta de lixo. Use a função .ls.objects() para listar objetos no espaço de trabalho e encontrar o que ocupa mais memória e o que pode ser removido.

Use as funções Rprof(), summaryRprof() e system.time() para cronometrar o tempo e gasto em cada função e descobrir qual delas você deveria procurar otimizar. Rotinas externas compiladas podem ajudar a acelerar a execução do programa. Com o pacote Rcpp você pode transferir objetos de R para funções C++ e voltar quando são necessárias sub-rotinas otimizadas.

Para volumes de dados for muito grandes existem bibliotecas que incluem a funcionalidade de descarregar dados em bancos de dados externos ou arquivos binários simples e acessar parte deles. Alguns exemplos são:

Biblioteca Descrição
bigmemory grava e acessa matrizes em arquivos no disco.
ff fornece estruturas de dados que podem ser grabadas em disco, agindo como se permanecessem em RAM.
filehash implementa uma base de dados simples tipo chave-valor gravada em disco
ncdf, ncdf4 fornece interface para arquivos Unidata netCDF
RODBC, RMySQL, ROracle, RPostgreSQL, RSQLite acesso aos respectivos DBMS externos.

No que se refere à análise dos dados em grandes volumes estão disponíveis:

Pacotes biglm e speedglm: ajuste de modelos lineares lineares e generalizados para grandes conjuntos de dados de uma maneira eficiente em termos de memória. Incluem as funções lm() e glm() para lidar com grandes conjuntos de dados.

Diversos pacotes oferecem funções para operações sobre grandes matrizes produzidas pelo pacote bigmemory. biganalytics oferece agrupamento k-means, estatísticas de coluna e um wrapper para biglm. O pacote bigrf pode ser usado para se adequar às florestas de classificação e regressão. bigtabulate fornece funcionalidade table(), split() e tapply(). O pacote bigalgebra inclui funções avançadas da álgebra linear.

biglars oferece cálculo de regressão para conjuntos grande, usado juntamente com o pacote ff.

O pacote data.table introduz uma versão melhorada de um data frame, com métodos mais rápidos e eficientes para: agregação de dados; junções de intervalo; adição, modificação e exclusão de colunas por referência (sem cópias). Um data.table pode ser usado em qualquer função que receba um data frame como argumento.

Depuração de Erros (debugging)

Qualquer projeto de programação com algum grau de complexidade está sujeito a erros. Depuração de erros ou debugging é o processo de se encontrar e resolver as falhas no código. Por mais interessante que seja escrever um bloco de código para resolver algum problema, encontrar erros pode ser tedioso e demorado. Existem erros que impedem a execução do código causando a emissão de mensagens de erros. Estes são, em geral, os mais fáceis de se encontrar. Mas também existem situações em que o código roda perfeitamente mas produz resultados inesperados e incorretos.

As táticas de debugging envolvem rodar as linhas de código interativamente verificando o valor das variáveis, testar o efeito sobre um conjunto de dados que produzem resultados conhecidos, análise do fluxo do código e do estado da memória a cada instante da execução.

Na programação em R erros são geralmente causados por digitação incorreta do nome de variáveis ou funções e chamadas à funções com parâmetros de tipo incorretos, inclusive quando objetos importados de fontes externas contém partes que são NULL, NaN ou NA e são passados como parâmetros para funções que não fazem a verificação para a existência desses valores.

Função Efeito
debug() Marca uma função para debugging.
undebug() Desmarca uma função para debugging.
browser() Permite percorrer o código de execução de uma função passo a passo.
trace() Modifica a função para permite a inserção temporária de de código auxiliar.
untrace() Cancela a função anterior e remove o código temporário.
traceback() Imprime a sequência de chamadas a funções que produziram o último erro não capturado.

Durante a depuração com o uso de browser() a tecla executa a linha sob o cursor e passa o foco para a próxima linha. Teclar força a execução até o final da função sem pausas. Digitar exible a pilha de execução (call stack) e interrompe a execução e desloca o foco para o nível imediatamente superior. Também é possível usar comandos como ls(), print() e atribuições no prompt do depurador.

Atualizando R e suas Bibliotecas

A atualização de R pode ser um pouco trabalhosa. Seguem algumas sugestões para usuários de Windows e Linux.

No Windows

Uma forma possível e prática para atualizar a instalação do R no Windows consiste em usar a biblioteca installr. Para isso a bliblioteca deve ser instalada e executada de dentro do próprio console (ou do Rstudio, ou outra IDE).

Como eu não utilizo o Windows esta opção está mencionada aqui como uma sugestão, que eu não experimentei. Ela foi extraída da página R-statistics blog.
> # instalando e carregando a biblioteca
> install.packages("installr")
> require(installr)
> updateR()

A função updateR() iniciará o processo de atualização, verificando se novas versões estão disponíveis. Se a versão instalada for a mais recente a função termina e retorna FALSE. Caso contrário será perguntado se o usuário deseja prosseguir, após a exibição de um sumário das novidades na versão.

Será oferecida ao usuário a opção de copiar as bibliotecas instaladas para a nova versão e, em seguida, a de atualizar estas bibliotecas.

Mac e Linux

A atualização pode ser feita manualmente usando pacotes no website da CRAN.

$ sudo apt-key adv --keyserver keyserver.ubuntu.com
                   --recv-keys E298A3A825C0D65DFD57CBB651716619E084DAB9
$ sudo add-apt-repository
       'deb https://cloud.r-project.org/bin/linux/ubuntu bionic-cran35/'
$ sudo apt update
$ sudo apt install r-base

As linhas acima, para cada entrada no prompt, não devem ser quebradas.

Mais informações sobre instalações no Debian, Red Hat, SUSE e Ubuntu no site See CRAN-R Linux.

Para compilar à partir do código fonte consulte a página CRAN-R Installation and Administration.

Atualizando as bibliotecas

Tanto no Windows quanto no Linux para atualizar apenas as bibliotecas que foram instaladas com install.packages() basta usar, no console a função update.packages(). A função perguntará quais as bibliotecas você deseja atualizar. Para executar a atualização de todas elas, sem o prompt de consulta digite update.packages(ask = FALSE).

Além de update.packages() existem as funções old.packages() que informa quais as bibliotecas possuem versões mais atuais nos repositórios versões aplicáveis e new.packages() que procura por novas bibliotecas disponíveis e ainda não instaladas, oferecendo a opção de instalá-las.

Obs.: Pacotes instalados por devtools::install_github() não são atualizados pelos procedimento descritos. No Windows eles podem ser atualizados por installr.

No RStudio

Para atualizar o RStudio use o item de Menu: Help > Check for Updates. Para atualizar as bibliotecas use Menu: Tools > Check for Packages updates.


Aquisição de Dados

Matrizes e Arrays

Matrizes

Para uma revisão sobre matrizes na Álgebra Linear veja Matrizes

Uma matriz é um objeto de duas dimensões, um conjunto de elementos organizados em linhas e colunas. Todos os componentes de uma matriz devem ser do mesmo tipo. Uma matriz Mm×n contém m linhas e n colunas. Em R uma matriz pode ser criada à partir de um vetor, através da função matrix():

> # Criando uma matriz de 4 linhas e 5 colunas
> n <- matrix(1:20, nrow=4, ncol=5) 
> n
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    5    9   13   17
[2,]    2    6   10   14   18
[3,]    3    7   11   15   19
[4,]    4    8   12   16   20
> # Por default as colunas são preenchidas por colunas.
> # Para preencher por linhas fazemos
> m <- matrix(1:20, nrow=4, ncol=5, byrow=TRUE) 
> m
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    2    3    4    5
[2,]    6    7    8    9   10
[3,]   11   12   13   14   15
[4,]   16   17   18   19   20
> # Os elementos podem ser lidos diretamente
> m[3,4]
[1] 14
> # A n-ésima linha pode ser obtida (fazendo n=2)
> m[2,]
[1]  6  7  8  9 10
> # A n-ésima coluna pode ser obtida (n=3)
> m[,3]
[1]  3  8 13 18
> # O segundo elemento da terceira coluna é
> m[,3][2]
[1] 8
> # O mesmo que 
> m[2,3]
[1] 8
> # Mais de uma coluna pode ser extraída
> # Extraindo a 1a e 3a coluna da matriz m
> m [, c(1,3)]
     [,1] [,2]
[1,]    1    3
[2,]    6    8
[3,]   11   13
[4,]   16   18
> # Uma matrix também pode ser declarada sem ter seus elementos definidos
> matr <- matrix(nrow=3,ncol=2)
> matr
     [,1] [,2]
[1,]   NA   NA
[2,]   NA   NA
[3,]   NA   NA
> # NA é a forma de R representar que o valor não está disponível
> # A matrix  pode ser populada em seguida:
> matr[1,1] <- 1
> matr[1,2] <- 4     # etc.
> # Matrizes, como vetores, possuem atributos.
> attributes(m)
$ dim
[1] 4 5
> # A matriz m possui apenas o atributo dimensão
> dim(m)
[1] 4 5
> # 4 linhas e 5 colunas.
> # Uma matriz pode ser criada associando-se dimensões à um vetor
> k <- 1:10
> k
[1]  1  2  3  4  5  6  7  8  9 10
> dim(k) <- c(2,5)
> k
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    3    5    7    9
[2,]    2    4    6    8   10
> # Finalmente matrizes podem ser criadas por agrupamento de colunas
> x <- 1:3
> y <- 10:12
> cbind(x,y)
     x  y
[1,] 1 10
[2,] 2 11
[3,] 3 12
> # ou agrupamento de linhas
> rbind(x,y)
  [,1] [,2] [,3]
x    1    2    3
y   10   11   12
> # Os argumentos de rbind e cbind são vetores ou matrizes.
> # Por ex., se xy = rbind(x,y) então
> rbind(x,xy)
  [,1] [,2] [,3]
x    1    2    3
x    1    2    3
y   10   11   12
> # As dimensões devem ser compatíveis ou os argumentos podem ser truncados
> rbind(xy, 100:104)
  [,1] [,2] [,3]
x    1    2    3
y   10   11   12
   100  101  102
Warning message:
In rbind(xy, 100:104) :
  number of columns of result is not a multiple of vector length (arg 2)
> # Uma mensagem de erro foi gerada.
>
> # Uma matrix pode ser criada com atributos m linhas, n colunas
> # juntamente com nomes para as linhas e colunas
> celulas <- c(11, 12, 21, 22)
> nomesLinhas <- c("Linha 1", "Linha 2")
> nomesColunas <- c("Coluna 1", "Coluna 2")
> matriz <- matrix(celulas, nrow=2, ncol=2, byrow=TRUE,
                   dimnames=list(nomesLinhas, nomesColunas))
> matriz
        Coluna 1 Coluna 2
Linha 1       11       12
Linha 2       21       22
>
> # Esta matriz tem atributos dim (dimensões 2X2) e dimnames
> attributes(matriz)
$dim
[1] 2 2

$dimnames
$dimnames[[1]]
[1] "Linha 1" "Linha 2"

$dimnames[[2]]
[1] "Coluna 1" "Coluna 2"
> # Os nomes das linhas e colunas podem ser lidos e alterados
> colnames(matriz)
[1] "Coluna 1" "Coluna 2"
> colnames(matriz) <- c("col1", "col2")
> row.names(matriz)
[1] "Linha 1" "Linha 2"
> row.names(matriz) <- c("lin1" , "lin2")
> # Agora a matriz tem novos atributos names
> matriz
     col1 col2
lin1   11   12
lin2   21   22
> # O nome de uma linha (ou coluna) pode ser alterado isoladamente
> row.names(matriz)[2] <- "linha dois"
> matriz
           col1 col2
lin1         11   12
linha dois   21   22

Os seguintes funções foram usadas para a criação, acesso e manipulação de matrizes:

Função Efeito
M = matrix(vetor, nrow=m, ncol=n, byrow=TRUE) cria matriz Mm x n usando os elementos do vetor, distribuídos por linhas
matriz[m,n] retorna o elemento Mm,n
matriz[m,n] <- q associa o valor q ao elemento Mm,n
matriz[m,] retorna a m-ésima linha da matriz M
matriz[,n] retorna a n-ésima coluna da matriz M
attributes(M) exibe atributos da matriz M
rbind(x, y) junta por linhas os objetos x, y
cbind(x, y) junta por colunas os objetos x, y
colnames(M) exibe (ou atribue) nomes para as colunas de M
row.names(M) exibe (ou atribue) nomes para as linhas de M

Arrays

Arrays são generalizações dos objetos matrizes e são manipulados de forma semelhante. Um array de dimensões 2 × 3 × 5 pode ser visto como uma coleção de 5 matrizes 2 × 3. Para efeito de sua representação no console elas são representadas exatamente desta forma.

> arr <- array(1:12, c(2, 3, 2))
> arr
, , 1

     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6

, , 2

     [,1] [,2] [,3]
[1,]    7    9   11
[2,]    8   10   12
> # Como vemos acima o componente [,, 2] é uma matriz 2 por 3
> arr[,,2]
     [,1] [,2] [,3]
[1,]    7    9   11
[2,]    8   10   12
> # que tem na primeira linha e segunda coluna o valor
> arr[,,2][1,2]
[1] 9
>
> # Um array pode ser construído com seus atributos names
> # Um ex, a terceira dimensão do array representa valores em anos diversos
> dim1 <- c("Filho 1","Filho 2")
> dim2 <- c("Educação", "Saúde", "Alimentação")
> dim3 <- c("2010", "2011")
> Despesas <- array(, c(2, 3, 2), dimnames=list(dim1, dim2, dim3))
> # Todos os elementos são 'NA'Despesas
> Despesas[1,1,1]<-90.80
> Despesas[1,1,2]<-98.80
> Despesas
, , 2010

        Educação Saúde Alimentação
Filho 1     90.8    NA          NA
Filho 2       NA    NA          NA

, , 2011

        Educação Saúde Alimentação
Filho 1     98.8    NA          NA
Filho 2       NA    NA          NA

> Despesas["Filho 1",,]
            2010 2011
Educação    90.8 98.8
Saúde         NA   NA
Alimentação   NA   NA

> Despesas["Filho 1","Saúde","2010"]<-456
> Despesas["Filho 1","Saúde","2010"]
[1] 456
> Despesas["Filho 1",,]
             2010 2011
Educação     90.8 98.8
Saúde       456.0   NA
Alimentação    NA   NA


Listas e fatores.

Tipos de dados, variáveis e vetores

Tipos de dados

No R existem os seguintes tipos de dados e variáveis:

Tipo de Dado Exemplo
Lógico (Logic) TRUE, FALSE
Numérico (Numeric) 12.3, 5, 999
Inteiro (Integer) 2L, 34L, 0L
Complexo (Complex) 1 + 2i
Caracter (Character) ‘a’, “Bom dia”, “TRUE”, ‘23.4’
(Raw) “Bom dia” é armazenado como 42 6f 6d 20 64 69 61
*Em R não existem, de fato, escalares. Eles são representados por vetores com um único componente.

Com estes dados (e variáveis) se pode construir objetos predefinidos e mais complexos: eles são os escalares* (scalars), vetores (vectors), matrizes (matrices), arrays (matrizes em dimensões maiores que m × n), data frames e listas (lists).

Observe que R é sensível ao caso. TESTE, Teste e teste são variáveis diferentes. Os valores lógicos devem ser escritos em maiúsculas (mas podem ser abreviados para T e F).

Com frequência mencionaremos o nome de uma entidade em inglês para facilitar a leitura dos textos abundantes na internet. Em alguns casos podemos manter a nomenclatura original, em inglês.

Variáveis e vetores

e <- 154 # um escalar, numérico
l <- "pedro" # um escalar, caracter

a <- c(1, 3, 9.5, 6, -2, 4) # vetor numérico
b <- c("um", "dois", "três") # vetor de caracter
c <- c(TRUE, TRUE, FALSE, TRUE, FALSE) # vetor lógico

Todos os componentes de um vetor devem ser do mesmo tipo.

> # um escalar pode ser criado assim:
> x <- 10
> x
[1] 10
> print(x)
[1] 10
> # um vetor é criado usando o operador c()
> vetor_numerico <- c(1.2, 3.4, 5, 7, 8)
> vetor_numerico
[1] 1.2 3.4 5.0 7.0 8.0
> vetor_string <- c("a", "b", "c", "d")
> vetor_string
[1] "a" "b" "c" "d"
> vetor_logico <- c(FALSE, TRUE, TRUE)
> vetor_logico
[1] FALSE  TRUE  TRUE
> vetor_inteiros <- c(12L, 15L, 18L)
> vetor_inteiros
[1] 12 15 18
> # a classe de um objeto pode ser consultada com a função class()
> class(vetor_inteiros)
[1] "integer"
> class(vetor_logico)
[1] "logical"
> # O primeiro componente do vetor_numerico é
> vetor_numerico[1]
[1] 1.2
> # Seu comprimento
> length(vetor_numerico)
[1] 5
> # Podemos então obter seu último componente
> vetor_numerico[length(vetor_numerico)]
[1] 8
> # O terceiro e o quinto componentes são
> vetor_numerico[c(3, 5)] # 2nd and 4th elements of vector
[1] 5 8
> # O vetor v = (1, 2, 3, 4, 5, 6) pode ser criado com
> v <- c(1:6)
> v
[1] 1 2 3 4 5 6
> # Desta forma podemos obter 4 (por ex.)
> # elementos sequenciados de vetor_numerico
> vetor_numerico[(1:4)]
[1] 1.2 3.4 5.0 7.0
# Um componente pode ser alterado a qualquer momento
> vetor_numerico[1] <- 0
> vetor_numerico
[1] 0.0 3.4 5.0 7.0 8.0
# Novos componentes podem ser inseridos
> vetor_numerico[6] <- 203
> vetor_numerico
[1]   0   3   5   7   8 203
> Observe que o operador c concatena vetores
> frutas <- c("maçã","laranja")
> verduras <- c("alface", "brocolis","couve")
> c(frutas, verduras)
[1] "maçã"  "laranja"  "alface"  "brocolis"  "couve"

> # Em R um objeto é dinâmicamente modificado para acomodar uma atribuição
> w <- 1:3
> w[7] <- 17
> w
[1]  1  2  3 NA NA NA 17
NA é uma constante interna representando um valor ausente ou não disponível, Not Available.

Diferente do que ocorre na maioria das linguagens de programação os índices são contados à partir de 1 (e não 0).

Usamos os seguintes novos operadores:

Operador Efeito
v <- c(1:6) cria* o vetor v = (1, 2, 3, 4, 5, 6)
v <- c(a1, ..., an) cria o vetor v = (a1, ..., an), de comprimento n
v[r] retorna o r-ésimo componente, ar, se r ≤ n, NA caso contrário
v[c(p, q)] retorna componentes ap e aq
v[r] <- 5 substitui o valor de ar se r ≤ n, insere caso contrário
lenght(v) retorna o comprimento do vetor, lenght(v) = n neste caso.
*Mencionamos que o operador c concatena vetores.

Como já foi usado acima, podemos filtrar um vetor para pegar apenas alguns de seus componentes. Para ilustrar esta operação vamos criar um longo vetor e, a partir dele construir outro vetor apenas com algumas de suas componentes:

> u <- 1:10 # forma resumida de c(1:10)
> filtro <- c(T,T,T,F,F,F,T,T,T,F) # resumindo T = TRUE, F = FALSE
> u[filtro]
[1] 1 2 3 7 8 9
> # Observe ainda que podemos excluir o terceiro elemento
> u[-3]
[1]  1  2  4  5  6  7  8  9 10
> # ou todos os elementos entre o terceiro e o quinto, inclusive
> u[-3:-5]
[1]  1  2  6  7  8  9 10
> # Podemos atribuir este vetor filtrado a outra variável, para uso futuro
> novo_u <- u[-3:-5]
> # e remover o antigo, caso ele não seja mais necessário
> rm(u)
> # Se um valor for inserido o vetor muda de comprimento
> u[12] <- 100
> u
[1] 1 2 3 4 5 6 7 8 9 10 NA 100
> # A posição 11 fica indeterminada (NA)

> # A função seq fornece uma forma adicional de criar um vetor
> # onde se fornece os valores inicial e final e o acréscimo (ou passo)
> v2 <- seq(from=0, to=3, by=.25)
> v2
[1] 0.00 0.25 0.50 0.75 1.00 1.25 1.50 1.75 2.00 2.25 2.50 2.75 3.00
> # Um vetor pode ser repetido dentro de outro vetor maior
> v <- 1:3    # um shortcut para c(1:3)
> rep(v, times=3)
[1] 1 2 3 1 2 3 1 2 3
# Para repetir cada componente 2 vezes
> rep(v, each=2)
[1] 1 1 2 2 3 3
> # Podemos especificar quantas vezes repetir cada componente
> # No ex. abaixo repetir o primeiro componente 4 x, o segundo 2
> rep(c(0, 7), times = c(4,2))
[1] 0 0 0 0 7 7
> rep(v, times = c(1,0,3))
[1] 1 3 3 3
> # Repetir um vetor até obter outro de comprimento length.out
> rep(v,length.out=7)
[1] 1 2 3 1 2 3 1
> # Além das funções rep e seq podemos usar:
> # any: testa se algum elemento do vetor satisfaz uma condição
> any(v > 2)
[1] TRUE
>  any(v >= 4)
[1] FALSE
> # all: testa se todos os elementos do vetor satisfazem uma condição
> all(v > 2)
[1] FALSE
>  all(v >= 0)
[1] TRUE

Resumindo, usamos:

Função Efeito
rep(v, times = n) repete o vetor v n vezes
rep(v, times = c(r, s)) repete o primeiro componente r vezes, o segundo s vezes
rep(v, each = n) repete o vetor v, cada componente n vezes
rep(v, lenght.out = l) repete o vetor v até atingir um vetor de comprimento l
any(v condição) verifica se algum componente de v satisfaz à condição
all(v condição) verifica se todos os componentes de v satisfazem à condição

Um vetor vazio pode ser criado através da função vector(). O vetor pode ser populado em seguida. Além disso vetores possuem o atributo names que é também um vetor. Ele permite atribuir nomes aos componentes do vetor, como mostrado abaixo:

> # Cria um vetor vazio (inicialmente um vetor lógico)
> v <- vector()
> v
logical(0)
> # Popula o vetor v
> v[1] <- 1
> v[2] <- 2
> v[3] <- 3
> v
[1] 1 2 3
> is.vector(v)
[1] TRUE
# Nenhum valor foi associado a names
> names(v)
NULL
> # Popula o vetor names associado a v
> names(v)[1] <- "primeiro"
> names(v)[2] <- "segundo"
> names(v)[3] <- "terceiro"
> v
primeiro  segundo terceiro
       1        2        3
> # Alternativamente
> names(v) <- c("um", "dois" ,"três")
> v
  um dois três
   1    2    3
> # O vetor continua sendo um vetor numérico
> class(v)
[1] "numeric"
> # Um vetor pode ser criado com seu atributo names definido
> v <- c('a'=1, 'b'=2, 'c'=3)
> names(v)
[1] "a" "b" "c"
> # As aspas acima são opcionais.
> # O mesmo efeito seria obtido com v <- c(a=1, b=2, c=3)
> v
a b c
1 2 3
> # Como dito, names é um vetor. Seu primeiro componente é
> names(v)[1]
[1] "a"
> # É possível obter o componente do vetor pelo seu atributo
> pessoa <- c("nome"="Pedro","Sobrenome"="Malazartes")
> pessoa["nome"]
   nome
"Pedro"

Regras de reciclagem (recycling)

Vetores que aparecem um uma expressão não precisam, necessariamente, ter o mesmo comprimento. Caso um dos vetores seja de menor comprimento que o outro (ou outros) o resultado da expressão terá o comprimento do maior deles e os vetores menores serão reciclados (recycled). Na reciclagem eles são repetidos quantas vezes for necessário, podendo ser fracionados. Uma constante (um vetor de um elemento) é simplesmente repetido.

> u <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
> v <- c(1, 2, 3)
> u + v
[1]  2  4  6  5  7  9  8 10  12  11
> # v é reciclado para v' = (1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2)
> u + 10
 [1] 11 12 13 14 15 16 17 18 19 20


Matrizes e Arrays.

Estudos sobre o Software Estatístico R

Introdução ao R

R é uma linguagem de programação especialmente voltada para o tratamento de dados e estatística. É um software livre e de código aberto (free and open source), desenvolvido pelos estatísticos Ross Ilaka e Robert Gentleman a partir de 1993, inicialmente como um subconjunto da linguagem S. Em sua instalação básica R contém um grande número de funções estatísticas (modelagem linear e não linear, testes estatísticos clássicos, análise de séries temporais, classificação, agrupamento e muitos outros) e voltadas para a geração gráficos amplamente customizáveis. Além disso sua funcionalidade pode ser estendida por meio da instalação de pacotes (packages) muitos deles criados e disponibilizados por usuários. Tanto os pacotes de instalação como as extensões podem ser encontradas no site CRAN, The Comprehensive R Archive Network.

Pode-se interagir com R diretamente através de comandos de linha em um terminal ou por meio de alguns IDEs (ambientse de desenvolvimento integrados). Neste estudo usaremos o RStudio que pode ser baixado neste link.

Ao ser iniciado o ambiente do RStudio vemos uma informação de licença e algumas instruções básicas de uso. O prompt de comando é representado por um sinal (>), onde podemos inserir os comandos. Por exemplo, o comando help() abre as páginas de ajuda que aparecem, por default, no canto inferior esquerdo da tela. O comando help.start() acessa páginas html em CRAN e q() encerra a sessão do Studio. Um histórico dos comandos usados fica armazenado na pasta de trabalho em um arquivo oculto .Rhistory. Durante a sessão, ou quando ela é reiniciada, estes comandos ficam disponíveis e podem ser acessados usando as setas e do teclado.

R version 3.5.1 (2018-07-02) -- "Feather Spray"
Copyright (C) 2018 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu (64-bit)

R is free software and comes with ABSOLUTELY NO WARRANTY.
You are welcome to redistribute it under certain conditions.
Type 'license()' or 'licence()' for distribution details.

  Natural language support but running in an English locale

R is a collaborative project with many contributors.
Type 'contributors()' for more information and
'citation()' on how to cite R or R packages in publications.

Type 'demo()' for some demos, 'help()' for on-line help, or
'help.start()' for an HTML browser interface to help.
Type 'q()' to quit R.

[Workspace loaded from ~/Projetos/R/.RData]

Usaremos o console e suas respostas para introduzir alguns conceitos. As instruções contendo atribuições e funções podem ser separadas por linhas ou por ponto-e-vírgula (;). Os comentários em R, que são linhas que são ignoradas pelo interpretador, são marcados uma linha de cada vez, com o sinal #.

> # Escolher uma pasta de trabalho
> setwd("/home/usuario/Projetos/R")
> # Verificar a atual pasta de trabalho
> getwd()
[1] "/home/usuario/Projetos/R"
> # Criar uma nova pasta de trabalho
> dir.create("teste")
> setwd("/home/usuario/Projetos/R/teste")
> getwd()
[1] "/home/usuario/Projetos/teste"
> # Atribuição de valor à uma variável
> x <- 1; y <- 2
> # Operações podem ser feitas interativamente
> x + y
[1] 3
> # Se o código estiver rodando de dentro de um arquivo
> print(x + y)
[1] 3
> # O estado da atual sessão pode ser gravado
> save.image("sessao1.R")
> # A sessão pode ser mais tarde recuperada com o comando
> load("/home/usuario/Projetos/teste/sessao1.R")

A Área de Trabalho (Workspace) de R é o ambiente em uso durante uma sessão, incluindo os pacotes carregados, variáves e funções definidas pelo usuário, incluindo os objetos vetores, matrizes, data frames e listas. Uma imagem do workspace atual pode ser salva ao final de uma sessão. Ela será automaticamente recuperada quando a sessão for reiniciada. O diretório (ou pasta) de trabalho atual será usado (por default) por R para ler ou gravar arquivos e dados. Para ler ou gravar em outra pasta o caminho completo deve ser fornecido. A tabela abaixo reune alguns comandos usados para a manipulação do ambiente de trabalho em R:

Comando: Resultado:
getwd() pasta atual
setwd(“pasta”) altera pasta atual
dir.create(“teste”) cria a pasta teste
dir() lista arquivos na pasta atual
save.image(“arquivo.R”) grava estado da sessão em arquivo.R
load(“arquivo.R”) recupera a sessão gravada em arquivo.R
ls() lista os objetos carregados na workspace atual
rm(objectlist) remove um ou mais objetos
help(options) fornece informação sobre as opções disponíveis
options() exibe e modifica as opções disponíveis
history(#) exibe os últimos # commandos (default = 25)
savehistory(“myfile”) grava o histórico de comandos no arquivo myfile (default =.Rhistory)
loadhistory(“myfile”) recupera o histórico de comandos no arquivo myfile (default = .Rhistory)
save.image(“myfile”) grava o workspace no arquivo myfile (default = .RData)
save(objectlist, file=”myfile”) grava objetos específicos em um arquivo
load(“myfile”) carrega para a sessão atual o workspace
sink(“nomeArquivo.txt”) redireciona todas as saídas do R para o arquivo. O arquivo gravado é fechado com sink()
q() encerra a sessão de R (oferecendo ao usuário salvar o workspace.)

Vimos também que o operador de atribuição em R é (diferente da maioria das demais linguagens de programação), usando variável <- valor . Atribuições também podem ser feitas da seguinte forma:

> r = 5
> 98 -> kk

Estas não são práticas recomendadas, por convenção.

Maiores ajudas sobre uma função pode ser obtida:

Operador de ajuda Efeito
help.start() ajuda geral
help(“foo”) (?foo) ajuda sobre a função foo (aspas optionais)
help.search(“foo”) (??foo) busca pela string ‘foo’ no sistema de ajuda
example(“foo”) exemplos de uso da função foo
RSiteSearch(“foo”) busca pela string ‘foo’ no sistema de ajuda online e mailing lists
vignette() lista todas as vignettes disponíveis para os pacotes instalados
vignette(“foo”) lista todas as vignettes disponíveis para o tópico “foo”
data() lista todos os exemplos disponíveis de conjunto de dados contidos nos pacotes instalados

Vignettes são artigos introdutórios em formato pdf disponíveis para alguns pacotes.

Palavras Reservadas em R

Palavras Reservadas Uso
If, else, repeat, while, function, for, in, next, break Usados em loops, condicionais e funções
TRUE, FALSE Constantes lógicas
NULL Valor ausente ou indefinido
Inf Infinito
NaN Not a Number (não número)
NA Not Available (não disponível)
NA_integer_, NA_real_, NA_complex_, NA_ character_ Constantes de vetores com valores ausentes
Permite que uma função passe argumentospara outra

Regras e convenções para nomes de Variáveis

Para dar nomes às variáveis em R existem algumas regras e algumas convenções. Nomes válidos consistem de letras, números, pontos ou underline. A variável deve começar com uma letra ou ponto não seguido de número e não devem ser palavras reservadas.
Como convenção se pode usar:

Tipo Exemplo
apenas letras minúsculas idadealuno
palavras separadas por . idade.aluno
palavras separadas por _ idade_aluno
CamelCase minúsculas idadeAluno
CamelCase maiúsculas IdadeAluno

É aconselhável que um dos estilos seja escolhido e usados de forma consistente.

Em R o ponto . não tem um significado especial, sendo tratado apenas como um caracter.

Pacotes (Packages)

Apesar de que R já vem com um grande volume de funções em seu núcleo mínimo, um parte importante de sua funcionalidade está na possibilidade da instalação de módulos ou packages escritos por usuários que estão disponíveis para download e instalação. Estes pacotes são coleções de funções ou dados organizados de forma pré-estabelecida. Quando carregadas estas funções são usadas da mesma forma que as funções nativas.

Uma vez instalado o pacote passa a fazer parte de sua biblioteca (library). Ele fica disponível em uma sessão através do uso da função library(pacote). Você pode preparar seu ambiente de trabalho para que alguns pacotes escolhidos sejam carregados na inicialização. Por default R carrega pacotes padrões, incluindo: base, datasets, utils, grDevices, graphics, stats e methods.

Função Descrição
.libPaths() exibe localização da biblioteca.
install.packages(“pacote“) instala pacote.
library() exibe pacotes instalados.
library(pacote) Carrega pacote para uso.
installed.packages() exibe pacotes instalados.
remove.packages(“pacote“) desinstala pacote.
search() informa os pacotes carregados para uso.
update.packages() atualiza pacotes instalados.
help(package=”pacote“) Documentação sobre pacote
library(help = “pacote“) Sobre pacote (mais compacto que help(package=”pkt”)

Uma forma alternativa de desinstalação de pacotes é através do comando de linha R CMD REMOVE pacote.
Ao instalar um pacote usando a função install.packages() preste atenção às mensagens exibidas no console. Falhas na instalação são exibidas e, em geral, alguma sugestão de como o problema pode ser resolvido. A causa mais comum de falha são as dependências ausentes e, neste caso, bibliotecas específicas podem ser instaladas separadamente. Pode ocorrer que a biblioteca necessite de uma instalação funcional do Java, por exemplo. Esta instalação depende da plataforma que você está usando.

Você pode encontrar uma lista de pacotes no site CRAN – Contributed Packages, junto com instruções de instalação e uso. No final de 2018 existem mais de 13500 pacotes disponíveis no site CRAN.

Um exemplo de uso: instalamos o pacote “vcd” que contém uma tabela chamada Arthrites

> # instala o pacote vcd
> install.packages("vcd")    # um diálogo exibe as etapas da instalação
> # ajuda sobre o pacote
> help(package="vcd")        # exibe ajuda sobre vcd
> # para usar o pacote
> library(vcd)
> # para informações sobre Arthritis
> help(Arthritis)           # exibe informações sobre Arthritis
> head(Arthritis)           # exibe as 6 primeiras linhas da tabela 
  ID Treatment  Sex Age Improved
1 57   Treated Male  27     Some
2 46   Treated Male  29     None
3 77   Treated Male  30     None
4 17   Treated Male  32   Marked
5 36   Treated Male  46   Marked
6 23   Treated Male  58   Marked

Processamento em Lote (Batch processing)

Além de executar as linhas de código em uma sessão interativa de R (ou do RStudio) você pode digitar as linhas em um arquivo ASCII simples e executá-lo em lote. Isso pode ser útil no caso de um processamento demorado ou para operações programadas para ocorrer em algum momento.

Se você estiver trabalhando em Linux ou Mac OS X você deve digitar no console:

R CMD BATCH opções arquivoLote arquivoSaida

onde arquivoLote é o arquivo com as linhas de código e arquivoSaida o arquivo que deverá receber os resultados da execução. Por convenção o arquivoLote tem extensão .R e arquivoSaida tem extensão .Rout.

No Windows você digita no terminal:

"C:\Program Files\R\R-3.1.0\bin\R.exe" CMD BATCH --vanilla --slave "c:\my projects\myscript.R"

cuidando para dar o caminho correto de instalação de R e de seu arquivo de script.

Maiores detalhes em na documentação do CRAN, Introduction to R.


Tipos de dados, variáveis e vetores

Probabilidade e Estatística

O estudo matemático das probabilidades e da estatística, além de sua evidente importância prática, representa uma grande oportunidade para o uso dos conceitos da Teoria dos Conjuntos. Por isso faremos uma revisão dos conceitos relevantes.

Conjuntos

O conceito de conjuntos é um conceito primário, básico ao entendimento de toda a matemática. Conjuntos são coleções de objetos, não necessariamente envolvendo números ou outra entidade matemática. Podemos representar um conjunto exibindo explicitamente seus elementos. É o que fazemos mostrando os naipes de cartas de baralho:

$$ C_{1}=\{\spadesuit,\clubsuit,\diamondsuit,\heartsuit\}, $$
ou o conjunto dos inteiros ímpares menores que 10:
$$ C_{2}=\{1,3,5,7,9\}. $$

Outra forma útil consiste em descrever o conjunto usando a notação:
$$ \text{Conjunto } =\{x_i|\; \text{ alguma propriedade satisfeita pelos elementos} \}.$$
Em muitas situações o conjunto pode ser muito grande ou possuir infinitos elementos, de forma que não podemos explicitá-los uma a um. É o que ocorre com o conjunto dos inteiros pares
$$ C_{3}=\{ \left.n_i \in \mathbb{N}\right|n_i \,\,\text{ os inteiros pares}\} = \{ 2n_i | n_i \in \mathbb{Z} \} $$

$$ C_{3}=\{n_i \in \mathbb{N} | n_i \,\text{ um inteiro par} \} = \{ 2n_i | n_i \in \mathbb{Z} \}, $$

ou o conjunto de pontos no plano \(\mathbb{R}^2\) sobre a circunferência de raio 1,

$$ C_{4}=\{\left.(x,\,y)\in\mathbb{R}^2\right|(x^2+y^2=1)\}. $$

Se os elementos de um conjunto podem ser contados ele é dito enumerável e sua ordem, que denotaremos por \(\text{ord}(A)=n\), é o número de seus elementos. Nos exemplos acima temos \(\text{ord}(C_{1})=4\), \(\text{ord}(C_{2})=5\). O conjunto \(C_{3}\) é enumerável, com infinitos elementos, e \(C_{4}\) não é enumerável (também possuindo infinitos elementos).

Dizemos que um elemento \(a\) pertence à um conjunto \(C\) se \(a\) é um dos elementos de \(C\). Denotamos esta relação por \(a\in C\). Caso contrário escrevemos \(a\notin C\).

Dizemos que um conjunto \(A\) está contido no conjunto \(B\) se todos os elementos de \(A\) estão também em \(B\). Denotamos esta relação por \(A\subset B\). Caso contrário escrevemos \(A\not\subset B\). Observe que vale a seguinte afirmação: se \(A\subset B\) e \(x\in A\Rightarrow x\in B.\)

A contido em B
União e Intersecção

Conjuntos podem ser combinados de várias maneiras. Por exemplo, se \(A\) e \(B\) são dois conjuntos podemos encontrar a união dos dois, \(A\cup B\), ou sua intersecção \(A\cap B\), ilustradas na figura 1. Observe que
$$ x\in A\cup B \Rightarrow x\in A\text{ ou }x\in B, $$
$$ x\in A\cap B \Rightarrow x\in A\text{ e }x\in B. $$

Um número maior de conjuntos podem também ser combinados. Se \(A_{i}\) é uma coleção de conjuntos (\(i=1,\ldots,\,n)\) denotamos a união e intersecção destes conjuntos por: \(\underset{i=1}{\cup}A_{i}, \underset{i=1}{\cap}A_{i},\) respectivamente. Observe que dois conjuntos são disjuntos se \(A\cap B=\emptyset\).

 

Definição: Se \(A\subset S\) definimos \(\bar{A},\) o complementar de \(A,\) como o conjunto de todos os elementos de \(S\) que não estão em \(A\),
$$ \bar{A}=\{x\in S;\,\,x\notin A\}. $$

Observe que \(A\cup\bar{A}=S\).

Se \(S\) é finito ou numerável com \(n\) elementos então existem \(2^{n}\) eventos associados (subconjuntos de \(S\)).

 

O produto externo é outra forma de combinar conjuntos:

$$ A\times B=\left\{ (a,b)|a\in A,b\in B\right\}. $$

Seus elementos são os pares ordenados \((a,b)\). Observe que \(\mathbb{R}^n = \mathbb{R}\times\ldots\times\mathbb{R}.\)

Experimento aleatório e espaço amostral

Um experimento é não determinístico ou aleatório se seu resultado não pode ser determinado previamente, à partir das condições iniciais do sistema usado. Na prática um experimento pode ser considerado aleatório se o conjunto das condições iniciais e sua evolução até a obtenção do resultado forem muito complexas e de difícil análise. Por exemplo, quando se atira uma moeda todas as leis envolvidas no movimento são causais e é possível prever o resultado (com que face ela cairá ao solo) se todas as condições iniciais forem conhecidas. No entanto estas condições envolvem um grande número de variáveis (tais como as colisões com partículas do ar) e é, quase sempre, mais apropriado considerar que o resultado será aleatório. Na natureza macroscópica poucos experimentos são realmente aleatórios. No nível microscópico (quântico) temos fenômenos completamente aleatórios, tais como o momento em que uma substância radioativa sofrerá um decaimento e emitirá uma partícula ou radiação.

O conjunto dos resultados possíveis para um dado experimento é denomidado seu espaço amostral. Denotaremos por \(\varepsilon\) os experimentos e \(S\) seu espaço amostral. Alguns exemplos de experimentos aleatórios (dentro das ressalvas dadas acima) são:

\(\varepsilon_{1}:\) Jogue uma moeda 4 vezes e observe número de caras resultantes. \(S=\{0,1,2,3,4\} \).

\(\varepsilon_{2}:\) Jogue uma moeda 4 vezes e verifique a sequência de caras (que denotaremos por h) e coroas (que denotaremos por t). \(S=\{ \text{(hhhh), (hhht), …, (tttt)}\} \).

\(\varepsilon_{3}:\) Jogue uma moeda 4 vezes e verifique quantas caras e coroas resultam. \(S=\{(0,4),\,(1,3),\,(2,2),\,(3,1),\,(4,0)\} \).

\(\varepsilon_{4}:\) Deixe uma lâmpada acesa até queimar. Verifique o tempo de vida da lâmpada (um espaço amostral contínuo).

\(\varepsilon_{5}:\) Em um lote com 10 peças, sendo 3 defeituosas, retire 1 de cada vez, sem repor, até que todas com defeito sejam removidas. Quantas peças serão retiradas? \(S=\{3,4,5,6,7,8,9,10\}\).

\(\varepsilon_{5′}:\) Mesmo experimento anterior. Quantas peças podem ser retiradas sem que alguma tenha defeito? \(S=\{1,2,3,4,5,6,7\}\).

Definição: Um evento relativo ao experimento \(\varepsilon\) é um subconjunto de \(S\).

Exemplo 1: São eventos associados aos experimentos já listados:

\(\varepsilon_1\): \(A=\{2\} ,\) duas caras ocorrem,

\(\varepsilon_3\): \(B=\{(3,1),\,(4,0)\}\), mais caras que coroas,

\(\varepsilon_4\): \(C=\{t |\, t \lt 3000h \}\), lâmpada queima antes de 3000 horas.

Observe que, com esta definição, \(S\) e \(\emptyset\) são ambos eventos.

Se \(A\) e \(B\) são eventos então também são eventos:

\(A\cup B\) ocorre se \(A\) ou \(B\) ocorrem,
\(A\cap B\) ocorre se \(A\) e \(B\) ocorrem,
\(\bar{A}\) ocorre se \(A\) não ocorre.

No caso de diversos eventos \(A_{i}\) associados ao experimento:

\(\underset{i}{\cup}A_{i}\) ocorre se um dos \(A_i\) ocorre,
\(\underset{i}{\cap}A_{i}\) ocorre se todos os \(A_i\) ocorrem.
Resumindo: União e interseção.

Notação: Se um experimento consiste na execução do experimento \(\varepsilon\) \(n\) vezes denotamos seu espaço amostral por meio do produto externo
$$ \text{S}\times\ldots\times\text{S}=\left\{ \left(s_{1},\cdots,\,s_{n}\left|s_{i}\in S\right.\right)\right\}.$$

Definição: Dois eventos \(A\) e \(B\) são mutuamente excludentes se não podem ocorrer simultaneamente. Neste caso \(A\cap B=\emptyset\).

Definição: Uma coleção de subconjuntos de \(S\), que denotaremos por \(\{A_i\}\), é uma cobertura de  \(S\) se os subconjuntos são mutuamente disjuntos (\(A_{i}\cap A_{j}=\emptyset\) para \(i\neq j\))  sua união é o próprio \(S\) (\(\underset{i}{\cup}A_{i}=S\)). Desta forma cada elemento de \(S\) está contido em exatamente um dos subconjuntos \(A_{i}\).

Definição: A cada evento de \(S\) associado ao experimento \(\varepsilon\) associamos uma probabilidade de ocorrência \(P\left(A\right)\), um número real, satisfazendo

1. \(0\leq P\left(A\right)\leq1\),
2. \(P\left(S\right)=1\),
3. Se \(A\cap B=\emptyset\) então \(P\left(A\cup B\right)=P\left(A\right)+P\left(B\right)\).

Se \(\{A_{i}\}\) é uma coleção de eventos disjuntos (\(A_i \cap A_j=\emptyset\) para \(i\neq j\)) então \(P\left(\cup A_{i}\right)=\sum P\left(A_{i}\right)\).

Teorema: \(P(\emptyset)=0\)

Demonstração: \(A=A\cup\emptyset\) portanto \(P(A)=P\left(A\cup\emptyset\right)=P\left(A\right)+P\left(\emptyset\right)\Rightarrow P\left(\emptyset\right)=0 \)

Teorema: \(P(\overline{A})=1-P(A)\)

Demonstração: \(S=A\cup\overline{A}\), uma união disjunta. \(P\left(S\right)=1=P\left(A\right)+P\left(\overline{A}\right)\ \).

Esta última propriedade é muito interessante em alguns casos onde é mais fácil calcular \(P\left(\overline{A}\right)\), a probabilidade de não ocorrer o evento \(A\).

Teorema: \(P\left(A\cup B\right)=P\left(A\right)+P\left(B\right)-P\left(A\cap B\right)\)

Demonstração: \(A\cup B=A\cup\left(B\cap\overline{A}\right)\) e \(B=\left(B\cap A\right)\cup\left(B\cap\overline{A}\right)\). Como ambas uniões são disjuntas temos que
$$ P\left(A\cup B\right)=P\left(A\right)+P\left(B\cap\overline{A}\right)\,\,\,\text{e}\,\,\,P\left(B\right)=P\left(A\cap B\right)+P\left(\overline{A}\cap B\right) $$
$$ \Rightarrow P\left(A\cup B\right)=P\left(A\right)+P\left(B\right)-P\left(A\cap B\right).\ $$

Aplicando-se este mesmo resultado 2 vezes temos

$$
\begin{array}{rl}
P(A\cup B\cup C)= & P(A)+P(B)+P(C) \\
& -P(A\cap B)-P(B\cap C)-P(C\cap A)+P(A\cap B\cap C)
\end{array}
$$

Teorema: Se \(A\subset B\Longrightarrow P\left(A\right)\leq P\left(B\right)\)

Demonstração: Escreva \(B=A\cup\left(B\cap\overline{A}\right)\Longrightarrow P(B)=P(A)+P(B\cap\overline{A})\Longrightarrow P\left(A\right)\leq P\left(B\right).\)

Definição: Uma coleção de eventos \(\{A_i\}\) é uma partição de \(S\) se

1. \(A_{i}\cap A_{j}=\emptyset\) para \(i\neq j\),
2. \(\underset{i}{\cup}A_{i}=S\),
3. \(P\left(A_{i}\right)>0,\forall i\).

Portanto, uma partição é uma coleção de subconjuntos de \(S\) mutuamente disjuntos, que cobrem todo o conjunto \(S\). Uma partição é uma cobertura composta de subconjuntos de probabilidade não nula. Devido à propriedade 1, quando um experimento é realizado apenas um dos eventos de uma partição ocorre de cada vez.

Espaços amostrais finitos

Vamos considerar, nesta seção, experimentos cujos resultados são descritos por um espaço amostral consistindo de um número finito de \(k\) elementos, \(S={ a_1,\ldots,\,a_k}\). Chamaremos de um evento simples (ou elementar) a um evento formado por um resultado simples, \(A={a_i} \). A cada evento simples associaremos uma probabilidade \(p_i=P({a_i})\) satisfazendo

(a) \(0\leq p_{i}\leq1\),
(b) \(\sum_{i}^{k}p_{i}=1.\)

Notamos que \(\left\{ a_{i}\right\} \cap\left\{ a_{j}\right\} =\emptyset,\;i\neq j,\) o que significa que a coleção de todos os eventos simples de \(S\) é uma partição do espaço amostral.

Se tomarmos um evento constituído de \(r\) destes eventos simples (\(1\leq r\leq k)\; A={a’_1,\ldots,\,a’_r}\) (uma combinação de \(r\) eventos quaisquer de S) então
$$ P\left(A\right)=p_{1}+p_{2}+\ldots+p_{r}=\sum^{r}p{}_{i.} $$

Isto significa que conhecemos a probabilidade de \(A\) se conhecermos a probabilidade dos elementos simples que a compõem.

Se todos os \(k\) resultados são igualmente verossímeis (ocorrem com a mesma probabilidade) então
$$ p_{i}=\frac{1}{k}\;\;\text{e}\;\;P(A)=\frac{r}{k}. $$

Resumindo, se \(A\) é formado por \(k\) resultados simples igualmente prováveis então
$$ P(A)=\frac{\text{número de casos favoráveis}.}{\text{número de casos possíveis}} $$

Exemplo 2: Atirando uma moeda 2 vezes (ou duas moedas, ao mesmo tempo) qual é a probabilidade de se obter 1 cara? O experimento consiste em contar o número de caras resultantes e o espaço amostral é \(S={0,1,2}\). O evento favorável é \(A={1 \text{ cara }}={1 h}\). Note que \(P(A)\neq\frac{1}{3}\) pois os eventos de \(S\) não são igualmente verossímeis. Uma descrição mais apropriada do espaço amostral seria:
$$
S’=\{(h,h),\,(h,t),\,(t,h),\,(t,t)\}
$$

O espaço amostral \(S’\) consiste de 4 casos possíveis, dois deles favoráveis. Portanto
$$ P(A)=P(1\text{cara})=\frac{2}{4}=0,5. $$

Isto mostra a importância de se conhecer técnicas de contagens de eventos.

Exemplo 3: Um dado honesto (bem balanceado) cai com qualquer das faces virada para cima com a mesma probabilidade. Jogando-se o dado uma vez, qual a probabilidade de que ele caia com um número maior que 2? O espaço amostral é \(S=\{1,2,3,4,5,6\}\), o evento favorável é \(A=\{3,4,5,6\}\). A probabilidade procurada é \(P(A)=4/6=2/3.\)

Exemplo 4: Jogando-se um dado 2 vezes, qual é a probabilidade de que a soma dos números obtidos seja 6?

Neste caso o espaço amostral é
$$ S=\left\{\begin{array}{cccc}
(1,1) & (1,2) & \ldots & (1,6) \\
\vdots & & & \vdots \\
(6,1) & (6,2) & \ldots & (6,6)
\end{array}\right\}.
$$

Destes eventos simples os únicos favoráveis são \(A=\{(1,5),\,(2,4),\,(3,3),\,(4,2),\,(5,1)\}.\) Portanto \(P(A)=5/36.\)

Métodos de enumeração ou contagem

Vemos que é importante saber contar quantos eventos podem resultar de um certo experimento. Consideremos então a questão: de quantas maneiras diferentes podemos dispor de \(n\) objetos (permutações)? O primeiro pode ser escolhido entre \(n\) objetos, o segundo entre \(n-1\), até o útimo objeto restante. Como ilustrado na figura, o número resultante é \(n \times (n-1) \times \cdots \times 1 =n!\).

Como notação escreveremos \(_{n}P_{n}=n!\) para indicar a permutaçao de \(n\) objetos.

De quantas formas diferentes podemos escolher apenas \(r,\;(r\lt n)\) entre \(n\) objetos diferentes? Agora a escolha é interrompida após a seleção do \(r\)-ésimo objeto. Denotando por \(_{n}A_{r}\) este número temos
$$ _{n}A_{r}=n\left(n-1\right)\cdots\left(n-r+1\right)=\frac{n!}{\left(n-r\right)!}.$$

Se a ordem em que estes \(r\) elementos entram na seleção não é relevante então temos que remover da contagem acima as seleções repetidas. Temos que \(r\) objetos podem ser permutados de \(r!\) formas diferentes. Então, denotando por \(C\) o número de modos de permutar \(r\) entre \(n\) elementos, temos
$$ C=\frac{_{n}A_{r}}{r!}=\frac{n!}{r!\left(n-r\right)!}. $$

O número de combinações de \(n\) elementos em grupos de \(r\) elementos, sem que a ordem seja importante, aparece em diversas aplicações da matemática e recebe uma notação especial:
$$ C=\left(\begin{array}{c} n \\ r \end{array}\right)=\frac{n!}{r!\left(n-r\right)!}. $$
Estes são os chamados coeficientes binomiais. Eles possuem diversas propriedades interessantes. Entre elas, se \(n\) é um inteiro positivo e \(0\leq r\leq n\) então

$$
\left( \begin{array}{c} n \\ r \end{array}\right) =
\left(\begin{array}{c} n \\ n-r \end{array}\right), \;\;\;\;
\left(\begin{array}{c} n \\ r \end{array}\right)=
\left(\begin{array}{c} n-1 \\ r-1 \end{array}\right) +
\left(\begin{array}{c} n-1 \\ r \end{array}\right)
$$

Exemplo 5:. Na Loteria brasileira Megasena uma aposta simples consiste em escolher 6 entre 60 números. Qual a probabilidade de se escolher os 6 números sorteados? Temos que
$$ C=\left(\begin{array}{c} 60 \\ 6 \end{array}\right)=\frac{60!}{6!(54)!}=50063860 $$

é o número de resultados possíveis. A probabilidade de se acertar com um jogo simples é \(1/50063860\).

Observe que a operação acima pode ser simplificada da seguinte forma:
$$ \frac{60!}{6!(54)!}=\frac{55\times56\times57\times58\times59\times60}{2\times3\times4\times5\times6}=50063860. $$
(Os primeiros \(54\) fatores no numerador são cancelados por \(54!\) no denominador.)

Probabilidade Condicionada

Vamos usar de um exemplo para mostrar a diferença entre escolher objetos de um lote inicial, fazendo ou não a reposição dos objetos retirados após cada escolha.

Exemplo 6: Em um lote com 100 peças, 20 são defeituosas. Retiramos 2 peças e definimos dois eventos: \(\;A=\){1ª peça com defeito} \(B=\){2ª peça com defeito}. Se há reposição da peça retirada temos:
$$P(A)=\frac{20}{100}=\frac{1}{5},\;\;\; P(B)=\frac{1}{5}.$$

Mas, se não há a reposição, após a retirada da primeira peça restam 99, mas não sabemos quantas são defeituosas. Vamos denotar por \(P(B|A)\) = a probabilidade condicionada do evento \(B,\) tendo ocorrido o evento \(A.\) Se a primeira peça tinha defeito, restam 19 peças com defeito em um lote de 99, e \(P(B|A)=\frac{19}{99}\).

Como \(A\) ocorreu, o espaço amostral fica reduzido. Observe ainda que a probabilidade de \(B\) se \(A\) não tiver ocorrido é \(P(B|\overline{A})=\frac{20}{99}\).

Exemplo 7: Ex.: Dois dados são lançados e representamos o resultado por \(\left(x_{1},\,x_{2}\right).\) O espaço amostral é

$$
S=\left\{ \begin{array}{cccc}
(1,1) & (1,2) & \ldots & (1,6) \\
(2,1) & (2,2) & \ldots & (2,6) \\
\vdots & & & \vdots \\
(6,1) & (6,2) & \ldots & (6,6)
\end{array}\right\},
$$
consistindo de 36 eventos simples. Considere 2 eventos: \(A\) onde a soma dos dados é 10; \(B\) onde o primeiro resultado é maior que o segundo:

$$
\begin{array}{rl}
A= & \{(x_1,x_2)|x_1 + x_2=10\} = \{(4,6),(5,5),(6,4)\} \\ & \\
B= & \left\{(x_1,x_2)|x_1 \gt x_2\right\} \\
= & \left\{(2,1),(3,1),(3,2),(4,1),(4,2),(4,3),(5,1),\right. \\
& \left.(5,2),(5,3),(5,4),(6,1),(6,2),(6,3),(6,4),(6,5)\right\}
\end{array}
$$

A probabilidade de ocorrerem \(A\) e \(B\) são, respectivamente,
$$ P\left(A\right)=\frac{3}{6},\;\;P(B)=\frac{15}{36},$$
enquanto a probabilidade condicionada de ocorrer \(B\) tendo ocorrida \(A\) é $$ P(B|A)=\frac{1}{3}. $$ O espaço amostral se reduz para \(A={(4,6),\,(5,5),\,(6,4)}\) e, entre estes eventos apenas \((6,4)\) é favorável. Da mesma forma a probabilidade de ocorrer \(A\) tendo ocorrido \(B\) é
$$ P(A|B)=\frac{1}{15}, $$

pois \(\text{ord}(B)=15\) e apenas o evento \(\left(6,4\right)\) é favorável. Observe ainda que a probabilidade de que \(A\) e \(B\) ocorram simultaneamente é
$$ P(A\cap B)=\frac{1}{36}.$$

Note que:
$$P(A|B)=\frac{P(A\cap B)}{P(B)}=\frac{1}{36}\frac{36}{15}=\frac{1}{15}$$
$$P(B|A)=\frac{P(B\cap A)}{P(A)}=\frac{1}{36}\frac{36}{3}=\frac{1}{3}$$

Isto sugere a definição de probabilidade condicionada (que pode ser formalmente demostrada):

$$ P(B|A)=\frac{P(A\cap B)}{P(A)} $$

para \(P(A)\gt 0\). É claro que, se \(P(A)=0\), \(P(B|A)=0\). Podemos então escrever

$$ P(A\cap B)=P(B|A)P(A)=P(A|B)P(B). $$

Exemplo 8: Entre 100 calculadoras temos aparelhos novos (N) e usados (U), eletrônicos (E) e manuais (M), de acordo com a tabela:

Uma é escolhida ao acaso e verifica-se que é nova. Qual probabilidade de que ela seja eletrônica?

Como já se vericou que a calculadora é nova, o espaço amostral fica reduzido à apenas 70 unidades. Nele apenas 40 calculadoras são eletrônicas. Usando a definição de probabilidade condicionada temos

$$ P(E|N)=\frac{P(E\cap N)}{P(N)}=\frac{40/100}{70/100}=\frac{4}{7}.$$

Exemplo 9: Retomamos a situação das 100 peças, sendo 20 com defeito. Qual a probabilidade de se escolher 2, sem reposição, e serem ambas defeituosas?

Definimos os eventos A = {1ª com defeito}; B = {2ª com defeito}. O evento favorável é \(A\cap B\) e sua probabilidade é

$$ P(A\cap B)=P(B|A)P(A)=\frac{19}{99}\frac{20}{100}=\frac{19}{495}. $$

Uma observação será útil antes de prosseguirmos: seja \(\{M_{i}\}\) \(i=1,\ldots,\,k,\) é uma partição de \(S\). Podemos decompor \(B\) em partes mutuamente excludentes

$$ A=(A\cap M_{1})\cup\cdots\cup(A\cap M_{k}). $$

Portanto:

$$ P(A)=\sum_{i}P(A\cap M_{i})=\sum_{i}P(A|M_{i})P(M_{i}). $$

Exemplo 10: Na mesma situação anterior, qual a probabilidade de, escolhendo 2 peças, a segunda ter defeito? Novamente temos \(A=\) {1ª com defeito}; \(B=\) {2ª com defeito}. Queremos calcular \(P(B)\). Podemos escrever \(B\) como a união disjunta \(B=\left(B\cap A\right)\cup\left(B\cap\bar{A}\right)\). Então

$$
\begin{array}{rl}
P(B)= & P\left(B\cap A\right)+P\left(B\cap\bar{A}\right)=P(B|A)P(A)+P(B|\bar{A})P(\bar{A}) \\
= & \frac{19}{99}\frac{1}{5}+\frac{20}{99}\frac{4}{5}=\frac{1}{5}.
\end{array}
$$

Exemplo 11: Um produto é manufaturado por 3 fábricas diferentes que chamaremos de \(F_{1},\,F_{2}\)e \(F_{3}\). A quantidade de peças produzida por cada fábrica e a porcentagem de defeitos são exibidas na tabela:

Fábrica produção/dia peças com defeito
F1 2 2%
F2 1 2%
F3 1 4%

Após um certo tempo a produção das 3 fábricas é colocada em um depósito e uma peça é retirada ao acaso. Qual é a probabilidade dela ser defeituosa? Vamos definir os seguintes eventos \(D=\) {peça com defeito}; \(F_i\) = {peça fabricada por \(F_i\)}, \(i=1,2,3\). Podemos usar a união disjunta \(D=\cup_{i}(D\cap F_{i})\) para calcular

$$
\begin{array}{rl}
P(A)=& \sum_{i}P(D\cap F_{i})=\sum_{i}P(D|F_{i})P(F_{i})\\
=& P(D|F_{1})P(F_{1})+P(D|F_{2})P(F_{2})+P(D|F_{3})P(F_{3}) \\
=& 00,2\frac{1}{2}+00,2\frac{1}{4}+00,4\frac{1}{4}=0,025.
\end{array}
$$

Podemos ainda fazer a seguinte pergunta: Suponha que a peça retirada é defeituosa. Qual é a probabilidade de que ela tenha sido produzida na \(F_1?\) Queremos, portanto, \(P(F_{1}|D)\). Usamos

$$
\begin{array}{rl}
P(F_1|D)= & \frac{P(D|F_1)P(F_1)}{P(D)}=\frac{P(D|F_1)P(F_{1})}{\sum_{i=1}^{3}P(D|F_i)P(F_i)}= \\
& \frac{(0,02)\frac{1}{2}}{(0,02)\frac{1}{2}+(0,02)\frac{1}{4}+(0,04)\frac{1}{4}}=0,04,
\end{array}
$$

onde, na segunda igualdade, foi usado o fato de que \({F_i}\) é uma partição do espaço amostral.

Teorema de Bayes

Seja \({B_i}\) uma partição do espaço amostral e \(A\) um evento de \(S.\) Então
$$ P(B_i|A)=\frac{P(A|B_i)\,P(B_i)}{\sum_{k=1}^{3}P(A|B_k)\,P(B_k)},\:i=1,…,\,n. $$

Eventos independentes

Dois eventos são ditos independentes se a ocorrência de um não afeta a probabilidade de ocorrência do outro.

Exemplo 12: Um dado é jogado 2 vezes. Definimos os eventos \(A=\){1º mostra número par}, \(B=\){2º cai 5 ou 6}. Vemos que são dois eventos não relacionados. Temos

$$ P(A)=\frac{1}{2},\;\; P(B)=\frac{1}{3}.$$
$$ P(A\cap B)=\frac{6}{36}=\frac{1}{6}, $$

pois \(A\cap B=\{(2,5),(2,6),(4,5),(4,6),(6,5),(6,6)\}\). Consequentemente

$$ P(A|B)=\frac{P(A\cap B)}{P(B)}=\frac{1}{2}. $$

Observamos que \(P(A|B)=P(A).\) Da mesma forma \(P(B|A)=P(B).\)

Definição: \(A\)e \(B\)são eventos independentes se, e somente se, \(P(A\cap B)=P(A)P(B).\)

Uma boa revisão sobre a Teoria dos Conjuntos pode ser vista em Gigamatematica: Conjuntos Enumeráveis