How to: Texte mit Python analysieren Teil 2
Im ersten Teil haben wir das theoretische Grundkonzept von Text Mining erläutert. In diesem Teil wollen wir ein paar Dinge praktisch anwenden und uns mal ansehen, wie man so Texte mit Python analysieren kann und das alles an einem konkreten Beispiel aus den Texten herauskristallisieren kann.
Den ersten Teil könnt ihr unter diesem Link finden. Es wird kurz angerissen, was man mit Text Mining machen kann. Welche Machine Learning Verfahren es gibt und wie die grundsätzlichen Pre-Processing Schritte aussehen. Außerdem werden 2 Packages zur Textverarbeitung vorgestellt. In diesem Artikel werden wir nun die folgenden Themen, am Beispiel eines Trump Twitter Datensatzes, behandeln:
- Erklärung des Datensatzes
- Erste Untersuchung des Datensatzes
- Preprocessing Schritte
- Gewinnen der ersten Informationen & Erkenntnisse
- Ausblick auf weitere Analysen
Erklärung des Datensatzes
Für diesen Artikel habe ich mir einen bestehenden Datensatz von Kaggle rausgesucht. Ihr könnt ihn direkt hier downloaden: Trump Tweets | Kaggle. Er enthält eine Sammlung von allen Trump Tweets über mehrere Jahre verteilt. Dabei besteht der Datensatz aus den folgenden Spalten.
- id – enthält die ID des tweets im Datensatz
- link – enthält den Link zum Tweet auf Twitter
- content – der Textinhalt des Tweets
- date – Datum wann der Tweet getwittert wurde
- retweets – Wie häufig der Tweet geretweetet wurde
- favorites – Wie viele den Tweet als Favorit markiert haben
- Hashtags – enthält die Hashtags zum jeweiligen Tweet
- geo – enhält die Geo Daten wenn welche übermittelt wurden
Nach dem Download des Datensatzes und dem Anlegen eines Jupyter Notebooks müssen erstmal die wichtigsten Packages geladen werden.
import pandas as pd import spacy from collections import Counter import matplotlib.pyplot as plt import seaborn as sns import re
Pandas werden wir zum einlesen der Daten verwenden. SpaCy ist das Tool um verschiedene NLP Methoden auf den Datensatz anwenden zu können. collections bzw. Counter wird uns helfen die Wörter zu zählen. Für die Visualisierung der Erkenntnisse werden wir vor allem matplotlib und seaborn verwenden. Das RegEx Modul wird benötigt um die Texte zu standardisieren und bestimmte Begriffe zu entfernen.
Wenn ihr mehr über RegEx erfahren wollt. Dann lest euch am besten mal die Dokumentation des Moduls durch: Python RegEx Dokumentation.
Nun müssen wir für Spacy noch ein entsprechendes Sprachmodell laden. Diese könnt ihr über euer Jupyter Lab Terminal downloaden. wie das funktioniert wird auf der SpaCy Webseite erklärt: Spacy.io
Nun könnt ihr euer heruntergeladenes Sprachmodell und die dazugehörigen Stoppwörter laden. Ich habe mir dabei das Sprachmodell en_core_web_md ausgesucht. Danach lade ich die Daten in mein Programm und filtere das Dataframe auf Datensätze die in 2019 und danach gepostet wurden.
nlp = spacy.load('en_core_web_md') spacy_stopwords = spacy.lang.en.stop_words.STOP_WORDS # data load and filter on the beginning of 2019 df = pd.read_csv("./archive/trumptweets.csv",sep=',') df = df[df.date> '2019-01-01']
Prima nun können wir in die Untersuchung unseres Datensatzes einsteigen.
Untersuchung des Datensatzes
als erstes wollen wir uns mal ansehen, wie viele Einträge es in der Spalte Content gibt, wie viele davon Null sind und was der letzte Tweet im Datensatz ist.
# overview about number of tweets print('Anzahl Datensätze: ', df['content'].count()) print('Anzahl Null Datensätze: ', df['content'].isnull().sum()) print('Datum letzter Tweet: ', df['date'].max())
Wie man sieht haben wir 4.769 Zeilen in unseren Datensatz. Davon sind alle befüllt und keiner hat einen Nullwert. Das ist gut. Dann müssen wir an dieser Stelle schonmal keine Bereinigung vornehmen. Der letzte Tweet wurde am 20.01.2020 um 02:57:49 veröffentlicht. Das bedeutet aus 2020 haben wir eigentlich keine Daten und die Daten beschränken sich auf 2019. Denkt dran, dass wir den Datensatz zu Beginn schon auf alles ab 01.01.2019 eingegrenzt haben. Im nächsten Schritt können wir den content mit dem SpaCy Package bearbeiten und danach den Output ansehen.
# process the data with SpaCy and view a sample df['doc'] =[nlp(text) for text in df.content] df.sample(3)
In der Spalte Doc ist nun der Tokenisierte Text eingelesen. Dabei bildet jedes Wort ein eigenes Token, bei dem zusätzliche Informationen integriert sind (z.B. besitzt ein Token eine Information ob es sich um ein Nomen oder Verb handelt. Oder weitere Informationen je nach Sprachmodell das eingesetzt wird). Dann lasst uns doch mal gucken, wie viele Wörter wir in dem Datensatz haben und wie viele davon Unique sind. Ich habe das Ergebnis am Ende noch in eigene Variablen geschrieben, da wir diese in der Visualisierung wieder verwenden wollen.
all_words = [] for text in df.doc: wordrow = [word.text for word in text] all_words.extend(wordrow) print ('Number of Words: ', len(all_words)) # word count word_freq = Counter(all_words) print ('Numer of Unique Words: ', len(word_freq)) word_count_org = len(all_words) unique_words_org = len(word_freq)
Was wir sehen können ist, dass der Datensatz 168.987 Wörter in Summe enthält und 13.891 Unique Wörter. Wenn ich mir nun die 50 am häufigsten vorkommenden Wörter betrachte stellen wir fest, dass die meisten Wörter eher wenig Information über den Sinn des Textes geben.
common_words = word_freq.most_common(50) # plot it for nice overview temp = pd.DataFrame(common_words) temp.columns = ['Common_words','count'] temp.style.background_gradient(cmap='Blues')
Hier hilft uns aber nun SpaCy und das RegEx Package dabei, den Text zu standardisieren und die Komplexität zu reduzieren.
Preprocessing Schritte
Jetzt verändern wir aktiv die Daten in unserem Datensatz. Dazu definieren wir vor allem 3 Funktionen.
- clean_text: bereinigt den text von Sonderzeichen, Punkt und Komma, typischen HTML Tags etc. Hier wird vor allem das RegEx Modul angewandt.
- remove_stopwords: Entfernt Füllwörter wie „The“ oder „and“ die kaum Informationswert für den Inhalt des Textes haben.
- wordcounter: Zählt für jeden Text der übermittelt wird die Anzahl der Wörter
# clean text from emojis and hashtags def clean_text(text): if isinstance((text), (str)): text = re.sub('<[^>]*>', '', text) text = re.sub('[\d]+', '', text.lower()) text = re.sub('"', '', text.lower()) text = re.sub('\[.*?\]', '', text) text = re.sub('https?://\S+|www\.\S+', '', text) text = re.sub('<.*?>+', '', text) text = re.sub('\n', '', text) text = re.sub('\w*\d\w*', '', text) text = re.sub('\'','', text) return text if isinstance((text), (list)): return_list = [] for i in range(len(text)): temp_text = re.sub('<[^>]*>', '', text[i]) temp_text = re.sub('[\d]+', '', temp_text.lower()) temp_text = re.sub('"', '', temp_text.lower()) temp_text = re.sub('\[.*?\]', '', temp_text) temp_text = re.sub('https?://\S+|www\.\S+', '', temp_text) temp_text = re.sub('<.*?>+', '', temp_text) temp_text = re.sub('\n', '', temp_text) temp_text = re.sub('\w*\d\w*', '', temp_text) temp_text = re.sub('\'','', temp_text) return_list.append(temp_text) return(return_list) else: pass # Remove stopwords from the text def remove_stopwords(text): wordrow = [word for word in text if word.is_stop != True and word.is_punct != True] return wordrow # get number of words of the given text back def wordcounter(text): freq = len(text) return freq
Diese Funktionen rufen wir nun einfach noch mit den folgenden Codezeilen auf und hängen die Ergebnisse ebenfalls an unseren Datensatz an.
df['processed_txt'] = df['content'].apply(lambda x : clean_text(x)) df['processed_txt'] = [nlp(text) for text in df.processed_txt] df['processed_txt'] = df['processed_txt'].apply(lambda x : remove_stopwords(x)) df['word_numb_orig'] = df['doc'].apply(lambda x : wordcounter(x)) df['word_numb_cleaned'] = df['processed_txt'].apply(lambda x : wordcounter(x)) df.head()
Damit sind wir auch mit dem Preprocessing soweit schon durch und können direkt mit den Visualisierungen starten.
Gewinnen der ersten Informationen & Erkenntnisse
So dann schauen wir uns doch mal zuerst die Ergebnisse vom Pre-Processing an. Zuerst mal die häufigsten Wörter im Datensatz. Hierbei sehen wir, dass die Wörter nun auch schon erste Rückschlüsse auf die Themen bzw. Inhalte geben lassen. Das häufigste Wort das vorkommt ist dabei „great“, was nicht wirklich überraschend ist. Außerdem scheinen die Tweets sehr häufig von den Demokraten zu handeln.
# get all words, word freq and most common words all_words = [] for text in df.processed_txt: wordrow = [word.text for word in text if word.is_stop != True and word.is_punct != True and word.text != ' '] all_words.extend(wordrow) print ('Number of Words: ', len(all_words)) # word count word_freq = Counter(all_words) print ('Numer of Unique Words: ', len(word_freq)) word_count_clean = len(all_words) unique_words_clean = len(word_freq) common_words = word_freq.most_common(50) # plot it for nice overview temp = pd.DataFrame(common_words) temp.columns = ['Common_words','count'] temp.style.background_gradient(cmap='Blues')
Als nächstes schauen wir uns die Auswirkungen unserer Pre Processing Schritte auf die Komplexität der Texte an. Dies dient einfach nochmal der Verdeutlichung wie stark die Wörter vereinfacht wurden. Dies ist auch wichtig für spätere Machine Learning Algorithmen die wir in einem anderen Artikel darauf anwenden werden.
# Number of words and unique words in dataset liste = [word_count_org,word_count_clean] # color palette clrs = ['green' if (x < max(liste)) else 'red' for x in liste ] # Creating our own dataframe data = {"Label": ['Orig','Cleaned'], "W_Counts": liste} p_df = pd.DataFrame(data, columns=['Label', 'W_Counts']) # Defining the plot size plt.figure(figsize=(8, 8)) plots = sns.barplot(x="Label", y="W_Counts", data=p_df, palette = clrs) # Iterrating over the bars one-by-one for bar in plots.patches: plots.annotate(format(bar.get_height(), '.2f'), (bar.get_x() + bar.get_width() / 2, bar.get_height()), ha='center', va='center', size=15, xytext=(0, 8), textcoords='offset points') # Setting the title for the graph plt.title('Wordcount Original vs Cleaned') plt.show()
liste = [unique_words_org,unique_words_clean] # color palette clrs = ['green' if (x < max(liste)) else 'red' for x in liste ] # Creating our own dataframe data = {"Label": ['Orig','Cleaned'], "W_Counts": liste} p_df = pd.DataFrame(data, columns=['Label', 'W_Counts']) # Defining the plot size plt.figure(figsize=(8, 8)) plots = sns.barplot(x="Label", y="W_Counts", data=p_df, palette = clrs) # Iterrating over the bars one-by-one for bar in plots.patches: plots.annotate(format(bar.get_height(), '.2f'), (bar.get_x() + bar.get_width() / 2, bar.get_height()), ha='center', va='center', size=15, xytext=(0, 8), textcoords='offset points') # Setting the title for the graph plt.title('UNIQUE Wordcount Original vs Cleaned') plt.show()
Man kann sehen, dass wir die Anzahl der Wörter in den Texten mehr als halbiert haben und gleichzeitig die Komplexität um ca. ein Drittel reduziert haben. Ähnlich sieht es aus wenn wir die durchschnittliche Wörternanzahl je Tweet messen ( Original & Cleaned Text)
# Number of words and unique words in dataset liste = [df['word_numb_orig'].mean(),df['word_numb_cleaned'].mean()] # color palette clrs = ['green' if (x < max(liste)) else 'red' for x in liste ] # Creating our own dataframe data = {"Label": ['Orig','Cleaned'], "W_Counts": liste} p_df = pd.DataFrame(data, columns=['Label', 'W_Counts']) # Defining the plot size plt.figure(figsize=(8, 8)) plots = sns.barplot(x="Label", y="W_Counts", data=p_df, palette = clrs) # Iterrating over the bars one-by-one for bar in plots.patches: plots.annotate(format(bar.get_height(), '.2f'), (bar.get_x() + bar.get_width() / 2, bar.get_height()), ha='center', va='center', size=15, xytext=(0, 8), textcoords='offset points') # Setting the title for the graph plt.title('MEAN - Wordcount each Tweet') plt.show()
Auch hier lässt sich eine deutliche Reduktion des Wortumfangs erkennen. Nun wollen wir aber zum Schluss auch schonmal inhaltlich gucken ob wir was erkennen können.
# Plot the Timeline number of tweets serie = df['content'].groupby(pd.to_datetime(df['date']).dt.to_period('M').astype('str')).count() plt.figure(figsize=(20, 8)) clrs = 'red' plots = sns.lineplot(data=serie, color=clrs) plt.title('Timeline Number of tweets per month') plt.show()
Hier sieht man, dass die Anzahl der Tweets im 3. und 4. Quartal am höchsten war. Das ist erstmal eine Auffälligkeit die darauf hindeutet, dass es hier ein wichtiges Event für Trump gab. Mit den häufigsten Wörtern könnten wir nun schauen ob es Tweets mit diesen Wörtern gibt die mit dem Anstieg korrelieren. Da die komplette Liste etwas unübersichtlich ist habe ich direkt mal die Auffälligsten als Beispiel herausgesucht.
# Calculate for the top common words if it applies in each text import numpy as np def wordchecker(text, checkword): erg = np.NaN for word in text: if word.text == checkword: erg = True break return erg templist = temp['Common_words'].to_list() for obj in templist: df[obj] = np.NaN for columnname in df: for check in templist: if columnname == check: # iterate over all rows and check if the word is in it df[columnname] = df['processed_txt'].apply(lambda x: wordchecker(x, check)) # Grouping and plotting with most interesting words serie = df[['witch','impeachment','china','border','wall']].groupby(pd.to_datetime(df['date']).dt.to_period('M').astype('str')).count() plt.figure(figsize=(25, 15)) clrs = 'red' plots = sns.lineplot(data=serie, color=clrs, dashes=False) plt.title('Timeline Number of tweets per month') plt.show()
Für diese Sicht müssen wir erst einmal für jedes der Top Wörter eine eigene Spalte hinzufügen und dann prüfen ob es in dem jeweiligen Tweet gehört.
Wir sehen deutlich das zum Jahresende der Begriff Impeachment stark zunimmt. Ebenfalls verzeichnet das Wort „witch“ einen leichten Anstieg in den Tweets. Wenn ich „hunt“ mit hinzunehmen würde, sieht man dass diese Worte fast komplett mit einander korrelieren. Gleichzeitig nehmen andere Wörter wie „border“,“wall“ oder „china“ deutlich ab. Es scheint also, dass andere Themen von dem Thema Impeachment verdrängt wurden. Dies sind noch relativ einfache Analysen ohne Machine Learning etc. Was wir uns in weiteren Beiträgen anschauen möchten werde ich nun im letzten Abschnitt nochmal erläutern.
Ausblick auf weitere Analysen
In weiteren Artikeln werde ich auf diesem Datensatz noch weitere Analysen fahren. Der nächste Artikel wird zum Beispiel die Clusterverfahren zum finden von Clustern auf diesen Datensatz anwenden und miteinander vergleichen.
Noch ein weiterer Artikel wird eine Semantik Analyse auf diesen Daten anwenden. Ihr seht also.. das war nur der Anfang und Ich werde mich noch etwas mit den Trump Tweets aus 2019 beschäftigen. Ich hoffe nur, dass ich dazu auch genügend Zeit finde. Bis dahin hoffe ich konnte ich euch ein paar Ansätze für eure eigenen Text Mining Projekte geben.