Contents
or
Deep Breakdown
What it tells me (Situation Awareness):
(or ) is the cardinality of a set : the number of distinct elements it contains. For a finite set, cardinality is simply the count. For , . In DA/DS, cardinality is one of the most operationally important concepts: it tells you how many unique values a column contains, which determines the memory cost of categorical encoding, the number of cross-join rows, and whether a feature should be one-hot encoded or embedded. Every time you call .nunique() in pandas or COUNT(DISTINCT ...) in SQL, you are computing cardinality.
Flowchart
Real-world use
Where it is used
Exploratory data analysis (profiling each feature's number of unique values), choosing encoding strategy (one-hot for low-cardinality, embeddings for high-cardinality), checking primary key uniqueness (), estimating join complexity ( rows in a Cartesian product), and diagnosing data quality issues (a column with cardinality 1 is a constant and useless for modeling).
Problem it solves
When you receive a new dataset, you need to know: is the "city" column 3 values or 50,000 values? This directly determines how to encode it. One-hot encoding a column with would create 50,000 new binary columns, which is computationally and statistically disastrous. Cardinality-aware profiling prevents this mistake.
How it solves the problem
By computing for each column first, you can categorize features automatically: is a constant (drop), is low-cardinality (one-hot safe), is medium-cardinality (consider target encoding or ordinal encoding), is high-cardinality (embeddings or hashing). The threshold depends on the problem, but checking first is non-negotiable.
Example in a business
Profiling a customer transaction table before model building. You compute for every categorical column: city has , product_category has , payment_method has . Decision: one-hot encode product_category and payment_method; use target encoding for city. This cardinality-first approach prevents combinatorial explosions.
The formula, piece by piece
Breaking the notation:
- : vertical bars around a set symbol. Same bars as absolute value, but the argument is a set, not a number. Context distinguishes them.
- : alternative notation, common in combinatorics. Same meaning.
- For finite sets: = number of distinct elements.
- For infinite sets: cardinality becomes a more subtle concept (, , etc.), but in DA/DS all cardinalities are finite.
Key relationships:
- (inclusion-exclusion principle).
- (Cartesian product size).
- (power set: number of all subsets of ).
The formula in words
" is the count of distinct elements in the set . If , then . If , we first remove duplicates to get , so (sets do not repeat elements)."
Critical Concepts
- Sets have no duplicates by definition: is the same set as , so . Cardinality counts distinct elements.
- High-cardinality vs. low-cardinality features: this is a practical ML concept derived from set cardinality. Feature encoding strategy depends on it.
- Inclusion-exclusion: . You subtract the intersection because those elements would be counted twice. This is used in funnel analysis (how many users completed step A OR step B).
- Cardinality of the power set: the set of all subsets of has elements. This is why combinatorial search spaces explode: a set of 30 features has possible subsets.
- Cardinality and memory: in pandas, a column with unique values can be stored as a
Categoricaldtype using memory instead of .
Step-by-step calculation (tiny example)
Task: compute for and using inclusion-exclusion.
Step 1: Compute |A|, |B|, and |A ∩ B| . . , so .
Step 2: Apply inclusion-exclusion .
Step 3: Verify by listing the union . Count: 6 elements. Confirmed.
Code examples
Beginner example (Python)
# Cardinality: len() for sets, .nunique() for pandas columns
# Basic set cardinality
A = {1, 2, 3, 4}
B = {3, 4, 5, 6}
print(f"|A| = {len(A)}") # 4
print(f"|B| = {len(B)}") # 4
print(f"|A ∩ B| = {len(A & B)}") # 2
print(f"|A ∪ B| = {len(A | B)}") # 6 (inclusion-exclusion: 4+4-2=6)
print(f"|A ∪ B| by inclusion-exclusion: {len(A) + len(B) - len(A & B)}") # 6 # Sets remove duplicates automatically
C = {1, 1, 2, 2, 3}
print(f"\nC = {C}, |C| = {len(C)}") # C = {1, 2, 3}, |C| = 3 # Pandas: .nunique() computes cardinality of a column
import pandas as pd
df = pd.DataFrame({"city": ["Vienna", "Graz", "Vienna", "Linz", "Graz", "Vienna"]})
print(f"\nCardinality of 'city' column: {df['city'].nunique()}") # 3Beginner example (R)
# Cardinality in R: length(unique(x)) or length(set)
A <- c(1, 2, 3, 4)
B <- c(3, 4, 5, 6)
cat("|A| =", length(unique(A)), "\n") # 4
cat("|B| =", length(unique(B)), "\n") # 4
cat("|A ∩ B| =", length(intersect(A, B)), "\n") # 2
cat("|A ∪ B| =", length(union(A, B)), "\n") # 6 # Inclusion-exclusion manually
cat("|A| + |B| - |A ∩ B| =", length(A) + length(B) - length(intersect(A, B)), "\n") # 6 # Duplicate removal
C <- c(1, 1, 2, 2, 3)
cat("C unique count:", length(unique(C)), "\n") # 3 # Data frame column cardinality
df <- data.frame(city = c("Vienna", "Graz", "Vienna", "Linz", "Graz", "Vienna"))
cat("Cardinality of 'city':", length(unique(df$city)), "\n") # 3Professional example (Python)
import pandas as pd
import numpy as np
# Scenario: cardinality-aware feature profiling before model encoding
np.random.seed(42)
N_ROWS = 5_000
df = pd.DataFrame({
"user_id": range(N_ROWS), # high card (identifier)
"country": np.random.choice(["AT", "DE", "CH", "US"], N_ROWS), # low card
"city": np.random.choice([f"city_{i}" for i in range(200)], N_ROWS), # medium card
"product_category": np.random.choice(["electronics", "books", "clothing", "sports", "home"], N_ROWS), # low card
"session_source": np.random.choice(["organic", "paid", "email", "referral", "direct"], N_ROWS), # low card
"campaign_id": np.random.choice(range(3000), N_ROWS), # high card
"age_bucket": np.random.choice(["18-24", "25-34", "35-44", "45-54", "55+"], N_ROWS), # low card
"constant_col": ["active"] * N_ROWS, # zero variance
})
# Cardinality profiling
cardinality_report = pd.DataFrame({
"column": df.columns,
"cardinality": [df[col].nunique() for col in df.columns],
"n_rows": len(df),
})
cardinality_report["uniqueness_ratio"] = (
cardinality_report["cardinality"] / cardinality_report["n_rows"]
)
def recommend_encoding(card: int, n: int) -> str:
"""Recommend encoding strategy based on cardinality."""
ratio = card / n
if card == 1:
return "DROP (constant)"
if ratio == 1.0:
return "IDENTIFIER (drop or use as index)"
if card <= 10:
return "ONE-HOT"
if card <= 200:
return "TARGET ENCODE or ORDINAL"
return "HASH or EMBED"
cardinality_report["encoding_recommendation"] = cardinality_report.apply(
lambda row: recommend_encoding(row["cardinality"], row["n_rows"]), axis=1
)
print("Cardinality-Aware Feature Profiling")
print("=" * 65)
print(cardinality_report.to_string(index=False))Professional example (R)
# Cardinality-aware feature profiling and encoding recommendation
set.seed(42)
df <- data.frame(
user_id = sample(1:500, 1000, replace = TRUE),
region = sample(c("N","S","E","W"), 1000, replace = TRUE),
tier = sample(1:50, 1000, replace = TRUE)
)
n_rows <- nrow(df)
cardinality_report <- data.frame(
column = names(df),
cardinality = sapply(df, function(col) length(unique(col))),
n_rows = n_rows
)
recommend_encoding <- function(card, n_rows) {
if (card == n_rows)
return("IDENTIFIER (drop or use as index)")
if (card <= 10)
return("ONE-HOT")
if (card <= 200)
return("TARGET ENCODE or ORDINAL")
return("HASH or EMBED")
}
cardinality_report$encoding_recommendation <- mapply(
recommend_encoding, cardinality_report$cardinality, cardinality_report$n_rows
)
cat("Cardinality-Aware Feature Profiling\n")
print(cardinality_report)Source: AIO p. 30 of 286