Este projeto analisa dados de permissões de construção (Building Permits) da cidade de São Francisco, Califórnia, cobrindo um período de 5 anos (janeiro de 2013 a fevereiro de 2018) com aproximadamente 200.000 registros.
Uma permissão de construção é um documento oficial de aprovação emitido por uma agência governamental que permite ao proprietário ou contratante prosseguir com um projeto de construção ou reforma em sua propriedade. Cada cidade ou condado possui seu próprio departamento relacionado a edificações, que desempenha múltiplas funções como emissão de permissões, inspeção de edifícios para garantir medidas de segurança, e modificação de regulamentos para acomodar as necessidades da população em crescimento.
O dataset foi obtido no Kaggle (Building Permit Applications Data) e contém 43 colunas com informações detalhadas sobre cada permissão emitida, incluindo tipo de permissão, localização, custos estimados, datas de solicitação e aprovação, status, e informações do solicitante.
O dataset consiste em um único arquivo contendo 43 colunas e aproximadamente 200.000 registros de permissões de construção. As principais categorias de informações incluem:
Informações Temporais: - Datas de solicitação, emissão, aprovação e conclusão - Tempo de processamento das permissões
Informações de Localização: - Endereço completo - Número do bloco e lote - Coordenadas geográficas (latitude e longitude) - Distrito supervisor - Bairro
Informações do Projeto: - Tipo de permissão - Situação da permissão - Descrição do trabalho proposto - Número de unidades existentes e propostas - Custo estimado do projeto
Informações Administrativas: - Número da permissão - Número da solicitação - Informações do solicitante - Departamento responsável
Dados Estruturais: - Tipo de construção - Uso existente e proposto da propriedade
Este projeto visa realizar uma análise exploratória dos dados de permissões de construção de San Francisco para extrair insights sobre o processo de aprovação, padrões de desenvolvimento urbano e eficiência administrativa. Os objetivos específicos são:
Análise Temporal:
Análise de Tipos de Permissões:
Análise Geográfica:
Análise de Custos:
# Pacotes
library(tidyverse)
library(lubridate)
O dataset de Building Permits contém 43 colunas. Abaixo estão descritas todas as colunas do dataset:
Nesta etapa, realizaremos a importação do dataset de Building Permits para o ambiente R. O processo inclui:
read.csv() com parâmetros adequadosDetalhe:
Completed Date, Unit,
Revised Cost# 1. Definição do caminho e 2. Importação
permits <- read.csv("Building_Permits.csv", stringsAsFactors = FALSE)
# 3. Conversão de tipos de dados
# Converter datas usando lubridate
permits <- permits %>%
mutate(
Filed.Date = mdy(Filed.Date),
Issued.Date = mdy(Issued.Date),
Completed.Date = mdy(Completed.Date),
Permit.Creation.Date = mdy(Permit.Creation.Date),
Permit.Expiration.Date = mdy(Permit.Expiration.Date)
)
# Converter custos para numérico
permits <- permits %>%
mutate(
Estimated.Cost = as.numeric(Estimated.Cost),
Revised.Cost = as.numeric(Revised.Cost)
)
# Processar coordenadas geográficas
# A coluna Location está no formato "(lat, long)". Vamos limpar e separar.
permits <- permits %>%
mutate(Location_Clean = gsub("[()]", "", Location)) %>%
separate(Location_Clean, into = c("Latitude_Parsed", "Longitude_Parsed"), sep = ", ", convert = TRUE, remove = FALSE)
# 4. Tratamento de valores ausentes
# Vamos verificar NAs em colunas chave.
# Para este exercício, vamos manter os NAs em
# datas (pois indicam processos não finalizados)
# e custos (pois podem ser desconhecidos).
permits$Number.of.Existing.Stories[is.na(permits$Number.of.Existing.Stories)] <- 0
permits$Proposed.Units[is.na(permits$Proposed.Units)] <- 0
permits$Proposed.Construction.Type[is.na(permits$Proposed.Construction.Type)] <- 0
permits$Existing.Units[is.na(permits$Existing.Units)] <- 0
permits$Existing.Construction.Type[is.na(permits$Existing.Construction.Type)] <- 0
permits$Number.of.Proposed.Stories[is.na(permits$Number.of.Proposed.Stories)] <- 0
# 5. Remoção de colunas inutilizadas
permits <- permits %>%
select(-Record.ID)
# 6. Criação de variáveis derivadas
permits <- permits %>%
mutate(
# Tempo de processamento em dias
Processing_Time = as.numeric(difftime(Issued.Date, Filed.Date, units = "days")),
# Categoria de custo
Cost_Category = as.factor(case_when(
Estimated.Cost < 5000 ~ "Baixo (<5k)",
Estimated.Cost < 50000 ~ "Médio (5k-50k)",
Estimated.Cost < 1000000 ~ "Alto (50k-1M)",
Estimated.Cost >= 1000000 ~ "Muito Alto (>1M)",
TRUE ~ "Desconhecido"
))
)
# 7. Filtragem e limpeza
permits <- permits %>%
# Remove linhas duplicadas
distinct() %>%
# Remove registros sem número de permissão se houver
filter(!is.na(Permit.Number))
Nesta seção, analisamos o tempo de processamento das permissões e as tendências anuais.
# Resumo do tempo de processamento
summary(permits$Processing_Time)
## Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
## 0.00 0.00 0.00 26.05 6.00 1740.00 14928
# Visualização do tempo de processamento por tipo (limitando outliers para visualização)
permits %>%
filter(!is.na(Processing_Time), !is.na(Permit.Type.Definition)) %>%
ggplot(aes(x = reorder(Permit.Type.Definition, Processing_Time, FUN = median), y = Processing_Time)) +
geom_boxplot() +
coord_flip() +
labs(title = "Tempo de Processamento por Tipo de Permissão",
x = "Tipo de Permissão",
y = "Dias até Emissão") +
theme_minimal() +
scale_y_log10() # Escala logarítmica para melhor visualização
# Extrair ano de Filed.Date e Issued.Date
permits_annual <- permits %>%
mutate(
Filed_Year = year(Filed.Date),
Issued_Year = year(Issued.Date)
)
# Contagem por ano (Solicitações)
filed_counts <- permits_annual %>%
filter(!is.na(Filed_Year)) %>%
count(Filed_Year) %>%
mutate(Type = "Solicitadas")
# Contagem por ano (Emitidas)
issued_counts <- permits_annual %>%
filter(!is.na(Issued_Year)) %>%
count(Issued_Year, name = "n") %>%
rename(Filed_Year = Issued_Year) %>%
mutate(Type = "Emitidas")
# Combinar e plotar
bind_rows(filed_counts, issued_counts) %>%
filter(Filed_Year >= 2013, Filed_Year <= 2018) %>% # Focar no período principal
ggplot(aes(x = Filed_Year, y = n, color = Type)) +
geom_line(size = 1) +
geom_point() +
labs(title = "Volume Anual de Permissões: Solicitadas vs Emitidas",
x = "Ano",
y = "Quantidade",
color = "Status") +
theme_minimal()
Investigamos a frequência dos diferentes tipos de permissões e seus status atuais.
# Frequência de Tipos de Permissão
permits %>%
count(Permit.Type.Definition, sort = TRUE) %>%
ggplot(aes(x = reorder(Permit.Type.Definition, n), y = n)) +
geom_col(fill = "steelblue") +
coord_flip() +
labs(title = "Frequência dos Tipos de Permissão",
x = "Tipo de Permissão",
y = "Contagem") +
theme_minimal()
# Distribuição de Status
permits %>%
count(Current.Status, sort = TRUE) %>%
top_n(10, n) %>% # Top 10 status mais comuns
ggplot(aes(x = reorder(Current.Status, n), y = n)) +
geom_col(fill = "darkgreen") +
coord_flip() +
labs(title = "Top 10 Status Atuais das Permissões",
x = "Status",
y = "Contagem") +
theme_minimal()
Exploramos a distribuição espacial das permissões em São Francisco.
# Mapa de pontos (Scatter plot das coordenadas)
permits %>%
filter(!is.na(Latitude_Parsed), !is.na(Longitude_Parsed)) %>%
filter(Neighborhoods...Analysis.Boundaries != "") %>%
# Filtrar coordenadas inválidas (SF approx: Lat 37.7, Long -122.4)
filter(Latitude_Parsed > 37, Latitude_Parsed < 38, Longitude_Parsed > -123, Longitude_Parsed < -122) %>%
ggplot(aes(x = Longitude_Parsed, y = Latitude_Parsed, color = Neighborhoods...Analysis.Boundaries)) +
geom_point(alpha = 0.5, size = 0.5) +
labs(title = "Distribuição Espacial das Permissões por Bairro",
x = "Longitude",
y = "Latitude",
color = "Bairro") +
coord_quickmap() +
theme_minimal() +
theme(legend.position = "none") # Legenda removida para evitar poluição visual
# Top Bairros
# Nota: O nome da coluna de bairro é assumido como 'Neighborhoods...Analysis.Boundaries'
permits %>%
count(Neighborhoods...Analysis.Boundaries, sort = TRUE) %>%
top_n(15, n) %>%
filter(Neighborhoods...Analysis.Boundaries != "") %>%
ggplot(aes(x = reorder(Neighborhoods...Analysis.Boundaries, n), y = n)) +
geom_col(fill = "orange") +
coord_flip() +
labs(title = "Top 15 Bairros com Mais Permissões",
x = "Bairro",
y = "Contagem") +
theme_minimal()
# Mapa de calor de custos
permits %>%
filter(!is.na(Latitude_Parsed), !is.na(Longitude_Parsed), !is.na(Estimated.Cost)) %>%
filter(Latitude_Parsed > 37, Latitude_Parsed < 38, Longitude_Parsed > -123, Longitude_Parsed < -122) %>%
arrange(Estimated.Cost) %>%
ggplot(aes(x = Longitude_Parsed, y = Latitude_Parsed, color = Estimated.Cost)) +
geom_point(alpha = 0.5, size = 0.5) +
scale_color_viridis_c(trans = "log10", labels = scales::dollar) +
labs(title = "Distribuição Espacial de Custos (Escala Log)",
x = "Longitude",
y = "Latitude",
color = "Custo Estimado") +
coord_quickmap() +
theme_minimal()
Examinamos os custos estimados dos projetos.
# Distribuição de Custos Estimados (Histograma)
permits %>%
filter(!is.na(Estimated.Cost), Estimated.Cost > 10) %>%
ggplot(aes(x = Estimated.Cost)) +
geom_histogram(bins = 50, fill = "firebrick", color = "white") +
scale_x_log10(labels = scales::dollar) +
labs(title = "Distribuição dos Custos Estimados (Escala Log)",
x = "Custo Estimado (USD)",
y = "Frequência") +
theme_minimal()
# Custo por Tipo de Permissão
permits %>%
filter(!is.na(Estimated.Cost), Estimated.Cost > 10, !is.na(Permit.Type.Definition)) %>%
ggplot(aes(x = reorder(Permit.Type.Definition, Estimated.Cost, FUN = median), y = Estimated.Cost)) +
geom_boxplot() +
scale_y_log10(labels = scales::dollar) +
coord_flip() +
labs(title = "Distribuição de Custos por Tipo de Permissão",
x = "Tipo de Permissão",
y = "Custo Estimado (USD)") +
theme_minimal()
# Custo Médio por Bairro
permits %>%
filter(!is.na(Estimated.Cost), Neighborhoods...Analysis.Boundaries != "") %>%
group_by(Neighborhoods...Analysis.Boundaries) %>%
summarise(
Avg_Cost = mean(Estimated.Cost, na.rm = TRUE),
Median_Cost = median(Estimated.Cost, na.rm = TRUE),
Count = n()
) %>%
filter(Count > 50) %>% # Filtrar bairros com poucas observações para robustez
top_n(15, Avg_Cost) %>%
ggplot(aes(x = reorder(Neighborhoods...Analysis.Boundaries, Avg_Cost), y = Avg_Cost)) +
geom_col(fill = "darkred") +
coord_flip() +
scale_y_continuous(labels = scales::dollar) +
labs(title = "Top 15 Bairros por Custo Médio Estimado",
subtitle = "Considerando apenas bairros com > 50 permissões",
x = "Bairro",
y = "Custo Médio") +
theme_minimal()
# Taxa de Emissão por Faixa de Custo Estimado
permits %>%
filter(!is.na(Estimated.Cost), Estimated.Cost > 10) %>%
mutate(Issued_Flag = ifelse(!is.na(Issued.Date), 1, 0)) %>%
ggplot(aes(x = Estimated.Cost, y = Issued_Flag)) +
stat_summary_bin(fun = "mean", geom = "bar", bins = 50, fill = "steelblue", color = "white") +
scale_x_log10(labels = scales::dollar) +
scale_y_continuous(labels = scales::percent) +
labs(title = "Taxa de Emissão por Custo Estimado",
x = "Custo Estimado (USD)",
y = "Porcentagem Emitida") +
theme_minimal()
Com base nas análises realizadas, podemos concluir que: