Skip to content

The Unified Query System

Four analytics engines. One Python vocabulary. Every query validated before it hits the network.

import mixpanel_data as mp

ws = mp.Workspace()

ws.query("Login")                           # Insights
ws.query_funnel(["Signup", "Purchase"])      # Funnels
ws.query_retention("Signup", "Login")        # Retention
ws.query_flow("Purchase")                    # Flows

Each method returns a typed result with a lazy .df property. Each method validates every parameter before making an API call. And the keyword arguments you learn for one method work in all the others.


The pattern

Every query method shares the same vocabulary:

# These keywords mean the same thing everywhere
result = ws.query(
    "Login",
    where=Filter.equals("country", "US"),  # filter
    group_by="platform",                    # breakdown
    last=90,                                # time range
)

result = ws.query_funnel(
    ["Signup", "Purchase"],
    where=Filter.equals("country", "US"),  # same
    group_by="platform",                    # same
    last=90,                                # same
)

result = ws.query_retention(
    "Signup", "Login",
    where=Filter.equals("country", "US"),  # same
    group_by="platform",                    # same
    last=90,                                # same
)

result = ws.query_flow(
    "Purchase",
    # flows only support cohort filters in where=
    where=Filter.in_cohort(123, "Power Users"),
    last=90,
)

Learn Filter, GroupBy, where=, group_by=, and last= once. Use them across engines (flows has some restrictions — see below).


Strings first, objects when you need them

Every parameter that accepts a typed object also accepts a plain string. Start simple, upgrade to objects when you need more control:

# Strings — simple and readable
ws.query("Login", group_by="country")

# GroupBy object — when you need numeric bucketing
ws.query(
    "Purchase",
    group_by=GroupBy(
        "revenue",
        property_type="number",
        bucket_size=50,
    ),
)
# Strings — just event names
ws.query_funnel(["Signup", "Purchase"])

# FunnelStep objects — when you need per-step filters
ws.query_funnel([
    FunnelStep("Signup"),
    FunnelStep("Purchase", filters=[Filter.greater_than("amount", 50)]),
])
# Strings — just born and return events
ws.query_retention("Signup", "Login")

# RetentionEvent objects — when you need per-event filters
ws.query_retention(
    RetentionEvent("Signup", filters=[Filter.equals("source", "organic")]),
    "Login",
)
# String — just an anchor event
ws.query_flow("Purchase")

# FlowStep object — per-step direction or filters
ws.query_flow(
    FlowStep(
        "Purchase",
        forward=5,
        reverse=2,
        filters=[Filter.greater_than("amount", 50)],
    ),
)

Mix freely. Strings and objects can appear in the same query.

Note: FlowStep.filters accepts any Filter type. The query-level where= parameter on query_flow() is more restricted — it only accepts cohort filters (Filter.in_cohort / Filter.not_in_cohort).


Filters: typed methods, not operator strings

Every filter is a class method on Filter. Autocomplete shows you every option:

from mixpanel_data import Filter

# String comparisons
Filter.equals("country", "US")
Filter.equals("country", ["US", "CA", "UK"])  # multi-value
Filter.not_equals("status", "banned")
Filter.contains("email", "@company.com")

# Numeric comparisons
Filter.greater_than("amount", 100)
Filter.less_than("age", 18)
Filter.between("revenue", 50, 500)

# Existence
Filter.is_set("utm_source")
Filter.is_not_set("phone")

# Boolean
Filter.is_true("is_premium")
Filter.is_false("opted_out")

# Dates
Filter.in_the_last("created", 30, "day")
Filter.before("signup_date", "2025-01-01")

# Cohorts (see "Cohort scoping" below)
Filter.in_cohort(123, "Power Users")
Filter.not_in_cohort(456, "Bots")

Combine multiple filters with where=:

# AND logic (default) — all conditions must match
result = ws.query("Purchase", where=[
    Filter.equals("country", "US"),
    Filter.greater_than("amount", 25),
    Filter.is_true("is_premium"),
])

Filters work identically across query(), query_funnel(), query_retention(), and query_flow() (flows: cohort filters only).


Results: DataFrames, params, and metadata

Insights, funnel, and retention results share a common structure:

result = ws.query("Login", math="dau", last=30)

result.df            # pandas DataFrame — lazy, cached
result.params        # the exact bookmark JSON sent to the API
result.from_date     # resolved start date
result.to_date       # resolved end date
result.computed_at   # when Mixpanel computed the result
result.meta          # sampling factor, cache status

Flow results have computed_at, params, and meta but not from_date/to_date — flow data is structured around nodes, edges, and trees instead.

Engine-specific results add domain-relevant properties:

# Funnels
result = ws.query_funnel(["Signup", "Purchase"])
result.overall_conversion_rate   # 0.12
result.steps_data                # per-step counts and ratios

# Retention
result = ws.query_retention("Signup", "Login")
result.cohorts                   # per-cohort-date retention data
result.average                   # synthetic average

# Flows (sankey mode)
result = ws.query_flow("Purchase")
result.nodes_df                  # step | event | type | count
result.edges_df                  # source -> target with counts
result.graph                     # NetworkX DiGraph
result.top_transitions(5)        # highest-traffic edges
result.drop_off_summary()        # per-step drop-off rates

# Flows (tree mode)
result = ws.query_flow("Purchase", mode="tree")
result.trees                     # recursive FlowTreeNode objects
result.anytree                   # anytree AnyNode objects

The four engines

Insights — query()

The general-purpose analytics engine. Counts, aggregations, DAU/WAU/MAU, formulas, rolling windows.

from mixpanel_data import Metric, Formula, Filter, GroupBy

# DAU over 90 days, weekly
result = ws.query("Login", math="dau", last=90, unit="week")

# Revenue percentiles
result = ws.query("Purchase", math="p99", math_property="amount")

# Per-user average purchases
result = ws.query(
    "Purchase",
    math="total",
    per_user="average",
    math_property="amount",
)

# 7-day rolling average
result = ws.query("Signup", math="unique", rolling=7, last=60)

# Multi-event comparison
result = ws.query(["Signup", "Login", "Purchase"], math="unique")

Formulas

Compute derived metrics. Letters A-Z reference events by position:

# Top-level formula parameter
result = ws.query(
    [
        Metric("Signup", math="unique"),
        Metric("Purchase", math="unique"),
    ],
    formula="(B / A) * 100",
    formula_label="Conversion Rate",
    unit="week",
)

# Or Formula objects in the event list
result = ws.query([
    Metric("Signup", math="unique"),
    Metric("Purchase", math="unique"),
    Formula("(B / A) * 100", label="Conversion Rate"),
])

The Metric class

When different events need different aggregation:

result = ws.query([
    Metric("Signup", math="unique"),
    Metric("Purchase", math="total", property="revenue"),
    Metric(
        "Support Ticket",
        math="unique",
        filters=[Filter.equals("priority", "high")],
    ),
])

Full reference: Insights Queries


Funnels — query_funnel()

Step-by-step conversion analysis with conversion windows, exclusions, and step ordering.

from mixpanel_data import (
    FunnelStep, Exclusion, HoldingConstant, Filter,
)

# Two-step funnel with 7-day conversion window
result = ws.query_funnel(
    ["Signup", "Purchase"],
    conversion_window=7,
)
print(f"Conversion: {result.overall_conversion_rate:.1%}")

# Per-step filters and labels
result = ws.query_funnel([
    FunnelStep("Signup"),
    FunnelStep(
        "Add to Cart",
        filters=[Filter.greater_than("item_count", 0)],
    ),
    FunnelStep("Checkout"),
    FunnelStep("Purchase", label="Completed Purchase"),
])

# Exclude events between steps
result = ws.query_funnel(
    ["Signup", "Add to Cart", "Purchase"],
    exclusions=["Logout"],  # all steps
    # Or target a specific range:
    # exclusions=[Exclusion("Refund", from_step=1, to_step=2)],
)

# Hold a property constant across all steps
result = ws.query_funnel(
    ["Signup", "Purchase"],
    # users must complete on same platform
    holding_constant="platform",
)

# Session-based conversion
result = ws.query_funnel(
    ["Browse", "Add to Cart", "Purchase"],
    conversion_window=1,
    conversion_window_unit="session",
    math="conversion_rate_session",
)

# Funnel trends over time
result = ws.query_funnel(
    ["Signup", "Purchase"],
    mode="trends",
    last=90,
    unit="week",
)

Full reference: Funnel Queries


Retention — query_retention()

Cohort retention with custom buckets, alignment modes, and display options.

from mixpanel_data import RetentionEvent, Filter

# Weekly retention, last 90 days
result = ws.query_retention(
    "Signup", "Login",
    retention_unit="week",
    last=90,
)

# Average retention curve
avg = result.average
for i, rate in enumerate(avg["rates"]):
    print(f"  Week {i}: {rate:.1%}")

# Custom retention milestones
result = ws.query_retention(
    "Signup", "Login",
    retention_unit="day",
    bucket_sizes=[1, 3, 7, 14, 30, 60, 90],
    last=90,
)

# Per-event filters
result = ws.query_retention(
    RetentionEvent(
        "Signup",
        filters=[Filter.equals("source", "organic")],
    ),
    RetentionEvent(
        "Purchase",
        filters=[Filter.greater_than("amount", 0)],
    ),
    retention_unit="month",
    last=180,
)

# Retention trends over time
result = ws.query_retention(
    "Signup", "Login",
    mode="trends",
    unit="week",
    last=180,
)

Full reference: Retention Queries


Flows — query_flow()

Path analysis with forward/reverse tracing, three visualization modes, and graph algorithms.

from mixpanel_data import FlowStep, Filter

# What happens after Purchase?
result = ws.query_flow("Purchase", forward=5)

# What leads to Cancel Subscription?
result = ws.query_flow("Cancel Subscription", forward=0, reverse=5)

# Both directions
result = ws.query_flow("Add to Cart", forward=3, reverse=2)

# Hide noisy events, increase path variety
result = ws.query_flow(
    "Purchase",
    hidden_events=[
        "Session Start", "Page View", "Heartbeat",
    ],
    cardinality=10,
    collapse_repeated=True,
    last=90,
)

Three visualization modes

# Sankey (default) — aggregated node/edge graph
result = ws.query_flow("Purchase", mode="sankey")
print(result.nodes_df)
print(result.edges_df)
g = result.graph   # NetworkX DiGraph

# Paths — top user paths as sequences
result = ws.query_flow("Purchase", mode="paths")
print(result.df)

# Tree — recursive decision tree from anchor
result = ws.query_flow("Purchase", mode="tree")
for tree in result.trees:
    print(tree.render())   # ASCII visualization

NetworkX graph

The sankey-mode graph unlocks the full NetworkX algorithm library:

import networkx as nx

g = result.graph

# Shortest path from Signup to Purchase
path = nx.shortest_path(g, "Signup@0", "Purchase@3")

# Biggest bottleneck — highest betweenness centrality
betweenness = nx.betweenness_centrality(g, weight="count")
bottleneck = max(betweenness, key=betweenness.get)

# Micro-conversion between any two steps
cart_out = sum(
    d["count"]
    for _, _, d in g.out_edges("Add to Cart@2", data=True)
)
to_purchase = g.edges["Add to Cart@2", "Purchase@3"]["count"]
print(f"Cart → Purchase: {to_purchase / cart_out:.1%}")

# Dead ends — reachable but leading nowhere
dead_ends = [
    n for n in g.nodes()
    if g.out_degree(n) == 0 and g.in_degree(n) > 0
]

Tree traversal

Tree mode gives per-node conversion and drop-off counts:

result = ws.query_flow("Signup", mode="tree", forward=4)

for tree in result.trees:
    # At each fork, what % takes each branch?
    for node in tree.flatten():
        if node.children:
            total = node.total_count
            print(f"\nAfter {node.event} ({total} users):")
            ranked = sorted(
                node.children,
                key=lambda c: c.total_count,
                reverse=True,
            )
            for child in ranked:
                pct = child.total_count / total * 100
                print(f"  -> {child.event}: {pct:.0f}%")

    # Best path through the product
    best = max(
        tree.all_paths(),
        key=lambda p: p[-1].converted_count,
    )
    print(" -> ".join(
        f"{n.event}({n.conversion_rate:.0%})"
        for n in best
    ))

    # Biggest single drop-off
    worst = max(
        tree.flatten(),
        key=lambda n: n.drop_off_count,
    )
    print(f"Biggest drop-off: {worst.event} "
          f"({worst.drop_off_count} users lost)")

Trees also convert to anytree nodes for rendering, export, and Graphviz:

from anytree import RenderTree
from anytree.exporter import UniqueDotExporter

root = result.anytree[0]
for pre, _, node in RenderTree(root):
    print(f"{pre}{node.event} ({node.total_count})")

UniqueDotExporter(root).to_picture("flow.png")

Full reference: Flow Queries


Cross-cutting capabilities

These features work across multiple engines through the same parameters.

Cohort scoping

Three ways to use cohorts — all accept either a saved cohort ID or an inline CohortDefinition:

from mixpanel_data import (
    Filter, CohortBreakdown, CohortMetric,
    CohortCriteria, CohortDefinition,
)

# Define a cohort inline — no UI, no saving, no ID to look up
power_users = CohortDefinition(
    CohortCriteria.did_event("Purchase", at_least=3, within_days=30)
)

1. Filter — restrict a query to a segment

# Saved cohort
result = ws.query(
    "Login",
    where=Filter.in_cohort(123, "Power Users"),
)

# Inline cohort
result = ws.query(
    "Login",
    where=Filter.in_cohort(power_users, name="Power Users"),
)

# Exclude a cohort
result = ws.query(
    "Login",
    where=Filter.not_in_cohort(456, "Bots"),
)

# Works in all four engines
result = ws.query_funnel(
    ["Signup", "Purchase"],
    where=Filter.in_cohort(power_users, name="PU"),
)
result = ws.query_retention(
    "Signup", "Login",
    where=Filter.in_cohort(123, "PU"),
)
result = ws.query_flow(
    "Purchase",
    where=Filter.in_cohort(123, "PU"),
)

2. Breakdown — compare a segment against everyone else

result = ws.query(
    "Purchase",
    group_by=CohortBreakdown(123, "Power Users"),
)
# Two segments: "Power Users" and "Not In Power Users"

# Show only the cohort (no negation segment)
result = ws.query(
    "Purchase",
    group_by=CohortBreakdown(
        123, "PU", include_negated=False,
    ),
)

# Combine with property breakdowns
result = ws.query(
    "Purchase",
    group_by=[CohortBreakdown(123, "PU"), "platform"],
)

Works with query(), query_funnel(), and query_retention().

3. Metric — track cohort size over time

# How many power users do we have each week?
result = ws.query(
    CohortMetric(123, "Power Users"),
    last=90,
    unit="week",
)

# What % of active users are power users?
result = ws.query(
    [
        Metric("Login", math="unique"),
        CohortMetric(123, "Power Users"),
    ],
    formula="(B / A) * 100",
    formula_label="Power User %",
)

Works with query() only.

Engine compatibility

Capability query() query_funnel() query_retention() query_flow()
Cohort Filters yes yes yes yes
Cohort Breakdowns yes yes yes --
Cohort Metrics yes -- -- --

Custom properties

Use saved custom properties by ID or define computed properties inline at query time:

from mixpanel_data import (
    CustomPropertyRef, InlineCustomProperty,
    GroupBy, Filter, Metric,
)

# Reference a saved custom property
ref = CustomPropertyRef(42)

# Or define one inline — no UI, no saving
revenue = InlineCustomProperty.numeric(
    "A * B", A="price", B="quantity",
)

Custom properties plug into the same parameters you already know:

# Breakdown
result = ws.query(
    "Purchase",
    group_by=GroupBy(
        property=revenue,
        property_type="number",
        bucket_size=100,
    ),
)

# Filter
result = ws.query(
    "Purchase",
    where=Filter.greater_than(property=ref, value=100),
)

# Measurement
result = ws.query(
    Metric("Purchase", math="average", property=ref),
)

# Mix with regular properties
result = ws.query(
    "Purchase",
    group_by=[
        "country",
        GroupBy(
            property=revenue,
            property_type="number",
            bucket_size=50,
        ),
    ],
    where=[
        Filter.equals("platform", "iOS"),
        Filter.greater_than(property=ref, value=100),
    ],
)

Works with query(), query_funnel(), and query_retention().


Breakdowns

group_by= accepts strings, GroupBy objects, CohortBreakdown objects, and lists mixing all three:

# String — simple property breakdown
result = ws.query("Login", group_by="platform")

# Multiple properties
result = ws.query("Purchase", group_by=["country", "platform"])

# Numeric bucketing
result = ws.query(
    "Purchase",
    group_by=GroupBy(
        "revenue",
        property_type="number",
        bucket_size=50,
        bucket_min=0,
        bucket_max=500,
    ),
)

# Cohort breakdown
result = ws.query(
    "Purchase",
    group_by=CohortBreakdown(123, "Power Users"),
)

# Custom property breakdown
result = ws.query(
    "Purchase",
    group_by=GroupBy(
        property=CustomPropertyRef(42),
        property_type="number",
    ),
)

# Mix all types
result = ws.query(
    "Purchase",
    group_by=[
        "country",
        GroupBy(
            "revenue",
            property_type="number",
            bucket_size=50,
        ),
        CohortBreakdown(123, "Power Users"),
    ],
)

Time ranges

last= and absolute dates (from_date/to_date) work in all four engines. The unit= parameter applies to insights, funnels, and retention (flows has no unit=).

# Relative — last N days (default: 30)
result = ws.query("Login", last=90)
result = ws.query("Login", last=12, unit="week")

# Absolute — explicit dates
result = ws.query(
    "Login",
    from_date="2025-01-01",
    to_date="2025-03-31",
)

# Hourly granularity (insights only)
result = ws.query("Login", last=2, unit="hour")

Validation

Every query is validated before the API call. Invalid parameters raise BookmarkValidationError with all errors at once — no "fix one, discover the next" cycle:

from mixpanel_data import BookmarkValidationError

try:
    ws.query_funnel([""], conversion_window=-1)
except BookmarkValidationError as e:
    for error in e.errors:
        print(f"[{error.code}] {error.path}: {error.message}")
        if error.suggestion:
            print(f"  Did you mean: {error.suggestion}")
    # [F2_EMPTY_STEP_EVENT] steps[0].event:
    #   Step event name must be a non-empty string
    # [F1_MIN_STEPS] steps:
    #   At least 2 steps are required (got 1)
    # [F3_CONVERSION_WINDOW_POSITIVE] conversion_window:
    #   conversion_window must be a positive integer

Each error includes:

Field What it is
code Machine-readable rule ID (e.g., V1, F3, R6)
path JSONPath-like location (e.g., show[0].math)
message Human-readable description
severity "error" or "warning"
suggestion Fuzzy-matched alternatives for invalid enum values
fix Suggested fix payload for programmatic correction

You can also validate insights, funnel, or retention bookmark JSON directly (flow params are validated internally by query_flow()):

from mixpanel_data import validate_bookmark

errors = validate_bookmark(some_bookmark_dict)

Inspect and persist: build_*_params()

Every query_*() method has a corresponding build_*_params() that generates and validates the bookmark JSON without executing the query:

# Inspect what would be sent to the API
params = ws.build_params(
    "Login", math="dau", group_by="platform", last=90,
)
params = ws.build_funnel_params(
    ["Signup", "Purchase"], conversion_window=7,
)
params = ws.build_retention_params(
    "Signup", "Login", retention_unit="week",
)
params = ws.build_flow_params(
    "Purchase", forward=3, reverse=1,
)

import json
print(json.dumps(params, indent=2))

Save any query as a Mixpanel report:

from mixpanel_data import CreateBookmarkParams

result = ws.query("Login", math="dau", group_by="platform", last=90)

ws.create_bookmark(CreateBookmarkParams(
    name="DAU by Platform (90d)",
    bookmark_type="insights",
    params=result.params,
))

This works for all four engines. Build params in code, persist them as reports visible in the Mixpanel UI.


One query, eight capabilities

Here's the single query that best demonstrates what the unified system can do. The business question it answers: "What's the ARPU among activated users, by pricing plan, as a 4-week rolling average over the last quarter?"

Every B2B SaaS PM asks this. In the Mixpanel UI it requires creating a saved custom property, creating a saved cohort, building a two-metric report with a formula, adding a breakdown, and configuring rolling analysis — minimum 10 clicks across 3 screens, plus two permanent entities to manage.

Here it's one call with zero pre-saved entities:

from mixpanel_data import (
    Workspace, Metric, Filter,
    InlineCustomProperty,
    CohortCriteria, CohortDefinition,
)

ws = Workspace()

# Revenue doesn't exist in the data — compute it
revenue = InlineCustomProperty.numeric(
    "A * B", A="price", B="quantity",
)

# Define the target segment in code
activated = CohortDefinition(
    CohortCriteria.did_event(
        "Complete Onboarding",
        at_least=1,
        within_days=14,
    )
)

# One call. Eight capabilities.
result = ws.query(
    [
        Metric(
            "Purchase",
            math="total",
            property=revenue,       # inline custom property
        ),
        Metric("Purchase", math="unique"),
    ],
    formula="(A / B)",              # ARPU formula
    formula_label="ARPU",
    where=Filter.in_cohort(         # inline cohort filter
        activated, name="Activated",
    ),
    group_by="plan",                # breakdown by plan tier
    rolling=4,                      # 4-week rolling average
    unit="week",
    last=90,
)

print(result.df)
# DataFrame with date, event, count columns:
#         date          event   count
# 0 2025-01-06  ARPU | Free   12.50
# 1 2025-01-06  ARPU | Pro    87.30
# 2 2025-01-06  ARPU | Ent   245.00
# ...

# Happy with it? Save as a Mixpanel report:
from mixpanel_data import CreateBookmarkParams
ws.create_bookmark(CreateBookmarkParams(
    name="ARPU by Plan (Activated Users)",
    bookmark_type="insights",
    params=result.params,
))

What each line proves:

Feature What it demonstrates
InlineCustomProperty.numeric(...) Compute values that don't exist in your data
CohortDefinition(CohortCriteria(...)) Define segments in code, no UI trip
Metric(..., property=revenue) Custom properties plug into standard params
Two Metric objects Different aggregation per metric
formula="(A / B)" Derived KPIs from multiple metrics
Filter.in_cohort(activated, ...) Scope to a programmatic segment
group_by="plan" Segment by the dimension that matters
rolling=4 Smooth weekly noise into a trend

Change within_days=14 to within_days=7 and re-run. Swap "plan" for "country". Change the formula to A * B * (1 - C) to add discounts. Each iteration is instant — no UI round-trips, no saved entities to update.


Putting it all together

A complete analysis combining multiple engines:

import mixpanel_data as mp
from mixpanel_data import (
    Metric, Formula, Filter, GroupBy,
    FunnelStep, Exclusion, RetentionEvent,
    CohortCriteria, CohortDefinition,
    CohortBreakdown, InlineCustomProperty,
    CustomPropertyRef, CreateBookmarkParams,
)

ws = mp.Workspace()

# --- Reusable segments and computed properties ---

premium_users = CohortDefinition(
    CohortCriteria.did_event(
        "Upgrade", at_least=1, within_days=365,
    )
)

revenue = InlineCustomProperty.numeric(
    "A * B", A="price", B="quantity",
)


# --- Insights: revenue trends by country ---

daily_revenue = ws.query(
    Metric("Purchase", math="total", property=revenue),
    group_by="country",
    last=90,
    unit="week",
)
print(daily_revenue.df)


# --- Insights: conversion rate formula ---

conversion = ws.query(
    [
        Metric("Signup", math="unique"),
        Metric("Purchase", math="unique"),
    ],
    formula="(B / A) * 100",
    formula_label="Conversion Rate",
    where=Filter.in_cohort(
        premium_users, name="Premium",
    ),
    unit="week",
    last=90,
)
print(conversion.df)


# --- Funnels: checkout flow, premium vs. everyone ---

checkout = ws.query_funnel(
    [
        FunnelStep("Browse"),
        FunnelStep(
            "Add to Cart",
            filters=[Filter.greater_than("item_count", 0)],
        ),
        FunnelStep("Checkout"),
        FunnelStep("Purchase"),
    ],
    conversion_window=7,
    exclusions=["Logout"],
    group_by=CohortBreakdown(
        premium_users, name="Premium Users",
    ),
    last=90,
)
print(f"Overall: {checkout.overall_conversion_rate:.1%}")
print(checkout.df)


# --- Retention: do organic signups retain better? ---

bucket_sizes = [1, 3, 7, 14, 30]
organic_retention = ws.query_retention(
    RetentionEvent(
        "Signup",
        filters=[Filter.equals("source", "organic")],
    ),
    "Login",
    retention_unit="day",
    bucket_sizes=bucket_sizes,
    last=90,
)
avg = organic_retention.average
for day, rate in zip(bucket_sizes, avg["rates"]):
    print(f"  Day {day}: {rate:.1%}")


# --- Flows: what do users do after failed checkout? ---

failed_checkout = ws.query_flow(
    "Checkout Error",
    forward=4,
    hidden_events=["Session Start", "Page View"],
    cardinality=10,
    last=90,
)
for src, tgt, count in failed_checkout.top_transitions(5):
    print(f"  {src} -> {tgt}: {count:,}")

# Tree mode: where do users diverge?
tree_result = ws.query_flow(
    "Checkout Error", mode="tree", forward=4,
)
for tree in tree_result.trees:
    print(tree.render())


# --- Save any result as a Mixpanel report ---

ws.create_bookmark(CreateBookmarkParams(
    name="Checkout Funnel (Premium Segment)",
    bookmark_type="funnels",
    params=checkout.params,
))

Quick reference

Methods

Method Engine Positional args
query() Insights events — str, Metric, Formula, or list
query_funnel() Funnels steps — list of str or FunnelStep
query_retention() Retention born_event, return_event
query_flow() Flows event — str, FlowStep, or list

Each has a matching build_*_params() that returns the validated bookmark dict without querying.

Shared parameters

Parameter Type Default Engines
where= Filter \| list[Filter] None All (flows: cohort only)
group_by= str \| GroupBy \| CohortBreakdown None I, F, R
last= int 30 All
from_date= str (YYYY-MM-DD) None All
to_date= str (YYYY-MM-DD) None All
unit= "day" \| "week" \| "month" "day" I, F, R
mode= engine-specific varies All
math= engine-specific varies I, F, R
math_property= str None I, F

I = Insights, F = Funnels, R = Retention

Engine-specific parameters

Engine Unique parameters
Insights per_user, percentile_value, formula, formula_label, rolling, cumulative
Funnels conversion_window, conversion_window_unit, order, exclusions, holding_constant
Retention retention_unit, alignment, bucket_sizes
Flows forward, reverse, count_type, cardinality, collapse_repeated, hidden_events

Result types

Engine Result type Key properties
Insights QueryResult .df, .series, .params
Funnels FunnelQueryResult .df, .overall_conversion_rate
Retention RetentionQueryResult .df, .cohorts, .average
Flows FlowQueryResult .nodes_df, .edges_df, .graph, .trees

Imports

# Everything you might need
from mixpanel_data import (
    # Core
    Workspace,

    # Insights
    Metric, Formula, GroupBy, Filter,
    MathType, PerUserAggregation,

    # Funnels
    FunnelStep, Exclusion, HoldingConstant,
    FunnelMathType,

    # Retention
    RetentionEvent,
    RetentionMathType,

    # Flows
    FlowStep, FlowTreeNode,

    # Cohorts
    CohortCriteria, CohortDefinition,
    CohortBreakdown, CohortMetric,

    # Custom properties
    CustomPropertyRef, InlineCustomProperty, PropertyInput,

    # Persistence
    CreateBookmarkParams,

    # Validation
    validate_bookmark, BookmarkValidationError,
)

Next steps