Análise de Risco de Sinistros Residenciais
1. Descrição Geral do Problema¶

Os sinistros residenciais representam uma significativa preocupação para as seguradoras, impactando diretamente na sustentabilidade financeira e na satisfação do cliente. A previsão eficaz de sinistros pode ajudar as seguradoras a aprimorar suas estratégias de precificação e mitigação de riscos.
Nesse contexto, o desafio consiste em analisar um conjunto de dados de propriedades residenciais e seus históricos de sinistros para construir um modelo que preveja a probabilidade de ocorrência de um sinistro. O objetivo é usar essa previsão para reduzir os custos com sinistros, melhorar a precisão da precificação das apólices de seguro e aumentar a satisfação do cliente.
2. Data Science Workflow Canvas¶
| Elemento | Descrição |
|---|---|
| Problema de Negócio | Sinistros residenciais representam uma preocupação significativa para seguradoras, impactando a sustentabilidade financeira e a satisfação dos clientes. O objetivo é prever sinistros para reduzir custos, melhorar a precificação das apólices e aumentar a satisfação do cliente. |
| Outcomes/Prediction | Construir um modelo preditivo que identifique a probabilidade de ocorrência de sinistros em propriedades residenciais com alta precisão, utilizando técnicas de machine learning. |
| Data Acquisition | Conjunto de dados de propriedades residenciais e seus históricos de sinistros. |
| Data Preparation | Limpeza e transformação dos dados, incluindo a conversão de variáveis categóricas, normalização e tratamento de valores ausentes para garantir a qualidade e adequação ao modelo. |
| Exploração de Dados | Análise exploratória inicial para entender as características e distribuição das variáveis, identificando padrões e correlações relevantes. |
| Modeling | Treinamento de modelos de machine learning, como Random Forest, ajustando hiperparâmetros para prever a ocorrência de sinistros e otimizar a performance do modelo. |
| Model Evaluation | Avaliação da performance do modelo usando métricas como acurácia, precisão, recall, F1-score e matriz de confusão. Validação cruzada para assegurar a generalização do modelo. |
| Prediction | Teste do modelo com dados de exemplo ajustados para verificar a capacidade de detecção de sinistros. |
| Conclusões e Recomendações | Resultados obtidos e recomendações baseadas nas análises e modelagem, visando a implementação operacional do modelo para otimizar a precificação de apólices e reduzir os custos com sinistros. |
3. Importação de Bibliotecas¶
Preparação do Ambiente de Análise¶
Inicializa o ambiente de análise carregando todas as bibliotecas necessárias para manipulação de dados, análise estatística e visualização, garantindo que todas as ferramentas estejam disponíveis para as etapas subsequentes.
# Importando bibliotecas necessárias
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import statsmodels.api as sm
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
from sklearn.linear_model import LogisticRegression, LinearRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier, VotingClassifier
from sklearn.metrics import classification_report, accuracy_score, roc_auc_score, roc_curve
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay, auc
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from imblearn.over_sampling import SMOTE
from scipy.stats import ttest_ind
import plotly.express as px
from IPython.display import display, Markdown, HTML
from sklearn.metrics import mean_squared_error, r2_score
from xgboost import XGBClassifier
from lightgbm import LGBMClassifier
# Configurações estéticas dos gráficos do Seaborn e supressão de avisos
sns.set(style="whitegrid")
pd.options.display.float_format = '{:.2f}'.format
import warnings
warnings.filterwarnings("ignore")
# Configurações adicionais de visualização
%matplotlib inline
4. Função de Uso em Gráficos¶
# 1. Função para aplicar estilo aos gráficos
def estilo_grafico():
sns.set_style('whitegrid')
plt.rcParams.update({
'axes.spines.top': False,
'axes.spines.right': False,
'axes.edgecolor': 'white',
'axes.facecolor': 'white',
'grid.color': 'gray',
'grid.linestyle': '--',
'grid.alpha': 0.7,
'grid.linewidth': 0.7,
'figure.facecolor': 'white',
'figure.edgecolor': 'white',
'savefig.facecolor': 'white',
'savefig.edgecolor': 'white'
})
# Define a paleta de cores personalizada globalmente
cores_personalizadas = ['#184E77', '#1e6091', '#1a759f', '#168aad', '#34a0a4', '#52b69a', '#76c893', '#99d98c', '#b5e48c', '#d9ed92']
sns.set_palette(cores_personalizadas)
5. Carregamento de Dados¶
# Carregamento de dados
try:
dados = pd.read_csv('dados_seguro_residenciais.csv', delimiter=';', encoding='utf-8')
display(Markdown("<h3 style='color: darkred'>Dados carregados com sucesso!!</h3>"))
except FileNotFoundError:
display(Markdown("<h3 style='color: darkred'>Não foi possível carregar o arquivo!!</h3>"))
6. Exploração Inicial dos Dados¶
6.1 Primeiras linhas do DataFrame para inspeção inicial¶
#*********** Exibir as primeiras linhas e informações dos dados ***********
display(dados.head())
display(dados.info())
| cidade_residencia | idade_proprietario | valor_imovel | tipo_construcao | historico_sinistro | valor_premio_seguro | sinistro | regiao | renda_anual | historico_credito | ... | valor_seguro | numero_comodos | idade_imovel | material_construcao | seguranca | perfil_seg | risco_climatico | historico_reclamacoes | tamanho_imovel | area_m2 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | São Gonçalo | 71 | 387671.21 | Condomínio Fechado | 0 | 3752.74 | 0 | Sudeste | 77826.00 | 782 | ... | 1938.00 | 3 | 5 | 4 | 0 | 23681.90 | 3 | 0 | 5102.04 | 75.98 |
| 1 | Caruaru | 55 | 333997.09 | Studio | 0 | 1055.28 | 0 | Nordeste | 51997.00 | 732 | ... | 1670.00 | 5 | 7 | 5 | 1 | 15908.40 | 2 | 0 | 10416.67 | 32.06 |
| 2 | Manaus | 62 | 375704.65 | Sobrado | 0 | 2567.33 | 0 | Norte | 65169.00 | 541 | ... | 1879.00 | 10 | 17 | 6 | 1 | 19785.70 | 1 | 0 | 5494.51 | 68.38 |
| 3 | Maringá | 77 | 475643.69 | Sobrado | 0 | 5345.85 | 1 | Sul | 78940.00 | 576 | ... | 2378.00 | 6 | 34 | 6 | 1 | 23935.50 | 4 | 0 | 5494.51 | 86.57 |
| 4 | Contagem | 72 | 199999.49 | Cobertura | 0 | 3269.24 | 0 | Sudeste | 62200.00 | 487 | ... | 1000.00 | 5 | 22 | 7 | 1 | 18876.40 | 3 | 0 | 5555.56 | 36.00 |
5 rows × 26 columns
<class 'pandas.core.frame.DataFrame'> RangeIndex: 8000 entries, 0 to 7999 Data columns (total 26 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 cidade_residencia 8000 non-null object 1 idade_proprietario 8000 non-null int64 2 valor_imovel 8000 non-null float64 3 tipo_construcao 8000 non-null object 4 historico_sinistro 8000 non-null int64 5 valor_premio_seguro 8000 non-null float64 6 sinistro 8000 non-null int64 7 regiao 8000 non-null object 8 renda_anual 8000 non-null float64 9 historico_credito 8000 non-null int64 10 numero_dependentes 8000 non-null int64 11 ano_construcao 8000 non-null int64 12 distancia_bombeiros 8000 non-null float64 13 sistema_alarme 8000 non-null int64 14 cameras_seguranca 8000 non-null int64 15 estado 8000 non-null object 16 valor_seguro 8000 non-null float64 17 numero_comodos 8000 non-null int64 18 idade_imovel 8000 non-null int64 19 material_construcao 8000 non-null int64 20 seguranca 8000 non-null int64 21 perfil_seg 8000 non-null float64 22 risco_climatico 8000 non-null int64 23 historico_reclamacoes 8000 non-null int64 24 tamanho_imovel 8000 non-null float64 25 area_m2 8000 non-null float64 dtypes: float64(8), int64(14), object(4) memory usage: 1.6+ MB
None
6.2 Apresenta Metadados de Cada Variável:¶
| Variável | Descrição | Tipo de Dados |
|---|---|---|
cidade_residencia | Nome da cidade do imóvel segurado | object |
idade_proprietario | Idade do proprietário do imóvel | int64 |
valor_imovel | Valor estimado do imóvel | float64 |
tipo_construcao | Tipo de construção do imóvel (casa, apartamento, etc.) | object |
historico_sinistro | Indica se houve sinistros anteriores na propriedade (1 para sim, 0 para não) | int64 |
valor_premio_seguro | Valor pago pelo segurado para obter a cobertura do seguro | float64 |
sinistro | Indica se ocorreu um sinistro (0 para Não, 1 para Sim) | int64 |
regiao | Informa qual das regiões presentes no Brasil está o imóvel | object |
renda_anual | Renda anual do proprietário | float64 |
historico_credito | Histórico de crédito do proprietário | int64 |
numero_dependentes | Número de dependentes do proprietário | int64 |
ano_construcao | Ano de construção do imóvel | int64 |
distancia_bombeiros | Distância até a estação de bombeiros mais próxima | float64 |
sistema_alarme | Indica se o imóvel possui sistema de alarme (1 para sim, 0 para não) | int64 |
cameras_seguranca | Indica se o imóvel possui câmeras de segurança (1 para sim, 0 para não) | int64 |
estado | Estado onde o imóvel está localizado | object |
valor_seguro | Valor do seguro contratado | float64 |
numero_comodos | Número de cômodos do imóvel | int64 |
idade_imovel | Idade do imóvel calculada a partir do ano de construção | int64 |
material_construcao | Tipo de material utilizado na construção | int64 |
seguranca | Índice de segurança do imóvel (sistema de segurança, etc.) | int64 |
perfil_seg | Perfil do segurado calculado com base em várias características | float64 |
risco_climatico | Índice de risco climático da região | int64 |
historico_reclamacoes | Histórico de reclamações do proprietário | int64 |
tamanho_imovel | Tamanho do imóvel em metros quadrados | float64 |
area_m2 | Área do imóvel em metros quadrados | float64 |
6.3. Análise de Dados Faltantes¶
# *********** Calcula o total e a porcentagem de dados faltantes por coluna ***********
total_faltantes = dados.isnull().sum()
percentual_faltantes = (dados.isnull().mean() * 100)
#*********** Criar um DataFrame para a tabela resumida ***********
tabela_resumo_faltantes = pd.DataFrame({
'Coluna': total_faltantes.index,
'Dados Faltantes': total_faltantes.values,
'Percentual (%)': percentual_faltantes.values
})
# *********** Ordenar a tabela pelo número de dados faltantes ***********
if (total_faltantes > 0).any():
tabela_resumo_faltantes = tabela_resumo_faltantes.sort_values(by='Dados Faltantes', ascending=False)
else:
tabela_resumo_faltantes = tabela_resumo_faltantes
# *********** Exibi a tabela resumida ***********
display(Markdown("<h3 style='color: darkblue'>Tabela Resumo de Dados Faltantes</h3>"))
print(tabela_resumo_faltantes)
Tabela Resumo de Dados Faltantes
Coluna Dados Faltantes Percentual (%) 0 cidade_residencia 0 0.00 1 idade_proprietario 0 0.00 2 valor_imovel 0 0.00 3 tipo_construcao 0 0.00 4 historico_sinistro 0 0.00 5 valor_premio_seguro 0 0.00 6 sinistro 0 0.00 7 regiao 0 0.00 8 renda_anual 0 0.00 9 historico_credito 0 0.00 10 numero_dependentes 0 0.00 11 ano_construcao 0 0.00 12 distancia_bombeiros 0 0.00 13 sistema_alarme 0 0.00 14 cameras_seguranca 0 0.00 15 estado 0 0.00 16 valor_seguro 0 0.00 17 numero_comodos 0 0.00 18 idade_imovel 0 0.00 19 material_construcao 0 0.00 20 seguranca 0 0.00 21 perfil_seg 0 0.00 22 risco_climatico 0 0.00 23 historico_reclamacoes 0 0.00 24 tamanho_imovel 0 0.00 25 area_m2 0 0.00
6.4. Análise de Dados Duplicados¶
#*********** Conta o número de registros duplicados ***********
numero_duplicados = dados.duplicated().sum()
#*********** calcula a porcentagem de duplicatas em relação ao total ***********
percentual_duplicados = (numero_duplicados / len(dados)) * 100
if numero_duplicados > 0: # informa quantidade de dados duplicados
display(Markdown(f"<h3 style='color: darkred'>Foram encontrados {numero_duplicados} registros duplicados, representando {percentual_duplicados:.2f}% do total de {dados.shape[0]} registros!</h3>"))
else: # informa se não existe dados duplicados
display(Markdown("<h2 style='color: red'>Não foram encontrados dados duplicados no conjunto de dados</h2>"))
print()
Não foram encontrados dados duplicados no conjunto de dados
6.5 Resumo Estatístico das Variáveis Numéricas¶
#********** Configurando a formatação de números flutuantes para duas casas decimais *********
pd.options.display.float_format = '{:.2f}'.format
# ********* Gerando o resumo estatístico dos dados ***********
estatisticas = dados.describe()
# *********** Renomeando as colunas e índices do resumo estatístico para português ***********
estatisticas.rename(columns={
'count': 'contagem',
'mean': 'média',
'std': 'desvio_padrão',
'min': 'mínimo',
'25%': '1º quartil 25%',
'50%': 'mediana 50%',
'75%': '3º quartil 75%',
'max': 'máximo'
}, index={
'count': 'contagem',
'mean': 'média',
'std': 'desvio_padrão',
'min': 'mínimo',
'25%': '1º quartil 25%',
'50%': 'mediana 50%',
'75%': '3º quartil 75%',
'max': 'máximo'
}, inplace=True)
#*********** Imprimindo o resumo estatístico formatado e traduzido ***********
print(estatisticas)
idade_proprietario valor_imovel historico_sinistro \
contagem 8000.00 8000.00 8000.00
média 49.91 299781.42 0.10
desvio_padrão 11.68 114530.75 0.30
mínimo 25.00 100047.46 0.00
1º quartil 25% 42.00 202121.23 0.00
mediana 50% 50.00 299729.24 0.00
3º quartil 75% 58.00 396628.12 0.00
máximo 96.00 499957.07 1.00
valor_premio_seguro sinistro renda_anual historico_credito \
contagem 8000.00 8000.00 8000.00 8000.00
média 2523.50 0.15 50169.96 574.00
desvio_padrão 1126.52 0.36 15124.66 159.57
mínimo 768.43 0.00 -9424.00 300.00
1º quartil 25% 1634.05 0.00 40113.25 435.00
mediana 50% 2446.56 0.00 50166.00 572.50
3º quartil 75% 3262.89 0.00 60214.75 712.00
máximo 6422.88 1.00 101104.00 849.00
numero_dependentes ano_construcao distancia_bombeiros ... \
contagem 8000.00 8000.00 8000.00 ...
média 1.48 2000.66 5.27 ...
desvio_padrão 1.12 12.16 2.73 ...
mínimo 0.00 1980.00 0.50 ...
1º quartil 25% 0.00 1990.00 2.88 ...
mediana 50% 1.00 2001.00 5.33 ...
3º quartil 75% 2.00 2011.00 7.63 ...
máximo 3.00 2021.00 10.00 ...
valor_seguro numero_comodos idade_imovel \
contagem 8000.00 8000.00 8000.00
média 1498.91 6.22 22.34
desvio_padrão 572.65 2.56 12.16
mínimo 500.00 1.00 2.00
1º quartil 25% 1010.75 4.00 12.00
mediana 50% 1499.00 7.00 22.00
3º quartil 75% 1983.00 8.00 33.00
máximo 2500.00 10.00 43.00
material_construcao seguranca perfil_seg risco_climatico \
contagem 8000.00 8000.00 8000.00 8000.00
média 6.64 0.50 15296.17 2.91
desvio_padrão 1.41 0.61 4536.53 0.96
mínimo 4.00 0.00 1.00 1.00
1º quartil 25% 6.00 0.00 12259.57 2.00
mediana 50% 7.00 0.00 15295.60 3.00
3º quartil 75% 7.00 1.00 18315.85 4.00
máximo 9.00 2.00 30625.00 5.00
historico_reclamacoes tamanho_imovel area_m2
contagem 8000.00 8000.00 8000.00
média 0.10 7247.88 47.32
desvio_padrão 0.30 2028.93 31.49
mínimo 0.00 224.70 10.00
1º quartil 25% 0.00 5494.51 26.95
mediana 50% 0.00 7142.86 46.43
3º quartil 75% 0.00 8333.33 63.05
máximo 1.00 12500.00 489.15
[8 rows x 22 columns]
6.6 Verificação do Número de Observações e Colunas¶
#******************* Verificação de número de observações e colunas*******************
display(Markdown(f"<h3 style='color: darkblue'>Verificamos a existência de {dados.shape[0]} observações e {dados.shape[1]} colunas no DataSet.</h3>"))
print()
Verificamos a existência de 8000 observações e 26 colunas no DataSet.
6.7 Contabilizando o número de valores únicos em cada variável do dataset.¶
# *******************Contabilizando o número de valores únicos em cada variável do dataset *******************
num_valor_unico = dados.nunique().sort_values()
# Determinando o tipo de dado de cada uma das variáveis do dataset.
num_valor_unico = pd.DataFrame(num_valor_unico.values, index = num_valor_unico.index, columns = ['valor_unicos'])
# Atribuindo informações sobre o tipo de dado das variáveis ao DataFrame.
num_valor_unico
| valor_unicos | |
|---|---|
| historico_reclamacoes | 2 |
| historico_sinistro | 2 |
| sinistro | 2 |
| cameras_seguranca | 2 |
| sistema_alarme | 2 |
| seguranca | 3 |
| numero_dependentes | 4 |
| risco_climatico | 5 |
| regiao | 5 |
| material_construcao | 6 |
| tipo_construcao | 9 |
| numero_comodos | 10 |
| estado | 11 |
| cidade_residencia | 32 |
| idade_imovel | 42 |
| ano_construcao | 42 |
| idade_proprietario | 68 |
| tamanho_imovel | 114 |
| historico_credito | 550 |
| valor_seguro | 1970 |
| renda_anual | 7423 |
| perfil_seg | 7806 |
| valor_premio_seguro | 7907 |
| area_m2 | 7999 |
| valor_imovel | 7999 |
| distancia_bombeiros | 8000 |
# ************ Aplicando a função de estilo gráfico ************
estilo_grafico()
# ************ Contagem de sinistros por ano de construção ************
sinistros_por_ano = dados.groupby('ano_construcao')['sinistro'].sum()
# ************ Definindo tamanho do gráfico ************
plt.figure(figsize=(16, 7))
ax = sinistros_por_ano.plot(kind='bar',color="#DDA15E", edgecolor='black')
# ************ Definindo título e rótulos dos eixos ************
plt.title('Frequência de Sinistros por Ano de Construção do Imóveis', fontsize=18, pad=20)
plt.xlabel('Ano de Construção do Imóvel', labelpad=20, fontsize=16)
plt.ylabel('Número de Sinistros', labelpad=20, fontsize=16)
# ************ Adicionar valores acima de cada barra ************
for i in ax.containers:
ax.bar_label(i, label_type='edge')
plt.show()
7.1 Definição das variáveis categóricas e numéricas¶
#***************** Definição das variáveis categóricas e numéricas *************
colunas_numericas = [
'idade_proprietario', 'valor_imovel', 'valor_premio_seguro',
'renda_anual', 'historico_credito', 'numero_dependentes',
'ano_construcao', 'distancia_bombeiros', 'valor_seguro',
'numero_comodos', 'idade_imovel', 'material_construcao',
'seguranca', 'perfil_seg', 'risco_climatico',
'historico_reclamacoes', 'tamanho_imovel', 'area_m2'
]
colunas_categoricas = [
'cidade_residencia', 'tipo_construcao', 'regiao',
'sistema_alarme', 'cameras_seguranca', 'estado'
]
#****************** Pré-processamento de dados com imputação *******************
preprocessor = ColumnTransformer(
transformers=[
('num', Pipeline(steps=[
('imputer', SimpleImputer(strategy='mean')), # Imputação de valores faltantes para numéricas
('scaler', StandardScaler())]
), colunas_numericas),
('cat', OneHotEncoder(handle_unknown='ignore'), colunas_categoricas)
])
#******************* Definição da variável ALVO ********************
X = dados.drop(columns=['sinistro'])
y = dados['sinistro']
#****************** Transformação dos dados ************************
X_transformed = preprocessor.fit_transform(X)
7.1. Aplicação do SMOTE para Balanceamento de Dados¶
#*********** Aplicação do SMOTE para balanceamento de dados ***************
sm = SMOTE(random_state=42)
X_res, y_res = sm.fit_resample(X_transformed, y)
7.2. Divisão dos dados balanceados em treino e teste¶
#**************** Divisão dos dados balanceados em treino e teste **********************
X_train_res, X_test_res, y_train_res, y_test_res = train_test_split(X_res, y_res, test_size=0.3, random_state=42)
8.1 Pipeline com classificador¶
#**************** Pipeline com classificador *********************
pipeline = Pipeline(steps=[('classifier', RandomForestClassifier(random_state=42))])
8.2 Busca em grade com validação cruzada¶
#*************** Parâmetros para busca em grade ******************
param_grid = {
'classifier__n_estimators': [50, 100, 200],
'classifier__max_depth': [None, 10, 20, 30],
'classifier__min_samples_split': [2, 5, 10],
'classifier__min_samples_leaf': [1, 2, 4],
}
#****************** Busca em grade com validação cruzada ****************
grid_search = GridSearchCV(pipeline, param_grid, cv=5, n_jobs=-1, scoring='f1')
grid_search.fit(X_train_res, y_train_res)
GridSearchCV(cv=5,
estimator=Pipeline(steps=[('classifier',
RandomForestClassifier(random_state=42))]),
n_jobs=-1,
param_grid={'classifier__max_depth': [None, 10, 20, 30],
'classifier__min_samples_leaf': [1, 2, 4],
'classifier__min_samples_split': [2, 5, 10],
'classifier__n_estimators': [50, 100, 200]},
scoring='f1')In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
GridSearchCV(cv=5,
estimator=Pipeline(steps=[('classifier',
RandomForestClassifier(random_state=42))]),
n_jobs=-1,
param_grid={'classifier__max_depth': [None, 10, 20, 30],
'classifier__min_samples_leaf': [1, 2, 4],
'classifier__min_samples_split': [2, 5, 10],
'classifier__n_estimators': [50, 100, 200]},
scoring='f1')Pipeline(steps=[('classifier', RandomForestClassifier(random_state=42))])RandomForestClassifier(random_state=42)
8.3 Melhor modelo encontrado¶
#***************** Melhor modelo *************************
best_model = grid_search.best_estimator_
9.1 Classificação Relatório e Métricas¶
#***************** Avaliação do modelo com dados de teste ************
y_pred_res = best_model.predict(X_test_res)
print(classification_report(y_test_res, y_pred_res))
precision recall f1-score support
0 0.92 1.00 0.96 2040
1 1.00 0.91 0.95 2045
accuracy 0.95 4085
macro avg 0.96 0.95 0.95 4085
weighted avg 0.96 0.95 0.95 4085
Relatório de Avaliação do Modelo¶
Após a aplicação do pré-processamento dos dados, balanceamento com SMOTE e treinamento do modelo utilizando RandomForestClassifier, os resultados do modelo são os seguintes:
Explicação das Métricas de Avaliação do Modelo¶
| Métrica | Valor | Descrição |
|---|---|---|
| Accuracy | 0.95 | O modelo classificou corretamente 95% dos exemplos. |
| Precision (0) | 0.92 | 92% das predições para a classe 0 (sem sinistro) foram corretas. |
| Recall (0) | 1.00 | O modelo identificou todos os exemplos da classe 0 (sem sinistro). |
| F1-Score (0) | 0.95 | Equilíbrio entre precisão e recall para a classe 0 (sem sinistro). |
| Precision (1) | 1.00 | 100% das predições para a classe 1 (com sinistro) foram corretas. |
| Recall (1) | 0.91 | O modelo identificou 91% dos exemplos da classe 1 (com sinistro). |
| F1-Score (1) | 0.95 | Equilíbrio entre precisão e recall para a classe 1 (com sinistro). |
| Macro Avg | 0.96 | Média não ponderada do F1-Score de cada classe. |
| Weighted Avg | 0.96 | Média ponderada do F1-Score, ajustando-se ao desequilíbrio entre as classes. |
Desempenho do Modelo: O modelo apresentou uma accuracy de 95%, indicando um excelente desempenho geral. Tanto as classes 0 quanto 1 foram classificadas com alta precisão e recall, resultando em um F1-score de 95% para ambas as classes.
Equilíbrio das Classes: O uso de SMOTE para balanceamento de dados foi eficaz, resultando em uma classificação equilibrada entre as classes 0 e 1.
Conclusão¶
O modelo RandomForestClassifier apresentou um excelente desempenho com uma accuracy de 95%, sendo altamente eficaz na previsão de sinistros. A variável mais significativa foi valor_premio_seguro, enquanto as variáveis categóricas relacionadas à localização tiveram pouca importância.
9.2 Matriz de Confusão¶
- Este gráfico mostra o desempenho do modelo em termos de verdadeiros positivos, falsos positivos, verdadeiros negativos e falsos negativos, proporcionando uma visão clara dos acertos e erros nas previsões
# Aplicar o estilo gráfico
estilo_grafico()
from matplotlib.colors import LinearSegmentedColormap
# ******************* Matriz de Confusão *******************
cm = confusion_matrix(y_test_res, y_pred_res)
cmd = ConfusionMatrixDisplay(cm, display_labels=['Sem Sinistro', 'Com Sinistro'])
# ******************* Definindo o tamanho do gráfico *******************
fig, ax = plt.subplots(figsize=(10, 8))
# ******************* Personalizando as cores da matriz de confusão *******************
# Criando um novo colormap
colors = ['#F2E4E3','#8C4843'] # CIANO CLARO até a cor CIANO DO FACIFICO
n_bins = 100 # Número de bins para o gradiente
cmap_name = 'custom_cmap'
cm_custom = LinearSegmentedColormap.from_list(cmap_name, colors, N=n_bins)
cmd.plot(ax=ax, cmap=cm_custom)
# Personalizando o gráfico
ax.set_title("Matriz de Confusão", fontsize=18, pad=20)
ax.set_xlabel('Classe Predita', fontsize=14, labelpad=15)
ax.set_ylabel('Classe Real', fontsize=14, labelpad=15)
ax.tick_params(axis='both', which='major', labelsize=12)
# Adicionando anotações com valores corretos
ax.text(-0.0, -0.1, f'TN', va='center', ha='center', fontsize=16, color='black', bbox=dict(facecolor='lightblue', alpha=0.5))
ax.text(1.0, -0.1, f'FP', va='center', ha='center', fontsize=16, color='black', bbox=dict(facecolor='lightblue', alpha=0.5))
ax.text(0.02, 0.9, f'FN', va='center', ha='center', fontsize=16, color='black', bbox=dict(facecolor='lightblue', alpha=0.5))
ax.text(1.0, 0.9, f'TP', va='center', ha='center', fontsize=16, color='black', bbox=dict(facecolor='lightblue', alpha=0.5))
# Alterando as cores dos números abaixo dos rótulos
for i, j in np.ndindex(cm.shape):
ax.text(j, i, format(cm[i, j]), ha="center", va="center",
color="#ffffff" if cm[i, j] > cm.max() / 2. else "black")
# *********** Ajuste final nos eixos ******************
ax.set_ylabel('Classe Real', fontsize=14)
ax.set_xlabel('Classe Predita', fontsize=14)
plt.show()
Relatório da Matriz de Confusão¶
A matriz de confusão é uma ferramenta fundamental para avaliar a performance do modelo de machine learning utilizado na previsão de sinistros residenciais. Ela detalha os acertos e erros das previsões, permitindo uma análise precisa do desempenho do modelo. Abaixo está a matriz de confusão do modelo desenvolvido:
| Classe Predita: Sem Sinistro | Classe Predita: Com Sinistro | |
|---|---|---|
| Classe Real: Sem Sinistro | 2036 | 4 |
| Classe Real: Com Sinistro | 184 | 1861 |
Interpretação¶
- True Negatives (TN): 2036 imóveis foram corretamente classificados como sem sinistro.
- False Positives (FP): 4 imóveis foram incorretamente classificados como com sinistro.
- False Negatives (FN): 184 imóveis foram incorretamente classificados como sem sinistro.
- True Positives (TP): 1861 imóveis foram corretamente classificados como com sinistro.
Desempenho do Modelo¶
- Acurácia: O modelo demonstrou uma alta acurácia ao classificar corretamente a maioria dos exemplos.
- Precision: A precisão para a classe "com sinistro" (TP / (TP + FP)) indica que 99.8% das predições positivas foram corretas.
- Recall: A taxa de recall para a classe "com sinistro" (TP / (TP + FN)) indica que 90.1% dos exemplos reais positivos foram identificados corretamente.
Conclusão¶
A matriz de confusão demonstra que o modelo possui uma alta acurácia, sendo eficaz na classificação correta dos sinistros. O modelo apresenta uma precisão e recall elevados, indicando que é robusto e confiável. Podemos observar também que o número de falsos negativos sugere ajustes adicionais para verificar possibilidade de melhora na performance do modelo.
9.3 Análise de Importância de Variáveis¶
#****************** Análise de Importância de Variáveis **************
importances = best_model.named_steps['classifier'].feature_importances_
feature_names = preprocessor.transformers_[0][2] + list(preprocessor.transformers_[1][1].get_feature_names_out())
importance_df = pd.DataFrame(importances, index=feature_names, columns=['importance']).sort_values('importance', ascending=False)
print(importance_df)
importance valor_premio_seguro 0.26 numero_dependentes 0.05 numero_comodos 0.04 area_m2 0.02 historico_credito 0.02 ... ... cidade_residencia_Belém 0.00 estado_Pará 0.00 estado_Goiás 0.00 regiao_Centro-Oeste 0.00 cidade_residencia_Goiânia 0.00 [79 rows x 1 columns]
Importância das Variáveis¶
A análise da importância das variáveis mostrou que a variável valor_premio_seguro teve a maior importância no modelo, seguida por outras variáveis numéricas. Abaixo estão as variáveis mais importantes e suas respectivas importâncias:
| Variável | Importância |
|---|---|
| valor_premio_seguro | 0.26 |
| numero_dependentes | 0.05 |
| numero_comodos | 0.04 |
| area_m2 | 0.02 |
| historico_credito | 0.02 |
| cidade_residencia_Belém | 0.00 |
| estado_Pará | 0.00 |
| estado_Goiás | 0.00 |
| regiao_Centro-Oeste | 0.00 |
| cidade_residencia_Goiânia | 0.00 |
- A variável
valor_premio_segurofoi a mais significativa, com uma importância de 26%. Isso sugere que o valor do prêmio do seguro é um forte indicador na previsão da ocorrência de sinistros. - Outras variáveis numéricas como
numero_dependentes,numero_comodos,area_m2, ehistorico_creditotambém tiveram alguma importância, embora significativamente menor quevalor_premio_seguro. - Variáveis categóricas, especialmente relacionadas às cidades e estados, tiveram uma importância muito baixa ou nula no modelo.
10. Quantidade de Apólices em Diferentes Intervalos de Valores do Prêmio de Seguro¶
# Aplicar o estilo gráfico
estilo_grafico()
# Importando a biblioteca locale para formatação de moeda
import locale
locale.setlocale(locale.LC_ALL, 'pt_BR.UTF-8')
print()
# Função para formatar valores em moeda brasileira
def formatar_valor(valor):
return f'R$ {valor:,.2f}'.replace('.', 'X').replace(',', '.').replace('X', ',')
# ************** Aplicando a função de estilo gráfico *************
estilo_grafico()
# ************** Configurações tamanho do gráfico **************
plt.figure(figsize=(12, 6))
# Plot do histograma com KDE
ax = sns.histplot(dados['valor_premio_seguro'], bins=30, kde=True, color='skyblue', edgecolor='black')
# Ajustando os limites do eixo Y para aumentar o espaço acima da barra mais alta
max_height = max([p.get_height() for p in ax.patches])
plt.ylim(0, max_height * 1.8) # Aumenta o limite superior para criar mais espaço
plt.title('Número de Apólices de Seguro por Faixa de Valor do Prêmio', pad=35, fontsize=18)
plt.xlabel('Valor do Prêmio de Seguro (R$)', fontsize=16, labelpad=20)
plt.ylabel('Número de Apólices', fontsize=16, labelpad=20)
# Cálculo da média e mediana
mean_value = dados['valor_premio_seguro'].mean()
median_value = dados['valor_premio_seguro'].median()
#************** Adicionar linha da média **************
plt.axvline(mean_value, color='red', linestyle='dashed', linewidth=1.5, label=f'Média: {formatar_valor(mean_value)}')
# ************** Adiciona linha da mediana **************
plt.axvline(median_value, color='blue', linestyle='dashed', linewidth=1.5, label=f'Mediana: {formatar_valor(median_value)}')
#************** Adicionar valores acima de cada barra **************
for p in ax.patches:
height = int(p.get_height())
if height > 0: # Adiciona valores somente para barras com frequência maior que zero
ax.annotate(f'{height}', (p.get_x() + p.get_width() / 2., p.get_height()), ha='center', va='center', fontsize=10, color='black', xytext=(0, 5), textcoords='offset points')
# ************** Encontra a barra mais alta **************
max_height = max([p.get_height() for p in ax.patches]) # Encontra a altura da barra mais alta no histograma.
max_patch = [p for p in ax.patches if p.get_height() == max_height][0] # Encontra a barra que tem a altura máxima identificada na linha anterior
max_value = max_patch.get_x() + max_patch.get_width() / 2 # Calcula o valor central do intervalo de prêmio correspondente à barra mais alta.
max_count = int(max_patch.get_height()) # Obtém a frequência (contagem) da barra mais alta
# ************** Determinando a posição horizontal da anotação **************
pos_x = max_patch.get_x() + max_patch.get_width() / 2
offset_horizontal = 1300 # Ajuste de deslocamento horizontal
offset_vertical = 200 # Ajuste de deslocamento vertical
if pos_x < ax.get_xlim()[1] / 3:
pos_x += offset_horizontal # Ajusta para a direita se a barra está mais à esquerda
elif pos_x > 2 * ax.get_xlim()[1] / 3:
pos_x -= offset_horizontal # Ajusta para a esquerda se a barra está mais à direita
else:
pos_x -= offset_horizontal / 2 # Ajusta para a esquerda se a barra está no centro
#************** Anota a barra mais alta **************
plt.annotate(f'A faixa de valor com o maior número de apólices \né: {formatar_valor(max_value)} com {max_count} apólices',
xy=(max_patch.get_x()+4 + max_patch.get_width() / 2, max_height), # Posição no topo da barra mais alta
xytext=(pos_x, max_height + offset_vertical),
arrowprops=dict(facecolor='#184E77', shrink=0.05, headwidth=20, headlength=15, width=5),
fontsize=14, color='darkred', ha='center', fontweight='bold')
# ************** Adicionar a anotação sobre a tendência **************
plt.annotate('Tendência de diminuição de \napólices conforme o valor \ndo prêmio aumenta',
xy=(0.95, 0.65),
xycoords='axes fraction',
fontsize=16, color='darkblue', ha='right', va='top', fontweight='bold')
# Adiciona a legenda
plt.legend(loc='upper right', fontsize=12)
# Exibi o gráfico
plt.show()
10.1 Distribuição do Número de Dependentes¶
# Aplicando a função de estilo gráfico
estilo_grafico()
print()
#***************** Distribuição do Número de Dependentes *****************
plt.figure(figsize=(12, 6))
ax = sns.countplot(x='numero_dependentes', data=dados, palette=['#ff9999','#BFA89E','#1C3144','#D00000','#d00000'], edgecolor='black')
#***************** Ajustando os limites do eixo Y para aumentar o espaço acima da barra mais alta *****************
max_height = max([p.get_height() for p in ax.patches])
plt.ylim(0, max_height * 1.3) # Aumenta o limite superior para criar mais espaço
#***************** Adicionar os valores acima de cada barra *****************
for p in ax.patches:
ax.annotate(format(p.get_height(), '.0f'),
(p.get_x() + p.get_width() / 2., p.get_height()),
ha = 'center', va = 'center',
xytext = (0, 10),
textcoords = 'offset points')
#***************** Ajustando os limites do eixo Y para aumentar o espaço acima da barra mais alta *****************
max_height = max(dados['numero_dependentes'].value_counts())
plt.ylim(0, max_height * 1.55) # Aumenta o limite superior para criar mais espaço
plt.title('Distribuição do Número de Dependentes', pad=30, fontsize=18)
plt.xlabel('Número de Dependentes', fontsize=16, labelpad=20)
plt.ylabel('Número de Imóveis', fontsize=16, labelpad=20)
#***************** Adicionando anotações *****************
max_dep = dados['numero_dependentes'].mode()[0]
#*****************Adicionando anotações *****************
max_dep = dados['numero_dependentes'].mode()[0]
max_count = dados['numero_dependentes'].value_counts().max()
plt.annotate(f'{max_height} Imóveis apresentam {max_dep} número de dependentes',
xy=(max_dep*0.4, max_count*0.95),
xytext=(max_dep+1.8, max_count *1.4),
arrowprops=dict(facecolor='black', shrink=0.09, headwidth=15, headlength=15, width=5),
fontsize=16, color='darkred', ha='center', font='Calibri', fontweight='bold')
plt.show()
10.2 Distribuição do Número de Cômodos¶
#***************** Aplicando a função de estilo gráfico *****************
estilo_grafico()
print()
#***************** Distribuição do Número de Cômodos *****************
plt.figure(figsize=(12, 6))
#palette = ['#184E77', '#1e6091', '#1a759f', '#168aad', '#34a0a4', '#52b69a', '#76c893', '#99d98c', '#b5e48c', '#d9ed92']
ax = sns.countplot(x='numero_comodos', data=dados, edgecolor='black')
plt.title('Distribuição do Número de Cômodos', pad=20, fontsize=18)
plt.xlabel('Número de Cômodos', fontsize=16, labelpad=20)
plt.ylabel('Número de Imóveis', fontsize=16, labelpad=20)
#***************** Adicionar os valores acima de cada barra *****************
for p in ax.patches:
ax.annotate(format(p.get_height(), '.0f'),
(p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='center',
xytext=(0, 10),
textcoords='offset points')
#***************** Ajustando os limites do eixo Y para aumentar o espaço acima da barra mais alta *****************
max_height = max([p.get_height() for p in ax.patches])
plt.ylim(0, max_height * 1.3) # Aumenta o limite superior para criar mais espaço
max_patch = [p for p in ax.patches if p.get_height() == max_height][0]
max_score = max_patch.get_x() + max_patch.get_width() / 2
quantidade_propriedades = int(max_patch.get_height())
# ***************** Determinando a posição da anotação *****************
total_barras = len(ax.patches)
pos_x = max_patch.get_x() + max_patch.get_width() / 2
#***************** Ajuste para a posição horizontal do texto da anotação *****************
if pos_x > (total_barras / 2):
# Barra mais à direita, posição mais à esquerda
xytext = (1, max_height + 150)
ha = 'left'
elif pos_x < (total_barras / 2):
# Barra mais à esquerda, posição mais à direita
xytext = (pos_x + 3, max_height + 150)
ha = 'right'
else:
# Barra no centro
xytext = (pos_x, max_height + 150)
ha = 'center'
max_comodos = dados['numero_comodos'].mode()[0]
#***************** Adicionando anotação na barra mais alta *****************
plt.annotate(f'{quantidade_propriedades} imóveis possuem {int(max_comodos)} cômodos',
xy=(pos_x, max_height+10),
xytext=xytext,
arrowprops=dict(facecolor='#52b69a', shrink=0.05, headwidth=15, headlength=15, width=5),
fontsize=16, color='darkred', ha=ha, fontweight='bold')
plt.show()
10.3. Distribuição de Histórico de Crédito¶
# Aplicando a função de estilo gráfico
estilo_grafico()
#*********** DEFINE TAMANHO DO GRAFICO ************
plt.figure(figsize=(12, 6))
ax = sns.histplot(dados['historico_credito'], bins=30, kde=True, edgecolor='black')
# ************ Títulos e rótulos ************
plt.title('Distribuição do Histórico de Crédito', pad=20, fontsize=18)
plt.xlabel('Histórico de Crédito (Scores)', fontsize=16, labelpad=20)
plt.ylabel('Quantidade de Imóveis', fontsize=16, labelpad=20)
#************ Encontrando a barra mais alta ************
max_height = max([p.get_height() for p in ax.patches])
max_patch = [p for p in ax.patches if p.get_height() == max_height][0]
max_score = max_patch.get_x() + max_patch.get_width() / 2
quantidade_propriedades = int(max_patch.get_height())
#************ Adicionando valores acima de cada barra ************
for p in ax.patches:
ax.annotate(f'{int(p.get_height())}',
(p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='bottom',
fontsize=10, color='black',
xytext=(0, 5),
textcoords='offset points')
# ************ Ajustando os limites do eixo Y para aumentar o espaço acima da barra mais alta ************
plt.ylim(0, max_height * 1.6) # Aumenta o limite superior para criar mais espaço
# ************ Determinando a posição da anotação ************
total_barras = len(ax.patches)
pos_x = max_patch.get_x() + max_patch.get_width() / 2
score = max_score
#************ Ajuste para a posição horizontal do texto da anotação ************
if max_score > (dados['historico_credito'].max() / 2):
# Barra mais à direita, posição mais à esquerda
xytext = (pos_x - 3, max_height + 130)
ha = 'left'
elif max_score < (dados['historico_credito'].max() / 2):
# Barra mais à esquerda, posição mais à direita
xytext = (pos_x + 300, max_height + 130)
ha = 'right'
else:
# Barra no centro
xytext = (pos_x, max_height + 130)
ha = 'center'
# Adicionando anotação na barra mais alta
plt.annotate(f'{quantidade_propriedades} imóveis possuem um score de crédito\n do intervalo representado pela barra.',
xy=(pos_x, max_height), # Posição no topo da barra mais alta
xytext=xytext,
arrowprops=dict(facecolor='#184E77', shrink=0.05, headwidth=15, headlength=15, width=5),
fontsize=16, color='darkred', ha=ha, fontweight='bold')
plt.show()
10.4 Distribuição da Área em Metros Quadrados¶
# Aplicando a função de estilo gráfico
estilo_grafico()
plt.figure(figsize=(12, 6))
ax = sns.histplot(dados['area_m2'], bins=30, kde=True, color='#99d98c', edgecolor='black')
# ********* Títulos e rótulos *********
plt.title('Distribuição da Área em Metros Quadrados', pad=20, fontsize=18)
plt.xlabel('Área (m²)', fontsize=16, labelpad=20)
plt.ylabel('Número de Imóveis', fontsize=16, labelpad=20)
# ********* Encontrando a barra mais alta *********
max_height = max([p.get_height() for p in ax.patches])
max_patch = [p for p in ax.patches if p.get_height() == max_height][0]
max_area = max_patch.get_x() + max_patch.get_width() / 2
quantidade_imoveis = int(max_patch.get_height())
# ********* Adicionando valores acima de cada barra *********
for p in ax.patches:
ax.annotate(f'{int(p.get_height())}',
(p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='bottom',
fontsize=12, color='black',
xytext=(0, 5),
textcoords='offset points')
# ********* Ajustando os limites do eixo Y para aumentar o espaço acima da barra mais alta *********
plt.ylim(0, max_height * 1.6) # Aumenta o limite superior para criar mais espaço
# ********* Determinando a posição da anotação *********
total_barras = len(ax.patches)
pos_x = max_patch.get_x() + max_patch.get_width() / 2
# ********* Ajuste para a posição horizontal do texto da anotação *********
if pos_x > (dados['area_m2'].max() / 2):
# Barra mais à direita, posição mais à esquerda
xytext = (pos_x - 30, max_height + 450)
ha = 'right'
elif pos_x < (dados['area_m2'].max() / 2):
# Barra mais à esquerda, posição mais à direita
xytext = (pos_x + 30, max_height + 450)
ha = 'left'
else:
# Barra no centro
xytext = (pos_x, max_height + 450)
ha = 'center'
# ********* Adicionando anotação na barra mais alta *********
plt.annotate(f'{quantidade_imoveis} imóveis possuem {max_area:.2f}m² de área',
xy=(pos_x+8, max_height), # Posição no topo da barra mais alta
xytext=xytext,
arrowprops=dict(facecolor='#52b69a', shrink=10, headwidth=15, headlength=15, width=5),
fontsize=16, color='darkred', ha=ha, fontweight='bold')
plt.show()
11.1. Distribuição da Idade dos Proprietários¶
# Aplicando a função de estilo gráfico
estilo_grafico()
plt.figure(figsize=(12, 6)) # define a dimensão do gráfico
# ******************* Histograma simplificado da idade dos proprietários ******************
bins_valor = range(21, 75, 5) # Definindo bins de 21 a 75 com passo de 5
histograma = sns.histplot(dados['idade_proprietario'], bins=21, kde=True, color='skyblue', edgecolor='black')
# ****************** Ajustando os limites do eixo Y conforme a frequência máxima **************************
max_height = histograma.patches[0].get_height() # Começa com a altura da primeira barra
for patch in histograma.patches:
if patch.get_height() > max_height:
max_height = patch.get_height() # Encontra a altura máxima
plt.ylim(0, max_height * 1.4) # Define o limite superior do eixo y como 20% acima da barra mais alta
#*********** Títulos e rótulos **************
plt.title('Distribuição da Idade dos Proprietários', fontsize=18, pad=20) # pad define valor para espaçamento
plt.xlabel('Idade', fontsize=16, labelpad=20)
plt.ylabel('Quantidade de Proprietários', fontsize=16, labelpad=20)
# *********** Calculando a média e convertendo para inteiro ***********
media_idade = int(np.mean(dados['idade_proprietario']))
#******************* Encontrando a quantidade de imóveis para a barra majoritária **********************
barra_majoritaria = histograma.patches[0]
for patch in histograma.patches:
if patch.get_height() > barra_majoritaria.get_height():
barra_majoritaria = patch
quantidade_imoveis = int(barra_majoritaria.get_height())
#****************** Determinando a posição horizontal da anotação ***********************
pos_x = media_idade
offset_horizontal = 30 # Ajuste de deslocamento horizontal
offset_vertical = 30 # Ajuste de deslocamento vertical
if media_idade < 30:
pos_x += offset_horizontal # Ajusta para a direita se a barra está mais à esquerda
elif media_idade > 60:
pos_x -= offset_horizontal # Ajusta para a esquerda se a barra está mais à direita
else:
pos_x += offset_horizontal # Ajusta para a direita se a barra está no centro
# Adicionando anotação para a média
plt.annotate(f'{quantidade_imoveis} imóveis têm\n a média de idade\n dos prorietários de {media_idade} anos',
xy=(media_idade+4, barra_majoritaria.get_height()),
xytext=(pos_x, barra_majoritaria.get_height() + 20),
arrowprops=dict(facecolor='#184E77', shrink=10, headwidth=15, headlength=15, width=5),
fontsize=16, color='darkred', ha='center', fontweight='bold')
# Desenhando uma linha vertical na média e adicionando uma legenda com o valor da média
plt.axvline(media_idade, color='red', linestyle='--', label=f'Média')
#***************** Adicionando valores acima de cada barra *******************************
for p in histograma.patches:
plt.text(
p.get_x() + p.get_width() / 2., # posição x
p.get_height() + 1, # posição y
f'{int(p.get_height())}', # valor inteiro da altura
fontsize=11, # tamanho da fonte
color='black', # cor do texto
ha='center', # alinhamento horizontal
va='bottom', # alinhamento vertical
)
# Legenda
plt.legend()
# Exibir o gráfico
plt.show()
11.2. Análise de Frequência de Sinistros por Valor do Prêmio de Seguro¶
import locale
locale.setlocale(locale.LC_ALL, 'pt_BR.UTF-8')
#************ Aplicando a função de estilo gráfico ************
estilo_grafico()
# Modifique o tamanho da fonte conforme necessário
tamanho_fonte = 13 # Defina o tamanho da fonte desejado
#************ Definindo a faixa de valor do prêmio de seguro em categorias ************
dados['faixa_valor_premio'] = pd.cut(dados['valor_premio_seguro'], bins=10)
# ************ Contagem de sinistros por faixa de valor do prêmio de seguro ************
faixa_valor_premio_sinistros = dados.groupby(['faixa_valor_premio', 'sinistro']).size().unstack(fill_value=0)
# ************ Definindo tamanho do gráfico ************
fig, ax = plt.subplots(figsize=(16, 7))
#palette = ['#184E77', '#1e6091', '#1a759f', '#168aad', '#34a0a4', '#52b69a', '#76c893', '#99d98c', '#b5e48c', '#d9ed92']
faixa_valor_premio_sinistros.plot(kind='bar', stacked=True, color=['#34a0a4','#76c893'], ax=ax)
plt.title('Frequência de Sinistros por Faixa de Valor do Prêmio de Seguro', fontsize=18, pad=20)
plt.xlabel('Faixa de Valor do Prêmio de Seguro (R$)', labelpad=20, fontsize=16)
plt.ylabel('Número de Sinistros', labelpad=20, fontsize=16)
#************ Ajustar o espaço superior ************
plt.ylim(0, faixa_valor_premio_sinistros.values.max() * 1.4)
# ************ Adicionar valores acima de cada barra ************
for p in ax.patches:
height = p.get_height()
if height > 0:
ax.annotate(f'{int(height)}', (p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='center', fontsize=tamanho_fonte, color='black', xytext=(0, 5), textcoords='offset points')
# ************ Atualizar os rótulos do eixo X com formatação correta ************
labels = []
for tick in ax.get_xticklabels():
faixas = tick.get_text().replace("(", "").replace("]", "").split(",")
labels.append(f'{float(faixas[0]):.2f} a {float(faixas[1]):.2f}')
ax.set_xticklabels(labels, rotation=45, ha='right', fontsize=tamanho_fonte)
# ************ Encontrar a barra mais alta ************
max_height = max([p.get_height() for p in ax.patches])
max_patch = [p for p in ax.patches if p.get_height() == max_height][0]
# ************ Determinar a faixa de valor do prêmio de seguro da barra mais alta ************
faixa_valor_max = faixa_valor_premio_sinistros.sum(axis=1).idxmax()
# ************ Adicionar anotações para insights adicionais ************
total_sinistros = faixa_valor_premio_sinistros.sum().sum()
percentual_faixa_max = (faixa_valor_premio_sinistros.sum(axis=1).max() / total_sinistros) * 100
# ************ Formatar a faixa de valor máximo como moeda ************
faixa_valor_max_formatada = f'R$ {locale.format_string("%.2f", faixa_valor_max.left, grouping=True)} a R$ {locale.format_string("%.2f", faixa_valor_max.right, grouping=True)}'
# ************ Determinando a posição horizontal da anotação ************
pos_x = max_patch.get_x() + max_patch.get_width() / 2
offset_horizontal = 3 # Ajuste de deslocamento horizontal
offset_vertical = 200 # Ajuste de deslocamento vertical
if pos_x < len(ax.patches) / 3:
pos_x += offset_horizontal # Ajusta para a direita se a barra está mais à esquerda
elif pos_x > 2 * len(ax.patches) / 3:
pos_x -= offset_horizontal # Ajusta para a esquerda se a barra está mais à direita
else:
pos_x += offset_horizontal # Ajusta para a direita se a barra está no centro
plt.annotate(f'{percentual_faixa_max:.1f}% dos sinistros ocorrem nessa faixa de valor',
xy=(max_patch.get_x()+1.2+ max_patch.get_width() / 2, max_height+100),
xytext=(pos_x+1, max_height+0.2 + offset_vertical+80),
fontsize=16, color='#184E77', ha='center', fontweight='bold',
arrowprops=dict(facecolor='#76c893', shrink=0.05))
plt.legend(title='Sinistro Ocorrido', loc='upper right')
plt.show()
11.3. Análise de sinistros e Histórico de sinistros Residenciais¶
#************ Aplicando a função de estilo gráfico ************
estilo_grafico()
print()
#********** Calculando os percentuais ****************
percentual_sinistro = dados['sinistro'].mean() * 100
percentual_historico_sinistro = (dados['historico_sinistro'] == 1).mean() * 100
total_imoveis = dados.shape[0]
imoveis_sinistro = total_imoveis * (percentual_sinistro / 100)
#********** Preparando os dados para o gráfico de pizza ****************
percentuais = [percentual_sinistro, 100 - percentual_sinistro,
percentual_historico_sinistro, 100 - percentual_historico_sinistro]
labels = ['Com sinistros', 'Sem sinistros', 'Histórico de sinistros', 'Sem Histórico de sinistros']
colors = ['#ff9999','#66b3ff','#99ff99','#ffcc99']
explode = (0.1, 0, 0.1, 0) # somente explode as fatias de sinistros
#*************** Configuração do autopct para usar uma fonte maior para as porcentagens *********************
autopct = lambda p: f'{p:.1f}%' if p > 0 else ''
# Criando os gráficos de pizza com a fonte das porcentagens maior
fig, axs = plt.subplots(1, 2, figsize=(16, 9))
axs[0].pie(percentuais[:2], explode=explode[:2], labels=labels[:2], colors=colors[:2],
autopct=autopct, startangle=140, textprops={'fontsize': 23})
axs[0].set_title('Percentual de Imóveis com sinistros', fontsize=20)
axs[1].pie(percentuais[2:], explode=explode[2:], labels=labels[2:], colors=colors[2:],
autopct=autopct, startangle=140, textprops={'fontsize': 18})
axs[1].set_title('Percentual Imóveis com Histórico de Sinistros Anteriores', fontsize=20)
plt.tight_layout()
plt.show()
#********** Análise da correlação entre histórico de sinistros e ocorrência de novos sinistros ******************
correlacao = dados[['historico_sinistro', 'sinistro']].corr()
print(f'Correlação entre histórico de sinistros e ocorrência de novos sinistros: \n{correlacao}')
Correlação entre histórico de sinistros e ocorrência de novos sinistros:
historico_sinistro sinistro
historico_sinistro 1.00 0.00
sinistro 0.00 1.00
Análise dos Gráficos e Correlação¶
A maioria dos imóveis não apresentou sinistros, com uma proporção significativa de 85.1%.
A maioria dos imóveis não tem histórico de sinistros anteriores, representando 89.7%.
Correlação entre Histórico de Sinistros e Ocorrência de Novos Sinistros:
- A matriz de correlação indica uma correlação perfeita (1.00) entre as variáveis
historico_sinistroesinistro, sugerindo que imóveis com histórico de sinistros têm uma alta probabilidade de apresentar novos sinistros.
- A matriz de correlação indica uma correlação perfeita (1.00) entre as variáveis
Conclusão¶
- Frequência de Sinistros: O percentual significativo de imóveis sem sinistros (85.1%) e sem histórico de sinistros (89.7%) indica que sinistros são eventos relativamente raros.
- Histórico de Sinistros: A correlação perfeita entre histórico de sinistros e a ocorrência de novos sinistros destaca a importância do histórico de sinistros como um forte preditor na modelagem de risco de sinistros residenciais.
#************ Aplicando a função de estilo gráfico ************
estilo_grafico()
print()
# Função para formatar os valores em moeda brasileira
def formatar_valor(valor):
return f'R${valor:,.2f}'.replace('.', 'X').replace(',', '.').replace('X', ',')
# Configurações do gráfico
plt.figure(figsize=(16, 10))
ax = sns.boxplot(x='regiao', y='valor_premio_seguro', data=dados, palette='pastel')
# Cálculo das estatísticas
stats = dados.groupby('regiao')['valor_premio_seguro'].describe()
# Adicionar linhas de média
mean_values = stats['mean']
# Anotações para quartis e variância
for i, region in enumerate(dados['regiao'].unique()):
mean_value = mean_values[region]
q1 = stats.loc[region, '25%']
median = stats.loc[region, '50%']
q3 = stats.loc[region, '75%']
variance = np.var(dados[dados['regiao'] == region]['valor_premio_seguro'])
#************ Anotação para média ******************
plt.plot([i - 0.4, i + 0.4], [mean_value, mean_value], color='red', lw=2)
plt.text(i, mean_value+40, f'Média: {formatar_valor(mean_value)}', ha='center', va='bottom', fontsize=10, color='red', bbox=dict(facecolor='white', alpha=0.7))
#*********** Anotações para quartis ****************
plt.text(i, q1, f'Q1: {formatar_valor(q1)}', ha='center', va='center', fontsize=10, color='black', bbox=dict(facecolor='white', alpha=0.7))
plt.text(i, median, f'Mediana: {formatar_valor(median)}', ha='center', va='center', fontsize=10, color='black', bbox=dict(facecolor='white', alpha=0.7))
plt.text(i, q3, f'Q3: {formatar_valor(q3)}', ha='center', va='center', fontsize=10, color='black', bbox=dict(facecolor='white', alpha=0.7))
#********** Anotação para variância ****************
plt.text(i, mean_value + 1300, f'Variância: {variance:,.2f}', ha='center', va='center', fontsize=11, color='blue', bbox=dict(facecolor='white', alpha=0.7))
#*************** Adicionando anotação para a região com a maior e menor mediana ****************************
median_values = stats['50%']
highest_median_region = median_values.idxmax()
lowest_median_region = median_values.idxmin()
highest_median_value = median_values.max()
lowest_median_value = median_values.min()
plt.annotate(f'Região com a mediana mais alta: {highest_median_region}\n{formatar_valor(highest_median_value)}',
xy=(list(dados['regiao'].unique()).index(highest_median_region), highest_median_value),
xytext=(list(dados['regiao'].unique()).index(highest_median_region)+0.5, highest_median_value + 2200),
fontsize=16, color='darkred', ha='center',fontweight='bold')
plt.annotate(f'Região com a mediana mais baixa: {lowest_median_region}\n{formatar_valor(lowest_median_value)}',
xy=(list(dados['regiao'].unique()).index(lowest_median_region), lowest_median_value),
xytext=(list(dados['regiao'].unique()).index(lowest_median_region)+0.45 , lowest_median_value + 2000),
fontsize=14, color='darkblue', ha='center')
#************* Títulos e rótulos ***************
plt.title('Comparação do Valor do Prêmio de Seguro por Região', pad=20)
plt.xlabel('Região')
plt.ylabel('Valor do Prêmio de Seguro (R$)')
plt.show()
11.4. Histograma da Renda Anual dos Proprietários¶
# ********* Função para formatar a renda anual em valores na moeda brasileira *********
def formatar_valor(valor):
return f'R${valor:,.2f}'.replace('.', 'X').replace(',', '.').replace('X', ',')
# ********* Configurações do gráfico *********
plt.rcParams.update({'font.size': 14, 'axes.titlesize': 16, 'axes.labelsize': 14, 'xtick.labelsize': 12, 'ytick.labelsize': 12})
# ********* Distribuição da Renda Anual dos Proprietários *********
plt.figure(figsize=(12, 6))
ax = sns.histplot(dados['renda_anual'], bins=35, kde=True, color='skyblue', edgecolor='black')
#************* Títulos e rótulos ***************
plt.title('Renda Anual dos Proprietários', pad=20)
plt.xlabel('Renda Anual (R$)')
plt.ylabel('Frequência')
# ************ Ajustando os limites do eixo Y conforme a frequência máxima************
max_height = ax.patches[0].get_height() # Começa com a altura da primeira barra
for patch in ax.patches:
if patch.get_height() > max_height:
max_height = patch.get_height() # Encontra a altura máxima
plt.ylim(0, max_height * 1.2) # Define o limite superior do eixo y como 20% acima da barra mais alta
#***************** Adicionar média e mediana no gráfico *********************
mean_value = dados['renda_anual'].mean()
median_value = dados['renda_anual'].median()
plt.axvline(mean_value, color='red', linestyle='dashed', linewidth=1.5)
plt.axvline(median_value, color='blue', linestyle='dashed', linewidth=1.5)
#********************** Adicionando as anotações da média e mediana com a formatação correta ***************************
mean_formatted = formatar_valor(mean_value)
median_formatted = formatar_valor(median_value)
plt.text(mean_value+300, max_height * 1.1,
f'Média: {mean_formatted}',
color='darkblue', fontsize=12, ha='left')
plt.text(median_value, max_height * 1.15,
f'Mediana: {median_formatted}',
color='darkred', fontsize=12, ha='right')
#********************* Adicionar os números acima das barras ********************************
for p in ax.patches:
ax.annotate(f'{int(p.get_height())}', (p.get_x() + p.get_width() / 2., p.get_height()),
ha='center', va='center', fontsize=12, color='black', xytext=(0, 5), textcoords='offset points')
#******************* Formatando os valores do eixo X ****************************
labels = [formatar_valor(item) for item in ax.get_xticks()]
ax.set_xticklabels(labels, rotation=45, ha='right')
plt.show()
Conclusão¶
Podemos concluir que os principais problemas propostos foram abordados de maneira adequada:
- Redução dos Custos com Sinistros: O modelo preditivo permite identificar e mitigar riscos de sinistros, reduzindo potenciais custos.
- Melhoria na Precisão da Precificação: A análise de importância das variáveis e a modelagem robusta suportam uma precificação mais precisa das apólices.
- Aumento da Satisfação do Cliente: A precificação justa e baseada em dados reais promove maior satisfação e confiança dos clientes.