Spaces:
Running
Running
Commit ·
e61791a
1
Parent(s): 7f1cd53
Geração da planilha, correções
Browse files- oulad.pkl +2 -2
- template_unificado_features.xlsx +3 -0
- uci.pkl +2 -2
- webapp/home.py +32 -28
- webapp/src/utilidades.py +158 -69
oulad.pkl
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6c040f4a216fca8105d4553f2a598f5bb8d9a5d685bf8bbece704e881fd56fe7
|
| 3 |
+
size 143089
|
template_unificado_features.xlsx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ff058a3d3b447fb6723357141f549d15f445f5c915c6f0f861a6d95ba291e288
|
| 3 |
+
size 5213
|
uci.pkl
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d3db46da97fc1e891182b22caeba81d64b48cc5ea7922f2f624c9973c5f352de
|
| 3 |
+
size 3176212
|
webapp/home.py
CHANGED
|
@@ -29,21 +29,21 @@ criar_sidebar_landpage()
|
|
| 29 |
st.title("📊 Sistema de Análise de Dados Educacionais")
|
| 30 |
st.markdown("### Análise Inteligente com IA")
|
| 31 |
|
| 32 |
-
# Seção 1:
|
| 33 |
st.markdown("## 📥 Passo 1: Baixe o Template")
|
| 34 |
st.markdown("""
|
| 35 |
O template inclui as **2 features mais importantes** identificadas em:
|
| 36 |
-
- **UCI**: Escolas públicas portuguesas
|
| 37 |
-
- **OULAD**: Plataforma de aprendizado online
|
| 38 |
""")
|
| 39 |
|
| 40 |
col1, col2 = st.columns([2, 1])
|
| 41 |
with col1:
|
| 42 |
st.markdown("""
|
| 43 |
**Como usar o Template Unificado:**
|
| 44 |
-
1. Baixe o template Excel com as features mais importantes
|
| 45 |
2. Preencha o template com seus dados (incluindo o nome do aluno)
|
| 46 |
-
3.
|
| 47 |
4. Faça upload do template preenchido para análise automática
|
| 48 |
|
| 49 |
**Vantagens do Template Unificado:**
|
|
@@ -60,30 +60,34 @@ with col2:
|
|
| 60 |
- Coluna de resultado final
|
| 61 |
""")
|
| 62 |
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
st.error("Erro ao gerar template unificado. Verifique se os dados estão carregados.")
|
| 87 |
|
| 88 |
# Seção 2: Upload e Análise
|
| 89 |
st.markdown("## 📤 Passo 2: Envie o Template Preenchido")
|
|
|
|
| 29 |
st.title("📊 Sistema de Análise de Dados Educacionais")
|
| 30 |
st.markdown("### Análise Inteligente com IA")
|
| 31 |
|
| 32 |
+
# Seção 1: Download do Template
|
| 33 |
st.markdown("## 📥 Passo 1: Baixe o Template")
|
| 34 |
st.markdown("""
|
| 35 |
O template inclui as **2 features mais importantes** identificadas em:
|
| 36 |
+
- **UCI**: Escolas públicas portuguesas (nota_2bim, faltas)
|
| 37 |
+
- **OULAD**: Plataforma de aprendizado online (pontuacao, regiao)
|
| 38 |
""")
|
| 39 |
|
| 40 |
col1, col2 = st.columns([2, 1])
|
| 41 |
with col1:
|
| 42 |
st.markdown("""
|
| 43 |
**Como usar o Template Unificado:**
|
| 44 |
+
1. Baixe o template Excel pré-gerado com as features mais importantes
|
| 45 |
2. Preencha o template com seus dados (incluindo o nome do aluno)
|
| 46 |
+
3. Use escala 0-10 para 'resultado_final' (padrão brasileiro)
|
| 47 |
4. Faça upload do template preenchido para análise automática
|
| 48 |
|
| 49 |
**Vantagens do Template Unificado:**
|
|
|
|
| 60 |
- Coluna de resultado final
|
| 61 |
""")
|
| 62 |
|
| 63 |
+
# Botão para download do template pré-gerado
|
| 64 |
+
if st.button("📥 Baixar Template Unificado", type="primary"):
|
| 65 |
+
import os
|
| 66 |
+
|
| 67 |
+
# Verificar se o arquivo existe
|
| 68 |
+
template_path = "template_unificado_features.xlsx"
|
| 69 |
+
if os.path.exists(template_path):
|
| 70 |
+
st.session_state.template_downloaded = True
|
| 71 |
|
| 72 |
+
# Carregar e mostrar preview
|
| 73 |
+
df_template = pd.read_excel(template_path)
|
| 74 |
+
feature_cols = [col for col in df_template.columns if col not in ['nome_aluno', 'resultado_final']]
|
| 75 |
+
st.success(f"✅ Template unificado disponível! Inclui {len(feature_cols)} features: {', '.join(feature_cols)}")
|
| 76 |
+
|
| 77 |
+
st.markdown("**Preview do Template Unificado:**")
|
| 78 |
+
st.dataframe(df_template.head(), use_container_width=True)
|
| 79 |
+
|
| 80 |
+
# Download do arquivo
|
| 81 |
+
with open(template_path, "rb") as file:
|
| 82 |
+
st.download_button(
|
| 83 |
+
"⬇️ Baixar Template Excel",
|
| 84 |
+
data=file.read(),
|
| 85 |
+
file_name="template_analise_educacional.xlsx",
|
| 86 |
+
mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
| 87 |
+
key='download_unified_template'
|
| 88 |
+
)
|
| 89 |
+
else:
|
| 90 |
+
st.error("❌ Template não encontrado. Execute o sistema para gerar o template.")
|
|
|
|
| 91 |
|
| 92 |
# Seção 2: Upload e Análise
|
| 93 |
st.markdown("## 📤 Passo 2: Envie o Template Preenchido")
|
webapp/src/utilidades.py
CHANGED
|
@@ -1254,9 +1254,15 @@ def gerar_template_unificado() -> pd.DataFrame:
|
|
| 1254 |
df_importance_uci = calcular_feature_importance_uci()
|
| 1255 |
top_features_uci = df_importance_uci.nlargest(2, 'importance')['feature'].tolist() if not df_importance_uci.empty else []
|
| 1256 |
|
| 1257 |
-
# Get TOP
|
| 1258 |
df_importance_oulad = calcular_feature_importance_oulad()
|
| 1259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1260 |
|
| 1261 |
# Build template with name field first
|
| 1262 |
template_data = {'nome_aluno': [''] * 10}
|
|
@@ -1295,7 +1301,7 @@ def gerar_template_unificado() -> pd.DataFrame:
|
|
| 1295 |
if col == 'nome_aluno':
|
| 1296 |
continue
|
| 1297 |
elif col == 'resultado_final':
|
| 1298 |
-
df_template.loc[0, col] =
|
| 1299 |
elif 'reprovacoes' in col or 'faltas' in col or 'tentativas' in col:
|
| 1300 |
df_template.loc[0, col] = 0
|
| 1301 |
elif 'nota' in col or 'pontuacao' in col:
|
|
@@ -1386,6 +1392,14 @@ def validar_template_usuario(df_usuario: pd.DataFrame, df_template: pd.DataFrame
|
|
| 1386 |
if 'resultado_final' not in df_usuario.columns:
|
| 1387 |
return False, "Coluna 'resultado_final' não encontrada no arquivo"
|
| 1388 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1389 |
# Verificar se tem a coluna nome_aluno (para template unificado)
|
| 1390 |
if 'nome_aluno' not in df_usuario.columns:
|
| 1391 |
return False, "Coluna 'nome_aluno' não encontrada no arquivo"
|
|
@@ -1435,7 +1449,8 @@ def realizar_eda_automatica(df_usuario: pd.DataFrame) -> dict:
|
|
| 1435 |
X = df_usuario.drop([target_col, 'nome_aluno'], axis=1, errors='ignore')
|
| 1436 |
|
| 1437 |
# Detectar tipo de problema (regressão vs classificação)
|
| 1438 |
-
|
|
|
|
| 1439 |
|
| 1440 |
# Dividir dados
|
| 1441 |
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
|
@@ -1567,8 +1582,8 @@ def realizar_analise_completa(df_usuario: pd.DataFrame) -> dict:
|
|
| 1567 |
# Distribuições
|
| 1568 |
resultados['graficos']['distribuicoes'] = criar_graficos_distribuicao(df_usuario)
|
| 1569 |
|
| 1570 |
-
#
|
| 1571 |
-
resultados['graficos']['
|
| 1572 |
|
| 1573 |
return resultados
|
| 1574 |
|
|
@@ -1588,17 +1603,18 @@ def criar_graficos_distribuicao(df_usuario: pd.DataFrame) -> dict:
|
|
| 1588 |
if 'resultado_final' in df_usuario.columns:
|
| 1589 |
fig, ax = plt.subplots(figsize=(10, 6))
|
| 1590 |
|
| 1591 |
-
#
|
| 1592 |
df_traduzido = df_usuario.copy()
|
| 1593 |
-
df_traduzido['
|
| 1594 |
-
'
|
| 1595 |
-
|
| 1596 |
-
'
|
| 1597 |
-
|
|
|
|
| 1598 |
|
| 1599 |
-
df_traduzido['
|
| 1600 |
ax.set_title('Distribuição de Resultados da Turma', fontsize=16, fontweight='bold')
|
| 1601 |
-
ax.set_xlabel('
|
| 1602 |
ax.set_ylabel('Quantidade de Alunos')
|
| 1603 |
ax.tick_params(axis='x', rotation=45)
|
| 1604 |
|
|
@@ -1615,69 +1631,100 @@ def criar_graficos_distribuicao(df_usuario: pd.DataFrame) -> dict:
|
|
| 1615 |
st.error(f"Erro ao criar gráficos de distribuição: {e}")
|
| 1616 |
return {}
|
| 1617 |
|
| 1618 |
-
def
|
| 1619 |
-
"""Cria
|
| 1620 |
try:
|
| 1621 |
import matplotlib.pyplot as plt
|
| 1622 |
-
import
|
|
|
|
| 1623 |
|
| 1624 |
graficos = {}
|
| 1625 |
|
| 1626 |
-
if 'resultado_final' in df_usuario.columns:
|
| 1627 |
-
#
|
| 1628 |
-
|
| 1629 |
-
|
| 1630 |
-
'Pass': 'Aprovados',
|
| 1631 |
-
'Fail': 'Reprovados'
|
| 1632 |
-
})
|
| 1633 |
-
|
| 1634 |
-
# Calcular médias por grupo
|
| 1635 |
-
medias = df_traduzido.groupby('resultado_final').agg({
|
| 1636 |
-
'faltas': 'mean',
|
| 1637 |
-
'nota_2bim': 'mean',
|
| 1638 |
-
'cliques_plataforma': 'mean',
|
| 1639 |
-
'pontuacao_atividades': 'mean'
|
| 1640 |
-
}).round(2)
|
| 1641 |
|
| 1642 |
-
#
|
| 1643 |
-
|
| 1644 |
-
|
|
|
|
|
|
|
|
|
|
| 1645 |
|
| 1646 |
-
#
|
| 1647 |
-
|
| 1648 |
-
medias['faltas'].plot(kind='bar', ax=axes[0,0], color=['#28a745', '#dc3545'])
|
| 1649 |
-
axes[0,0].set_title('Média de Faltas')
|
| 1650 |
-
axes[0,0].set_ylabel('Número de Faltas')
|
| 1651 |
-
axes[0,0].tick_params(axis='x', rotation=0)
|
| 1652 |
|
| 1653 |
-
#
|
| 1654 |
-
|
| 1655 |
-
medias['nota_2bim'].plot(kind='bar', ax=axes[0,1], color=['#28a745', '#dc3545'])
|
| 1656 |
-
axes[0,1].set_title('Média das Notas')
|
| 1657 |
-
axes[0,1].set_ylabel('Nota (0-10)')
|
| 1658 |
-
axes[0,1].tick_params(axis='x', rotation=0)
|
| 1659 |
|
| 1660 |
-
#
|
| 1661 |
-
|
| 1662 |
-
|
| 1663 |
-
axes[1,0].set_title('Média de Cliques na Plataforma')
|
| 1664 |
-
axes[1,0].set_ylabel('Número de Cliques')
|
| 1665 |
-
axes[1,0].tick_params(axis='x', rotation=0)
|
| 1666 |
|
| 1667 |
-
|
| 1668 |
-
|
| 1669 |
-
|
| 1670 |
-
|
| 1671 |
-
|
| 1672 |
-
|
| 1673 |
-
|
| 1674 |
-
|
| 1675 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1676 |
|
| 1677 |
return graficos
|
| 1678 |
|
| 1679 |
except Exception as e:
|
| 1680 |
-
st.error(f"Erro ao criar
|
| 1681 |
return {}
|
| 1682 |
|
| 1683 |
def exibir_resultados_com_ia(resultados: dict, df_usuario: pd.DataFrame):
|
|
@@ -1691,7 +1738,7 @@ def exibir_resultados_com_ia(resultados: dict, df_usuario: pd.DataFrame):
|
|
| 1691 |
with col1:
|
| 1692 |
st.metric("Total de Alunos", len(df_usuario))
|
| 1693 |
with col2:
|
| 1694 |
-
taxa_aprovacao = (df_usuario['resultado_final'] =
|
| 1695 |
st.metric("Taxa de Aprovação", f"{taxa_aprovacao:.1f}%")
|
| 1696 |
with col3:
|
| 1697 |
st.metric("Features Analisadas", len(df_usuario.columns) - 2)
|
|
@@ -1705,8 +1752,9 @@ def exibir_resultados_com_ia(resultados: dict, df_usuario: pd.DataFrame):
|
|
| 1705 |
# Interpretação via OpenAI
|
| 1706 |
contexto = {
|
| 1707 |
'total_alunos': len(df_usuario),
|
| 1708 |
-
'aprovados': (df_usuario['resultado_final'] =
|
| 1709 |
-
'reprovados': (df_usuario['resultado_final']
|
|
|
|
| 1710 |
}
|
| 1711 |
|
| 1712 |
# Tentar usar OpenAI se disponível
|
|
@@ -1744,9 +1792,50 @@ def exibir_resultados_com_ia(resultados: dict, df_usuario: pd.DataFrame):
|
|
| 1744 |
"""
|
| 1745 |
st.info(f"💡 **Interpretação**: {interpretacao}")
|
| 1746 |
|
| 1747 |
-
# 4.
|
| 1748 |
-
st.markdown("###
|
| 1749 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1750 |
|
| 1751 |
def criar_grafico_correlacao_traduzido(corr_matrix: pd.DataFrame):
|
| 1752 |
"""Cria heatmap de correlação com rótulos traduzidos"""
|
|
|
|
| 1254 |
df_importance_uci = calcular_feature_importance_uci()
|
| 1255 |
top_features_uci = df_importance_uci.nlargest(2, 'importance')['feature'].tolist() if not df_importance_uci.empty else []
|
| 1256 |
|
| 1257 |
+
# Get TOP features from OULAD, excluding temporal features
|
| 1258 |
df_importance_oulad = calcular_feature_importance_oulad()
|
| 1259 |
+
if not df_importance_oulad.empty:
|
| 1260 |
+
# Exclude temporal features that don't make sense for regular school periods
|
| 1261 |
+
temporal_features = ['date_registration', 'date_unregistration']
|
| 1262 |
+
df_filtered = df_importance_oulad[~df_importance_oulad['feature'].isin(temporal_features)]
|
| 1263 |
+
top_features_oulad = df_filtered.nlargest(2, 'importance')['feature'].tolist()
|
| 1264 |
+
else:
|
| 1265 |
+
top_features_oulad = []
|
| 1266 |
|
| 1267 |
# Build template with name field first
|
| 1268 |
template_data = {'nome_aluno': [''] * 10}
|
|
|
|
| 1301 |
if col == 'nome_aluno':
|
| 1302 |
continue
|
| 1303 |
elif col == 'resultado_final':
|
| 1304 |
+
df_template.loc[0, col] = 7.5 # Exemplo de nota 0-10 (padrão brasileiro)
|
| 1305 |
elif 'reprovacoes' in col or 'faltas' in col or 'tentativas' in col:
|
| 1306 |
df_template.loc[0, col] = 0
|
| 1307 |
elif 'nota' in col or 'pontuacao' in col:
|
|
|
|
| 1392 |
if 'resultado_final' not in df_usuario.columns:
|
| 1393 |
return False, "Coluna 'resultado_final' não encontrada no arquivo"
|
| 1394 |
|
| 1395 |
+
# Verificar se resultado_final está na escala 0-10 (padrão brasileiro)
|
| 1396 |
+
resultado_values = df_usuario['resultado_final'].dropna()
|
| 1397 |
+
if len(resultado_values) > 0:
|
| 1398 |
+
if not pd.api.types.is_numeric_dtype(resultado_values):
|
| 1399 |
+
return False, "Coluna 'resultado_final' deve conter valores numéricos (0-10)"
|
| 1400 |
+
if (resultado_values < 0).any() or (resultado_values > 10).any():
|
| 1401 |
+
return False, "Valores em 'resultado_final' devem estar entre 0 e 10"
|
| 1402 |
+
|
| 1403 |
# Verificar se tem a coluna nome_aluno (para template unificado)
|
| 1404 |
if 'nome_aluno' not in df_usuario.columns:
|
| 1405 |
return False, "Coluna 'nome_aluno' não encontrada no arquivo"
|
|
|
|
| 1449 |
X = df_usuario.drop([target_col, 'nome_aluno'], axis=1, errors='ignore')
|
| 1450 |
|
| 1451 |
# Detectar tipo de problema (regressão vs classificação)
|
| 1452 |
+
# Sempre tratar como regressão se for numérico (escala 0-10)
|
| 1453 |
+
is_regression = pd.api.types.is_numeric_dtype(y)
|
| 1454 |
|
| 1455 |
# Dividir dados
|
| 1456 |
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
|
|
|
| 1582 |
# Distribuições
|
| 1583 |
resultados['graficos']['distribuicoes'] = criar_graficos_distribuicao(df_usuario)
|
| 1584 |
|
| 1585 |
+
# Gráfico radar (será criado com seleção de aluno)
|
| 1586 |
+
resultados['graficos']['radar'] = criar_grafico_radar_aluno(df_usuario)
|
| 1587 |
|
| 1588 |
return resultados
|
| 1589 |
|
|
|
|
| 1603 |
if 'resultado_final' in df_usuario.columns:
|
| 1604 |
fig, ax = plt.subplots(figsize=(10, 6))
|
| 1605 |
|
| 1606 |
+
# Criar bins para notas numéricas (escala 0-10)
|
| 1607 |
df_traduzido = df_usuario.copy()
|
| 1608 |
+
df_traduzido['faixa_nota'] = pd.cut(
|
| 1609 |
+
df_traduzido['resultado_final'],
|
| 1610 |
+
bins=[0, 5, 7, 10],
|
| 1611 |
+
labels=['Insuficiente (0-5)', 'Regular (5-7)', 'Bom (7-10)'],
|
| 1612 |
+
include_lowest=True
|
| 1613 |
+
)
|
| 1614 |
|
| 1615 |
+
df_traduzido['faixa_nota'].value_counts().plot(kind='bar', ax=ax, color=['#dc3545', '#ffc107', '#28a745'])
|
| 1616 |
ax.set_title('Distribuição de Resultados da Turma', fontsize=16, fontweight='bold')
|
| 1617 |
+
ax.set_xlabel('Faixa de Nota')
|
| 1618 |
ax.set_ylabel('Quantidade de Alunos')
|
| 1619 |
ax.tick_params(axis='x', rotation=45)
|
| 1620 |
|
|
|
|
| 1631 |
st.error(f"Erro ao criar gráficos de distribuição: {e}")
|
| 1632 |
return {}
|
| 1633 |
|
| 1634 |
+
def criar_grafico_radar_aluno(df_usuario: pd.DataFrame, nome_aluno: str = None) -> dict:
|
| 1635 |
+
"""Cria gráfico radar comparando aluno individual com média da turma"""
|
| 1636 |
try:
|
| 1637 |
import matplotlib.pyplot as plt
|
| 1638 |
+
import numpy as np
|
| 1639 |
+
from math import pi
|
| 1640 |
|
| 1641 |
graficos = {}
|
| 1642 |
|
| 1643 |
+
if 'resultado_final' in df_usuario.columns and 'nome_aluno' in df_usuario.columns:
|
| 1644 |
+
# Se não especificado, usar o primeiro aluno como exemplo
|
| 1645 |
+
if nome_aluno is None:
|
| 1646 |
+
nome_aluno = df_usuario['nome_aluno'].iloc[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1647 |
|
| 1648 |
+
# Verificar se o aluno existe
|
| 1649 |
+
aluno_data = df_usuario[df_usuario['nome_aluno'] == nome_aluno]
|
| 1650 |
+
if aluno_data.empty:
|
| 1651 |
+
st.warning(f"Aluno '{nome_aluno}' não encontrado. Usando primeiro aluno como exemplo.")
|
| 1652 |
+
nome_aluno = df_usuario['nome_aluno'].iloc[0]
|
| 1653 |
+
aluno_data = df_usuario.iloc[[0]]
|
| 1654 |
|
| 1655 |
+
# Obter dados do aluno selecionado
|
| 1656 |
+
aluno_row = aluno_data.iloc[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1657 |
|
| 1658 |
+
# Calcular médias da turma (excluindo o aluno selecionado)
|
| 1659 |
+
turma_media = df_usuario[df_usuario['nome_aluno'] != nome_aluno].mean(numeric_only=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1660 |
|
| 1661 |
+
# Selecionar colunas numéricas para o radar (incluindo resultado_final)
|
| 1662 |
+
colunas_numericas = [col for col in df_usuario.select_dtypes(include=[np.number]).columns
|
| 1663 |
+
if col in aluno_row.index]
|
|
|
|
|
|
|
|
|
|
| 1664 |
|
| 1665 |
+
if len(colunas_numericas) >= 3: # Mínimo 3 dimensões para radar
|
| 1666 |
+
# Preparar dados para o radar
|
| 1667 |
+
valores_aluno = [aluno_row[col] for col in colunas_numericas]
|
| 1668 |
+
valores_turma = [turma_media[col] for col in colunas_numericas]
|
| 1669 |
+
|
| 1670 |
+
# Normalizar valores para escala 0-10 (assumindo que as features já estão nessa escala)
|
| 1671 |
+
# Se não estiverem, fazer normalização simples
|
| 1672 |
+
max_val = max(max(valores_aluno), max(valores_turma))
|
| 1673 |
+
if max_val > 10:
|
| 1674 |
+
valores_aluno = [v/max_val*10 for v in valores_aluno]
|
| 1675 |
+
valores_turma = [v/max_val*10 for v in valores_turma]
|
| 1676 |
+
|
| 1677 |
+
# Criar gráfico radar
|
| 1678 |
+
fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))
|
| 1679 |
+
|
| 1680 |
+
# Ângulos para cada dimensão
|
| 1681 |
+
angles = [n / float(len(colunas_numericas)) * 2 * pi for n in range(len(colunas_numericas))]
|
| 1682 |
+
angles += angles[:1] # Fechar o círculo
|
| 1683 |
+
|
| 1684 |
+
# Adicionar valores para fechar o círculo
|
| 1685 |
+
valores_aluno += valores_aluno[:1]
|
| 1686 |
+
valores_turma += valores_turma[:1]
|
| 1687 |
+
|
| 1688 |
+
# Plotar dados
|
| 1689 |
+
ax.plot(angles, valores_aluno, 'o-', linewidth=2, label=f'{nome_aluno}', color='#2E86AB', markersize=8)
|
| 1690 |
+
ax.fill(angles, valores_aluno, alpha=0.25, color='#2E86AB')
|
| 1691 |
+
|
| 1692 |
+
ax.plot(angles, valores_turma, 'o-', linewidth=2, label='Média da Turma', color='#A23B72', markersize=8)
|
| 1693 |
+
ax.fill(angles, valores_turma, alpha=0.25, color='#A23B72')
|
| 1694 |
+
|
| 1695 |
+
# Configurar eixos com traduções
|
| 1696 |
+
traducao_rotulos = {
|
| 1697 |
+
'nota_2bim': 'Nota 2º Bimestre',
|
| 1698 |
+
'faltas': 'Faltas',
|
| 1699 |
+
'pontuacao': 'Pontuação',
|
| 1700 |
+
'resultado_final': 'Nota Final'
|
| 1701 |
+
}
|
| 1702 |
+
|
| 1703 |
+
ax.set_xticks(angles[:-1])
|
| 1704 |
+
ax.set_xticklabels([traducao_rotulos.get(col, col.replace('_', ' ').title()) for col in colunas_numericas])
|
| 1705 |
+
ax.set_ylim(0, 10)
|
| 1706 |
+
ax.set_yticks([2, 4, 6, 8, 10])
|
| 1707 |
+
ax.set_yticklabels(['2', '4', '6', '8', '10'])
|
| 1708 |
+
ax.grid(True)
|
| 1709 |
+
|
| 1710 |
+
# Título e legenda
|
| 1711 |
+
ax.set_title(f'Comparação: {nome_aluno} vs Média da Turma', size=16, fontweight='bold', pad=20)
|
| 1712 |
+
ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.0))
|
| 1713 |
+
|
| 1714 |
+
# Adicionar valores nas pontas
|
| 1715 |
+
for i, (angle, valor_aluno, valor_turma) in enumerate(zip(angles[:-1], valores_aluno[:-1], valores_turma[:-1])):
|
| 1716 |
+
ax.text(angle, valor_aluno + 0.5, f'{valor_aluno:.1f}', ha='center', va='center', fontweight='bold', color='#2E86AB')
|
| 1717 |
+
ax.text(angle, valor_turma - 0.5, f'{valor_turma:.1f}', ha='center', va='center', fontweight='bold', color='#A23B72')
|
| 1718 |
+
|
| 1719 |
+
plt.tight_layout()
|
| 1720 |
+
graficos['radar_comparacao_aluno'] = fig
|
| 1721 |
+
else:
|
| 1722 |
+
st.warning("Não há colunas numéricas suficientes para criar o gráfico radar.")
|
| 1723 |
|
| 1724 |
return graficos
|
| 1725 |
|
| 1726 |
except Exception as e:
|
| 1727 |
+
st.error(f"Erro ao criar gráfico radar: {e}")
|
| 1728 |
return {}
|
| 1729 |
|
| 1730 |
def exibir_resultados_com_ia(resultados: dict, df_usuario: pd.DataFrame):
|
|
|
|
| 1738 |
with col1:
|
| 1739 |
st.metric("Total de Alunos", len(df_usuario))
|
| 1740 |
with col2:
|
| 1741 |
+
taxa_aprovacao = (df_usuario['resultado_final'] >= 5.0).mean() * 100 # Aprovação >= 5.0
|
| 1742 |
st.metric("Taxa de Aprovação", f"{taxa_aprovacao:.1f}%")
|
| 1743 |
with col3:
|
| 1744 |
st.metric("Features Analisadas", len(df_usuario.columns) - 2)
|
|
|
|
| 1752 |
# Interpretação via OpenAI
|
| 1753 |
contexto = {
|
| 1754 |
'total_alunos': len(df_usuario),
|
| 1755 |
+
'aprovados': (df_usuario['resultado_final'] >= 5.0).sum(),
|
| 1756 |
+
'reprovados': (df_usuario['resultado_final'] < 5.0).sum(),
|
| 1757 |
+
'media_geral': df_usuario['resultado_final'].mean()
|
| 1758 |
}
|
| 1759 |
|
| 1760 |
# Tentar usar OpenAI se disponível
|
|
|
|
| 1792 |
"""
|
| 1793 |
st.info(f"💡 **Interpretação**: {interpretacao}")
|
| 1794 |
|
| 1795 |
+
# 4. Gráfico Radar - Comparação Individual
|
| 1796 |
+
st.markdown("### 🎯 Análise Individual - Gráfico Radar")
|
| 1797 |
+
|
| 1798 |
+
# Campo de busca para seleção do aluno
|
| 1799 |
+
if 'nome_aluno' in df_usuario.columns:
|
| 1800 |
+
nomes_alunos = sorted(df_usuario['nome_aluno'].unique().tolist()) # Ordem alfabética
|
| 1801 |
+
nome_selecionado = st.selectbox(
|
| 1802 |
+
"Selecione o aluno para análise:",
|
| 1803 |
+
options=nomes_alunos,
|
| 1804 |
+
index=0,
|
| 1805 |
+
help="Escolha um aluno para comparar com a média da turma"
|
| 1806 |
+
)
|
| 1807 |
+
|
| 1808 |
+
# Criar gráfico radar para o aluno selecionado
|
| 1809 |
+
grafico_radar = criar_grafico_radar_aluno(df_usuario, nome_selecionado)
|
| 1810 |
+
|
| 1811 |
+
if 'radar_comparacao_aluno' in grafico_radar:
|
| 1812 |
+
st.pyplot(grafico_radar['radar_comparacao_aluno'])
|
| 1813 |
+
|
| 1814 |
+
# Interpretação do gráfico radar
|
| 1815 |
+
try:
|
| 1816 |
+
from .openai_interpreter import interpretar_grafico
|
| 1817 |
+
contexto_radar = {
|
| 1818 |
+
'nome_aluno': nome_selecionado,
|
| 1819 |
+
'total_alunos': len(df_usuario),
|
| 1820 |
+
'media_turma': df_usuario['resultado_final'].mean()
|
| 1821 |
+
}
|
| 1822 |
+
interpretacao = interpretar_grafico('radar_comparacao', contexto_radar)
|
| 1823 |
+
st.info(f"💡 **Interpretação**: {interpretacao}")
|
| 1824 |
+
except:
|
| 1825 |
+
interpretacao = f"""
|
| 1826 |
+
Este gráfico radar compara o desempenho de {nome_selecionado} com a média da turma.
|
| 1827 |
+
Áreas onde o aluno está acima da média (linha azul acima da rosa) indicam pontos fortes.
|
| 1828 |
+
Áreas abaixo da média podem indicar necessidades de apoio pedagógico.
|
| 1829 |
+
"""
|
| 1830 |
+
st.info(f"💡 **Interpretação**: {interpretacao}")
|
| 1831 |
+
else:
|
| 1832 |
+
st.warning("Não foi possível criar o gráfico radar para este aluno.")
|
| 1833 |
+
else:
|
| 1834 |
+
st.warning("Coluna 'nome_aluno' não encontrada nos dados.")
|
| 1835 |
+
|
| 1836 |
+
# 5. Tabela de Dados
|
| 1837 |
+
st.markdown("### 📋 Dados Completos da Turma")
|
| 1838 |
+
st.dataframe(df_usuario, use_container_width=True)
|
| 1839 |
|
| 1840 |
def criar_grafico_correlacao_traduzido(corr_matrix: pd.DataFrame):
|
| 1841 |
"""Cria heatmap de correlação com rótulos traduzidos"""
|