Master Python for Data Science: Loops, Functions & Error Handling

CAREER Published Updated 5 mins read By Leon Wei
Master Python for Data Science: Loops, Functions & Error Handling cover image

Quick summary

Summarize this blog with AI

Loops, functions, and exceptions become easier to reason about when they share one small data-cleaning task. This lesson reads numeric measurements, preserves rejected rows for review, normalizes valid values, and flags unusually distant observations. Every policy is explicit; no row disappears just because an exception occurred.

Tested with Python 3.12.14 using the standard library. Run the code blocks in order. The measurements are fictional teaching data, not a statistical benchmark.

Parse rows and keep an error trail

This input contract accepts a list of numeric strings. Empty strings, malformed numbers, NaN, and infinity are rejected. A programming error that passes a non-string is raised separately instead of being mistaken for a bad numeric measurement.

import math

def parse_measurements(raw_values):
    valid = []
    rejected = []
    for row_number, raw in enumerate(raw_values, start=1):
        if not isinstance(raw, str):
            raise TypeError('each measurement must be text')
        try:
            value = float(raw)
        except ValueError:
            rejected.append((row_number, raw, 'not a number'))
            continue
        if not math.isfinite(value):
            rejected.append((row_number, raw, 'not finite'))
            continue
        valid.append(value)
    return valid, rejected

values, rejected = parse_measurements(['10', 'bad', '20', '', '30', 'nan'])
print(values)
print(rejected)

Expected output:

[10.0, 20.0, 30.0]
[(2, 'bad', 'not a number'), (4, '', 'not a number'), (6, 'nan', 'not finite')]

enumerate retains a one-based source row number. continue skips only the current invalid value. Use a for loop when traversing a known iterable; reserve while for a condition-driven process with a clear termination rule. Exceptions should explain an exceptional path, not replace ordinary loop control.

Normalize with an explicit constant-data policy

Min-max normalization maps the smallest value to 0 and the largest to 1 using (x - minimum) / (maximum - minimum). Compute the endpoints once. This implementation accepts a list or tuple of finite built-in integers or floats whose magnitudes fit the floating-point range, returns a new list, returns an empty list for empty input, and maps constant data to zeros by deliberate policy.

def finite_values(values):
    if not isinstance(values, (list, tuple)):
        raise TypeError('values must be a list or tuple')
    if any(type(value) not in (int, float) for value in values):
        raise TypeError('values must contain numbers, not strings or booleans')
    try:
        all_finite = all(math.isfinite(value) for value in values)
    except OverflowError as exc:
        raise ValueError('measurement magnitude exceeds the float range') from exc
    if not all_finite:
        raise ValueError('values must be finite')
    return list(values)

def min_max_scale(values):
    data = finite_values(values)
    if not data:
        return []
    low, high = min(data), max(data)
    if low == high:
        return [0.0] * len(data)
    span = high - low
    try:
        finite_span = math.isfinite(span)
    except OverflowError as exc:
        raise ValueError('numeric range is too large') from exc
    if not finite_span:
        raise ValueError('numeric range is too large')
    return [(value - low) / span for value in data]

print(min_max_scale(values))
print(min_max_scale([5, 5, 5]))
print(min_max_scale([]))

Expected output:

[0.0, 0.5, 1.0]
[0.0, 0.0, 0.0]
[]

Python integers have arbitrary precision, while these statistical calculations use floating-point arithmetic. The helper turns an overflow during the finite-value check into a specific ValueError with a clear unsupported-magnitude message. Returning zeros for a constant feature is a modeling choice, not a mathematical requirement. Another application may reject it or remove the feature. Training and prediction also need a shared fitted minimum and maximum; recomputing them independently on each batch changes the meaning of the scale.

Make an outlier threshold actually control the result

The following function uses the interquartile range (IQR). It flags values strictly below Q1 - threshold × IQR or above Q3 + threshold × IQR. Quartiles use the standard library's inclusive method; stating that choice matters because small samples can produce different quartiles under other methods. At least four observations are required by this teaching contract.

from statistics import quantiles

def detect_outliers(values, threshold=1.5):
    data = finite_values(values)
    if len(data) < 4:
        raise ValueError('at least four values are required')
    if type(threshold) not in (int, float):
        raise TypeError('threshold must be a number')
    try:
        finite_threshold = math.isfinite(threshold)
    except OverflowError as exc:
        raise ValueError('threshold magnitude exceeds the float range') from exc
    if not finite_threshold or threshold < 0:
        raise ValueError('threshold must be finite and non-negative')
    try:
        q1, _, q3 = quantiles(data, n=4, method='inclusive')
    except OverflowError as exc:
        raise ValueError('numeric range is too large') from exc
    iqr = q3 - q1
    low = q1 - threshold * iqr
    high = q3 + threshold * iqr
    if not all(math.isfinite(value) for value in (q1, q3, iqr, low, high)):
        raise ValueError('numeric range is too large')
    return [value for value in data if value < low or value > high]

sample = [1, 2, 3, 4, 10]
print(detect_outliers(sample))
print(detect_outliers(sample, threshold=4))
print(detect_outliers([5, 5, 5, 5]))

Expected output:

[10]
[]
[]

For this sample Q1 is 2, Q3 is 4, and IQR is 2. The default upper fence is 7, while a threshold of 4 moves it to 12. The result demonstrates that the argument is used. An outlier flag is a request for investigation, not permission to delete an inconvenient observation. With IQR zero, any value different from the shared quartile is outside the fences; consider that behavior before applying the rule to discrete or highly repeated data.

Exercise the failure paths

for operation in (
    lambda: min_max_scale([1, float('nan')]),
    lambda: min_max_scale([True, 2]),
    lambda: min_max_scale([10**1000]),
    lambda: detect_outliers([1, 2, 3]),
    lambda: detect_outliers([1, 2, 3, 4], threshold=-1),
):
    try:
        operation()
    except (TypeError, ValueError) as exc:
        print(type(exc).__name__)

Expected output:

ValueError
TypeError
ValueError
ValueError
ValueError

Keep exception handling close to the boundary where recovery is possible. A batch loader can record a rejected row; a modeling function can reject an invalid dataset and let its caller decide what to do. Avoid broad handlers that silently substitute zero, because zero may be a legitimate measurement.

Keep the lesson reproducible

The normalization step makes a few linear passes over the data instead of recomputing extrema for every element. The quartile calculation sorts internally, so it has different costs. Benchmark representative data before replacing these clear functions with a more complex implementation. For DataFrame operations, continue with NumPy and pandas data cleaning; for traceback diagnosis, use the error-handling guide.

Primary references: Python's quartile methods, finite-number check, and exception-handling tutorial.

Interview Prep

Begin Your SQL, Python, and R Journey

Master 230 interview-style coding questions and build the data skills needed for analyst, scientist, and engineering roles.

Related Articles

All Articles