8  Data Frames

Status 🟦🟦🟦

8.1 O que são data frames ?

Conforme o R Core Team (2023), data frame é a estrutura que imita de forma mais próxima um dataset do SAS ou SPSS. De forma resumida um data frame é uma estrutura tabular com colunas (variáveis, atributos, etc) e linhas (registros, casos, observações, instâncias, etc). Diferente de uma matriz um data frame pode ter diferentes tipos de dados em suas colunas.

Um data frame possui todas as colunas com o mesmo tamanho (quantidade de regitros). A classe de um objeto data frame possui o nome data.frame. Abaixo pode ser visualizada a classe do data frame iris (muito usado em exemplos em Ciência de Dados) e também as primeiras linhas com o comando head.

class(iris)
[1] "data.frame"
head(iris)
  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
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

Um data frame é na verdade uma lista, assim as operações efetuadas em listas possuem equivalência em data frames.

typeof(iris)
[1] "list"

8.1.1 Criando Data Frames

Objetos da classe data.frame podem ser criados com a função data.frame.

Aqui serão usadas as convenções de nomes conforme capítulos Nomeando Objetos e Convenções.

df_exemplo <- data.frame(
  VAR_A = c(1:5),
  VAR_B = c(101:105)
)
df_exemplo
  VAR_A VAR_B
1     1   101
2     2   102
3     3   103
4     4   104
5     5   105

8.1.2 Aplicar convenções de nomes

Para continuar os próximos tópicos vamos trabalhar com um data frame (df_iris) criado a partir do data frame iris. Faremos ajustes nos nomes deste data frame.

# criar data frame df_iris
df_iris <- iris
# mudar nomes para maiusculas
names(df_iris) <- toupper(names(df_iris))
# substituir '.' por '_'
names(df_iris) <- gsub(names(df_iris), pattern = "\\.", replacement = "_")  

class(df_iris)
[1] "data.frame"
head(df_iris)
  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
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

8.2 Atributos

Os atributos “padrão” de um data frame são: names, class e row.names. É possível acessá-los com a função attributes. O atributo names também pode ser obtido com a função names.

attributes(df_iris)
$names
[1] "SEPAL_LENGTH" "SEPAL_WIDTH"  "PETAL_LENGTH" "PETAL_WIDTH"  "SPECIES"     

$class
[1] "data.frame"

$row.names
  [1]   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
 [19]  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36
 [37]  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54
 [55]  55  56  57  58  59  60  61  62  63  64  65  66  67  68  69  70  71  72
 [73]  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90
 [91]  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107 108
[109] 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
[127] 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
[145] 145 146 147 148 149 150
names(df_iris)
[1] "SEPAL_LENGTH" "SEPAL_WIDTH"  "PETAL_LENGTH" "PETAL_WIDTH"  "SPECIES"     

8.3 Dimensões

A função dim retorna as dimensões de um data frame (linhas e colunas). Estes dados também podem ser obtidos com as funções nrow e ncol.

dim(df_iris)
[1] 150   5
nrow(df_iris)
[1] 150
ncol(df_iris)
[1] 5

8.4 Acessando Dados

8.4.1 Índices

Como a estrutura de um data frame é organizada em linhas e colunas, podemos acessar os dados utilizando colchetes ([ ]): base[linha, coluna]. Podem ser usados intervalos de índices com o operador :.

# Acessar primeira linha e segunda coluna (Sepal.Width)
df_iris[1, 2]
[1] 3.5
# Acessar linhas 1 até 3 e a segunda coluna
df_iris[1:3, 2]
[1] 3.5 3.0 3.2

Apesar de ser possível, utilizar o índice faz com que a referência seja relativa, ou seja, a variável ‘1’ pode mudar caso o data frame seja editado. Por exemplo, caso em algum momento anterior a variável PETAL_LENGTH tenha sido excluída, uma nova variável assumirá o índice 1. Além disto, no momento da leitura do código por um usuário não fica claro qual variável está sendo acessada.

8.4.2 Usando Nomes das Colunas

Existem diversas outras formas para acessar dados de um data frame, inclusive utilizando o nome da coluna de forma explícita.

# Acessar primeira linha e segunda coluna (pelo nome)
df_iris[1:3, 'SEPAL_WIDTH']
[1] 3.5 3.0 3.2

Uma forma bastante comum é através da utilização do operador $ para acessar a coluna pelo seu nome.

# Acessar primeira linha e segunda coluna
df_iris[1, ]$SEPAL_WIDTH
[1] 3.5
# Acessar linhas 1 até 3 e a segunda coluna
df_iris[1:3, ]$SEPAL_WIDTH
[1] 3.5 3.0 3.2
Nome Abreviado

Assim como nas listas, variáveis de um data frame podem ser acessadas com o mínimo de caracteres que as identifiquem dentro do data frame. Por exemplo, df_iris$SP retornará a variável SPECIES.

8.5 Filtrando Dados

Digamos que se deseje acessar apenas dados que cumpram determinada condição. Para isto, na seleção das linhas do data frame, deve ser informada condição lógica na forma abaixo:

# Retorna valores de Speal.Width onde Petal.Length for maior do que 6
x <- df_iris[df_iris$PETAL_LENGTH > 6, 'SEPAL_WIDTH']
y <- df_iris[df_iris$PETAL_LENGTH > 6.5, ]$SEPAL_WIDTH

x
[1] 3.0 2.9 3.6 3.8 2.6 2.8 2.8 3.8 3.0
y
[1] 3.0 3.8 2.6 2.8
# Função que compara os objetos
identical(x, y)
[1] FALSE

O retorno é dado pelas linhas em que a variável PETAL_LENGTH atende as condições declaradas. Este teste retorna um vetor de valores lógicos, e os valores TRUE são os que “permanecem”. Abaixo outro exemplo:

head(df_iris$PETAL_LENGTH) > 1.4
[1] FALSE FALSE FALSE  TRUE FALSE  TRUE

Aplicando este vetor de valores lógicos, o R entende que as posições correspondentes a TRUE devem ser mantidas. No exemplo abaixo, as posições (linhas) 4 e 6 atendem a condição especificada, portanto apenas estas serão selecionadas.

df_iris2 <- head(df_iris)
filtro <- head(df_iris2$PETAL_LENGTH) > 1.4
filtro
[1] FALSE FALSE FALSE  TRUE FALSE  TRUE
df_iris2[filtro, 'SEPAL_WIDTH']
[1] 3.1 3.9

Equivalente ao comando abaixo:

df_iris2[c(4, 6), 'SEPAL_WIDTH']
[1] 3.1 3.9

8.5.1 Classes de retorno

Os filtros em data frames usados com $ ou [ ] (com apenas 1 variável) retornam vetores e não data frames. Desta forma se perde a classe e a estrutura tabular característica do data frame original.

class(df_iris[1:3, 1])
[1] "numeric"
class(df_iris[1:3, 'SEPAL_WIDTH'])
[1] "numeric"

Entretanto, sendo selecionadas mais de uma coluna, a classe retornada segue sendo data.frame.

class(df_iris[1:3, c("SEPAL_LENGTH", "SEPAL_WIDTH")])
[1] "data.frame"
class(df_iris[1:3, 1:2])
[1] "data.frame"

8.6 Função Subset

A função subset permite efetuar filtro em um data frame e muitas vezes oferece uma forma mais organizada visualmente, principalmente quando em filtros com muitas condições. Uma outra vantagem é que a função subset retorna faz a seleção em um data.frame e retorna um data frame, mesmo com a seleção de apenas 1 variável.

Esta função também permite seleção de colunas a serem mantidas. Note que a função subset não demanda que o data frame seja referenciado antes das variáveis e também aceita os nomes das variáveis sem aspas. Isto torna o código mais legível.

class(subset(df_iris, select = SEPAL_WIDTH))
[1] "data.frame"
df_mtcars <- mtcars
# mudar nomes para maiusculas
names(df_mtcars) <- toupper(names(df_mtcars))

subset(x = df_mtcars, # dados
       subset = MPG > 25, # filtro  
       select = c(MPG, CYL, HP)) # colunas
                MPG CYL  HP
Fiat 128       32.4   4  66
Honda Civic    30.4   4  52
Toyota Corolla 33.9   4  65
Fiat X1-9      27.3   4  66
Porsche 914-2  26.0   4  91
Lotus Europa   30.4   4 113

Usando um filtro um pouco mais complexo e sem inserir o nome dos argumentos da função (x, subset e select):

df_mtcars_filtrado <- subset(df_mtcars, # dados 
       MPG > 25 & CYL == 4 & HP > 70, # filtro  
       c(MPG, CYL, HP)) # colunas

df_mtcars_filtrado
               MPG CYL  HP
Porsche 914-2 26.0   4  91
Lotus Europa  30.4   4 113

Nos exemplos anteriores foram declaradas de forma explícita as variáveis a serem mantidas. Para declarar as variáveis a serem excluídas basta utiliza o sinal de subtração -, de forma análoga a seleção por índices em componentes de vetores.

df_mtcars |> 
  subset(select = -c(DISP, DRAT, VS, AM)) |> 
  head()
                   MPG CYL  HP    WT  QSEC GEAR CARB
Mazda RX4         21.0   6 110 2.620 16.46    4    4
Mazda RX4 Wag     21.0   6 110 2.875 17.02    4    4
Datsun 710        22.8   4  93 2.320 18.61    4    1
Hornet 4 Drive    21.4   6 110 3.215 19.44    3    1
Hornet Sportabout 18.7   8 175 3.440 17.02    3    2
Valiant           18.1   6 105 3.460 20.22    3    1

8.7 Junção de Dados

Uma grande necessidade ao se trablahar com dados tabulados é a junção de dados. A junção nada mais é do que usar bases de dados diferentes e carregar dados entre elas a partir de uma chave de identificação. Vamos usar duas bases de dados, uma com código e nome do município e outra com o código do município e sua população. Estes dados foram buscados em IBGE (s.d.).

df_cidades <-
  data.frame(
    COD_MUNICIPIO = c('4314902', '3550308', '3304557'),
    NOME = c('Porto Alegre', 'São Paulo', 'Rio de Janeiro')
  )
                        
df_populacao <-
  data.frame(
    COD_MUNICIPIO = c('4314902', '3550308', '3304557'),
    POPULACAO = c(1332570, 11451245, 6211423)
  )

head(df_cidades)
  COD_MUNICIPIO           NOME
1       4314902   Porto Alegre
2       3550308      São Paulo
3       3304557 Rio de Janeiro
head(df_populacao)
  COD_MUNICIPIO POPULACAO
1       4314902   1332570
2       3550308  11451245
3       3304557   6211423

Para juntar estes dados, usaremos como chave de identificação presente nas duas tabelas o campo COD_MUNICIPIO. A função usada, merge exige dois argumentos x e y, que são as bases de dados que usaremos para a junção.

df_completo <- merge(x = df_cidades, y = df_populacao,
                     by = "COD_MUNICIPIO")

head(df_completo)
  COD_MUNICIPIO           NOME POPULACAO
1       3304557 Rio de Janeiro   6211423
2       3550308      São Paulo  11451245
3       4314902   Porto Alegre   1332570

Este exemplo é o mais básico, onde os dados presentes em ambas tabelas são das mesmas ciades e também são ligadas por apenas uma chave de identificação. Vejamos um exemplo um pouco mais realista, onde alguns dados não estão presentes em ambas tabelas.

# rbind faz a inclusao de linha nas as bases criadas
df_cidades <- rbind(df_cidades, c('3106200', 'Belo Horizonte'))
df_populacao <- rbind(df_populacao, c('4106902', 1773733))

df_completo <- merge(x = df_cidades, y = df_populacao,
                     by = "COD_MUNICIPIO")

head(df_completo)
  COD_MUNICIPIO           NOME POPULACAO
1       3304557 Rio de Janeiro   6211423
2       3550308      São Paulo  11451245
3       4314902   Porto Alegre   1332570

Veja que os dados de Belo Horizonte e do Município de código 4106902 (Curitiba) não foram inseridos no data frame resultante. Por padrão a função merge faz a junção pelos dados presentes nos dois data frames. Caso desejemos especificar, usamos os parâmetros all.x e all.y.

Usando all.x informamos ao R que desejamos que todas as linhas presentes na base passada como argumento x sejam mantidas. Onde não existirem dados para estas linhas na tabela y serão preenchidos com NA.

df_completo_x <- merge(x = df_cidades, y = df_populacao,
                     by = "COD_MUNICIPIO", all.x = T)

head(df_completo_x)
  COD_MUNICIPIO           NOME POPULACAO
1       3106200 Belo Horizonte      <NA>
2       3304557 Rio de Janeiro   6211423
3       3550308      São Paulo  11451245
4       4314902   Porto Alegre   1332570

De forma análoga, usar all.y informa para que as linhas da base y sejam mantidas.

df_completo_y <- merge(x = df_cidades, y = df_populacao,
                     by = "COD_MUNICIPIO", all.y = T)

head(df_completo_y)
  COD_MUNICIPIO           NOME POPULACAO
1       3304557 Rio de Janeiro   6211423
2       3550308      São Paulo  11451245
3       4106902           <NA>   1773733
4       4314902   Porto Alegre   1332570

Para cruzamento de todas as linhas das duas tabelas usamos o argumento all.

df_completo <- merge(x = df_cidades, y = df_populacao,
                     by = "COD_MUNICIPIO", all = T)

head(df_completo)
  COD_MUNICIPIO           NOME POPULACAO
1       3106200 Belo Horizonte      <NA>
2       3304557 Rio de Janeiro   6211423
3       3550308      São Paulo  11451245
4       4106902           <NA>   1773733
5       4314902   Porto Alegre   1332570

Última atualização: 11/10/2024 - 21:53:29