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.
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.
library(tidyverse)
library(lubridate)
library(janitor)
library(knitr)
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.
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
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")
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…
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 |
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 |
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.
A preparação da base diária segue cinco decisões principais:
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
)
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")
)
)
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.
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_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 |
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.
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. ````