O Brasil continua sendo um dos países com maior desigualdade social do mundo, o que reflete que também existe desigualdade na educação, e consentemente no acesso à oportunidades possibilitadas pelo desenvolvimento do invíduo através de uma educação formal de qualidade [1]. O acesso a educação de qualidade contribui para diminuir a desigualdade social e melhorar a qualidade de vida da população, logo esforços para o desenvlvimento da educação no país tornam-se primordiais [2].
Um das formas de analisar o desempenho da escolas é avaliar o seu Índice de Desenvolvimento da Educação Básica (Ideb), reúne, em um só indicador, os resultados de dois conceitos igualmente importantes para a qualidade da educação: o fluxo escolar e as médias de desempenho nas avaliações. O Ideb agrega ao enfoque pedagógico das avaliações em larga escala a possibilidade de resultados sintéticos, facilmente assimiláveis, e que permitem traçar metas de qualidade educacional para os sistemas [3].
Assim, o presente trabalho buscará agrupar escolas a partir da idenficação de atributos similares, para avaliar se esses grupos refletem algum padrão de desempenho e características. Dessa maneira, com a separação de escolas por grupos, pode-se agilizar a análise, por profissionais da educação, na identificação das escolas, ou até mesmo de regiões, e suas características.
[1] https://brasilescola.uol.com.br/sociologia/desigualdade-social.htm
[2] https://educacaointegral.org.br/reportagens/desigualdades-educacionais-no-brasil/
[3] https://www.gov.br/inep/pt-br/areas-de-atuacao/pesquisas-estatisticas-e-indicadores/ideb
Foram utilizados 3 conjuntos de dados para essa clusterização: o censo escolar de 2019, a notas do ideb de 2019 e um conjunto de dados auxiliar com os dados de geolocalização dos municipios brasileiros para visulizações em mapas.
• Censo Escolar 2019
O Censo Escolar é o principal instrumento de coleta de informações da educação básica e a mais importante pesquisa estatística educacional brasileira. É coordenado pelo Inep e realizado em regime de colaboração entre as secretarias estaduais e municipais de educação e com a participação de todas as escolas públicas e privadas do país. A pesquisa estatística abrange as diferentes etapas e modalidades da educação básica e profissional. Entretanto, não utilizaremos os dados de escolas particulares, pois nem todas escolas particulares participam da prova do Saeb, e a porcentagem de participação é desconhecida, então desconsideramos por conta das incertezas.
Link: https://www.gov.br/inep/pt-br/acesso-a-informacao/dados-abertos/microdados/censo-escolar
• Notas do Ideb 2019
Como foi mensionado na introdução, o Ideb reúne, em um só indicador, os resultados de dois conceitos igualmente importantes para a qualidade da educação: o fluxo escolar e as médias de desempenho nas avaliações. O Ideb é calculado a partir dos dados sobre aprovação escolar, obtidos no Censo Escolar, e das médias de desempenho no Sistema de Avaliação da Educação Básica (Saeb). Para podermos generalizar a avaliação do ensino, utilizaremos somente a nota do Ideb, sem levar em consideração as notas do Saeb.
Link: https://www.gov.br/inep/pt-br/areas-de-atuacao/pesquisas-estatisticas-e-indicadores/ideb/resultados
• Municipios Brasileiros
Conjunto de dados auxiliar com os dados de geolocalização dos municipios brasileiros para visulizações em mapas.
Link: https://github.com/kelvins/Municipios-Brasileiros
A principio, submetemos o trabalho como 'Clusterização das Escolas Públicas Brasileiras para Análise de Grupos de Desempenho', mas por não termos conseguido lidar com os tamanhos do arquivos de forma eficiente a tempo da entrega, tivemos que mudar o escopo de escolas públicas brasileiras para escolas públicas de ensino fundamental do Ceará, diminuindo o volume de dados e possibilitando a execução dos modelos em um tempo razoável para prosseguirmos com o trabalho. Dessa maneira, alguns passos e análises foram feitos primeiramente para o país inteira, mas que ainda assim fazem parte do pipeline para reduzirmos ao conjunto de dados referente ao Ceará
• O conjunto de dados do Censo Escolar 2019 é dividido em 5 tipos de conjuntos de dados: escolas; matrículas; turmas e docentes, sendo alguns desses conjuntos divididos por regiões do Brasil. Tentar simplesmente juntar todos os atributos de todos os conjuntos aumentaria demais a dimensionalidade para a clusterização, além de que colocaria todos atributos em um mesmo patamar de pesos.
• Primeiramente, listamos os atributos mais importantes dos conjuntos, tanto para diminuir a dimensionalidade dos atributos e tentar ajudar os modelos quanto para diminuirmos um pouco o volume do dataset para processamento.
• Para diminuir ainda mais a dimensão dos atributos, aplicamos uma clusterização primeiramente em cada conjunto separadamente e em seguida aplicamos outra clusterização de disso. O objetivo da primeira clusterização é, além de reduzir o a dimensionalidade, tentar resumir as informações dos conjuntos de forma a distingui-los em grupos, preservando as diferenças. Também poderíamos passar a clusterização de dados categóricos e booleanos (natureza dos dados dos conjuntos) para dados numéricos a partir das contagens dos grupos, assim possibilitando utilizarmos mais modelos de clusterização, pois as gama de modelos para atributos de natureza categorica e booleana é menor. Além dos objetivos citados, também utilizamos essa estratégia para podemos lidar com os diferentes números de dados para cada escola, pois em uma escola há várias matrículas, várias turmas e docentes, dessa maneira poderíamos também resumir o número de linhas para somente uma, a linha da contagem dos clusters.
• Por exemplo, ao clusterizarmos os docentes de uma escola, poderíamos passar de 30 atributos categóricos e booleanos para 5 atributos (caso seja esse número de grupos), onde os valores desses atributos seria o número de docentes naquele cluster daquela referida escola. Essa lógica foi aplicada para cada conjunto de dados.
Embora os dados do Censo Escolar e Ideb sejam bem indexados e de fácil obtenção, o conjunto de dados é relativamente grande para ser aberto inteiramente em um computador sem algum outro recurso para manipulação de grandes dados, e existem dados faltantes por conta do não preenchimento completo do censo por algumas escolas, então precisamos fazer certos procedimentos para por trabalharmos com os dados.
No caso do volume, para podermos utilizar o conjunto de dados sem usar algum recurso externo, podemos utilizar certos procedimentos na abertura do arquivo .csv. No caso dos dados faltantes, em alguns atributos podemos assumir certas condições e implicações, preenchendo-os por outros valores.
# É necessária a instalação no Kmodes para utilização no Colab
!pip install kmodes
#Instalação necessária para a utilização dataframe dask no Colab
!python -m pip install dask[dataframe] --upgrade
import pandas as pd
import zipfile
import numpy as np
import matplotlib.pyplot as plt
import dask.dataframe as dd
from yellowbrick.cluster import SilhouetteVisualizer
from kmodes.kmodes import KModes
from sklearn.cluster import KMeans
from sklearn.cluster import MiniBatchKMeans
from sklearn.metrics import silhouette_score
from sklearn.decomposition import PCA
from functools import reduce
from google.colab import drive
drive.mount('/content/drive')
#Estilização dos plots
plt.style.use("ggplot")
#Caminho no drive para importações
general_path = "/content/drive/MyDrive/Colab Notebooks/"
No conjunto de dados do censo, não utlizamos todos os dados os atributos, ao invés disso utilizamos somentes atributos selecionados que julgamos previamente, através de discussões, serem mais relevantes para caracterizar cada conjunto. A seleção previa foi necessária para podermos importarmos somente os atributos desejados, diminuindo espaço em memória na abertura conjunto.
cols_esc = {'CO_ENTIDADE':'category',
'CO_UF':'category',
'TP_DEPENDENCIA':'category',
'TP_LOCALIZACAO':'category',
'TP_LOCALIZACAO_DIFERENCIADA':'category',
'IN_VINCULO_SECRETARIA_EDUCACAO':'category',
'IN_VINCULO_SEGURANCA_PUBLICA':'category',
'TP_OCUPACAO_PREDIO_ESCOLAR':'category',
'IN_LOCAL_FUNC_SOCIOEDUCATIVO':'category',
'IN_LOCAL_FUNC_UNID_PRISIONAL':'category',
'IN_LOCAL_FUNC_PRISIONAL_SOCIO':'category',
'IN_LOCAL_FUNC_GALPAO':'category',
'IN_ENERGIA_INEXISTENTE':'category',
'IN_BIBLIOTECA':'category',
'IN_LABORATORIO_CIENCIAS':'category',
'IN_LABORATORIO_INFORMATICA':'category',
'IN_ACESSIBILIDADE_INEXISTENTE':'category',
'IN_EQUIP_MULTIMIDIA':'category',
'IN_COMP_PORTATIL_ALUNO':'category',
'IN_TABLET_ALUNO':'category',
'IN_INTERNET':'category',
'IN_MATERIAL_PED_CIENTIFICO':'category',
'IN_EXAME_SELECAO':'category',
'TP_PROPOSTA_PEDAGOGICA':'category'}
Para evitar a descompactação dos arquivos, utilizamos o ZipFile para extrairmos os dados diretos do arquivo .zip. Na instrução também já definimos os tipos dos atributos para categóricos, para diminuir o espaço ocupado do conjunto de dados. Em tipo categóricos, o pandas guarda os dados das categorias em memória, mas os dados subsequentes ele apenas salva um ponteiro para o valor da referida categoria, diminuindo assim o espaço ocupado em memória.
with zipfile.ZipFile(general_path + 'microdados_educacao_basica_2019.zip') as z:
with z.open('microdados_educacao_basica_2019/DADOS/ESCOLAS.CSV') as f:
escolas = pd.read_csv(f,sep='|',
encoding='ISO-8859-1',
usecols=list(cols_esc.keys()),
dtype=cols_esc)
O arquivo original de escolas.CSV era de 111.3MB e conseguimos diminuir para 30.1MB como podemos ver ao final da instrução. Repetindo esse procedimento para todos os conjuntos, podemos diminuir drasticamente os 16GB totais do conjunto de dados.
escolas.info(memory_usage="deep")
Com a instrução a baixo podemos ver que existem dados faltantes nesse conjunto. Então prosseguimos com o tratamentos desses casos.
escolas.isna().sum()
No caso de 'TP_OCUPACAO_PREDIO_ESCOLAR', que é uma variável categórica com 3 labels, avaliamos que os dados faltantes sejam para escolas que não ocupam prédio escolar, como indicado no dicionário de dados. Para não eliminarmos todas as linhas esses dados faltantes, adicionaremos uma categoria nova.
escolas['TP_OCUPACAO_PREDIO_ESCOLAR'] = escolas['TP_OCUPACAO_PREDIO_ESCOLAR'].cat.add_categories(0)
Os atributos 'IN_VINCULO_SECRETARIA_EDUCACAO' e 'IN_VINCULO_SEGURANCA_PUBLICA' são booleanos, então assumimos que convém preenchermos os dados faltantes por 0, que significa que tais escolas não tem vínculo.
escolas = escolas.fillna({'IN_VINCULO_SECRETARIA_EDUCACAO':'0',
'IN_VINCULO_SEGURANCA_PUBLICA':'0',
'TP_OCUPACAO_PREDIO_ESCOLAR':0})
Podemos ver que o resto dos dados faltantes sejam das mesmas linhas por terem todos os mesmo valores. Nessa caso, se elas todas não apresentam essas informações, não podemos preenche-las com outros valores.
escolas.isna().sum()
escolas = escolas.dropna()
Os procedimentos são analógos para os outros conjuntos de dados. Em turmas, restaram poucos atributos relevantes pois nesse conjunto há muita repetição de atributos do conjunto de dados 'matrículas' e 'escolas'.
cols_tur = {'CO_ENTIDADE':'category',
'CO_UF':'category',
'TP_MEDIACAO_DIDATICO_PEDAGO':'category',
'NU_DIAS_ATIVIDADE':'category',
'TP_TIPO_ATENDIMENTO_TURMA':'category'}
with zipfile.ZipFile(general_path + 'microdados_educacao_basica_2019.zip') as z:
with z.open('microdados_educacao_basica_2019/DADOS/TURMAS.CSV') as f:
turmas = pd.read_csv(f,sep='|',
encoding='ISO-8859-1',
usecols=list(cols_tur.keys()),
dtype=cols_tur)
turmas.info()
turmas.isna().sum()
turmas = turmas.dropna()
cols_mat = {"CO_ENTIDADE":'category',
"CO_UF":'category',
"TP_COR_RACA":'category',
"TP_ZONA_RESIDENCIAL":'category',
"TP_LOCAL_RESID_DIFERENCIADA":'category',
"IN_NECESSIDADE_ESPECIAL":'category',
"IN_TRANSPORTE_PUBLICO":'category',
"IN_TRANSP_BICICLETA":'category',
"IN_TRANSP_MICRO_ONIBUS":'category',
"IN_TRANSP_ONIBUS":'category',
"IN_TRANSP_TR_ANIMAL":'category',
"IN_TRANSP_VANS_KOMBI":'category',
"IN_TRANSP_OUTRO_VEICULO":'category'}
with zipfile.ZipFile(general_path + 'microdados_educacao_basica_2019.zip') as z:
with z.open('microdados_educacao_basica_2019/DADOS/MATRICULA_NORDESTE.CSV') as f:
matriculas = pd.read_csv(f,sep='|',
encoding='ISO-8859-1',
usecols=list(cols_mat.keys()),
dtype=cols_mat)
matriculas.info(memory_usage="deep")
matriculas.isna().sum()
matriculas = matriculas.fillna({'TP_LOCAL_RESID_DIFERENCIADA':'0',
'IN_TRANSPORTE_PUBLICO':'0',
'IN_TRANSP_BICICLETA':'0',
'IN_TRANSP_MICRO_ONIBUS':'0',
'IN_TRANSP_ONIBUS':'0',
'IN_TRANSP_TR_ANIMAL':'0',
'IN_TRANSP_VANS_KOMBI':'0',
'IN_TRANSP_OUTRO_VEICULO':'0'})
matriculas.isna().sum()
matriculas.dropna(inplace=True)
cols_doc = {"CO_ENTIDADE":'category',
"CO_UF":'category',
"TP_ESCOLARIDADE":'category',
"TP_TIPO_IES_1":'category',
"IN_COMPLEMENTACAO_PEDAGOGICA":'category',
"IN_ESPECIALIZACAO":'category',
"IN_MESTRADO":'category',
"IN_DOUTORADO":'category',
"IN_POS_NENHUM":'category'}
with zipfile.ZipFile(general_path + 'microdados_educacao_basica_2019.zip') as z:
with z.open('microdados_educacao_basica_2019/DADOS/DOCENTES_NORDESTE.CSV') as f:
docentes = pd.read_csv(f,sep='|',
encoding='ISO-8859-1',
usecols=list(cols_doc.keys()),
dtype = cols_doc)
docentes.info()
docentes.isna().sum()
docentes = docentes.fillna({'TP_TIPO_IES_1':'2',
'IN_ESPECIALIZACAO':'0',
'IN_MESTRADO':'0',
'IN_DOUTORADO':'0',
'IN_POS_NENHUM':'0'
})
docentes.isna().sum()
Como podemos ver, existem alguns dados faltantes nas notas do Ideb. Para a análise explorátória, estaremos utilizando os dados do Ideb por municipio. As notas são separadas pela rede estadual, municipal, federal e pública, mas nem todos os muncípios possuem todas as redes, mas ainda assim o espaço fica na tabela, gerando dados faltantes. Nesse caso, podemos elimina-las. Os dados do Ideb por muncípio serão utilizados para visualização da dispersão das notas no mapa do Brasil.
ideb = pd.read_csv(general_path + 'ideb_2019.csv', sep=';', na_values='-')
ideb.isna().sum()
ideb = ideb.dropna()
ideb.head()
Como dito anteriormente, as notas são separadas pela rede estadual, municipal, federal e pública, não encontramos explicação de como é feito o cálculo da rede pública no geral, mas é ela que sempre é referenciada em análises encontradas na internet, então selecionaremos somente as notas da rede pública.
cod_notas = ideb.filter(items=['UF','COD_MUNIC','NOTA_IDEB']).where(ideb.REDE=='Pública')
cod_notas = cod_notas.dropna()
municipios = pd.read_csv(general_path + 'municipios.csv', usecols=['codigo_ibge','latitude','longitude'])
municipios.head()
Para a plotarmos os gráficos de dispersão das notas e visualizá-los dispersos no mapa do Brasil, podemos utilizar as coordenadas de longitude e latitude como x e y de um plot comum. Para isso, precisamos juntar as notas do referidos municipios e as suas coordenadas.
ideb_municipios = pd.merge(municipios,cod_notas,
how='left', left_on=['codigo_ibge'],
right_on=['COD_MUNIC'])
Ajustando a formatação dos dados para converoa para float.
ideb_municipios['NOTA_IDEB'] = ideb_municipios['NOTA_IDEB'].str.replace(',','.')
ideb_municipios['NOTA_IDEB'] = ideb_municipios['NOTA_IDEB'].astype('float')
Para selecionar as escolas do estado do Ceará, utilizaremos o atributo 'CO_UF' (Código de Unidade Federativa) igual a 23, que corresponde ao Ceará.
escolas_ce = escolas[escolas['CO_UF']=='23']
escolas_ce.info()
turmas_ce = turmas[turmas.CO_ENTIDADE.isin(escolas_ce['CO_ENTIDADE'])]
turmas_ce.info()
matriculas_ce = matriculas[matriculas.CO_ENTIDADE.isin(escolas_ce['CO_ENTIDADE'])]
matriculas_ce.info()
docentes_ce = docentes[docentes.CO_ENTIDADE.isin(escolas_ce['CO_ENTIDADE'])]
docentes_ce.info()
Primeiro filtramos os dados do Ceará para podemos diminuir o número de iterações do segundo filtro. O segundo filtro consiste em pegar somente as escolas com as notas do Ideb disponíveis, pois não temos as notas de escolas que são exclusivamente EJA ou profissionalizantes, então as retiramos para não inviésarmos os agrupamentos.
Os dados são divididos em anos iniciais (1º ao 5º) e finais (6º ao 9°), então vamos juntar os conjuntos de dados. Para escolas do ensino fundamental que são somente de ano iniciais ou finais, mantemos somentes os valores incidentes. No caso de escolas com as duas modalidades, fizemos uma média.
finais = pd.read_csv(general_path + "ideb_2019_ escolas_anos_finais", sep=';', na_values='-')
iniciais = pd.read_csv(general_path + "ideb_2019_ escolas_anos_iniciais", sep=';', na_values='-')
Primeiro, retiramos os dados faltantes e selecionamos somente os dados do Ceará.
finais = finais.dropna()
iniciais = iniciais.dropna()
finais = finais[finais['UF']=='CE']
iniciais = iniciais[iniciais['UF']=='CE']
Em seguida, substituímos as vírgulas por pontos para poder transformar os dados em float.
finais['NOTA_IDEB'] = finais['NOTA_IDEB'].str.replace(',','.').astype('float')
iniciais['NOTA_IDEB'] = iniciais['NOTA_IDEB'].str.replace(',','.').astype('float')
Juntando os dados a partir do código da escola.
IDEB_geral = iniciais.merge(finais,
how='outer',
on='COD_ESCOLA',
indicator='flag', suffixes=['_AF', '_AI'])
Aqui, juntamos os dois conjuntos de dados aplicando as regras ditas à cima.
nota = []
for i in IDEB_geral.index:
if(IDEB_geral['flag'][i] == 'both'):
nota.append((IDEB_geral['NOTA_IDEB_AF'][i]+IDEB_geral['NOTA_IDEB_AI'][i])/2)
elif(IDEB_geral['flag'][i] == 'left_only'):
nota.append(IDEB_geral['NOTA_IDEB_AF'][i])
else:
nota.append(IDEB_geral['NOTA_IDEB_AI'][i])
IDEB_geral['NOTA_IDEB'] = nota
IDEB_geral = IDEB_geral.drop(['NOTA_IDEB_AI','NOTA_IDEB_AF','flag','UF_AF','UF_AI'], axis=1)
Nem todas as escolas públicas realizaram a prova do SAEB ou forneceram os dados necessários para o cálculo do Ideb de 2019, por isso, selecionaremos somentes as escolas cearences que possuem essa nota, a partir do seu código de escola que as identifica.
A coluna do código de escolas do conjunto de escolas com notas do Ideb é inteiro, então precisamos converter a coluna do código do conjunto de escolas do censo escolas para inteiro também.
escolas_ce['CO_ENTIDADE'] = escolas_ce['CO_ENTIDADE'].astype('int')
Em seguida, filtramos somente as escolas do censo que possuiem nota no Ideb.
escolas_ce_Ideb = escolas_ce.merge(IDEB_geral, how = 'inner', left_on='CO_ENTIDADE', right_on='COD_ESCOLA')
escolas_ce_Ideb.drop(columns=['NOTA_IDEB', 'CO_UF', 'COD_ESCOLA', 'CO_ENTIDADE'], inplace=True)
escolas_ce_Ideb.info()
Repetimos o mesmo prosseno nos outros conjuntos.
turmas_ce['CO_ENTIDADE'] = turmas_ce['CO_ENTIDADE'].astype('int')
turmas_ce_Ideb = turmas_ce.merge(IDEB_geral, how = 'inner', left_on='CO_ENTIDADE', right_on='COD_ESCOLA')
turmas_ce_Ideb.drop(columns=['NOTA_IDEB', 'CO_UF', 'COD_ESCOLA', 'CO_ENTIDADE'], inplace=True)
turmas_ce_Ideb.info()
matriculas_ce['CO_ENTIDADE'] = matriculas_ce['CO_ENTIDADE'].astype('int')
matriculas_ce_Ideb = matriculas_ce.merge(IDEB_geral, how = 'inner', left_on='CO_ENTIDADE', right_on='COD_ESCOLA')
matriculas_ce_Ideb.drop(columns=['NOTA_IDEB', 'CO_UF', 'COD_ESCOLA', 'CO_ENTIDADE'], inplace=True)
matriculas_ce_Ideb.info()
docentes_ce['CO_ENTIDADE'] = docentes_ce['CO_ENTIDADE'].astype('int')
docentes_ce_Ideb = docentes_ce.merge(IDEB_geral, how = 'inner', left_on='CO_ENTIDADE', right_on='COD_ESCOLA')
docentes_ce_Ideb.drop(columns=['NOTA_IDEB', 'CO_UF', 'COD_ESCOLA', 'CO_ENTIDADE'], inplace=True)
docentes_ce_Ideb.info()
A clusterização de atributos booleanos ou categóricos pode ser um pouco mais difícil por conta da gama de modelo que lida com esse tipo de dados diretamente, e o problema complica mais ainda quando temos um conjunto de dados que possui esses dois tipo. A melhor maneira que encontramos para lidar com a diferença no tipo dos atributos foi utilizar a técnica do One Hot Enconded, codificando cada categoria em dados booleanos.
escolas_ce_ohe = pd.get_dummies(escolas_ce_Ideb)
turmas_ce_ohe = pd.get_dummies(turmas_ce_Ideb)
matriculas_ce_ohe = pd.get_dummies(matriculas_ce_Ideb)
docentes_ce_ohe = pd.get_dummies(docentes_ce_Ideb)
Agora realizaremos uma análise exploratória para verificarmos algumas tendências dos dados para tentarmos assimilar de alguma maneira o que pode acontecer com os agrupamentos e como eles podem ficar sepadados depois.
# Função auxiliar para ajudar na numeração das plotagens
n_fig = 0
def nfig():
global n_fig
n_fig+=1
return str('Fig.'+str(n_fig)+': ')
escolas['TP_DEPENDENCIA'].value_counts()
figura = plt.figure(figsize=(15,5))
figura.add_subplot(1,2,1)
plt.title('Dependência Administrativa')
plt.ylabel('Frequência Absoluta')
plt.xticks(rotation=30)
plt.tick_params(labelsize=10)
plt.bar(['Municipal', 'Privada', 'Estadual', 'Federal'], escolas['TP_DEPENDENCIA'].value_counts(), color='green', alpha=0.7, edgecolor='black');
figura.add_subplot(1,2,2)
plt.title('Localização diferenciada')
plt.ylabel('Frequência Absoluta')
plt.tick_params(labelsize=10)
plt.xticks(rotation=30)
plt.bar(['Não se aplica', 'Assentamento', 'Terra indígena', 'Quilombos'], escolas['TP_LOCALIZACAO_DIFERENCIADA'].value_counts(), color='red', alpha=0.7, edgecolor='black');
figura.suptitle(nfig()+'Bar plot', fontsize=20)
plt.show();
figura = plt.figure(figsize=(20,5))
figura.add_subplot(1,3,1)
plt.title('Tipo de mediação didático-pedagógica')
plt.ylabel('Frequência Absoluta')
plt.xticks(rotation=30)
plt.tick_params(labelsize=10)
plt.bar(['Presencial', 'Semipresencial', 'EAD'], turmas['TP_MEDIACAO_DIDATICO_PEDAGO'].value_counts(), color='green', alpha=0.7, edgecolor='black');
figura.add_subplot(1,3,2)
plt.title('Nº de dias de atividade por semana')
plt.xticks(rotation=30)
plt.ylabel('Frequência Absoluta')
plt.tick_params(labelsize=10)
plt.bar(['5', '1', '6', '2', '3', '4', '7'], turmas['NU_DIAS_ATIVIDADE'].value_counts(), color='red', alpha=0.7, edgecolor='black')
figura.add_subplot(1,3,3)
plt.title('Tipo de atendimento a turma')
plt.xticks(rotation=30)
plt.ylabel('Frequência Absoluta')
plt.tick_params(labelsize=10)
plt.bar(['Exclusivo Escolarização', 'AEE', 'AC', 'ACE'], turmas['TP_TIPO_ATENDIMENTO_TURMA'].value_counts(), color='blue', alpha=0.7, edgecolor='black')
figura.suptitle(nfig()+'Bar plot', fontsize=20)
plt.show()
figura = plt.figure(figsize=(20,5))
figura.add_subplot(1,2,1)
plt.title('Tipo de zona residencial')
plt.xticks(rotation=30)
plt.ylabel('Frequência Absoluta')
plt.tick_params(labelsize=10)
plt.bar(['Urbana', 'Rural'], matriculas['TP_ZONA_RESIDENCIAL'].value_counts(), color='green', alpha=0.7, edgecolor='black');
figura.add_subplot(1,2,2)
plt.title('Raça')
plt.xticks(rotation=30)
plt.ylabel('Frequência Absoluta')
plt.tick_params(labelsize=10)
plt.bar(['Parda', 'Não declarada','Branca','Preta', 'Indígena', 'Amarela'], matriculas['TP_COR_RACA'].value_counts(), color='blue', alpha=0.7, edgecolor='black')
figura.suptitle(nfig()+'Bar plot', fontsize=20)
plt.show()
figura = plt.figure(figsize=(15,15))
figura.add_subplot(2,2,1)
plt.title('Escolaridade')
plt.xticks(rotation=30)
plt.ylabel('Frequência Absoluta')
plt.bar(['Superior', 'Médio', 'Fundamental', 'Fundamental incompleto'], docentes['TP_ESCOLARIDADE'].value_counts(), color='green', alpha=0.7, edgecolor='black');
figura.add_subplot(2,2,2)
plt.title('Tipo de instituição de ensino')
plt.xticks(rotation=30)
plt.ylabel('Frequência Absoluta')
plt.bar(['Pública', 'Privada'], docentes['TP_TIPO_IES_1'].value_counts(), color='red', alpha=0.7, edgecolor='black')
figura.add_subplot(2,3,4)
plt.title('Especialização')
plt.xticks(rotation=30)
plt.ylabel('Frequência Absoluta')
plt.tick_params(labelsize=8)
plt.bar(['Não', 'Sim'], docentes['IN_ESPECIALIZACAO'].value_counts(), color='blue', alpha=0.7, edgecolor='black')
figura.add_subplot(2,3,5)
plt.title('Mestrado')
plt.xticks(rotation=30)
plt.ylabel('Frequência Absoluta')
plt.tick_params(labelsize=8)
plt.bar(['Não', 'Sim'], docentes['IN_MESTRADO'].value_counts(), color='blue', alpha=0.7, edgecolor='black')
figura.add_subplot(2,3,6)
plt.title('Doutorado')
plt.xticks(rotation=30)
plt.ylabel('Frequência Absoluta')
plt.tick_params(labelsize=8)
plt.bar(['Não', 'Sim'], docentes['IN_DOUTORADO'].value_counts(), color='blue', alpha=0.7, edgecolor='black')
figura.suptitle(nfig()+'Bar plot', fontsize=20)
plt.show()
plt.figure(figsize=(10,8))
plt.scatter(y=ideb_municipios['latitude'], x=ideb_municipios['longitude'],
c=ideb_municipios['NOTA_IDEB'],
cmap='RdYlGn')
plt.colorbar()
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.title(nfig()+'IDEB por município', fontsize=20);
Podemos ver que as notas médias mais estáveis se concentram mais ao sul e suldestes, enquanto no nordeste e na parte superior do norte se concentram nas notas mais baixas, com exceções no Ceará. A partir do gráfico, poderemos comparar a dispersão das notas com a dispersão dos clusters futuramente, caso encontremos diferenças nas médias de notas nos clusters.
ideb_ce = ideb_municipios.filter(items=['latitude','longitude','NOTA_IDEB']).where(ideb_municipios.UF=='CE')
plt.figure(figsize=(10,8))
plt.scatter(y=ideb_ce['latitude'], x=ideb_ce['longitude'],
c=ideb_ce['NOTA_IDEB'],
cmap='RdYlGn', s=325)
plt.colorbar()
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.title(nfig()+'IDEB dos municípios do Ceará', fontsize=20);
Analisando o gráfico de dispersão de notas do Ceará, podemos ver que mesmo dentro do estado ainda há um certa discrepânica nas notas, onde existem notas baixas próximas as notas altas.
Avaliaremos o desempenho do Kmeans e do Kmodes. O Kmodes, menos conhecido, ao invés de calcular a distância numérica entre os clusters, avalia as k modas e a similaridade de entre as amostras para realizar o agrupamento. Para a avaliação dos resultados, utilizaremos o método do cotovelo e da silhueta. No método do cotovelo, avaliaremos o custo de agrupamento para cada número de clusters, e no método da silhueta avaliaremos o tamanho e distância entre os clusters.
cost = []
fig, ax = plt.subplots(2, 2, figsize=(15,10))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Escolas (Kmeans)')
for i in [2, 3, 4, 5]:
km = KMeans(n_clusters=i, max_iter=100)
km.fit(escolas_ce_ohe)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-1][mod])
visualizer.fit(escolas_ce_ohe)
cost.append(km.inertia_)
plt.plot([2, 3, 4, 5], cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Escolas (Kmeans)', fontsize=16)
plt.show()
#
#
#Segunda parte
fig, ax = plt.subplots(2, 2, figsize=(15,10))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Escolas (Kmeans)')
for i in [6, 7, 8 , 9]:
km = KMeans(n_clusters=i, max_iter=100)
km.fit(escolas_ce_ohe)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-3][mod])
visualizer.fit(escolas_ce_ohe)
cost.append(km.inertia_)
plt.plot([2, 3, 4, 5, 6, 7, 8 , 9], cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Escolas (Kmeans)', fontsize=16)
plt.show()
Pelo gráfico do cotovelo, não conseguimos definir bem um bom K para utilizarmos. Conferindo a silhueta, vemos que o seu score também não é tão bom mas, reparando bem, podemos ver que o maior score de silhueta se dá em k=7, onde o tamanho de cada clusters também está mais equilibrado.
cost = []
fig, ax = plt.subplots(2, 2, figsize=(15,10))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Turmas (Kmeans)')
for i in [2, 3, 4, 5]:
km = KMeans(n_clusters=i, max_iter=100)
km.fit(turmas_ce_ohe)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-1][mod])
visualizer.fit(turmas_ce_ohe)
cost.append(km.inertia_)
plt.plot([2,3,4,5],cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Turmas (Kmeans)', fontsize=16)
plt.show()
#
#
#Segunda parte
fig, ax = plt.subplots(2, 2, figsize=(15,10))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Turmas (Kmeans)')
for i in [6, 7, 8 , 9]:
km = KMeans(n_clusters=i, max_iter=100)
km.fit(turmas_ce_ohe)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-3][mod])
visualizer.fit(turmas_ce_ohe)
cost.append(km.inertia_)
plt.plot([2,3,4,5,6,7,8,9],cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Turmas (Kmeans)', fontsize=16)
plt.show()
Em turmas, o método do cotovelo também não nos fornece um bom indicativo para a escolha de um bom K. Partindo para o método da silhueta, encontramos bons resultados de score, mas não encontramos uma boa proporção entre os cluster, acabamos escolhendo k=7.
cost = []
fig, ax = plt.subplots(2, 2, figsize=(15,10))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Docentes (Kmeans)')
for i in [2, 3, 4, 5]:
km = KMeans(n_clusters=i, max_iter=100)
km.fit(docentes_ce_ohe)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-1][mod])
visualizer.fit(docentes_ce_ohe)
cost.append(km.inertia_)
plt.plot([2, 3, 4, 5], cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Docentes (Kmeans)', fontsize=16)
plt.show()
Pelo método do cotovelo, podemos ver que o k=3 seria um boa escolha mas, olhando para a sua respectiva forma de silhueta, podemos ver que a sua proporção não é tão boa. Olhando para o score e proporção dos clusters, escolhemos o k=5.
pca = PCA(n_components=3)
principalComponents_matriculas = pca.fit_transform(matriculas_ce_Ideb)
principal_breast_Df = pd.DataFrame(data = principalComponents_matriculas
, columns = ['PC1', 'PC2', 'PC3'])
print('Explained variation per principal component: {}'.format(pca.explained_variance_ratio_))
principal_breast_Df.info()
df_matriculas_percent = principal_breast_Df.sample(frac=0.2)
df_matriculas_percent.info()
cost = []
fig, ax = plt.subplots(2, 2, figsize=(15,10))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Matrículas (Kmeans)')
for i in [2, 3, 4, 5]:
km = KMeans(n_clusters=i, max_iter=100)
km.fit(df_matriculas_percent)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-1][mod])
visualizer.fit(df_matriculas_percent)
cost.append(km.inertia_)
plt.plot([2, 3, 4, 5], cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Matrícula (Kmeans)', fontsize=16)
plt.show()
Em matrículas, tivemos problemas no processamento dos dados, onde o tempo execução dos modelos estavam inviabilizando a continuidade do trabalho, com execuções durando mais de 8hrs e sem terem terminado ainda, por exemplo. Para driblar esse problema, aplicamos o PCA e reduzimos o conjunto de dados para 20% do tamanho original. Ainda assim, a execução demorou pouco mais de 2hrs, como obtivemos um resultado interessante com k=3, escolhemos esse número.
Acreditamos que o Kmodes apresentaria os melhores resultados por conta da sua natureza ser voltada para variáveis categóricas e booleanas, porem o Kmeans apresentou resultados melhores. A seguir, pode-ser ver que os resultados das silhuetas do Kmodes não são tão coesos como no Kmeans.
cost = []
fig, ax = plt.subplots(2, 2, figsize=(15,20))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Docentes (Kmodes)', fontsize=16)
for i in [2, 3, 4, 5]:
km = KModes(n_clusters=i, max_iter=50, init = "Cao", n_init = 1, verbose=0)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-1][mod])
visualizer.fit(docentes_ce_ohe)
cost.append(km.cost_)
plt.plot([2, 3, 4, 5], cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Docentes (Kmodes)', fontsize=16)
plt.show()
cost = []
fig, ax = plt.subplots(2, 2, figsize=(15,20))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Escolas (Kmodes)', fontsize=16)
for i in [2, 3, 4, 5]:
km = KModes(n_clusters=i, max_iter=50, init = "Cao", n_init = 1, verbose=0)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-1][mod])
visualizer.fit(escolas_ce_ohe)
cost.append(km.cost_)
plt.plot([2, 3, 4, 5], cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Escolas (Kmodes)', fontsize=16)
plt.show()
#
#
# Segunda parte
fig, ax = plt.subplots(2, 2, figsize=(15,20))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Escolas (Kmodes)', fontsize=16)
for i in [6, 7, 8 , 9]:
km = KModes(n_clusters=i, max_iter=50, init = "Cao", n_init = 1, verbose=0)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-3][mod])
visualizer.fit(escolas_ce_ohe)
cost.append(km.cost_)
plt.plot([2, 3, 4, 5, 6, 7, 8 , 9], cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Escolas (Kmodes)', fontsize=16)
plt.show()
cost = []
fig, ax = plt.subplots(2, 2, figsize=(15,20))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Turmas (Kmodes)')
for i in [2, 3, 4, 5]:
km = KModes(n_clusters=i, max_iter=50, init = "Cao", n_init = 1, verbose=0)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-1][mod])
visualizer.fit(turmas_ce_ohe)
cost.append(km.cost_)
plt.plot([2, 3, 4, 5], cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Turmas (Kmodes)', fontsize=16)
plt.show()
#
#
# Segunda parte
fig, ax = plt.subplots(2, 2, figsize=(15,20))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Turmas (Kmodes)', fontsize=16)
for i in [6, 7, 8 , 9]:
km = KModes(n_clusters=i, max_iter=50, init = "Cao", n_init = 1, verbose=0)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-3][mod])
visualizer.fit(turmas_ce_ohe)
cost.append(km.cost_)
plt.plot([2, 3, 4, 5, 6, 7, 8 , 9], cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Turmas (Kmodes)', fontsize=16)
plt.show()
Dado os testes realizados, próximos para utilizar cada modelo com os k's escolhidos.
km = KMeans(n_clusters=7, max_iter=100)
escolas_cluster = km.fit_predict(escolas_ce_ohe)
Em seguida, anexamos as classificações no conjunto de dados.
escolas_ce_Ideb['CLUSTER'] = escolas_cluster
Para facilitar a continuidade do trabalho depois de encerrarmos a sessão do colab, salvamos os resuldados para utizarmos prosteriormente sem precisarmos executar todo o pipe novamente.
escolas_ce_Ideb.to_csv(general_path+'escolas_ce_cluster', encoding='ISO-8859-1', index=False)
Os procedimentos nos outros conjuntos são análogos.
turmas_ce_Ideb = turmas_ce.merge(IDEB_geral, how = 'inner', left_on='CO_ENTIDADE', right_on='COD_ESCOLA')
turmas_ce_Ideb.drop(columns=['NOTA_IDEB', 'CO_UF', 'COD_ESCOLA'], inplace=True)
km = KMeans(n_clusters=7, max_iter=100)
turmas_cluster = km.fit_predict(turmas_ce_ohe)
turmas_ce_Ideb['CLUSTER'] = turmas_cluster
turmas_ce_Ideb.to_csv(general_path+'turmas_ce_cluster', encoding='ISO-8859-1', index=False)
docentes_ce_Ideb = docentes_ce.merge(IDEB_geral, how = 'inner', left_on='CO_ENTIDADE', right_on='COD_ESCOLA')
docentes_ce_Ideb.drop(columns=['NOTA_IDEB', 'CO_UF', 'COD_ESCOLA'], inplace=True)
km = KMeans(n_clusters=5, max_iter=100)
docentes_cluster = km.fit_predict(docentes_ce_ohe)
docentes_ce_Ideb['CLUSTER'] = docentes_cluster
docentes_ce_Ideb.to_csv(general_path+'docentes_ce_cluster', encoding='ISO-8859-1', index=False)
matriculas_ce_Ideb = matriculas_ce.merge(IDEB_geral, how = 'inner', left_on='CO_ENTIDADE', right_on='COD_ESCOLA')
matriculas_ce_Ideb.drop(columns=['NOTA_IDEB', 'CO_UF', 'COD_ESCOLA'], inplace=True)
matriculas_ce_Ideb_frac = matriculas_ce_Ideb.sample(frac=0.2)
matriculas_copy = matriculas_ce_Ideb_frac
pca = PCA(n_components=3)
pca.fit(matriculas_ce_Ideb)
principalComponents_matriculas = pca.transform(matriculas_copy)
matriculas_ce_pca = pd.DataFrame(data = principalComponents_matriculas
, columns = ['PC1', 'PC2', 'PC3'])
km = KMeans(n_clusters=3, max_iter=100)
matriculas_cluster = km.fit_predict(matriculas_ce_pca)
matriculas_copy['CLUSTER'] = matriculas_cluster
matriculas_copy.to_csv(general_path+'matriculas_ce_cluster', encoding='ISO-8859-1', index=False)
Agora, executaremos a contagem da incidência de cada categoria nos conjuntos para reduzirmos a dimensionalidade.
Primeiro, importamos os resultados da primeira clusterização.
matriculas_cluster = pd.read_csv(general_path+'matriculas_ce_cluster', encoding='ISO-8859-1', sep=',')
matriculas_cluster.head()
Em seguida, criamos um dataframe com somente os codigos das escolas.
co_escolas = matriculas_cluster['CO_ENTIDADE'].unique()
df_matriculas_cluster = pd.DataFrame(data=co_escolas,columns=['CO_ENTIDADE'])
E então criamos uma nova coluna para cada cluster. A ideia é criar as k colunas, uma para cada cluster, para depois atualizarmos o seu valor com o número de indicências de cada cluster para a referida escola.
df_matriculas_cluster[0] = 0
df_matriculas_cluster[1] = 0
df_matriculas_cluster[2] = 0
df_matriculas_cluster.info()
Em seguida, em um laço, para cada escola de ensino válida realizamos a contagem dos clusters das matrículas e associamos cada valor da contagem a uma coluna do referido cluster criado anteriormente.
for i in range(len(co_escolas)):
tpl = matriculas_cluster[matriculas_cluster['CO_ENTIDADE']==co_escolas[i]]['CLUSTER'].value_counts()
df_matriculas_cluster.loc[df_matriculas_cluster['CO_ENTIDADE']==co_escolas[i],tpl.index] = tpl.values[0]
No caso de matrículas, cada escola ficou com somete 1 tipo de cluster.
df_matriculas_cluster.head()
Os mesmos passos foram repeditos para os outros conjuntos.
docentes_cluster = pd.read_csv(general_path+'docentes_ce_cluster', encoding='ISO-8859-1', sep=',')
docentes_cluster.head()
docentes_cluster['CLUSTER'].value_counts()
df_docentes_cluster = pd.DataFrame(data=co_escolas,columns=['CO_ENTIDADE'])
df_docentes_cluster[0] = 0
df_docentes_cluster[1] = 0
df_docentes_cluster[2] = 0
df_docentes_cluster[3] = 0
df_docentes_cluster[4] = 0
for i in range(len(co_escolas)):
tpl = docentes_cluster[docentes_cluster['CO_ENTIDADE']==co_escolas[i]]['CLUSTER'].value_counts()
df_docentes_cluster.loc[df_docentes_cluster['CO_ENTIDADE']==co_escolas[i],tpl.index] = tpl.values
df_docentes_cluster.head()
turmas_cluster = pd.read_csv(general_path+'turmas_ce_cluster', encoding='ISO-8859-1', sep=',')
turmas_cluster.head()
turmas_cluster['CLUSTER'].value_counts()
df_turmas_cluster = pd.DataFrame(data=co_escolas,columns=['CO_ENTIDADE'])
df_turmas_cluster[0] = 0
df_turmas_cluster[1] = 0
df_turmas_cluster[2] = 0
df_turmas_cluster[3] = 0
df_turmas_cluster[4] = 0
df_turmas_cluster[5] = 0
df_turmas_cluster[6] = 0
df_turmas_cluster.head()
for i in range(len(co_escolas)):
tpl = turmas_cluster[turmas_cluster['CO_ENTIDADE']==co_escolas[i]]['CLUSTER'].value_counts()
df_turmas_cluster.loc[df_turmas_cluster['CO_ENTIDADE']==co_escolas[i],tpl.index] = tpl.values
df_turmas_cluster.head()
df_escolas_cluster = pd.read_csv(general_path+'escolas_ce_cluster', encoding='ISO-8859-1', sep=',')
df_escolas_cluster.head()
df_escolas_cluster['CO_ENTIDADE'] = co_escolas
df_turmas_cluster = df_turmas_cluster.rename(columns = {0: '0_TU',
1: '1_TU',
2: '2_TU',
3: '3_TU',
4: '4_TU',
5: '5_TU',
6: '6_TU'}, inplace = False)
df_matriculas_cluster = df_matriculas_cluster.rename(columns = {0: '0_MA',
1: '1_MA',
2: '2_MA',
3: '3_MA',
4: '4_MA',
5: '5_MA',
6: '6_MA'}, inplace = False)
df_docentes_cluster = df_docentes_cluster.rename(columns = {0: '0_DO',
1: '1_DO',
2: '2_DO',
3: '3_DO',
4: '4_DO'}, inplace = False)
df_escolas_cluster = df_escolas_cluster.rename(columns = {'CLUSTER':'CLT_ES'}, inplace = False)
df_escolas_cluster = df_escolas_cluster[['CO_ENTIDADE','CLT_ES']]
E essa é a conjunto final que conseguimos:
dfs = [df_escolas_cluster,df_turmas_cluster, df_docentes_cluster, df_matriculas_cluster]
df_final = reduce(lambda left,right: pd.merge(left,right,on='CO_ENTIDADE'), dfs)
df_final = df_final.drop(['CO_ENTIDADE'], axis=1)
df_final.head()
df_final.info()
Para a criação do modelo final, por conta do tempo, utilizamos somente o Kmeans.
cost=[]
fig, ax = plt.subplots(4, 2, figsize=(15,20))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Escolas(Conjunto Final) (Kmeans)', fontsize=16)
for i in [2, 3, 4, 5, 6, 7, 8 , 9]:
km = KMeans(n_clusters=i, max_iter=100)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-1][mod])
visualizer.fit(df_final)
cost.append(km.inertia_)
plt.plot([2, 3, 4, 5, 6, 7, 8 , 9], cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Escolas (Conjunto Final) (Kmodes)', fontsize=16)
plt.show()
Também tentamos aplicar o modelo com os dados padronizados, mas a eficiência caiu drasticamente.
scaler = StandardScaler()
df_final_std = scaler.fit_transform(df_final)
cost=[]
fig, ax = plt.subplots(4, 2, figsize=(15,20))
fig.suptitle(nfig()+'Score e Forma da Silhueta para Escolas(Conjunto Final) (Kmeans)', fontsize=16)
for i in [2, 3, 4, 5, 6, 7, 8 , 9]:
km = KMeans(n_clusters=i, max_iter=100)
q, mod = divmod(i, 2)
visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q-1][mod])
visualizer.fit(df_final_std)
cost.append(km.inertia_)
plt.plot([2, 3, 4, 5, 6, 7, 8 , 9], cost, 'bx-')
plt.xlabel('No. of clusters')
plt.ylabel('Cost')
plt.title(nfig()+'Método do Cotovelo para Escolas (Conjunto Final) (Kmodes)', fontsize=16)
plt.show()
Escolhemos o Kmeans sem a padronização dos dados com k=3. Pelo método do cotovelo, apesar de não vermos um limiar bem destacável, podemos ver que há uma queda acentuada no k=3, e vendo as formas da silhueta, podemos que o valor mais razoável também é o k=3.
km = KMeans(n_clusters=3, max_iter=100)
cluster_final = km.fit_predict(df_final)
df_final['CLUSTER'] = cluster_final
df_final.to_csv(general_path+'cluster_final', encoding='ISO-8859-1', index=False)
df_final['CO_ENTIDADE'] = co_escolas
Agora, dado que temos os dados clusterizados, vamos verificar se há alguma diferença nas notas de cada cluster calculando a média de cada um.
escolas_ce_Ideb['CO_ENTIDADE'] = co_escolas
escolas_ce_Ideb['CLUSTER'] = cluster_final
escolas_ce_Ideb['NOTA_IDEB'] = IDEB_geral['NOTA_IDEB']
n0 = escolas_ce_Ideb[escolas_ce_Ideb['CLUSTER'] == 0]['NOTA_IDEB'].mean()
n1 = escolas_ce_Ideb[escolas_ce_Ideb['CLUSTER'] == 1]['NOTA_IDEB'].mean()
n2 = escolas_ce_Ideb[escolas_ce_Ideb['CLUSTER'] == 2]['NOTA_IDEB'].mean()
plt.title(nfig()+'Média das notas por cluster', fontsize=20)
plt.bar(['Cluster 0', 'Cluster 1', 'Cluster 2'], [n0,n1,n2], alpha=0.7, color=['red','green','blue'])
plt.tick_params(labelsize=16)
plt.show;
Ao final dos testes, podemos perceber que, apesar de separadamente termos conseguido bons resultados na clusterização de cada conjunto, ao juntarmos os clusters não conseguimos uma boa caracterização das escolas para sua cluesterização.
Lembrando que tomamos alguns viéses para reduzir o tamanho do conjunto de dados, pois ao final, acabamos clusterizando apenas as escolas de ensino fundamental do ceará e com apenas 20% dos matrículas.
Durante o trabalho, percebemos a complexidade da tarefa que era de clusterizar as escolas de forma a separá-las em grupos de forma encontrar diferença em suas notas, pois estamos avaliamos diferentes tipo de dados e levando-os a um único grupo que seria seu cluster final. O conjunto escolas possui dados referentes a estrutura física da mesma; matrículas e turmas possuem dados referente a caracterização de seus alunos e docentes possuem mais dados relacionados a caracterização de sua formação. Ao tentarmos clusterizá-los através de um algorimo, estamos perdendo várias avaliações subjetivas dos recursos humanos e aspectos culturais da organização das escolas, que fazem total diferença no desempenho das mesmas.
Como não conseguimos aplicar a clusterização para as escolas de todo o Brasil, não sabemos se o estratégia utilizada é realmente ineficaz para o probblema ou se o problema não realmente a solução que buscavamos.