O que se passou nas mais de 4000 sessões de discussão do parlamento Português que ocorreram desde 1976? Neste notebook vamos tentar visualizar o que se passou da maneira mais simples - contando palavras, e fazendo gráficos.
Para obter os textos de todas as sessões usaremos o demo.cratica.org, onde podemos aceder facilmente a todas as sessões do parlamento de 1976 a 2015. Depois com um pouco de python, pandas e matplotlib vamos analisar o que se passou.
Para executar estes notebook será necessário descarregar e abrir com o Jupiter Notebooks (a distribuição Anaconda faz com que instalar todas as ferramentas necessárias seja fácil - https://www.continuum.io/downloads)
In [1]:
%matplotlib inline
import pylab
import matplotlib
import pandas
import numpy
dateparse = lambda x: pandas.datetime.strptime(x, '%Y-%m-%d')
sessoes = pandas.read_csv('sessoes_democratica_org.csv',index_col=0,parse_dates=['data'], date_parser=dateparse)
Bem, agora podemos começar a brincadeira.
Pandas, quantas vezes aparece a palavra 'Aplausos' no texto?
In [2]:
#define a funçao que conta aplausos
# procura 'aplauso' (nota: tambem conta 'aplausos')
def conta_aplausos(texto):
return texto.count('aplauso')
#aplica a funçao a cada sessão e cria nova coluna com os resultados
sessoes['n_aplausos'] = sessoes['sessao'].map(conta_aplausos)
ax = sessoes.plot(x='data',y='n_aplausos',figsize=(15,6),linewidth=0.1,marker='.',markersize=1)
ax.set_xlabel('Data da sessão')
ax.set_ylabel('Numero de aplausos')
Out[2]:
Salta logo á vista que há sessões excepcionais com muito mais aplausos que outras (para cima de 200 menções de aplausos). Talvez sejam mais interessantes? ou controversas?
Vamos investigar as sessões mais animadas.
Pandas, quais são as sessões com mais de 250 aplausos?
In [3]:
#define funçao que verifica se o numero de aplausos é maior que 200
def tem_mais_que_250_aplausos(val):
return val['n_aplausos']>250
#aplica a função a todas as linhas do dataframe
# assim cria-se um novo dataframe apenas com os elementos onde a função é 'true'
sessoes_250aplausos = sessoes[tem_mais_que_250_aplausos]
sessoes_250aplausos
Out[3]:
Várias sessões. Analisemos as palavras mais frequentes em cada uma.
In [7]:
import re
from collections import Counter
# aceita um texto, e retorna as 10 palavras mais comuns, com o correspondente número de ocorrencias
def conta_palavras(texto):
palavras = re.split(';|,|\n| |\(|\)|\?|\!|:',texto) #separa as palavras
palavras = [x.title() for x in palavras if len(x)>=5] #organiza e remove as palavras com menos de 5 caracteres
return Counter(palavras).most_common(10) # analisa as palavras e determina as 10 mais frequentes
# conta palavras em cada sessao com mais de 250 aplausos
l = sessoes_250aplausos['sessao'].map(conta_palavras)
datas = sessoes_250aplausos['data'][l.index] #selecciona apenas as datas, a sintaxe é contrieved
#agrupa os dados num dataframe, com uma coluna por data, e cada célula indicando ('n_contagens' x 'palavra')
dados = [[str(str(z)+' x '+y) for (y,z) in l[x]] for x in l.index]
pandas.DataFrame(dados,index=datas).transpose()
Out[7]:
Antes de tirarmos conclusões, resolvemos 2 problemas com os dados. Primeiro, há palavras que não adicionam muito á conversa ('governo', 'muito','ministro','todos', etc). Segundo, há palavras que podem ter um significado diferente consoante são usadas sozinhas ou numa expressão (e.g. 'Milhões de Euros' vs 'Milhões de Pensionistas'). Vamos reprocessar os dados com isto em conta.
In [9]:
# conta as 10 palavras mais comuns, tendo em conta expressoes comuns com significado importante, e removendo as palavras mais comuns que não nos ajudam a perceber o que se esta a passar
def conta_palavras_xpto(texto):
# substitui expressoes
expressoes = [['milhões de euros','milhões-de-euros'],['programa do governo','programa-do-Governo'],['orçamento do estado','orçamento-de-Estado'],['orçamento de Estado','orçamento-de-Estado'],['união europeia','união-europeia'],['bernardino soares','bernardino-soares'],['antónio josé seguro','antónio-seguro'],['honório novo','honório-novo'],['moção de censura','moção-de-censura'],['partido socialista','partido-socialista'],['partido social democrata','partido-social-democrata'],['bloco de esquerda','bloco-de-esquerda'],['partido comunista','partido-comunista'],['jerónimo de sousa','jerónimo-de-sousa'],['luís montenegro','luís-montenegro'],['medidas','medida']]
for chave in expressoes:
texto = texto.replace(chave[0],chave[1])
#remove palavras nao interessantes
lista = ['portugal','portugueses','estado','governo','deputado','deputada','primeiro-ministro','presidente','ministro','ministra','sobre','fazer','vozes','também','aplausos','quando','porque','muito','cds-pp','palavra','ainda','dizer','todos','deste','nesta','nossa','temos','nosso','nossa','estão','maria','sempre','sr.as','neste','silva','favor','agora']
for palavra in lista:
texto = texto.replace(palavra,'')
palavras = re.split(';|,|\n| |\(|\)|\?|\!|:',texto) #separa as palavras
palavras = [x.title() for x in palavras if len(x)>=5] #organiza e remove as palavras com menos de 5 caracteres
return Counter(palavras).most_common(10) # analisa as palavras e determina as 10 mais frequentes
# conta palavras em cada sessao com mais de 250 aplausos
l = sessoes_250aplausos['sessao'].map(conta_palavras_xpto)
datas = sessoes_250aplausos['data'][l.index]
#agrupa os dados num dataframe, com uma coluna por data, e cada célula indicando ('n_contagens' x 'palavra')
dados = [[str(str(z)+' x '+y) for (y,z) in l[x]] for x in l.index]
pandas.DataFrame(dados,index=datas).transpose()
Out[9]:
Vemos que as sessões mais aplaudidas são ou sobre o orçamento de estado ou discussões aparentemente partidárias.
Curiosamente as sessões sobre o orçamento de estado parecem ser todas no fim de Outubro. Vamos ver se isso tambem é verdade nos outros anos. Contemos o número de ocorrencias da expressão e façamos o gráfico.
In [10]:
def conta_orcamento_de_estado(texto):
return (texto.count('orçamento de estado') + texto.count('orçamento do estado'))
#aplica a funçao a cada sessão e cria nova coluna com os resultados
sessoes['n_orcamento_de_estado'] = sessoes['sessao'].map(conta_orcamento_de_estado)
ax = sessoes.plot(x='data',y='n_orcamento_de_estado',figsize=(15,6),linewidth=0.2,marker='.',markersize=0.5)
start, end = ax.get_xlim() #poe uma tick por ano
import numpy #para criar um eixo do x uniformemente espaçado
ax.xaxis.set_ticks((numpy.linspace(start, end, 29)))
ax.set_xlabel('Data da sessão')
ax.set_ylabel('Numero de ocorrencias de "Orçamento de Estado"')
Out[10]:
Interessante - antes de 1984 não se usava a expressão orçamento de estado ou orçamento do estado. Tambem, parece ser periódico. Vamos histogramar isto para ver o número de ocorrencias em cada semana do ano:
In [24]:
# retorna um vector com um item por sessao, e valor verdadeira se o número da semana é =i, falso se nao é
def selecciona_semana(data,i):
return data.map(lambda d: d.weekofyear == i)
import numpy
ocorrencias_por_semana = numpy.zeros(53)
for i in range(1,53):
# para cada semana do ano, calcula um vector com verdadeiro para as sessoes que ocorreram nesta semana e falso para as que nao ocorreram. soma tudo (verdadeiro = 1, falso = 0)
# com esse vector, filtra apenas os items da coluna 'n_orcamento_de_estado' onde o vector é verdadeiro/sessao foi na semana que estamos a contar
# soma as contagens das sessoes seleccionadas/filtradas
ocorrencias_por_semana[i] = numpy.sum(sessoes['n_orcamento_de_estado'][selecciona_semana(sessoes['data'],i)])
f = pylab.figure(figsize=(10,8))
ax = pylab.bar(range(1,53),ocorrencias_por_semana[1:53])
pylab.xlabel('Semana do ano')
pylab.ylabel('Ocorrencias de "Orçamento de Estado"')
Out[24]:
Parece que se fala em orçamento de estado +- sempre com a mesma frequência de Jan a Junho, depois há férias (Julho-Agosto), e na 3ª semana de Outubro a coisa começa a aquecer - o termo orçamento de estado começa-se a usar 2x ou 3x mais do que o normal.
Antes de mais, vamos tentar perceber porque não se usa a expressão orçamento de estado antes de 1984. Vamos analisar as palavras:
In [11]:
def selecciona_antes_ano(data,ano):
return data.map(lambda d: d.year < ano)
sessoes_antes1984 = sessoes[selecciona_antes_ano(sessoes['data'],1984)]
sessoes_antes1988 = sessoes[selecciona_antes_ano(sessoes['data'],1988)]
sessoes_antes1984
ax = sessoes_antes1988.plot(x='data',y='n_orcamento_de_estado',figsize=(15,6),linewidth=0.2,marker='.',markersize=0.5)
ax.set_xlabel('Data da sessão')
ax.set_ylabel('Numero de ocorrencias de "Orçamento de Estado"')
Out[11]:
Foi claramente em 1984 que a moda começou. Vamos ver se pelo menos usavam 'orçamento' com a mesma frequencia que em 1985.
In [12]:
def conta_orcamento(texto):
return texto.count('orçamento')
sessoes_antes1988.loc[:,'n_orcamento'] = sessoes_antes1988['sessao'].apply(conta_orcamento)
ax = sessoes_antes1988.plot(x='data',y='n_orcamento',figsize=(15,6),linewidth=0.2,marker='.',markersize=0.5)
ax.set_xlabel('Data da sessão')
ax.set_ylabel('Numero de ocorrencias de "Orçamento"')
Out[12]:
Orçamento sempre se usou, pelos vistos.
Por curiosidade, vamos analisar as palavras mais frequentes de todas as sessões de vários anos 1980, 1990, 2000 e 2010 e ver se há alguma tendência.
In [27]:
# Cria uma lista com todas as palavras no texto com mais de 4 caracteres, na sequência que aparecem neste
def agrupa_palavras(texto):
palavras = re.split(';|,|\n| |\(|\)|\?|\!|:',texto) # separa as palavras
palavras = [x.title() for x in palavras if len(x)>=5] # organiza e remove as palavras com menos de 5 caracteres
return palavras
# Cria uma lista das 40 palavras mais comuns neste conjunto de sessões, e conta o número de ocorrencias
def conta_palavras(sessoes):
lista = sessoes['sessao'].map(agrupa_palavras) # cria uma lista de 'lista de palavras', um elemento por sessao
palavras = []
for l in lista:
palavras.extend(l) # junta as várias 'listas de palavras' todas na mesma lista (i.e. junta as várias sessoes, antes de contar)
return Counter(palavras).most_common(40) # conta as palavras mais frequentes
def selecciona_ano(data,ano):
return data.map(lambda d: d.year == ano)
#filtra sessoes que ocorreram num dado ano
sessoes_1980 = sessoes[selecciona_ano(sessoes['data'],1980)]
sessoes_1990 = sessoes[selecciona_ano(sessoes['data'],1990)]
sessoes_2000 = sessoes[selecciona_ano(sessoes['data'],2000)]
sessoes_2010 = sessoes[selecciona_ano(sessoes['data'],2010)]
# conta palavras mais frequentes nos vários grupos de sessoes
c1980 = conta_palavras(sessoes_1980)
c1990 = conta_palavras(sessoes_1990)
c2000 = conta_palavras(sessoes_2000)
c2010 = conta_palavras(sessoes_2010)
#organiza os dados numa tabela
dados = [[str(str(z)+' x '+y) for (y,z) in c1980]]
dados.append([str(str(z)+' x '+y) for (y,z) in c1990])
dados.append([str(str(z)+' x '+y) for (y,z) in c2000])
dados.append([str(str(z)+' x '+y) for (y,z) in c2010])
pandas.DataFrame(dados,index=['1980','1990','2000','2010']).transpose()
Out[27]:
Sao praticamente as mesmas palavras.
Calculemos as mais frequentes de todos juntos e retiremo-las desta contagem:
In [28]:
# Cria uma lista das 100 palavras mais comuns neste conjunto de sessões, e conta o número de ocorrencias
def conta_palavras100(sessoes):
lista = sessoes['sessao'].map(agrupa_palavras) # cria uma lista de 'lista de palavras', um elemento por sessao
palavras = []
for l in lista:
palavras.extend(l) # junta as 'listas de palavras' todas na mesma lista
return Counter(palavras).most_common(100) # conta as palavras mais frequentes
todas_sessoes = pandas.concat([sessoes_1980, sessoes_1990, sessoes_2000, sessoes_2010])
contagem = conta_palavras100(todas_sessoes)
dados = [[str(str(z)+' x '+y) for (y,z) in contagem]]
palavras_a_retirar = sorted([y for (y,z) in contagem],key=len)
pandas.DataFrame(dados).transpose()
Out[28]:
In [67]:
# Cria uma lista com todas as palavras no texto com mais de 4 caracteres, na sequência que aparecem neste
# mas não incluí as 100 palavras mais comuns nesta lista
def agrupa_palavras2(texto):
texto = texto.lower() #processa tudo em minusculas
#remove palavras nao interessantes
for palavra in palavras_a_retirar:
texto = texto.replace(palavra.lower(),'')
palavras = re.split(';|,|\n| |\(|\)|\?|\!|:',texto) # separa as palavras
palavras = [x.title() for x in palavras if len(x)>=5] # organiza e remove as palavras com menos de 5 caracteres
return palavras
def conta_palavras2(sessoes):
lista = sessoes['sessao'].map(agrupa_palavras2) # cria uma lista de 'lista de palavras', um elemento por sessao
palavras = []
for l in lista:
palavras.extend(l) # junta as 'listas de palavras' todas na mesma lista
return Counter(palavras).most_common(40) # conta as palavras mais frequentes
c1980 = conta_palavras2(sessoes_1980)
c1990 = conta_palavras2(sessoes_1990)
c2000 = conta_palavras2(sessoes_2000)
c2010 = conta_palavras2(sessoes_2010)
dados = [[str(str(z)+' x '+y) for (y,z) in c1980]]
dados.append([str(str(z)+' x '+y) for (y,z) in c1990])
dados.append([str(str(z)+' x '+y) for (y,z) in c2000])
dados.append([str(str(z)+' x '+y) for (y,z) in c2010])
pandas.DataFrame(dados,index=['1980','1990','2000','2010']).transpose()
Out[67]:
Quando descontamos as 100 palavras mais comuns salientam-se mais as diferenças entre os textos. Mesmo assim é difícil tirar conclusões profundas, mas há algumas mais básicas:
In [14]:
def conta_decretolei(texto):
return (texto.count('decreto-lei')+texto.count('decretos-lei'))
sessoes['n_decreto'] = sessoes['sessao'].map(conta_decretolei)
ax = sessoes.plot(x='data',y='n_decreto',figsize=(15,6),linewidth=0.2,marker='.',markersize=0.5)
ax.set_xlabel('Data da sessão')
ax.set_ylabel('Numero de ocorrencias de decreto-lei')
Out[14]:
Há picos maiores na decada de 80, mas o que interessa neste gráfico é a área,o que é difícil de quantificar num gráfico com tantos pontos. Vamos agrupar por ano:
In [15]:
def selecciona_ano(data,i):
return data.map(lambda d: d.year == i)
import numpy
ocorrencias_por_ano = numpy.zeros(2016-1976)
for i in range(0,2016-1976):
# para cada ano, soma o número de ocorrencias de decreto
# (filtrando as sessoes que ocorrem num dado ano, selecionando-as da coluna 'n_decreto' e somando todos os valores seleccionados)
ocorrencias_por_ano[i] = numpy.sum(sessoes['n_decreto'][selecciona_ano(sessoes['data'],i+1976)])
f = pylab.figure(figsize=(10,8))
ax = pylab.bar(range(1976,2016),ocorrencias_por_ano)
pylab.xlabel('Ano')
pylab.ylabel('Ocorrencias de "decreto-lei"')
Out[15]:
É verdade. Falava-se mais de decreto-lei á volta de 1980, e em 1986.
In [ ]:
In [ ]:
In [ ]:
In [ ]: