Every data scientist knows the joke: 80 percent of the work is cleaning data, and the other 20 percent is complaining about cleaning data. The joke persists because it is true. Raw data arrives messy, inconsistent, and full of surprises. Column names have spaces and capitalization issues. Dates are stored as strings in five different formats. Null values appear as empty strings, the literal text “null”, the integer 0, or the string “N/A” depending on which upstream system produced the data.
Standard pandas handles a lot of this, but at scale, with complex, messy real-world data, it gets verbose, slow, and error-prone fast. The cleaning code ends up scattered across dozens of assignment statements, each one modifying the DataFrame in place without any obvious indication of what changed or why. The result is code that works but is hard to read, hard to debug, and hard to hand off to someone else.
The libraries in this article fix that. They introduce better abstractions, smarter defaults, and APIs that make your cleaning intent clear. Here are five Python tools that make data cleaning faster and more enjoyable Source: KDnuggets.
pyjanitor: fluent, chainable DataFrame cleaning
pyjanitor is a Python package built on top of pandas that adds a verb-based API for common data cleaning tasks. Instead of scattering mutations across multiple assignment statements, you chain operations in a single readable pipeline: rename columns, drop nulls, encode categoricals, filter rows, all in one expression.
The key insight behind pyjanitor is that cleaning operations follow patterns. You almost always rename columns. You almost always handle missing values. You almost always need to convert types or normalize string formatting. pyjanitor wraps these patterns into named functions that compose naturally with pandas’ method chaining.
Here is what a cleaning pipeline looks like with pyjanitor:
import pandas as pd
import janitor
df = (
pd.read_csv("raw_data.csv")
.clean_names()
.remove_empty()
.rename_column("customer id", "customer_id")
.convert_date("order_date")
.fill_direction("status", direction="down")
)
Compare that to the equivalent vanilla pandas code, which would require separate lines for lowercasing column names, dropping all-null rows, renaming specific columns, parsing dates, and filling missing values. The pyjanitor version is shorter, more readable, and less likely to introduce bugs because each function does exactly what its name says.
pyjanitor also includes functions that pandas lacks entirely. The transform_column method lets you apply a function to a specific column without touching the rest of the DataFrame. The collapse_levels method flattens multi-level column indices. The aggregate method provides a cleaner syntax for group-by operations. These are not major innovations, but they eliminate the small frustrations that add up during a cleaning session.
The practical benefit of pyjanitor is not just readability. It is debuggability. When a cleaning pipeline is written as a chain of named functions, each step is self-documenting. If something breaks, you can comment out individual steps and test them in isolation. With vanilla pandas, where cleaning is spread across dozens of lines of assignment statements, isolating the problem step requires more mental effort and more temporary variables.
pyjanitor also handles some cleaning tasks that pandas handles poorly or not at all. The encode_categoricals method converts string columns to categorical types with a single call. The filter_on method provides a SQL-like WHERE clause for filtering rows. The to_datetime method handles multiple date formats in the same column, which is a common problem that pandas’ to_datetime struggles with when the data is inconsistent.
Great Expectations: data quality as code
Great Expectations takes a different approach to cleaning. Rather than providing functions to transform data, it provides a framework for validating that your data meets specific quality standards. You define expectations, such as “column X should not contain null values” or “column Y should only contain values between 0 and 100,” and Great Expectations runs those checks against your data, producing reports that show which expectations passed and which failed.
This matters because cleaning without validation is guesswork. You can write a function that removes duplicates, but how do you know it removed the right ones? You can fill null values with defaults, but how do you know the defaults are reasonable? Great Expectations answers these questions by providing automated checks that run as part of your data pipeline.
The library generates data documentation automatically, including profiled metrics, distribution charts, and expectation suites that describe what your data should look like. This documentation is useful for onboarding new team members, debugging pipeline failures, and communicating data quality to stakeholders who do not read code.
Great Expectations integrates with common data infrastructure: Airflow, dbt, Spark, and cloud storage. You can run expectations as part of an Airflow DAG, store results in a database, and set up alerts that notify you when data quality degrades. The library is more of an investment than pyjanitor, requiring setup and configuration, but the payoff is significant for teams that work with data continuously.
The practical workflow with Great Expectations looks like this: you connect to your data source, run a profiling pass that suggests expectations based on the data’s actual characteristics, review and adjust those expectations, and then add the expectation suite to your pipeline. From that point on, every time the pipeline runs, the expectations are checked automatically. If a check fails, the pipeline can halt, log the failure, or alert a human, depending on how you configure it.
This approach catches problems early. Without Great Expectations, a column type mismatch or an unexpected null spike might go unnoticed until a report is generated or a model is trained on bad data. With Great Expectations, the problem is caught at the point of ingestion, before it propagates through the rest of the pipeline. The earlier you catch a data quality issue, cheaper it is to fix.
Cerberus: lightweight validation for nested data
Not all data lives in DataFrames. API responses, event logs, configuration files, and document store exports often arrive as Python dictionaries or JSON objects, where column-level DataFrame validation does not apply but you still need to enforce types, required fields, and value constraints.
Cerberus is a schema validation library designed for this use case. You define a schema as a plain Python dictionary, call validator.validate(document), and inspect structured error messages per field. The library has no dependencies, runs anywhere, and is easy to embed in a cleaning function or ingestion pipeline.
A Cerberus schema looks like this:
from cerberus import Validator
schema = {
"name": {"type": "string", "required": True},
"age": {"type": "integer", "min": 0, "max": 150},
"email": {"type": "string", "regex": r"^[\w\.-]+@[\w\.-]+\.\w+$"},
}
v = Validator(schema)
v.validate({"name": "Alice", "age": 30, "email": "[email protected]"})
# True
v.validate({"name": "", "age": -5, "email": "not-an-email"})
# False — errors contain per-field details
Cerberus is particularly useful when cleaning data that arrives from external sources. If you are ingesting JSON from an API and need to verify that every record has the expected structure before processing it, Cerberus catches malformed records early and provides specific error messages that help you fix the upstream problem.
DataPrep: automated EDA and cleaning
DataPrep automates the exploratory data analysis that typically precedes cleaning. Instead of manually checking distributions, correlations, and missing value patterns, DataPrep generates a report with a single function call. The report includes column types, missing value counts, distribution histograms, correlation matrices, and duplicate detection.
For cleaning specifically, DataPrep’s clean module handles common transformations automatically: renaming columns, encoding categoricals, imputing missing values, and detecting outliers. It is not as flexible as writing custom cleaning code, but for exploratory work or quick analysis, it saves significant time.
The library is useful in the early stages of a project when you are trying to understand what the data looks like and what needs to be fixed. Once you have identified the problems, you can write targeted cleaning code using pyjanitor or pandas, or you can let DataPrep handle the entire process if the data is simple enough.
Polars with LazyFrame: cleaning at scale
Polars deserves mention here because its lazy evaluation model changes how you think about cleaning large datasets. With pandas, every cleaning operation executes immediately, which means the entire dataset must fit in memory and each step creates a new copy. With Polars LazyFrame, you build up a sequence of cleaning operations as a query plan, and Polars optimizes and executes the entire plan at once.
This matters for cleaning because real-world datasets are often large enough that pandas struggles with memory usage during cleaning. Polars processes data in chunks, uses Apache Arrow’s columnar format for efficient memory usage, and applies predicate pushdown and projection pushdown to skip unnecessary work. The result is cleaning code that runs faster and uses less memory than the equivalent pandas operations.
Polars also provides a cleaner syntax for many cleaning operations. The with_columns method lets you add or transform columns without the inplace mutation that pandas relies on. The filter method is more readable than boolean indexing. The cast method makes type conversion explicit. These are not major syntactic improvements, but they make cleaning code easier to read and maintain.
The performance difference between Polars and pandas for cleaning operations can be dramatic. On a dataset with 10 million rows, a cleaning pipeline that takes 45 seconds in pandas might finish in 8 seconds in Polars. The difference comes from Polars’ use of parallel execution across multiple CPU cores, which pandas does not support natively. For teams cleaning large datasets regularly, this performance improvement translates directly into faster iteration cycles and less time waiting for code to run.
Polars is not a drop-in replacement for pandas. The API is different, and some pandas functions do not have direct equivalents. But for new projects or for cleaning tasks where performance matters, Polars is worth the migration cost. The library also interoperates with pandas, so you can clean data in Polars and then convert to a pandas DataFrame for downstream analysis that depends on pandas-specific functionality.
Choosing the right tool
The best cleaning library depends on your data and your workflow. For most pandas users, pyjanitor is the quickest win: it adds familiar functionality with minimal learning curve and integrates seamlessly with existing code. For teams that need automated data quality checks, Great Expectations provides the infrastructure for validation and monitoring. For nested or unstructured data, Cerberus offers lightweight validation without DataFrame overhead. For quick exploration, DataPrep automates the analysis that precedes cleaning. For large datasets, Polars provides the performance and syntax that pandas cannot match.
The common thread across all these tools is that they make cleaning intent visible. Raw pandas code often hides what is happening behind chains of method calls and index manipulation. These libraries replace that with explicit, named operations that describe what you are doing and why. That clarity is worth the small investment of learning a new API, because the payoff is code that you, your teammates, and your future self can actually understand.
Discussion
Leave a comment
No comments yet
Be the first to start the conversation.