BRAGA

02 Writing02-02 Notes02-02-060

Contrapositive

Published 16 Apr 20269 min read
Contents
  1. 01Deep Breakdown
  2. 02Flowchart
  3. 03Real-world use
  4. 04The formula, piece by piece
  5. 05The formula in words
  6. 06Critical Concepts
  7. 07Step-by-step calculation (tiny example)
  8. 08Code examples

The contrapositive of p  ⟹  qp \implies q is ¬q  ⟹  ¬p\lnot q \implies \lnot p.

Deep Breakdown

What it tells me (Situation Awareness): The contrapositive of a conditional statement p  ⟹  qp \implies q ("if pp, then qq") is the logically equivalent statement ¬q  ⟹  ¬p\lnot q \implies \lnot p ("if not qq, then not pp"). The original implication and its contrapositive are always logically equivalent — they have identical truth tables. This is a fundamental tool in mathematical proof and in constructing correct logical reasoning. In DA/DS, the contrapositive appears in: data validation (flipping an assertion to check its logical equivalent), writing correct filter negations, debugging pipeline conditions, and understanding model decision boundaries.

Flowchart

YesNoOriginal: p → qBuild truth tablefor both formsCompare truthvalues row byrowAll rows match?Logicallyequivalent:same meaningDifferentstatements (notcontrapositive)Contrapositive:¬q → ¬pUse when:proving p → qby proving ¬q →¬p is easierExample: 'ifvalid, then noerror' isequivalent to 'iferror, theninvalid'

Real-world use

Where it is used

Data validation and pipeline logic: flipping a condition to check the equivalent constraint from the other direction. Proof by contrapositive in algorithm correctness. Writing correct NOT logic in SQL and pandas filters. Debugging conditional statements where the direct form is unclear but the contrapositive is obvious.

Problem it solves

Sometimes the direct statement p  ⟹  qp \implies q is hard to verify directly. The contrapositive ¬q  ⟹  ¬p\lnot q \implies \lnot p is logically identical but may be far easier to check or prove. In data pipelines, you might want to assert "if a record is valid, it must have a non-null ID" — but it is easier to catch violations as "if ID is null, the record is invalid." Both express the same constraint.

How it solves the problem

Since p  ⟹  q≡¬q  ⟹  ¬pp \implies q \equiv \lnot q \implies \lnot p, proving either one proves the other. In SQL and pandas, negating the condition and flipping the direction often produces a simpler or more readable filter. In proofs, if the direct path is hard, switch to the contrapositive.

Example in a business

Data quality rule: "If a transaction is marked approved, then its amount must be positive." Direct form: approved → amount > 0. Contrapositive: amount <= 0 → NOT approved. Both rules are identical. In a data audit query, you might write: SELECT * FROM transactions WHERE amount <= 0 AND status = 'approved' — this catches violations of the contrapositive (and thus of the original rule). Zero rows = no violations.

The formula, piece by piece

Breaking the notation:

  • p  ⟹  qp \implies q: "if pp then qq" — a conditional statement. False only when pp is True and qq is False.
  • ¬q\lnot q: the negation of qq — "NOT qq", the opposite truth value.
  • ¬p\lnot p: the negation of pp — "NOT pp".
  • ¬q  ⟹  ¬p\lnot q \implies \lnot p: "if NOT qq, then NOT pp" — the contrapositive.

Key equivalences to distinguish:

Name Form Logically equivalent to original?
Original p  ⟹  qp \implies q yes (itself)
Contrapositive ¬q  ⟹  ¬p\lnot q \implies \lnot p YES
Converse q  ⟹  pq \implies p NO (different meaning)
Inverse ¬p  ⟹  ¬q\lnot p \implies \lnot q NO (different meaning)

Truth table confirming equivalence of p  ⟹  qp \implies q and ¬q  ⟹  ¬p\lnot q \implies \lnot p: | pp | qq | p  ⟹  qp \implies q | ¬q\lnot q | ¬p\lnot p | ¬q  ⟹  ¬p\lnot q \implies \lnot p | |-----|-----|-----------------|-----------|-----------|----------------------------| | T | T | T | F | F | T | | T | F | F | T | F | F | | F | T | T | F | T | T | | F | F | T | T | T | T |

Columns 3 and 6 are identical in every row — the two statements are logically equivalent.

The formula in words

"If pp implies qq, then the absence of qq implies the absence of pp. Flipped and negated: same meaning. 'If it rains, the ground gets wet' is equivalent to 'if the ground is not wet, it did not rain.' You cannot break one without breaking the other."

Critical Concepts

  1. Contrapositive vs. converse: the converse (q  ⟹  pq \implies p) is NOT logically equivalent to the original. This is a classic logic error. "If it rains, I use an umbrella" does NOT mean "if I use an umbrella, it rains." But the contrapositive — "if I did not use an umbrella, it did not rain" — is logically equivalent.
  2. Proof by contrapositive: in mathematics and algorithm analysis, if proving p  ⟹  qp \implies q is difficult, prove ¬q  ⟹  ¬p\lnot q \implies \lnot p instead. They are the same claim.
  3. SQL / pandas filter negation: negating a multi-condition filter requires De Morgan's laws combined with the contrapositive. NOT (A AND B) = NOT A OR NOT B — this is the negation, not the contrapositive. Know the difference.
  4. Implication truth value: p  ⟹  qp \implies q is False only when pp is True and qq is False.

All other combinations (including pp False and qq anything) make it vacuously True. This is the source of many beginner logic errors.

Step-by-step calculation (tiny example)

Task: verify that "If a customer is premium, their discount rate is above 10%" and its contrapositive "If discount rate is not above 10%, the customer is not premium" express the same constraint.

Step 1: Define variables

  • pp = "customer is premium" (True/False)
  • qq = "discount > 10%" (True/False)
  • Original: p  ⟹  qp \implies q
  • Contrapositive: ¬q  ⟹  ¬p\lnot q \implies \lnot p

Step 2: Check four possible data scenarios

Customer type Discount pp qq Violates p  ⟹  qp \implies q? Violates ¬q  ⟹  ¬p\lnot q \implies \lnot p?
premium 15% T T No No
premium 5% T F YES — BUG YES — BUG
standard 15% F T No No
standard 5% F F No No

Step 3: Conclude Both formulations flag the same exact row as a violation. The SQL audit query for either formulation is: SELECT * FROM customers WHERE is_premium = TRUE AND discount_rate <= 0.10. Zero rows = data satisfies the business rule.

Code examples

Beginner example (Python)

python
# Contrapositive: p → q is logically equivalent to ¬q → ¬p
from itertools import product

def implication(p: bool, q: bool) -> bool:
    """p implies q: False only when p=True and q=False."""
    return (not p) or q

def contrapositive(p: bool, q: bool) -> bool:
    """Contrapositive of p→q: ¬q implies ¬p."""
    return implication(not q, not p)

def converse(p: bool, q: bool) -> bool:
    """Converse of p→q: q→p (NOT equivalent to original)."""
    return implication(q, p)
print("Comparing original, contrapositive, and converse:")
print(f" {'p':>6} {'q':>6} {'p→q':>6} {'¬q→¬p':>8} {'q→p':>6} {'equiv(orig,contra)':>20} {'equiv(orig,conv)':>18}")
print(" " + "-" * 80)
for p_val, q_val in product([True, False], repeat=2):
    orig = implication(p_val, q_val)
contra = contrapositive(p_val, q_val)
conv = converse(p_val, q_val)
print( f" {str(p_val):>6} {str(q_val):>6} {str(orig):>6} " f"{str(contra):>8} {str(conv):>6} " f"{str(orig == contra):>20} {str(orig == conv):>18}" )
print()
print("Conclusion:")
print(" original == contrapositive → ALWAYS True (logically equivalent)")
print(" original == converse → NOT always True (different statement)")

Beginner example (R)

r
# Contrapositive equivalence in R
implication <- function(p, q) (!p) | q  # p → q
contrapositive <- function(p, q) (!q) | p  # ¬q → ¬p (i.e., implication(¬q, ¬p))
converse <- function(p, q) (!q) | p  # q → p # Note: contrapositive(p,q) = implication(!q, !p) = !(!q) | !p = q | !p
# Let's write them explicitly:
original_impl <- function(p, q) (!p) | q
contra_impl <- function(p, q) (!(!q)) | (!p)  # simplifies to q | !p
combos <- expand.grid(p = c(TRUE, FALSE), q = c(TRUE, FALSE))
combos$original <- original_impl(combos$p, combos$q)
combos$contrapositive <- contra_impl(combos$p, combos$q)
combos$converse <- converse(combos$p, combos$q)
combos$orig_eq_contra <- combos$original == combos$contrapositive
combos$orig_eq_conv <- combos$original == combos$converse
print(combos)
cat("\noriginal always == contrapositive:", all(combos$orig_eq_contra), "\n")
cat("original always == converse: ", all(combos$orig_eq_conv), "\n")  # Practical: data validation using contrapositive
df <- data.frame( customer_id = 1:6, is_premium = c(TRUE, TRUE, FALSE, FALSE, TRUE, FALSE), discount_pct = c(15, 5, 20, 8, 12, 3)
)  # Rule: if premium, then discount > 10%
# Equivalent contrapositive: if discount <= 10%, then NOT premium
# Violations (both catch the same rows):
violations_direct <- df[df$is_premium & df$discount_pct <= 10, ]
violations_contra <- df[df$discount_pct <= 10 & df$is_premium, ]
cat(sprintf("\nViolations (direct): %d rows\n", nrow(violations_direct)))
cat(sprintf("Violations (contrapositive): %d rows\n", nrow(violations_contra)))
cat("Same rows?", all.equal(violations_direct, violations_contra), "\n")

Professional example (Python)

python
import pandas as pd
import numpy as np
from itertools import product   # Scenario: data quality audit using contrapositive logic
# Rule set for a financial transactions table
np.random.seed(42)
n = 200
df = pd.DataFrame({ "transaction_id": range(1, n + 1), "status": np.random.choice(["approved", "pending", "rejected"], n, p=[0.6, 0.3, 0.1]), "amount": np.random.uniform(-500, 10_000, n).round(2), "has_kyc": np.random.choice([True, False], n, p=[0.85, 0.15]), "risk_score": np.random.uniform(0, 100, n).round(1),
})   # Introduce some deliberate violations for demonstration
df.loc[10, "status"] = "approved"
df.loc[10, "amount"] = -200.0   # approved but negative amount
df.loc[20, "status"] = "approved"
df.loc[20, "has_kyc"] = False   # approved but no KYC # Business rules expressed as (original, contrapositive) pairs
# Rule 1: if approved → amount > 0
# Contrapositive: if amount <= 0 → NOT approved
# Rule 2: if approved → has_kyc
# Contrapositive: if NOT has_kyc → NOT approved
rules = { "R1: approved → amount > 0": df["status"].eq("approved") & df["amount"].le(0), "R2: approved → has_kyc": df["status"].eq("approved") & ~df["has_kyc"], "R3 (contrapositive of R1): amount<=0 → not approved": df["amount"].le(0) & df["status"].eq("approved"), "R4 (contrapositive of R2): no_kyc → not approved": ~df["has_kyc"] & df["status"].eq("approved"),
}
print("Data Quality Audit — Contrapositive Rule Verification")
print("=" * 58)
for rule_name, violation_mask in rules.items():
    count = violation_mask.sum()
status = "VIOLATIONS FOUND" if count > 0 else "PASS"
print(f" {rule_name:<48} {count:>3} rows [{status}]")
print()
print("Note: R1 and R3 catch the SAME violations (they are contrapositives).")
print(" R2 and R4 catch the SAME violations (they are contrapositives).")   # Show the actual offending rows
print("\nViolating transactions (Rule 1 / Rule 3 — identical rows):")
violations = df[df["status"].eq("approved") & df["amount"].le(0)]
print(violations[["transaction_id", "status", "amount", "has_kyc", "risk_score"]] .to_string(index=False))

Professional example (R)

r
# Rule 1:
approved => amount > 0; contrapositive: amount <= 0 => not approved
set.seed(42)
df <- data.frame( transaction_id = 1:100, status = sample(c("approved", "pending", "rejected"), 100, replace = TRUE), amount = runif(100, -10, 100), has_kyc = sample(c(TRUE, FALSE), 100, replace = TRUE)
)
rule1 <- df$status == "approved" & df$amount <= 0
rule3 <- df$amount <= 0 & df$status != "approved"
violations <- df[rule1, ]
cat("Note: R1 and R3 catch the SAME violations (contrapositives).\n")
cat("Violating transactions (Rule 1 / Rule 3):\n")
print(head(violations[, c("transaction_id", "status", "amount", "has_kyc")]))

Source: MFB

#foundations#number-sets-and-logic#mathematical-reasoning

Keyboard shortcuts

Single keys, ignored while typing in a field or with Cmd, Ctrl or Alt held.

0
Home
1 2 3 4
Work, Writing, Tools, About
j / k
Next / previous section on this page
n / p
Next / previous project or essay
u
Up one level
l
Deutsch
t
Light or dark theme
?
This list
Esc
Close