{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Let's load the necessary libraries" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:01.021841Z", "iopub.status.busy": "2026-03-13T14:27:01.021519Z", "iopub.status.idle": "2026-03-13T14:27:10.261632Z", "shell.execute_reply": "2026-03-13T14:27:10.260705Z" } }, "outputs": [], "source": [ "import pandas as pd # Used for data manipulation\n", "import numpy as np # Used for numerical operations\n", "import matplotlib.pyplot as plt # Used for plotting\n", "import seaborn as sns # Used for plotting\n", "from huggingface_hub import login # Used to log in to Hugging Face and access datasets\n", "import spacy # Used for text preprocessing and NLP tasks\n", "from spacytextblob.spacytextblob import SpacyTextBlob # Used for dictionary-based sentiment analysis\n", "from sklearn.feature_extraction.text import TfidfVectorizer # Used for creating TF-IDF representations of the text\n", "from sklearn.model_selection import train_test_split # Used for splitting the dataset into training and testing sets\n", "from sklearn.ensemble import RandomForestClassifier # Used for training a Random Forest classifier\n", "from sklearn.metrics import confusion_matrix, accuracy_score, roc_auc_score, recall_score, precision_score # Used for evaluating the performance of the model" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will use a pre-labeled dataset for sentence-level sentiment analysis of ECB speeches [@Pfeifer2023], which is available on Hugging Face ([Central Bank Communication Dataset](https://huggingface.co/datasets/Moritz-Pfeifer/CentralBankCommunication)). The dataset contains sentences from ECB speeches that have been labeled as positive or negative in terms of sentiment.\n", "\n", "Let's load the dataset into a pandas DataFrame" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:10.264230Z", "iopub.status.busy": "2026-03-13T14:27:10.263896Z", "iopub.status.idle": "2026-03-13T14:27:11.555904Z", "shell.execute_reply": "2026-03-13T14:27:11.554704Z" } }, "outputs": [], "source": [ "df = pd.read_csv(\"hf://datasets/Moritz-Pfeifer/CentralBankCommunication/Sentiment/ECB_prelabelled_sent.csv\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Data Exploration\n", "\n", "Let's get some basic information about the dataset" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:11.558548Z", "iopub.status.busy": "2026-03-13T14:27:11.558276Z", "iopub.status.idle": "2026-03-13T14:27:11.566541Z", "shell.execute_reply": "2026-03-13T14:27:11.565556Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "RangeIndex: 2563 entries, 0 to 2562\n", "Data columns (total 2 columns):\n", " # Column Non-Null Count Dtype \n", "--- ------ -------------- ----- \n", " 0 text 2563 non-null object\n", " 1 sentiment 2563 non-null int64 \n", "dtypes: int64(1), object(1)\n", "memory usage: 40.2+ KB\n" ] } ], "source": [ "df.info()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The dataset contains two columns: \"text\", which contains the text of the sentence, and \"sentiment\", which contains the sentiment label (0 for negative, 1 for positive). Let's take a look at the first few rows of the dataset" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:11.606798Z", "iopub.status.busy": "2026-03-13T14:27:11.606562Z", "iopub.status.idle": "2026-03-13T14:27:11.616307Z", "shell.execute_reply": "2026-03-13T14:27:11.615421Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
textsentiment
0target2 is seen as a tool to promote the furth...1
1the slovak republic for example is now home to...1
2the earlier this happens the earlier economic ...1
3the bank has made essential contributions in k...1
4moreover the economic size and welldeveloped f...1
\n", "
" ], "text/plain": [ " text sentiment\n", "0 target2 is seen as a tool to promote the furth... 1\n", "1 the slovak republic for example is now home to... 1\n", "2 the earlier this happens the earlier economic ... 1\n", "3 the bank has made essential contributions in k... 1\n", "4 moreover the economic size and welldeveloped f... 1" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's check the distribution of the sentiment labels in the dataset. This will give us an idea of whether the dataset is balanced or if there is a class imbalance that we need to be aware of when training our models." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:11.618573Z", "iopub.status.busy": "2026-03-13T14:27:11.618358Z", "iopub.status.idle": "2026-03-13T14:27:11.772403Z", "shell.execute_reply": "2026-03-13T14:27:11.771285Z" } }, "outputs": [ { "data": { "image/png": "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I+nmm282tbMu/cGkZfUHih5XVatW9Rxz3k1mertZs2ZmfsmSJc18rZlK32Sm17Rs2LBhhu2rXbu2OU+ZS5+jevXqZpuuueYaE+CQ9xCIkCfor7vRo0fLG2+8IQcOHMiw/IcffpDWrVtL586dZfv27fL+++/LunXrpF+/fp4yDz74oBw8eNAEm48//thcvkUv8OutQIEC8vrrr8uOHTtk1qxZ8uWXX8qwYcPMssaNG5vQo9c/0nClk79frfqBvHjxYk+QUsuXLzfh7K677jL3n3nmGfMh+vbbb8vOnTvlqaeekgceeEDWrFmTrfsNyA803OiPCj0W9djVY/Pbb7+Vq666yhz3euFt9eyzz8qPP/4on3/+uezatcscX/pDJD0NWroetXv3bnMsv/baa36P5Y0bN8qvv/7qmafHq37e6DL1zjvvyIgRI+Tll182z6mfU7oduo3IY/TEjEAg69Gjh9OxY0dzu2HDhs4jjzxibi9cuFBPKmpud+/e3endu7fP477++munQIECTlJSkrNr1y5TdvPmzZ7lv/zyi5kXGxub6XN/8MEHTkREhOf+jBkznPDw8AzlKlas6FlPSkqKU7p0aefdd9/1LL/vvvuce+65x9xOTEx0Chcu7MTFxfmso2fPnqYcYDPv411t3LjRHIN33323ExIS4sydO9ezTI+16OhoZ/z48eZ++/btnYcfftjveuPj483x/t1335n7q1evNvePHz/uU65JkybOk08+6blfu3ZtZ9SoUZ77MTExTv369T33y5cv78ybN89nHS+++KLTqFGji9gLyA3UECFP0X5E+stLfwV627p1q+nwfNlll3km/eWYlpYm8fHx5leg9h24/vrrPY/RX5daXe5t9erV0rJlS7n88stNPwKtVdJr4F1Ih07tCHrPPffI3LlzzX197CeffOL5RanbfubMGfM83tv77rvv+vwSBWz12WefmWNCm6D0YtzaFN6/f39TS3TTTTf5HGs33nijqZlRjz/+uMyfP1/q1q1rapO0Gf1i6XHrHsva1P3ee+95juVjx47J/v37pWfPnj7HsvZ74ljOe6y6lhnyPv1g1KDzf//3f542f6XBp0+fPqZ/QXoVKlQwgcgf7yvX/P7773LHHXfIY489ZkaZaOdKbXbTDzv9IL4Q+oHZpEkT0yS3cuVK88F+++23e7ZVLVmyxAQvb1w7DRDTv0ebuzTwREdHm/+///57s2vSjyzVY9idp8eYHsd6bK1atUqaN28uTzzxhLz66qv/erdqH6bhw4ebJrqkpCQTgNw+gu6xrM1mDRo0yNDMj7yFQIQ8R4ff6y/Aq6++2jNPa360bV9rffzRjo7//POPfPfdd1KvXj0zb8+ePT5Dbrds2WLKTJgwwfQlUh988IHPekJDQyU1NfW826j9jbSfgvZl0v4MWmOkj1U1atQwwWffvn0mNAHwpYMa0h/Lel+PIf2RoiFF6Q8VPW69zxukHar1x5JOt9xyiwwdOtRvIHKPx/Mdz1dccYX5Iaa1RBqIdIBHZGSkWab/648aHQ3n1hoh7yIQIc/RUWD64aMdrF1PP/20GQ2ivwZ79eplPlC1Gl1rZ7ScBiL9INNzBbm/PAcPHmw6a7q/Lq+88koTiLR8+/bt5ZtvvpEpU6b4PLeOJtPO0l988YUZGaZD7f0Nt9d16oe2Pv7nn382TXEubYrTztjakVp/YepImYSEBFO9r9XtPXr0uKT7D8iL9JjWJjENOFp7qzW/OlLz77//NrW46rnnnjM/eK699lozIk2b3nT0lz8VK1Y0x6mW0Zph/SzQ488f/bzREaUpKSkSGxvrs0zna820DrbQGip9Xg1pOuJt0KBBl2BP4JLJlZ5LwEV0slR79+51ChUq5OlUrTZt2uS0bNnSueyyy5xixYqZzpAvv/yyZ/nBgwed22+/3TxOO0FrR8iyZcs6U6ZM8ZSZOHGiU65cOadIkSJO69atTcfo9B0vH3vsMdPJU+c///zzGTpVu3bu3GnK6LK0tDSfZXr/tddec6pVq2Y6ipYpU8Y835o1a3hvwGr+jneXDpDo37+/GbSgx/FNN91kjnvvzszVq1c3x2+pUqXMen777Te/naqVdpaOiopygoKCzPP661St9PjX5ytatKhz6tSpDNulHb3r1q3rhIaGOiVLlnRuvfVWZ8GCBdm2T5AzgvSfSxe3gMClw/e1WcvtawAAsBeBCNbQcwppc5c2uel5R3QUyh9//GGatLQJDQBgL/oQwRraAVNHp2kHSO3Hox2ftaMkYQgAQA0RAACwHidmBAAA1iMQAQAA6xGIAACA9QhEAADAegQiAFb66quvzJmKvS/fAsBeBCIAuUovgKsX5tVLMeg13qKioswFfNevX59tz9G0aVOf610pPe2Cno8qPDxccpted6tTp065vRmA1TgPEYBcddddd5lzRM2aNUuqVKkiR44cMdeK++uvvy7p8+rFPTV8AYCRQ5cIAYAM9BpR+jH01VdfZbp3Tpw44fTq1ctc76148eJOs2bNnG3btnmW6/Xk6tSpY647p9eNCwsLc+69914nISHBLNdrVOlzeE96XavVq1f7XKduxowZTnh4uPPpp586V199tbke1l133eUkJiY6M2fONOsuUaKE069fP+eff/7xPH9ycrIzdOhQJzo62lzr6sYbbzTrdrnrXbZsmXPNNdeY6+zpdev02nru9qffPu/HA8gZNJkByDV6dXGdFi1aZK4Snp5earFt27Zy+PBhWbp0qWzdulWuv/56c+057xqkX3/91axDr1yu05o1a2Ts2LFm2WuvvSaNGjWSXr16mSYynfQadv7oldNff/11mT9/vixbtsz0M+rcubN5bp1mz54t06ZNk48++sjzmIcffli++eYb85jt27fLPffcI23atJFffvnFZ72vvvqqefzatWtl3759MmTIELNM/+/SpYt5jLt92pwHIIflUPACAL8++ugjc4XwwoULO40bN3ZiYmKc77//3iz74osvTI3PmTNnfB5z5ZVXOlOnTvXUsGjNjFsjpLTGpkGDBp77/q5g7q+GSO/v2bPHU6ZPnz4ZrnCutTs6X2lZvVL6H3/84bPu5s2bm9eR2XrffPNNJzIyMktXeAeQM+hDBCDX+xBpLdDXX39tOlJrzcz48ePlP//5jxw7dsxckDciIsLnMUlJSaZWyFWpUiVzfTpXuXLlTGftC1W0aFG58sorPfcjIyPNurUWy3ueu+5vv/3W1GJdffXVPuvR2i7vbU6/3n+7fQAuHQIRgFxXuHBhadmypZmee+45efTRR+X555+Xvn37mvCgTVfplShRwnM7/QV6dTh9WlraBW+Hv/Wca936f8GCBU1Tnv7vzTtE+VuHBikAgYNABCDg1KhRw/QJ0v5C2n8oODjY1NRczIiy1NTUbN1Gdd1115n1am3PLbfcEnDbByDr6FQNINf8+eefctttt8mcOXNMh+T4+Hj58MMPTZNZx44dpUWLFqZDtJ6jZ/ny5bJ3716Ji4uTZ555RrZs2ZLl59EwtXHjRvP4//3vf/+q9sgfbSq7//775cEHH5QFCxaY7d+8ebOMGzfOdMK+kO3T1797926zfXoaAgA5i0AEINdos1KDBg0kNjZWbr31VqlZs6Y8++yzZkTY5MmTTdOSBgtd9sgjj5gA0rVrVxNstC9PVulILm3S0pqnMmXKmFFe2WXGjBkmEA0ePFiqVasmHTp0MOErs5Fs/ujr1cfecMMNZvt01BqAnBWkPatz+DkBAAACCjVEAADAegQiAABgPQIRAACwHoEIAABYj0AEAACsRyACAADWIxABAADrEYgAAID1CEQAAMB6BCIAAGA9AhEAALAegQgAAIjt/h+28L+boqUjvgAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "df['sentiment'].value_counts().plot(kind='bar')\n", "plt.title(\"Distribution of Sentiment Labels\")\n", "plt.xlabel(\"Sentiment\")\n", "plt.ylabel(\"Count\")\n", "plt.xticks(ticks=[0, 1], labels=[\"Negative\", \"Positive\"], rotation=0)\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There is a slight imbalance but it is not as severe as in some other datasets that we have seen.\n", "\n", "\n", "### Dictionary-Based Approach to Sentiment Analysis\n", "\n", "We can use the SpacyTextBlob component to perform dictionary-based sentiment analysis. This component uses the TextBlob library under the hood, which provides a simple API for performing sentiment analysis based on a predefined lexicon of words and their associated sentiment scores." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:11.775060Z", "iopub.status.busy": "2026-03-13T14:27:11.774834Z", "iopub.status.idle": "2026-03-13T14:27:12.370825Z", "shell.execute_reply": "2026-03-13T14:27:12.369909Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The ECB's monetary policy is very effective in stabilizing the economy.: 0.78\n", "The ECB's monetary policy is not suitable for stabilizing the economy.: -0.275\n" ] } ], "source": [ "# Load the spaCy English model and add the SpacyTextBlob component to the pipeline\n", "nlp = spacy.load('en_core_web_sm')\n", "nlp.add_pipe('spacytextblob')\n", "\n", "# Example of how to use the SpacyTextBlob component for sentiment analysis\n", "text = \"The ECB's monetary policy is very effective in stabilizing the economy.\"\n", "doc = nlp(text)\n", "print(f\"{text}: {doc._.blob.polarity}\")\n", "\n", "# Opposite example\n", "text = \"The ECB's monetary policy is not suitable for stabilizing the economy.\"\n", "doc = nlp(text)\n", "print(f\"{text}: {doc._.blob.polarity}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the polarity score ranges from -1 (very negative) to 1 (very positive), with 0 being neutral. This is different from the binary sentiment labels in our dataset, so we will need to convert the polarity scores to binary labels if we want to compare the results directly.\n", "\n", "Also, note that the dictionary-based approach may not always capture the nuances of the language, especially in complex sentences or when there are negations. " ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:12.373065Z", "iopub.status.busy": "2026-03-13T14:27:12.372859Z", "iopub.status.idle": "2026-03-13T14:27:12.387199Z", "shell.execute_reply": "2026-03-13T14:27:12.386317Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The ECB's monetary policy is not very effective for stabilizing the economy.: -0.23076923076923073\n", "The ECB's monetary policy is very ineffective for stabilizing the economy.: 0.2\n" ] } ], "source": [ "text = \"The ECB's monetary policy is not very effective for stabilizing the economy.\"\n", "doc = nlp(text)\n", "print(f\"{text}: {doc._.blob.polarity}\")\n", "\n", "text = \"The ECB's monetary policy is very ineffective for stabilizing the economy.\"\n", "doc = nlp(text)\n", "print(f\"{text}: {doc._.blob.polarity}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The second sentence is more negative than the first one, but the polarity score does not reflect that.\n", "\n", "Let's apply the SpacyTextBlob component to the entire dataset and see how well it performs in terms of classifying the sentiment of the sentences" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:12.389446Z", "iopub.status.busy": "2026-03-13T14:27:12.389234Z", "iopub.status.idle": "2026-03-13T14:27:30.039899Z", "shell.execute_reply": "2026-03-13T14:27:30.038903Z" } }, "outputs": [], "source": [ "df['textblob_sentiment'] = df['text'].apply(lambda x: nlp(x)._.blob.polarity)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can convert the polarity scores to binary labels using a simple threshold " ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:30.042412Z", "iopub.status.busy": "2026-03-13T14:27:30.042199Z", "iopub.status.idle": "2026-03-13T14:27:30.046878Z", "shell.execute_reply": "2026-03-13T14:27:30.045983Z" } }, "outputs": [], "source": [ "df['textblob_label'] = df['textblob_sentiment'].apply(lambda x: 1 if x > 0 else 0)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can evaluate the performance of the dictionary-based approach by comparing the predicted labels with the true labels in the dataset. We can use metrics such as accuracy, precision, and recall to assess the performance of the model" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:30.049546Z", "iopub.status.busy": "2026-03-13T14:27:30.049240Z", "iopub.status.idle": "2026-03-13T14:27:30.062138Z", "shell.execute_reply": "2026-03-13T14:27:30.061251Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Accuracy: 0.59\n", "Recall: 0.78\n", "Precision: 0.47\n" ] } ], "source": [ "accuracy = accuracy_score(df['sentiment'], df['textblob_label'])\n", "recall = recall_score(df['sentiment'], df['textblob_label'])\n", "precision = precision_score(df['sentiment'], df['textblob_label'])\n", "\n", "print(f\"Accuracy: {accuracy:.2f}\")\n", "print(f\"Recall: {recall:.2f}\")\n", "print(f\"Precision: {precision:.2f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that we did not have to train any model for the dictionary-based approach, as it relies on a predefined lexicon of words and their associated sentiment scores. However, the performance of this approach may not be as good as more advanced machine learning methods, especially if the dataset contains a lot of domain-specific language or if the sentences are complex and contain multiple sentiments.\n", "\n", "\n", "### Machine Learning Approach to Sentiment Analysis\n", "\n", "\n", "For the machine learning approach, we will need to preprocess the text data and convert it into a format that can be used as input for a machine learning model. This typically involves steps such as tokenization, stopword removal, and creating numerical representations of the text (e.g., using bag-of-words or TF-IDF).\n", "\n", "Let's start by defining a function to preprocess the text data" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:30.064627Z", "iopub.status.busy": "2026-03-13T14:27:30.064403Z", "iopub.status.idle": "2026-03-13T14:27:30.068845Z", "shell.execute_reply": "2026-03-13T14:27:30.067811Z" } }, "outputs": [], "source": [ "def preprocess_texts(texts):\n", "\n", " # Use the spaCy pipeline to process the texts, which will handle tokenization, stopword removal, and lemmatization for us\n", " docs = nlp.pipe(texts, disable=[\"parser\", \"ner\"])\n", "\n", " processed_texts = []\n", " for doc in docs:\n", "\n", " # Lemmatize the tokens and convert them to lowercase, while also removing stopwords, punctuation, whitespace, and numbers\n", " tokens = [token.lemma_.lower() for token in doc \n", " if not token.is_stop and \n", " not token.is_punct and \n", " not token.is_space and \n", " not token.like_num\n", " ]\n", "\n", " # Join the tokens back into a single string and add it to the list of processed texts\n", " processed_texts.append(\" \".join(tokens))\n", "\n", " return processed_texts" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:30.071111Z", "iopub.status.busy": "2026-03-13T14:27:30.070893Z", "iopub.status.idle": "2026-03-13T14:27:33.223573Z", "shell.execute_reply": "2026-03-13T14:27:33.222690Z" } }, "outputs": [], "source": [ "df['processed_text'] = preprocess_texts(df['text'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, we need to split the dataset into a training set and a testing set. It's important to do this before vectorization to avoid data leakage." ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:33.225865Z", "iopub.status.busy": "2026-03-13T14:27:33.225612Z", "iopub.status.idle": "2026-03-13T14:27:33.231722Z", "shell.execute_reply": "2026-03-13T14:27:33.230833Z" } }, "outputs": [], "source": [ "X = df['processed_text']\n", "y = df['sentiment']\n", "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) # We use 20% of the data for testing, set a random state for reproducibility, and stratify to maintain class balance" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can create a TF-IDF representation of the processed text data. Importantly, we fit the vectorizer only on the training data to avoid data leakage, then transform both the training and test sets." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:33.233791Z", "iopub.status.busy": "2026-03-13T14:27:33.233594Z", "iopub.status.idle": "2026-03-13T14:27:33.273569Z", "shell.execute_reply": "2026-03-13T14:27:33.272561Z" } }, "outputs": [], "source": [ "vectorizer = TfidfVectorizer(max_features=1000) # We limit the number of features to 1000 to reduce the dimensionality of the data and speed up the training process\n", "X_train = vectorizer.fit_transform(X_train) # Fit on training data only\n", "X_test = vectorizer.transform(X_test) # Transform test data using the fitted vectorizer" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then, we can train a Random Forest classifier on the training data" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:33.275828Z", "iopub.status.busy": "2026-03-13T14:27:33.275569Z", "iopub.status.idle": "2026-03-13T14:27:33.694560Z", "shell.execute_reply": "2026-03-13T14:27:33.692758Z" } }, "outputs": [], "source": [ "clf_rf = RandomForestClassifier(n_estimators=100, random_state = 42).fit(X_train, y_train)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To evaluate the performance of the model, we can make predictions on the testing set and calculate metrics such as accuracy, precision, and recall" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:33.697546Z", "iopub.status.busy": "2026-03-13T14:27:33.697365Z", "iopub.status.idle": "2026-03-13T14:27:33.748885Z", "shell.execute_reply": "2026-03-13T14:27:33.748000Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Accuracy: 0.9142300194931774\n", "Precision: 0.9060773480662984\n", "Recall: 0.8586387434554974\n", "ROC AUC: 0.9720822087086598\n" ] } ], "source": [ "y_pred_rf = clf_rf.predict(X_test)\n", "y_proba_rf = clf_rf.predict_proba(X_test)\n", "\n", "print(f\"Accuracy: {accuracy_score(y_test, y_pred_rf)}\")\n", "print(f\"Precision: {precision_score(y_test, y_pred_rf)}\")\n", "print(f\"Recall: {recall_score(y_test, y_pred_rf)}\")\n", "print(f\"ROC AUC: {roc_auc_score(y_test, y_proba_rf[:, 1])}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Our machine learning model does perform quite well on the testing set. We can also visualize the confusion matrix to see how well the model is classifying the positive and negative sentences" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:33.751112Z", "iopub.status.busy": "2026-03-13T14:27:33.750906Z", "iopub.status.idle": "2026-03-13T14:27:33.855187Z", "shell.execute_reply": "2026-03-13T14:27:33.854189Z" } }, "outputs": [ { "data": { "image/png": "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fPvtt1KmTBlp06aN2Q8AGcHaKIAfmz59ugkylNZs6EJfGlQ0a9ZMXnvtNencubO8/PLL7vOrVq1q/s2TJ4+p89A1OqKjo9P1mk2aNPF4/s4770ju3LllzZo1JrsCAOlFZgPwU3v37pXvv//eBBQqc+bM0qlTJ1PDobZu3WrL0uXHjx+XPn36SNmyZU03im4a5FAvAiCjyGwAfpzVuHLlihQuXNi9T7tQdBXR06dPS3h4eLqvGRIS4u6GSbpaaVI9evSQEydOmFqO4sWLS1hYmNSrV08uXbp0E98NACcjswH4IQ0yZs2aJW+88YbJYCRu27ZtMwGALl1epUoV06VyPaGhoXL16lWPffny5ZNjx455BBx63aS0VuPJJ580dRoVK1Y0wcbJkydt+C4BOAWZDcAPffbZZyZ70bNnT9ONkdT9999vsh4xMTGmG6VUqVKmq0UDlKVLl8rw4cPNeTrCZO3ateaYBgw6ukRHqWjWYvz48eY6n3/+ufmaHDlyuK9funRpmT17tik2jY2NlWHDhmUoiwIAichsAH5IgwktAk0eaKj77rvPZCM0QPj444/N8FcdzqqFnd999537PB2JcvDgQROMaEZDlS9fXt566y355z//aYpJtSZk6NChHtfXmhANdKpXry4PP/ywyXLkz5//FnzXAIKV5UregQsAAOBFZDYAAICtCDYAAICtCDYAAICtCDYAAICtCDYAAICtCDYAAICtCDYAAICtCDYAAICtCDYAAICtCDYAAICtCDYAAIDY6f8BUaLscldMzTIAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "conf_mat = confusion_matrix(y_test, y_pred_rf, labels=[1, 0]).transpose() # Transpose the sklearn confusion matrix to match the convention in the lecture\n", "sns.heatmap(conf_mat, annot=True, cmap='Blues', fmt='g', xticklabels=['Positive', 'Negative'], yticklabels=['Positive', 'Negative'])\n", "plt.xlabel(\"Actual\")\n", "plt.ylabel(\"Predicted\")\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's try the sentences that we used for the dictionary-based approach and see how the machine learning model classifies them" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "execution": { "iopub.execute_input": "2026-03-13T14:27:33.857455Z", "iopub.status.busy": "2026-03-13T14:27:33.857243Z", "iopub.status.idle": "2026-03-13T14:27:33.876901Z", "shell.execute_reply": "2026-03-13T14:27:33.875950Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The ECB's monetary policy is not very effective for stabilizing the economy. -> Negative\n", "The ECB's monetary policy is very ineffective for stabilizing the economy. -> Negative\n" ] } ], "source": [ "test_sentences = pd.Series([\n", " \"The ECB's monetary policy is not very effective for stabilizing the economy.\",\n", " \"The ECB's monetary policy is very ineffective for stabilizing the economy.\"\n", "])\n", "test_processed = preprocess_texts(test_sentences)\n", "test_vectorized = vectorizer.transform(test_processed)\n", "test_pred_rf = clf_rf.predict(test_vectorized)\n", "print(test_sentences[0], \"->\", \"Positive\" if test_pred_rf[0] == 1 else \"Negative\")\n", "print(test_sentences[1], \"->\", \"Positive\" if test_pred_rf[1] == 1 else \"Negative\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Our machine learning model is able to capture the difference in sentiment between the two sentences, while the dictionary-based approach was not. This is because the machine learning model can learn from the context and the combination of words in the sentences, while the dictionary-based approach relies on predefined sentiment scores for individual words, which may not always capture the nuances of the language.\n", "\n", "We could do many more things to improve the performance of our machine learning model, such as hyperparameter tuning, using more advanced models (e.g., gradient boosting, or deep learning models), or using word embeddings instead of TF-IDF representations. However, this should give you a good starting point for performing sentiment analysis using both dictionary-based and machine learning approaches." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3", "path": "/usr/local/share/jupyter/kernels/python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.12" } }, "nbformat": 4, "nbformat_minor": 4 }