@Vesemir: Ich probiere mich dann auch mal am Original. Es wurde ja schon einiges gezeigt und gesagt.
Funktionen bekommen alles was sie ausser Konstanten benötigen, als Argument(e) übergeben. Globale Variablen und dann Funktionen die einfach magisch auf solche zugreifen sind schlecht, weil unübersichtlich und fehleranfällig. `tokenize()` braucht beispielsweise die Stoppworte als Argument, statt einfach so auf irgendwo in der ”Umgebung” existierende Stoppworte zuzugreifen.
Pandas zum Einlesen der SentiWS-CSV-Dateien ist Overkill. Bei Pandas sollte man immer wenn man `iterrows()` verwendet sowieso mal kurz innehalten und überlegen was man da macht, denn das ist genau das was man in Pandas ja eigentlich nicht machen will — selber manuell über alle Zeilen iterieren.
Insgesamt wird Pandas hier arg missbraucht, denn auch das Speichern von Listen in einzelnen Zellen ist nicht schön oder sinnvoll. Zudem ist das auch nur ein Zwischenwert, der nicht wirklich verwendet wird in dem DataFrame.
Einbuchstabige Namen sind in der Regel keine guten Namen. Zeichen in Namen kosten nichts, und man sollte eher auf Lesbarkeit Wert legen denn auf wenig Tippen. Quelltext wird deutlich häufiger gelesen als geschrieben, und mit Autovervollständigung in Editoren kann man auch nicht mehr wirklich mit zu viel Mehrarbeit beim schreiben argumentieren.
Grunddatentypen haben in Namen nichts zu suchen. Den Leser interessiert ja in aller Regel nicht was für einen Typ er da hat, sondern was der Wert bedeutet. Bei Abbildungen wie Wörterbüchern bietet es sich an im Namen zu kodieren was die Schlüssel und die Werte bedeuten. Also beispielsweise `word_to_score` statt `senti_dict`, weil dort Worte auf Bewertungen abgebildet werden.
Sirius3 hat \w+ als regulären Ausdruck für Worte verwendet. Das ist nicht einfach nur kürzer, sondern erfasst nicht nur Umlaute und ß. Auch in ”deutschen” Worten können andere Zeichen, zum Beispiel mit Accents vorkommen.
Das `sentiment_by_speaker` am Ende sollte man vielleicht auch ausgeben. Sonst passiert an der Stelle einfach nichts.
`file` ist ein passender Name für ein Dateiobjekt, wo man so Methoden wie `close()` oder `read()` erwartet, aber nicht für einen Datei*namen*. Das wäre `filename`. Das wurde dadurch umschifft, dass das eigentliche `file` im Quelltext `f` genannt wurde.
`str.replace()` ersetzt in der gesamten Zeichenkette, nicht nur am Ende. Wenn man etwas am Ende entfernen möchte, nimmt man `str.removesuffix()`. Wenn man mit Dateinamen und -pfaden arbeitet, sollte man aber am besten `pathlib.Path` verwenden und da nicht drauf operieren als wären es beliebige Zeichenketten.
`str.split()` das auf eine Trennstelle begrenzt wird, ist eigentlich `str.partition()`.
Zwischenstand (ungetestet):
Code: Alles auswählen
#!/usr/bin/env python3
import csv
import re
from pathlib import Path
import nltk
import pandas as pd
from nltk.corpus import stopwords
def load_sentiws_simple(path):
with open(path, encoding="utf-8", newline="") as file:
return {
word.lower(): float(score) for _, word, score in csv.reader(file)
}
def tokenize(stop_words, text):
return [
match[0]
for match in re.finditer(r"\w+", text.lower())
if match[0] not in stop_words
]
def calculate_sentiws_score(word_to_score, tokens):
return sum(word_to_score.get(token, 0) for token in tokens)
def main():
filenames = ["2025_Bundespräsident.txt"]
nltk.download("stopwords")
german_stop_words = set(stopwords.words("german"))
word_to_score = {
**load_sentiws_simple("SentiWS_ML_negativ.csv"),
**load_sentiws_simple("SentiWS_ML_positiv.csv"),
}
print("Wörter im Lexikon:", len(word_to_score))
speeches = []
for path in map(Path, filenames):
year, _, speaker = path.name.partition("_")
text = path.read_text(encoding="utf-8")
speeches.append(
{
"Jahr": int(year),
"Sprecher": speaker,
"Text": text,
"SentiWS_Score": calculate_sentiws_score(
word_to_score, tokenize(german_stop_words, text)
),
}
)
sentiment_by_speaker = (
pd.DataFrame(speeches)
.groupby("Sprecher")["SentiWS_Score"]
.mean()
.sort_values(ascending=False)
)
print(sentiment_by_speaker)
if __name__ == "__main__":
main()
Pandas könnte man mit ein bisschen Code hier auch noch loswerden mit der `sort()`-Methode auf Listen oder der `sorted()`-Funktion und `itertools.groupby()` und `statistics.mean()` aus der Standardbibliothek.
Ebenfalls ungetestet:
Code: Alles auswählen
#!/usr/bin/env python3
import csv
import re
from itertools import groupby
from operator import itemgetter
from pathlib import Path
from statistics import mean
import nltk
from nltk.corpus import stopwords
def load_sentiws_simple(path):
with open(path, encoding="utf-8", newline="") as file:
return {
word.lower(): float(score) for _, word, score in csv.reader(file)
}
def tokenize(stop_words, text):
return [
match[0]
for match in re.finditer(r"\w+", text.lower())
if match[0] not in stop_words
]
def calculate_sentiws_score(word_to_score, tokens):
return sum(word_to_score.get(token, 0) for token in tokens)
def main():
filenames = ["2025_Bundespräsident.txt"]
nltk.download("stopwords")
german_stop_words = set(stopwords.words("german"))
word_to_score = {
**load_sentiws_simple("SentiWS_ML_negativ.csv"),
**load_sentiws_simple("SentiWS_ML_positiv.csv"),
}
print("Wörter im Lexikon:", len(word_to_score))
speeches = []
for path in map(Path, filenames):
year, _, speaker = path.name.partition("_")
text = path.read_text(encoding="utf-8")
speeches.append(
{
"Jahr": int(year),
"Sprecher": speaker,
"Text": text,
"SentiWS_Score": calculate_sentiws_score(
word_to_score, tokenize(german_stop_words, text)
),
}
)
get_speaker = itemgetter("Sprecher")
speeches.sort(key=get_speaker)
sentiment_by_speaker = [
(mean(speech["SentiWS_Score"] for speech in group), speaker)
for speaker, group in groupby(speeches, get_speaker)
]
sentiment_by_speaker.sort(reverse=True)
for mean_score, speaker in sentiment_by_speaker:
print(f"{speaker:>20} {mean_score}")
if __name__ == "__main__":
main()