---
title: "Regex search"
output: rmarkdown::html_vignette
vignette: >
%\VignetteIndexEntry{Regex search}
%\VignetteEngine{knitr::rmarkdown}
%\VignetteEncoding{UTF-8}
---
```{r setup, include=FALSE}
knitr::opts_chunk$set(
eval = identical(Sys.getenv("TEXTPRESS_BUILD_LIVE_VIGNETTES"), "true"),
message = FALSE,
warning = FALSE,
comment = "#>"
)
```
`search_regex()` matches patterns against a text corpus and returns results in KWIC format with inline highlighting. This vignette applies it to a generational-politics corpus with three patterns -- age ranges, numeric change expressions, and voter-mobilization language -- each match returned at the sentence level, ready for close reading or aggregation.
## Terms and RSS entries
Terms used to select relevant entries from political RSS feeds.
```{r search-terms}
search_terms <- c(
"Gen Z", "Millennial", "Gen X", "Baby Boomer",
"young voters", "older voters"
)
```
Fetch recent entries from the selected feeds, filter their title and description locally, and deduplicate the resulting URLs.
```{r web-urls, message=FALSE, warning=FALSE}
library(textpress)
library(dplyr)
library(DT)
politics_feeds <- textpress::rss_politics
web_urls <- textpress::fetch_rss(politics_feeds$url) |>
filter(grepl(
paste(search_terms, collapse = "|"),
paste(title, description),
ignore.case = TRUE
)) |>
select(-doc_id) |>
distinct(url, .keep_all = TRUE)
```
## Web text and sentence split
`read_urls()` scrapes and parses the article text; `nlp_split_sentences()` segments each document into analysis-ready sentence rows.
```{r web-text}
web_text_list <- web_urls |>
textpress::read_urls(cores = 6)
web_text <- web_text_list$text |>
mutate(doc_id = match(url, unique(url))) |>
relocate(doc_id, .before = 1)
```
Segment each document into sentence rows.
```{r web-ss}
web_ss <- web_text |>
textpress::nlp_split_sentences(by = c('doc_id', 'node_id'))
```
## Regex patterns
Three patterns: `age_range` matches explicit age constructions; `from_to` captures numeric change expressions; `energized` catches voter-mobilization language.
```{r patterns}
patterns <- list(
energized = "\\b(?:energi[zs]ed|motivated|mobili[zs]ed|fired\\s+up|disillusioned|apathetic)\\b",
age_range = paste(
"\\b(?:aged?(?:\\s+between)?|ages?)\\s+\\d{2,3}(?:(?:\\s*(?:[-–]|to|and)\\s*\\d{2,3})|(?:\\s+and\\s+(?:older|younger|over|under)))\\b",
"\\bunder\\s+\\d{2}(?!\\d|,|%)\\b",
"\\b\\d{2,3}(?!\\d|,|%)\\s*(?:\\+|and\\s+(?:older|younger|over|under))\\b",
sep = "|"
),
from_to = "\\bfrom\\s+\\d+\\s+to\\s+\\d+\\b"
)
```
## Search results
### Search ~ `age_range`
Sentences containing explicit age ranges or constructions (e.g. *"adults aged 18 to 29"*, *"under 30"*, *"65+"*).
```{r out-age-range}
fs <- web_ss |>
textpress::search_regex(
query = patterns$age_range,
by = c('doc_id', 'node_id'),
highlight = c('', '')
) |>
distinct(text, .keep_all = TRUE) |>
select(doc_id, node_id, pattern, text)
if (!is.null(fs) && nrow(fs) > 0) fs |> DT::datatable(rownames = FALSE, escape = FALSE)
```
### Search ~ `from_to`
Sentences with numeric change expressions (e.g. *"from 42 to 51 percent"*) -- useful for tracking shifts in polling or support figures.
```{r out-from-to}
fs1 <- web_ss |>
textpress::search_regex(
query = patterns$from_to,
by = c('doc_id', 'node_id'),
highlight = c('', '')
) |>
distinct(text, .keep_all = TRUE) |>
select(doc_id, node_id, pattern, text)
if (!is.null(fs1) && nrow(fs1) > 0) fs1 |> DT::datatable(rownames = FALSE, escape = FALSE)
```
### Search ~ `energized`
Sentences with voter-mobilization language -- terms like *"energized"*, *"motivated"*, *"mobilized"*, *"disillusioned"*, *"apathetic"*.
```{r out-energized}
fs2 <- web_ss |>
textpress::search_regex(
query = patterns$energized,
by = c('doc_id', 'node_id'),
highlight = c('', '')
) |>
distinct(text, .keep_all = TRUE) |>
select(doc_id, node_id, pattern, text)
if (!is.null(fs2) && nrow(fs2) > 0) fs2 |> DT::datatable(rownames = FALSE, escape = FALSE)
```
## Summary
`search_regex()` is best for precise, expressive patterns -- age ranges, numeric constructions, multi-word sequences -- where you control the match exactly. Results come back sentence-level with inline highlighting. For broader vocabulary coverage without hand-crafting every variant, see the dictionary search vignette.