Skip to content

Domain features (raw signals → model-ready)

This is where ml4 does what a generic AutoML tool can't. It doesn't treat a column like 137.118.191.41 or 513 LYNN ST,Hurt,VA,24563 as an opaque string to hash — it knows what the value means and applies domain knowledge to pull out the pieces that matter, compute relationships between columns, and hand the model dozens of typed, explainable features. All of this is leakage-safe by construction: the parsers are row-local and stateless, so they fit once and leak nothing.

1. A raw, messy frame

Real signup / support data arrives as raw strings — free text, packed tags, composite fields, timestamps. Here every column is exactly as captured:

import pandas as pd

df = pd.DataFrame({
    # --- identity & network ---
    "ip_string":    ["71.87.248.253", "72.87.248.253", "203.0.113.9", "10.0.0.4"],
    "email":        ["alex.m@gmail.com", "user@mailinator.com", "a@b", "jane.doe@acme.co"],
    "signup_url":   ["https://acme.co/promo?ref=x", "http://bit.ly/2xyz",
                     "https://192.168.0.1/", "https://shop.acme.co/cart"],
    "user_agent":   ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120",
                     "Dalvik/2.1.0 (Linux; Android 10; SM-A515U1)",
                     "myApp/2.8.9 iPhone", ""],
    # --- geography ---
    "billing_addr": ["513 LYNN ST,Hurt,VA,24563", "1306 S COOPER ST,KOKOMO,IN,46902",
                     "1016 SW 29TH ST,OKLAHOMA CITY,OK,73109", "PO BOX 22,TUCSON,AZ,85757"],
    "home_zip":     ["02467", "02002", "12345", "02135"],
    "ship_zip":     ["02368", "02467", "02135", "02445"],   # a second zip → distance features
    "account_owner":["Alex Morgan", "Carter Henderson", "Jamie Small", "Robin Abulafia"],
    # --- support-ticket text ---
    "subject":      ["Refund request for duplicate payment", "Upgrade mobile plan tier",
                     "Cannot log in after reset", "Downgrade business voice package"],
    "description":  ["The promotional discount was not applied to my account.",
                     "Customer would like a faster tier; add an additional line.",
                     "Password reset email never arrives; blocked at login.",
                     "Update the billing address and primary contact email."],
    "tags":         ["billing,refund,urgent", "plan,upsell", "auth,login,bug", "billing,account"],
    # --- timestamps ---
    "created_at":   pd.to_datetime(["2026-03-22", "2026-02-09", "2026-02-14", "2026-04-04"]),
    "resolved_at":  pd.to_datetime(["2026-03-24", "2026-02-11", "2026-02-14", "2026-04-10"]),
    "escalated":    [1, 0, 1, 0],
})

2. Route each column to its domain

DataConfig (on cfg.data) routes each raw column to the right parser. Nothing is auto-detected — you assign the domain:

from parleml_ml4 import AutoSupervised, Tasks
from parleml_ml4.config import presets

cfg = presets.quick_explore()
cfg.ml_task = Tasks.BINARY_CLASSIFICATION
cfg.data.target_column = "escalated"

# --- network & identity ---
cfg.data.ip_domain        = ["ip_string"]      # → country / ASN / org / network type / private / bogon
cfg.data.email_domain     = ["email"]          # → domain, role / disposable / typo flags, entropy
cfg.data.url_domain       = ["signup_url"]     # → subdomain / domain / TLD, shortener / IP-literal, path
cfg.data.useragent_domain = ["user_agent"]     # → browser / OS / device class, bot flag
# --- geography ---
cfg.data.address_domain   = ["billing_addr"]   # → number / street / type / city / state / PO-box
cfg.data.zip_domain       = ["home_zip", "ship_zip"]  # two zips → prefix / place / state + DISTANCE
cfg.data.names_domains    = [["account_owner"]]       # → known-name, ranks, length / script flags
# --- free text & packed tokens ---
cfg.data.text_domain      = ["subject", "description"]  # → TF-IDF → SVD topics + per-term target lift
cfg.data.embedding_domain = ["description"]             # → neural sentence embeddings, reduced
cfg.data.mt_domain        = ["tags"]                    # comma-separated bags → multi-token target encoding
# --- time ---
cfg.data.time_domain        = ["created_at", "resolved_at"]  # → calendar parts, weekend / holiday flags
cfg.data.base_time_columns  = ["created_at"]                 # anchor → days-since recency

3. What each domain extracts (the gains)

Every raw column becomes many typed features — not one blind hash:

Raw column Domain ml4 extracts / computes
ip_string IP country, region, city, ASN / org, network type, IPv6 / private / bogon flags
email Email local / domain parts, role / disposable / typo-squat flags, entropy
signup_url URL subdomain / domain / TLD, shortener / IP-literal flags, path depth
user_agent User-Agent browser / OS / device class, bot flag
billing_addr Address number, street, street-type, city, state, PO-box heuristic
home_zip, ship_zip Postal (×2) prefix / place / state plus the geographic DISTANCE between the two
account_owner Names known-name flag, popularity ranks, synthetic-name (repeat / mixed-script) flags
subject, description Text TF-IDF → SVD topics, per-term target lift, neural embeddings
tags Multi-token each comma-separated tag target-encoded from the packed bag
created_at, resolved_at Datetime (×2) calendar parts, weekend / business-hour, holiday flags, days-since anchor

Relationships across columns, not just within one

With two postal columns in zip_domain, ml4 derives the distance between home_zip and ship_zip (a classic fraud signal), plus match / prefix-match flags. The match step also emits cross-source consistency flags — e.g. "the billing city disagrees with the IP-geolocated city". See Stateless domain encoders.

4. Fit — parsing happens automatically

model = AutoSupervised(cfg).fit(df)
labels = model.predict(df.drop(columns=["escalated"]))

No manual regexes, gazetteers, or token pipelines: the IP became geo / ASN columns, the address split into street / city / state, the two zips produced a distance, the UA unpacked into OS / device class, the free text became topics + embeddings, the tags were target-encoded per token, and the timestamps became calendar + recency features — every one traced in the feature recipe for audit.

IP features need a MaxMind database — but degrade gracefully

The ip_domain geo / ASN features need the MaxMind GeoLite2 databases, which aren't bundled for licensing reasons. Unlike a hard failure, ml4 degrades to null for those features (with a one-time warning) and still emits the stdlib flags (private, bogon, IPv6). Set the databases up per the reference-data guide, or leave ip_domain=[].

See the FE suite guide, the stateless and stateful encoder catalogues, and the Geography & reference data guide for the MaxMind setup and the country setting.

Next: Geography & reference data · Stateless domain encoders