Quantcast
Channel: Adventures in the Land of Binaries
Viewing all articles
Browse latest Browse all 34

Desvendando os Microdados do ENEM 2010

$
0
0

Estava olhando o conteúdo do Portal Brasileiro de Dados Abertos (vide link) e me deparei com os Microdados do ENEM[1] fornecidos pelo INEP [2]. Fiquei interessado nessas informações e resolvi baixar os arquivos da última edição disponibilizada do exame: 2010.

Achei muito organizado o material fornecido pela instituição ao público, e então resolvi destrinchar os dados usando uma ferramenta mais apropriada: o SGBD de código aberto mais avançado do mundo, o PostgreSQL[3].

O principal conteúdo no arquivo ZIP é o "DADOS_ENEM_2010.txt", um arquivo de texto com 4.626.094 linhas e míseros 4,4 GB...! Cada linha representa um inscrito no exame, e os campos dividem-se em seções de variáveis como CONTROLE DO INSCRITO, CONTROLE DA ESCOLA, CIDADE DA PROVA, PROVA OBJETIVA e PROVA DE REDAÇÃO.

A lista de informações de cada inscrito é extensa, por isso resolvi extrair apenas algun campos de maior interessante. Fazemos isso usando as instruções a seguir no Linux:


cat DADOS_ENEM_2010.txt | cut -b 1-12,21-179,533-572,951,997-1006 > enem10a.txt

O arquivo resultante "enem10a.txt" fica bem menor, com cerca de 980 MB... Os dados nele ainda não estão perfeitos: existem valores em branco em colunas como código e nome de município e nas notas. Para o código do município, usamos o SED com a seguinte instrução para inserir zeros no lugar de vazio, o que será tratado posteriormente:


sed 's/^\(.\{12\}\)\s\{7\}/\10000000/' enem10a.txt > enem10b.txt

Agora temos outro arquivo de texto com 980 MB, pronto para ser carregado no SGBD. É preciso então criar o banco de dados "enem". Podemos fazer isso usando o comando createdb.

Uma vez conectado ao banco recém-criado, criaremos a tabela "enem10" usando a seguinte instrução SQL:


CREATE TABLE enem10 (
num_inscr int8,
cod_munic int,
nom_munic varchar,
sig_uf char(2),
idc_cn int2,
idc_ch int2,
idc_lc int2,
idc_mt int2,
not_cn numeric(6,2),
not_ch numeric(6,2),
not_lc numeric(6,2),
not_mt numeric(6,2),
idc_rd char(1),
not_rd numeric(6,2)
);

Veja que começamos a normalizar os dados, principalmente pela especificação de restrições de tipos de dados para cada uma das colunas. Além disso, tabela e cada uma de duas colunas serão melhor documentadas se dotadas de descrições. Isso pode ser feito através dos comandos de criação de comentários abaixo:


COMMENT ON TABLE enem10 IS 'Microdados do Exame Nacional do Ensino Médio 2010';
COMMENT ON COLUMN enem10.num_inscr IS 'Número de inscrição no ENEM 2010';
COMMENT ON COLUMN enem10.cod_munic IS 'Código do Município em que o inscrito mora';
COMMENT ON COLUMN enem10.nom_munic IS 'Nome do município em que o inscrito mora ';
COMMENT ON COLUMN enem10.sig_uf IS 'Código da Unidade da Federação do inscrito no Enem';
COMMENT ON COLUMN enem10.idc_cn IS 'Presença à prova objetiva de Ciências da Natureza';
COMMENT ON COLUMN enem10.idc_ch IS 'Presença à prova objetiva de Ciências Humanas';
COMMENT ON COLUMN enem10.idc_lc IS 'Presença à prova objetiva de Linguagens e Códigos';
COMMENT ON COLUMN enem10.idc_mt IS 'Presença à prova objetiva de Matemática';
COMMENT ON COLUMN enem10.not_cn IS 'Nota da prova de Ciências da Natureza ';
COMMENT ON COLUMN enem10.not_ch IS 'Nota da prova de Ciências Humanas';
COMMENT ON COLUMN enem10.not_lc IS 'Nota da prova de Linguagens e Códigos';
COMMENT ON COLUMN enem10.not_mt IS 'Nota da prova de Matemática';
COMMENT ON COLUMN enem10.idc_rd IS 'Presença à redação';
COMMENT ON COLUMN enem10.not_rd IS 'Nota da prova de redação';

Para dar carga de maneira mais eficiente no PostgreSQL, além de um tuning básico, podemos utilizar a ferramenta pgloader [4]. Para isso, após instalar o pacote "pgloader", crie um arquivo de configurações de nome "pgloader.conf" com o seguinte conteúdo:


[pgsql]
base = enem
log_file = /tmp/pgloader.log
;log_min_messages = DEBUG
client_min_messages = WARNING
client_encoding = 'utf-8'
lc_messages = C
;pg_option_client_encoding = 'utf-8'
;pg_option_standard_conforming_strings = on
pg_option_work_mem = 512MB
copy_every = 10000
commit_every = 50000
null = " "
empty_string = ""
max_parallel_sections = 4

[enem10]
table = enem10
format = fixed
filename = enem10b.txt
columns = *
fixed_specs = num_inscr:0:12, cod_munic:12:7, nom_munic:19:150, sig_uf:169:2, idc_cn:171:1, idc_ch:172:1, idc_lc:173:1, idc_mt:174:1, not_cn:175:9, not_ch:184:9, not_lc:193:9, not_mt:202:9, idc_rd:211:1, not_rd:212:9

Esse arquivo especificará ao pgloader de que forma o arquivo de entrada "enem10b.txt" será lido para alimentar a tabela "enem10" no banco de dados. Para maior desempenho, é utilizado o comando COPY (e não INSERT INTO) a cada 10 mil linhas e as transações são efetivadas a cada 50 mil registros. O grande pulo do gato é a substituição de espaços em branco pelo valor nulo. Para iniciar a carga, basta executar pgloader nesse diretório.

Assim que o processo de carga finalizar, é preciso executar as instruções SQL abaixo para ajustes finais nos dados:


UPDATE enem10 SET nom_munic = trim(nom_munic);

UPDATE enem10 SET cod_munic = null WHERE cod_munic = 0;

Confira então se a tabela "enem10" possui as 4,6 milhões de linhas referentes a cada inscrito no exame de 2010. Eis um exemplo do conteúdo dessa tabela:


enem=# SELECT * FROM enem10 LIMIT 10;

num_inscr | cod_munic | nom_munic | sig_uf | idc_cn | idc_ch | idc_lc | idc_mt | not_cn | not_ch | not_lc | not_mt | idc_rd | not_rd
--------------+-----------+----------------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------+--------
200000382760 | 2105302 | IMPERATRIZ | MA | 1 | 1 | 1 | 1 | 545.40 | 598.20 | 589.00 | 502.10 | P | 550.00
200004076118 | 3202306 | GUACUI | ES | 1 | 1 | 1 | 1 | 434.10 | 505.80 | 439.00 | 495.30 | P | 475.00
200001265338 | 4309209 | GRAVATAI | RS | 1 | 1 | 1 | 1 | 491.10 | 598.40 | 528.70 | 322.50 | P | 450.00
200003174558 | 2211001 | TERESINA | PI | 1 | 1 | 1 | 1 | 499.90 | 521.10 | 479.10 | 411.50 | P | 875.00
200000277562 | 1501709 | BRAGANCA | PA | 1 | 1 | 1 | 1 | 479.70 | 583.90 | 447.20 | 398.60 | P | 575.00
200000104197 | 2800670 | BOQUIM | SE | 1 | 1 | 1 | 1 | 341.90 | 438.40 | 360.00 | 370.70 | P | 550.00
200004343078 | 3139409 | MANHUACU | MG | 1 | 1 | 1 | 1 | 499.00 | 610.90 | 452.00 | 513.80 | P | 450.00
200001011958 | 1502400 | CASTANHAL | PA | 1 | 1 | 1 | 1 | 597.80 | 599.30 | 517.80 | 586.60 | P | 700.00
200002382852 | 3106200 | BELO HORIZONTE | MG | 1 | 1 | 1 | 1 | 506.50 | 623.70 | 555.50 | 530.10 | P | 725.00
200000757106 | 1709500 | GURUPI | TO | 1 | 1 | 1 | 1 | 494.70 | 534.10 | 558.00 | 430.80 | P | 800.00
(10 rows)

A essa altura você já deve ter percebido que trabalhar com tamanho volume de dados no SGBD não é nada trivial. As consultas tendem a ser mais lentas a cada vez que uma varredura sequencial de tabela (i.e., full scan) é invocado. Para minimizar esse problema, podemos criar tabelas totalizadoras.

Para criar a tabela "nota_media_cidade", uma agregação da média e desvio padrão das notas e quantidades de inscritos para cada cidade (i.e., municípios com mais de 1.000 alunos), podemos executar a seguinte instrução SQL (note que desconsideramos os candidatos que não compareceram às provas):


SELECT nom_munic AS municipio, sig_uf AS uf,
avg(not_cn + not_ch + not_lc + not_mt + not_rd)::int AS media,
stddev(not_cn + not_ch + not_lc + not_mt + not_rd)::int AS desvio,
count(num_inscr) AS inscritos
INTO nota_media_cidade
FROM enem10
WHERE cod_munic IS NOT NULL
AND idc_cn = 1 AND idc_ch = 1 AND idc_lc = 1 AND idc_mt = 1 AND idc_rd = 'P'
GROUP BY nom_munic, sig_uf
HAVING count(num_inscr) > 1000
ORDER BY media DESC;

Outra análise interessante é criar a tabela "nota_media_estado", uma agregação da média, desvio padrão, mínima e máxima das notas e quantidades de inscritos para cada Unidade da Federação (i.e., estado brasileiro). Para isso, executamos a instrução SQL abaixo:


SELECT sig_uf AS uf,
avg(not_cn + not_ch + not_lc + not_mt + not_rd)::int AS media,
stddev(not_cn + not_ch + not_lc + not_mt + not_rd)::int AS desvio,
min(not_cn + not_ch + not_lc + not_mt + not_rd)::int AS minima,
max(not_cn + not_ch + not_lc + not_mt + not_rd)::int AS maxima,
count(num_inscr) AS inscritos
INTO nota_media_estado
FROM enem10
WHERE cod_munic IS NOT NULL
AND idc_cn = 1 AND idc_ch = 1 AND idc_lc = 1 AND idc_mt = 1 AND idc_rd = 'P'
GROUP BY sig_uf
ORDER BY 2 DESC;

Nas agregações anteriores, usamos a soma das notas dos candidatos nas 4 provas objetivas e na redação. Para obter o desempenho dos candidatos separadamente em cada uma das provas, podemos criar a tabela "nota_prova_geral" conforme instrução a seguir:


SELECT
min(not_cn) AS min_cn, max(not_cn) AS max_cn, avg(not_cn)::numeric(6,2) AS med_cn, stddev(not_cn)::numeric(6,2) AS dsv_cn,
min(not_ch) AS min_ch, max(not_ch) AS max_ch, avg(not_ch)::numeric(6,2) AS med_ch, stddev(not_ch)::numeric(6,2) AS dsv_ch,
min(not_lc) AS min_lc, max(not_lc) AS max_lc, avg(not_lc)::numeric(6,2) AS med_lc, stddev(not_lc)::numeric(6,2) AS dsv_lc,
min(not_mt) AS min_mt, max(not_mt) AS max_mt, avg(not_mt)::numeric(6,2) AS med_mt, stddev(not_mt)::numeric(6,2) AS dsv_mt,
min(not_rd) AS min_rd, max(not_rd) AS max_rd, avg(not_rd)::numeric(6,2) AS med_rd, stddev(not_rd)::numeric(6,2) AS dsv_rd
INTO nota_prova_geral
FROM enem10
WHERE idc_cn = 1 AND idc_ch = 1 AND idc_lc = 1 AND idc_mt = 1 AND idc_rd = 'P';

A fim de melhor entender os dados dessa tabela, podemos criar a visão "nota_prova" com esse comando SQL:


CREATE VIEW nota_prova AS
SELECT 'Ciências da Natureza' AS prova, min_cn AS min, max_cn AS max, med_cn AS media, dsv_cn AS desvio FROM nota_prova_geral
UNION
SELECT 'Ciências Humanas', min_ch, max_ch, med_ch, dsv_ch FROM nota_prova_geral
UNION
SELECT 'Linguagens e Códigos', min_lc, max_lc, med_lc, dsv_lc FROM nota_prova_geral
UNION
SELECT 'Matemática', min_mt, max_mt, med_mt, dsv_mt FROM nota_prova_geral
UNION
SELECT 'Redação', min_rd, max_rd, med_rd, dsv_rd FROM nota_prova_geral
ORDER BY 1;

Como resultado, teremos as seguintes estruturas no banco de dados "enem":


enem=# \d+
List of relations
Schema | Name | Type | Owner | Size | Description
--------+-------------------+-------+-------+------------+---------------------------------------------------
public | enem10 | table | hjort | 1452 MB | Microdados do Exame Nacional do Ensino Médio 2010
public | nota_media_cidade | table | hjort | 40 kB |
public | nota_media_estado | table | hjort | 8192 bytes |
public | nota_prova | view | hjort | 0 bytes |
public | nota_prova_geral | table | hjort | 16 kB |
(5 rows)

Pronto! Agora podemos começar a fazer as análises dos dados usando essas tabelas e visão. Eis alguns exemplos a seguir.

1. Quais são as cidades cujos alunos obtiveram as maiores médias?

municipioufmediadesvioinscritos
NITEROIRJ2.9354209.328
FLORIANOPOLISSC2.9353856.160
VALINHOSSP2.8924241.634
NOVA FRIBURGORJ2.8913762.129
SAO CAETANO DO SULSP2.8884022.092
BOTUCATUSP2.8874051.449
ITAJUBAMG2.8863612.647
JUIZ DE FORAMG2.88439911.411
ARAXAMG2.8833961.054
ARARAQUARASP2.8823903.214
CATANDUVASP2.8634081.115
PATOS DE MINASMG2.8583921.767
RIBEIRAO PRETOSP2.8564069.512
SAO CARLOSSP2.8554035.528
SAO JOSE DO RIO PRETOSP2.8524135.686
BARBACENAMG2.8493742.469
JAUSP2.8474091.233
POUSO ALEGREMG2.8463792.340
VICOSAMG2.8454102.713
UBERABAMG2.8434204.058
PORTO ALEGRERS2.84338924.059
UBAMG2.8413851.229
BELO HORIZONTEMG2.84042263.090
POCOS DE CALDASMG2.8393552.614
BLUMENAUSC2.8313641.485
PIRASSUNUNGASP2.8303961.317
CAMPINASSP2.83041713.638
VITORIAES2.8284398.475
VOLTA REDONDARJ2.8283733.934
RIO DE JANEIRORJ2.82640893.300
SAO JOSE DOS CAMPOSSP2.82540311.545
JABOTICABALSP2.8223811.077
DIVINOPOLISMG2.8223694.787
SANTA MARIARS2.8193907.601
SANTOSSP2.8174045.195
GUARATINGUETASP2.8173971.559
LAVRASMG2.8143882.446
JUNDIAISP2.8143925.232
CONSELHEIRO LAFAIETEMG2.8133822.429
PASSOSMG2.8133991.316
CURITIBAPR2.81239438.904
SAO JOAO DEL REIMG2.8123542.273
CRICIUMASC2.8103991.232
MARILIASP2.8074092.721
LAGOA SANTAMG2.8063911.038
MOGI MIRIMSP2.8064121.249
NOVA LIMAMG2.8064031.701
SAO JOSESC2.8053502.478
PIRACICABASP2.8043994.435
TAUBATESP2.8044073.209

(Vide "nota_media_cidade")

2. Em quais estados os alunos obtiveram as maiores médias?

ufmediadesviominimamaximainscritos
RJ2.7643921.5884.242220.383
SP2.7393921.4884.346522.098
MG2.7373871.5134.239368.835
SC2.7283631.6054.17960.242
PR2.7043701.5634.111159.061
RS2.6883631.5784.230199.630
DF2.6753831.5794.11140.292
GO2.6453931.5854.13477.254
CE2.6414081.5434.221146.687
ES2.6403921.5924.17475.985
PE2.6263801.4504.186155.738
PB2.5923711.5614.16068.475
MS2.5903711.6014.12769.354
RN2.5893741.4934.10564.952
PA2.5873681.4754.131120.739
MT2.5593561.5444.02976.255
AP2.5513351.6153.7829.413
PI2.5503901.5674.21763.441
RO2.5483461.5704.09433.387
AL2.5453661.6334.10730.301
BA2.5453661.4434.166264.654
MA2.5433741.5444.109123.806
RR2.5273501.6223.8738.921
TO2.5233741.5584.01419.491
AM2.5143451.5174.08480.490
SE2.4993701.5374.12633.099
AC2.4913441.6213.9959.887

(Vide "nota_media_estado")

3. Qual foi o desempenho geral dos alunos em cada uma das provas?

provaminmaxmediadesvio
Ciências da Natureza297.30844.70489.0579.96
Ciências Humanas265.10883.70550.1689.83
Linguagens e Códigos254.00810.10512.0077.28
Matemática313.40973.20506.90112.51
Redação250.001000.00596.44132.34

(Vide "nota_prova")

Bom, através dos dados pude constatar que o ensino médio brasileiro de qualidade (pelo menos no ano de 2010) está polarizado no eixo Sudeste-Sul do país. Parabéns a Niterói - RJ, Florianópolis - SC e Valinhos - SP, as cidades campeãs no ensino! Tomara que o Ministério da Educação tenha ideia de como homogeneizar (para melhor!) o ensino em todas as regiões do Brasil.

Com relação às notas, a mídia limita-se a divulgar apenas as mínimas e máximas (vide [5,6]). Entretanto, qualquer profissional com conhecimento estatístico sabe que o mais importante nesse tipo de análise são as médias e os desvios padrão. Um exemplo disso é na prova de matemática, onde ocorreu a maior nota das objetivas, porém também a maior diferença entre as notas dos candidatos. Ou seja, é uma disciplina cujo ensino precisa ser reforçado! :D

Referências


[1] Sobre o Enem - http://portal.inep.gov.br/web/enem/sobre-o-enem/
[2] Microdados do Enem - http://dados.gov.br/dataset/microdados-do-exame-nacional-do-ensino-medio-enem/
[3] PostgreSQL - http://www.postgresql.org/
[4] pgloader - http://pgfoundry.org/projects/pgloader/
[5] Confira as notas mínima e máxima das provas do Enem (Estadão) - http://www.estadao.com.br/noticias/vidae,confira-as-notas-minima-e-maxima-das-provas-do-enem,666251,0.htm
[6] Como calcular a nota do Enem? - http://vestibular.brasilescola.com/enem/como-calcular-nota-enem.htm

Viewing all articles
Browse latest Browse all 34