

0 / 2 embers
0 / 3000 xp
click for more info
Complete a lesson to start your streak
click for more info
Difficulty: 7
click for more info
Not enough gems
Cost: 6 gems
1: Event Data
incomplete
2: Event Structure
incomplete
3: Event Counting
incomplete
4: Resample
incomplete
5: Resample Aggregations
incomplete
6: Active Users
incomplete
7: Time Patterns
incomplete
8: Funnel Metrics
incomplete
9: Conversion Rates
incomplete
10: Funnel Drop-Offs
incomplete
11: Ordered Funnels
incomplete
12: Cohorts
incomplete
13: Cohort Retention
incomplete
14: Rolling Metrics
incomplete
15: Growth Rates
incomplete
Back
ctrl+,
Next
ctrl+.
This lesson's interactive features are locked, please to keep using them
Every (well-built) event record should have these columns:
| Column | Purpose | Example |
|---|---|---|
timestamp |
When it happened | 2024-03-15 14:22:01 |
user_id |
Who did it | user_4821 |
event_type |
What they did | purchase |
properties |
Extra context | {"amount": 29.99, "plan": "pro"} |
If the event can occur without authentication, like a pageview, then the user_id might be an anonymous identifier like a cookie ID, device ID, or unique request ID instead.
The properties column is the most interesting. It stores event-specific details in a fairly loose key-value format. A purchase event might have amount and plan properties, while a pageview event might have url and referrer properties.
While you could have a separate table for each event type with its own columns, many organizations take a less strict approach so that it's easier to add new event types and properties without changing the schema.
Properties often arrive as JSON strings, and in our case, we'll parse them with json.loads() to turn them into Python dictionaries.
events = pd.DataFrame(
{
"event_type": ["page_view", "page_view"],
"properties": [
'{"url": "/pricing", "referrer": "google"}',
'{"url": "/docs", "referrer": "newsletter"}',
],
}
)
parsed = events["properties"].apply(json.loads)
events["has_referrer"] = parsed.apply(lambda props: props.get("referrer") is not None)
The apply() method runs the parsing function on each row's properties value. After parsing, each row is a normal dictionary, so you can inspect it or extract values safely.
Alternatively, you can use pd.json_normalize() to expand a column of dicts into separate columns in one step:
props_df = pd.json_normalize(events["properties"].apply(json.loads))
events = pd.concat([events, props_df], axis=1)
The pd.concat() function stitches the original event columns and the normalized property columns back together.
Always handle missing keys with dict.get() to avoid errors when events don't have certain properties. Missing keys will return None automatically.
SnackStack events carry a JSON blob of extra context in their properties column, and the analytics team can't filter or aggregate on it until it's broken out into real columns.
Complete the parse_event_properties function. It accepts an events DataFrame whose properties column holds JSON strings and returns a copy with new amount and plan columns.