Conditional logic, conditional aggregation, ratios, and rounding
Problem¶
- How to create categories?
- How to count only rows that satisfy a condition?
- How to compute percentages?
Notions: Conditional logic, conditional aggregation, ratios, and rounding
SQL and Pandas syntax¶
SELECT
id,
CASE
WHEN amount >= 100 THEN 'high'
WHEN amount >= 50 THEN 'medium'
ELSE 'low'
END AS amount_category
FROM t;
SELECT
key,
SUM(CASE WHEN status = 'ok' THEN 1 ELSE 0 END) AS ok_count,
ROUND(100.0 * SUM(CASE WHEN status = 'ok' THEN 1 ELSE 0 END) / COUNT(*), 2) AS ok_pct
FROM t
GROUP BY key;
-- PostgreSQL alternatives
SELECT
key,
COUNT(*) FILTER (WHERE status = 'ok') AS ok_count,
ROUND((100.0 * COUNT(*) FILTER (WHERE status = 'ok') / COUNT(*))::numeric, 2) AS ok_pct
FROM t
GROUP BY key;
df["amount_category"] = "low"
df.loc[df["amount"] >= 50, "amount_category"] = "medium"
df.loc[df["amount"] >= 100, "amount_category"] = "high"
df["is_ok"] = df["status"].eq("ok")
out = (
df.groupby("key", as_index=False)
.agg(
ok_count=("is_ok", "sum"),
total_count=("is_ok", "size"),
ok_pct=("is_ok", lambda s: round(100 * s.mean(), 2))
)
)
Example¶
import sqlite3
import pandas as pd
## SQL
con = sqlite3.connect(":memory:")
con.executescript("""
CREATE TABLE payments (
id INTEGER,
user_id INTEGER,
amount REAL,
status TEXT
);
INSERT INTO payments VALUES
(1, 10, 20.0, 'paid'),
(2, 10, 150.0, 'failed'),
(3, 20, 75.0, 'paid'),
(4, 20, 125.0, 'paid');
""")
sql = """
-- Count paid payments and compute the paid percentage per user.
WITH labeled AS (
SELECT
*,
CASE
WHEN amount >= 100 THEN 'high'
WHEN amount >= 50 THEN 'medium'
ELSE 'low'
END AS amount_category
FROM payments
)
SELECT
user_id,
SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid_count,
COUNT(*) AS total_count,
ROUND(100.0 * SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) / COUNT(*), 2) AS paid_pct
FROM labeled
GROUP BY user_id
ORDER BY user_id;
"""
pd.read_sql_query(sql, con)
# user_id paid_count total_count paid_pct
# 0 10 1 2 50.0
# 1 20 2 2 100.0
## Pandas
payments = pd.DataFrame({
"id": [1, 2, 3, 4],
"user_id": [10, 10, 20, 20],
"amount": [20.0, 150.0, 75.0, 125.0],
"status": ["paid", "failed", "paid", "paid"]
})
payments["amount_category"] = "low"
payments.loc[payments["amount"] >= 50, "amount_category"] = "medium"
payments.loc[payments["amount"] >= 100, "amount_category"] = "high"
payments["is_paid"] = payments["status"].eq("paid")
payments
# id user_id amount status amount_category is_paid
# 0 1 10 20.0 paid low True
# 1 2 10 150.0 failed high False
# 2 3 20 75.0 paid medium True
# 3 4 20 125.0 paid high True
(
payments.groupby("user_id", as_index=False)
.agg(
paid_count=("is_paid", "sum"),
total_count=("is_paid", "size"),
paid_pct=("is_paid", lambda s: round(100 * s.mean(), 2))
)
)
# user_id paid_count total_count paid_pct
# 0 10 1 2 50.0
# 1 20 2 2 100.0