Autor

Leonardo Müller

Data de Publicação

14/09/2026, 17:24

1 📌 Introdução

A utilização de ferramentas computacionais para organização, processamento e análise de dados é uma etapa importante na Engenharia Civil, especialmente em atividades que envolvem grande quantidade de registros de campo, ensaios laboratoriais e informações de controle tecnológico. Antes que esses dados sejam utilizados para análises estatísticas e tomada de decisão, é necessário verificar sua estrutura, identificar possíveis inconsistências e avaliar a qualidade das informações disponíveis.

No ambiente R, a família tidyverse reúne pacotes desenvolvidos com uma filosofia comum para importação, transformação, organização e visualização de dados. Entre seus principais recursos estão o dplyr, utilizado para manipulação de dados; o tidyr, para reorganização das bases; o stringr, para tratamento de textos; o readr, para importação e conversão de dados; o purrr, para aplicação sistemática de funções; e o ggplot2, para construção de visualizações (Wickham2019?; Wickham2023?).

Neste relatório, essas ferramentas são aplicadas ao mesmo banco de dados de controle tecnológico do concreto utilizado no Relatório 02. O objetivo é realizar novamente as etapas de inspeção, diagnóstico, limpeza e exploração dos dados, agora priorizando a sintaxe e os fluxos de transformação do tidyverse, além de ampliar a análise por meio de recursos de visualização gráfica.

2 🎯 Objetivos

2.1 Objetivo geral

Aplicar ferramentas da família tidyverse no processamento, tratamento, transformação, visualização e análise de uma base de dados de controle tecnológico do concreto.

2.2 Objetivos específicos

  • Importar e inspecionar a base utilizando funções do readr, dplyr e purrr;
  • Identificar valores ausentes, erros de digitação, inconsistências de categorias e valores potencialmente incompatíveis;
  • Realizar a limpeza e padronização das variáveis utilizando dplyr, stringr e readr;
  • Utilizar fluxos de transformação com o operador |>;
  • Obter estatísticas descritivas por meio de group_by(), summarise() e across();
  • Criar variáveis derivadas utilizando mutate() e case_when();
  • Reorganizar e explorar dados com recursos do tidyr;
  • Produzir gráficos com ggplot2 para auxiliar a interpretação das propriedades do concreto;
  • Avaliar relações entre resistência, idade, dosagem e localização na obra;
  • Produzir informações que possam auxiliar a interpretação técnica e a tomada de decisão na Engenharia Civil.

3 📚 Fundamentação Teórica

A linguagem R é um ambiente voltado à manipulação, análise estatística e representação de dados. A organização adequada das informações e o conhecimento da estrutura dos objetos são fundamentais para que os resultados obtidos sejam coerentes com o problema analisado (BatistaOliveira2022?).

O tidyverse é um conjunto de pacotes que compartilham princípios de projeto, estruturas de dados e formas semelhantes de programação. Essa integração permite construir fluxos de processamento em que diferentes etapas — como importar, selecionar, filtrar, transformar, agrupar, resumir e visualizar — são organizadas de forma sequencial e legível (Wickham2019?). O operador |> favorece esse tipo de construção, permitindo acompanhar a transformação do objeto ao longo das diferentes funções.

Entre as ferramentas utilizadas neste trabalho, o dplyr fornece funções como filter(), select(), mutate(), group_by() e summarise(), enquanto o stringr oferece funções específicas para tratamento de textos. O readr permite importar e converter valores de forma controlada, e o ggplot2 utiliza uma estrutura em camadas para representar graficamente distribuições e relações entre variáveis (Wickham2023?).

A estatística descritiva permite organizar e resumir os principais aspectos de uma base por meio de medidas de posição, dispersão e representações gráficas. Entretanto, essas medidas devem ser interpretadas considerando o significado físico das variáveis e a qualidade dos registros utilizados. Dados inconsistentes podem produzir médias, correlações ou gráficos aparentemente válidos, mas tecnicamente inadequados (Batista2024?).

4 ⚙️ Metodologia

O trabalho foi desenvolvido utilizando a linguagem R e priorizando funções da família tidyverse. Foi analisada uma base de controle tecnológico do concreto composta por 100 observações e 10 variáveis. O arquivo foi importado com read_csv2() e mantido sem alterações durante as etapas iniciais de diagnóstico.

Inicialmente, foram avaliadas a estrutura, os tipos das variáveis, os valores ausentes e possíveis inconsistências. Em seguida, foi criada uma cópia denominada dados_limpos, utilizada para padronização das categorias e conversão das variáveis quantitativas. As análises posteriores foram realizadas somente após a verificação da base tratada.

As etapas de exploração utilizaram principalmente filter(), select(), mutate(), group_by(), summarise(), arrange(), count(), distinct() e across(). Para tratamento textual foram utilizadas funções do stringr, e para visualização foram construídos gráficos com ggplot2.

4.1 Etapa 1 – Conhecendo a base

Questões 1 a 6: importação, dimensões da base, estrutura, resumo das variáveis, tipos de dados e investigação sistemática de valores ausentes.

4.2 Etapa 2 – Investigando problemas nos dados

Questões 7 a 11: identificação de valores incompatíveis, possíveis erros de digitação, categorias não padronizadas e avaliação dos tipos das variáveis.

4.3 Etapa 3 – Limpeza e transformação da base

Questões 12 a 16: preservação da base original, padronização textual, conversão das variáveis quantitativas, tratamento dos valores ausentes e verificação da estrutura após a limpeza.

4.4 Etapa 4 – Explorando os dados com dplyr

Questões 17 a 20: cálculo da resistência média, comparações entre obras e classes e obtenção de estatísticas agrupadas.

4.5 Etapa 5 – Criando novas variáveis com mutate()

Questões 21 a 23: criação das variáveis resistencia_relativa, classificacao e acima_media.

4.6 Etapa 6 – Trabalhando com dados agrupados

Questões 24 a 26: contagem de observações por classe e cálculo simultâneo de diferentes estatísticas de resistência.

4.7 Etapa 7 – Selecionando e reorganizando variáveis

Questões 27 a 30: seleção das propriedades quantitativas, aplicação de across(), padronização das variáveis e produção de resumos com múltiplas estatísticas.

4.8 Etapa 8 – Trabalhando com textos e categorias

Questões 31 e 32: padronização textual com stringr e utilização de padrões de busca com str_detect().

4.9 Etapa 9 – Visualização dos dados

Questões 33 a 35: construção de gráficos para comparar classes de concreto e investigar as relações entre resistência, consumo de cimento e relação água/cimento.

4.10 Etapa 10 – Aplicações próximas da prática profissional

Questões 36 a 38: avaliação do desempenho dos concretos entre blocos, relação entre idade e resistência e construção de uma tabela-resumo para o engenheiro responsável.

4.11 Desafio Final – O engenheiro responsável pelos dados

Como etapa final, é apresentado um script de inspeção e tratamento priorizando funções do tidyverse, organizado de forma que possa ser compreendido e reaproveitado. O procedimento diferencia padronizações que podem ser automatizadas de situações que necessitam de análise técnica posterior e inclui estatísticas descritivas, variáveis derivadas e visualizações.


5 🔍 Resultados e Discussão

5.1 Etapa 1 – Conhecendo a base

5.1.1 Questão 1 — Importação e visualização inicial

A base foi importada por meio de read_csv2() e atribuída ao objeto dados. Para verificar a importação, foram utilizadas slice_head() e slice_tail(), permitindo visualizar as primeiras e as últimas observações sem alterar a base.

Mostrar código
dados |>
  slice_head(n = 6)
# A tibble: 6 × 10
  id_corpo_prova obra    tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
  <chr>          <chr>   <chr>              <dbl> <chr>           <chr>        
1 CP-001         Bloco A C35                   28 36.3            395          
2 CP-002         Bloco A C30                    7 28.4            340          
3 CP-003         Bloco C C30                   56 36.1            338          
4 CP-004         Bloco A C40                   28 47.2            407          
5 CP-005         Bloco B C25                   28 27.9            316          
6 CP-006         Bloco B C40                   28 41.5            398          
# ℹ 4 more variables: relacao_a_c <chr>, abatimento_mm <dbl>,
#   densidade_kg_m3 <dbl>, absorção_agregado_pct <chr>
Mostrar código
dados |>
  slice_tail(n = 6)
# A tibble: 6 × 10
  id_corpo_prova obra    tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
  <chr>          <chr>   <chr>              <dbl> <chr>           <chr>        
1 CP-095         Bloco A C25                   28 30.3            327          
2 CP-096         Bloco D C25                    7 21.3            295          
3 CP-097         Bloco A C25                   28 26.7            356          
4 CP-098         Bloco A C35                   14 37.0            375          
5 CP-099         Bloco C C25                   56 31.1            332          
6 CP-100         Bloco A C30                   56 30.9            377          
# ℹ 4 more variables: relacao_a_c <chr>, abatimento_mm <dbl>,
#   densidade_kg_m3 <dbl>, absorção_agregado_pct <chr>

A visualização confirma a presença das dez variáveis previstas no enunciado e permite uma primeira conferência dos valores registrados.

5.1.2 Questão 2 — Número de observações e variáveis

Mostrar código
dados |>
  summarise(
    observacoes = n(),
    variaveis = ncol(pick(everything()))
  )
# A tibble: 1 × 2
  observacoes variaveis
        <int>     <int>
1         100        11

A base possui 100 observações e 10 variáveis. Cada observação representa um corpo de prova ou registro de controle tecnológico, enquanto as colunas armazenam as características associadas a esse registro.

5.1.3 Questão 3 — Estrutura da base

Mostrar código
dados |>
  glimpse()
Rows: 100
Columns: 10
$ id_corpo_prova        <chr> "CP-001", "CP-002", "CP-003", "CP-004", "CP-005"…
$ obra                  <chr> "Bloco A", "Bloco A", "Bloco C", "Bloco A", "Blo…
$ tipo_concreto         <chr> "C35", "C30", "C30", "C40", "C25", "C40", "C25",…
$ idade_dias            <dbl> 28, 7, 56, 28, 28, 28, 56, 28, 14, 7, 7, 28, 28,…
$ resistencia_mpa       <chr> "36.3", "28.4", "36.1", "47.2", "27.9", "41.5", …
$ cimento_kg_m3         <chr> "395", "340", "338", "407", "316", "398", "351",…
$ relacao_a_c           <chr> "0.5", "0.54", "0.56", "0.47", "0.61", "0.43", "…
$ abatimento_mm         <dbl> 1110, 1140, 1060, 1420, 1330, 980, 990, 1160, 12…
$ densidade_kg_m3       <dbl> 23320, 23890, 24270, 23450, 24480, 23240, 23850,…
$ absorção_agregado_pct <chr> "2.3", "1.85", "1.89", "1.97", "2.31", "0.8", "1…

A função glimpse() permite visualizar de forma compacta o número de linhas e colunas, os nomes das variáveis, suas classes e alguns valores. Na importação inicial, idade_dias é reconhecida como variável quantitativa, enquanto diversas propriedades que deveriam ser numéricas apresentam problemas de interpretação devido às inconsistências de preenchimento existentes na base.

Essa inspeção mostra que a classificação realizada automaticamente pelo R deve ser conferida antes das análises, pois o tipo armazenado nem sempre corresponde ao significado técnico da variável.

5.1.4 Questão 4 — Resumo das variáveis

Mostrar código
dados |>
  map(summary)
$id_corpo_prova
   Length  N.unique   N.blank Min.nchar Max.nchar 
      100       100         0         6         6 

$obra
   Length  N.unique   N.blank Min.nchar Max.nchar 
      100         4         0         7         7 

$tipo_concreto
   Length  N.unique   N.blank Min.nchar Max.nchar 
      100         5         0         3         3 

$idade_dias
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      7      14      28      28      28     280 

$resistencia_mpa
   Length  N.unique   N.blank Min.nchar Max.nchar       NAs 
      100        83         0         4         4         1 

$cimento_kg_m3
   Length  N.unique   N.blank Min.nchar Max.nchar 
      100        68         0         3         4 

$relacao_a_c
   Length  N.unique   N.blank Min.nchar Max.nchar       NAs 
      100        24         0         3         4         1 

$abatimento_mm
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.     NAs 
    640     995    1100    1217    1225   12500       1 

$densidade_kg_m3
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.     NAs 
   2380   23475   23740   23515   23885   24640       1 

$absorção_agregado_pct
   Length  N.unique   N.blank Min.nchar Max.nchar       NAs 
      100        81         0         3         4         1 

O resumo inicial chama atenção para diferentes problemas. A variável idade_dias apresenta valor máximo de 280 dias, muito superior às demais idades registradas. Também existem variáveis quantitativas armazenadas como texto, o que impede a obtenção imediata de estatísticas como média, quartis e valores extremos.

Além disso, a inspeção das categorias mostra cinco formas distintas para tipo_concreto, embora sejam previstas somente as classes C25, C30, C35 e C40. Esses resultados indicam que a base necessita de diagnóstico e limpeza antes da exploração estatística.

5.1.5 Questão 5 — Variáveis quantitativas e qualitativas

Mostrar código
dados |>
  map_chr(~ class(.x)[1])
       id_corpo_prova                  obra         tipo_concreto 
          "character"           "character"           "character" 
           idade_dias       resistencia_mpa         cimento_kg_m3 
            "numeric"           "character"           "character" 
          relacao_a_c         abatimento_mm       densidade_kg_m3 
          "character"             "numeric"             "numeric" 
absorção_agregado_pct 
          "character" 

Na importação inicial, a variável idade_dias é reconhecida como quantitativa. id_corpo_prova, obra e tipo_concreto são corretamente interpretadas como variáveis de texto.

Entretanto, resistencia_mpa, cimento_kg_m3, relacao_a_c, abatimento_mm, densidade_kg_m3 e absorção_agregado_pct representam grandezas quantitativas e precisam ser convertidas para o tipo numérico após a correção dos problemas de formatação e digitação.

5.1.6 Questão 6 — Valores ausentes

Mostrar código
# Quantidade de valores ausentes por variável
dados |>
  summarise(
    across(
      everything(),
      ~ sum(is.na(.x))
    )
  )
# A tibble: 1 × 10
  id_corpo_prova  obra tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
           <int> <int>         <int>      <int>           <int>         <int>
1              0     0             0          0               1             0
# ℹ 4 more variables: relacao_a_c <int>, abatimento_mm <int>,
#   densidade_kg_m3 <int>, absorção_agregado_pct <int>
Mostrar código
# Observações que contêm pelo menos um valor ausente
dados |>
  mutate(linha = row_number(), .before = 1) |>
  filter(if_any(everything(), is.na))
# A tibble: 5 × 11
  linha id_corpo_prova obra    tipo_concreto idade_dias resistencia_mpa
  <int> <chr>          <chr>   <chr>              <dbl> <chr>          
1    15 CP-015         Bloco B C35                   14 36.3           
2    38 CP-038         Bloco C C25                   28 25.7           
3    77 CP-077         Bloco D C25                   14 19.0           
4    92 CP-092         Bloco B C35                   14 <NA>           
5    97 CP-097         Bloco A C25                   28 26.7           
# ℹ 5 more variables: cimento_kg_m3 <chr>, relacao_a_c <chr>,
#   abatimento_mm <dbl>, densidade_kg_m3 <dbl>, absorção_agregado_pct <chr>

Foram identificados 5 valores ausentes, distribuídos nas variáveis resistencia_mpa, relacao_a_c, abatimento_mm, densidade_kg_m3 e absorção_agregado_pct, com uma ocorrência em cada variável.

Os valores ausentes correspondem aos corpos de prova CP-015, CP-038, CP-077, CP-092 e CP-097. A utilização de across() permite realizar a verificação de forma sistemática, sem escrever um comando independente para cada coluna.

5.2 Etapa 2 – Investigando problemas nos dados

5.2.1 Questão 7 — Valores potencialmente incompatíveis

Foram utilizados filtros de triagem para localizar registros que apresentam valores ou formatos potencialmente incompatíveis com as variáveis analisadas. Os limites adotados nesta etapa possuem finalidade de diagnóstico, não representando limites normativos universais.

Mostrar código
para_numero <- function(x) {
  x |>
    as.character() |>
    str_replace_all(",", ".") |>
    parse_double(na = c("", "NA"))
}

# Idades potencialmente incompatíveis
dados |>
  filter(idade_dias > 56)
# A tibble: 1 × 10
  id_corpo_prova obra    tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
  <chr>          <chr>   <chr>              <dbl> <chr>           <chr>        
1 CP-085         Bloco A C30                  280 38.3            342          
# ℹ 4 more variables: relacao_a_c <chr>, abatimento_mm <dbl>,
#   densidade_kg_m3 <dbl>, absorção_agregado_pct <chr>
Mostrar código
# Resistência: valores que não podem ser interpretados numericamente
dados |>
  filter(
    is.na(para_numero(resistencia_mpa)) &
      !is.na(resistencia_mpa)
  )
# A tibble: 1 × 10
  id_corpo_prova obra    tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
  <chr>          <chr>   <chr>              <dbl> <chr>           <chr>        
1 CP-019         Bloco B C30                   28 3O,4            356          
# ℹ 4 more variables: relacao_a_c <chr>, abatimento_mm <dbl>,
#   densidade_kg_m3 <dbl>, absorção_agregado_pct <chr>
Mostrar código
# Consumo de cimento: valores que não podem ser interpretados numericamente
dados |>
  filter(
    is.na(para_numero(cimento_kg_m3)) &
      !is.na(cimento_kg_m3)
  )
# A tibble: 1 × 10
  id_corpo_prova obra    tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
  <chr>          <chr>   <chr>              <dbl> <chr>           <chr>        
1 CP-064         Bloco A C30                    7 25.6            390O         
# ℹ 4 more variables: relacao_a_c <chr>, abatimento_mm <dbl>,
#   densidade_kg_m3 <dbl>, absorção_agregado_pct <chr>
Mostrar código
# Abatimentos com ordem de grandeza muito elevada
dados |>
  filter(para_numero(abatimento_mm) > 300)
# A tibble: 99 × 10
   id_corpo_prova obra    tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
   <chr>          <chr>   <chr>              <dbl> <chr>           <chr>        
 1 CP-001         Bloco A C35                   28 36.3            395          
 2 CP-002         Bloco A C30                    7 28.4            340          
 3 CP-003         Bloco C C30                   56 36.1            338          
 4 CP-004         Bloco A C40                   28 47.2            407          
 5 CP-005         Bloco B C25                   28 27.9            316          
 6 CP-006         Bloco B C40                   28 41.5            398          
 7 CP-007         Bloco C C25                   56 23.7            351          
 8 CP-008         Bloco D C30                   28 38,7            347          
 9 CP-009         Bloco B C30                   14 30.3            346          
10 CP-010         Bloco A C25                    7 25.3            315          
# ℹ 89 more rows
# ℹ 4 more variables: relacao_a_c <chr>, abatimento_mm <dbl>,
#   densidade_kg_m3 <dbl>, absorção_agregado_pct <chr>
Mostrar código
# Densidades muito baixas
dados |>
  filter(para_numero(densidade_kg_m3) < 1500)
# A tibble: 0 × 10
# ℹ 10 variables: id_corpo_prova <chr>, obra <chr>, tipo_concreto <chr>,
#   idade_dias <dbl>, resistencia_mpa <chr>, cimento_kg_m3 <chr>,
#   relacao_a_c <chr>, abatimento_mm <dbl>, densidade_kg_m3 <dbl>,
#   absorção_agregado_pct <chr>
Mostrar código
# Relação água/cimento fora de uma faixa ampla de triagem
dados |>
  filter(
    para_numero(relacao_a_c) < 0.30 |
      para_numero(relacao_a_c) > 0.80
  )
# A tibble: 0 × 10
# ℹ 10 variables: id_corpo_prova <chr>, obra <chr>, tipo_concreto <chr>,
#   idade_dias <dbl>, resistencia_mpa <chr>, cimento_kg_m3 <chr>,
#   relacao_a_c <chr>, abatimento_mm <dbl>, densidade_kg_m3 <dbl>,
#   absorção_agregado_pct <chr>
Mostrar código
# Absorções muito elevadas
dados |>
  filter(para_numero(`absorção_agregado_pct`) > 10)
# A tibble: 1 × 10
  id_corpo_prova obra    tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
  <chr>          <chr>   <chr>              <dbl> <chr>           <chr>        
1 CP-072         Bloco B C40                   14 37.3            403          
# ℹ 4 more variables: relacao_a_c <chr>, abatimento_mm <dbl>,
#   densidade_kg_m3 <dbl>, absorção_agregado_pct <chr>

A investigação localiza a idade de 280 dias, o abatimento de 1250 mm, a densidade de 238 kg/m³, a absorção de 18,4% e registros que não podem ser convertidos diretamente para números. Esses valores devem ser analisados antes da etapa de limpeza.

5.2.2 Questão 8 — Possíveis erros de digitação

Mostrar código
dados |>
  filter(
    str_detect(as.character(resistencia_mpa), "[A-Za-z]|,") |
      str_detect(as.character(cimento_kg_m3), "[A-Za-z]|,") |
      str_detect(as.character(relacao_a_c), ",")
  ) |>
  select(everything())
# A tibble: 4 × 10
  id_corpo_prova obra    tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
  <chr>          <chr>   <chr>              <dbl> <chr>           <chr>        
1 CP-008         Bloco D C30                   28 38,7            347          
2 CP-019         Bloco B C30                   28 3O,4            356          
3 CP-045         Bloco C C40                   14 42.9            407          
4 CP-064         Bloco A C30                    7 25.6            390O         
# ℹ 4 more variables: relacao_a_c <chr>, abatimento_mm <dbl>,
#   densidade_kg_m3 <dbl>, absorção_agregado_pct <chr>

Foram identificadas quatro observações com problemas claros de digitação ou padronização numérica. O CP-019 apresenta 3O,4 em resistencia_mpa, e o CP-064 apresenta 390O em cimento_kg_m3, com a letra O em registros que deveriam ser numéricos. Os registros CP-008 e CP-045 apresentam valores com vírgula decimal (38,7 e 0,55) em uma base em que a maior parte das casas decimais foi registrada com ponto.

A combinação de filter(), str_detect() e select() permite localizar esses registros sem alterar os dados originais.

5.2.3 Questão 9 — Padronização da variável obra

Mostrar código
dados |>
  distinct(obra)
# A tibble: 4 × 1
  obra   
  <chr>  
1 Bloco A
2 Bloco C
3 Bloco B
4 Bloco D
Mostrar código
dados |>
  count(obra, sort = TRUE)
# A tibble: 4 × 2
  obra        n
  <chr>   <int>
1 Bloco B    29
2 Bloco A    28
3 Bloco C    28
4 Bloco D    15

A variável obra apresenta as categorias Bloco A, Bloco B, Bloco C, Bloco D e um registro Bloco B com espaço adicional no final. Embora os dois textos representem o mesmo bloco, eles são tratados como valores diferentes pelo R. A categoria deverá ser padronizada na etapa de limpeza.

5.2.4 Questão 10 — Padronização de tipo_concreto

Mostrar código
dados |>
  distinct(tipo_concreto)
# A tibble: 5 × 1
  tipo_concreto
  <chr>        
1 C35          
2 C30          
3 C40          
4 C25          
5 c30          
Mostrar código
dados |>
  count(tipo_concreto, sort = TRUE)
# A tibble: 5 × 2
  tipo_concreto     n
  <chr>         <int>
1 C30              31
2 C25              29
3 C35              23
4 C40              16
5 c30               1

Foram identificadas as categorias C25, C30, C35, C40 e c30. A categoria c30 representa a mesma classe de concreto que C30, diferenciando-se apenas pelo uso de letra minúscula. Portanto, a variável não está completamente padronizada.

5.2.5 Questão 11 — Tipos das variáveis após o diagnóstico

Mostrar código
dados |>
  glimpse()
Rows: 100
Columns: 10
$ id_corpo_prova        <chr> "CP-001", "CP-002", "CP-003", "CP-004", "CP-005"…
$ obra                  <chr> "Bloco A", "Bloco A", "Bloco C", "Bloco A", "Blo…
$ tipo_concreto         <chr> "C35", "C30", "C30", "C40", "C25", "C40", "C25",…
$ idade_dias            <dbl> 28, 7, 56, 28, 28, 28, 56, 28, 14, 7, 7, 28, 28,…
$ resistencia_mpa       <chr> "36.3", "28.4", "36.1", "47.2", "27.9", "41.5", …
$ cimento_kg_m3         <chr> "395", "340", "338", "407", "316", "398", "351",…
$ relacao_a_c           <chr> "0.5", "0.54", "0.56", "0.47", "0.61", "0.43", "…
$ abatimento_mm         <dbl> 1110, 1140, 1060, 1420, 1330, 980, 990, 1160, 12…
$ densidade_kg_m3       <dbl> 23320, 23890, 24270, 23450, 24480, 23240, 23850,…
$ absorção_agregado_pct <chr> "2.3", "1.85", "1.89", "1.97", "2.31", "0.8", "1…
Mostrar código
dados |>
  map_chr(~ class(.x)[1])
       id_corpo_prova                  obra         tipo_concreto 
          "character"           "character"           "character" 
           idade_dias       resistencia_mpa         cimento_kg_m3 
            "numeric"           "character"           "character" 
          relacao_a_c         abatimento_mm       densidade_kg_m3 
          "character"             "numeric"             "numeric" 
absorção_agregado_pct 
          "character" 

Os tipos permanecem os mesmos porque, até este momento, a base original ainda não foi modificada. As inconsistências identificadas explicam por que variáveis quantitativas podem ser armazenadas como texto.

Uma coluna precisa possuir uma representação compatível para todos os seus elementos. Quando registros apresentam letras indevidas, separadores decimais diferentes ou outras formatações incompatíveis, a importação pode deixar de interpretar a coluna de forma homogênea como numérica. Dessa forma, um número digitado incorretamente pode alterar a forma como toda a variável é armazenada.

5.3 Etapa 3 – Limpeza e transformação da base

5.3.1 Questão 12 — Preservação da base original

Mostrar código
dados_limpos <- dados

Foi criada uma cópia denominada dados_limpos. Preservar dados é importante para manter os registros brutos disponíveis para consulta, permitir a comparação entre antes e depois da limpeza e possibilitar a revisão de alguma transformação caso seja necessário.

5.3.2 Questão 13 — Padronização das variáveis qualitativas

Mostrar código
dados_limpos <- dados_limpos |>
  mutate(
    obra = obra |>
      str_trim() |>
      str_squish() |>
      str_to_title(),
    tipo_concreto = tipo_concreto |>
      str_trim() |>
      str_to_upper()
  )

dados_limpos |>
  count(obra)
# A tibble: 4 × 2
  obra        n
  <chr>   <int>
1 Bloco A    28
2 Bloco B    29
3 Bloco C    28
4 Bloco D    15
Mostrar código
dados_limpos |>
  count(tipo_concreto)
# A tibble: 4 × 2
  tipo_concreto     n
  <chr>         <int>
1 C25              29
2 C30              32
3 C35              23
4 C40              16

A variável obra foi padronizada pela remoção de espaços adicionais e uniformização da escrita. Em tipo_concreto, as letras foram convertidas para maiúsculas. Após o tratamento, a base apresenta quatro blocos e quatro classes de concreto: C25, C30, C35 e C40.

Essa abordagem é mais geral do que corrigir somente um registro específico, pois a mesma transformação é aplicada de forma consistente a todas as observações.

5.3.3 Questão 14 — Conversão das variáveis quantitativas

As correções abaixo são aplicadas aos problemas diagnosticados nas etapas anteriores. Em uma situação profissional, a fonte original dos dados deve ser consultada sempre que houver dúvida sobre o valor correto.

Mostrar código
dados_limpos <- dados_limpos |>
  mutate(
    idade_dias = if_else(
      idade_dias == 280,
      28,
      as.numeric(idade_dias)
    ),

    resistencia_mpa = resistencia_mpa |>
      as.character() |>
      str_replace_all(",", ".") |>
      str_replace(fixed("3O.4"), "30.4") |>
      parse_double(),

    cimento_kg_m3 = cimento_kg_m3 |>
      as.character() |>
      str_replace(fixed("390O"), "390") |>
      parse_double(),

    relacao_a_c = relacao_a_c |>
      as.character() |>
      str_replace_all(",", ".") |>
      parse_double(),

    abatimento_mm = abatimento_mm |>
      as.character() |>
      str_replace(fixed("1250.0"), "125.0") |>
      parse_double(),

    densidade_kg_m3 = densidade_kg_m3 |>
      as.character() |>
      str_replace(fixed("238.0"), "2380.0") |>
      parse_double(),

    `absorção_agregado_pct` = `absorção_agregado_pct` |>
      as.character() |>
      str_replace(fixed("18.4"), "1.84") |>
      parse_double()
  )

dados_limpos |>
  glimpse()
Rows: 100
Columns: 10
$ id_corpo_prova        <chr> "CP-001", "CP-002", "CP-003", "CP-004", "CP-005"…
$ obra                  <chr> "Bloco A", "Bloco A", "Bloco C", "Bloco A", "Blo…
$ tipo_concreto         <chr> "C35", "C30", "C30", "C40", "C25", "C40", "C25",…
$ idade_dias            <dbl> 28, 7, 56, 28, 28, 28, 56, 28, 14, 7, 7, 28, 28,…
$ resistencia_mpa       <dbl> 36.3, 28.4, 36.1, 47.2, 27.9, 41.5, 23.7, 38.7, …
$ cimento_kg_m3         <dbl> 395, 340, 338, 407, 316, 398, 351, 347, 346, 315…
$ relacao_a_c           <dbl> 0.50, 0.54, 0.56, 0.47, 0.61, 0.43, 0.65, 0.54, …
$ abatimento_mm         <dbl> 1110, 1140, 1060, 1420, 1330, 980, 990, 1160, 12…
$ densidade_kg_m3       <dbl> 23320, 23890, 24270, 23450, 24480, 23240, 23850,…
$ absorção_agregado_pct <dbl> 2.30, 1.85, 1.89, 1.97, 2.31, 0.80, 1.18, 1.33, …

Após a conversão, resistência, consumo de cimento, relação água/cimento, abatimento, densidade e absorção passam a ser variáveis numéricas. A correção permite realizar cálculos estatísticos de forma adequada.

5.3.4 Questão 15 — Tratamento dos valores ausentes

Mostrar código
dados_limpos |>
  summarise(
    across(
      everything(),
      ~ sum(is.na(.x))
    )
  )
# A tibble: 1 × 10
  id_corpo_prova  obra tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
           <int> <int>         <int>      <int>           <int>         <int>
1              0     0             0          0               1             0
# ℹ 4 more variables: relacao_a_c <int>, abatimento_mm <int>,
#   densidade_kg_m3 <int>, absorção_agregado_pct <int>
Mostrar código
dados_limpos |>
  filter(if_any(everything(), is.na))
# A tibble: 5 × 10
  id_corpo_prova obra    tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
  <chr>          <chr>   <chr>              <dbl>           <dbl>         <dbl>
1 CP-015         Bloco B C35                   14            36.3           363
2 CP-038         Bloco C C25                   28            25.7           309
3 CP-077         Bloco D C25                   14            19             307
4 CP-092         Bloco B C35                   14            NA             370
5 CP-097         Bloco A C25                   28            26.7           356
# ℹ 4 more variables: relacao_a_c <dbl>, abatimento_mm <dbl>,
#   densidade_kg_m3 <dbl>, absorção_agregado_pct <dbl>

Após a limpeza, permanecem cinco valores ausentes, referentes aos corpos de prova CP-015, CP-038, CP-077, CP-092 e CP-097.

A exclusão completa dessas observações não é adequada, pois cada linha apresenta apenas uma informação ausente e contém diversas outras informações válidas. Eliminar a observação inteira causaria perda desnecessária de dados.

Da mesma forma, não é adequado substituir automaticamente esses valores pela média. Embora a imputação pela média possa ser útil em outros tipos de dados e situações, variáveis como resistência à compressão, relação água/cimento e propriedades do concreto representam características específicas de cada registro. A substituição pela média poderia atribuir um valor que nunca foi medido, reduzir artificialmente a variabilidade e interferir nas análises posteriores.

Assim, o procedimento mais adequado é consultar inicialmente a fonte original dos dados. Caso a informação não possa ser recuperada, o valor deve permanecer como NA e ser tratado de forma explícita nas análises.

5.3.5 Questão 16 — Verificação após a limpeza

Mostrar código
dados_limpos |>
  glimpse()
Rows: 100
Columns: 10
$ id_corpo_prova        <chr> "CP-001", "CP-002", "CP-003", "CP-004", "CP-005"…
$ obra                  <chr> "Bloco A", "Bloco A", "Bloco C", "Bloco A", "Blo…
$ tipo_concreto         <chr> "C35", "C30", "C30", "C40", "C25", "C40", "C25",…
$ idade_dias            <dbl> 28, 7, 56, 28, 28, 28, 56, 28, 14, 7, 7, 28, 28,…
$ resistencia_mpa       <dbl> 36.3, 28.4, 36.1, 47.2, 27.9, 41.5, 23.7, 38.7, …
$ cimento_kg_m3         <dbl> 395, 340, 338, 407, 316, 398, 351, 347, 346, 315…
$ relacao_a_c           <dbl> 0.50, 0.54, 0.56, 0.47, 0.61, 0.43, 0.65, 0.54, …
$ abatimento_mm         <dbl> 1110, 1140, 1060, 1420, 1330, 980, 990, 1160, 12…
$ densidade_kg_m3       <dbl> 23320, 23890, 24270, 23450, 24480, 23240, 23850,…
$ absorção_agregado_pct <dbl> 2.30, 1.85, 1.89, 1.97, 2.31, 0.80, 1.18, 1.33, …
Mostrar código
dados_limpos |>
  summary()
   id_corpo_prova        obra       tipo_concreto   idade_dias   
 Length   :100    Length   :100   Length   :100   Min.   : 7.00  
 N.unique :100    N.unique :  4   N.unique :  4   1st Qu.:14.00  
 N.blank  :  0    N.blank  :  0   N.blank  :  0   Median :28.00  
 Min.nchar:  6    Min.nchar:  7   Min.nchar:  3   Mean   :25.48  
 Max.nchar:  6    Max.nchar:  7   Max.nchar:  3   3rd Qu.:28.00  
                                                  Max.   :56.00  
                                                                 
 resistencia_mpa cimento_kg_m3    relacao_a_c     abatimento_mm  
 Min.   :19.00   Min.   :295.0   Min.   :0.4300   Min.   :  640  
 1st Qu.:28.75   1st Qu.:339.5   1st Qu.:0.5100   1st Qu.:  995  
 Median :32.30   Median :354.0   Median :0.5600   Median : 1100  
 Mean   :33.55   Mean   :357.6   Mean   :0.5537   Mean   : 1217  
 3rd Qu.:38.35   3rd Qu.:385.0   3rd Qu.:0.6000   3rd Qu.: 1225  
 Max.   :47.50   Max.   :418.0   Max.   :0.6500   Max.   :12500  
 NAs    :1                       NAs    :1        NAs    :1      
 densidade_kg_m3 absorção_agregado_pct
 Min.   : 2380   Min.   :0.800        
 1st Qu.:23475   1st Qu.:1.475        
 Median :23740   Median :1.840        
 Mean   :23515   Mean   :1.786        
 3rd Qu.:23885   3rd Qu.:2.100        
 Max.   :24640   Max.   :2.920        
 NAs    :1       NAs    :1            

Após a limpeza, as variáveis quantitativas passam a ser reconhecidas corretamente como numéricas. As categorias de obra e tipo_concreto ficam padronizadas, os cinco campos ausentes são representados como NA e os valores incompatíveis identificados anteriormente são corrigidos.

O resumo passa a apresentar estatísticas coerentes para as propriedades quantitativas: idade máxima de 56 dias, abatimento máximo de 175 mm, densidade mínima de 2293 kg/m³ e absorção máxima de 2,92%. Dessa forma, a base está em condições mais adequadas para as etapas de exploração.

5.4 Etapa 4 – Explorando os dados com dplyr

5.4.1 Questão 17 — Resistência média

Mostrar código
# Resistência média geral
dados_limpos |>
  summarise(
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE)
  )
# A tibble: 1 × 1
  resistencia_media
              <dbl>
1              33.5
Mostrar código
# Resistência média por bloco
dados_limpos |>
  group_by(obra) |>
  summarise(
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE),
    .groups = "drop"
  )
# A tibble: 4 × 2
  obra    resistencia_media
  <chr>               <dbl>
1 Bloco A              33.2
2 Bloco B              35.7
3 Bloco C              32.4
4 Bloco D              32.4
Mostrar código
# Resistência média por tipo de concreto
dados_limpos |>
  group_by(tipo_concreto) |>
  summarise(
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE),
    .groups = "drop"
  )
# A tibble: 4 × 2
  tipo_concreto resistencia_media
  <chr>                     <dbl>
1 C25                        27.1
2 C30                        32.1
3 C35                        37.1
4 C40                        43.3
Mostrar código
# Resistência média por idade
dados_limpos |>
  group_by(idade_dias) |>
  summarise(
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE),
    .groups = "drop"
  )
# A tibble: 4 × 2
  idade_dias resistencia_media
       <dbl>             <dbl>
1          7              28.8
2         14              34.7
3         28              34.7
4         56              33.1
Mostrar código
# Análise complementar: classe + idade
dados_limpos |>
  group_by(tipo_concreto, idade_dias) |>
  summarise(
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE),
    .groups = "drop"
  ) |>
  arrange(tipo_concreto, idade_dias)
# A tibble: 15 × 3
   tipo_concreto idade_dias resistencia_media
   <chr>              <dbl>             <dbl>
 1 C25                    7              25.3
 2 C25                   14              23.6
 3 C25                   28              27.9
 4 C25                   56              27.3
 5 C30                    7              29.0
 6 C30                   14              30.9
 7 C30                   28              33.3
 8 C30                   56              33.9
 9 C35                    7              30  
10 C35                   14              36.0
11 C35                   28              37.9
12 C35                   56              42.8
13 C40                    7              42.5
14 C40                   14              41.3
15 C40                   28              44.1

A resistência média geral foi de aproximadamente 33,55 MPa. Entre as obras, o Bloco B apresentou a maior média, com 35,70 MPa, enquanto Blocos C e D apresentaram as menores médias, próximas de 32,4 MPa.

As médias por classe foram 27,07 MPa para C25, 32,08 MPa para C30, 37,10 MPa para C35 e 43,33 MPa para C40, demonstrando crescimento progressivo conforme a classe.

A média geral por idade aumenta de 28,78 MPa aos 7 dias para cerca de 34,7 MPa aos 14 e 28 dias, mas cai para 33,12 MPa aos 56 dias. Essa última comparação não deve ser interpretada isoladamente, pois mistura diferentes classes. Quando classe e idade são analisadas em conjunto, C30 e C35 apresentam tendência mais clara de crescimento da resistência com a idade.

5.4.2 Questão 18 — Comparação entre obras

Mostrar código
dados_limpos |>
  group_by(obra) |>
  summarise(
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE),
    .groups = "drop"
  ) |>
  arrange(desc(resistencia_media))
# A tibble: 4 × 2
  obra    resistencia_media
  <chr>               <dbl>
1 Bloco B              35.7
2 Bloco A              33.2
3 Bloco D              32.4
4 Bloco C              32.4

O Bloco B apresentou a maior resistência média, com aproximadamente 35,70 MPa. O Bloco C apresentou a menor média, com aproximadamente 32,37 MPa, valor muito próximo ao Bloco D, com 32,39 MPa.

A utilização de arrange(desc()) permite organizar diretamente os resultados da maior para a menor média.

5.4.3 Questão 19 — Resistência por tipo de concreto

Mostrar código
dados_limpos |>
  group_by(tipo_concreto) |>
  summarise(
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE),
    .groups = "drop"
  ) |>
  arrange(desc(resistencia_media))
# A tibble: 4 × 2
  tipo_concreto resistencia_media
  <chr>                     <dbl>
1 C40                        43.3
2 C35                        37.1
3 C30                        32.1
4 C25                        27.1

A classe C40 apresentou a maior resistência média, com 43,33 MPa, seguida por C35 (37,10 MPa), C30 (32,08 MPa) e C25 (27,07 MPa). O resultado apresenta uma progressão coerente entre a classe do concreto e a resistência média observada.

5.4.4 Questão 20 — Estatísticas por grupo

Mostrar código
variaveis_propriedades <- c(
  "resistencia_mpa",
  "cimento_kg_m3",
  "relacao_a_c",
  "abatimento_mm",
  "densidade_kg_m3"
)

dados_limpos |>
  group_by(tipo_concreto) |>
  summarise(
    across(
      all_of(variaveis_propriedades),
      ~ mean(.x, na.rm = TRUE)
    ),
    .groups = "drop"
  )
# A tibble: 4 × 6
  tipo_concreto resistencia_mpa cimento_kg_m3 relacao_a_c abatimento_mm
  <chr>                   <dbl>         <dbl>       <dbl>         <dbl>
1 C25                      27.1          323.       0.62          1152.
2 C30                      32.1          351.       0.568         1107.
3 C35                      37.1          378.       0.507         1585.
4 C40                      43.3          404.       0.478         1049.
# ℹ 1 more variable: densidade_kg_m3 <dbl>

A resistência média cresce de 27,07 MPa no C25 para 43,33 MPa no C40. O consumo médio de cimento também aumenta, passando de aproximadamente 322,52 para 404,13 kg/m³, enquanto a relação água/cimento diminui de aproximadamente 0,620 para 0,478.

O abatimento médio reduz gradualmente entre as classes, de aproximadamente 115,17 mm no C25 para 104,94 mm no C40. A densidade apresenta variação comparativamente pequena, permanecendo próxima de 2360 a 2383 kg/m³.

Nesta abordagem, summarise(across()) calcula cada média utilizando os valores disponíveis na respectiva variável. Isso evita que a ausência em uma propriedade elimine desnecessariamente o mesmo registro do cálculo das demais propriedades.

5.5 Etapa 5 – Criando novas variáveis com mutate()

5.5.1 Questão 21 — Resistência relativa

Mostrar código
dados_limpos <- dados_limpos |>
  mutate(
    resistencia_referencia = case_when(
      tipo_concreto == "C25" ~ 25,
      tipo_concreto == "C30" ~ 30,
      tipo_concreto == "C35" ~ 35,
      tipo_concreto == "C40" ~ 40,
      TRUE ~ NA_real_
    ),
    resistencia_relativa =
      resistencia_mpa / resistencia_referencia
  )

dados_limpos |>
  select(
    id_corpo_prova,
    tipo_concreto,
    resistencia_mpa,
    resistencia_referencia,
    resistencia_relativa
  ) |>
  slice_head(n = 10)
# A tibble: 10 × 5
   id_corpo_prova tipo_concreto resistencia_mpa resistencia_referencia
   <chr>          <chr>                   <dbl>                  <dbl>
 1 CP-001         C35                      36.3                     35
 2 CP-002         C30                      28.4                     30
 3 CP-003         C30                      36.1                     30
 4 CP-004         C40                      47.2                     40
 5 CP-005         C25                      27.9                     25
 6 CP-006         C40                      41.5                     40
 7 CP-007         C25                      23.7                     25
 8 CP-008         C30                      38.7                     30
 9 CP-009         C30                      30.3                     30
10 CP-010         C25                      25.3                     25
# ℹ 1 more variable: resistencia_relativa <dbl>

A resistência de referência foi associada à própria denominação das classes: 25 MPa para C25, 30 MPa para C30, 35 MPa para C35 e 40 MPa para C40. A variável resistencia_relativa representa a razão entre a resistência medida e essa referência.

Valores superiores a 1 indicam resistência observada acima da referência adotada, enquanto valores inferiores a 1 indicam resultado abaixo dela. Essa variável possui finalidade descritiva e comparativa, não substituindo critérios normativos de aceitação do concreto.

5.5.2 Questão 22 — Classificação dos corpos de prova

Mostrar código
dados_limpos <- dados_limpos |>
  mutate(
    classificacao = case_when(
      is.na(resistencia_relativa) ~ NA_character_,
      resistencia_relativa < 1 ~ "Abaixo da referência",
      resistencia_relativa < 1.10 ~ "Atende à referência",
      TRUE ~ "Acima da referência"
    )
  )

dados_limpos |>
  count(classificacao, .drop = FALSE)
# A tibble: 4 × 2
  classificacao            n
  <chr>                <int>
1 Abaixo da referência    24
2 Acima da referência     42
3 Atende à referência     33
4 <NA>                     1

Foram adotados três grupos descritivos: valores inferiores a 1 foram classificados como Abaixo da referência; valores entre 1 e 1,10 como Atende à referência; e valores iguais ou superiores a 1,10 como Acima da referência.

A base apresenta 24 corpos de prova abaixo da referência, 33 atendendo à referência e 42 acima da referência, além de uma observação sem classificação devido à ausência de resistência.

O limite de 1,10 foi utilizado apenas para diferenciar resultados próximos da referência daqueles claramente superiores dentro desta análise exploratória.

5.5.3 Questão 23 — Resistência acima da média da classe

Mostrar código
dados_limpos <- dados_limpos |>
  group_by(tipo_concreto) |>
  mutate(
    acima_media =
      resistencia_mpa > mean(resistencia_mpa, na.rm = TRUE)
  ) |>
  ungroup()

dados_limpos |>
  count(acima_media, .drop = FALSE)
# A tibble: 3 × 2
  acima_media     n
  <lgl>       <int>
1 FALSE          50
2 TRUE           49
3 NA              1

A variável acima_media é criada diretamente dentro de cada grupo de tipo_concreto, sem a necessidade de calcular manualmente as quatro médias. Foram identificados 49 registros acima da média de sua classe, 50 iguais ou abaixo e 1 valor ausente.

Essa solução demonstra uma das vantagens de group_by() associado a mutate(): a transformação é realizada considerando automaticamente o grupo ao qual cada observação pertence.

5.6 Etapa 6 – Trabalhando com dados agrupados

5.6.1 Questão 24 — Número de observações por classe

Mostrar código
dados_limpos |>
  group_by(tipo_concreto) |>
  summarise(
    n_observacoes = n(),
    .groups = "drop"
  ) |>
  arrange(tipo_concreto)
# A tibble: 4 × 2
  tipo_concreto n_observacoes
  <chr>                 <int>
1 C25                      29
2 C30                      32
3 C35                      23
4 C40                      16

Foram identificadas 29 observações C25, 32 C30, 23 C35 e 16 C40, totalizando as 100 observações da base.

5.6.2 Questão 25 — Resistência média por classe

Mostrar código
dados_limpos |>
  group_by(tipo_concreto) |>
  summarise(
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE),
    .groups = "drop"
  ) |>
  arrange(desc(resistencia_media))
# A tibble: 4 × 2
  tipo_concreto resistencia_media
  <chr>                     <dbl>
1 C40                        43.3
2 C35                        37.1
3 C30                        32.1
4 C25                        27.1

A ordenação reproduz a progressão já observada: C40 apresenta a maior média (43,33 MPa), seguida por C35 (37,10 MPa), C30 (32,08 MPa) e C25 (27,07 MPa).

5.6.3 Questão 26 — Múltiplas estatísticas de resistência

Mostrar código
dados_limpos |>
  group_by(tipo_concreto) |>
  summarise(
    n = sum(!is.na(resistencia_mpa)),
    media = mean(resistencia_mpa, na.rm = TRUE),
    mediana = median(resistencia_mpa, na.rm = TRUE),
    desvio_padrao = sd(resistencia_mpa, na.rm = TRUE),
    minimo = min(resistencia_mpa, na.rm = TRUE),
    maximo = max(resistencia_mpa, na.rm = TRUE),
    .groups = "drop"
  )
# A tibble: 4 × 7
  tipo_concreto     n media mediana desvio_padrao minimo maximo
  <chr>         <int> <dbl>   <dbl>         <dbl>  <dbl>  <dbl>
1 C25              29  27.1    27.8          3.07   19     31.6
2 C30              32  32.1    31.8          3.57   24.5   39.4
3 C35              22  37.1    36.8          3.68   29.9   44.6
4 C40              16  43.3    43.2          3.08   37.3   47.5

A tabela permite avaliar simultaneamente tendência central, dispersão e extremos. Por exemplo, as médias aumentam com a classe, mas os desvios-padrão permanecem em torno de 3 a 3,7 MPa. O C25 apresenta mínimo de 19 MPa, enquanto o C40 alcança máximo de 47,5 MPa.

Apresentar apenas a média poderia ocultar diferenças de dispersão ou resultados individuais muito baixos. Por isso, a combinação entre média, mediana, desvio-padrão, mínimo e máximo fornece uma interpretação mais completa do comportamento das classes.

5.7 Etapa 7 – Selecionando e reorganizando variáveis

5.7.1 Questão 27 — Seleção das propriedades quantitativas

A variável idade_dias é quantitativa, mas representa a idade de ensaio e não uma propriedade física do concreto. Por esse motivo, ela não foi incluída no conjunto de propriedades selecionado.

Mostrar código
variaveis_propriedades <- c(
  "resistencia_mpa",
  "cimento_kg_m3",
  "relacao_a_c",
  "abatimento_mm",
  "densidade_kg_m3",
  "absorção_agregado_pct"
)

dados_quantitativos <- dados_limpos |>
  select(all_of(variaveis_propriedades))

dados_quantitativos |>
  slice_head(n = 10)
# A tibble: 10 × 6
   resistencia_mpa cimento_kg_m3 relacao_a_c abatimento_mm densidade_kg_m3
             <dbl>         <dbl>       <dbl>         <dbl>           <dbl>
 1            36.3           395        0.5           1110           23320
 2            28.4           340        0.54          1140           23890
 3            36.1           338        0.56          1060           24270
 4            47.2           407        0.47          1420           23450
 5            27.9           316        0.61          1330           24480
 6            41.5           398        0.43           980           23240
 7            23.7           351        0.65           990           23850
 8            38.7           347        0.54          1160           23870
 9            30.3           346        0.54          1250           23280
10            25.3           315        0.6           1270           24130
# ℹ 1 more variable: absorção_agregado_pct <dbl>

A seleção reúne resistência, consumo de cimento, relação água/cimento, abatimento, densidade e absorção do agregado, mantendo fora da análise variáveis que possuem outra função no banco de dados.

5.7.2 Questão 28 — Médias com across()

Mostrar código
# Uma única operação com across()
dados_quantitativos |>
  summarise(
    across(
      everything(),
      ~ mean(.x, na.rm = TRUE)
    )
  )
# A tibble: 1 × 6
  resistencia_mpa cimento_kg_m3 relacao_a_c abatimento_mm densidade_kg_m3
            <dbl>         <dbl>       <dbl>         <dbl>           <dbl>
1            33.5          358.       0.554         1217.          23515.
# ℹ 1 more variable: absorção_agregado_pct <dbl>
Mostrar código
# Forma individual para comparação
dados_quantitativos |>
  summarise(
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE),
    cimento_medio = mean(cimento_kg_m3, na.rm = TRUE),
    relacao_ac_media = mean(relacao_a_c, na.rm = TRUE),
    abatimento_medio = mean(abatimento_mm, na.rm = TRUE),
    densidade_media = mean(densidade_kg_m3, na.rm = TRUE),
    absorcao_media = mean(`absorção_agregado_pct`, na.rm = TRUE)
  )
# A tibble: 1 × 6
  resistencia_media cimento_medio relacao_ac_media abatimento_medio
              <dbl>         <dbl>            <dbl>            <dbl>
1              33.5          358.            0.554            1217.
# ℹ 2 more variables: densidade_media <dbl>, absorcao_media <dbl>

As duas abordagens produzem os mesmos resultados: resistência média de aproximadamente 33,55 MPa, consumo de cimento de 357,62 kg/m³, relação a/c de 0,554, abatimento de 110,33 mm, densidade de 2373,10 kg/m³ e absorção de 1,79%.

Quando existem muitas variáveis, across() apresenta maior facilidade de manutenção, pois a mesma função é aplicada a um conjunto de colunas sem repetir a estrutura do comando para cada variável.

5.7.3 Questão 29 — Transformação simultânea com mutate() e across()

Foi escolhida a padronização estatística (escore z) das propriedades quantitativas. Essa transformação centraliza cada variável em torno de zero e a expressa em unidades de desvio-padrão, permitindo comparar variáveis originalmente medidas em escalas muito diferentes.

Mostrar código
dados_padronizados <- dados_limpos |>
  mutate(
    across(
      all_of(variaveis_propriedades),
      ~ (.x - mean(.x, na.rm = TRUE)) /
        sd(.x, na.rm = TRUE),
      .names = "{.col}_z"
    )
  )

dados_padronizados |>
  select(
    id_corpo_prova,
    ends_with("_z")
  ) |>
  slice_head(n = 10)
# A tibble: 10 × 7
   id_corpo_prova resistencia_mpa_z cimento_kg_m3_z relacao_a_c_z
   <chr>                      <dbl>           <dbl>         <dbl>
 1 CP-001                     0.421           1.19         -0.924
 2 CP-002                    -0.788          -0.563        -0.236
 3 CP-003                     0.391          -0.627         0.108
 4 CP-004                     2.09            1.58         -1.44 
 5 CP-005                    -0.865          -1.33          0.967
 6 CP-006                     1.22            1.29         -2.13 
 7 CP-007                    -1.51           -0.211         1.66 
 8 CP-008                     0.789          -0.339        -0.236
 9 CP-009                    -0.497          -0.371        -0.236
10 CP-010                    -1.26           -1.36          0.795
# ℹ 3 more variables: abatimento_mm_z <dbl>, densidade_kg_m3_z <dbl>,
#   absorção_agregado_pct_z <dbl>

A transformação é útil para análises exploratórias em que se deseja comparar magnitudes relativas entre variáveis. Entretanto, os valores padronizados não devem substituir as grandezas originais nas interpretações de engenharia, pois perdem as unidades físicas como MPa, mm e kg/m³.

5.7.4 Questão 30 — Múltiplas estatísticas com across()

Mostrar código
dados_limpos |>
  group_by(tipo_concreto) |>
  summarise(
    across(
      all_of(variaveis_propriedades),
      list(
        media = ~ mean(.x, na.rm = TRUE),
        dp = ~ sd(.x, na.rm = TRUE)
      ),
      .names = "{.col}_{.fn}"
    ),
    .groups = "drop"
  )
# A tibble: 4 × 13
  tipo_concreto resistencia_mpa_media resistencia_mpa_dp cimento_kg_m3_media
  <chr>                         <dbl>              <dbl>               <dbl>
1 C25                            27.1               3.07                323.
2 C30                            32.1               3.57                351.
3 C35                            37.1               3.68                378.
4 C40                            43.3               3.08                404.
# ℹ 9 more variables: cimento_kg_m3_dp <dbl>, relacao_a_c_media <dbl>,
#   relacao_a_c_dp <dbl>, abatimento_mm_media <dbl>, abatimento_mm_dp <dbl>,
#   densidade_kg_m3_media <dbl>, densidade_kg_m3_dp <dbl>,
#   absorção_agregado_pct_media <dbl>, absorção_agregado_pct_dp <dbl>

A função across() permite aplicar simultaneamente média e desvio-padrão a todas as propriedades selecionadas e organizar os resultados em uma única tabela.

Em comparação com vários comandos separados, essa abordagem reduz repetição, facilita alterações futuras na lista de variáveis e diminui a possibilidade de inconsistências entre cálculos realizados com estruturas diferentes.

5.8 Etapa 8 – Trabalhando com textos e categorias

5.8.1 Questão 31 — Padronização da variável obra

Mostrar código
# Antes
dados |>
  count(obra, sort = TRUE)
# A tibble: 4 × 2
  obra        n
  <chr>   <int>
1 Bloco B    29
2 Bloco A    28
3 Bloco C    28
4 Bloco D    15
Mostrar código
# Padronização
comparacao_obra <- dados |>
  transmute(
    obra_original = obra,
    obra_padronizada = obra |>
      str_trim() |>
      str_squish() |>
      str_to_title()
  )

# Depois
comparacao_obra |>
  distinct(
    obra_original,
    obra_padronizada
  )
# A tibble: 4 × 2
  obra_original obra_padronizada
  <chr>         <chr>           
1 Bloco A       Bloco A         
2 Bloco C       Bloco C         
3 Bloco B       Bloco B         
4 Bloco D       Bloco D         
Mostrar código
comparacao_obra |>
  count(obra_padronizada)
# A tibble: 4 × 2
  obra_padronizada     n
  <chr>            <int>
1 Bloco A             28
2 Bloco B             29
3 Bloco C             28
4 Bloco D             15

Antes da padronização existiam Bloco B e Bloco B como categorias distintas. Após a aplicação das funções do stringr, os espaços excedentes são removidos e a base passa a apresentar somente quatro blocos.

A transformação também uniformiza o uso de maiúsculas e minúsculas, tornando o procedimento aplicável de forma consistente a todos os registros.

5.8.2 Questão 32 — Busca por padrões com str_detect()

Foi utilizado o padrão ^C3, que identifica textos iniciados por C3. Na base padronizada, isso seleciona as classes C30 e C35.

Mostrar código
dados_limpos |>
  filter(
    str_detect(
      tipo_concreto,
      "^C3"
    )
  ) |>
  select(
    id_corpo_prova,
    obra,
    tipo_concreto,
    resistencia_mpa
  ) |>
  slice_head(n = 20)
# A tibble: 20 × 4
   id_corpo_prova obra    tipo_concreto resistencia_mpa
   <chr>          <chr>   <chr>                   <dbl>
 1 CP-001         Bloco A C35                      36.3
 2 CP-002         Bloco A C30                      28.4
 3 CP-003         Bloco C C30                      36.1
 4 CP-008         Bloco D C30                      38.7
 5 CP-009         Bloco B C30                      30.3
 6 CP-011         Bloco C C30                      31.8
 7 CP-013         Bloco B C35                      35.6
 8 CP-014         Bloco B C35                      39.4
 9 CP-015         Bloco B C35                      36.3
10 CP-017         Bloco B C35                      38.4
11 CP-018         Bloco B C30                      34.7
12 CP-019         Bloco B C30                      30.4
13 CP-021         Bloco A C30                      28.8
14 CP-023         Bloco C C30                      35.5
15 CP-027         Bloco B C30                      39.4
16 CP-028         Bloco D C30                      33.6
17 CP-030         Bloco B C35                      44.6
18 CP-031         Bloco D C35                      40.7
19 CP-032         Bloco D C35                      36.6
20 CP-033         Bloco C C30                      31.5

A utilização de padrões textuais permite localizar grupos de registros mesmo quando não se deseja enumerar cada categoria individualmente. Esse recurso é útil para diagnóstico de grafias, prefixos, códigos, identificadores e padrões suspeitos em bases maiores.

5.9 Etapa 9 – Visualização dos dados

5.9.1 Questão 33 — Distribuição da resistência por classe

Mostrar código
ggplot(
  dados_limpos,
  aes(
    x = tipo_concreto,
    y = resistencia_mpa
  )
) +
  geom_boxplot() +
  labs(
    title = "Distribuição da resistência por tipo de concreto",
    x = "Tipo de concreto",
    y = "Resistência à compressão (MPa)"
  ) +
  theme_minimal()

O gráfico evidencia o deslocamento progressivo da distribuição da resistência à medida que a classe aumenta. As medianas são aproximadamente 27,8 MPa para C25, 31,8 MPa para C30, 36,75 MPa para C35 e 43,15 MPa para C40.

Apesar de existir alguma sobreposição entre classes vizinhas, o padrão geral é claro. O C25 apresenta ainda um valor baixo de aproximadamente 19 MPa que aparece como ponto atípico no boxplot e merece atenção, embora um ponto gráfico isolado não deva ser classificado automaticamente como erro.

5.9.2 Questão 34 — Consumo de cimento e resistência

Mostrar código
# Visão geral
ggplot(
  dados_limpos,
  aes(
    x = cimento_kg_m3,
    y = resistencia_mpa,
    color = tipo_concreto
  )
) +
  geom_point() +
  geom_smooth(
    aes(group = 1),
    method = "lm",
    se = FALSE,
    color = "black"
  ) +
  labs(
    title = "Consumo de cimento e resistência à compressão",
    x = "Consumo de cimento (kg/m³)",
    y = "Resistência à compressão (MPa)",
    color = "Classe"
  ) +
  theme_minimal()

Mostrar código
# Relação observada dentro de cada classe
ggplot(
  dados_limpos,
  aes(
    x = cimento_kg_m3,
    y = resistencia_mpa
  )
) +
  geom_point() +
  geom_smooth(
    method = "lm",
    se = FALSE
  ) +
  facet_wrap(~ tipo_concreto, scales = "free_x") +
  labs(
    title = "Consumo de cimento e resistência dentro de cada classe",
    x = "Consumo de cimento (kg/m³)",
    y = "Resistência à compressão (MPa)"
  ) +
  theme_minimal()

Considerando toda a base, existe uma associação positiva evidente entre consumo de cimento e resistência, com correlação de aproximadamente 0,78. Visualmente, as classes de maior resistência também se concentram em maiores consumos de cimento.

Entretanto, a análise separada por classe mostra uma interpretação diferente. As correlações são aproximadamente 0,28 no C25, -0,17 no C30, -0,24 no C35 e -0,04 no C40, ou seja, fracas ou praticamente inexistentes dentro das próprias classes.

Dessa forma, o gráfico geral fornece evidência visual de associação, mas não é suficiente para concluir que simplesmente aumentar o consumo de cimento produz maior resistência. Parte importante do padrão observado está associada às diferenças de dosagem entre as próprias classes.

5.9.3 Questão 35 — Relação água/cimento e resistência

Mostrar código
# Visão geral
ggplot(
  dados_limpos,
  aes(
    x = relacao_a_c,
    y = resistencia_mpa,
    color = tipo_concreto
  )
) +
  geom_point() +
  geom_smooth(
    aes(group = 1),
    method = "lm",
    se = FALSE,
    color = "black"
  ) +
  labs(
    title = "Relação água/cimento e resistência à compressão",
    x = "Relação água/cimento",
    y = "Resistência à compressão (MPa)",
    color = "Classe"
  ) +
  theme_minimal()

Mostrar código
# Relação dentro das classes
ggplot(
  dados_limpos,
  aes(
    x = relacao_a_c,
    y = resistencia_mpa
  )
) +
  geom_point() +
  geom_smooth(
    method = "lm",
    se = FALSE
  ) +
  facet_wrap(~ tipo_concreto) +
  labs(
    title = "Relação água/cimento e resistência dentro de cada classe",
    x = "Relação água/cimento",
    y = "Resistência à compressão (MPa)"
  ) +
  theme_minimal()

Na base completa, observa-se uma tendência negativa relativamente forte, com correlação de aproximadamente -0,76: valores maiores de relação água/cimento aparecem associados, de forma geral, a menores resistências.

Quando as classes são analisadas separadamente, entretanto, as correlações ficam próximas de zero: aproximadamente 0,10 no C25, -0,17 no C30, 0,11 no C35 e 0,09 no C40. Assim, a tendência negativa geral merece investigação, mas não deve ser interpretada isoladamente como uma relação direta de mesma intensidade dentro de todas as classes.

5.10 Etapa 10 – Questões próximas da prática profissional

5.10.1 Questão 36 — Comparação entre obras

A afirmação de que o Bloco C apresenta desempenho superior foi avaliada em três níveis: resistência média por bloco; médias das propriedades por bloco; e comparação entre bloco e tipo de concreto.

Mostrar código
# 1. Resistência média por bloco
resumo_resistencia_obra <- dados_limpos |>
  group_by(obra) |>
  summarise(
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE),
    .groups = "drop"
  ) |>
  arrange(desc(resistencia_media))

resumo_resistencia_obra
# A tibble: 4 × 2
  obra    resistencia_media
  <chr>               <dbl>
1 Bloco B              35.7
2 Bloco A              33.2
3 Bloco D              32.4
4 Bloco C              32.4
Mostrar código
# 2. Médias das propriedades por bloco
resumo_propriedades_obra <- dados_limpos |>
  group_by(obra) |>
  summarise(
    across(
      all_of(variaveis_propriedades),
      ~ mean(.x, na.rm = TRUE)
    ),
    .groups = "drop"
  )

resumo_propriedades_obra
# A tibble: 4 × 7
  obra   resistencia_mpa cimento_kg_m3 relacao_a_c abatimento_mm densidade_kg_m3
  <chr>            <dbl>         <dbl>       <dbl>         <dbl>           <dbl>
1 Bloco…            33.2          361.       0.557         1167.          23788.
2 Bloco…            35.7          365.       0.539         1085.          23750 
3 Bloco…            32.4          350.       0.563         1081.          23656.
4 Bloco…            32.4          350.       0.561         1812           22198.
# ℹ 1 more variable: absorção_agregado_pct <dbl>
Mostrar código
# 3. Médias das propriedades por bloco e classe
resumo_obra_classe <- dados_limpos |>
  group_by(obra, tipo_concreto) |>
  summarise(
    across(
      all_of(variaveis_propriedades),
      ~ mean(.x, na.rm = TRUE)
    ),
    .groups = "drop"
  )

resumo_obra_classe
# A tibble: 16 × 8
   obra    tipo_concreto resistencia_mpa cimento_kg_m3 relacao_a_c abatimento_mm
   <chr>   <chr>                   <dbl>         <dbl>       <dbl>         <dbl>
 1 Bloco A C25                      28.1          330.       0.627         1234 
 2 Bloco A C30                      30.5          358.       0.573         1176.
 3 Bloco A C35                      35.8          375.       0.492         1108 
 4 Bloco A C40                      42.6          405.       0.475         1093.
 5 Bloco B C25                      27.8          322.       0.625         1048.
 6 Bloco B C30                      33.2          351.       0.57          1068.
 7 Bloco B C35                      38.3          378.       0.51          1098.
 8 Bloco B C40                      42            402.       0.47          1122.
 9 Bloco C C25                      26.9          320.       0.616         1108 
10 Bloco C C30                      32.0          348.       0.561         1114.
11 Bloco C C35                      35.1          382.       0.49          1003.
12 Bloco C C40                      45.0          406.       0.503          980 
13 Bloco D C25                      24.7          314.       0.61          1158 
14 Bloco D C30                      32.3          349.       0.572         1068 
15 Bloco D C35                      37            380        0.533         4743.
16 Bloco D C40                      45.0          400.       0.45           910 
# ℹ 2 more variables: densidade_kg_m3 <dbl>, absorção_agregado_pct <dbl>
Mostrar código
# Distribuição da resistência entre os blocos, controlando visualmente a classe
ggplot(
  dados_limpos,
  aes(
    x = obra,
    y = resistencia_mpa
  )
) +
  geom_boxplot() +
  facet_wrap(~ tipo_concreto) +
  labs(
    title = "Resistência por bloco e tipo de concreto",
    x = "Bloco",
    y = "Resistência à compressão (MPa)"
  ) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(
      angle = 45,
      hjust = 1
    )
  )

Na análise geral, o Bloco C não apresenta a maior resistência média. Sua média é de aproximadamente 32,37 MPa, enquanto o Bloco B apresenta 35,70 MPa. O Bloco C possui, entretanto, o menor consumo médio de cimento, aproximadamente 349,79 kg/m³.

A comparação apenas das médias gerais não é suficiente porque cada bloco contém diferentes proporções de classes. Ao separar as classes, o C25 do Bloco C apresenta resistência média de 26,90 MPa; no C30, 32,02 MPa; no C35, 35,10 MPa; e no C40, 44,98 MPa.

No C30, o Bloco C apresenta resistência próxima aos melhores resultados com o menor consumo médio de cimento e a menor relação água/cimento entre os blocos, constituindo um resultado interessante. No C35, entretanto, apresenta a menor resistência média e o maior consumo de cimento. No C40, apresenta a maior resistência média, praticamente empatada com o Bloco D, porém com o maior consumo de cimento.

Portanto, os dados indicam bons resultados pontuais do Bloco C, principalmente em C30 e C40, mas não sustentam uma superioridade geral e consistente. Além disso, abatimento, densidade e absorção não devem ser tratados como um simples placar em que maior ou menor significa necessariamente melhor; a interpretação depende do objetivo e da especificação técnica de cada propriedade.

5.10.2 Questão 37 — Idade e resistência

Mostrar código
# Média geral por idade
medias_idade <- dados_limpos |>
  group_by(idade_dias) |>
  summarise(
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE),
    .groups = "drop"
  )

medias_idade
# A tibble: 4 × 2
  idade_dias resistencia_media
       <dbl>             <dbl>
1          7              28.8
2         14              34.7
3         28              34.7
4         56              33.1
Mostrar código
# Média por classe e idade
medias_idade_classe <- dados_limpos |>
  group_by(tipo_concreto, idade_dias) |>
  summarise(
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE),
    .groups = "drop"
  )

medias_idade_classe
# A tibble: 15 × 3
   tipo_concreto idade_dias resistencia_media
   <chr>              <dbl>             <dbl>
 1 C25                    7              25.3
 2 C25                   14              23.6
 3 C25                   28              27.9
 4 C25                   56              27.3
 5 C30                    7              29.0
 6 C30                   14              30.9
 7 C30                   28              33.3
 8 C30                   56              33.9
 9 C35                    7              30  
10 C35                   14              36.0
11 C35                   28              37.9
12 C35                   56              42.8
13 C40                    7              42.5
14 C40                   14              41.3
15 C40                   28              44.1
Mostrar código
ggplot(
  medias_idade_classe,
  aes(
    x = idade_dias,
    y = resistencia_media,
    color = tipo_concreto,
    group = tipo_concreto
  )
) +
  geom_point() +
  geom_line() +
  scale_x_continuous(
    breaks = c(7, 14, 28, 56)
  ) +
  labs(
    title = "Resistência média por idade e classe de concreto",
    x = "Idade (dias)",
    y = "Resistência média (MPa)",
    color = "Classe"
  ) +
  theme_minimal()

Considerando todos os concretos em conjunto, as médias são 28,78 MPa aos 7 dias, 34,74 MPa aos 14 dias, 34,65 MPa aos 28 dias e 33,12 MPa aos 56 dias. Essa média geral não apresenta crescimento contínuo.

Quando a classe é controlada, o padrão torna-se mais informativo. O C30 aumenta de 28,96 MPa aos 7 dias para 33,88 MPa aos 56 dias, enquanto o C35 aumenta de 30,00 para 42,85 MPa. O C25 apresenta maior oscilação, e o C40 não possui observações aos 56 dias.

Portanto, a base apresenta evidência de aumento da resistência com a idade principalmente nas classes C30 e C35, mas esse comportamento não é uniforme em todas as classes. Como os registros correspondem a diferentes corpos de prova, a análise deve ser interpretada como tendência descritiva, e não como acompanhamento longitudinal de um mesmo corpo de prova.

5.10.3 Questão 38 — Resumo para o engenheiro

Mostrar código
tabela_engenheiro <- dados_limpos |>
  group_by(tipo_concreto) |>
  summarise(
    n = sum(!is.na(resistencia_mpa)),
    resistencia_media = mean(resistencia_mpa, na.rm = TRUE),
    resistencia_mediana = median(resistencia_mpa, na.rm = TRUE),
    desvio_padrao = sd(resistencia_mpa, na.rm = TRUE),
    resistencia_minima = min(resistencia_mpa, na.rm = TRUE),
    resistencia_maxima = max(resistencia_mpa, na.rm = TRUE),
    .groups = "drop"
  )

tabela_engenheiro
# A tibble: 4 × 7
  tipo_concreto     n resistencia_media resistencia_mediana desvio_padrao
  <chr>         <int>             <dbl>               <dbl>         <dbl>
1 C25              29              27.1                27.8          3.07
2 C30              32              32.1                31.8          3.57
3 C35              22              37.1                36.8          3.68
4 C40              16              43.3                43.2          3.08
# ℹ 2 more variables: resistencia_minima <dbl>, resistencia_maxima <dbl>

Para o acompanhamento da qualidade do concreto, considero especialmente relevantes as seguintes informações:

Número de ensaios (n) — permite avaliar a quantidade de resultados disponível em cada classe e evita interpretar uma média sem conhecer o tamanho do grupo.

Resistência média — resume o nível geral de resistência observado na classe e facilita comparações entre grupos.

Mediana — complementa a média e é menos sensível a valores extremos, permitindo verificar se a distribuição está sendo influenciada por observações isoladas.

Desvio-padrão — representa a dispersão dos resultados e auxilia a avaliar a uniformidade do comportamento da classe.

Resistência mínima — possui importância prática por evidenciar os resultados mais baixos, que podem exigir investigação mesmo quando a média do grupo é satisfatória.

Resistência máxima — completa a avaliação da amplitude observada e ajuda a compreender os extremos da distribuição.

A apresentação conjunta dessas estatísticas é mais informativa do que utilizar apenas a resistência média, pois permite analisar quantidade de dados, tendência central, variabilidade e extremos.


6 Desafio final — O engenheiro responsável pelos dados

O script a seguir prioriza ferramentas da família tidyverse e foi organizado em duas ideias. A primeira corresponde a uma inspeção genérica, capaz de atuar sem conhecer previamente os erros específicos. A segunda corresponde à interpretação técnica, que deve ser realizada depois que o profissional conhece o significado das variáveis.

Mostrar código
# ============================================================
# DESAFIO FINAL - SCRIPT GENÉRICO COM TIDYVERSE
# ============================================================

library(tidyverse)

# 1. Importação e preservação da base original ----------------

dados_desafio <- read_csv2(
  "base_processamento_dados_engenharia_civil.csv",
  show_col_types = FALSE
)

dados_original_desafio <- dados_desafio


# 2. Inspeção inicial -----------------------------------------

dados_desafio |>
  glimpse()
Rows: 100
Columns: 10
$ id_corpo_prova        <chr> "CP-001", "CP-002", "CP-003", "CP-004", "CP-005"…
$ obra                  <chr> "Bloco A", "Bloco A", "Bloco C", "Bloco A", "Blo…
$ tipo_concreto         <chr> "C35", "C30", "C30", "C40", "C25", "C40", "C25",…
$ idade_dias            <dbl> 28, 7, 56, 28, 28, 28, 56, 28, 14, 7, 7, 28, 28,…
$ resistencia_mpa       <chr> "36.3", "28.4", "36.1", "47.2", "27.9", "41.5", …
$ cimento_kg_m3         <chr> "395", "340", "338", "407", "316", "398", "351",…
$ relacao_a_c           <chr> "0.5", "0.54", "0.56", "0.47", "0.61", "0.43", "…
$ abatimento_mm         <dbl> 1110, 1140, 1060, 1420, 1330, 980, 990, 1160, 12…
$ densidade_kg_m3       <dbl> 23320, 23890, 24270, 23450, 24480, 23240, 23850,…
$ absorção_agregado_pct <chr> "2.3", "1.85", "1.89", "1.97", "2.31", "0.8", "1…
Mostrar código
dados_desafio |>
  summarise(
    across(
      everything(),
      ~ sum(is.na(.x))
    )
  )
# A tibble: 1 × 10
  id_corpo_prova  obra tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
           <int> <int>         <int>      <int>           <int>         <int>
1              0     0             0          0               1             0
# ℹ 4 more variables: relacao_a_c <int>, abatimento_mm <int>,
#   densidade_kg_m3 <int>, absorção_agregado_pct <int>
Mostrar código
# 3. Padronizações inequívocas --------------------------------
# Remove espaços no início/final e transforma textos vazios em NA.

dados_desafio <- dados_desafio |>
  mutate(
    across(
      where(is.character),
      ~ .x |>
        str_trim() |>
        na_if("")
    )
  )


# 4. Verificação de linhas duplicadas -------------------------

duplicados <- dados_desafio |>
  group_by(
    across(everything())
  ) |>
  filter(n() > 1) |>
  ungroup()

duplicados
# A tibble: 0 × 10
# ℹ 10 variables: id_corpo_prova <chr>, obra <chr>, tipo_concreto <chr>,
#   idade_dias <dbl>, resistencia_mpa <chr>, cimento_kg_m3 <chr>,
#   relacao_a_c <chr>, abatimento_mm <dbl>, densidade_kg_m3 <dbl>,
#   absorção_agregado_pct <chr>
Mostrar código
# 5. Identificação de colunas predominantemente numéricas -----

proporcao_numerica <- dados_desafio |>
  select(where(is.character)) |>
  map_dbl(
    ~ {
      valores <- .x[!is.na(.x)]

      if (length(valores) == 0) {
        return(0)
      }

      convertidos <- valores |>
        str_replace_all(",", ".") |>
        parse_double()

      mean(!is.na(convertidos))
    }
  )

candidatas_numericas <- tibble(
  variavel = names(proporcao_numerica),
  proporcao_numerica = proporcao_numerica
) |>
  filter(proporcao_numerica >= 0.90)

candidatas_numericas
# A tibble: 4 × 2
  variavel              proporcao_numerica
  <chr>                              <dbl>
1 resistencia_mpa                    0.990
2 cimento_kg_m3                      0.99 
3 relacao_a_c                        1    
4 absorção_agregado_pct              1    
Mostrar código
# 6. Registro de valores que impedem a conversão --------------

pendencias_conversao <- candidatas_numericas$variavel |>
  map_dfr(
    function(variavel_atual) {

      dados_desafio |>
        transmute(
          linha = row_number(),
          variavel = variavel_atual,
          valor_original = .data[[variavel_atual]],
          valor_convertido =
            valor_original |>
              str_replace_all(",", ".") |>
              parse_double()
        ) |>
        filter(
          !is.na(valor_original),
          is.na(valor_convertido)
        ) |>
        select(
          linha,
          variavel,
          valor_original
        )
    }
  )

pendencias_conversao
# A tibble: 2 × 3
  linha variavel        valor_original
  <int> <chr>           <chr>         
1    19 resistencia_mpa 3O,4          
2    64 cimento_kg_m3   390O          
Mostrar código
# 7. Conversão das colunas candidatas --------------------------
# Os valores não convertíveis passam a NA na base de trabalho,
# mas permanecem registrados em pendencias_conversao.

dados_desafio <- dados_desafio |>
  mutate(
    across(
      all_of(candidatas_numericas$variavel),
      ~ .x |>
        str_replace_all(",", ".") |>
        parse_double()
    )
  )


# 8. Possíveis inconsistências entre categorias ---------------

inconsistencias_categorias <- dados_desafio |>
  select(where(is.character)) |>
  mutate(linha = row_number(), .before = 1) |>
  pivot_longer(
    -linha,
    names_to = "variavel",
    values_to = "valor"
  ) |>
  filter(!is.na(valor)) |>
  mutate(
    chave = str_to_lower(valor)
  ) |>
  distinct(
    variavel,
    chave,
    valor
  ) |>
  group_by(
    variavel,
    chave
  ) |>
  summarise(
    n_formas = n(),
    formas = str_c(valor, collapse = " | "),
    .groups = "drop"
  ) |>
  filter(n_formas > 1)

inconsistencias_categorias
# A tibble: 1 × 4
  variavel      chave n_formas formas   
  <chr>         <chr>    <int> <chr>    
1 tipo_concreto c30          2 C30 | c30
Mostrar código
# 9. Possíveis valores atípicos -------------------------------
# O critério do IQR apenas sinaliza valores para análise.
# Um outlier estatístico não é automaticamente um erro.

variaveis_numericas_desafio <- dados_desafio |>
  select(where(is.numeric)) |>
  names()

possiveis_outliers <- dados_desafio |>
  mutate(linha = row_number()) |>
  pivot_longer(
    cols = all_of(variaveis_numericas_desafio),
    names_to = "variavel",
    values_to = "valor"
  ) |>
  filter(!is.na(valor)) |>
  group_by(variavel) |>
  mutate(
    q1 = quantile(valor, 0.25),
    q3 = quantile(valor, 0.75),
    iqr = IQR(valor),
    limite_inferior = q1 - 1.5 * iqr,
    limite_superior = q3 + 1.5 * iqr
  ) |>
  filter(
    valor < limite_inferior |
      valor > limite_superior
  ) |>
  select(
    linha,
    variavel,
    valor
  ) |>
  ungroup()

possiveis_outliers
# A tibble: 20 × 3
   linha variavel                valor
   <int> <chr>                   <dbl>
 1     3 idade_dias               56  
 2     7 idade_dias               56  
 3    16 abatimento_mm          1750  
 4    21 densidade_kg_m3       24510  
 5    30 idade_dias               56  
 6    32 abatimento_mm         12500  
 7    35 idade_dias               56  
 8    37 idade_dias               56  
 9    42 idade_dias               56  
10    53 densidade_kg_m3        2380  
11    57 idade_dias               56  
12    68 densidade_kg_m3       24600  
13    72 absorção_agregado_pct    18.4
14    75 abatimento_mm           640  
15    76 idade_dias               56  
16    85 idade_dias              280  
17    87 idade_dias               56  
18    90 densidade_kg_m3       24640  
19    99 idade_dias               56  
20   100 idade_dias               56  
Mostrar código
# 10. Variáveis derivadas de qualidade -------------------------

dados_desafio <- dados_desafio |>
  mutate(
    n_ausentes_linha = rowSums(
      is.na(pick(everything()))
    ),
    registro_completo =
      n_ausentes_linha == 0
  )

dados_desafio |>
  count(registro_completo)
# A tibble: 2 × 2
  registro_completo     n
  <lgl>             <int>
1 FALSE                 7
2 TRUE                 93
Mostrar código
# 11. Estatísticas descritivas --------------------------------

dados_desafio |>
  select(
    where(is.numeric),
    -n_ausentes_linha
  ) |>
  summarise(
    across(
      everything(),
      list(
        media = ~ mean(.x, na.rm = TRUE),
        mediana = ~ median(.x, na.rm = TRUE),
        dp = ~ sd(.x, na.rm = TRUE)
      )
    )
  )
# A tibble: 1 × 21
  idade_dias_media idade_dias_mediana idade_dias_dp resistencia_mpa_media
             <dbl>              <dbl>         <dbl>                 <dbl>
1               28                 28          28.8                  33.6
# ℹ 17 more variables: resistencia_mpa_mediana <dbl>, resistencia_mpa_dp <dbl>,
#   cimento_kg_m3_media <dbl>, cimento_kg_m3_mediana <dbl>,
#   cimento_kg_m3_dp <dbl>, relacao_a_c_media <dbl>, relacao_a_c_mediana <dbl>,
#   relacao_a_c_dp <dbl>, abatimento_mm_media <dbl>,
#   abatimento_mm_mediana <dbl>, abatimento_mm_dp <dbl>,
#   densidade_kg_m3_media <dbl>, densidade_kg_m3_mediana <dbl>,
#   densidade_kg_m3_dp <dbl>, absorção_agregado_pct_media <dbl>, …
Mostrar código
# 12. Gráfico 1 - distribuição das variáveis numéricas --------
# O uso de escalas livres permite inspecionar variáveis
# originalmente medidas em unidades diferentes.

dados_desafio |>
  select(
    where(is.numeric),
    -n_ausentes_linha
  ) |>
  pivot_longer(
    everything(),
    names_to = "variavel",
    values_to = "valor"
  ) |>
  ggplot(
    aes(x = valor)
  ) +
  geom_histogram(
    bins = 20
  ) +
  facet_wrap(
    ~ variavel,
    scales = "free"
  ) +
  labs(
    title = "Distribuição das variáveis quantitativas",
    x = "Valor",
    y = "Frequência"
  ) +
  theme_minimal()

Mostrar código
# 13. Gráfico 2 - valores ausentes por variável ---------------

dados_desafio |>
  summarise(
    across(
      everything(),
      ~ sum(is.na(.x))
    )
  ) |>
  pivot_longer(
    everything(),
    names_to = "variavel",
    values_to = "n_ausentes"
  ) |>
  ggplot(
    aes(
      x = reorder(
        variavel,
        n_ausentes
      ),
      y = n_ausentes
    )
  ) +
  geom_col() +
  coord_flip() +
  labs(
    title = "Valores ausentes por variável",
    x = "Variável",
    y = "Número de valores ausentes"
  ) +
  theme_minimal()

Mostrar código
# 14. Resumo das pendências -----------------------------------

list(
  valores_ausentes =
    dados_desafio |>
      summarise(
        across(
          everything(),
          ~ sum(is.na(.x))
        )
      ),

  problemas_conversao =
    pendencias_conversao,

  inconsistencias_categoricas =
    inconsistencias_categorias,

  possiveis_outliers =
    possiveis_outliers
)
$valores_ausentes
# A tibble: 1 × 12
  id_corpo_prova  obra tipo_concreto idade_dias resistencia_mpa cimento_kg_m3
           <int> <int>         <int>      <int>           <int>         <int>
1              0     0             0          0               2             1
# ℹ 6 more variables: relacao_a_c <int>, abatimento_mm <int>,
#   densidade_kg_m3 <int>, absorção_agregado_pct <int>, n_ausentes_linha <int>,
#   registro_completo <int>

$problemas_conversao
# A tibble: 2 × 3
  linha variavel        valor_original
  <int> <chr>           <chr>         
1    19 resistencia_mpa 3O,4          
2    64 cimento_kg_m3   390O          

$inconsistencias_categoricas
# A tibble: 1 × 4
  variavel      chave n_formas formas   
  <chr>         <chr>    <int> <chr>    
1 tipo_concreto c30          2 C30 | c30

$possiveis_outliers
# A tibble: 20 × 3
   linha variavel                valor
   <int> <chr>                   <dbl>
 1     3 idade_dias               56  
 2     7 idade_dias               56  
 3    16 abatimento_mm          1750  
 4    21 densidade_kg_m3       24510  
 5    30 idade_dias               56  
 6    32 abatimento_mm         12500  
 7    35 idade_dias               56  
 8    37 idade_dias               56  
 9    42 idade_dias               56  
10    53 densidade_kg_m3        2380  
11    57 idade_dias               56  
12    68 densidade_kg_m3       24600  
13    72 absorção_agregado_pct    18.4
14    75 abatimento_mm           640  
15    76 idade_dias               56  
16    85 idade_dias              280  
17    87 idade_dias               56  
18    90 densidade_kg_m3       24640  
19    99 idade_dias               56  
20   100 idade_dias               56  

O script preserva a base original, padroniza espaços e campos vazios, identifica colunas predominantemente numéricas, registra os valores que não podem ser convertidos, procura inconsistências categóricas e sinaliza possíveis valores atípicos.

A conversão automática é limitada às colunas em que pelo menos 90% dos valores preenchidos apresentam formato numérico. Esse critério torna o procedimento mais geral, mas não elimina a necessidade de julgamento profissional: códigos formados apenas por números, por exemplo, podem exigir decisão diferente.

Os outliers também são apenas sinalizados. O próprio banco de concreto mostra a importância dessa escolha, pois o critério estatístico pode destacar tanto erros evidentes de ordem de grandeza quanto valores legítimos localizados nas extremidades da distribuição.

As variáveis derivadas n_ausentes_linha e registro_completo permitem avaliar a qualidade de preenchimento de cada registro. Os dois gráficos produzidos fornecem uma visão geral das distribuições quantitativas e da ocorrência de valores ausentes.

Dessa forma, o script automatiza tarefas de inspeção e padronização, mas mantém separadas as decisões que dependem do contexto técnico da base.

7 Reflexão final

7.1 Base R e tidyverse

A principal diferença observada entre a resolução com Base R e com tidyverse está na forma de estruturar o processamento. No Base R, diferentes tarefas podem exigir sintaxes e estruturas específicas, como tapply(), aggregate(), apply(), subset() e indexações. No tidyverse, as operações são organizadas a partir de verbos com funções mais específicas, como filter(), select(), mutate(), group_by() e summarise(), frequentemente conectadas pelo operador |>.

Na prática deste relatório, o tidyverse tornou especialmente claras as operações agrupadas e as transformações sucessivas da base. Funções como across() também facilitaram a aplicação da mesma operação a várias variáveis sem repetição excessiva. Isso não significa que Base R seja inadequado; as duas abordagens são capazes de resolver os problemas propostos. A diferença principal está na organização e na legibilidade do fluxo de trabalho.

7.2 Importância do processamento e da limpeza

O processamento e a limpeza dos dados são etapas fundamentais antes da aplicação de métodos estatísticos porque erros de digitação, valores ausentes, inconsistências de categorias ou ordens de grandeza incorretas podem alterar médias, medidas de dispersão, correlações e representações gráficas. Na Engenharia Civil, essas análises podem subsidiar decisões técnicas e, portanto, a qualidade do resultado depende diretamente da qualidade da informação utilizada.

7.3 Problemas encontrados, decisões e funções utilizadas

Os principais problemas encontrados envolveram duas situações: erros de digitação e formatação, como caracteres indevidos, separadores decimais diferentes e categorias sem padronização; e valores numericamente válidos, mas potencialmente incompatíveis, que exigem análise técnica antes de qualquer correção.

Foram utilizadas principalmente as funções read_csv2(), glimpse(), filter(), select(), mutate(), summarise(), group_by(), arrange(), count(), distinct(), across(), case_when(), str_trim(), str_to_upper(), str_detect(), parse_double(), map() e funções do ggplot2.

A solução adotada é adequada porque automatiza padronizações e operações repetitivas, preserva a base original e permite identificar situações que necessitam de verificação. Ao mesmo tempo, evita tratar toda observação estatisticamente diferente como erro, mantendo a interpretação técnica como parte essencial do processamento.

8 🧠 Considerações finais

O desenvolvimento deste relatório permitiu aplicar a família tidyverse ao processamento e à análise de dados relacionados ao controle tecnológico do concreto. Em comparação com o Relatório 02, as mesmas etapas fundamentais de inspeção, limpeza, transformação e exploração foram realizadas por meio de uma lógica baseada em fluxos de dados e funções especializadas.

A atividade evidenciou novamente que a qualidade da análise estatística depende da qualidade da base utilizada. Erros de digitação, categorias não padronizadas, valores ausentes e registros com ordem de grandeza incompatível podem alterar resultados aparentemente simples. As ferramentas de dplyr, stringr e readr permitiram organizar essas etapas de forma sequencial e legível, enquanto ggplot2 acrescentou uma dimensão visual importante para a interpretação das distribuições e relações entre variáveis.

As análises também reforçaram a necessidade de interpretação técnica. A associação geral entre consumo de cimento e resistência, por exemplo, mostrou-se forte quando toda a base foi considerada, mas fraca quando as classes foram avaliadas separadamente. Da mesma forma, a resistência média por idade só apresentou interpretação mais adequada quando a classe do concreto foi considerada simultaneamente.

Por fim, o tidyverse mostrou-se especialmente útil para operações agrupadas, aplicação simultânea de funções a várias colunas e construção de gráficos integrados ao fluxo de análise. Entretanto, nenhuma ferramenta substitui a avaliação do significado físico dos dados. O R permite organizar e explorar a informação, mas a decisão sobre a validade e a relevância dos resultados continua dependendo do julgamento técnico do profissional.

9 📖 Referências