Entrega parcial · Pré-processamento

Entre o clima, o calendário e o pedal

Preparação dos dados do Capital Bikeshare para transformar registros brutos de aluguel em uma base analítica sobre mobilidade urbana, rotina e condições ambientais.

João Pedro de Araújo Souza / Programação para Análise de Dados / 2026.1

Ponto de partida

Pergunta-guia: em quais condições a cidade pedala mais?

Este relatório parcial prepara os dados para responder a essa pergunta posteriormente. Antes de comparar meses, estações, clima ou perfil dos usuários, é necessário transformar a base bruta em uma base legível, consistente e adequada para análise.

Sistemas de bicicletas compartilhadas não registram apenas deslocamentos. Eles também deixam rastros sobre a relação entre mobilidade urbana, rotina da cidade e ambiente. Em uma mesma base, é possível observar se um dia chuvoso reduz a procura, se fins de semana mudam o perfil do usuário ou se a temperatura torna o uso da bicicleta mais provável.

Neste projeto, os dados são tratados como uma espécie de painel urbano: cada linha resume um pedaço da cidade em movimento. A etapa atual não busca ainda tirar conclusões finais, mas construir uma base limpa o suficiente para que a análise exploratória faça sentido na próxima fase.

Pacotes utilizados

library(tidyverse)
library(lubridate)
library(janitor)
library(knitr)
Função dos pacotes no projeto:

  • tidyverse: leitura, seleção, transformação e organização dos dados.
  • lubridate: tratamento da coluna de data.
  • janitor: padronização dos nomes das variáveis.
  • knitr: apresentação de tabelas no relatório.

Fonte dos dados

Os dados utilizados pertencem ao Bike Sharing Dataset, construído a partir do sistema Capital Bikeshare, de Washington D.C., nos Estados Unidos. A base cobre os anos de 2011 e 2012 e combina registros de aluguel de bicicletas com informações de calendário, clima e estação do ano.

A fonte original disponibiliza dois arquivos principais:

  • day.csv: registros agregados por dia.
  • hour.csv: registros agregados por hora.

O arquivo diário é o eixo principal deste relatório parcial, enquanto o arquivo horário será usado como apoio para validar a consistência da agregação diária.

Fonte dos dados: UCI Machine Learning Repository — Bike Sharing Dataset

Importação dos dados

Antes de importar, o código abaixo verifica se os arquivos estão na mesma pasta do relatório. Isso evita um erro comum no RStudio: tentar ler um arquivo que está em outro diretório.

arquivos_necessarios <- c("day.csv", "hour.csv")

arquivos_ausentes <- arquivos_necessarios[!file.exists(arquivos_necessarios)]

if (length(arquivos_ausentes) > 0) {
  stop(
    paste(
      "Arquivo(s) não encontrado(s):",
      paste(arquivos_ausentes, collapse = ", "),
      "\nColoque os arquivos CSV na mesma pasta deste relatório Rmd."
    )
  )
}
day_raw <- read_csv("day.csv")
hour_raw <- read_csv("hour.csv")

Primeiro contato com as bases

dimensoes <- tibble(
  arquivo = c("day.csv", "hour.csv"),
  unidade_de_observacao = c("Um dia", "Uma hora"),
  linhas = c(nrow(day_raw), nrow(hour_raw)),
  colunas = c(ncol(day_raw), ncol(hour_raw))
)

kable(dimensoes)
arquivo unidade_de_observacao linhas colunas
day.csv Um dia 731 16
hour.csv Uma hora 17379 17

Leitura inicial: a base diária resume a demanda por dia, enquanto a base horária abre a mesma dinâmica em mais detalhe. Para uma entrega parcial focada no pré-processamento, trabalhar primeiro no nível diário deixa a análise mais clara e evita excesso de granularidade logo no início.

glimpse(day_raw)
## Rows: 731
## Columns: 16
## $ instant    <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, …
## $ dteday     <date> 2011-01-01, 2011-01-02, 2011-01-03, 2011-01-04, 2011-01-05…
## $ season     <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
## $ yr         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ mnth       <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
## $ holiday    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,…
## $ weekday    <dbl> 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4,…
## $ workingday <dbl> 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1,…
## $ weathersit <dbl> 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 2, 2,…
## $ temp       <dbl> 0.3441670, 0.3634780, 0.1963640, 0.2000000, 0.2269570, 0.20…
## $ atemp      <dbl> 0.3636250, 0.3537390, 0.1894050, 0.2121220, 0.2292700, 0.23…
## $ hum        <dbl> 0.805833, 0.696087, 0.437273, 0.590435, 0.436957, 0.518261,…
## $ windspeed  <dbl> 0.1604460, 0.2485390, 0.2483090, 0.1602960, 0.1869000, 0.08…
## $ casual     <dbl> 331, 131, 120, 108, 82, 88, 148, 68, 54, 41, 43, 25, 38, 54…
## $ registered <dbl> 654, 670, 1229, 1454, 1518, 1518, 1362, 891, 768, 1280, 122…
## $ cnt        <dbl> 985, 801, 1349, 1562, 1600, 1606, 1510, 959, 822, 1321, 126…

Dicionário prático das variáveis

A base original contém variáveis de identificação, calendário, clima e demanda. Algumas vêm codificadas por números, o que é útil para armazenamento, mas pouco amigável para interpretação.

dicionario <- tibble(
  variavel_original = c(
    "instant", "dteday", "season", "yr", "mnth", "holiday", "weekday",
    "workingday", "weathersit", "temp", "atemp", "hum", "windspeed",
    "casual", "registered", "cnt"
  ),
  significado = c(
    "Índice do registro",
    "Data da observação",
    "Estação do ano codificada numericamente",
    "Ano codificado: 0 para 2011 e 1 para 2012",
    "Mês do ano",
    "Indica se o dia é feriado",
    "Dia da semana codificado numericamente",
    "Indica se o dia é útil",
    "Condição climática codificada numericamente",
    "Temperatura normalizada",
    "Sensação térmica normalizada",
    "Umidade normalizada",
    "Velocidade do vento normalizada",
    "Quantidade de usuários casuais",
    "Quantidade de usuários registrados",
    "Total de bicicletas alugadas"
  )
)

kable(dicionario)
variavel_original significado
instant Índice do registro
dteday Data da observação
season Estação do ano codificada numericamente
yr Ano codificado: 0 para 2011 e 1 para 2012
mnth Mês do ano
holiday Indica se o dia é feriado
weekday Dia da semana codificado numericamente
workingday Indica se o dia é útil
weathersit Condição climática codificada numericamente
temp Temperatura normalizada
atemp Sensação térmica normalizada
hum Umidade normalizada
windspeed Velocidade do vento normalizada
casual Quantidade de usuários casuais
registered Quantidade de usuários registrados
cnt Total de bicicletas alugadas

Diagnóstico da base bruta

Valores ausentes

ausentes_day <- day_raw %>%
  summarise(across(everything(), ~ sum(is.na(.)))) %>%
  pivot_longer(
    cols = everything(),
    names_to = "variavel",
    values_to = "quantidade_ausente"
  )

kable(ausentes_day)
variavel quantidade_ausente
instant 0
dteday 0
season 0
yr 0
mnth 0
holiday 0
weekday 0
workingday 0
weathersit 0
temp 0
atemp 0
hum 0
windspeed 0
casual 0
registered 0
cnt 0

Linhas duplicadas

duplicatas <- tibble(
  arquivo = c("day.csv", "hour.csv"),
  linhas_duplicadas = c(
    sum(duplicated(day_raw)),
    sum(duplicated(hour_raw))
  )
)

kable(duplicatas)
arquivo linhas_duplicadas
day.csv 0
hour.csv 0

Decisão de limpeza:

Mesmo quando uma base parece organizada, esta etapa é necessária. Valores ausentes, duplicatas ou codificações pouco claras podem distorcer médias, gráficos e interpretações. Aqui, o principal trabalho não é “consertar erro grave”, mas transformar uma base técnica em uma base analítica.

Pré-processamento da base diária

A preparação da base diária segue cinco decisões principais:

  1. converter a data para o tipo correto;
  2. substituir códigos numéricos por categorias interpretáveis;
  3. retornar variáveis climáticas para escalas mais compreensíveis;
  4. criar variáveis novas para análise;
  5. selecionar apenas as colunas úteis para a próxima etapa do projeto.
day_clean <- day_raw %>%
  clean_names() %>%
  mutate(
    data = ymd(dteday),

    ano = if_else(yr == 0, 2011, 2012),

    numero_mes = mnth,

    mes = factor(
      mnth,
      levels = 1:12,
      labels = c(
        "Jan", "Fev", "Mar", "Abr", "Mai", "Jun",
        "Jul", "Ago", "Set", "Out", "Nov", "Dez"
      )
    ),

    estacao = factor(
      season,
      levels = c(1, 2, 3, 4),
      labels = c("Primavera", "Verão", "Outono", "Inverno")
    ),

    dia_semana = factor(
      weekday,
      levels = 0:6,
      labels = c(
        "Domingo", "Segunda", "Terça", "Quarta",
        "Quinta", "Sexta", "Sábado"
      )
    ),

    fim_de_semana = factor(
      if_else(dia_semana %in% c("Sábado", "Domingo"), "Sim", "Não"),
      levels = c("Não", "Sim")
    ),

    feriado = factor(
      holiday,
      levels = c(0, 1),
      labels = c("Não", "Sim")
    ),

    dia_util = factor(
      workingday,
      levels = c(0, 1),
      labels = c("Não", "Sim")
    ),

    clima = factor(
      weathersit,
      levels = c(1, 2, 3, 4),
      labels = c(
        "Claro ou parcialmente nublado",
        "Neblina ou nublado",
        "Chuva ou neve leve",
        "Chuva forte ou neve intensa"
      )
    ),

    temperatura_celsius = temp * 41,
    sensacao_termica_celsius = atemp * 50,
    umidade_percentual = hum * 100,
    velocidade_vento = windspeed * 67,

    percentual_casuais = casual / cnt * 100,
    percentual_registrados = registered / cnt * 100,

    faixa_temperatura = case_when(
      temperatura_celsius < 10 ~ "Frio",
      temperatura_celsius < 20 ~ "Ameno",
      temperatura_celsius < 30 ~ "Quente",
      TRUE ~ "Muito quente"
    ),

    faixa_temperatura = factor(
      faixa_temperatura,
      levels = c("Frio", "Ameno", "Quente", "Muito quente")
    ),

    classe_demanda = ntile(cnt, 4),

    classe_demanda = factor(
      classe_demanda,
      levels = c(1, 2, 3, 4),
      labels = c("Baixa", "Média-baixa", "Média-alta", "Alta")
    )
  ) %>%
  select(
    data,
    ano,
    numero_mes,
    mes,
    dia_semana,
    fim_de_semana,
    feriado,
    dia_util,
    estacao,
    clima,
    faixa_temperatura,
    temperatura_celsius,
    sensacao_termica_celsius,
    umidade_percentual,
    velocidade_vento,
    casual,
    registered,
    cnt,
    percentual_casuais,
    percentual_registrados,
    classe_demanda
  )

Pré-processamento da base horária

A base horária é preparada de forma mais enxuta. O objetivo aqui não é analisá-la completamente nesta entrega parcial, mas usá-la para conferir se a soma das horas de cada dia corresponde ao total diário informado em day.csv.

hour_clean <- hour_raw %>%
  clean_names() %>%
  mutate(
    data = ymd(dteday),
    ano = if_else(yr == 0, 2011, 2012),
    hora = hr,

    periodo_dia = case_when(
      hora >= 5  & hora <= 11 ~ "Manhã",
      hora >= 12 & hora <= 17 ~ "Tarde",
      hora >= 18 & hora <= 23 ~ "Noite",
      TRUE ~ "Madrugada"
    ),

    periodo_dia = factor(
      periodo_dia,
      levels = c("Madrugada", "Manhã", "Tarde", "Noite")
    )
  )

Validação entre base horária e diária

Como existem dois arquivos, é possível fazer uma checagem simples: somar os aluguéis por hora em cada data e comparar com o total diário. Essa etapa funciona como uma “conferência de quilometragem” antes de seguir viagem com a análise.

validacao_agregacao <- hour_clean %>%
  group_by(data) %>%
  summarise(
    cnt_somado_pelas_horas = sum(cnt),
    casual_somado_pelas_horas = sum(casual),
    registered_somado_pelas_horas = sum(registered),
    .groups = "drop"
  )

day_prepared <- day_clean %>%
  left_join(validacao_agregacao, by = "data") %>%
  mutate(
    diferenca_total = cnt - cnt_somado_pelas_horas,
    diferenca_casual = casual - casual_somado_pelas_horas,
    diferenca_registrados = registered - registered_somado_pelas_horas
  )
resumo_validacao <- day_prepared %>%
  summarise(
    dias_comparados = n(),
    maior_diferenca_total = max(abs(diferenca_total), na.rm = TRUE),
    maior_diferenca_casual = max(abs(diferenca_casual), na.rm = TRUE),
    maior_diferenca_registrados = max(abs(diferenca_registrados), na.rm = TRUE)
  )

kable(resumo_validacao)
dias_comparados maior_diferenca_total maior_diferenca_casual maior_diferenca_registrados
731 0 0 0

Resultado da validação: se as diferenças acima forem iguais a zero, a base diária está coerente com a soma da base horária. Isso aumenta a confiança para usar a base diária como principal fonte da análise exploratória.

Base final pré-processada

Após a limpeza, a base passa a ter datas reais, categorias nomeadas, variáveis climáticas em escala interpretável e variáveis derivadas para apoiar a análise.

day_prepared %>%
  select(
    data, ano, mes, dia_semana, fim_de_semana, estacao, clima,
    temperatura_celsius, casual, registered, cnt, classe_demanda
  ) %>%
  head(12) %>%
  kable(digits = 2)
data ano mes dia_semana fim_de_semana estacao clima temperatura_celsius casual registered cnt classe_demanda
2011-01-01 2011 Jan Sábado Sim Primavera Neblina ou nublado 14.11 331 654 985 Baixa
2011-01-02 2011 Jan Domingo Sim Primavera Neblina ou nublado 14.90 131 670 801 Baixa
2011-01-03 2011 Jan Segunda Não Primavera Claro ou parcialmente nublado 8.05 120 1229 1349 Baixa
2011-01-04 2011 Jan Terça Não Primavera Claro ou parcialmente nublado 8.20 108 1454 1562 Baixa
2011-01-05 2011 Jan Quarta Não Primavera Claro ou parcialmente nublado 9.31 82 1518 1600 Baixa
2011-01-06 2011 Jan Quinta Não Primavera Claro ou parcialmente nublado 8.38 88 1518 1606 Baixa
2011-01-07 2011 Jan Sexta Não Primavera Neblina ou nublado 8.06 148 1362 1510 Baixa
2011-01-08 2011 Jan Sábado Sim Primavera Neblina ou nublado 6.77 68 891 959 Baixa
2011-01-09 2011 Jan Domingo Sim Primavera Claro ou parcialmente nublado 5.67 54 768 822 Baixa
2011-01-10 2011 Jan Segunda Não Primavera Claro ou parcialmente nublado 6.18 41 1280 1321 Baixa
2011-01-11 2011 Jan Terça Não Primavera Neblina ou nublado 6.93 43 1220 1263 Baixa
2011-01-12 2011 Jan Quarta Não Primavera Claro ou parcialmente nublado 7.08 25 1137 1162 Baixa

Resumo consolidado das variáveis de interesse

resumo_geral <- day_prepared %>%
  summarise(
    data_inicial = min(data),
    data_final = max(data),
    dias_observados = n(),
    media_alugueis_dia = mean(cnt),
    mediana_alugueis_dia = median(cnt),
    menor_demanda = min(cnt),
    maior_demanda = max(cnt),
    media_temperatura_celsius = mean(temperatura_celsius),
    media_umidade_percentual = mean(umidade_percentual),
    media_vento = mean(velocidade_vento),
    media_percentual_registrados = mean(percentual_registrados),
    media_percentual_casuais = mean(percentual_casuais)
  )

kable(resumo_geral, digits = 2)
data_inicial data_final dias_observados media_alugueis_dia mediana_alugueis_dia menor_demanda maior_demanda media_temperatura_celsius media_umidade_percentual media_vento media_percentual_registrados media_percentual_casuais
2011-01-01 2012-12-31 731 4504.35 4548 22 8714 20.31 62.79 12.76 82.44 17.56
resumo_estacao <- day_prepared %>%
  group_by(estacao) %>%
  summarise(
    dias_observados = n(),
    media_alugueis = mean(cnt),
    mediana_alugueis = median(cnt),
    media_temperatura = mean(temperatura_celsius),
    .groups = "drop"
  ) %>%
  arrange(desc(media_alugueis))

kable(resumo_estacao, digits = 2)
estacao dias_observados media_alugueis mediana_alugueis media_temperatura
Outono 188 5644.30 5353.5 28.96
Verão 184 4992.33 4941.5 22.32
Inverno 178 4728.16 4634.5 17.34
Primavera 181 2604.13 2209.0 12.21
resumo_clima <- day_prepared %>%
  group_by(clima) %>%
  summarise(
    dias_observados = n(),
    media_alugueis = mean(cnt),
    mediana_alugueis = median(cnt),
    media_umidade = mean(umidade_percentual),
    .groups = "drop"
  ) %>%
  arrange(desc(media_alugueis))

kable(resumo_clima, digits = 2)
clima dias_observados media_alugueis mediana_alugueis media_umidade
Claro ou parcialmente nublado 463 4876.79 4844 56.56
Neblina ou nublado 247 4035.86 4040 72.59
Chuva ou neve leve 21 1803.29 1817 84.89

Arquivo gerado para a próxima etapa

write_csv(day_prepared, "bike_day_preprocessado.csv")

Entrega parcial: este relatório chega até o pré-processamento. A próxima etapa será a análise exploratória, com gráficos e tabelas comparando demanda por estação, mês, clima, temperatura, fim de semana e perfil dos usuários.

Fechamento da etapa parcial

Nesta etapa, a base deixou de ser apenas um conjunto de códigos numéricos e passou a representar uma leitura mais clara da cidade: datas, períodos, clima, temperatura, tipo de dia e composição dos usuários.

O pré-processamento realizado prepara o terreno para investigar padrões de mobilidade. A partir daqui, será possível analisar não apenas quantas bicicletas foram alugadas, mas em quais contextos a demanda cresce ou diminui. Essa diferença é importante porque uma boa análise não começa no gráfico; ela começa na forma como os dados são organizados para que o gráfico possa contar algo verdadeiro. ````