4 Text-as-data representations

Note🎯 Learning goals

After working through Tutorial 4, you’ll be able to…

  • Explain why we need to represent text numerically
  • Explain what a document-feature matrix (DFM) is
  • Create and inspect a DFM with quanteda
  • Name other text-as-data representations beyond the bag-of-words

1 Why do we need text-as-data representations?

Computers cannot “read” text the way we do. For computers to analyze text, we need to transform it into a numerical format. How we do this is called a text-as-data representation.

A classic representation is the bag-of-words, which builds on the bag-of-words assumption (see Tutorial 3): We reduce texts to the frequency of their features and ignore their order and context. The most common way of storing a bag-of-words representation is the document-feature matrix (DFM).

First, we load our packages and data, and repeat the preprocessing from Tutorial 3:

# load packages
library(tidyverse)
library(quanteda)

# read in the data
data <- read.csv2("data/data_tvseries.csv", encoding = "UTF-8")

# create corpus (see Tutorial 3)
corpus_tv <- corpus(data, text_field = "Description")

# preprocessing (see Tutorial 3)
tokens_tv <- corpus_tv |>
  tokens(remove_punct = TRUE,
         remove_numbers = TRUE,
         remove_symbols = TRUE) |>
  tokens_tolower() |>
  tokens_remove(stopwords("english")) |>
  tokens_wordstem()

2 The document-feature matrix

2.1 What is a document-feature matrix?

A document-feature matrix (DFM) is a matrix in which…

  • Rows = documents
  • Columns = features
  • Cells = how often a feature occurs in a document

Let’s look at an example with three short news headlines:

headlines <- c(
  doc1 = "On the state of the German economy: Will we have another financial crisis in Germany in 2023?",
  doc2 = "The GDP is going down, unemployment is up: Crisis ahead in Germany?",
  doc3 = "German economy tumbles into crisis: Government under pressure"
)

headlines |>
  tokens(remove_punct = TRUE, remove_numbers = TRUE) |>
  tokens_tolower() |>
  tokens_remove(stopwords("english")) |>
  tokens_wordstem() |>
  dfm()
Document-feature matrix of: 3 documents, 14 features (54.76% sparse) and 0 docvars.
      features
docs   state german economi anoth financi crisi germani gdp go unemploy
  doc1     1      1       1     1       1     1       1   0  0        0
  doc2     0      0       0     0       0     1       1   1  1        1
  doc3     0      1       1     0       0     1       0   0  0        0
[ reached max_nfeat ... 4 more features ]

For example, the feature “crisi” (the stem of “crisis”) occurs once in every document, while “economi” occurs in doc1 and doc3, but not in doc2.

A DFM can be used as input for many methods, e.g., dictionaries or machine learning. With little preprocessing, it becomes very large and contains many zeros (because most features occur in only a few documents) - it is sparse.

2.2 Creating a DFM for our data

We create a DFM from our tokens with dfm():

dfm_tv <- tokens_tv |>
  dfm()

dfm_tv
Document-feature matrix of: 900 documents, 4,234 features (99.65% sparse) and 5 docvars.
       features
docs    nine nobl famili fight control land westero ancient enemi return
  text1    1    1      1     1       1    1       1       1     1      1
  text2    0    0      1     0       0    0       0       0     0      0
  text3    0    0      0     0       0    0       0       0     0      0
  text4    0    0      0     0       0    0       0       0     0      0
  text5    0    0      0     0       0    0       0       0     0      0
  text6    0    0      0     0       0    0       0       0     0      0
[ reached max_ndoc ... 894 more documents, reached max_nfeat ... 4,224 more features ]

We see that…

  • the DFM consists of 900 documents,
  • after preprocessing, it still contains 4,234 unique features,
  • it is 99.65% sparse, i.e., more than 99% of all cells contain a 0.

2.3 Inspecting a DFM

quanteda offers several functions to inspect a DFM:

ndoc(dfm_tv)
[1] 900
nfeat(dfm_tv)
[1] 4234
sparsity(dfm_tv)
[1] 0.9965425
featnames(dfm_tv) |>
  head(10)
 [1] "nine"    "nobl"    "famili"  "fight"   "control" "land"    "westero"
 [8] "ancient" "enemi"   "return" 

3 Other text-as-data representations

The bag-of-words representation is simple and often useful. But because it ignores the order and context of features, it has clear limitations. This is why there are other text-as-data representations, for example:

  • N-grams: sequences of n features (e.g., “new york” as a bigram), which keep some information about word order
  • Syntax: information about the grammatical role of features (e.g., subject vs. object)
  • Semantic representations, e.g., word embeddings, which represent features based on the contexts they occur in, so that similar words (e.g., “series” and “show”) get similar representations

We will learn more about these later in the course.

💡 Take-Aways

🤓 Smart Hacks

By default, the cells of a DFM contain absolute frequencies. With dfm_weight(), we can change this, e.g., to the proportion of each feature within a document. This is useful if documents differ in length:

dfm_tv |>
  dfm_weight(scheme = "prop") |>
  head(3)
Document-feature matrix of: 3 documents, 4,234 features (99.65% sparse) and 5 docvars.
       features
docs          nine       nobl     famili      fight    control       land
  text1 0.08333333 0.08333333 0.08333333 0.08333333 0.08333333 0.08333333
  text2 0          0          0.06250000 0          0          0         
  text3 0          0          0          0          0          0         
       features
docs       westero    ancient      enemi     return
  text1 0.08333333 0.08333333 0.08333333 0.08333333
  text2 0          0          0          0         
  text3 0          0          0          0         
[ reached max_nfeat ... 4,224 more features ]

Another common weighting is tf-idf (dfm_tfidf()), which gives more weight to features that occur often in a document, but rarely across all documents.

🎲 Quiz

Note🎲 Question 1

Which of the following statements about document-feature matrices are correct?

📚 More tutorials on this

You still have questions? The following tutorials & papers can help you with that: