# Totals, unique users and counting traps

> Why the same week of data gives different numbers of 'active users', and how to count them right.

- Course: [Product Analytics: From Data to Decisions](https://productovi.com/courses/product-analytics.md)
- Module: What exactly are we measuring?
- Lesson 2/32, ~32 min
- Source: https://productovi.com/courses/product-analytics/counting-traps

## Where does 22 come from?

**Alex Tutor:** Back on the Halves product team: Maya has a number for the board and wants you to check it.

**Maya:** The board deck is due Friday. I counted everyone in last week's export: 22 active users. Can that go on the slide?

**Sofia:** The file has 147 rows, though. Which is the real number?

**Alex Tutor:** Neither, yet. 147 is the **event total**: every row, repeats included. 22 is the number of **unique users**: everyone with at least one row, counted once.

![A tall stack of paper receipts stands next to a short row of person figures, each standing once.](https://productovi.com/courses/product-analytics/inline/counting-traps-illustration-2.webp)

*Rows and people are different counts: one busy person can leave a dozen rows.*

**Dataset (CSV):** [Halves event log, 1–7 June (147 rows)](https://productovi.com/datasets/product-analytics/counting-traps/halves-events-week-1.csv)

Every event from last week, in time order. Times are UTC.

- `event_time`: When it happened, in UTC
- `user_id`: Who: the Halves account
- `event`: What happened: app_opened, expense_added or settle_up_recorded
- `platform`: Where the event was logged: ios, android or server
- `entry_method`: expense_added only: manual, receipt_scan or recurring (created by the server)
- `amount_usd`: Amount in US dollars, for expenses and settle-ups
- `settlement_id`: settle_up_recorded only: which settlement the row belongs to

**Dataset (CSV):** [Halves users in the log (22 rows)](https://productovi.com/datasets/product-analytics/counting-traps/halves-users-week-1.csv)

One row per account that appears in the event log.

- `user_id`: The Halves account
- `signup_date`: Date the account was created
- `platform`: The user's phone platform: ios or android
- `acquisition_channel`: How the user came to Halves: invite, app_store_search, paid_social, referral_link or staff
- `timezone`: The user's home time zone
- `is_internal`: true for a team account, false for a customer; not always filled in

**Alex Tutor:** u014 has 12 rows this week, u005 has one. In the total, u014 weighs twelve times more. In unique users, each counts once.

**Sofia:** So “how many people” means unique users. Then 22 is right?

**Alex Tutor:** It counts people with *any* row. Before calling them active, look at who they are.

> **Exercise:** u014 already opened Halves this week. Next week the same person opens it 10 more times than usual. **True or false:** both the event total and the number of unique users go up.

## Rows nobody made

**Alex Tutor:** Some of the 22 have only one kind of row: an expense at 06:00 UTC on Monday, `platform = server`, `entry_method = recurring`.

**Alex Tutor:** Those are **server-side events**: Halves added their monthly bills, like rent, automatically. These people never opened the app all week.

**Maya:** They pay for Plus, though. Don't they count?

**Alex Tutor:** As subscribers, yes. As active users, no. “Active” should mean the person did something. For Halves, let's say: opened the app at least once that week.

> **Exercise:** Use the event log. How many **unique users opened the app** (`app_opened`) at least once during the week?

## The busiest users work here

**Sofia:** Two accounts stand out: 20 rows each, and every expense is exactly \$1.

**Alex Tutor:** A classic QA pattern: testers add tiny expenses to check that the app works. That's **internal traffic**, and it doesn't belong in product metrics.

![Two staff members with badges keep tapping a test phone at a desk while a pile of tiny identical receipts grows beside them.](https://productovi.com/courses/product-analytics/inline/counting-traps-illustration-3.webp)

*Testers' taps look like a busy user, but they don't belong in product numbers.*

**Maya:** Easy, there's an `is_internal` flag. Filter it out.

**Alex Tutor:** Try it: the flag catches one account. The other has an empty `is_internal`, because someone forgot to set it. Another column in the users file gives it away.

**Alex Tutor:** Treat any flag set by hand as incomplete until you've checked it.

> **Exercise:** Use the event log and the users file, and remove **every** staff account, flagged or not. How many real users opened the app at least once during the week?

**Alex Tutor:** Maya will rerun this every week, so the staff filter has to be written down. Two versions are on the table.

> **Exercise:** Which rule should the weekly report use to drop staff accounts?

## Counting expenses

**Theo:** For the store page I want “expenses added last week”. The file has 60 `expense_added` rows. Can I use 60?

**Alex Tutor:** Same traps, now in an event count. Some of those rows were created by the server, and some were typed by the two QA accounts.

**Sofia:** So I drop `recurring` rows and both staff accounts, then count rows?

**Alex Tutor:** Yes. Here counting rows is right, because each expense is exactly one event. What's wrong is the rows that no person added.

> **Exercise:** Theo needs “expenses added by people last week”. Of the 60 `expense_added` rows, how many were added by real users in the app, without recurring bills and staff accounts?

**Alex Tutor:** Theo has one more request for the store page: a typical expense. Here an average can mislead.

> **Exercise:** Theo wants "a typical expense" for the store page. With staff accounts removed, last week's 40 `expense_added` rows average \$81.28 and their median is \$40.50. Which number fits, and why?

## Settle-ups: count payments, not rows

**Sofia:** And settle-ups? There are 11 `settle_up_recorded` rows.

**Alex Tutor:** Here rows overcount. The payer logs a settle-up, and the receiver's phone logs it again on confirmation. To count payments, count distinct `settlement_id`.

![Two people each hold half of the same torn receipt; the two halves fit together into one.](https://productovi.com/courses/product-analytics/inline/counting-traps-illustration-4.webp)

*Payer and receiver both log it: two rows, one settle-up.*

**Maya:** And “settle-ups per user”? Do I credit the payer or the receiver?

**Alex Tutor:** Depends on the question. Payments settled: count ids. People who settled up: count users on either side. Write down which one you mean.

> **Exercise:** The log has 11 `settle_up_recorded` rows. How many **settle-ups** (payments) do they describe?

**Alex Tutor:** Now Maya's other reading: not payments, but people who took part in a settle-up, as payer or as receiver.

> **Exercise:** How many **different users** have at least one `settle_up_recorded` row in the week's log?

## The answer for Maya

**Alex Tutor:** Put it together. The same week gives four different “user” numbers, depending on what you filter out.

> **One week, four counts**
>
> | Who is counted | Users |
> |---|---|
> | Anyone with a row in the log | 22 |
> | … who opened the app | 19 |
> | … minus accounts with `is_internal = true` | 18 |
> | … minus every staff account | **17** |

**Maya:** So the slide says 17? That looks worse than 22.

**Alex Tutor:** It's the number that survives when someone checks it. And put the definition under it, so everyone who recounts gets 17.

**Sofia:** Weekly active users: real people, not staff, who opened the app at least once during the week.

**Alex Tutor:** Before the definition, one last sorting job: which of this week's numbers need distinct ids, and which are honest as row counts?

> **Exercise:** Which of these numbers must be counted as **distinct ids** rather than rows? Select all that apply.

> **Exercise:** Write to Maya in 2–3 sentences: why “22 active users” is wrong for last week and what number should go on the slide instead, with its definition.

> **Key ideas**
>
> - The **event total** counts rows, repeats included; **unique users** count each person once. Pick the one your question needs.
> - **Server-side events** (recurring bills) put people in the log who never opened the app: 22 users with any event, 19 who opened it.
> - **Internal traffic** must go, and one flag isn't enough: `is_internal` removes one staff account (18), `acquisition_channel = staff` finds the other (17).
> - For expenses, rows are right once cleaned: 60 − 5 recurring − 20 staff = 35 added by people.
> - Two-sided events overcount: 11 settle-up rows are 8 payments. Count distinct `settlement_id`.

**Alex Tutor:** Your sentence is almost a metric definition. Next lesson we make it precise enough that nobody can read it two ways.

- Previous lesson: [Events, properties and users](https://productovi.com/courses/product-analytics/events-properties-users.md)
- Next lesson: [Metric definition: one rule, one number](https://productovi.com/courses/product-analytics/metric-definitions.md)

---
Productovi Learning. Free, no sign-up needed to read.
