Contents
Deep Breakdown
What it tells me (Situation Awareness): The general form of a straight line in 2D is , where is the normal vector to the line (perpendicular direction). This is the 2D special case of the plane equation and is the most general linear form: it handles all orientations including vertical lines (), which the slope-intercept form cannot represent. In DA/DS: every linear decision boundary in 2D is a line . This is the output of 2D logistic regression, 2D SVM, and 2D linear discriminant analysis. The signed distance formula gives the SVM margin distance and is used for feature engineering (distance from a boundary) and anomaly scoring.
Flowchart
Real-world use
Where it is used
2D logistic regression and SVM decision boundary, geometric distance calculations in 2D data (distance from each customer to the revenue-cost break-even line), 2D outlier analysis (distance from the OLS regression line), separating hyperplane in 2D classification visualization, and engineering tolerance bounds (acceptable region between two parallel lines).
Problem it solves
The general form handles all line orientations in a uniform way. Converting a line to this form enables: computing the signed distance from any point to the line, checking which side of the line a point is on, and finding intersections with other lines or curves, all using the same arithmetic structure.
How it solves the problem
Rewriting the standard slope-intercept form gives , i.e., , , . The normal vector points perpendicular to the line. For any point , the expression is the dot product of the normal with the displacement from a reference point on the line — its sign tells you which half-plane is in, and dividing by gives the actual distance.
Example in a business
A DS trains a 2D logistic regression to classify transactions as fraud vs. normal based on two features. The learned decision boundary is a line . For each new transaction, they compute the signed distance : the magnitude gives confidence (how far from the boundary), the sign gives the predicted class. They use as an uncertainty score: transactions with go to a human reviewer.
The formula, piece by piece
- : coefficients defining the line's orientation; is the normal vector (perpendicular to line)
- : the offset constant; determines the line's position
- : every point on the line satisfies this equation
- Normal vector: — perpendicular to the line
- Direction vector: — parallel to the line
- Slope (if ): ; -intercept:
- Signed distance from :
- Unsigned distance:
- Two lines are parallel iff their normal vectors are parallel:
The formula in words
"A straight line in 2D is a set of points where the linear combination equals zero. The coefficients and define the direction the line 'faces' (its normal). To find how far any point is from the line, plug it in, and divide by the length of the normal vector." Tiny example: Line . Normal: . Slope: . . Point : . On the line. Point : . Distance . Positive side.
Critical Concepts
- Handles all orientations: slope-intercept breaks for vertical lines. General form handles all lines (vertical: ; horizontal: ).
- Sign = classification: and define two half-planes. This is exactly the linear SVM prediction function (before the sign/threshold).
- Parallel lines: and are parallel. Distance between them: .
- SVM margin: the two support hyperplanes are ; their distance from the decision boundary is each, total margin .
- Logistic regression boundary: the boundary is exactly where , i.e., where .
- Normalization: dividing by gives the Hesse normal form where the distance formula simplifies to directly.
Step-by-step calculation (tiny example)
Step 1: Convert slope-intercept to general form Line : rearranges to . So , , .
Step 2: Compute signed distance from a point : . is on the positive-normal side of the line.
Step 3: Verify classification : . Negative side. The line : at , . Since , the origin is above the line — consistent with the sign.
Code examples
Beginner example (Python)
import numpy as np
def line_signed_distance(x1: float, y1: float, A: float, B: float, C: float) -> float:
"""Signed distance from point (x1, y1) to line Ax + By + C = 0."""
return (A * x1 + B * y1 + C) / float(np.sqrt(A**2 + B**2))
def line_from_slope_intercept(m: float, b: float) -> tuple[float, float, float]:
"""Convert y = mx + b to general form (A, B, C) where Ax + By + C = 0."""
return m, -1.0, b # Line: y = 2x - 3 => 2x - y - 3 = 0
A, B, C = line_from_slope_intercept(2.0, -3.0)
print(f"Line: {A:.1f}x + {B:.1f}y + {C:.1f} = 0")
print(f" Normal vector: ({A:.1f}, {B:.1f})")
print(f" Slope: {-A/B:.4f}, y-intercept: {-C/B:.4f}")
print(f" ||normal|| = {np.sqrt(A**2+B**2):.4f}")
test_pts = [(4, 1), (0, 0), (1, -1), (3, 3)]
for x, y in test_pts:
d = line_signed_distance(x, y, A, B, C)
side = "ON line" if np.isclose(d, 0) else ("positive side" if d > 0 else "negative side[[")
print(f"]] ({x}, {y}): d = {d:.4f} -> {side}")Beginner example (R)
# -- Straight line general form: Ax + By +
C = 0 ---
line_signed_dist <- function(x1, y1, A, B, C) { (A * x1 + B * y1 + C) / sqrt(A^2 + B^2)
} #
y = 2x - 3 => 2x - y - 3 = 0
A <- 2; B <- -1; C <- -3
cat(sprintf("Line: %.0fx + %.0fy + %.0f = 0\n", A, B, C))
cat(sprintf(" Slope: %.4f, y-intercept: %.4f\n", -A/B, -C/B))
test_pts <- list(c(4, 1), c(0, 0), c(1, -1))
for (pt in test_pts) { d <- line_signed_dist(pt[1], pt[2], A, B, C)
side <- if (abs(d) < 1e-10) "ON line"
else
if (d > 0) "positive"
else "negative"
cat(sprintf(" (%g, %g): d=%.4f -> %s\n", pt[1], pt[2], d, side))
}Professional example (Python)
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import StandardScaler
np.random.seed(42) # -- Business scenario: 2D logistic regression with signed distance as uncertainty score ---
N_CLASS0 = 150
N_CLASS1 = 150 # Class 0: normal transactions (low amount, low velocity)
X0 = np.random.randn(N_CLASS0, 2) + np.array([0, 0])
# Class 1: fraud (high amount or high velocity)
X1 = np.random.randn(N_CLASS1, 2) + np.array([2, 1.5])
X = np.vstack([X0, X1])
y = np.array([0]*N_CLASS0 + [1]*N_CLASS1)
scaler = StandardScaler()
X_std = scaler.fit_transform(X)
lr = LogisticRegression(random_state=42)
lr.fit(X_std, y) # Extract decision line: w[0]*x + w[1]*y +
b = 0
w = lr.coef_[0]
b = lr.intercept_[0]
A, B, C = w[0], w[1], b
print(f"Logistic regression decision line: {A:.4f}*x + {B:.4f}*y + {C:.4f} = 0")
print(f" Slope: {-A/B:.4f}, y-intercept: {-C/B:.4f}") # Test: compute signed distances for new observations
X_test = np.random.randn(20, 2)
X_test[:10] += np.array([2, 1.5]) # fraud-like
X_test_std = scaler.transform(X_test)
signed_dists = (X_test_std @ w + b) / np.linalg.norm(w)
pred_classes = (signed_dists > 0).astype(int)
confidence = np.abs(signed_dists)
results = pd.DataFrame({ "sample": range(20), "true_class": [1]*10 + [0]*10, "pred_class": pred_classes, "signed_dist": signed_dists.round(4), "confidence": confidence.round(4), "review": confidence < 0.5,
})
print(f"\nSigned distance classification (low confidence |d| < 0.5 -> human review):")
print(results.to_string(index=False))
print(f"\nFlagged for review: {results['review'].sum()} / {len(results)}")Source: 1300 p. 147