Skip to content

Concordance

A term's dispersion across the text, with keyword-in-context.

lexograph.presets.concordance.concordance(text, terms, *, ignore_case=True, normalize=False, figsize=None, background='white')

Draw a lexical-dispersion plot of where each term falls in the text.

Parameters:

Name Type Description Default
text str

The source text.

required
terms Sequence[str]

The terms to plot, one row each (top to bottom in this order).

required
ignore_case bool

Match terms case-insensitively.

True
normalize bool

If True, scale the x-axis to [0, 1] (fraction of the text) instead of absolute token offset.

False
figsize tuple[float, float] | None

Figure size in inches. Defaults to a height that grows with the number of terms.

None
background str

Figure and axes background colour.

'white'

Returns:

Name Type Description
A Figure

class:matplotlib.figure.Figure with one axes. Never calls show().

Raises:

Type Description
ValueError

If terms is empty.

Contract
  • Returns a Figure with exactly one axes.
  • There is exactly one y-row per term, in the given order.

Examples:

>>> from lexograph import load_demo_text
>>> fig = concordance(load_demo_text(), ["Bennet", "Bingley", "wife"])
>>> type(fig).__name__
'Figure'
>>> len(fig.axes[0].get_yticks())
3
Source code in lexograph/presets/concordance.py
def concordance(
    text: str,
    terms: Sequence[str],
    *,
    ignore_case: bool = True,
    normalize: bool = False,
    figsize: tuple[float, float] | None = None,
    background: str = "white",
) -> Figure:
    """Draw a lexical-dispersion plot of where each term falls in the text.

    Args:
        text: The source text.
        terms: The terms to plot, one row each (top to bottom in this order).
        ignore_case: Match terms case-insensitively.
        normalize: If ``True``, scale the x-axis to ``[0, 1]`` (fraction of the
            text) instead of absolute token offset.
        figsize: Figure size in inches. Defaults to a height that grows with the
            number of terms.
        background: Figure and axes background colour.

    Returns:
        A :class:`matplotlib.figure.Figure` with one axes. Never calls ``show()``.

    Raises:
        ValueError: If ``terms`` is empty.

    Contract:
        - Returns a Figure with exactly one axes.
        - There is exactly one y-row per term, in the given order.

    Examples:
        >>> from lexograph import load_demo_text
        >>> fig = concordance(load_demo_text(), ["Bennet", "Bingley", "wife"])
        >>> type(fig).__name__
        'Figure'
        >>> len(fig.axes[0].get_yticks())
        3
    """
    if len(terms) == 0:
        msg = "need at least one term for a concordance plot"
        raise ValueError(msg)

    total = len(tokens(text))
    span = float(total) if total else 1.0
    offsets = term_offsets(text, terms, ignore_case=ignore_case)
    colours = categorical_colors(list(range(len(terms))))

    if figsize is None:
        figsize = (10.0, 0.6 * len(terms) + 1.5)
    fig = Figure(figsize=figsize, facecolor=background)
    ax = fig.subplots()
    ax.set_facecolor(background)

    for row, term in enumerate(terms):
        xs = offsets[term]
        if normalize and len(xs):
            xs = xs / span
        if len(xs):
            ax.vlines(xs, row - 0.4, row + 0.4, color=colours[row])

    ax.set_yticks(range(len(terms)))
    ax.set_yticklabels(list(terms))
    ax.set_ylim(-0.5, len(terms) - 0.5)
    ax.invert_yaxis()  # first term on top
    ax.set_xlim(0.0, 1.0 if normalize else span)
    ax.set_xlabel(
        "position in text" + (" (fraction)" if normalize else " (token offset)")
    )
    ax.set_title("Lexical dispersion")
    fig.tight_layout()
    return fig

lexograph.layout.dispersion.term_offsets(text, terms, *, ignore_case=True)

Return the token offsets at which each term occurs, in order.

Parameters:

Name Type Description Default
text str

The source text.

required
terms Sequence[str]

The terms to locate (each matched as a whole token).

required
ignore_case bool

Match case-insensitively.

True

Returns:

Type Description
dict[str, FloatArray]

A mapping from each input term to a float array of the token indices at

dict[str, FloatArray]

which it occurs (empty if it never does). The mapping preserves the

dict[str, FloatArray]

order of terms.

Examples:

>>> offsets = term_offsets("the cat and the dog and the cat", ["cat", "dog"])
>>> offsets["cat"].tolist()
[1.0, 7.0]
>>> offsets["dog"].tolist()
[4.0]
Source code in lexograph/layout/dispersion.py
def term_offsets(
    text: str,
    terms: Sequence[str],
    *,
    ignore_case: bool = True,
) -> dict[str, FloatArray]:
    """Return the token offsets at which each term occurs, in order.

    Args:
        text: The source text.
        terms: The terms to locate (each matched as a whole token).
        ignore_case: Match case-insensitively.

    Returns:
        A mapping from each input term to a float array of the token indices at
        which it occurs (empty if it never does). The mapping preserves the
        order of ``terms``.

    Examples:
        >>> offsets = term_offsets("the cat and the dog and the cat", ["cat", "dog"])
        >>> offsets["cat"].tolist()
        [1.0, 7.0]
        >>> offsets["dog"].tolist()
        [4.0]
    """
    toks = tokens(text)
    haystack = [t.lower() for t in toks] if ignore_case else toks
    result: dict[str, FloatArray] = {}
    for term in terms:
        needle = term.lower() if ignore_case else term
        positions = [i for i, tok in enumerate(haystack) if tok == needle]
        result[term] = np.asarray(positions, dtype=float)
    return result

lexograph.layout.dispersion.kwic(text, term, *, width=5, ignore_case=True)

Return keyword-in-context lines for term.

Parameters:

Name Type Description Default
text str

The source text.

required
term str

The term to find (matched as a whole token).

required
width int

How many context tokens to keep on each side.

5
ignore_case bool

Match case-insensitively.

True

Returns:

Name Type Description
One list[KWIC]

class:KWIC per occurrence, in text order.

Raises:

Type Description
ValueError

If width is negative.

Examples:

>>> lines = kwic("the small cat sat on the cat mat", "cat", width=2)
>>> len(lines)
2
>>> lines[0].left, lines[0].keyword, lines[0].right
('the small', 'cat', 'sat on')
Source code in lexograph/layout/dispersion.py
def kwic(
    text: str,
    term: str,
    *,
    width: int = 5,
    ignore_case: bool = True,
) -> list[KWIC]:
    """Return keyword-in-context lines for ``term``.

    Args:
        text: The source text.
        term: The term to find (matched as a whole token).
        width: How many context tokens to keep on each side.
        ignore_case: Match case-insensitively.

    Returns:
        One :class:`KWIC` per occurrence, in text order.

    Raises:
        ValueError: If ``width`` is negative.

    Examples:
        >>> lines = kwic("the small cat sat on the cat mat", "cat", width=2)
        >>> len(lines)
        2
        >>> lines[0].left, lines[0].keyword, lines[0].right
        ('the small', 'cat', 'sat on')
    """
    if width < 0:
        msg = f"width must be non-negative, got {width}"
        raise ValueError(msg)
    toks = tokens(text)
    haystack = [t.lower() for t in toks] if ignore_case else toks
    needle = term.lower() if ignore_case else term
    lines: list[KWIC] = []
    for i, tok in enumerate(haystack):
        if tok != needle:
            continue
        left = " ".join(toks[max(0, i - width) : i])
        right = " ".join(toks[i + 1 : i + 1 + width])
        lines.append(KWIC(offset=i, left=left, keyword=toks[i], right=right))
    return lines

lexograph.layout.dispersion.KWIC dataclass

One keyword-in-context line.

Attributes:

Name Type Description
offset int

The token index of the keyword.

left str

The context tokens to the left, space-joined.

keyword str

The matched token, as it appeared in the text.

right str

The context tokens to the right, space-joined.

Source code in lexograph/layout/dispersion.py
@dataclass(frozen=True, slots=True)
class KWIC:
    """One keyword-in-context line.

    Attributes:
        offset: The token index of the keyword.
        left: The context tokens to the left, space-joined.
        keyword: The matched token, as it appeared in the text.
        right: The context tokens to the right, space-joined.
    """

    offset: int
    left: str
    keyword: str
    right: str