[Pp]rüfung(srelevant)?
entschlüsseln?Laden wir zunächst einen Korpus. Dazu können Sie sich des frei vergübaren Ten Thousand German News Articles Dataset bedienen. Es enthält, nun ja, zehntausend Artikel der österreichischen Tageszeitung Der Standard. Laden Sie sich den Datensatz auf Ihren Computer herunter und achten Sie beim Ausführen des Codes auf passende Pfade. Der Code zum Laden der Artikel in eine Variable (articles
) ist hier vorgegeben. Jeder Artikel besteht dabei aus einem Ressort, dem er entstammt, und dem eigentlichen Text. Und längst wissen wir, wie wir die genaue Zahl der in einem solchen DataFrame (oder Tibble) enthaltenen Zeilen (hier also Artikel) ermitteln können.
# Python
import pandas as pd
articles = pd.read_csv('10kgnad_articles.csv', sep = ';', header = None, names = [ 'ressort', 'article' ], quotechar = "'")
# R
library(tidyverse)
articles <- read_csv2('10kgnad_articles.csv', col_names = c('ressort', 'article'), quote = "'", col_types = 'cc')
Ganz brachial nach Volltexten zu suchen, funktioniert in Python über die contains-pandas-Funktion und in R über die str_detect-tidyverse-Funktion. Beiden übergeben Sie die Spalte des Dataframes, die durchsucht werden soll (bei uns: article
) und den Suchbegriff.
# Python
articles.loc[articles['article'].str.contains('suchbegriff')]
# R
articles %>%
filter(str_detect(article, 'suchbegriff'))
Um reguläre Ausdrücke zu suchen, können Sie schlicht dieselbe Funktion nutzen und einfach den regulären Ausdruck direkt übernehmen.
# Python
articles.loc[articles['article'].str.contains('[Ss]uchbegriff')]
# R
articles %>%
filter(str_detect(article, '[Ss]uchbegriff'))
Sebastian Kurz
vorkommt. Wie viele Artikel finden Sie?Nun wollen wir die Artikeltexte tokenisieren und überführen sie zunächst schlicht in Unigramme. Dafür können wir auf die sehr umfangreichen und frei verfügbaren Pakete nltk in Python bzw. quanteda in R zurückgreifen. Die Pakete funktionieren unterschiedlich. R fokussiert auf Formalia und ist entsprechend einfach in der Handhabung, benötigt aber zunächst ein sogenanntes “Corpus”-Objekt. Python hingegen kann direkt mit dem Dataframe arbeiten, braucht aber sprachspezifische Informationen, die zunächst heruntergeladen und entsprechend konfiguriert werden müssen. Außerdem geben die Funktionen etwas unterschiedliche Werte zurück: Während die nltk-tokenize-Funktionen einzelne Texte als Token-Arrays zurückgeben, die wir zum Beispiel wieder an den ursprünglichen DataFrame anhängen können, retournieren die quanteda-tokens-Funktionen ein eigenes Objekt, das wir separat weiterverarbeiten. Das Ergebnis sieht indes bei beiden (nahezu) gleich aus.
# Python
## zunächst Installation der Konfiguration
import nltk
nltk.download('punkt')
## eigentliche Tokenisierung
from nltk.tokenize import word_tokenize
articles['tokens_unigram'] = articles['article'].apply(word_tokenize, language='german')
articles.head(2)
# R
library(quanteda)
articles_corpus <- corpus(articles, text_field = 'article')
articles_tokens <- tokens(articles_corpus, what = 'word')
articles_tokens %>%
head(n = 2)
Wenn wir uns Diktionären / Lexika zuwenden, dann gibt es einerseits die Möglichkeit, selbst eine Liste an Wörtern als Diktionär anzulegen und damit zu arbeiten, oder andererseits auf veröffentliche Diktionäre zurückzugreifen, die entsprechend validiert sind. Für den Moment wenden wir uns einem bekannten Sentiment-Diktionär von Christian Rauh zu. Er hat darin viele (viele!) Wörter mit Werten versehen, die angeben, ob ein Wort eher positiv (+1
) oder eher negativ (-1
) emotional aufgeladen ist. Das Wort “fürchterlich” beispielsweise weist einen negativen Wert auf, während das Wort “Frühlingsgefühl” wohl eher positiv konnotiert ist. Für diese Übung wurde das Diktionär in ein einfaches CSV-Format überführt und kann direkt heruntergeladen und im Skript geladen werden.
Anschließend unterscheiden sich Python und R allerdings. In Python können wir nun Wörter in den Dokumenten in einer Schleife durchlaufen und jeweils nachschauen, ob sie im Diktionär hinterlegt sind. Wenn ja, nehmen wir den entsprechenden Wert (+1
oder -1
) mit auf und summieren am Ende alle gefundenen Begriffe eines Dokuments in einen Dokument-Score (sodass sich beispielsweise ein negativ und ein positiv aufgeladenes Wort zu einem neutralen Sentiment zusammenfügen). Mit R ist das etwas einfacher: Hier können wir mit der quanteda-dictionary-Funktion aus der Liste an Wörtern ein Diktionär generieren, das wir dann schlicht auf unsere Tokens anwenden. Die Schleifen und Rechenvorgänge übernimmt dabei das quanteda-Paket für uns und übergibt uns am Ende ein Objekt, in dem nurmehr die Begriffe “positiv” und “negativ” als Dokument-Inhalte übrig bleiben – so oft sie eben entsprechende Wörter im Original-Dokument ersetzen können. Anschließend müssen wir nur noch das etwas unhandliche Datenformat auflösen, mit den ursprünglichen Textinformationen (Ressorts) zusammenbringen und die “positiv”- und “negativ”-Wörter je Dokument in einen Sentiment-Score umwandeln.
In folgendem Beispiel laden wir also das Diktionär von Rauh und wenden es auf unsere Dokumente an. Das dauert in R aufgrund seiner Stapelverarbeitung nicht sonderlich lange, in Python schon deutlich länger.
# Python
rauh_words = pd.read_csv('rauh_sentiment_dict.csv', sep = ',')
rauh_dict_positiv = list(rauh_words.loc[rauh_words['sentiment'] == 1].feature.str.strip())
rauh_dict_negativ = list(rauh_words.loc[rauh_words['sentiment'] == -1].feature.str.strip())
articles['tokens_unigram'] = articles['article'].apply(word_tokenize, language='german')
articles['sentiment_positiv'] = articles['article'].apply(lambda a: sum([1 if word in a else 0 for word in rauh_dict_positiv]))
articles['sentiment_negativ'] = articles['article'].apply(lambda a: sum([1 if word in a else 0 for word in rauh_dict_negativ]))
articles['sentiment'] = articles['sentiment_positiv'] - articles['sentiment_negativ']
# R
rauh_words <- read_csv('rauh_sentiment_dict.csv')
rauh_dict <- dictionary(list(positiv = (rauh_words %>%
filter(sentiment == '1') %>%
distinct(feature) %>%
pull(feature)),
negativ = (rauh_words %>%
filter(sentiment == '-1') %>%
distinct(feature) %>%
pull(feature))))
articles_corpus <-
articles %>%
corpus(text_field = 'article')
articles_tokens <-
articles_corpus %>%
tokens(what = 'word')
articles <-
articles_tokens %>%
tokens_lookup(dictionary = rauh_dict) %>%
dfm() %>%
convert(to = 'data.frame') %>%
as_tibble() %>%
bind_cols(articles) %>%
mutate(sentiment = positiv - negativ)
Kollokationsmatrizen werden auch “feature co-occurrence matrices” oder kurz FCM genannt. Es gibt über die in den Unterlagen vorgestellten Verfahren hinausgehende Möglichkeiten, Ko-Vorkommnisse zwischen Begriffen (“features”) zu berechnen. Um das Beispiel-System aus den Unterlagen aber nachvollziehen zu können, setzen wir auf die angesprochenen fünf Begriffe vor/nach einem Suchbegriff und eine Gewichtung zu dieser Distanz. Damit der Computer nicht überfordert ist, erstellen wir diese Kollokationsmatrix aber nur für die ersten zehn Dokumente.
Erneut können wir in R auf eine fertige Funktion aus dem quanteda-Paket zurückgreifen, müssen uns aber mit einigem Drumherum rumschlagen, während wir in Python mithilfe von nltk und pandas alles selbst bauen müssen.
# Python
articles_sample = articles.head(10)
unigram_tokens_sample = [ token for token_list in articles_sample['tokens_unigram'].tolist() for token in token_list ]
unigram_types_sample = list(set(unigram_tokens_sample))
fcm = pd.DataFrame(columns=unigram_types_sample)
for single_type in unigram_types_sample:
fcm.loc[len(fcm)] = 0
for article_index in range(0, len(articles_sample)):
article_tokens = articles_sample.loc[article_index, 'tokens_unigram']
for current_token_position in range(0, len(article_tokens)):
current_token = article_tokens[current_token_position]
for position_window in range(1, 6):
if current_token_position + position_window < len(article_tokens):
current_comparing_token = article_tokens[current_token_position + position_window]
fcm.loc[unigram_types_sample.index(current_token), current_comparing_token] += 1/position_window
if current_token_position - position_window >= 0:
current_comparing_token = article_tokens[current_token_position - position_window]
fcm.loc[unigram_types_sample.index(current_token), current_comparing_token] += 1/position_window
print(fcm)
# R
articles %>%
slice_head(n = 10) %>%
corpus(text_field = 'article') %>%
tokens(what = 'word') %>%
fcm(context = 'window',
window = 5,
count = 'weighted',
weights = 1/1:5) %>%
convert(to = 'data.frame') %>%
as_tibble()
Die Klausur, die die Studierenden schreiben, ist anspruchsvoll.
.Ab hier folgen nun verschiedene Lösungswege zu den oben vorgestellten Übungen. Damit Sie die nicht “versehentlich” überscrollen und so Ihrer Übungsmöglichkeiten beraubt werden, folgt hier zunächst ein visueller Bruch.
# Python
import pandas as pd
articles = pd.read_csv('10kgnad_articles.csv', sep = ';', header = None, names = [ 'ressort', 'article' ], quotechar = "'")
articles.count()
articles.groupby(['ressort']).size()
# R
library(tidyverse)
articles <- read_csv2('10kgnad_articles.csv', col_names = c('ressort', 'article'), quote = "'", col_types = 'cc')
articles %>%
count()
articles %>%
count(ressort)
# Python
articles.loc[articles['article'].str.contains('Sebastian Kurz')]
articles.loc[articles['article'].str.contains('K[aä]tz(ch)?en')]
articles.loc[articles['article'].str.contains('Sebastian Kurz')].groupby(['ressort']).size()
articles.loc[articles['article'].str.contains('K[aä]tz(ch)?en')].groupby(['ressort']).size()
# R
articles %>%
filter(str_detect(article, 'Sebastian Kurz'))
articles %>%
filter(str_detect(article, 'K[aä]tz(ch)?en'))
articles %>%
filter(str_detect(article, 'Sebastian Kurz')) %>%
count(ressort)
articles %>%
filter(str_detect(article, 'K[aä]tz(ch)?en')) %>%
count(ressort)
# Python
from nltk.tokenize import sent_tokenize
articles['tokens_sentence'] = articles['article'].apply(sent_tokenize, language='german')
sentence_tokens = [ token for token_list in articles['tokens_sentence'].tolist() for token in token_list ]
sentence_types = list(set(sentence_tokens))
len(sentence_tokens)
len(sentence_types)
len(sentence_types)/len(sentence_tokens)
unigram_tokens = [ token for token_list in articles['tokens_unigram'].tolist() for token in token_list ]
unigram_types = list(set(unigram_tokens))
len(unigram_tokens)
len(unigram_types)
len(unigram_types)/len(unigram_tokens)
# R
sentence_tokens <- tokens(articles_corpus, what = 'sentence')
sentence_types <- types(sentence_tokens)
length(sentence_tokens)
length(sentence_types)
length(sentence_types)/length(sentence_tokens)
unigram_tokens <- tokens(articles_corpus, what = 'word')
unigram_types <- types(unigram_tokens)
length(unigram_tokens)
length(unigram_types)
length(unigram_types)/length(unigram_tokens)
# Python
articles[['ressort', 'sentiment']].groupby('ressort').agg({'sentiment': 'mean'})
dict_kanzlerschaft = ['Merkel', 'Kanzlerin', 'Bundeskanzlerin', 'Regierungschefin']
articles['kanzlerschaft'] = articles['article'].apply(lambda a: sum([1 if word in a else 0 for word in dict_kanzlerschaft]))
articles.loc[articles['kanzlerschaft'] > 0].count()
articles.loc[articles['kanzlerschaft'] > 0].groupby(['ressort']).size()
# R
articles %>%
group_by(ressort) %>%
summarise(mean_sentiment = mean(sentiment))
dict_kanzlerschaft <- dictionary(list(kanzlerschaft = c('Merkel', 'Kanzlerin', 'Bundeskanzlerin', 'Regierungschefin')))
articles_kanzlerschaft <-
unigram_tokens %>%
tokens_lookup(dictionary = dict_kanzlerschaft) %>%
dfm() %>%
convert(to = 'data.frame') %>%
as_tibble() %>%
bind_cols(articles)
articles_kanzlerschaft %>%
filter(kanzlerschaft > 0) %>%
count()
articles_kanzlerschaft %>%
filter(kanzlerschaft > 0) %>%
count(ressort)
# Python
tokens = [ 'die', 'klausur', ',', 'die', 'die', 'studierenden', 'schreiben', ',', 'ist', 'anspruchsvoll', '.' ]
types = list(set(tokens))
fcm = pd.DataFrame(columns=types)
for single_type in types:
fcm.loc[len(fcm)] = 0
for current_token_position in range(0, len(tokens)):
current_token = tokens[current_token_position]
for position_window in range(1, 6):
if current_token_position + position_window < len(tokens):
current_comparing_token = tokens[current_token_position + position_window]
fcm.loc[types.index(current_token), current_comparing_token] += 1/position_window
if current_token_position - position_window >= 0:
current_comparing_token = tokens[current_token_position - position_window]
fcm.loc[types.index(current_token), current_comparing_token] += 1/position_window
print(fcm)
# R
'Die Klausur, die die Studierenden schreiben, ist anspruchsvoll.' %>%
tokens() %>%
tokens_tolower() %>%
fcm(context = 'window',
window = 5,
count = 'weighted',
weights = 1/1:5) %>%
convert(to = 'data.frame') %>%
as_tibble()