- name
- mixpanelyst
- description
- This skill should be used when the user asks about Mixpanel product analytics, event data, funnel analysis, retention curves, cohort analysis, segmentation queries, user behavior, conversion rates, churn, DAU/MAU, ARPU, revenue metrics, feature adoption, A/B test results, user paths, flow analysis, or any request to query, explore, visualize, or analyze Mixpanel data using Python. Also use when the user asks to read, write, or manage Mixpanel "business context" — the markdown documentation that grounds AI assistants in an organization's structure and goals.
- allowed-tools
- Bash Read Write WebFetch
# mixpanel_headless API Reference
Analyze Mixpanel data by writing and executing Python code using the `mixpanel_headless` library and `pandas`.
Before running bundled helper scripts, set `SKILL_DIR` to the absolute path of this
`skills/mixpanelyst` directory.
```python
import mixpanel_headless as mp
ws = mp.Workspace()
result = ws.query("Login", last=30)
print(result.df.head())
```
## Query Engines
| Question | Method | Returns |
|----------|--------|---------|
| How much? How many? Trends? | `ws.query()` | `QueryResult` |
| Do users convert through a sequence? | `ws.query_funnel()` | `FunnelQueryResult` |
| Do users come back? | `ws.query_retention()` | `RetentionQueryResult` |
| What paths do users take? | `ws.query_flow()` | `FlowQueryResult` |
| Who are they? What do they look like? | `ws.query_user()` | `UserQueryResult` |
All result types have a `.df` property returning a pandas DataFrame and a `.params` dict containing the bookmark JSON.
`FlowQueryResult` also has `.graph` (NetworkX DiGraph) and `.anytree` (list of tree roots).
**Quick lookups** use `python3 -c "..."` one-liners. **Multi-step analysis** writes `.py` files.
## Discovery — ALWAYS Do Both Steps Before Querying
Guessing event names causes silent empty results. Guessing API parameters causes TypeErrors and invalid queries. **Discover both the data schema AND the API surface before writing any query.**
### Step 1: Discover the data schema
```python
import mixpanel_headless as mp
from mixpanel_headless import Filter, GroupBy, Metric
ws = mp.Workspace()
# 1. Find real event names
events = ws.events()
top = ws.top_events(limit=10)
print("Events:", events[:20])
print("Top:", [(e.event, e.count) for e in top])
# 2. Find real property names for the event you'll query
props = ws.properties("Login") # use an actual event name from step 1
print("Properties:", props)
# 3. (Optional) Check property values to validate filter inputs
vals = ws.property_values("platform", event="Login")
print("Platforms:", vals)
```
### Step 2: Discover the API surface with `help.py`
**`help.py` is the source of truth for method signatures, parameter names, type constructors, and enum values.** The method signatures later in this document are summaries — always verify with `help.py` before using a method or type you haven't looked up.
**Never guess parameter names.** If you're unsure whether a parameter is called `property` or `math_property`, or what arguments `GroupBy()` accepts, run `help.py` first. Wrong parameter names cause TypeErrors that waste tool calls.
```bash
# Look up a query method BEFORE writing the query
python3 $SKILL_DIR/scripts/help.py Workspace.query
python3 $SKILL_DIR/scripts/help.py Workspace.query_funnel
# Look up types BEFORE constructing them
python3 $SKILL_DIR/scripts/help.py Filter # → classmethods: .equals(), .less_than(), etc.
python3 $SKILL_DIR/scripts/help.py Metric # → property=, NOT math_property=
python3 $SKILL_DIR/scripts/help.py GroupBy # → property, property_type only
python3 $SKILL_DIR/scripts/help.py MathType # → enum values
# Look up result types to know what columns .df returns
python3 $SKILL_DIR/scripts/help.py QueryResult
python3 $SKILL_DIR/scripts/help.py FlowQueryResult
# Search when you're not sure of the exact name
python3 $SKILL_DIR/scripts/help.py search cohort # → CohortBreakdown, CohortMetric, CohortDefinition, ...
python3 $SKILL_DIR/scripts/help.py search retention # → query_retention, RetentionEvent, RetentionMathType, ...
# List everything
python3 $SKILL_DIR/scripts/help.py types # all public types
python3 $SKILL_DIR/scripts/help.py exceptions # all exceptions
```
For tutorials and guides: `WebFetch(url="https://mixpanel.github.io/mixpanel-headless/llms.txt")`
### Discovery method signatures
```python
def events(self) -> list[str]: ...
# List all event names (cached).
def properties(self, event: str) -> list[str]: ...
# List all property names for an event (cached).
def property_values(self, property_name: str, *, event: str | None = None, limit: int = 100) -> list[str]: ...
# Get sample values for a property.
def top_events(self, *, type: Literal['general', 'average', 'unique'] = 'general', limit: int | None = None) -> list[TopEvent]: ...
# Get today's most active events. TopEvent has .event (str), .count (int), .percent_change (float).
def funnels(self) -> list[FunnelInfo]: ...
# List saved funnels.
def cohorts(self) -> list[SavedCohort]: ...
# List saved cohorts.
def list_bookmarks(self, bookmark_type: BookmarkType | None = None) -> list[BookmarkInfo]: ...
# List all saved reports (bookmarks).
def lexicon_schemas(self, *, entity_type: EntityType | None = None) -> list[LexiconSchema]: ...
# List Lexicon schemas (event/property definitions).
def clear_discovery_cache(self) -> None: ...
# Clear cached discovery results.
# User Guide: WebFetch(url="https://mixpanel.github.io/mixpanel-headless/guide/discovery/index.md")
```
## Exploratory Analysis Workflow
When exploring an unfamiliar dataset or asked to "find insights," follow this systematic approach. Do NOT skip to querying — explore first.
### Step 1: Orient — Map the Event Schema
```python
import mixpanel_headless as mp
ws = mp.Workspace()
events = ws.events()
top = ws.top_events(limit=15)
print("Events:", events)
print("Top events:", [(e.event, e.count) for e in top])
# Profile the top 3-5 events by volume
for event in [e.event for e in top[:5]]:
props = ws.properties(event)
print(f"\n{event} ({len(props)} properties):")
for p in props[:15]:
vals = ws.property_values(p, event=event, limit=10)
print(f" {p}: {vals}")
```
### Step 2: Classify Properties
Infer property types from sampled values to decide how to use each:
- **Boolean**: values are `['true', 'false']` — segment with `group_by`, often pre-computed behavioral flags
- **Low-cardinality categorical** (<10 values): `platform`, `tier`, `category` — use for `group_by`
- **Numeric**: values parse as int/float: `price`, `total`, `count` — use for `math='average'` or `math='sum'` with `math_property=`
- **High-cardinality** (>100 values): IDs, names — skip for `group_by`, may need custom property cleanup
- **Temporal**: ISO dates or epoch values — use for time-based analysis
Property naming patterns that signal analytical value:
- `is_*`, `has_*`, `was_*`, `post_*` → boolean flags, often pre-computed behavioral segments worth investigating
- `*_total`, `*_count`, `*_value`, `*_amount` → numeric, aggregate with avg/sum/median
- `*_name`, `*_type`, `*_category`, `*_tier` → categorical, use for breakdowns
- `*_id`, `*_uuid` → identifiers, skip for breakdowns
### Step 3: Scan for Significant Segments
For each boolean and low-cardinality categorical property on key events, run a quick breakdown against a numeric metric:
```python
# Example: scan all interesting properties on a purchase event
numeric_prop = 'order_total' # or whatever the key metric is
interesting_props = [p for p in props if not p.endswith('_id')]
for prop in interesting_props:
vals = ws.property_values(prop, event=event, limit=10)
if len(set(vals)) <= 10: # low cardinality — worth a breakdown
result = ws.query(event, math='average', math_property=numeric_prop,
group_by=prop, last=90, mode='total')
print(f"\n{numeric_prop} by {prop}:")
print(result.df.to_string(index=False))
# Flag segments where metric differs >15% from overall
```
### Step 4: Deep Dive on Significant Findings
When a breakdown reveals a notable difference (>15% between segments):
1. **Quantify**: calculate the exact ratio between segments
2. **Cross-reference**: does this segment differ on other metrics too?
3. **Investigate causally**: run funnels or retention filtered by the segment
4. **Control for confounds**: add a second `group_by` dimension to check if the effect holds
### Step 5: Analyze Messy String Properties
When string properties have complex/unreadable values (e.g., campaign names from tools like Braze):
1. Sample 15-20 values to identify the naming convention
2. Look for structural patterns: date codes, targeting prefixes, channel suffixes, audience tags
3. Design regex cleanup rules, one layer per structural element
4. Create a custom property with `ws.create_custom_property(CreateCustomPropertyParams(...))`
5. Verify by querying with the custom property as `group_by`
### Custom Property Formula Reference
Formulas use a SQL-like expression language. Variables (A, B, _A, etc.) map to properties via `composedProperties`.
**Variable binding:** `LET(name, expression, body)` — define intermediate results:
```
LET(raw, A, REGEX_REPLACE(raw, "pattern", "replacement"))
LET(x, A * B, IFS(x < 50, "low", x < 200, "mid", TRUE, "high"))
```
**Conditionals:** `IF(cond, then, else)`, `IFS(cond1, val1, cond2, val2, ..., TRUE, default)`
**String functions:** `UPPER(s)`, `LOWER(s)`, `LEN(s)`, `LEFT(s, n)`, `RIGHT(s, n)`, `MID(s, start, count)`, `SPLIT(s, delim, n)`, `HAS_PREFIX(s, p)`, `HAS_SUFFIX(s, p)`, `PARSE_URL(s, "domain")`
**Regex functions (PCRE2 engine):**
- `REGEX_MATCH(haystack, pattern)` — returns true/false
- `REGEX_EXTRACT(haystack, pattern, capture_group)` — returns match or capture group
- `REGEX_REPLACE(haystack, pattern, replacement)` — replaces all matches
**Regex engine quirks (Mixpanel-specific):**
- **Case-insensitive by default** — use `(?-i)` to switch to case-sensitive matching within a pattern
- **Backreferences work** — `$1`, `$2` capture groups and `$0` whole-match all work in `REGEX_REPLACE` replacements
- **`{n,m}` quantifiers conflict with formula syntax** — curly braces are parsed as formula constructs. Use repeated character classes instead (e.g., `[0-9][0-9][0-9][0-9]` instead of `[0-9]{4}`)
- **`\d`, `\w` shorthand classes don't work** — use `[0-9]`, `[A-Za-z0-9_]` explicitly
- **Escape backslashes carefully** — in formula strings, `\\\\` may be needed for a literal `\` depending on how the formula is constructed (Python string → JSON → regex engine)
**CamelCase splitting** — insert space between lowercase→uppercase boundaries:
```
REGEX_REPLACE(text, "(?-i)([a-z])([A-Z])", "$1 $2")
// ChickenSundaysApril → Chicken Sundays April
```
**Practical multi-step cleanup example** (campaign names from Braze):
```
LET(s1, REGEX_REPLACE(A, "^[0-9][0-9][0-9][0-9][0-9]*_", ""),
LET(s2, REGEX_REPLACE(s1, "^(NW|TARGETED|REGIONAL|NTL)_", ""),
LET(s3, REGEX_REPLACE(s2, "_(Push|Email|NotificationCenter|ModalInAppMessage)_.*$", ""),
LET(s4, REGEX_REPLACE(s3, "_", " "),
REGEX_REPLACE(s4, " +", " ")
))))
```
**Type functions:** `STRING(x)`, `NUMBER(x)`, `BOOLEAN(x)`, `DEFINED(x)`
**Math:** `+`, `-`, `*`, `/`, `%`, `MIN(a,b)`, `MAX(a,b)`, `FLOOR(n)`, `CEIL(n)`, `ROUND(n)`
**Date:** `DATEDIF(start, end, unit)` — units: D, M, Y, MD, YM, YD. `TODAY()` for current date.
**List:** `SUM(list)`, `ANY(x, list, expr)`, `ALL(x, list, expr)`, `FILTER(x, list, expr)`, `MAP(x, list, expr)`
**Comparison:** `==`, `!=`, `<`, `>`, `<=`, `>=` (case-insensitive for strings), `IN` for list membership
Auf GitHub ansehen