import os
import gzip
import json
import requests
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np
import re
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
BASIS_URL = 'https://www.parlament.gv.at'
#Plenarsitzungen des Nationalrats für eine gegebene Gesetzesperiode
LISTE_URL = '/PAKT/STPROT/index.shtml?SUCH=&xdocumentUri=%2FPAKT%2FSTPROT%2Findex.shtml&pageNumber=&R_PLSO=PL' + \
'&GP={gp}&STEP=&INTRANET=N&feldRnr=1&STPROT=ALLE&ascDesc=DESC&FBEZ=FP_011&NRBRBV=NR&BEZ=FP_211' + \
'&requestId=D6CBFB9744&LISTE=&jsMode=&listeId=211&NUR_VORL=N'
Falls schon heruntergeladene Protokolle existieren, diese laden.
%%time
FILE = 'protokolle_txt.json.zip'
if os.path.exists(FILE):
# Protokolle aus Datei laden
with gzip.open(FILE, 'rt') as f:
protokolle_txt = json.load(f)
else:
# Protokolle herunterladen
protokolle_txt = dict()
Hier werden Protokolle geladen, für angegebene Gesetzesperiode. Falls bestehende Protokolle schon geladen sind, nicht nötig auszuführen.
%%time
# Status 8.8.2016
# Stenographische Protokolle in strukturiertem HTML Format ab XXII. Gesetzgebungsperiode (23.05.2006, 150. NR Sitzung)
# Ab XXIII. Gesetzgebungsperiode (1. Sitzung 30.10.2006) durchgehend neues Format.
# Analysiere daher ab XXIII. Gesetzgebungsperiode
# Aktuellstest Protokoll: XXV. Gesetzgebungsperiode, 24.02.2016
gesetzgebungsperioden = ('XXV', 'XXIV', 'XXIII')
for gp in gesetzgebungsperioden:
print(' ')
print(gp)
# Internetseite mit Links zu den Protokollen der entsprechenden Gesetzgebungsperidoe wird abgerufen
r = requests.get(BASIS_URL + LISTE_URL.format(gp = gp))
soup = BeautifulSoup(r.text, 'lxml')
# Alle Links in der Ergebnistabelle
links = soup.find('table', class_='tabelle filter').find_all('a')
# Davon alle auf Stenographisches Protokoll in HTML Format
links = [link for link in links if link.text == 'HTML']
# Davon die URL
links = [link.get('href') for link in links]
for link in links:
# In der URL steckt die Nummer der Sitzung, der zur Information ausgegeben wird
print(link.split('/')[5].split('_')[1], end=' ')
# Eindeutiger key des Protokolls, bestehend aus Gesetzperiode, Art der Sitzung (Nationalrat) und Nummer
key = gp + '_' + link.split('/')[5]
r_temp = requests.get(BASIS_URL + link)
protokolle_txt[key] = r_temp.text
print()
# Protokolle in gezippten JSON File speichern
with gzip.open(FILE, mode="wt") as f:
json.dump(protokolle_txt, f)
len(protokolle_txt)
Wir haben also 409 Nationalratssitzungen. Jeder der Sitzungen ist im HTML-Format gespeichert.
protokolle_txt['XXIII_NRSITZ_00043'][:400]
Falls die Protokolle schon ausgelsen wurden und strukturiert in einem Datenframe gespeichert wurden, wird die Datei nun eingelesen.
%%time
FILE = 'df_protokolle.pkl'
if os.path.exists(FILE):
# Protokolle aus Datei laden
df = pd.read_pickle(FILE)
Hier werden Protokolle ausgelesen und ausgewählte Elemente in einen Datenframe gespeichert. Das dauert einige Zeit. Falls entsprechende Datenframe schon geladen ist und kein neues Parsen gewünscht wird, nicht ausführen.
%%time
# key_okay speichert alle Protokolle, die bereits ausgelesen wurden.
# Nun sollte das Auslesen in einem Lauf funktioneren. Während des Entwickeln des Codes,
# wurde der folgende Code öfters ausgeführt und ich wollte nicht jedesmal ganz von vorne anfangen.
key_okay=[]
# Ergebnis wird in einem Datenframe gespeichert
df = pd.DataFrame()
# Countdown damit man sieht, wie viele Protokolle noch ausgelesen werden müssen
countdown = len(protokolle_txt) - len(key_okay)
for key in protokolle_txt:
#for key in ('XXIV_NRSITZ_00207',):
if key in key_okay:
continue
countdown = countdown - 1
# HTML-Protokoll parsen mittels BeautifulSoup
txt = protokolle_txt[key]
soup = BeautifulSoup(txt, 'lxml')
# Bei manchen Sitzungen gibt es Fehler bei den IDs (Name, Datum, ...).
# Diese werden manuell überschrieben
if key == 'XXIV_NRSITZ_00026':
# Falsche IDs in entsprechendem Protokoll
sitzung = '26. Sitzung des\r\nNationalrates der Republik Österreich'
periode = 'XXIV. Gesetzgebungsperiode'
datum = 'Dienstag, 16. Juni 2009'
elif key == 'XXIII_NRSITZ_00060':
# Falsche IDs in entsprechendem Protokoll
sitzung = '60. Sitzung des\r\nNationalrates der Republik Österreich'
periode = 'XXIII. Gesetzgebungsperiode'
datum = 'Donnerstag, 8. Mai 2008'
# Normalerweise lassen sich die IDs auslesen, Identifikation anhand HTML-Class IDs
else:
sitzung = soup.find('p', class_='DBl02').get_text()
periode = soup.find('p', class_='DBl04').get_text()
datum = soup.find('p', class_='DBl05').get_text()
# Nochmals eine manuelle Datenbereinigung
if key == 'XXIV_NRSITZ_00220':
sitzung = '220. Sitzung des\r\nNationalrates der Republik Österreich'
if key == 'XXV_NRSITZ_00001':
datum = 'Dienstag, 29. Oktober 2013'
if key == 'XXIII_NRSITZ_00021':
datum = 'Donnerstag, 3. Mai 2007'
print(countdown, ' - ', key, ' - ', periode[:periode.find('.')], ' - ', sitzung[:sitzung.find('.')])
# Normalerweise sind Reden an HTML-Class 'StandardRB' (RB scheint für Rede-Beginn zu stehen) erkennbar
rede_pos = soup.find('p', class_='StandardRB')
while rede_pos != None:
# Nun wird der Text der Rede ausgelesen.
# Zuerst der Text des Paragraphs mit der 'StandardRB'-Class
rede_txt = rede_pos.get_text()
# Doch leider kann der Text über mehrere Paragraphen gehen.
# Zusätzlich Reden des Schriftführers/der Schriftführerin ausschließen, gibt es bei den Angelobungen neuer
# Abgeordneter, inklusive der Antworten "Ich gelobe."
# Hier gibt es oft Probleme mit den HTML-Classes, ebenso bei
while ((rede_txt.startswith('Schriftführer') == False) &
(max(rede_txt.find('Ich gelobe.'),rede_txt.find('Ich\r\ngelobe.')) == -1)):
# Identifikation der HTML Klasse des nächsten Paragraphs
rede_pos = rede_pos.find_next('p')
klasse = rede_pos['class']
# Paragraphen mit den folgenden Klassen sind eine Fortsetzung der Rede.
# Daher wird der Text zur bestehenden Rede hinzugefügt
if klasse in (['MsoNormal'], ['StandardRB'], ['MsoBodyText'], ['MsoListBullet'],
['INHANTR'],
['MsoListBulletCxSpFirst'], ['MsoListBulletCxSpMiddle'], ['MsoListBulletCxSpLast'],
['MsoBodyText2'], ['MsoNormalCxSpMiddle']) :
# INHANTR z.B. wegen XXII_NRSITZ_00058
# MsoListBulletCxSpFirst, ...Middle und ... Last z.B. wegen XXV_NRSITZ_00012
# MsoBodyText2 z.B wegen XXII_NRSITZ_00051, könnte man auch als Ende verwenden
# MsoNormalCxSpMiddle z.B. wegen XXIV_NRSITZ_00159
rede_txt = rede_txt + rede_pos.get_text()
# Die folgenden Klassen markieren das Ende einer Rede
elif klasse in (['StandardRE'], ['RE'], ['StandardR'], ['SB'], ['RE0']):
# RE0 z.B. wegen XXII_NRSITZ_00035
rede_txt = rede_txt + rede_pos.get_text()
df = df.append({'key':key, 'sitzung': sitzung, 'periode': periode, 'datum': datum, 'rede': rede_txt},
ignore_index=True)
break
# Die folgenden Klassen markieren einen Zwischentext, der nicht Teil der Rede ist
elif klasse in (['ZM'],['RB'], ['Format1']):
# 'ZM' - Entschließungsantrag
# 'RB' - Redebeginn, nicht benötigt, verwende StandardRB
pass
# Nicht oben erwähnte Klasse führt zu Fehlermeldung und speichert Dokument,
# damit es genauer analysiert werden kann.
# Wurde während der Entwicklung verwendet, sollte jetzt nicht vorkommen, es sei denn,
# es kommen neue Protokolle mit neuen Formatierungen vor
else:
print(key)
print(klasse)
print(rede_txt)
with open('temp.html', 'w', encoding='utf8') as fp:
fp.write(protokolle_txt[key])
raise ValueError('Unerwartetes Element in Rede')
# Finde Start der nächsten Rede
if key == 'XXIV_NRSITZ_00120':
# 'Falsches StandardR in entsprechendem Protokoll
rede_pos = rede_pos.find_next('p', class_=lambda x: x in ('StandardRB', 'StandardR'))
else:
rede_pos = rede_pos.find_next('p', class_='StandardRB')
key_okay.append(key)
df.to_pickle('df_protokolle.pkl')
Die Sitzung XXIII_NRSITZ_00076 oben ist keine echte Sitzung, deshalb oben keine richtige Nummer. Spielt haber keine Rolle, da diese Sitzung eh keine Rede enthält.
Die folgenden drei Zellen wurden verwendet, um während der Entwicklung des Codes für einzelne Protokolle das entsprechende HTML File zu schreiben. Dieses konnte dann im Browser analysiert werden.
#print(key)
#with open('temp.html', 'w', encoding='utf8') as fp:
# fp.write(protokolle_txt[key])
#with open('temp.html', 'w', encoding='utf8') as fp:
# fp.write(protokolle_txt['XXIII_NRSITZ_00021'])
df.info()
Wir haben also mehr als 30.000 Reden. So sehen die einzelnen Zeilen aus, pro Zeile gibt es eine Rede.
df[:3]
# Die Spalte periode wird verkürzt, nur der Wert, nicht noch der Text 'Gesetzgebungsperiode'
df.periode = df.periode.str.split('.',expand=True)[0]
Nun wollen wir das Datum bereinigen. Leider gibt es hier ein paar Sonderfälle, nämlich Sitzungen über mehrere Tage. Wir nehmen hier einfach den letzten Tag. Im Beispiel also den 29. Mai 2009.
df.loc[df.key=='XXIV_NRSITZ_00023', 'datum'].iloc[0]
df.datum = df.datum.str.rsplit(',', expand=True, n=1)[1].str.strip()
df.loc[df.key=='XXIV_NRSITZ_00023', 'datum'].iloc[0]
Nun wird das Textformat, z.B. '29. Mai 2009' in ein Python-Datumsformat umgewandelt.
# Interessanterweise gibt es sowohl Februar als auch Feber
map_monat = {'Jänner':1, 'Feber':2, 'Februar':2, 'März':3, 'April':4, 'Mai':5, 'Juni':6, 'Juli':7, 'August':8,
'September':9, 'Oktober':10, 'November':11, 'Dezember':12}
def datum_umwandeln(txt):
txt = txt.replace('\r\n',' ').replace('\xa0', ' ')
tag, monat, jahr = txt.split(' ',maxsplit=2)
datum = tag + str(map_monat[monat]) + '.' + str(jahr)
return pd.to_datetime(datum, format='%d.%m.%Y')
df.datum = df.datum.apply(datum_umwandeln)
df.groupby(df.datum.dt.year)[['key']].count()
sns.set(context='talk', style='whitegrid')
sns.countplot(y=df.datum.dt.year, data=df)
sns.despine()
sns.set(font_scale=1.5)
sns.plt.title('Anzahl Reden pro Jahr')
sns.set(font_scale=1)
sns.axlabel('Reden', 'Jahr')
sns.plt.savefig('reden_pro_jahr.svg')
sns.set(context='talk', style='whitegrid')
sns.countplot(y=df.datum.dt.month, data=df)
sns.despine()
sns.set(font_scale=1.5)
sns.plt.title('Anzahl Reden pro Monat')
sns.set(font_scale=1)
sns.axlabel('Reden', 'Monat')
sns.plt.savefig('reden_pro_monat.svg')
sns.set(context='talk', style='whitegrid')
ax = sns.countplot(y=df.datum.dt.dayofweek, data=df)
sns.despine()
sns.set(font_scale=1.5)
sns.plt.title('Anzahl Reden pro Wochentag')
sns.set(font_scale=1)
sns.axlabel('Reden', 'Wochentag')
#labels = [item.get_text() for item in ax.get_yticklabels()]
labels = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']
ax.set_yticklabels(labels)
sns.plt.savefig('reden_pro_wochentag.svg')
df_temp = (df.groupby([df.datum.dt.year, df.datum.dt.month])['key'].count()).unstack()
mask = df_temp.isnull()
df_temp = df_temp.fillna(value=0)
df_temp = df_temp.astype('int')
df_temp[:3]
sns.set(context='talk', style='whitegrid')
sns.heatmap(df_temp, annot=True, linewidths=.5, fmt='d', mask=mask)
#sns.despine()
sns.set(font_scale=1.5)
sns.plt.title('Anzahl Reden (in 100 Reden)')
sns.set(font_scale=1)
sns.axlabel('Monat', 'Jahr')
sns.plt.savefig('reden_pro_jahr_monat.svg')
df_temp = (df.groupby([df.datum.dt.dayofweek, df.datum.dt.year])['key'].count()).unstack()
df_temp.index = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']
mask = df_temp.isnull()
df_temp = df_temp.fillna(value=0)
df_temp = df_temp.astype('int')
df_temp[:3]
sns.set(context='talk', style='whitegrid')
sns.heatmap(df_temp, annot=True, linewidths=.5, fmt='d', mask=mask)
sns.set(font_scale=1.5)
sns.plt.title('Anzahl Reden (in 100 Reden)')
sns.set(font_scale=1)
sns.axlabel('Jahr', 'Wochentag')
sns.plt.savefig('reden_pro_wochentag_jahr.svg')
df_temp = (df.groupby([df.datum.dt.dayofweek, df.datum.dt.month])['key'].count()).unstack()
df_temp.index = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']
mask = df_temp.isnull()
df_temp = df_temp.fillna(value=0)
df_temp = df_temp.astype('int')
df_temp[:3]
sns.set(context='talk', style='whitegrid')
sns.heatmap(df_temp, annot=True, linewidths=.5, fmt='d', mask=mask)
sns.set(font_scale=1.5)
sns.plt.title('Anzahl Reden (in 100 Reden)')
sns.set(font_scale=1)
sns.axlabel('Monat', 'Wochentag')
sns.plt.savefig('reden_pro_wochentag_monat.svg')
Der Redner ist der erste Teil des Felds rede. Dieser soll herausgelöst und extra gespeichert werden, inklusive der Partei.
#Sonderzeichen entfernen
df.rede = df.rede.str.replace('\xa0',' ')
df.rede = df.rede.str.replace('\r\n',' ')
df.rede = df.rede.str.replace('\xad','')
df.rede = df.rede.str.replace('†', '')
df.rede = df.rede.str.replace('|', '')
Sonderfall, erster Abschnitt der Rede fehlt, falsch in HTML abgebildet. Manuelle Korrektur.
mask = df.rede.str.startswith('Im Sicherheitsbereich bin ich zuständig')
rede_temp = df[mask].iloc[0].rede
rede_fehlend = '''Bundesministerin für Inneres Mag. Dr. Maria Theresia Fekter: Herr Präsident! Hohes Haus!
Werte Damen und Herren auf der Besuchergalerie und vor den Bildschirmen! Liebe Kollegin Moser, Sie haben zwar
die Ministerin Bures angesprochen, aber da ich das Wohnpaket verhandelt habe, weise ich darauf hin, dass Ihrer
Aufmerksamkeit entgangen sein dürfte, dass wir für die thermische Sanierung im Wohnbau sehr viel Geld in die Hand
nehmen (Abg. Dr. Moser: Leider nur 100! Wir brauchen 300!) und dass die Infrastrukturprojekte ja dazu gedacht sind,
Arbeitsplätze zu schaffen, damit es nicht zu Arbeitslosigkeit kommt. Und das nützt den Menschen, Frau Kollegin Moser!
(Beifall bei der ÖVP.) '''
df.loc[mask, 'rede'] = rede_fehlend + rede_temp
Sonderfall, bei dem die Rede zu früh beginnt und das Ende fehlt.
mask = df.rede.str.startswith('Abgeordneter Dr. Michael Spindelegger: Ich bedanke mich für das Vertrauen und nehme die Wahl sehr gerne an. (Allgemeiner Beifall.)')
rede_temp = df[mask].iloc[0].rede
rede_fehlend = '''umzugehen. Wir sind dazu bereit und laden Sie alle hier noch einmal ein, bei dieser Aufklärungsarbeit mitzuwirken.
(Beifall bei der SPÖ und bei Abgeordneten der Grünen. – Abg. Grillitsch: Sie haben keine Verantwortung!)'''
start_pos = rede_temp.find('Abgeordneter Dr. Josef Cap (SPÖ)')
df.loc[mask, 'rede'] = rede_temp[start_pos:] + rede_fehlend
Bei Rednern aus der Regierung, wird deren Titel mitangeführt. Dieser muss gesondert behandelt werden. Übrigens beeindruckend, wie viele unterschiedliche Minister/Ministerinnen es gab - es sind 62. Zusätzlich gibt es noch andere "Titel", die nicht zur Regierung gehören, z.B. Volksanwalt, Schriftführerin. Diese werden auch gesondert erfasst.
TITEL_REGIERUNG = (
'Bundeskanzler',
'Bundesminister für Arbeit, Soziales und Konsumentenschutz',
'Bundesminister für Europa, Integration und Äußeres',
'Bundesminister für Finanzen',
'Bundesminister für Finanzen Vizekanzler',
'Bundesminister für Gesundheit',
'Bundesminister für Gesundheit, Familie und Jugend',
'Bundesminister für Inneres',
'Bundesminister für Justiz',
'Bundesminister für Kunst und Kultur, Verfassung und Medien',
'Bundesminister für Kunst und Kultur, Verfassung und öffentlichen Dienst',
'Bundesminister für Land- und Forstwirtschaft, Umwelt und Wasserwirtschaft',
'Bundesminister für Landesverteidigung',
'Bundesminister für Landesverteidigung und Sport',
'Bundesminister für Soziales und Konsumentenschutz',
'Bundesminister für Verkehr, Innovation und Technologie',
'Bundesminister für Wirtschaft und Arbeit',
'Bundesminister für Wirtschaft, Familie und Jugend',
'Bundesminister für Wissenschaft und Forschung',
'Bundesminister für Wissenschaft, Forschung und Wirtschaft',
'Bundesminister für Wissenschaft, Forschung und Wirtschaft Vizekanzler',
'Bundesminister für europäische und internationale Angelegenheiten',
'Bundesminister für europäische und internationale Angelegenheiten Vizekanzler',
'Bundesminister für soziale Sicherheit, Generationen und Konsumentenschutz',
'Bundesminister für soziale Sicherheit, Generationen und Konsumentenschutz Vizekanzler',
'Bundesminister im Bundeskanzleramt',
'Bundesminister ohne Portefeuille',
'Bundesministerin für Unterricht, Kunst und Kultur',
'Bundesministerin für Bildung und Frauen',
'Bundesministerin für Bildung, Wissenschaft und Kultur',
'Bundesministerin für Familien und Jugend',
'Bundesministerin für Finanzen',
'Bundesministerin für Frauen und öffentlichen Dienst',
'Bundesministerin für Frauen, Medien und Regionalpolitik',
'Bundesministerin für Frauen, Medien und öffentlichen Dienst',
'Bundesministerin für Gesundheit',
'Bundesministerin für Gesundheit und Frauen',
'Bundesministerin für Gesundheit, Familie und Jugend',
'Bundesministerin für Inneres',
'Bundesministerin für Justiz',
'Bundesministerin für Unterricht, Kunst und Kultur',
'Bundesministerin für Verkehr, Innovation und Technologie',
'Bundesministerin für Wissenschaft und Forschung',
'Bundesministerin für auswärtige Angelegenheiten',
'Bundesministerin für europäische und internationale Angelegenheiten',
'Bundesministerin für soziale Sicherheit, Generationen und Konsumentenschutz',
'Bundesministerin ohne Portefeuille',
'Staatssekretär im Bundesministerium für Finanzen',
'Staatssekretär im Bundesministerium für Verkehr, Innovation und Technologie',
'Staatssekretärin im Bundesministerium für soziale Sicherheit, Generationen und Konsumentenschutz',
'Staatssekretär im Bundeskanzleramt',
'Staatssekretärin im Bundesministerium für Wirtschaft, Familie und Jugend',
'Staatssekretär im Bundesministerium für europäische und internationale Angelegenheiten',
'Staatssekretärin im Bundesministerium für Verkehr, Innovation und Technologie',
'Staatssekretärin im Bundesministerium für Wirtschaft und Arbeit',
'Staatssekretär im Bundesministerium für Gesundheit und Frauen',
'Staatssekretär im Bundesministerium für soziale Sicherheit, Generationen und Konsumentenschutz',
'Staatssekretär im Bundesministerium für auswärtige Angelegenheiten',
'Staatssekretär im Bundesministerium für Inneres',
'Staatssekretärin im Bundeskanzleram',
'Staatssekretär im Bundesministerium für Wissenschaft, Forschung und Wirtschaft',
'Staatssekretärin im Bundesministerium für Finanzen'
)
TITEL_ANDERE = (
'Präsident des Rechnungshofes',
'Volksanwältin',
'Volksanwalt',
'Schriftführerin',
'Schriftführer',
'Präsident',
'Präsidentin',
'Berichterstatter',
'Berichterstatterin',
'Mitglied des Europäischen Parlaments'
)
# Stellt sicher, dass z.B. 'Bundesminister für europäische und internationale Angelegenheiten Vizekanzler' vor
# 'Bundesminister für europäische und internationale Angelegenheiten Vizekanzler'
TITEL_REGIERUNG = sorted(TITEL_REGIERUNG, reverse=True, key=len)
TITEL_ANDERE = sorted(TITEL_ANDERE, reverse=True, key=len)
len(TITEL_REGIERUNG)
# Funktion um aus der Rede den Redner (Anrede, Name, Partei) auszulesen
def formatieren(rede):
redner, rede_txt = rede.split(':', maxsplit=1)
redner = redner.replace('\n',' ').strip()
rede_txt = rede_txt.replace('\n',' ').strip()
for titel in TITEL_REGIERUNG:
if redner.startswith(titel):
redner_anrede = titel
redner_name = redner.replace(titel,'').strip()
# In Ausnahmefall noch Anmerkung bei Namen , z.B. (mit Beifall von ... begrüßt)
redner_name = re.sub(r'\([^)]*\)', '', redner_name)
redner_partei = 'Regierung'
rede_txt = re.sub(r'\([^)]*\)', '', rede_txt)
return pd.Series({'redner_anrede': redner_anrede, 'redner_name': redner_name,
'redner_partei': redner_partei, 'rede_txt': rede_txt})
for titel in TITEL_ANDERE:
if redner.startswith(titel):
redner_anrede = titel
redner_name = redner.replace(titel,'').strip()
# In Ausnahmefall noch Anmerkung bei Namen , z.B. (mit Beifall von ... begrüßt)
redner_name = re.sub(r'\([^)]*\)', '', redner_name)
redner_partei = 'Andere'
rede_txt = re.sub(r'\([^)]*\)', '', rede_txt)
return pd.Series({'redner_anrede': redner_anrede, 'redner_name': redner_name,
'redner_partei': redner_partei, 'rede_txt': rede_txt})
redner_anrede, redner_name = redner.split(' ', maxsplit=1)
redner_partei = redner_name[redner_name.rfind('(')+1:redner_name.rfind(')')].strip()
if redner_partei in ('zur Geschäftsbehandlung',
'in Übersetzung durch eine Gebärdensprachdolmetscherin',
'in Übersetzung durch einen Gebärdensprachdolmetscher',
'in Übersetzung durch die Gebärdensprachdolmetscherin',
'in Richtung SPÖ, die noch immer Beifall spendet',
'sich zunächst an Dr. Jarolim wendend',
'mit Beifall begrüßt',
'die Höhe des Rednerpultes einstellend',
'fortsetzend'):
redner_name = redner_name[:redner_name.rfind('(')]
if redner_name.count('(') > 0:
redner_partei = redner_name[redner_name.rfind('(')+1:redner_name.rfind(')')].strip()
else:
redner_partei = ''
redner_name = redner_name[:redner_name.rfind('(')].strip()
# Enfernen von Zwischenrufen, die sind immer in Klammern
rede_txt = re.sub(r'\([^)]*\)', '', rede_txt)
return pd.Series({'redner_anrede': redner_anrede, 'redner_name': redner_name,
'redner_partei': redner_partei, 'rede_txt': rede_txt})
%%time
df = pd.concat([df, df.rede.apply(formatieren)], axis=1)
df[:3]
Abgeordnete (männlich und weiblich) haben am meisten Reden gehalten. Verhältnis weiblich zu männlich ist 1 zu 2,6. Der Bundesminister für Arbeit, Soziales und Konsumentenschutz war dann der fleißigste mit 161 Reden.
df.redner_anrede.value_counts()
len(df[df.redner_anrede == 'Abgeordneter']) / len(df[df.redner_anrede == 'Abgeordnete'])
len(df[df.redner_anrede == 'Abgeordnete']) / len(df[df.redner_anrede == 'Abgeordneter'])
Insgesamt gibt es 68 "Berufsbezeichnungen".
len(df.redner_anrede.unique())
Das Thema 'Kultur' wurde in den unterschiedlichsten Ministerien behandelt.
df.loc[df.redner_anrede.str.contains('Kultur'), 'redner_anrede'].value_counts()
df_temp = df.loc[df.redner_anrede.str.contains('Kultur'), ['redner_anrede', 'datum']]
df_temp.groupby([df.datum.dt.year, df.redner_anrede])['redner_anrede'].count()
Wie hat sich das Verhätnis von Reden weiblicher Abgeordneter zu denen von männlichen über die Zeit entwickelt?
df_ratio = df.groupby(df.datum.dt.year)[['redner_anrede']].agg(lambda x:
sum(x == 'Abgeordnete') / sum(x == 'Abgeordneter') * 100)
df_ratio.columns = ['Frau pro Mann']
df_ratio[:3]
sns.set(context='talk', style='whitegrid')
sns.barplot(x='datum', y='Frau pro Mann', data=df_ratio.reset_index())
sns.despine()
sns.set(font_scale=1.5)
sns.plt.title('Reden von weiblichen Abgeordneten pro 100 Reden von männlichen')
sns.set(font_scale=1)
sns.axlabel('Jahr', 'Anzahl')
sns.plt.savefig('geschlecht.svg')
Nun schauen wir uns die Parteien an. Hier sieht man 2 Einträge, ohne Partei. Das wird manuell korregiert.
Im Folgenden behandle ich Reden von Regierungsmitgliedern unter der virtuellen Partei 'Regierung'.
df.redner_partei.value_counts()
Manuelle Korrektur für 2 Fälle, in denen die Partei fehlt.
df.loc[df.redner_partei == '', ['redner_name', 'redner_partei']]
df.loc[(df.redner_partei == '') & (df.redner_name == 'Peter Wurm'), 'redner_partei'] = 'FPÖ'
df.loc[(df.redner_partei == '') & (df.redner_name == 'Wolfgang Zanger'), 'redner_partei'] = 'FPÖ'
df.loc[df.redner_partei == 'Andere', 'redner_anrede'].value_counts()
# NEOS und NEOS-LIF im folgenden als eine Partei betrachtet
df.loc[df.redner_partei == 'NEOS-LIF', 'redner_partei'] = 'NEOS'
# Behandle 'ohne Klubzugehörigkeit' und 'Andere' gemeinsam als 'Andere'.
# Das ist eine Vereinfachung, da Fokus auf den Parteien und Regierung liegt.
df.loc[df.redner_partei == 'ohne Klubzugehörigkeit', 'redner_partei'] = 'Andere'
df.redner_partei.value_counts()
Definieren Reihenfolge der Parteien und zugehörige Farben.
parteien_ordnung = ['Regierung',
'SPÖ',
'ÖVP',
'FPÖ',
'Grüne',
'BZÖ',
'STRONACH',
'NEOS',
'Andere']
# Farbennamen von http://www.luminoso.com/colors/
parteien_farben = ['grey', #Regierung
'red',
'black',
'blue',
'green',
'orange',
'yellow',
'pink',
'light grey'] #Andere
sns.palplot(sns.xkcd_palette(parteien_farben))
df.groupby(['redner_partei', 'periode'])[['key']].count().unstack()
sns.set(context='talk', style='whitegrid', palette=sns.xkcd_palette(parteien_farben))
sns.countplot(y='redner_partei', data=df, order=parteien_ordnung)
sns.despine()
sns.set(font_scale=1.5)
sns.plt.title('Reden pro Partei')
sns.set(font_scale=1)
sns.axlabel('Reden', '')
sns.plt.savefig('reden_pro_partei.svg')
Wie hat sich die relative Anzahl an Reden pro Partei über die Zeit verändert?
df_temp = df.groupby([df.datum.dt.year, 'redner_partei'])[['key']].count().unstack()
df_temp[:3]
df_temp = df_temp.apply(lambda x: x / np.sum(x), axis=1) * 100
df_temp[:3]
df_temp = df_temp.stack().reset_index()
df_temp = df_temp.rename(columns={'redner_partei': 'Partei'})
df_temp[:3]
sns.set(context='talk', style='whitegrid', palette=sns.xkcd_palette(parteien_farben))
ax = sns.factorplot(x = 'datum', y = 'key', hue = 'Partei' , data = df_temp, size=8, hue_order=parteien_ordnung)
ax.set(ylim=(0, 30))
sns.despine()
sns.set(font_scale=1.5)
sns.plt.title('% Reden pro Partei')
sns.set(font_scale=1)
sns.axlabel('Jahr', 'Prozent')
sns.plt.savefig('reden_pro_partei_pro_jahr.svg')
Eine andere Darstellung der Verteilung der Reden pro Partei.
df_temp = (df.groupby([df.datum.dt.year, df.redner_partei])['key'].count()).unstack()
df_temp = df_temp[parteien_ordnung]
df_temp = df_temp.transpose()
mask = df_temp.isnull()
df_temp = df_temp.fillna(value=0)
df_temp = df_temp.astype('int')
df_temp[:13]
sns.set(context='talk', style='whitegrid', palette=sns.xkcd_palette(parteien_farben))
sns.heatmap(df_temp, annot=True, linewidths=.5, fmt='d', mask=mask)
#sns.despine()
sns.set(font_scale=1.5)
sns.plt.title('Anzahl Reden (in 100 Reden)')
sns.set(font_scale=1)
sns.axlabel('Partei', 'Jahr')
#sns.plt.savefig('geschlecht.svg')
Wie hat sich das Verhältnis der Reden von weiblichen Abgeordneten zu denen von männlichen pro Partei (exkl. Regierung) über die Zeit entwickelt?
df_ratio = df.groupby([df.datum.dt.year, 'redner_partei'])[['redner_anrede']].agg(lambda x:
sum(x == 'Abgeordnete') / sum(x == 'Abgeordneter')*100)
df_ratio = df_ratio.reset_index()
df_ratio = df_ratio.rename(columns={'redner_partei': 'Partei', 'redner_anrede': 'Rednerin pro Redner'})
df_ratio = df_ratio.loc[df_ratio.Partei != 'Regierung']
df_ratio[:3]
parteien_ordnung_ohne_regierung = parteien_ordnung.copy()
parteien_ordnung_ohne_regierung.remove('Regierung')
parteien_farben_ohne_regierung = parteien_farben.copy()
parteien_farben_ohne_regierung.remove('grey')
sns.set(context='talk', style='whitegrid', palette=sns.xkcd_palette(parteien_farben_ohne_regierung))
ax = sns.factorplot(x = 'datum', y = 'Rednerin pro Redner', hue = 'Partei' , data = df_ratio, size=8,
hue_order=parteien_ordnung_ohne_regierung)
ax.set(ylim=(0, 110))
sns.despine()
sns.set(font_scale=1.5)
sns.plt.title('Reden weiblicher Abgeordneter je 100 Reden von männlichen Abgeordneten')
sns.set(font_scale=1)
sns.axlabel('Jahr', 'Reden')
sns.plt.savefig('geschlecht_je_partei.svg')
Wie ist z.B. der Rückgang bei den NEOS zu erklären?
df[df.redner_partei == 'NEOS'].groupby([df.datum.dt.year, 'redner_anrede', 'redner_name'])[['key']].count().unstack('datum')
df[df.redner_partei == 'STRONACH'].groupby([df.datum.dt.year, 'redner_anrede', 'redner_name'])[['key']].count().unstack('datum')
# from http://stackoverflow.com/questions/17507876/trying-to-count-words-in-a-string
def anzahl_woerter(txt):
return len(re.findall(r'\b\w+\b', txt))
anzahl_woerter('Johnny.Appleseed!is:a*good&farmer')
%%time
df['rede_anzahl_woerter'] = df.rede_txt.apply(anzahl_woerter)
df[:3]
df_temp = (df.groupby(df.datum.dt.year)
.agg({'rede_anzahl_woerter': 'sum', 'key': 'count'})
.rename(columns={'rede_anzahl_woerter': 'woerter', 'key': 'reden'}))
df_temp['woerter_pro_rede'] = df_temp['woerter'] / df_temp['reden']
df_temp[:3]
sns.set(context='talk', style='whitegrid', palette=sns.xkcd_palette(parteien_farben))
sns.barplot(x='datum', y='woerter_pro_rede', data=df_temp.reset_index())
sns.despine()
sns.set(font_scale=1.5)
sns.plt.title('Durchschnittliche Anzahl an Worten pro Rede je Jahr')
sns.set(font_scale=1)
sns.axlabel('Jahr', 'Wörter pro Rede')
sns.plt.savefig('woerter_pro_rede_je_jahr.svg')
df_temp = (df.groupby('redner_partei')
.agg({'rede_anzahl_woerter': 'sum', 'key': 'count'})
.rename(columns={'rede_anzahl_woerter': 'woerter', 'key': 'reden'}))
df_temp['woerter_pro_rede'] = df_temp['woerter'] / df_temp['reden']
df_temp[:3]
sns.set(context='talk', style='whitegrid', palette=sns.xkcd_palette(parteien_farben))
sns.barplot(x='redner_partei', y='woerter_pro_rede', data=df_temp.reset_index(), order=parteien_ordnung)
sns.despine()
sns.set(font_scale=1.5)
sns.plt.title('Durchschnittliche Anzahl an Worten pro Rede je Partei')
sns.set(font_scale=1)
sns.axlabel('Partei', 'Wörter pro Rede')
sns.plt.savefig('woerter_pro_rede_je_partei.svg')
df_temp = (df.groupby([df.datum.dt.year, 'redner_partei'])
.agg({'rede_anzahl_woerter': 'sum', 'key': 'count'})
.rename(columns={'rede_anzahl_woerter': 'woerter', 'key': 'reden'}))
df_temp['woerter_pro_rede'] = df_temp['woerter'] / df_temp['reden']
df_temp = df_temp.woerter_pro_rede
df_temp = df_temp.unstack()
df_temp = df_temp / 100
df_temp = df_temp[parteien_ordnung]
df_temp = df_temp.transpose()
df_temp[:3]
sns.set(context='talk', style='whitegrid', palette=sns.xkcd_palette(parteien_farben))
sns.heatmap(df_temp, annot=True, linewidths=.5)
sns.despine()
sns.set(font_scale=1.5)
sns.plt.title('Durchschnittliche Wörter pro Rede (in 100 Wörtern)')
sns.set(font_scale=1)
sns.axlabel('Jahr', 'Partei')
#sns.plt.savefig('geschlecht.svg')