Skip to main content

A package using the Higher Criticism statistic to find discriminating words in two text corpora.

Project description

HCT-Bootstrap: Discriminating Words in Text Corpora

Python 3.8+ License: Unlicensed

A Python package for identifying statistically significant, discriminating words between two text corpora using the Higher Criticism method and bootstrapping for stability analysis.

Overview

This package provides tools to perform a robust stylistic comparison of two texts or two collections of texts. The primary goal is to move beyond simple word frequency counts and identify words that are "surprisingly" frequent in one corpus compared to the other, a hallmark of stylistic difference.

This is particularly useful for analyzing and comparing text generated by Large Language Models (LLMs), comparing different authors, or tracking stylistic changes in a corpus over time. The package provides both a core computational function for raw data analysis and a convenient all-in-one function that generates a full report with dataframes and plots.

Higher Criticism for Word-Frequency Tables

The core statistical engine of this package is the Higher Criticism (HC) test, adapted for textual analysis as described in the work of Kipnis (2022).

In statistical terms, comparing two corpora involves thousands of individual hypothesis tests—one for each unique word. For each word, we can calculate a p-value that represents the probability of observing its frequency difference by pure chance. However, with thousands of tests, we expect many small p-values to occur randomly, a problem known as the multiple testing problem.

Traditional methods like the Bonferroni correction are too conservative in this setting, as they often miss subtle but collectively significant signals. The Higher Criticism statistic, in contrast, is specifically designed to detect a sparse, heterogeneous signal. In our context, this translates to the assumption that an author's or model's unique "style" is not defined by a change in every word, but by a small, unknown subset of words that they use idiosyncratically.

The HC statistic works by examining the ordered sequence of all p-values. It seeks the optimal threshold where the number of surprisingly small p-values is most significant relative to what would be expected under the null hypothesis (that there is no difference between the corpora). This makes it exceptionally well-suited for identifying a faint but consistent stylistic "fingerprint" that distinguishes two bodies of text. This package includes the method described in the aforementioned paper to apply this powerful statistical test to word-frequency tables derived from text. The core statistical code is taken from Alon Kipnis's TwoSampleHC GitHub repository.

References

The primary statistical method is based on the work of Donoho and Jin, and its application to text is detailed by Kipnis:

Installation

  1. Activate your virtual environment. Make sure you have a Python 3.8+ environment ready and activated.

  2. Install directly from PyPI:

    pip install hct-bootstrap
    

    Installation with display features: If you want to automatically generate the plotting in Jupyter Notebook, install the package with the optional display dependencies.

    pip install "hct-bootstrap[display]"
    

Quick Start: How to Use the Code

Here is a simple example of how to use the analyze_and_display function to compare two lists of documents and generate a full report.

from hct_bootstrap import analyze_and_display
import random

#Set random seed for reproducibility
random.seed(42)

background_vocab = [
    "analysis", "system", "data", "model", "research", "method", "result", "time", "user", "network",
    "process", "study", "group", "problem", "level", "information", "effect", "change", "design", "feature",
    "performance", "approach", "case", "point", "development", "variable", "pattern", "value", "algorithm", "test",
    "environment", "structure", "condition", "behavior", "factor", "application", "control", "theory", "practice", "solution",
    "science", "technology", "paper", "review", "framework", "function", "state", "rule", "context", "form",
    "concept", "equation", "experiment", "measure", "source", "type", "area", "base", "number", "size",
    "quality", "property", "task", "technique", "principle", "resource", "material", "energy", "rate", "scale",
    "phase", "stage", "component", "class", "architecture", "mechanism", "relation", "role", "impact", "access"
]

ai_style_words = ["delve", "intricate", "underscore", "meticulous", "crucial", "tapestry", "robust", "realm", "nuance", "leverage"]

human_corpus = []
ai_corpus = []

# 1.  Create 'human' corpus
# Generate 1000 documents, each containing 250 random background words
for _ in range(1000):
    doc = " ".join(random.choices(background_vocab, k=250))
    human_corpus.append(doc)
    
#2.  Create 'AI' corpus 
# Generate 1000 documents of the same length, using the same background vocabulary, but also injecting our 'AI style words' into each document
for _ in range(1000):
    doc = " ".join(random.choices(background_vocab, k=250))
    doc += " " + " ".join(ai_style_words)
    ai_corpus.append(doc)


# 2. Run the analysis and display the report
# We set coupled=False because the lists are not paired one-to-one.  If, for example, you used a list of prompts to generate pairs of text using different LLMs, you would set coupled=True (the corpora would be of equal length)
# We use 1000 bootstrap iterations

results = analyze_and_display(
    corpus1_docs=human_corpus, 
    corpus1_name="Human",
    corpus2_docs=ai_corpus, 
    corpus2_name="AI",
    coupled=False,        
    n_iterations=1000     
)

Example Report Output

Running the code above will generate a report in your console or notebook similar to this:

=== Higher Criticism Analysis Report ===
Comparing: 'Human' vs. 'AI'
Coupled Analysis: False
Bootstrap Iterations: 1000
--------------------------

 --- Corpus Information (Post-Cleaning) ---
# of words in Human: 250,000
# of words in AI: 260,000

--- Higher Criticism Results (Analysis on Full Text) ---
word p_value human_frequency (%) AI_frequency (%) more_frequent_in
nuance 3.98703e-271 0.0 0.384792 AI
crucial 3.98703e-271 0.0 0.384792 AI
delve 3.98703e-271 0.0 0.384792 AI
intricate 3.98703e-271 0.0 0.384792 AI
leverage 3.98703e-271 0.0 0.384792 AI
tapestry 3.98703e-271 0.0 0.384792 AI
robust 3.98703e-271 0.0 0.384792 AI
meticulous 3.98703e-271 0.0 0.384792 AI
underscore 3.98703e-271 0.0 0.384792 AI
realm 3.98703e-271 0.0 0.384792 AI
(showing top 10 of 17 discriminating words from full data run)

Overall HC Score (Full Data): 4.6882

--- Stable Words Found in 100% of Bootstrap Iterations ---

Words more frequent in 'Human':
None

Words more frequent in 'AI':
meticulous, underscore, nuance, realm, robust, crucial, delve, tapestry, leverage

--- Histogram of Bootstrap Word Count Frequencies ---
(A matplotlib histogram plot is displayed here)

Standard Deviation of Bootstrap HC scores: 0.0095

=== End of Report ===

API Reference

The package exposes two primary functions:

analyze_and_display(...)

The main convenience function for interactive analysis. It performs the full pipeline and prints a formatted report.

  • corpus1_docs (List[str]): A list of document strings for the first corpus.
  • corpus1_name (str): A descriptive name for the first corpus.
  • corpus2_docs (List[str]): A list of document strings for the second corpus.
  • corpus2_name (str): A descriptive name for the second corpus.
  • coupled (bool): Set to True if corpus1_docs[i] is paired with corpus2_docs[i]. Requires lists to be of the same length.
  • n_iterations (int, optional, default: 100): The number of bootstrap iterations to perform for stability analysis.
  • random_seed (int, optional, default: 42): Seed for reproducible random sampling.
  • pos_tags (List[str], optional): A list of spaCy Part-of-Speech tags to include in the analysis. Defaults to content words like nouns, verbs, adjectives, and adverbs.
  • nlp (spacy.Language, optional): A pre-loaded spaCy model. If None, the default_spacy_model will be loaded internally.
  • default_spacy_model (str, optional, default: "en_core_web_sm"): The name of the default spaCy model to load if nlp is not provided.

discriminate(...)

The core computational engine. It performs the same analysis as analyze_and_display but returns the raw results as a tuple of (results_dict, cleaned_corpus1_str, cleaned_corpus2_str) without printing or plotting anything. This function is ideal for use in scripts or when you want to handle the output data programmatically. Its parameters are the same as analyze_and_display.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hct_bootstrap-0.1.1.tar.gz (20.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hct_bootstrap-0.1.1-py3-none-any.whl (18.4 kB view details)

Uploaded Python 3

File details

Details for the file hct_bootstrap-0.1.1.tar.gz.

File metadata

  • Download URL: hct_bootstrap-0.1.1.tar.gz
  • Upload date:
  • Size: 20.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.8.5

File hashes

Hashes for hct_bootstrap-0.1.1.tar.gz
Algorithm Hash digest
SHA256 01080f3548f34aad8e6cabf531fb485396b6c309cbcd5add21cbdf1bb5081f1f
MD5 79445a386b04c71cc25fe28539a0c44f
BLAKE2b-256 f33f23fadc447131d39054f6a4fc2b81efd0ea593958404efdc8743c5691a9b6

See more details on using hashes here.

File details

Details for the file hct_bootstrap-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: hct_bootstrap-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 18.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.8.5

File hashes

Hashes for hct_bootstrap-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 907125004c8e7ec48499f6fdb8fb5ac4bb4d6f9c182663c6abc606dce455bb6f
MD5 d50eef74751e043a9d9364d7b46cd3f6
BLAKE2b-256 06fe6385bac174aeb8802f1a40e6ff6865bc5070845568fd0f06e3a360ef81b3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page