python4oceanographers

Turning ripples into waves

Para um futuro melhor

Sábado passado foi o Open Data Day e, por pura sorte, acabei tropeçando no projeto dados-abertos-cd do Calango H4cker Clube de Brasília.

O projeto busca, em suas próprias palavras,

Soluções para acesso, exploração e consumo dos Dados Abertos
disponibilizados pela Câmara dos Deputados do Brasil.

Vamos brincar com os dados que esse pessoal incrível colocou online? Seguindo a receita de bolo to próprio projeto vamos usar o módulo suds para criar um cliente e fazer um query nos dados.

In [2]:
from suds.client import Client


url = 'http://www.camara.gov.br/SitCamaraWS/Proposicoes.asmx?wsdl'
client = Client(url, headers={'Content-Type': 'text/xml; charset=UTF-8'})

print(client)

Suds ( https://fedorahosted.org/suds/ )  version: 0.4 GA  build: R699-20100913

Service ( Proposicoes ) tns="http://www.camara.gov.br/SitCamaraWS/Proposicoes"
   Prefixes (0)
   Ports (1):
      (ProposicoesSoap)
         Methods (11):
            ListarProposicoes(xs:string sigla, xs:string numero, xs:string ano, xs:string datApresentacaoIni, xs:string datApresentacaoFim, xs:string idTipoAutor, xs:string parteNomeAutor, xs:string siglaPartidoAutor, xs:string siglaUFAutor, xs:string generoAutor, xs:string codEstado, xs:string codOrgaoEstado, xs:string emTramitacao, )
            ListarProposicoesTramitadasNoPeriodo(xs:string dtInicio, xs:string dtFim, )
            ListarProposicoesVotadasEmPlenario(xs:string ano, xs:string tipo, )
            ListarSiglasTipoProposicao()
            ListarSituacoesProposicao()
            ListarTiposAutores()
            ObterProposicao(xs:string tipo, xs:string numero, xs:string ano, )
            ObterProposicaoPorID(xs:string idProp, )
            ObterVotacaoProposicao(xs:string tipo, xs:string numero, xs:string ano, )
            obterHierarquiaProposicao(xs:string idProposicao, )
            obterProposicaoPrincipal(xs:string idProposicao, )
         Types (0):



Nunca havia usado o suds antes, mas o objeto suds parece bem simples e fácil de usar. Para facilitar a exploração dos dados vamos colocar os anos de 2011--2014 em um único dataframe do pandas.

In [3]:
import pandas as pd
from suds.client import WebFault

kw = dict(sigla='MPV', numero=None, datApresentacaoIni=None, datApresentacaoFim=None,
          idTipoAutor=None, parteNomeAutor=None, siglaPartidoAutor=None, siglaUFAutor=None,
          generoAutor=None, codEstado=None, codOrgaoEstado=None, emTramitacao=1)

dfs = []
for ano in range(1995, 2015):
    try:
        res = client.service.ListarProposicoes(ano=ano, **kw)
        props = res['proposicoes']['proposicao']
        dfs.append(pd.DataFrame(props))
    except WebFault:
        print(u"Impossível baixar ano {}".format(ano))

df = pd.concat(dfs, axis=0)
ERROR:suds.client:<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:ns0="http://www.camara.gov.br/SitCamaraWS/Proposicoes" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <ns1:Body>
      <ns0:ListarProposicoes>
         <ns0:sigla>MPV</ns0:sigla>
         <ns0:ano>1995</ns0:ano>
         <ns0:emTramitacao>1</ns0:emTramitacao>
      </ns0:ListarProposicoes>
   </ns1:Body>
</SOAP-ENV:Envelope>
ERROR:suds.client:<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:ns0="http://www.camara.gov.br/SitCamaraWS/Proposicoes" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <ns1:Body>
      <ns0:ListarProposicoes>
         <ns0:sigla>MPV</ns0:sigla>
         <ns0:ano>1996</ns0:ano>
         <ns0:emTramitacao>1</ns0:emTramitacao>
      </ns0:ListarProposicoes>
   </ns1:Body>
</SOAP-ENV:Envelope>

Impossível baixar ano 1995
Impossível baixar ano 1996
ERROR:suds.client:<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:ns0="http://www.camara.gov.br/SitCamaraWS/Proposicoes" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <ns1:Body>
      <ns0:ListarProposicoes>
         <ns0:sigla>MPV</ns0:sigla>
         <ns0:ano>1997</ns0:ano>
         <ns0:emTramitacao>1</ns0:emTramitacao>
      </ns0:ListarProposicoes>
   </ns1:Body>
</SOAP-ENV:Envelope>


Impossível baixar ano 1997
ERROR:suds.client:<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:ns0="http://www.camara.gov.br/SitCamaraWS/Proposicoes" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <ns1:Body>
      <ns0:ListarProposicoes>
         <ns0:sigla>MPV</ns0:sigla>
         <ns0:ano>1998</ns0:ano>
         <ns0:emTramitacao>1</ns0:emTramitacao>
      </ns0:ListarProposicoes>
   </ns1:Body>
</SOAP-ENV:Envelope>


Impossível baixar ano 1998
ERROR:suds.client:<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:ns0="http://www.camara.gov.br/SitCamaraWS/Proposicoes" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <ns1:Body>
      <ns0:ListarProposicoes>
         <ns0:sigla>MPV</ns0:sigla>
         <ns0:ano>1999</ns0:ano>
         <ns0:emTramitacao>1</ns0:emTramitacao>
      </ns0:ListarProposicoes>
   </ns1:Body>
</SOAP-ENV:Envelope>


Impossível baixar ano 1999
ERROR:suds.client:<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:ns0="http://www.camara.gov.br/SitCamaraWS/Proposicoes" xmlns:ns1="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <ns1:Body>
      <ns0:ListarProposicoes>
         <ns0:sigla>MPV</ns0:sigla>
         <ns0:ano>2000</ns0:ano>
         <ns0:emTramitacao>1</ns0:emTramitacao>
      </ns0:ListarProposicoes>
   </ns1:Body>
</SOAP-ENV:Envelope>


Impossível baixar ano 2000

Humm... sem dados antes do ano de 2000 :-(

As duas funções abaixo são apenas "estéticas" uma mostra o dataframe como uma tabela HTML e a outra limpa os dados um pouco.

In [4]:
def to_html(df, css='./data/table.css'):
    from IPython.display import HTML
    with open(css, 'r') as f:
        style = """<style>{}</style>""".format(f.read())
    table = dict(style=style, table=df.to_html())
    return HTML(u'{style}<div class="datagrid">{table}</div>'.format(**table))


def clean_df(df):
    """Almost clean dataframe."""
    columns = {k: tupla[0] for k, tupla in enumerate(df.irow(0))}
    df.rename(columns=columns, inplace=True)
    df = df.applymap(lambda x: x[1])
    df.set_index('id', drop=True, inplace=True)
    df['autor1'] = [nome.txtNomeAutor for nome in df['autor1']]
    df['regime'] = [unicode(txt[1]) for cod, txt in df['regime']]
    df['apreciacao'] = [unicode(apr[1]) for id, apr in df['apreciacao']]
    df['orgaoNumerador'] = [unicode(nome[1]) for id, sigle, nome in df['orgaoNumerador']]
    df['tipoProposicao'] = [unicode(nome[1]) for id, sigla, nome in df['tipoProposicao']]
    df['situacao'] = [unicode(desc[1]) for id, desc, org, princ in df['situacao']]
    drop = ['ultimoDespacho', 'qtdAutores', 'numero', 'indGenero', 'qtdOrgaosComEstado', 'datApresentacao']
    df.drop(drop, inplace=True, axis=1)
    df['ano'] = df['ano'].apply(int)
    return df

Esse é o resultado das funções acima:

In [5]:
df = clean_df(df)

textos = df[['txtEmenta', 'txtExplicacaoEmenta']]
df.drop(['txtEmenta', 'txtExplicacaoEmenta'], inplace=True, axis=1)
to_html(df.head())
Out[5]:
nome tipoProposicao ano orgaoNumerador regime apreciacao autor1 situacao
id
44997 MPV 20/2001 Medida Provisória 2001 Urgência Proposição Sujeita à Apreciação do Plenário Poder Executivo Transformado em Norma Jurídica
45017 MPV 19/2001 Medida Provisória 2001 Urgência Proposição Sujeita à Apreciação do Plenário Poder Executivo Transformado em Norma Jurídica
44915 MPV 18/2001 Medida Provisória 2001 Urgência Proposição Sujeita à Apreciação do Plenário Poder Executivo Transformado em Norma Jurídica
44913 MPV 17/2001 Medida Provisória 2001 Urgência Proposição Sujeita à Apreciação do Plenário Poder Executivo Transformado em Norma Jurídica
44740 MPV 16/2001 Medida Provisória 2001 Urgência Proposição Sujeita à Apreciação do Plenário Poder Executivo Transformado em Norma Jurídica

A primeira informação que descobri explorando esses dados é que o regime, a apreciação, o tipo de proposição e autor (apenas esse eu esperava) são sempre valores únicos.

Todo regime é de urgência, toda proposição está sujeita a apreciação do plenário todos os tipos de proposição são medidas provisórias e o autor é sempre o poder executivo.

In [6]:
def print_unique(col):
    for el in df[col].unique().tolist():
        print(u'{}: {}'.format(col, el))

print_unique(col='regime')
print_unique(col='apreciacao')
print_unique(col='tipoProposicao')
print_unique(col='autor1')
regime: Urgência
apreciacao: Proposição Sujeita à Apreciação do Plenário
apreciacao: .
tipoProposicao: Medida Provisória
autor1: Poder Executivo

Como não tenho ainda ideia do que fazer com os dados vamos criar apenas uns gráficos exploratórios. Abaixo temos três histogramas com o número de proposições por ano, o órgão numerador e a situação.

In [7]:
figsize = (7, 2.75)

ax = df[['ano']].apply(pd.value_counts).sort().plot(kind='bar', figsize=figsize)

Vou deixar pra comentar sobre os padrões observados acima em outro post ;-)

In [8]:
ax = df[['orgaoNumerador']].apply(pd.value_counts).plot(kind='barh', figsize=figsize)

Parece que alguém esqueceu de preencher esse dado em algumas entradas... Quase 100 campos vazios.

In [9]:
ax = df[['situacao']].apply(pd.value_counts).plot(kind='barh', figsize=figsize)

Para continuar a exploração dos dados vamos buscar algumas palavras chaves nos textos das propostas.

In [10]:
def find_word(word=u'educação'):
    ids = []
    for id, row in textos[['txtEmenta', 'txtExplicacaoEmenta']].iterrows():
        texto = '\n'.join([row[0], row[1]])
        if word in texto:
            ids.append(id)
    return ids

print('educação: {}\nimposto: {}'.format(len(find_word(word=u'educação')), len(find_word(word='imposto'))))
educação: 8
imposto: 16

Temos 8 propostas contendo a palavra educação e o dobro contendo a palavra imposto. (Juro que escolhi aleatoriamente as palavras!)

Caso alguém queira ler os textos basta usar a id para ler o dataframe.

In [11]:
ids = find_word(word=u'educação')

print(unicode(textos['txtEmenta'].ix[ids[7]]))
Autoriza o pagamento de subvenção econômica aos produtores da safra 2011/2012 de cana-de-açúcar e de etanol da região Nordeste e o financiamento da renovação e implantação de canaviais com equalização da taxa de juros; dispõe sobre os arranjos de pagamento e as instituições de pagamento integrantes do Sistema de Pagamentos Brasileiro - SPB; altera a Lei nº 12.783, de 11 de janeiro de 2013, para autorizar a União a emitir, sob a forma de colocação direta, em favor da Conta de Desenvolvimento Energético - CDE, títulos da dívida pública mobiliária federal; e dá outras providências. 

NOVA EMENTA: Autoriza o pagamento de subvenção econômica aos produtores da safra 2011/2012 de cana-de-açúcar e de etanol que especifica e o financiamento da renovação e implantação de canaviais com equalização da taxa de juros; dispõe sobre os arranjos de pagamento e as instituições de pagamento integrantes do Sistema de Pagamentos Brasileiro - SPB; autoriza a União a emitir, sob a forma de colocação direta, em favor da Conta de Desenvolvimento Energético - CDE, títulos da dívida pública mobiliária federal; estabelece novas condições para as operações de crédito rural oriundas de, ou contratadas com, recursos do Fundo Constitucional de Financiamento do Nordeste - FNE; altera os prazos previstos nas Leis nº 11.941, de 27 de maio de 2009, e nº 12.249, de 11 de junho de 2010; autoriza a União a contratar o Banco do Brasil S.A. ou suas subsidiárias para atuar na gestão de recursos, obras e serviços de engenharia relacionados ao desenvolvimento de projetos, modernização, ampliação, construção ou reforma da rede integrada e especializada para atendimento da mulher em situação de violência; disciplina o documento digital no Sistema Financeiro Nacional; disciplina a regularização de áreas ocupadas por entidades de assistência social, de educação ou templos de qualquer culto no Distrito Federal; disciplina a transferência, no caso de falecimento, do direito de utilização privada de área pública por equipamentos urbanos do tipo quiosque, trailer, feira, banca de venda de jornais e de revistas; altera a incidência da Contribuição para o PIS/Pasep e da Cofins na cadeia de produção e comercialização da soja e de seus subprodutos; altera as Leis nºs 12.666, de 14 de junho de 2012, 5.991, de 17 de dezembro de 1973, 11.508, de 20 de julho de 2007,  9.503, de 23 de setembro de 1997, 9.069, de 29 de junho de 1995, 10.865, de 30 de abril de 2004, 12.587, de 3 de janeiro de 2012, 10.826, de 22 de dezembro de 2003, 10.925, de 23 de julho de 2004, 12.350, de 20 de dezembro de 2010, 4.870, de 1º de dezembro de 1965 e 11.196, de 21 de novembro de 2005, e o Decreto nº 70.235, de 6 de março de 1972; revoga dispositivos da Lei nº 12.546, de 14 de dezembro de 2011; e dá outras providências.

Notem que nesse caso eu não escolhi o índice [7] de forma aleatória, escolhi um que tem a palavra educação fora do escopo que eu imaginei.

Fiz isso para mostrar que trabalhar com textos é muito complicado! Temos que tomar muito cuidado. Nossa contagem sobre a educação, por exemplo, caiu de 8 para 7 ao olhar os dados de perto.

Caso alguém tenha interesse em mais dados abertos do governo chequem o site http://dados.gov.br/. Para maiores informações sobre o evento do Calango olhem página do evento e o Google Hangout que ocorreu sábado passado:

In [12]:
from IPython.display import YouTubeVideo
YouTubeVideo("8IITQSAHJiQ")
Out[12]:
In [13]:
HTML(html)
Out[13]:

This post was written as an IPython notebook. It is available for download or as a static html.

Creative Commons License
python4oceanographers by Filipe Fernandes is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Based on a work at https://ocefpaf.github.io/.

Comments