Análise de Risco de Sinistros Residenciais

Luciano Magalhães Luciano Magalhães   |  schedule Junho, 2024  |   view_object_track Machine Learning

1. Descrição Geral do Problema¶

Imagem Análise de Risto de Sinistros Residenciais

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¶

ElementoDescrição
Problema de NegócioSinistros 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/PredictionConstruir 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 AcquisitionConjunto de dados de propriedades residenciais e seus históricos de sinistros.
Data PreparationLimpeza 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 DadosAnálise exploratória inicial para entender as características e distribuição das variáveis, identificando padrões e correlações relevantes.
ModelingTreinamento 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 EvaluationAvaliaçã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.
PredictionTeste do modelo com dados de exemplo ajustados para verificar a capacidade de detecção de sinistros.
Conclusões e RecomendaçõesResultados 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.

In [1]:
# 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¶

In [2]:
# 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¶

In [ ]:
# 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¶

In [4]:
#*********** Exibir as primeiras linhas e informações dos dados ***********
display(dados.head())

display(dados.info())
cidade_residenciaidade_proprietariovalor_imoveltipo_construcaohistorico_sinistrovalor_premio_segurosinistroregiaorenda_anualhistorico_credito...valor_seguronumero_comodosidade_imovelmaterial_construcaosegurancaperfil_segrisco_climaticohistorico_reclamacoestamanho_imovelarea_m2
0São Gonçalo71387671.21Condomínio Fechado03752.740Sudeste77826.00782...1938.00354023681.90305102.0475.98
1Caruaru55333997.09Studio01055.280Nordeste51997.00732...1670.00575115908.402010416.6732.06
2Manaus62375704.65Sobrado02567.330Norte65169.00541...1879.0010176119785.70105494.5168.38
3Maringá77475643.69Sobrado05345.851Sul78940.00576...2378.006346123935.50405494.5186.57
4Contagem72199999.49Cobertura03269.240Sudeste62200.00487...1000.005227118876.40305555.5636.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ávelDescriçãoTipo de Dados
cidade_residenciaNome da cidade do imóvel seguradoobject
idade_proprietarioIdade do proprietário do imóvelint64
valor_imovelValor estimado do imóvelfloat64
tipo_construcaoTipo de construção do imóvel (casa, apartamento, etc.)object
historico_sinistroIndica se houve sinistros anteriores na propriedade (1 para sim, 0 para não)int64
valor_premio_seguroValor pago pelo segurado para obter a cobertura do segurofloat64
sinistroIndica se ocorreu um sinistro (0 para Não, 1 para Sim)int64
regiaoInforma qual das regiões presentes no Brasil está o imóvelobject
renda_anualRenda anual do proprietáriofloat64
historico_creditoHistórico de crédito do proprietárioint64
numero_dependentesNúmero de dependentes do proprietárioint64
ano_construcaoAno de construção do imóvelint64
distancia_bombeirosDistância até a estação de bombeiros mais próximafloat64
sistema_alarmeIndica se o imóvel possui sistema de alarme (1 para sim, 0 para não)int64
cameras_segurancaIndica se o imóvel possui câmeras de segurança (1 para sim, 0 para não)int64
estadoEstado onde o imóvel está localizadoobject
valor_seguroValor do seguro contratadofloat64
numero_comodosNúmero de cômodos do imóvelint64
idade_imovelIdade do imóvel calculada a partir do ano de construçãoint64
material_construcaoTipo de material utilizado na construçãoint64
segurancaÍndice de segurança do imóvel (sistema de segurança, etc.)int64
perfil_segPerfil do segurado calculado com base em várias característicasfloat64
risco_climaticoÍndice de risco climático da regiãoint64
historico_reclamacoesHistórico de reclamações do proprietárioint64
tamanho_imovelTamanho do imóvel em metros quadradosfloat64
area_m2Área do imóvel em metros quadradosfloat64

6.3. Análise de Dados Faltantes¶

In [5]:
# *********** 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¶

In [6]:
#*********** 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¶

In [7]:
#********** 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¶

In [9]:
#******************* 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.¶

In [10]:
# *******************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
Out[10]:
valor_unicos
historico_reclamacoes2
historico_sinistro2
sinistro2
cameras_seguranca2
sistema_alarme2
seguranca3
numero_dependentes4
risco_climatico5
regiao5
material_construcao6
tipo_construcao9
numero_comodos10
estado11
cidade_residencia32
idade_imovel42
ano_construcao42
idade_proprietario68
tamanho_imovel114
historico_credito550
valor_seguro1970
renda_anual7423
perfil_seg7806
valor_premio_seguro7907
area_m27999
valor_imovel7999
distancia_bombeiros8000

In [11]:
# ************ 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()
No description has been provided for this image


7. Pré-processamento dos Dados¶

7.1 Definição das variáveis categóricas e numéricas¶

In [12]:
#***************** 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¶

In [13]:
#*********** 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¶

In [14]:
#**************** 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. Desenvolvimento do Modelo¶

8.1 Pipeline com classificador¶

In [15]:
#**************** Pipeline com classificador *********************

pipeline = Pipeline(steps=[('classifier', RandomForestClassifier(random_state=42))])

8.2 Busca em grade com validação cruzada¶

In [16]:
#***************  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)
Out[16]:
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¶

In [17]:
#*****************  Melhor modelo *************************

best_model = grid_search.best_estimator_


9. Avaliação do Modelo¶

9.1 Classificação Relatório e Métricas¶

In [18]:
#***************** 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étricaValorDescrição
Accuracy0.95O modelo classificou corretamente 95% dos exemplos.
Precision (0)0.9292% das predições para a classe 0 (sem sinistro) foram corretas.
Recall (0)1.00O modelo identificou todos os exemplos da classe 0 (sem sinistro).
F1-Score (0)0.95Equilíbrio entre precisão e recall para a classe 0 (sem sinistro).
Precision (1)1.00100% das predições para a classe 1 (com sinistro) foram corretas.
Recall (1)0.91O modelo identificou 91% dos exemplos da classe 1 (com sinistro).
F1-Score (1)0.95Equilíbrio entre precisão e recall para a classe 1 (com sinistro).
Macro Avg0.96Média não ponderada do F1-Score de cada classe.
Weighted Avg0.96Média ponderada do F1-Score, ajustando-se ao desequilíbrio entre as classes.
  1. 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.

  2. 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
In [19]:
# 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()
No description has been provided for this image

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 SinistroClasse Predita: Com Sinistro
Classe Real: Sem Sinistro20364
Classe Real: Com Sinistro1841861

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¶

In [20]:
#****************** 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ávelImportância
valor_premio_seguro0.26
numero_dependentes0.05
numero_comodos0.04
area_m20.02
historico_credito0.02
cidade_residencia_Belém0.00
estado_Pará0.00
estado_Goiás0.00
regiao_Centro-Oeste0.00
cidade_residencia_Goiânia0.00
  • A variável valor_premio_seguro foi 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, e historico_credito também tiveram alguma importância, embora significativamente menor que valor_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¶

In [21]:
# 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()
No description has been provided for this image

10.1 Distribuição do Número de Dependentes¶

In [22]:
# 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()
No description has been provided for this image

10.2 Distribuição do Número de Cômodos¶

In [23]:
#***************** 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()
No description has been provided for this image

10.3. Distribuição de Histórico de Crédito¶

In [24]:
# 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()
No description has been provided for this image

10.4 Distribuição da Área em Metros Quadrados¶

In [25]:
# 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()
No description has been provided for this image


11. Outros Gráficos¶

11.1. Distribuição da Idade dos Proprietários¶

In [26]:
# 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()
No description has been provided for this image

11.2. Análise de Frequência de Sinistros por Valor do Prêmio de Seguro¶

In [27]:
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()
No description has been provided for this image

11.3. Análise de sinistros e Histórico de sinistros Residenciais¶

In [28]:
#************ 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}')
No description has been provided for this image
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¶

  1. A maioria dos imóveis não apresentou sinistros, com uma proporção significativa de 85.1%.

  2. A maioria dos imóveis não tem histórico de sinistros anteriores, representando 89.7%.

  3. 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_sinistro e sinistro, sugerindo que imóveis com histórico de sinistros têm uma alta probabilidade de apresentar novos sinistros.

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.
In [29]:
#************ 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()
No description has been provided for this image

11.4. Histograma da Renda Anual dos Proprietários¶

In [30]:
# *********  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()

No description has been provided for this image

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.

© Copyright 2024 | Luciano Magalhães