# fig_plots
import folium
import numpy as np
import pandas as pd
import plotly.colors
import plotly.graph_objects as go
from dash import html
from scipy.stats import pearsonr
from scipy.stats import t
def get_paleta(x,cor_unica=None):
paletas = {
'area_km2': 'Blues',
'Hab': 'YlOrRd',
'Mor': 'YlOrRd',
'Mor/Hab': 'YlOrRd',
'N_ren': 'Reds',
'NS': 'Reds',
'ren_avg': 'RdYlGn',
'ren_mdn': 'RdYlGn',
'T.A.': 'RdBu',
'IDH-R': 'RdBu',
'IDH-L': 'RdBu',
'IDH-E': 'RdBu',
'IDH': 'RdBu',
'QTI': 'RdYlGn',
'CRA': 'Blues',
'PPR': 'Reds',
'nd_med': 'RdYlGn',
'DIEs': 'RdYlGn_r',
'N_setores': 'Blues'}
# Obtém a paleta ou usa padrão
paleta_name = paletas.get(x, 'Greys')
if cor_unica is not None:
scale_cor={'area_km2':0.50,
'Hab':0.4,
'ren_avg':0.2,
'ren_mdn':0.15,
'Mor/Hab':0.75,
'T.A.':0.80,
'IDH-R': 0.75,
'IDH-L':0.75,
'IDH':0.75,
'CRA': 0.50,
'IDH-E':0.75,
'nd_med':0.80,
'N_setores':0.5}
idx=scale_cor.get(x,0.25)
paleta_name = plotly.colors.sample_colorscale(paleta_name, [idx])[0]
return paleta_name
def kpis_out(df,x):
serie = df[x].dropna()
# Estilo compacto
card_style = {
'background': 'white',
'padding': '8px 5px', # Menor padding
'borderRadius': '6px',
'textAlign': 'center',
'minHeight': '80px', # Altura controlada
}
kpis = [
html.Div([
html.H4(f'{serie.mean():.2f}', style={'margin': '5px 0', 'fontSize': '26px'}),
html.P('Média', style={'margin': '0', 'fontSize': '18px'})
], style=card_style),
html.Div([
html.H4(f'{serie.median():.2f}', style={'margin': '5px 0', 'fontSize': '26px'}),
html.P('Mediana', style={'margin': '0', 'fontSize': '18px'})
], style=card_style),
html.Div([
html.H4(f'{serie.max():.2f}', style={'margin': '5px 0', 'fontSize': '26px'}),
html.P('Máximo', style={'margin': '0', 'fontSize': '18px'})
], style=card_style),
html.Div([
html.H4(f'{serie.min():.2f}', style={'margin': '5px 0', 'fontSize': '26px'}),
html.P('Mínimo', style={'margin': '0', 'fontSize': '18px'})
], style=card_style)
]
return kpis
def add_pontos_descartes(map,gdf_d):
ponto_colors = {
"Estimados": "gray",
"Dados": "back"}
layer_pontos = folium.FeatureGroup(name="Pontos (⚪Estimados/⚫ coletados)", show=False)
if gdf_d is not None and gdf_d.empty:
print("AVISO: GeoDataFrame de pontos está vazio!")
for _, row in gdf_d.iterrows():
try:
bairro = row["Bairro"]
lat = row["lat"]
lon = row["lon"]
cor = row["Cor"]
# Verificar coordenadas válidas
if pd.isna(lat) or pd.isna(lon):
print(f"AVISO: Coordenadas inválidas para bairro {bairro}")
continue
marker_color = ponto_colors.get(cor, "black")
popup_html = f"""
Bairro: {bairro}
Tipo: {cor}
Lat: {lat:.5f}
Lon: {lon:.5f}
"""
# Adicionar ao layer_pontos
folium.CircleMarker(
location=[lat, lon],
radius=3,
color=marker_color,
fill=True,
fill_color=marker_color,
fill_opacity=1,
popup=popup_html,
tooltip=f"{bairro} — {cor}"
).add_to(layer_pontos)
except Exception as e:
print(f"Erro ao processar ponto: {e}")
continue
layer_pontos.add_to(map) # Adicionar layer_pontos ao mapa
folium.LayerControl().add_to(map) # Adicionar controle de camadas
return map
MAP_CACHE = {} # cache LOCAL do módulo
def mapa_folium(gdf, x,gdf_d=None):
if x in MAP_CACHE:
return MAP_CACHE[x]
paleta_name = get_paleta(x)
# Criar mapa
map = folium.Map(location=[-1.43, -48.42], zoom_start=12, tiles='CartoDB Positron')
# Choropleth
choropleth = folium.Choropleth(
geo_data=gdf,
data=gdf,
columns=['Bairro', x],
key_on='feature.properties.Bairro',
fill_color=paleta_name,
fill_opacity=0.7,
line_opacity=0.2,
legend_name=x
).add_to(map)
# Tooltip
tooltip = folium.features.GeoJsonTooltip(
fields=['Bairro', x],
aliases=['Bairro', x],
style="font-size: 12px;"
)
choropleth.geojson.add_child(tooltip)
if gdf_d is not None:
map=add_pontos_descartes(map,gdf_d)
MAP_CACHE[x] = map
return map
else:
MAP_CACHE[x] = map
return map
def toptop_bairro(df,x):
paleta_name=get_paleta(x)
rank=15
top = df.nlargest(rank, x).sort_values(x, ascending=True)
# Define limites da escala de cores
cmin_val = df[x].min() # ou df[x].min() para escala global
cmax_val = df[x].max() # ou df[x].max() para escala global
fig = go.Figure()
fig.add_trace(
go.Bar(
y=top['Bairro'],
x=top[x],
orientation='h',
texttemplate='%{x:.2f}',
marker=dict(
color=top[x], # Valores para o gradiente
colorscale=paleta_name,
cmin=cmin_val,
cmax=cmax_val,
opacity=0.7,
line=dict(width=1, color='white')),
hovertemplate=f'%{{y}}
{x}: %{{x}}',
name=x
)
)
fig.update_layout(title='Bairros com maiores valores')
return fig
def histplot_bairro_px(df,x):
cor_hist=get_paleta(x,cor_unica=1)
nb=10
fig = go.Figure()
fig.add_trace(
go.Histogram(
x=df[x],
nbinsx=nb,
histnorm='percent',
texttemplate='%{y:.1f}%',
textposition='inside',
marker=dict(color=cor_hist,
line=dict(width=1, color='white')) ),
)
fig.update_layout(xaxis_title=x,yaxis_title='percentual(%)')
return fig
def boxplot_bairro_px(df,x):
cor_box=get_paleta(x,cor_unica=1)
fig = go.Figure()
fig.add_trace(
go.Box(
y=df[x],
boxpoints='all',
name=x,
opacity=1,
marker=dict(color=cor_box,
line=dict(width=1, color='white'),
opacity=0.99)) )
return fig
def scatterplot_bairro_px(df, x, hue_var=None, teste=None):
palette = {
"Ideal": "#02a84f",
"Baixo": "#f7e305",
"Alto": "#f7870f",
"Critico": "#f01707"}
cor_fill = get_paleta(x, cor_unica=1)
# =========================
# Colunas necessárias
# =========================
y = 'QTI'
cols = [x, y]
if hue_var:
cols.append(hue_var)
if "Bairro" in df.columns:
cols.append("Bairro")
dados = df[cols].dropna()
if x == hue_var:
dados.columns.values[0] = 'Risco'
x = 'Risco'
dados = dados.loc[:, ~dados.columns.duplicated()] # remover colunas duplicadas
if 'Risco' in dados.columns:
mapeamento = {
1: 'Ideal',
2: 'Baixo',
3: 'Alto',
4: 'Critico'}
dados['Risco'] = dados['Risco'].map(mapeamento)
if len(dados) < 2:
print('dados insuficientes')
return None
else:
x_vals = dados[x].values
y_vals = dados[y].values
correlacao, p_valor = pearsonr(x_vals, y_vals)
coef_angular, intercepto = np.polyfit(x_vals, y_vals, 1)
stats = {
"variavel_x": x,
"variavel_y": y,
"correlacao": correlacao,
"p_valor": p_valor,
"r_quadrado": correlacao ** 2,
"coef_angular": coef_angular,
"intercepto": intercepto,
"equacao": f"y = {coef_angular:.4f}x + {intercepto:.4f}",
"n": len(dados)}
# 1. Primeiro: CRIAR FIGURA VAZIA
fig = go.Figure()
# 2. SEGUNDO: Banda de confiança (camada mais baixa)
# Preparar dados da banda
x_range = np.linspace(x_vals.min(), x_vals.max(), 100)
y_pred = coef_angular * x_range + intercepto
n = len(x_vals)
x_mean = np.mean(x_vals)
# Resíduos e erro padrão
y_hat = coef_angular * x_vals + intercepto
residuos = y_vals - y_hat
s_err = np.sqrt(np.sum(residuos ** 2) / (n - 2))
# Inicializar variáveis
y_upper = None
y_lower = None
p_perm = None
if teste is None:
# Intervalo de confiança clássico
t_val = t.ppf(0.975, df=n - 2)
Sxx = np.sum((x_vals - x_mean) ** 2)
conf = t_val * s_err * np.sqrt(1 / n + (x_range - x_mean) ** 2 / Sxx)
y_upper = y_pred + conf
y_lower = y_pred - conf
elif teste == "bootstrap":
# Intervalo de confiança via bootstrap
B = 5000
y_boot = np.zeros((B, len(x_range)))
r_perm = np.zeros(B)
r_obs, _ = pearsonr(x_vals, y_vals)
for b in range(B):
idx = np.random.randint(0, n, n)
xb = x_vals[idx]
yb = y_vals[idx]
m_b, c_b = np.polyfit(xb, yb, 1)
y_boot[b, :] = m_b * x_range + c_b
y_perm = np.random.permutation(y_vals)
r_perm[b], _ = pearsonr(x_vals, y_perm)
p_perm = np.mean(np.abs(r_perm) >= abs(r_obs))
y_lower = np.percentile(y_boot, 2.5, axis=0)
y_upper = np.percentile(y_boot, 97.5, axis=0)
# Adicionar banda de confiança (PRIMEIRA camada)
if y_upper is not None and y_lower is not None:
fig.add_trace(
go.Scatter(
x=np.concatenate([x_range, x_range[::-1]]),
y=np.concatenate([y_upper, y_lower[::-1]]),
fill='toself',
fillcolor=cor_fill,
opacity=0.5,
line=dict(color='rgba(255,255,255,0)'),
hoverinfo="skip",
showlegend=False,
name='IC 95%'))
# 3. TERCEIRO: Linha de regressão (camada intermediária)
fig.add_trace(
go.Scatter(
x=x_range,
y=y_pred,
mode='lines',
line=dict(color='black', width=2, dash='solid'),
name='Linha de regressão',
showlegend=True,
legendgroup="regressao"
)
)
# 4. QUARTA: Pontos do scatterplot (camada mais ALTA/sobreposta)
if hue_var and hue_var in dados.columns:
categorias = dados[hue_var].unique()
for categoria in categorias:
dados_cat = dados[dados[hue_var] == categoria]
fig.add_trace(
go.Scatter(
x=dados_cat[x],
y=dados_cat[y],
mode='markers',
marker=dict(
size=12,
color=palette.get(categoria, '#888888'),
opacity=1
),
name=str(categoria),
text=dados_cat['Bairro'] if 'Bairro' in dados_cat.columns else None,
hoverinfo='text+x+y',
showlegend=True
)
)
else:
# Sem categoria
fig.add_trace(
go.Scatter(
x=dados[x],
y=dados[y],
mode='markers',
marker=dict(
size=12,
color='blue',
opacity=0.9,
line=dict(width=1, color='black')
),
name='Dados',
text=dados['Bairro'] if 'Bairro' in dados.columns else None,
hoverinfo='text+x+y',
showlegend=True
)
)
# =========================
# Layout e formatação
# =========================
texto_stats = (
f"Estatísticas
"
f"r = {stats['correlacao']:.4f}
"
f"r² = {stats['r_quadrado']:.4f}
"
f"n = {stats['n']}
"
f"{stats['equacao']}")
if p_perm is not None:
texto_stats += f"
p-perm = {p_perm:.4f}"
fig.add_annotation(
x=0.01,
y=0.99,
xref="paper",
yref="paper",
showarrow=False,
align="left",
text=texto_stats,
bgcolor="#eef2f7",
opacity=0.85
)
fig.update_layout(
title_x=0.5,
xaxis_title=x,
yaxis_title=y,
legend_title=hue_var)
fig.update_traces(marker_size=12, marker_opacity=0.8)
# Interpretação da correlação
r_abs = abs(correlacao)
if r_abs >= 0.8:
força = "MUITO FORTE"
elif r_abs >= 0.6:
força = "FORTE"
elif r_abs >= 0.4:
força = "MODERADA"
elif r_abs >= 0.2:
força = "FRACA"
else:
força = "MUITO FRACA"
direcao = "POSITIVA" if correlacao > 0 else "NEGATIVA"
alpha = 0.05
if p_perm is not None:
significancia = f"ESTATISTICAMENTE SIGNIFICATIVA Valor-perm: {p_perm:.4f}" if p_perm < alpha else f"NÃO SIGNIFICATIVA Valor-perm: {p_perm:.4f}"
else:
significancia = f"ESTATISTICAMENTE SIGNIFICATIVA Valor-p: {p_valor:.4f}" if (isinstance(p_valor, (int,
float)) and p_valor < 0.05) else f"NÃO SIGNIFICATIVA Valor-p: {p_valor:.4f}"
#print(f"Interpretação: {força} correlação {direcao} ({significancia})")
return fig