Positive Look-Behind in Regular Expressions
Positive look-behind (?<=...) is a zero-width assertion that checks whether a pattern is preceded by another pattern without consuming those characters. It lets you match something only when it is immediately preceded by a specific context. In data science this is extremely useful for extracting numbers that come after “Price: ”, product codes that follow “SKU:”, or IDs that appear after a known label — all without including the preceding text in the final match.
TL;DR — Positive Look-Behind
(?<=...)→ assert that ... must precede the match- Zero-width: the look-behind text is not part of the captured result
- Perfect for context-sensitive extraction from logs and reports
- Works seamlessly with pandas
.str.extract()
1. Basic Positive Look-Behind
import re
text = "Price: 1250 USD, Tax: 87 EUR, Total: 1337 USD"
# Numbers preceded by "Price: "
print(re.findall(r"(?<=Price: )\d+", text)) # ['1250']
# Product codes preceded by "SKU: "
print(re.findall(r"(?<=SKU: )([A-Z0-9-]+)", "SKU: ABC123 sold, SKU: XYZ789 in stock")) # ['ABC123', 'XYZ789']
2. Real-World Data Science Examples with Pandas
import pandas as pd
# Demo rows so this example runs as-is (in your project: df = pd.read_csv("logs.csv"))
df = pd.DataFrame({"log": [
"Price: 1,250.75 USD for ORD-12345 2026-03-19 alice@example.com verified SKU: ABC-123 in stock",
"Tax: 87.50 EUR for ORD-98765 2026-03-20 bob@spam.ru SKU: XYZ-789 out of stock",
"Cancelled ORD-55555 Price: 12.00 GBP carol@data.org Order ID: 777",
]})
# Example 1: Extract amounts only when preceded by "Price: " (positive look-behind)
df["price_amount"] = df["log"].str.extract(r"(?<=Price: )(\d+(?:,\d+)?(?:\.\d+)?)")
# Example 2: Extract order IDs only when preceded by "Order ID: "
df["order_id"] = df["log"].str.extract(r"(?<=Order ID: )(\d+)")
# Example 3: Extract SKUs only when preceded by "SKU: "
df["sku"] = df["log"].str.extract(r"(?<=SKU: )([A-Z0-9-]+)")
print(df[["price_amount", "order_id", "sku"]])
3. Advanced Positive Look-Behind
# Look-behind with a character class
text = "Profit: +1250 USD, Loss: -340 EUR"
print(re.findall(r"(?<=[+-])\d+", text)) # ['1250', '340']
# Python's re requires fixed-width look-behind: (?<=Price:\s*) raises re.error.
# Put the variable part outside and capture the value instead:
print(re.findall(r"Price:\s*(\d+)", "Price: 1250 USD")) # ['1250']
4. Best Practices in 2026
- Use positive look-behind
(?<=...)whenever you need context before the match without including it - Combine with capturing groups to extract only the part you want
- Keep look-behind expressions simple and fixed-width where possible for maximum compatibility and readability
- Pre-compile patterns that contain look-behind for repeated use on large datasets
- Use with pandas
.str.extract()for vectorized zero-width assertions across entire DataFrames
Positive Look-Behind for Clean Values
A positive look-behind (?<=...) matches a position that is preceded by a given text. The prefix decides which values you want, but only the value itself is returned, so there is nothing to strip afterwards.
It works well for labelled values with a fixed prefix, such as currency codes, field names like id= or hashtags. Because Python requires a fixed-width look-behind, list each prefix with the same length, or use separate look-behinds joined by | outside them, such as (?:(?<=\$)|(?<=USD ))\d+.
Joining look-behinds this way is allowed because each one is fixed-width on its own.
Worked Example: Amounts with Two Different Prefixes
We extract amounts written either as $25 or as USD 25 from a column of notes.
import pandas as pd
notes = pd.Series(["Paid $25 today", "Refund USD 40", "Fee EUR 5", "Tip $3 and USD 7"])
pattern = r"(?:(?<=\$)|(?<=USD ))\d+"
amounts = notes.str.findall(pattern)
print(amounts.tolist())
print(amounts.explode().dropna().astype(int).sum())
The results are [['25'], ['40'], [], ['3', '7']] and the total is 75. The euro fee is skipped because neither prefix matches, and the two look-behinds of different widths work because each is fixed on its own.
Practical Tips
Escape special characters in prefixes, such as \$, \. and \(, because inside a lookbehind they keep their regex meaning. An unescaped $ means end of string and silently matches nothing.
Collect the allowed prefixes in a list and build the pattern from it, joining one lookbehind per prefix. Adding a new currency then means adding a list entry rather than editing a long pattern by hand.
Finally, compare the extracted totals with a few hand-checked rows before trusting the pattern on the full column.
Convert the extracted strings to integers or decimals immediately, and keep the original text column for comparison during the first runs.
If amounts may contain thousands separators, such as $1,250, widen the value part to [\d,]+ and remove the commas before converting.
Common Mistakes
- Writing (?<=\$|USD ), which mixes widths in one look-behind and fails.
- Forgetting to escape $, which means end of string.
- Expecting the prefix in the result.
- Summing before converting text to numbers.
Quick Summary
- (?<=...) requires a prefix.
- Only the value is returned.
- Each look-behind must be fixed-width.
- Join several look-behinds with an outer group.
Conclusion
Positive look-behind (?<=...) is a zero-width superpower that gives you precise control over what precedes a match without consuming those characters. In 2026 data science projects it is essential for accurate, context-aware text extraction from logs, reports, and unstructured data. Master positive look-behind (together with its negative counterpart), combine it with pandas vectorized methods, and your regex pipelines will become significantly more precise and professional.
Next steps:
- Review one of your current regex patterns and add a positive look-behind assertion to make the extraction more context-sensitive