In data science and backend development, CSV files often contain malformed data, i.e. missing values, incorrect column counts, or wrong data types. Processing this data without validation can lead to application crashes or skewed analysis.
This guide explores how to detect and remove invalid rows using Python's built-in csv module, covering structural validation, type checking, and deduplication.
Understanding Invalid CSV Data
Invalid data typically falls into three categories:
- Structural Errors: A row has more or fewer columns than the header.
- Missing Data: Required fields contain
Noneor empty strings (""). - Type Mismatches: A numeric column (like 'Age') contains text (like "Twenty").
Method 1: Filtering by Structure (Column Counts)
The most basic validation ensures that every data row has the same number of columns as the header row.
Consider the following data.csv as input for the example below:
Name,Age,City
John Doe,29,New York
Jane Smith,25
,,Example:
import csv
def filter_structural_errors(input_file):
valid_rows = []
with open(input_file, 'r') as file:
reader = csv.reader(file)
header = next(reader) # Read header
expected_cols = len(header)
valid_rows.append(header)
for row in reader:
if len(row) != expected_cols:
print(f"Skipping malformed row (Column count mismatch): {row}")
continue
if any(not cell.strip() for cell in row):
print(f"Skipping row with empty values: {row}")
continue
valid_rows.append(row)
return valid_rows
filter_structural_errors('data.csv')Output:
Skipping malformed row (Column count mismatch): ['Jane Smith', '25']
Skipping row with empty values: ['', '', '']Method 2: Validating Data Types
Once the structure is correct, you must ensure the data inside the columns is valid (e.g., verifying that an ID is an integer or an email follows a pattern).
import re
def is_valid_content(row):
"""
Validates specific columns:
Index 0 (Name): String
Index 1 (Age): Integer
Index 2 (Email): Email Regex
"""
name, age_str, email = row
try:
age = int(age_str)
if age < 0 or age > 120:
return False
email_pattern = r"[^@]+@[^@]+\.[^@]+"
if not re.match(email_pattern, email):
return False
return True
except ValueError:
return False
row_good = ["Alice", "30", "alice@example.com"]
row_bad = ["Bob", "Thirty", "bob@example"]
print(f"Row Good: {is_valid_content(row_good)}") # Output: Row Good: True
print(f"Row Bad: {is_valid_content(row_bad)}") # Output: Row Bad: FalseMethod 3: Removing Duplicate Rows
Duplicates can skew statistical analysis. To remove them, track rows using a Python set. Note that lists are not hashable (cannot be added to a set), so rows must be converted to tuples first.
def remove_duplicates(rows):
unique_rows = set()
cleaned_data = []
for row in rows:
row_tuple = tuple(row)
if row_tuple not in unique_rows:
unique_rows.add(row_tuple)
cleaned_data.append(row) # Keep the original list format
return cleaned_dataBuilding a Complete Cleaning Pipeline
Combining these techniques allows you to create a robust script that reads a raw file and produces a clean dataset.
import csv
import re
def clean_csv_dataset(input_path, output_path):
unique_set = set()
with open(input_path, 'r', newline='') as infile, \
open(output_path, 'w', newline='') as outfile:
reader = csv.reader(infile)
writer = csv.writer(outfile)
header = next(reader, None)
if header:
writer.writerow(header)
expected_cols = len(header)
for row in reader:
if len(row) != expected_cols:
continue
if not row[1].isdigit():
continue
row_tuple = tuple(row)
if row_tuple in unique_set:
continue
unique_set.add(row_tuple)
writer.writerow(row)Conclusion
Filtering invalid CSV rows ensures data integrity for downstream applications.
- Structure First: Always validate column counts (
len(row)) before accessing indices to preventIndexError. - Validate Types: Use
try-exceptblocks to filter out data with incorrect types (e.g., strings in numeric columns). - Deduplicate: Use sets to remove exact duplicates efficiently.