Clusterização das Escolas Públicas Brasileiras para Análise de Grupos de Desempenho

Carlos Eduardo Cassimiro da Silva
Pedro Cercelino Matos

1. Problema

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

Conjunto de dados

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

>>>Mudanças que aconteceram no projeto<<<

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á

Resumo da estratégia adotada

• 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.

2. Pré-processamento

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.

Bibliotecas utilizadas

In [ ]:
# É necessária a instalação no Kmodes para utilização no Colab
!pip install kmodes
Collecting kmodes
  Downloading kmodes-0.11.0-py2.py3-none-any.whl (18 kB)
Requirement already satisfied: scipy>=0.13.3 in /usr/local/lib/python3.7/dist-packages (from kmodes) (1.4.1)
Requirement already satisfied: joblib>=0.11 in /usr/local/lib/python3.7/dist-packages (from kmodes) (1.0.1)
Requirement already satisfied: scikit-learn>=0.22.0 in /usr/local/lib/python3.7/dist-packages (from kmodes) (0.22.2.post1)
Requirement already satisfied: numpy>=1.10.4 in /usr/local/lib/python3.7/dist-packages (from kmodes) (1.19.5)
Installing collected packages: kmodes
Successfully installed kmodes-0.11.0
In [ ]:
#Instalação necessária para a utilização dataframe dask no Colab
!python -m pip install dask[dataframe] --upgrade
Requirement already satisfied: dask[dataframe] in /usr/local/lib/python3.7/dist-packages (2.12.0)
Requirement already satisfied: numpy>=1.13.0 in /usr/local/lib/python3.7/dist-packages (from dask[dataframe]) (1.19.5)
Collecting partd>=0.3.10
  Downloading partd-1.2.0-py3-none-any.whl (19 kB)
Requirement already satisfied: pandas>=0.23.0 in /usr/local/lib/python3.7/dist-packages (from dask[dataframe]) (1.1.5)
Collecting fsspec>=0.6.0
  Downloading fsspec-2021.7.0-py3-none-any.whl (118 kB)
     |████████████████████████████████| 118 kB 17.1 MB/s 
Requirement already satisfied: toolz>=0.7.3 in /usr/local/lib/python3.7/dist-packages (from dask[dataframe]) (0.11.1)
Requirement already satisfied: python-dateutil>=2.7.3 in /usr/local/lib/python3.7/dist-packages (from pandas>=0.23.0->dask[dataframe]) (2.8.2)
Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/dist-packages (from pandas>=0.23.0->dask[dataframe]) (2018.9)
Collecting locket
  Downloading locket-0.2.1-py2.py3-none-any.whl (4.1 kB)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.7.3->pandas>=0.23.0->dask[dataframe]) (1.15.0)
Installing collected packages: locket, partd, fsspec
Successfully installed fsspec-2021.7.0 locket-0.2.1 partd-1.2.0
In [ ]:
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
In [ ]:
from google.colab import drive
drive.mount('/content/drive')
Mounted at /content/drive
In [ ]:
#Estilização dos plots
plt.style.use("ggplot")
In [ ]:
#Caminho no drive para importações
general_path = "/content/drive/MyDrive/Colab Notebooks/"

2.1 Escolas

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.

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

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

In [ ]:
escolas.info(memory_usage="deep")
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 228521 entries, 0 to 228520
Data columns (total 24 columns):
 #   Column                          Non-Null Count   Dtype   
---  ------                          --------------   -----   
 0   CO_ENTIDADE                     228521 non-null  category
 1   CO_UF                           228521 non-null  category
 2   TP_DEPENDENCIA                  228521 non-null  category
 3   TP_LOCALIZACAO                  228521 non-null  category
 4   TP_LOCALIZACAO_DIFERENCIADA     182468 non-null  category
 5   IN_VINCULO_SECRETARIA_EDUCACAO  140242 non-null  category
 6   IN_VINCULO_SEGURANCA_PUBLICA    140242 non-null  category
 7   TP_OCUPACAO_PREDIO_ESCOLAR      177167 non-null  category
 8   IN_LOCAL_FUNC_SOCIOEDUCATIVO    182468 non-null  category
 9   IN_LOCAL_FUNC_UNID_PRISIONAL    182468 non-null  category
 10  IN_LOCAL_FUNC_PRISIONAL_SOCIO   182468 non-null  category
 11  IN_LOCAL_FUNC_GALPAO            182468 non-null  category
 12  IN_ENERGIA_INEXISTENTE          182468 non-null  category
 13  IN_BIBLIOTECA                   182468 non-null  category
 14  IN_LABORATORIO_CIENCIAS         182468 non-null  category
 15  IN_LABORATORIO_INFORMATICA      182468 non-null  category
 16  IN_ACESSIBILIDADE_INEXISTENTE   182468 non-null  category
 17  IN_EQUIP_MULTIMIDIA             182468 non-null  category
 18  IN_COMP_PORTATIL_ALUNO          182468 non-null  category
 19  IN_TABLET_ALUNO                 182468 non-null  category
 20  IN_INTERNET                     182468 non-null  category
 21  IN_MATERIAL_PED_CIENTIFICO      182468 non-null  category
 22  IN_EXAME_SELECAO                182468 non-null  category
 23  TP_PROPOSTA_PEDAGOGICA          182468 non-null  category
dtypes: category(24)
memory usage: 30.1 MB

Com a instrução a baixo podemos ver que existem dados faltantes nesse conjunto. Então prosseguimos com o tratamentos desses casos.

In [ ]:
escolas.isna().sum()
Out[ ]:
CO_ENTIDADE                           0
CO_UF                                 0
TP_DEPENDENCIA                        0
TP_LOCALIZACAO                        0
TP_LOCALIZACAO_DIFERENCIADA       46053
IN_VINCULO_SECRETARIA_EDUCACAO    88279
IN_VINCULO_SEGURANCA_PUBLICA      88279
TP_OCUPACAO_PREDIO_ESCOLAR        51354
IN_LOCAL_FUNC_SOCIOEDUCATIVO      46053
IN_LOCAL_FUNC_UNID_PRISIONAL      46053
IN_LOCAL_FUNC_PRISIONAL_SOCIO     46053
IN_LOCAL_FUNC_GALPAO              46053
IN_ENERGIA_INEXISTENTE            46053
IN_BIBLIOTECA                     46053
IN_LABORATORIO_CIENCIAS           46053
IN_LABORATORIO_INFORMATICA        46053
IN_ACESSIBILIDADE_INEXISTENTE     46053
IN_EQUIP_MULTIMIDIA               46053
IN_COMP_PORTATIL_ALUNO            46053
IN_TABLET_ALUNO                   46053
IN_INTERNET                       46053
IN_MATERIAL_PED_CIENTIFICO        46053
IN_EXAME_SELECAO                  46053
TP_PROPOSTA_PEDAGOGICA            46053
dtype: int64

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.

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

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

In [ ]:
escolas.isna().sum()
Out[ ]:
CO_ENTIDADE                           0
CO_UF                                 0
TP_DEPENDENCIA                        0
TP_LOCALIZACAO                        0
TP_LOCALIZACAO_DIFERENCIADA       46053
IN_VINCULO_SECRETARIA_EDUCACAO        0
IN_VINCULO_SEGURANCA_PUBLICA          0
TP_OCUPACAO_PREDIO_ESCOLAR            0
IN_LOCAL_FUNC_SOCIOEDUCATIVO      46053
IN_LOCAL_FUNC_UNID_PRISIONAL      46053
IN_LOCAL_FUNC_PRISIONAL_SOCIO     46053
IN_LOCAL_FUNC_GALPAO              46053
IN_ENERGIA_INEXISTENTE            46053
IN_BIBLIOTECA                     46053
IN_LABORATORIO_CIENCIAS           46053
IN_LABORATORIO_INFORMATICA        46053
IN_ACESSIBILIDADE_INEXISTENTE     46053
IN_EQUIP_MULTIMIDIA               46053
IN_COMP_PORTATIL_ALUNO            46053
IN_TABLET_ALUNO                   46053
IN_INTERNET                       46053
IN_MATERIAL_PED_CIENTIFICO        46053
IN_EXAME_SELECAO                  46053
TP_PROPOSTA_PEDAGOGICA            46053
dtype: int64
In [ ]:
escolas = escolas.dropna()

2.2 Turmas

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'.

In [ ]:
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)
In [ ]:
turmas.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 2432438 entries, 0 to 2432437
Data columns (total 5 columns):
 #   Column                       Dtype   
---  ------                       -----   
 0   TP_MEDIACAO_DIDATICO_PEDAGO  category
 1   NU_DIAS_ATIVIDADE            category
 2   TP_TIPO_ATENDIMENTO_TURMA    category
 3   CO_ENTIDADE                  category
 4   CO_UF                        category
dtypes: category(5)
memory usage: 25.0 MB
In [ ]:
turmas.isna().sum()
Out[ ]:
TP_MEDIACAO_DIDATICO_PEDAGO       0
NU_DIAS_ATIVIDADE              8591
TP_TIPO_ATENDIMENTO_TURMA         0
CO_ENTIDADE                       0
CO_UF                             0
dtype: int64
In [ ]:
turmas = turmas.dropna()

2.3 Matricula

In [ ]:
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'}
In [ ]:
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)
In [ ]:
matriculas.info(memory_usage="deep")
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 15304589 entries, 0 to 15304588
Data columns (total 13 columns):
 #   Column                       Dtype   
---  ------                       -----   
 0   TP_COR_RACA                  category
 1   TP_ZONA_RESIDENCIAL          category
 2   TP_LOCAL_RESID_DIFERENCIADA  category
 3   IN_NECESSIDADE_ESPECIAL      category
 4   IN_TRANSPORTE_PUBLICO        category
 5   IN_TRANSP_BICICLETA          category
 6   IN_TRANSP_MICRO_ONIBUS       category
 7   IN_TRANSP_ONIBUS             category
 8   IN_TRANSP_TR_ANIMAL          category
 9   IN_TRANSP_VANS_KOMBI         category
 10  IN_TRANSP_OUTRO_VEICULO      category
 11  CO_ENTIDADE                  category
 12  CO_UF                        category
dtypes: category(13)
memory usage: 239.9 MB
In [ ]:
matriculas.isna().sum()
Out[ ]:
TP_COR_RACA                           0
TP_ZONA_RESIDENCIAL                  18
TP_LOCAL_RESID_DIFERENCIADA    10345300
IN_NECESSIDADE_ESPECIAL               0
IN_TRANSPORTE_PUBLICO             58792
IN_TRANSP_BICICLETA            12145386
IN_TRANSP_MICRO_ONIBUS         12145386
IN_TRANSP_ONIBUS               12145386
IN_TRANSP_TR_ANIMAL            12145386
IN_TRANSP_VANS_KOMBI           12145386
IN_TRANSP_OUTRO_VEICULO        12145386
CO_ENTIDADE                           0
CO_UF                                 0
dtype: int64
In [ ]:
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'})
In [ ]:
matriculas.isna().sum()
Out[ ]:
TP_COR_RACA                     0
TP_ZONA_RESIDENCIAL            18
TP_LOCAL_RESID_DIFERENCIADA     0
IN_NECESSIDADE_ESPECIAL         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
CO_ENTIDADE                     0
CO_UF                           0
dtype: int64
In [ ]:
matriculas.dropna(inplace=True)

2.4 Docentes

In [ ]:
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)
In [ ]:
docentes.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3044057 entries, 0 to 3044056
Data columns (total 9 columns):
 #   Column                        Dtype   
---  ------                        -----   
 0   TP_ESCOLARIDADE               category
 1   TP_TIPO_IES_1                 category
 2   IN_COMPLEMENTACAO_PEDAGOGICA  category
 3   IN_ESPECIALIZACAO             category
 4   IN_MESTRADO                   category
 5   IN_DOUTORADO                  category
 6   IN_POS_NENHUM                 category
 7   CO_ENTIDADE                   category
 8   CO_UF                         category
dtypes: category(9)
memory usage: 37.8 MB
In [ ]:
docentes.isna().sum()
Out[ ]:
TP_ESCOLARIDADE                       0
TP_TIPO_IES_1                   1042540
IN_COMPLEMENTACAO_PEDAGOGICA          0
IN_ESPECIALIZACAO                558340
IN_MESTRADO                      558340
IN_DOUTORADO                     558340
IN_POS_NENHUM                    558340
CO_ENTIDADE                           0
CO_UF                                 0
dtype: int64
In [ ]:
docentes = docentes.fillna({'TP_TIPO_IES_1':'2',                  
                            'IN_ESPECIALIZACAO':'0',
                            'IN_MESTRADO':'0',
                            'IN_DOUTORADO':'0',
                            'IN_POS_NENHUM':'0'
                           })
In [ ]:
docentes.isna().sum()
Out[ ]:
TP_ESCOLARIDADE                 0
TP_TIPO_IES_1                   0
IN_COMPLEMENTACAO_PEDAGOGICA    0
IN_ESPECIALIZACAO               0
IN_MESTRADO                     0
IN_DOUTORADO                    0
IN_POS_NENHUM                   0
CO_ENTIDADE                     0
CO_UF                           0
dtype: int64

2.5 Notas do Ideb e geolocalização do municípios

2.5.1 Ideb por município

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.

In [ ]:
ideb = pd.read_csv(general_path + 'ideb_2019.csv', sep=';', na_values='-')
ideb.isna().sum()
Out[ ]:
UF               0
COD_MUNIC        0
NOME_MUNIC       0
REDE             0
NOTA_IDEB     2278
dtype: int64
In [ ]:
ideb = ideb.dropna()
ideb.head()
Out[ ]:
UF COD_MUNIC NOME_MUNIC REDE NOTA_IDEB
0 RO 1100015 Alta Floresta D'Oeste Estadual 5,8
1 RO 1100015 Alta Floresta D'Oeste Municipal 4,7
2 RO 1100015 Alta Floresta D'Oeste Pública 5,7
3 RO 1100023 Ariquemes Estadual 5,3
4 RO 1100023 Ariquemes Municipal 5,1

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.

In [ ]:
cod_notas = ideb.filter(items=['UF','COD_MUNIC','NOTA_IDEB']).where(ideb.REDE=='Pública')
cod_notas = cod_notas.dropna()

2.5.2 Municipios

In [ ]:
municipios = pd.read_csv(general_path + 'municipios.csv', usecols=['codigo_ibge','latitude','longitude'])
In [ ]:
municipios.head()
Out[ ]:
codigo_ibge latitude longitude
0 5200050 -16.75730 -49.4412
1 3100104 -18.48310 -47.3916
2 5200100 -16.19700 -48.7057
3 3100203 -19.15510 -45.4444
4 1500107 -1.72183 -48.8788

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.

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

In [ ]:
ideb_municipios['NOTA_IDEB'] = ideb_municipios['NOTA_IDEB'].str.replace(',','.')
ideb_municipios['NOTA_IDEB'] = ideb_municipios['NOTA_IDEB'].astype('float')

2.6 Seleção dos dados do Ceará

2.6.1 Notas do Ceará por município e por escola

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á.

In [ ]:
escolas_ce = escolas[escolas['CO_UF']=='23']
escolas_ce.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 7608 entries, 60621 to 87931
Data columns (total 24 columns):
 #   Column                          Non-Null Count  Dtype   
---  ------                          --------------  -----   
 0   CO_ENTIDADE                     7608 non-null   category
 1   CO_UF                           7608 non-null   category
 2   TP_DEPENDENCIA                  7608 non-null   category
 3   TP_LOCALIZACAO                  7608 non-null   category
 4   TP_LOCALIZACAO_DIFERENCIADA     7608 non-null   category
 5   IN_VINCULO_SECRETARIA_EDUCACAO  7608 non-null   category
 6   IN_VINCULO_SEGURANCA_PUBLICA    7608 non-null   category
 7   TP_OCUPACAO_PREDIO_ESCOLAR      7608 non-null   category
 8   IN_LOCAL_FUNC_SOCIOEDUCATIVO    7608 non-null   category
 9   IN_LOCAL_FUNC_UNID_PRISIONAL    7608 non-null   category
 10  IN_LOCAL_FUNC_PRISIONAL_SOCIO   7608 non-null   category
 11  IN_LOCAL_FUNC_GALPAO            7608 non-null   category
 12  IN_ENERGIA_INEXISTENTE          7608 non-null   category
 13  IN_BIBLIOTECA                   7608 non-null   category
 14  IN_LABORATORIO_CIENCIAS         7608 non-null   category
 15  IN_LABORATORIO_INFORMATICA      7608 non-null   category
 16  IN_ACESSIBILIDADE_INEXISTENTE   7608 non-null   category
 17  IN_EQUIP_MULTIMIDIA             7608 non-null   category
 18  IN_COMP_PORTATIL_ALUNO          7608 non-null   category
 19  IN_TABLET_ALUNO                 7608 non-null   category
 20  IN_INTERNET                     7608 non-null   category
 21  IN_MATERIAL_PED_CIENTIFICO      7608 non-null   category
 22  IN_EXAME_SELECAO                7608 non-null   category
 23  TP_PROPOSTA_PEDAGOGICA          7608 non-null   category
dtypes: category(24)
memory usage: 12.0 MB

2.6.2 Turmas do Ceará

In [ ]:
turmas_ce = turmas[turmas.CO_ENTIDADE.isin(escolas_ce['CO_ENTIDADE'])]
turmas_ce.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 112918 entries, 62 to 2432349
Data columns (total 5 columns):
 #   Column                       Non-Null Count   Dtype   
---  ------                       --------------   -----   
 0   TP_MEDIACAO_DIDATICO_PEDAGO  112918 non-null  category
 1   NU_DIAS_ATIVIDADE            112918 non-null  category
 2   TP_TIPO_ATENDIMENTO_TURMA    112918 non-null  category
 3   CO_ENTIDADE                  112918 non-null  category
 4   CO_UF                        112918 non-null  category
dtypes: category(5)
memory usage: 8.1 MB

2.6.3 Matrículas do Ceará

In [ ]:
matriculas_ce = matriculas[matriculas.CO_ENTIDADE.isin(escolas_ce['CO_ENTIDADE'])]
matriculas_ce.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 2522937 entries, 3925790 to 6448727
Data columns (total 13 columns):
 #   Column                       Dtype   
---  ------                       -----   
 0   TP_COR_RACA                  category
 1   TP_ZONA_RESIDENCIAL          category
 2   TP_LOCAL_RESID_DIFERENCIADA  category
 3   IN_NECESSIDADE_ESPECIAL      category
 4   IN_TRANSPORTE_PUBLICO        category
 5   IN_TRANSP_BICICLETA          category
 6   IN_TRANSP_MICRO_ONIBUS       category
 7   IN_TRANSP_ONIBUS             category
 8   IN_TRANSP_TR_ANIMAL          category
 9   IN_TRANSP_VANS_KOMBI         category
 10  IN_TRANSP_OUTRO_VEICULO      category
 11  CO_ENTIDADE                  category
 12  CO_UF                        category
dtypes: category(13)
memory usage: 60.7 MB

2.6.4 Docentes do Ceará

In [ ]:
docentes_ce = docentes[docentes.CO_ENTIDADE.isin(escolas_ce['CO_ENTIDADE'])]
docentes_ce.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 493212 entries, 13 to 3043953
Data columns (total 9 columns):
 #   Column                        Non-Null Count   Dtype   
---  ------                        --------------   -----   
 0   TP_ESCOLARIDADE               493212 non-null  category
 1   TP_TIPO_IES_1                 493212 non-null  category
 2   IN_COMPLEMENTACAO_PEDAGOGICA  493212 non-null  category
 3   IN_ESPECIALIZACAO             493212 non-null  category
 4   IN_MESTRADO                   493212 non-null  category
 5   IN_DOUTORADO                  493212 non-null  category
 6   IN_POS_NENHUM                 493212 non-null  category
 7   CO_ENTIDADE                   493212 non-null  category
 8   CO_UF                         493212 non-null  category
dtypes: category(9)
memory usage: 12.4 MB

2.7 Filtrando os conjuntos de dados para as escolas com as notas do ideb disponíveis

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.

In [ ]:
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á.

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

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

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

In [ ]:
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])
In [ ]:
IDEB_geral['NOTA_IDEB'] = nota
IDEB_geral = IDEB_geral.drop(['NOTA_IDEB_AI','NOTA_IDEB_AF','flag','UF_AF','UF_AI'], axis=1)

2.8 Seleção dos dados filtrados pelas escolas cearense com notas disponíveis no Ideb

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.

2.8.1 Escolas

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.

In [ ]:
escolas_ce['CO_ENTIDADE'] = escolas_ce['CO_ENTIDADE'].astype('int')
/usr/local/lib/python3.7/dist-packages/ipykernel_launcher.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  """Entry point for launching an IPython kernel.

Em seguida, filtramos somente as escolas do censo que possuiem nota no Ideb.

In [ ]:
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()
<class 'pandas.core.frame.DataFrame'>
Index: 0 entries
Data columns (total 22 columns):
 #   Column                          Non-Null Count  Dtype   
---  ------                          --------------  -----   
 0   TP_DEPENDENCIA                  0 non-null      category
 1   TP_LOCALIZACAO                  0 non-null      category
 2   TP_LOCALIZACAO_DIFERENCIADA     0 non-null      category
 3   IN_VINCULO_SECRETARIA_EDUCACAO  0 non-null      category
 4   IN_VINCULO_SEGURANCA_PUBLICA    0 non-null      category
 5   TP_OCUPACAO_PREDIO_ESCOLAR      0 non-null      category
 6   IN_LOCAL_FUNC_SOCIOEDUCATIVO    0 non-null      category
 7   IN_LOCAL_FUNC_UNID_PRISIONAL    0 non-null      category
 8   IN_LOCAL_FUNC_PRISIONAL_SOCIO   0 non-null      category
 9   IN_LOCAL_FUNC_GALPAO            0 non-null      category
 10  IN_ENERGIA_INEXISTENTE          0 non-null      category
 11  IN_BIBLIOTECA                   0 non-null      category
 12  IN_LABORATORIO_CIENCIAS         0 non-null      category
 13  IN_LABORATORIO_INFORMATICA      0 non-null      category
 14  IN_ACESSIBILIDADE_INEXISTENTE   0 non-null      category
 15  IN_EQUIP_MULTIMIDIA             0 non-null      category
 16  IN_COMP_PORTATIL_ALUNO          0 non-null      category
 17  IN_TABLET_ALUNO                 0 non-null      category
 18  IN_INTERNET                     0 non-null      category
 19  IN_MATERIAL_PED_CIENTIFICO      0 non-null      category
 20  IN_EXAME_SELECAO                0 non-null      category
 21  TP_PROPOSTA_PEDAGOGICA          0 non-null      category
dtypes: category(22)
memory usage: 1.7+ KB

2.8.2 Turmas

Repetimos o mesmo prosseno nos outros conjuntos.

In [ ]:
turmas_ce['CO_ENTIDADE'] = turmas_ce['CO_ENTIDADE'].astype('int')
/usr/local/lib/python3.7/dist-packages/ipykernel_launcher.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  """Entry point for launching an IPython kernel.
In [ ]:
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)
In [ ]:
turmas_ce_Ideb.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 63027 entries, 0 to 63026
Data columns (total 3 columns):
 #   Column                       Non-Null Count  Dtype   
---  ------                       --------------  -----   
 0   TP_MEDIACAO_DIDATICO_PEDAGO  63027 non-null  category
 1   NU_DIAS_ATIVIDADE            63027 non-null  category
 2   TP_TIPO_ATENDIMENTO_TURMA    63027 non-null  category
dtypes: category(3)
memory usage: 677.7 KB

2.8.3 Matrículas

In [ ]:
matriculas_ce['CO_ENTIDADE'] = matriculas_ce['CO_ENTIDADE'].astype('int')
/usr/local/lib/python3.7/dist-packages/ipykernel_launcher.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  """Entry point for launching an IPython kernel.
In [ ]:
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()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1405544 entries, 0 to 1405543
Data columns (total 11 columns):
 #   Column                       Non-Null Count    Dtype   
---  ------                       --------------    -----   
 0   TP_COR_RACA                  1405544 non-null  category
 1   TP_ZONA_RESIDENCIAL          1405544 non-null  category
 2   TP_LOCAL_RESID_DIFERENCIADA  1405544 non-null  category
 3   IN_NECESSIDADE_ESPECIAL      1405544 non-null  category
 4   IN_TRANSPORTE_PUBLICO        1405544 non-null  category
 5   IN_TRANSP_BICICLETA          1405544 non-null  category
 6   IN_TRANSP_MICRO_ONIBUS       1405544 non-null  category
 7   IN_TRANSP_ONIBUS             1405544 non-null  category
 8   IN_TRANSP_TR_ANIMAL          1405544 non-null  category
 9   IN_TRANSP_VANS_KOMBI         1405544 non-null  category
 10  IN_TRANSP_OUTRO_VEICULO      1405544 non-null  category
dtypes: category(11)
memory usage: 25.5 MB

2.8.4 Docentes

In [ ]:
docentes_ce['CO_ENTIDADE'] = docentes_ce['CO_ENTIDADE'].astype('int')
/usr/local/lib/python3.7/dist-packages/ipykernel_launcher.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  """Entry point for launching an IPython kernel.
In [ ]:
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()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 251920 entries, 0 to 251919
Data columns (total 7 columns):
 #   Column                        Non-Null Count   Dtype   
---  ------                        --------------   -----   
 0   TP_ESCOLARIDADE               251920 non-null  category
 1   TP_TIPO_IES_1                 251920 non-null  category
 2   IN_COMPLEMENTACAO_PEDAGOGICA  251920 non-null  category
 3   IN_ESPECIALIZACAO             251920 non-null  category
 4   IN_MESTRADO                   251920 non-null  category
 5   IN_DOUTORADO                  251920 non-null  category
 6   IN_POS_NENHUM                 251920 non-null  category
dtypes: category(7)
memory usage: 3.6 MB

2.9 Criação do One Hot Encoded dos conjuntos

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.

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

3. Análise Exploratória

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.

In [ ]:
# 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)+': ')

3.1 Análise da frequência das variáveis

In [ ]:
escolas['TP_DEPENDENCIA'].value_counts()
Out[ ]:
3    109054
4     42226
2     30490
1       698
Name: TP_DEPENDENCIA, dtype: int64

3.1.1 Base de escolas

In [ ]:
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();
  • Dependência Administrativa: Nesse gráfico podemos observar que a grande maioria das escolas possui dependência administrativa associada a esfera municipal, seguido pela privada e estadual.
  • Localização diferenciada: O atributo de localização diferenciada diz respeito a localização não habitual em que a escola pode se situar, como era de se esperar, são poucas as escolas que se encontram nessas localidades.

3.1.2 Base de turmas

In [ ]:
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()
  • Tipo de mediação didático-pedagógica: Este gráfico nos mostra que a modalidade presencial é a principal forma de mediação das aulas, isso se explica pelo fato de que no Brasil os regimes semipresenciais e EAD são mais encontrados no ensino superior.
  • Nº de dias de atividade: Este atributo nos mostra a quantidade de dias em que são realizadas atividades escolares (aulas), aqui é clara a predominância de 5 dias semanais nas escolas consultadas pelo censo, fato que se deve a cultura de se ter aulas de "Segunda à Sexta" e folgas nos fins de semana.
  • Tipo de atendimento a turma: Os tipo de atendimento de turma, podem ser: Exclusivo escolarização, atividade complementar e escolarização, atividade complementar e atendimento educacional especial. A grande maioria das turmas são do tipo exclusivo escolarização, como é esperado.

3.1.3 Base de matrículas

In [ ]:
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()
  • Tipo de zona residencial: O tipo de zona residencial se divide em rural e urbana e o maior contingente de aluno matriculados reside na área urbana.
  • Raça: A distribuição das raças obtidas no censo mostra uma predominância de alunos das raças branca e parda, muitos alunos preferiram não declarar essa informação ou não preencheram na matrícula.

3.1.4 Base de docentes

In [ ]:
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()
  • Escolaridade: Como é de se esperar, a maior parte dos docentes presentes no censo possui ensino superior completo, embora uma fatia considerável possua apenas ensino médio ou abaixo.
  • Tipo de instituição de ensino: Este histograma nos mostra que há uma certa proximidade entra a quantidade de docentes formados em faculdades públicas e privadas, embora ainda haja mais docentes formados em escolas públicas.
  • Especialização, Mestrado e Doutorado: Estes 3 atributos tratam sobre a existência de especialização, mestrado e doutorado no currículo do docente. Percebe-se que quanto maior o grau de formação, menor é quantidade de professores que possuem esta formação.

3.2 Análise de dispersão das notas

3.2.1 Análise de dispersão das notas no Brasil

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

3.2.2 Análise da dispersão das notas no estado do Ceará

In [ ]:
ideb_ce = ideb_municipios.filter(items=['latitude','longitude','NOTA_IDEB']).where(ideb_municipios.UF=='CE')
In [ ]:
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.

4. Criação e avaliação dos modelos

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.

4.1 Primeira Parte - Clusterização para redução de dimensionalidade

4.1.1 Kmeans com One Hot Encoded

4.1.1.1 Escolas

In [ ]:
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_)
In [ ]:
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()
In [ ]:
#
#
#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_)
In [ ]:
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.

4.1.1.2 Turmas

In [ ]:
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_)
In [ ]:
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()

baixados.png

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

baixados (1).png

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.

4.1.1.3 Docentes

In [ ]:
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_) 
In [ ]:
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.

4.1.1.4 Matrículas

In [ ]:
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_))
Explained variation per principal component: [1.00000000e+00 1.78747356e-10 3.65031358e-11]
In [ ]:
principal_breast_Df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1405544 entries, 0 to 1405543
Data columns (total 3 columns):
 #   Column  Non-Null Count    Dtype  
---  ------  --------------    -----  
 0   PC1     1405544 non-null  float64
 1   PC2     1405544 non-null  float64
 2   PC3     1405544 non-null  float64
dtypes: float64(3)
memory usage: 32.2 MB
In [ ]:
df_matriculas_percent = principal_breast_Df.sample(frac=0.2)
In [ ]:
df_matriculas_percent.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 281109 entries, 555543 to 596233
Data columns (total 3 columns):
 #   Column  Non-Null Count   Dtype  
---  ------  --------------   -----  
 0   PC1     281109 non-null  float64
 1   PC2     281109 non-null  float64
 2   PC3     281109 non-null  float64
dtypes: float64(3)
memory usage: 8.6 MB
In [ ]:
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_)
In [ ]:
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()

image_2021-08-26_01-56-20.png

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.

4.1.2 Kmodes com One Hot Encoded

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.

4.1.2.1 Docentes
In [ ]:
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_)
In [ ]:
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()
4.1.2.2 Escolas
In [ ]:
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_)
In [ ]:
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()
In [ ]:
#
#
# 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_)
In [ ]:
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()
4.1.2.3 Matrículas
4.1.2.4 Turmas
In [ ]:
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_)

baixados (2).png

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

baixados (3).png

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

baixados (4).png

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

baixados (5).png

4.1.3 Escolha dos modelos

4.1.3.1 Escolas

Dado os testes realizados, próximos para utilizar cada modelo com os k's escolhidos.

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

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

In [ ]:
escolas_ce_Ideb.to_csv(general_path+'escolas_ce_cluster', encoding='ISO-8859-1', index=False)
4.1.3.2 Turmas

Os procedimentos nos outros conjuntos são análogos.

In [ ]:
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)
In [ ]:
km = KMeans(n_clusters=7, max_iter=100)
turmas_cluster = km.fit_predict(turmas_ce_ohe)
In [ ]:
turmas_ce_Ideb['CLUSTER'] = turmas_cluster
In [ ]:
turmas_ce_Ideb.to_csv(general_path+'turmas_ce_cluster', encoding='ISO-8859-1', index=False)
4.1.3.3 Docentes
In [ ]:
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)
In [ ]:
km = KMeans(n_clusters=5, max_iter=100)
docentes_cluster = km.fit_predict(docentes_ce_ohe)
In [ ]:
docentes_ce_Ideb['CLUSTER'] = docentes_cluster
docentes_ce_Ideb.to_csv(general_path+'docentes_ce_cluster', encoding='ISO-8859-1', index=False)
4.1.3.4 Matrículas
In [ ]:
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)
In [ ]:
matriculas_ce_Ideb_frac = matriculas_ce_Ideb.sample(frac=0.2)
matriculas_copy = matriculas_ce_Ideb_frac
In [ ]:
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'])
In [ ]:
km = KMeans(n_clusters=3, max_iter=100)
matriculas_cluster = km.fit_predict(matriculas_ce_pca)
In [ ]:
matriculas_copy['CLUSTER'] = matriculas_cluster
matriculas_copy.to_csv(general_path+'matriculas_ce_cluster', encoding='ISO-8859-1', index=False)

4.2 Segunda Parte - Criação do modelo para clusterização das escolas

4.2.1 Pré-processamento dos resultados da primeira parte

Agora, executaremos a contagem da incidência de cada categoria nos conjuntos para reduzirmos a dimensionalidade.

4.2.1.1 Matrículas

Primeiro, importamos os resultados da primeira clusterização.

In [ ]:
matriculas_cluster = pd.read_csv(general_path+'matriculas_ce_cluster', encoding='ISO-8859-1', sep=',')
matriculas_cluster.head()
Out[ ]:
TP_COR_RACA TP_ZONA_RESIDENCIAL TP_LOCAL_RESID_DIFERENCIADA IN_NECESSIDADE_ESPECIAL IN_TRANSPORTE_PUBLICO IN_TRANSP_BICICLETA IN_TRANSP_MICRO_ONIBUS IN_TRANSP_ONIBUS IN_TRANSP_TR_ANIMAL IN_TRANSP_VANS_KOMBI IN_TRANSP_OUTRO_VEICULO CO_ENTIDADE CLUSTER
0 3 1 0 0 0 0 0 0 0 0 0 23124920 0
1 3 1 0 0 0 0 0 0 0 0 0 23067756 0
2 3 1 0 0 0 0 0 0 0 0 0 23222662 1
3 0 1 0 0 0 0 0 0 0 0 0 23223219 1
4 3 2 0 0 1 0 0 0 0 1 0 23010568 0

Em seguida, criamos um dataframe com somente os codigos das escolas.

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

In [ ]:
df_matriculas_cluster[0] = 0
df_matriculas_cluster[1] = 0
df_matriculas_cluster[2] = 0
df_matriculas_cluster.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3021 entries, 0 to 3020
Data columns (total 4 columns):
 #   Column       Non-Null Count  Dtype
---  ------       --------------  -----
 0   CO_ENTIDADE  3021 non-null   int64
 1   0            3021 non-null   int64
 2   1            3021 non-null   int64
 3   2            3021 non-null   int64
dtypes: int64(4)
memory usage: 94.5 KB

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.

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

In [ ]:
df_matriculas_cluster.head()
Out[ ]:
CO_ENTIDADE 0 1 2
0 23124920 85 0 0
1 23067756 156 0 0
2 23222662 0 123 0
3 23223219 0 129 0
4 23010568 58 0 0
4.2.1.2 Docentes

Os mesmos passos foram repeditos para os outros conjuntos.

In [ ]:
docentes_cluster = pd.read_csv(general_path+'docentes_ce_cluster', encoding='ISO-8859-1', sep=',')
docentes_cluster.head()
Out[ ]:
TP_ESCOLARIDADE TP_TIPO_IES_1 IN_COMPLEMENTACAO_PEDAGOGICA IN_ESPECIALIZACAO IN_MESTRADO IN_DOUTORADO IN_POS_NENHUM CO_ENTIDADE CLUSTER
0 4 1 0 1 0 0 0 23164247 0
1 4 1 0 1 0 0 0 23164247 0
2 4 1 0 1 0 0 0 23164247 0
3 4 1 0 1 0 0 0 23164247 0
4 4 1 0 1 0 0 0 23164247 0
In [ ]:
docentes_cluster['CLUSTER'].value_counts()
Out[ ]:
0    72436
3    71160
2    58197
1    31816
4    18311
Name: CLUSTER, dtype: int64
In [ ]:
df_docentes_cluster = pd.DataFrame(data=co_escolas,columns=['CO_ENTIDADE'])
In [ ]:
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
In [ ]:
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
In [ ]:
df_docentes_cluster.head()
Out[ ]:
CO_ENTIDADE 0 1 2 3 4
0 23124920 7 3 31 30 6
1 23067756 18 16 17 24 4
2 23222662 86 6 39 19 0
3 23223219 57 44 11 59 0
4 23010568 15 4 13 10 3
4.2.1.3 Turmas
In [ ]:
turmas_cluster = pd.read_csv(general_path+'turmas_ce_cluster', encoding='ISO-8859-1', sep=',')
turmas_cluster.head()
Out[ ]:
TP_MEDIACAO_DIDATICO_PEDAGO NU_DIAS_ATIVIDADE TP_TIPO_ATENDIMENTO_TURMA CO_ENTIDADE CLUSTER
0 1 5 1 23165510 0
1 1 5 1 23165510 0
2 1 5 1 23165510 0
3 1 5 1 23165510 0
4 1 5 1 23165510 0
In [ ]:
turmas_cluster['CLUSTER'].value_counts()
Out[ ]:
0    45303
2     9296
3     2393
5     2335
4     1644
6     1057
1      999
Name: CLUSTER, dtype: int64
In [ ]:
df_turmas_cluster = pd.DataFrame(data=co_escolas,columns=['CO_ENTIDADE'])
In [ ]:
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()
Out[ ]:
CO_ENTIDADE 0 1 2 3 4 5 6
0 23124920 0 0 0 0 0 0 0
1 23067756 0 0 0 0 0 0 0
2 23222662 0 0 0 0 0 0 0
3 23223219 0 0 0 0 0 0 0
4 23010568 0 0 0 0 0 0 0
In [ ]:
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
In [ ]:
df_turmas_cluster.head()
Out[ ]:
CO_ENTIDADE 0 1 2 3 4 5 6
0 23124920 17 4 0 0 0 0 0
1 23067756 26 0 4 0 2 0 0
2 23222662 11 0 6 2 0 0 3
3 23223219 15 0 10 3 0 0 0
4 23010568 11 0 4 0 0 0 0
4.2.1.4 Escolas
In [ ]:
df_escolas_cluster = pd.read_csv(general_path+'escolas_ce_cluster', encoding='ISO-8859-1', sep=',')
df_escolas_cluster.head()
Out[ ]:
TP_DEPENDENCIA TP_LOCALIZACAO TP_LOCALIZACAO_DIFERENCIADA IN_VINCULO_SECRETARIA_EDUCACAO IN_VINCULO_SEGURANCA_PUBLICA TP_OCUPACAO_PREDIO_ESCOLAR IN_LOCAL_FUNC_SOCIOEDUCATIVO IN_LOCAL_FUNC_UNID_PRISIONAL IN_LOCAL_FUNC_PRISIONAL_SOCIO IN_LOCAL_FUNC_GALPAO IN_ENERGIA_INEXISTENTE IN_BIBLIOTECA IN_LABORATORIO_CIENCIAS IN_LABORATORIO_INFORMATICA IN_ACESSIBILIDADE_INEXISTENTE IN_EQUIP_MULTIMIDIA IN_COMP_PORTATIL_ALUNO IN_TABLET_ALUNO IN_INTERNET IN_MATERIAL_PED_CIENTIFICO IN_EXAME_SELECAO TP_PROPOSTA_PEDAGOGICA CLUSTER
0 3 1 0 1 0 1 0 0 0 0 0 1 0 1 0 1 1 0 1 0 0 1 5
1 3 1 0 1 0 1 0 0 0 0 0 0 0 1 0 1 0 0 1 0 0 1 4
2 3 2 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 1 9 0 1 4
3 3 1 0 1 0 1 0 0 0 0 0 0 0 1 0 1 0 0 1 9 0 1 4
4 3 1 0 1 0 1 0 0 0 0 0 0 0 0 0 1 0 0 1 1 0 1 1
In [ ]:
df_escolas_cluster['CO_ENTIDADE'] = co_escolas
4.2.1.5 Juntando os conjuntos
In [ ]:
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:

In [ ]:
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()
Out[ ]:
CLT_ES 0_TU 1_TU 2_TU 3_TU 4_TU 5_TU 6_TU 0_DO 1_DO 2_DO 3_DO 4_DO 0_MA 1_MA 2_MA
0 5 17 4 0 0 0 0 0 7 3 31 30 6 85 0 0
1 4 26 0 4 0 2 0 0 18 16 17 24 4 156 0 0
2 4 11 0 6 2 0 0 3 86 6 39 19 0 0 123 0
3 4 15 0 10 3 0 0 0 57 44 11 59 0 0 129 0
4 1 11 0 4 0 0 0 0 15 4 13 10 3 58 0 0
In [ ]:
df_final.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 3021 entries, 0 to 3020
Data columns (total 16 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   CLT_ES  3021 non-null   int64
 1   0_TU    3021 non-null   int64
 2   1_TU    3021 non-null   int64
 3   2_TU    3021 non-null   int64
 4   3_TU    3021 non-null   int64
 5   4_TU    3021 non-null   int64
 6   5_TU    3021 non-null   int64
 7   6_TU    3021 non-null   int64
 8   0_DO    3021 non-null   int64
 9   1_DO    3021 non-null   int64
 10  2_DO    3021 non-null   int64
 11  3_DO    3021 non-null   int64
 12  4_DO    3021 non-null   int64
 13  0_MA    3021 non-null   int64
 14  1_MA    3021 non-null   int64
 15  2_MA    3021 non-null   int64
dtypes: int64(16)
memory usage: 401.2 KB

4.2.2 Criação do modelo

4.2.2.1 Kmeans

Para a criação do modelo final, por conta do tempo, utilizamos somente o Kmeans.

In [ ]:
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_)
In [ ]:
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()
4.2.2.2 Kmeans com Padronização no dados

Também tentamos aplicar o modelo com os dados padronizados, mas a eficiência caiu drasticamente.

In [ ]:
scaler = StandardScaler()
df_final_std = scaler.fit_transform(df_final)
In [ ]:
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_)

índice.png

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

índice2.png

4.2.2.3 Escolha do modelo

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.

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

5. Avaliação dos Resultados

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.

In [ ]:
escolas_ce_Ideb['CO_ENTIDADE'] = co_escolas
In [ ]:
escolas_ce_Ideb['CLUSTER'] = cluster_final
In [ ]:
escolas_ce_Ideb['NOTA_IDEB'] = IDEB_geral['NOTA_IDEB']
In [ ]:
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()
In [ ]:
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;

índice3.png

6. Conclusão

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.