Python Data Analysis Reference
Pandas Cheat Sheet
Quickly find practical Pandas syntax for DataFrames, data cleaning, filtering, grouping, merging, reshaping, importing, and exporting data.
- Copy-ready Python
- Practical DataFrame examples
- Beginner to advanced
- Printable reference
import pandas as pd
df = pd.read_csv("sales.csv")
summary = (
df.dropna(subset=["Sales"])
.groupby("Region")["Sales"]
.sum()
.sort_values(ascending=False)
)
print(summary)
Start here
How to Use This Pandas Cheat Sheet
Use this reference while cleaning, transforming, combining, and analyzing tabular data with Python. Find the task you need, copy the relevant pattern, and adapt the column names and values to your dataset.
Find a task
Search for a Pandas operation such as filtering rows, filling missing values, grouping data, merging DataFrames, or exporting a file.
Adapt the example
Replace the sample DataFrame, column names, conditions, and output paths with values that match your own project.
Verify the result
Test transformations on a small copy of your data and inspect the result before overwriting files or using it in a production workflow.
Standard Pandas setup
The examples on this page use the conventional
pd alias and assume that Pandas is installed in your
active Python environment.
pip install pandas
import pandas as pd
This page focuses specifically on Pandas and DataFrame workflows. For variables, loops, functions, collections, and general language syntax, use the complete Python Cheat Sheet .
Pandas Finder
Find a Pandas Task
Search for DataFrame operations, functions, methods, errors, file formats, or data-cleaning tasks.

Essential Pandas syntax
Pandas Quick Reference
Use these common Pandas patterns to import, inspect, select, clean, summarize, combine, and export tabular data.
| Task | Syntax | Purpose | Copy |
|---|---|---|---|
| Import Pandas |
import pandas as pd
|
Import Pandas with its standard alias. | |
| Read a CSV file |
df = pd.read_csv("file.csv")
|
Load CSV data into a DataFrame. | |
| Preview rows |
df.head()
|
Display the first five rows. | |
| Select columns |
df[["name", "sales"]]
|
Return a DataFrame containing selected columns. | |
| Filter rows |
df.loc[df["sales"] > 1000]
|
Keep rows that satisfy a Boolean condition. | |
| Sort values |
df.sort_values("sales", ascending=False)
|
Sort rows by sales from highest to lowest. | |
| Remove missing values |
df.dropna(subset=["sales"])
|
Remove rows where the sales value is missing. | |
| Group and summarize |
df.groupby("region", as_index=False)["sales"].sum()
|
Calculate total sales for each region. | |
| Merge DataFrames |
pd.merge(orders, customers, on="customer_id", how="left")
|
Match customer data to every order using a left join. | |
| Create a pivot table |
df.pivot_table(index="region", values="sales", aggfunc="sum")
|
Summarize sales by region in a pivot table. | |
| Create a column |
df.assign(profit=df["sales"] - df["cost"])
|
Create a profit column without changing the original DataFrame. | |
| Export to CSV |
df.to_csv("cleaned.csv", index=False)
|
Save the DataFrame without writing its index. |
Most Pandas operations return a new object. Assign the result to a variable when you need to keep the transformed DataFrame.
Set up Pandas
Installation & Imports
Install Pandas in an isolated Python environment, import it with the
conventional pd alias, and confirm which version your
project is using.
Installation commands
| Method | Command | Use | Copy |
|---|---|---|---|
| Install with pip |
python -m pip install pandas
|
Install the latest compatible release from PyPI. | |
| Upgrade with pip |
python -m pip install --upgrade pandas
|
Upgrade Pandas in the active Python environment. | |
| Install with conda |
conda install -c conda-forge pandas
|
Install Pandas from the conda-forge channel. | |
| Include Excel support |
python -m pip install "pandas[excel]"
|
Install Pandas with optional dependencies for Excel files. |
import pandas as pd
The alias pd is the standard convention used throughout
Pandas documentation and most Python data projects.
import pandas as pd
print(pd.__version__)
Checking the version helps you compare behavior with the correct API documentation and release notes.
import pandas as pd
pd.show_versions()
Install Pandas inside a virtual environment. This keeps project dependencies isolated and reduces conflicts between package versions.
Core data structures
Series & DataFrames
A Series stores one labeled sequence of values. A DataFrame combines multiple labeled columns into a two-dimensional table.
Series vs. DataFrame
| Feature | Series | DataFrame |
|---|---|---|
| Dimensions | One-dimensional | Two-dimensional |
| Labels | Index labels | Row index and column labels |
| Data types | One dtype for the stored values | Each column can use a different dtype |
| Constructor | pd.Series() |
pd.DataFrame() |
| Common use | One variable, column, or time series | Complete tables and structured datasets |
| Spreadsheet comparison | Similar to one labeled column | Similar to an entire worksheet table |
Create the core structures
import pandas as pd
sales = pd.Series(
[1250, 980, 1430],
index=["East", "North", "West"],
name="sales"
)
print(sales)
The values form a one-dimensional array, while the region names form its index.
import pandas as pd
sales = pd.DataFrame({
"region": ["East", "North", "West"],
"revenue": [1250, 980, 1430],
"orders": [18, 14, 21]
})
print(sales)
Each dictionary key becomes a column, and Pandas creates a default numbered index.
Useful structure attributes
| Attribute | Returns | Example | Copy |
|---|---|---|---|
shape |
Number of rows and columns | df.shape |
|
index |
Row labels | df.index |
|
columns |
DataFrame column labels | df.columns |
|
dtypes |
Data type of each column | df.dtypes |
|
size |
Total number of values | df.size |
|
ndim |
Number of dimensions | df.ndim |
Automatic index alignment
import pandas as pd
first = pd.Series([10, 20], index=["A", "B"])
second = pd.Series([1, 2], index=["B", "C"])
result = first + second
print(result)
A NaN
B 21.0
C NaN
dtype: float64
Pandas aligns values by their index labels before calculating. Only
label B exists in both Series, so the unmatched labels
produce missing values.
Build tabular data
Creating DataFrames
Create DataFrames from dictionaries, records, tuples, Series, or existing Pandas objects. Choose the structure that most closely matches your source data.
Common DataFrame constructors
| Source | Syntax | Best for | Copy |
|---|---|---|---|
| Dictionary of lists |
pd.DataFrame({"name": ["Ava", "Leo"], "sales": [1200, 980]})
|
Column-oriented data already stored in Python lists. | |
| List of dictionaries |
pd.DataFrame([{"name": "Ava", "sales": 1200}, {"name": "Leo", "sales": 980}])
|
Record-oriented data such as API or JSON responses. | |
| List of tuples |
pd.DataFrame([("Ava", 1200), ("Leo", 980)], columns=["name", "sales"])
|
Compact row data with separately supplied column names. | |
| Dictionary of Series |
pd.DataFrame({"sales": pd.Series({"East": 1200, "West": 980})})
|
Columns that already have meaningful index labels. | |
| Existing DataFrame |
new_df = source_df.copy()
|
Creating an independent DataFrame for further work. |
Control the index and column order
import pandas as pd
data = {
"region": ["East", "West"],
"sales": [1250, 1430]
}
df = pd.DataFrame(
data,
index=["order_101", "order_102"]
)
df.index.name = "order_id"
print(df)
A custom index can identify rows with meaningful labels instead of default row numbers.
import pandas as pd
data = {
"sales": [1250, 1430],
"region": ["East", "West"],
"internal_id": [101, 102]
}
df = pd.DataFrame(
data,
columns=["region", "sales"]
)
df = df.astype({
"region": "str",
"sales": "int64"
})
print(df.dtypes)
The columns argument controls selection and order, while
astype() applies explicit data types.
Create a DataFrame from uneven records
import pandas as pd
records = [
{"product": "Laptop", "sales": 1800},
{"product": "Monitor"},
{"product": "Keyboard", "sales": 420}
]
df = pd.DataFrame(records)
print(df)
product sales
0 Laptop 1800.0
1 Monitor NaN
2 Keyboard 420.0
When records do not contain identical keys, Pandas creates the complete set of columns and fills unavailable values with a missing-value marker.
Lists within a dictionary must have matching lengths. Otherwise,
pd.DataFrame() raises a
ValueError.
Load external data
Importing Data
Load data from text files, spreadsheets, JSON documents, Parquet files, databases, and HTML tables. Specify columns and data types during import when practical.
Common Pandas readers
| Format | Syntax | Result | Copy |
|---|---|---|---|
| CSV |
df = pd.read_csv("sales.csv")
|
Loads a comma-separated file into a DataFrame. | |
| Excel |
df = pd.read_excel("sales.xlsx", sheet_name="Orders")
|
Loads a selected worksheet into a DataFrame. | |
| JSON |
df = pd.read_json("records.json")
|
Converts compatible JSON data into a Pandas object. | |
| Parquet |
df = pd.read_parquet("sales.parquet")
|
Loads a column-oriented Parquet dataset. | |
| SQL |
df = pd.read_sql_query("SELECT * FROM sales", connection)
|
Loads the result of a database query into a DataFrame. | |
| HTML tables |
tables = pd.read_html("https://example.com/report")
|
Returns a list containing each detected HTML table. |
Control the import
import pandas as pd
df = pd.read_csv(
"sales.csv",
usecols=[
"order_id",
"region",
"sales",
"order_date"
],
dtype={
"order_id": "int64",
"region": "str",
"sales": "float64"
},
parse_dates=["order_date"],
na_values=["", "N/A", "unknown"]
)
Import only required columns and define important data types while reading the file.
import pandas as pd
df = pd.read_excel(
"sales.xlsx",
sheet_name="Orders",
usecols="A:E",
header=0,
parse_dates=["order_date"]
)
print(df.head())
Select the worksheet, column range, header row, and date columns during import.
Read a large CSV in chunks
import pandas as pd
matching_rows = []
for chunk in pd.read_csv(
"large_sales.csv",
chunksize=100_000
):
filtered = chunk.loc[
chunk["sales"] > 1000
]
matching_rows.append(filtered)
result = pd.concat(
matching_rows,
ignore_index=True
)
print(result.head())
Use usecols, explicit data types, filters supported by the
storage format, or chunksize to reduce unnecessary memory
usage.
Import from a SQL database
import pandas as pd
from sqlite3 import connect
query = """
SELECT order_id, region, sales
FROM sales
WHERE region = ?
"""
with connect("sales.db") as connection:
df = pd.read_sql_query(
query,
connection,
params=("East",)
)
print(df.head())
Pandas does not sanitize SQL statements. Use the parameter system supported by your database driver instead of inserting untrusted values directly into a query string.
Excel, Parquet, HTML, and database integrations may require additional packages. Install only the dependencies required by your workflow.
Understand the dataset
Inspecting Data
Examine rows, columns, data types, missing values, descriptive statistics, category frequencies, and memory usage before changing the dataset.
Essential inspection methods
| Task | Syntax | What it shows | Copy |
|---|---|---|---|
| First rows | df.head(5) |
The first five rows by default. | |
| Last rows | df.tail(5) |
The final five rows by default. | |
| Random sample | df.sample(5, random_state=42) |
A reproducible random selection of rows. | |
| Structure summary | df.info(memory_usage="deep") |
Columns, non-null counts, dtypes, and memory usage. | |
| Descriptive statistics | df.describe(include="all") |
Summary statistics for numeric and non-numeric columns. | |
| Missing values | df.isna().sum() |
The number of missing values in each column. | |
| Distinct values | df.nunique(dropna=True) |
The number of distinct non-missing values per column. | |
| Value frequencies | df["region"].value_counts(dropna=False) |
How often each value, including missing values, occurs. | |
| Memory by column | df.memory_usage(deep=True) |
Estimated memory consumption for each column. |
Run an initial inspection
print(df.head())
print(df.tail())
print(df.sample(5, random_state=42))
df.info(memory_usage="deep")
summary = df.describe(
include="all"
)
print(summary)
Use several views because the beginning or end of a file may not represent the entire dataset.
region_counts = (
df["region"]
.value_counts(
dropna=False,
normalize=False
)
)
region_percent = (
df["region"]
.value_counts(
dropna=False,
normalize=True
)
.mul(100)
.round(1)
)
print(region_counts)
print(region_percent)
Frequency checks reveal dominant categories, unexpected labels, and missing values.
Build a column-quality report
import pandas as pd
quality_report = pd.DataFrame({
"dtype": df.dtypes.astype(str),
"missing": df.isna().sum(),
"missing_percent": (
df.isna()
.mean()
.mul(100)
.round(1)
),
"unique_values": df.nunique(
dropna=True
)
})
quality_report = quality_report.sort_values(
"missing",
ascending=False
)
print(quality_report)
Inspect the DataFrame immediately after import and again after major cleaning or transformation steps. This makes unexpected changes easier to detect.
Empty strings are not automatically considered missing values by
isna(). Normalize blank text separately or define it with
na_values during import.
Select subsets precisely
Selecting Rows & Columns
Select columns directly, use loc for label-based
selection, use iloc for position-based selection, and
use at or iat for individual values.
Selection quick reference
| Selection | Syntax | Result | Copy |
|---|---|---|---|
| One column |
df["sales"]
|
Returns the selected column as a Series. | |
| Multiple columns |
df[["region", "sales"]]
|
Returns the selected columns as a DataFrame. | |
| Row by label |
df.loc["order_102"]
|
Returns the row matching the index label. | |
| Labels and columns |
df.loc[["order_101", "order_103"], ["region", "sales"]]
|
Selects specified row labels and column labels. | |
| Row by position |
df.iloc[0]
|
Returns the first row by integer position. | |
| Position slice |
df.iloc[0:3, 1:3]
|
Selects the first three rows and columns 1–2. | |
| Scalar by labels |
df.at["order_102", "sales"]
|
Returns one value using row and column labels. | |
| Scalar by position |
df.iat[1, 2]
|
Returns one value using row and column positions. |
df["sales"] returns a Series. Use double brackets,
df[["sales"]], when you need a one-column DataFrame.
Example DataFrame
import pandas as pd
df = pd.DataFrame(
{
"product": [
"Laptop",
"Monitor",
"Keyboard",
"Mouse"
],
"region": [
"East",
"West",
"North",
"East"
],
"sales": [
1800,
1260,
420,
180
],
"orders": [
12,
18,
25,
30
]
},
index=[
"order_101",
"order_102",
"order_103",
"order_104"
]
)
print(df)
Label selection with loc
selection = df.loc[
["order_101", "order_103"],
["product", "region", "sales"]
]
print(selection)
Both lists contain labels: index labels for the rows and names for the columns.
selection = df.loc[
"order_101":"order_103",
"region":"sales"
]
print(selection)
Label-based slices include both the starting and ending labels when those labels exist.
Position selection with iloc
selection = df.iloc[
[0, 2],
[0, 2]
]
print(selection)
This selects rows 0 and 2 together with columns 0 and 2.
selection = df.iloc[
0:3,
1:3
]
print(selection)
Position 3 is excluded, following standard Python slice behavior.
An integer passed to loc is treated as an index label, not
as a row position. Use iloc when the number represents a
position.
Keep matching records
Filtering Data
Build Boolean conditions to keep rows that match numeric ranges, categories, text patterns, missing-value rules, or several criteria at once.
Filtering quick reference
| Filter | Syntax | Keeps | Copy |
|---|---|---|---|
| Comparison |
df.loc[df["sales"] > 1000]
|
Rows where sales exceed 1,000. | |
| AND conditions |
df.loc[(df["sales"] > 500) & (df["region"] == "East")]
|
Rows matching both conditions. | |
| OR conditions |
df.loc[(df["region"] == "East") | (df["region"] == "West")]
|
Rows matching either condition. | |
| Exclude matches |
df.loc[~df["region"].isin(["North", "South"])]
|
Rows outside the supplied category list. | |
| List membership |
df.loc[df["region"].isin(["East", "West"])]
|
Rows whose region appears in the list. | |
| Numeric range |
df.loc[df["sales"].between(500, 1500)]
|
Rows between 500 and 1,500, including both boundaries. | |
| Text contains |
df.loc[df["product"].str.contains("pro", case=False, na=False, regex=False)]
|
Rows containing the literal text, regardless of case. | |
| Non-missing values |
df.loc[df["sales"].notna()]
|
Rows where sales has a usable value. | |
| Query expression |
df.query("sales > 1000 and region == 'East'")
|
Rows matching a readable query expression. |
Use &, |, and ~ for Pandas
conditions. Do not use Python’s and, or, or
not with Boolean Series.
Combine several conditions
minimum_sales = 500
allowed_regions = ["East", "West"]
mask = (
df["sales"].ge(minimum_sales)
& df["region"].isin(allowed_regions)
& df["active"].eq(True)
)
result = df.loc[
mask,
[
"product",
"region",
"sales"
]
]
print(result)
Naming the mask makes complex filtering logic easier to read, test, and reuse.
search_term = "pro"
mask = df["product"].str.contains(
search_term,
case=False,
na=False,
regex=False
)
result = df.loc[
mask,
["product", "sales"]
]
print(result)
Set regex=False when the search term should be treated as
literal text rather than a regular expression.
Filter with query()
minimum_sales = 500
allowed_regions = ["East", "West"]
result = df.query(
"sales >= @minimum_sales "
"and region in @allowed_regions"
)
print(result)
Prefix a Python variable with @ inside a
query() expression. Use only trusted expressions rather
than constructing query strings from untrusted input.
DataFrame.filter() selects labels such as column names. It
does not filter rows according to their cell values.
Organize labels and rows
Sorting & Renaming
Sort records by one or more columns, control the position of missing values, organize index labels, and rename inconsistent rows or columns.
Sorting and renaming quick reference
| Task | Syntax | Result | Copy |
|---|---|---|---|
| Ascending values |
df.sort_values("sales")
|
Sorts sales from lowest to highest. | |
| Descending values |
df.sort_values("sales", ascending=False)
|
Sorts sales from highest to lowest. | |
| Multiple columns |
df.sort_values(["region", "sales"], ascending=[True, False])
|
Sorts regions A–Z, then sales high to low. | |
| Missing values first |
df.sort_values("sales", na_position="first")
|
Moves missing sales values to the beginning. | |
| Sort index |
df.sort_index(ascending=True)
|
Sorts rows according to their index labels. | |
| Top values |
df.nlargest(5, "sales")
|
Returns the five rows with the largest sales values. | |
| Rename columns |
df.rename(columns={"Sales Total": "sales", "Sales Region": "region"})
|
Renames only the supplied column labels. | |
| Rename index labels |
df.rename(index={"old_id": "new_id"})
|
Replaces selected index labels. | |
| Reset index |
df.reset_index(drop=True)
|
Creates a new default numbered index. |
Sort with several rules
sorted_df = df.sort_values(
by=[
"region",
"sales",
"order_date"
],
ascending=[
True,
False,
True
],
na_position="last",
kind="stable"
)
print(sorted_df)
Each value in ascending corresponds to the column in the
same position inside by.
sorted_df = (
df.sort_values(
"sales",
ascending=False
)
.reset_index(drop=True)
)
print(sorted_df)
Resetting the index after sorting creates consecutive row labels without retaining the previous index as a column.
Clean inconsistent column names
df = df.rename(
columns=lambda name: (
str(name)
.strip()
.lower()
.replace(" ", "_")
.replace("-", "_")
)
)
print(df.columns)
Clean column names immediately after import. Consistent lowercase labels without surrounding spaces make later selections, joins, and formulas easier to maintain.
Set and restore an index
indexed_df = (
df.set_index(
"order_id",
verify_integrity=True
)
.sort_index()
)
print(indexed_df)
verify_integrity=True raises an error when the proposed
index contains duplicate values.
restored_df = indexed_df.reset_index()
print(restored_df)
Without drop=True, the existing index becomes a regular
DataFrame column.
Sorting and renaming methods return a new object by default. Assign the result to a variable when you want to keep the change.
Detect and handle gaps
Missing Values
Identify missing observations, remove incomplete records when necessary, or fill values using rules that preserve the meaning of the data.
Missing-data quick reference
| Task | Syntax | Result | Copy |
|---|---|---|---|
| Detect missing values |
df.isna()
|
Returns a Boolean mask with the same shape as the DataFrame. | |
| Count by column |
df.isna().sum()
|
Counts missing values in every column. | |
| Keep complete values |
df.loc[df["sales"].notna()]
|
Keeps rows where sales is not missing. | |
| Drop incomplete rows |
df.dropna(subset=["order_id", "sales"])
|
Drops rows missing either critical value. | |
| Drop empty columns |
df.dropna(axis="columns", how="all")
|
Drops columns containing no usable values. | |
| Require enough values |
df.dropna(thresh=3)
|
Keeps rows containing at least three non-missing values. | |
| Fill one value |
df["discount"].fillna(0)
|
Replaces missing discounts with zero. | |
| Fill by column |
df.fillna({"region": "Unknown", "discount": 0})
|
Uses a different replacement for each supplied column. | |
| Forward fill |
df["price"].ffill(limit=1)
|
Propagates the previous valid price across one missing row. | |
| Backward fill |
df["price"].bfill(limit=1)
|
Uses the next valid price for one preceding gap. | |
| Interpolate |
df["temperature"].interpolate(method="linear")
|
Estimates numeric gaps between known observations. |
Normalize missing-value markers
import pandas as pd
text_columns = df.select_dtypes(
include=["string", "object"]
).columns
df[text_columns] = df[text_columns].replace(
r"^\s*$",
pd.NA,
regex=True
)
df = df.replace(
["N/A", "NA", "unknown", "-"],
pd.NA
)
print(df.isna().sum())
Empty strings and whitespace are not always recognized as missing automatically. Normalize them before calculating missing-value totals.
Fill according to column meaning
clean_df = df.fillna({
"region": "Unknown",
"discount": 0,
"active": False
})
median_sales = clean_df[
"sales"
].median()
clean_df["sales"] = clean_df[
"sales"
].fillna(median_sales)
Use a replacement that matches the meaning and data type of each column.
required_columns = [
"order_id",
"product",
"sales"
]
clean_df = df.dropna(
subset=required_columns
).reset_index(drop=True)
print(clean_df)
Drop a row only when the missing fields are essential to the intended analysis.
Fill ordered observations
df = df.sort_values([
"product",
"order_date"
])
df["price"] = (
df.groupby("product")["price"]
.ffill(limit=1)
)
Sort the data first and fill within the correct group to avoid carrying a value into an unrelated product.
df["temperature"] = (
df["temperature"]
.interpolate(
method="linear",
limit=2,
limit_area="inside"
)
)
This fills up to two internal gaps without extrapolating beyond the known observations.
Do not replace every missing value with zero. Zero, unknown, not applicable, and not yet recorded can represent different states and may change totals, averages, or model results.
Standardize and validate
Cleaning Data
Convert columns to appropriate data types, identify values that fail conversion, standardize inconsistent categories, validate acceptable ranges, and remove fields that are not needed.
Data-cleaning quick reference
| Task | Syntax | Result | Copy |
|---|---|---|---|
| Infer nullable dtypes |
df = df.convert_dtypes()
|
Converts columns to suitable nullable Pandas data types. | |
| Set explicit dtypes |
df = df.astype({"region": "string", "active": "boolean"})
|
Applies specified data types to selected columns. | |
| Convert numbers |
df["sales"] = pd.to_numeric(df["sales"], errors="coerce")
|
Converts invalid numeric text to a missing value. | |
| Convert dates |
df["order_date"] = pd.to_datetime(df["order_date"], errors="coerce")
|
Converts invalid dates to NaT. |
|
| Replace categories |
df["region"] = df["region"].replace({"E": "East", "W": "West"})
|
Standardizes known category variants. | |
| Limit numeric values |
df["discount"] = df["discount"].clip(lower=0, upper=1)
|
Restricts values to the inclusive range 0–1. | |
| Remove columns |
df = df.drop(columns=["temporary_note"], errors="ignore")
|
Removes the column if it exists. | |
| Select numeric columns |
numeric_df = df.select_dtypes(include="number")
|
Returns only numeric columns for validation or analysis. |
Convert values and preserve errors
raw_sales = df["sales"].copy()
df["sales"] = pd.to_numeric(
df["sales"],
errors="coerce"
)
invalid_sales = (
raw_sales.notna()
& df["sales"].isna()
)
invalid_rows = df.loc[
invalid_sales
]
print(invalid_rows)
Preserve the original Series so you can identify values that became missing during conversion.
raw_dates = df[
"order_date"
].copy()
df["order_date"] = pd.to_datetime(
df["order_date"],
format="%Y-%m-%d",
errors="coerce"
)
invalid_dates = (
raw_dates.notna()
& df["order_date"].isna()
)
print(df.loc[invalid_dates])
Supplying a known format makes the expected date structure explicit.
Use errors="raise" when invalid input should stop the
workflow. Use errors="coerce" when invalid values should
become missing and be reviewed separately.
Standardize category values
region_mapping = {
"E": "East",
"east": "East",
"EAST": "East",
"W": "West",
"west": "West",
"WEST": "West",
"N": "North",
"north": "North",
"NORTH": "North"
}
df["region"] = (
df["region"]
.replace(region_mapping)
.astype("string")
)
valid_regions = [
"East",
"West",
"North",
"South"
]
invalid_regions = df.loc[
df["region"].notna()
& ~df["region"].isin(valid_regions)
]
print(invalid_regions)
Validate numeric ranges
invalid_discount = (
df["discount"].notna()
& ~df["discount"].between(
0,
1,
inclusive="both"
)
)
invalid_rows = df.loc[
invalid_discount,
["order_id", "discount"]
]
print(invalid_rows)
Review invalid values before deciding whether to correct, remove, or cap them.
df["discount"] = (
df["discount"]
.clip(
lower=0,
upper=1
)
)
Use clipping only when the valid boundaries are known and values outside them are confirmed errors.
Recheck the cleaned dataset
df = df.convert_dtypes()
print(df.head())
print(df.dtypes)
print(df.isna().sum())
print(df.describe(include="all"))
Avoid silently changing invalid source values. Record which rows failed validation and which cleaning rule was applied, especially in recurring or production workflows.
Clean and transform text
String Operations
Normalize capitalization and whitespace, search text, replace substrings, split values into columns, extract structured patterns, and validate text formats.
String-method quick reference
| Task | Syntax | Result | Copy |
|---|---|---|---|
| String dtype |
df["name"] = df["name"].astype("string")
|
Converts the column to Pandas string dtype. | |
| Trim whitespace |
df["name"] = df["name"].str.strip()
|
Removes whitespace from both ends. | |
| Lowercase |
df["email"] = df["email"].str.lower()
|
Converts each email address to lowercase. | |
| Title case |
df["name"] = df["name"].str.title()
|
Capitalizes words for display-oriented text. | |
| Text length |
df["name_length"] = df["name"].str.len()
|
Returns the number of characters in each value. | |
| Find literal text |
df["product"].str.contains("pro", case=False, na=False, regex=False)
|
Returns a Boolean Series for literal matches. | |
| Replace literal text |
df["code"] = df["code"].str.replace("-", "", regex=False)
|
Removes literal hyphens from every code. | |
| Split into columns |
df[["category", "region"]] = df["label"].str.split("-", n=1, expand=True)
|
Splits each label once and expands it into two columns. | |
| Extract a pattern |
df["code"].str.extract(r"([A-Z]{2})-(\d{4})")
|
Extracts regex capture groups into separate columns. | |
| Pad identifiers |
df["customer_id"] = df["customer_id"].str.zfill(6)
|
Adds leading zeros until each identifier has six characters. |
Normalize messy text
df["customer_name"] = (
df["customer_name"]
.astype("string")
.str.strip()
.str.replace(
r"\s+",
" ",
regex=True
)
.str.title()
)
This removes outer whitespace, collapses repeated internal whitespace, and applies consistent capitalization.
df["email"] = (
df["email"]
.astype("string")
.str.strip()
.str.lower()
)
valid_email_shape = (
df["email"]
.str.fullmatch(
r"[^@\s]+@[^@\s]+\.[^@\s]+",
na=False
)
)
invalid_emails = df.loc[
~valid_email_shape
& df["email"].notna()
]
This performs a practical format check, not complete verification that an email account exists.
Split structured text
df[[
"department",
"region",
"team"
]] = df["assignment"].str.split(
"-",
n=2,
expand=True,
regex=False
)
With expand=True, the resulting pieces become separate
DataFrame columns.
parts = df["product_code"].str.extract(
r"^(?P<category>[A-Z]{2})-"
r"(?P<number>\d{4})$"
)
df = df.join(parts)
print(df[[
"product_code",
"category",
"number"
]])
Named capture groups become clearly labeled result columns.
Combine text columns
df["full_name"] = (
df["first_name"]
.astype("string")
.str.strip()
.str.cat(
df["last_name"]
.astype("string")
.str.strip(),
sep=" ",
na_rep=""
)
.str.strip()
)
Use regex=False when searching for or replacing ordinary
text. Enable regex only when the pattern intentionally contains regular
expression syntax.
For anchors, groups, quantifiers, lookarounds, and practical patterns, use the Regex Cheat Sheet .
Work with temporal data
Date & Time Operations
Parse date strings, extract calendar components, filter date ranges, calculate durations, create date sequences, and convert between time zones.
Date and time quick reference
| Task | Syntax | Result | Copy |
|---|---|---|---|
| Parse dates |
df["date"] = pd.to_datetime(df["date"], errors="coerce")
|
Converts valid values and changes invalid dates to NaT. |
|
| Parse as UTC |
df["created_at"] = pd.to_datetime(df["created_at"], utc=True)
|
Creates timezone-aware UTC timestamps. | |
| Extract year |
df["year"] = df["date"].dt.year
|
Returns the calendar year as an integer. | |
| Month name |
df["month"] = df["date"].dt.month_name()
|
Returns names such as January and February. | |
| Weekday name |
df["weekday"] = df["date"].dt.day_name()
|
Returns the weekday name for each timestamp. | |
| Monthly period |
df["month_period"] = df["date"].dt.to_period("M")
|
Groups each timestamp into a calendar month period. | |
| Remove time |
df["day"] = df["date"].dt.normalize()
|
Sets the time component to midnight while preserving datetime dtype. | |
| Round down to hour |
df["hour"] = df["date"].dt.floor("h")
|
Rounds timestamps down to the beginning of the hour. | |
| Format for display |
df["date_label"] = df["date"].dt.strftime("%Y-%m-%d")
|
Creates formatted strings from datetime values. | |
| Create date range |
pd.date_range("2026-01-01", periods=7, freq="D")
|
Creates seven consecutive daily timestamps. |
Use dt.strftime() only when you need display text.
Formatting converts timestamps into strings, so keep the original
datetime column for sorting, filtering, and calculations.
Extract calendar components
df["order_date"] = pd.to_datetime(
df["order_date"],
format="%Y-%m-%d",
errors="coerce"
)
df = df.assign(
order_year=df["order_date"].dt.year,
order_quarter=df["order_date"].dt.quarter,
order_month=df["order_date"].dt.month,
month_name=df["order_date"].dt.month_name(),
weekday=df["order_date"].dt.day_name(),
is_month_end=df["order_date"].dt.is_month_end
)
print(df.head())
Filter a date range
start_date = pd.Timestamp(
"2026-01-01"
)
end_date = pd.Timestamp(
"2026-04-01"
)
mask = df["order_date"].between(
start_date,
end_date,
inclusive="left"
)
quarter_one = df.loc[mask]
This includes January 1 but excludes April 1, producing a clear half-open reporting interval.
today = pd.Timestamp.now().normalize()
cutoff = today - pd.Timedelta(
days=30
)
recent_orders = df.loc[
df["order_date"].between(
cutoff,
today,
inclusive="both"
)
]
Normalize the current timestamp when the comparison should operate at calendar-day level.
Calculate durations
df["created_at"] = pd.to_datetime(
df["created_at"],
errors="coerce",
utc=True
)
df["completed_at"] = pd.to_datetime(
df["completed_at"],
errors="coerce",
utc=True
)
df["processing_time"] = (
df["completed_at"]
- df["created_at"]
)
df["processing_hours"] = (
df["processing_time"]
.dt.total_seconds()
.div(3600)
.round(2)
)
Work with time zones
df["created_at"] = pd.to_datetime(
df["created_at"],
utc=True
)
df["created_copenhagen"] = (
df["created_at"]
.dt.tz_convert(
"Europe/Copenhagen"
)
)
Convert timezone-aware UTC timestamps to the timezone required for reporting or display.
df["local_time"] = pd.to_datetime(
df["local_time"],
errors="coerce"
)
df["local_time"] = (
df["local_time"]
.dt.tz_localize(
"Europe/Copenhagen",
ambiguous="NaT",
nonexistent="shift_forward"
)
)
Localizing assigns a timezone to timestamps that do not already contain timezone information.
Use tz_localize() to assign a timezone to naive timestamps.
Use tz_convert() only after timestamps are timezone-aware.
Summarize data by category
GroupBy & Aggregation
Use groupby() to split rows into groups, calculate
summaries for each group, and combine the results into a new Series
or DataFrame.
GroupBy quick reference
| Task | Syntax | Result | Copy |
|---|---|---|---|
| Sum by group |
df.groupby("region")["sales"].sum()
|
Returns one sales total for each region. | |
| Multiple groups |
df.groupby(["region", "product"])["sales"].sum()
|
Returns one total for each region and product combination. | |
| Multiple statistics |
df.groupby("region")["sales"].agg(["sum", "mean", "count"])
|
Calculates several summaries for every group. | |
| Named aggregation |
df.groupby("region").agg(total_sales=("sales", "sum"))
|
Creates an aggregation with a clearly named output column. | |
| SQL-style result |
df.groupby("region", as_index=False)["sales"].sum()
|
Keeps the grouping key as a regular DataFrame column. | |
| Group result per row |
df.groupby("region")["sales"].transform("sum")
|
Returns a group total aligned with every original row. |
size() versus count()
size() counts every row in a group. The
count() method excludes missing values in the selected
column.
Basic GroupBy operations
regional_sales = (
df.groupby("region")["sales"]
.sum()
.sort_values(ascending=False)
)
average_sales = (
df.groupby("region")["sales"]
.mean()
)
Select a value column after groupby(), then apply the
required aggregation.
rows_per_region = (
df.groupby("region")
.size()
)
orders_per_region = (
df.groupby("region")["order_id"]
.count()
)
customers_per_region = (
df.groupby("region")["customer_id"]
.nunique()
)
Use nunique() when you need the number of distinct
values inside each group.
Group by multiple columns
product_summary = (
df.groupby(
["region", "product"],
as_index=False,
sort=False
)
.agg(
total_sales=("sales", "sum"),
average_sale=("sales", "mean"),
order_count=("order_id", "count")
)
.sort_values(
"total_sales",
ascending=False
)
)
Named aggregation
regional_summary = (
df.groupby(
"region",
as_index=False
)
.agg(
total_sales=("sales", "sum"),
average_sale=("sales", "mean"),
largest_sale=("sales", "max"),
order_count=("order_id", "count"),
unique_customers=(
"customer_id",
"nunique"
)
)
)
Transform grouped data
region_total = (
df.groupby("region")["sales"]
.transform("sum")
)
df = df.assign(
region_total=region_total,
region_share=(
df["sales"]
.div(region_total)
)
)
The transformed totals retain the same index as the original DataFrame.
region_average = (
df.groupby("region")["sales"]
.transform("mean")
)
df["above_region_average"] = (
df["sales"] > region_average
)
Use transform() when the calculated group value must
remain aligned with every source row.
Filter entire groups
high_value_regions = (
df.groupby("region")
.filter(
lambda group:
group["sales"].sum() >= 100_000
)
)
Missing and categorical group keys
summary = (
df.groupby(
"region",
dropna=False,
as_index=False
)
.agg(
total_sales=("sales", "sum")
)
)
Set dropna=False when rows with missing grouping keys
must be included in the result.
observed_summary = (
df.groupby(
"category",
observed=True
)["sales"]
.sum()
)
all_categories = (
df.groupby(
"category",
observed=False
)["sales"]
.sum()
)
Use observed=False when unobserved categories must also
appear in the grouped result.
Use built-in aggregations, agg(), or
transform() when they can express the operation.
Although GroupBy.apply() is flexible, it is often slower.
observed=True is the default for categorical groupers.
Set observed=False explicitly when the result must include
categories that do not appear in the data.
Combine multiple DataFrames
Merge, Join & Concat
Combine related tables by matching columns or indexes, append rows from multiple DataFrames, and verify that joins preserve the expected relationships.
Combining DataFrames quick reference
| Task | Syntax | Result | Copy |
|---|---|---|---|
| Inner merge |
left.merge(right, on="customer_id", how="inner")
|
Keeps keys found in both DataFrames. | |
| Left merge |
left.merge(right, on="customer_id", how="left")
|
Keeps every row from the left DataFrame. | |
| Outer merge |
left.merge(right, on="customer_id", how="outer")
|
Keeps the union of keys from both DataFrames. | |
| Different key names |
left.merge(right, left_on="customer_id", right_on="id")
|
Matches columns that use different names. | |
| Index join |
left.join(right, how="left")
|
Combines columns by matching index values. | |
| Append rows |
pd.concat([df_a, df_b], ignore_index=True)
|
Stacks DataFrames vertically and creates a new index. | |
| Combine columns |
pd.concat([df_a, df_b], axis="columns")
|
Places DataFrames side by side using their indexes. | |
| Left anti join |
left.merge(right, on="customer_id", how="left_anti")
|
Keeps left-side keys that do not appear on the right. |
Use merge() for database-style joins on columns,
join() primarily for index-based combinations, and
concat() to stack complete objects by rows or columns.
Merge on a shared column
orders = pd.DataFrame({
"order_id": [101, 102, 103],
"customer_id": [1, 2, 4],
"sales": [125.00, 89.50, 210.00]
})
customers = pd.DataFrame({
"customer_id": [1, 2, 3],
"customer_name": [
"Avery",
"Morgan",
"Jordan"
]
})
order_details = orders.merge(
customers,
on="customer_id",
how="left",
validate="many_to_one"
)
Use validate="one_to_one",
validate="one_to_many", or
validate="many_to_one" to detect duplicate merge keys
that would unexpectedly multiply rows.
Understand merge types
matched = orders.merge(
customers,
on="customer_id",
how="inner",
validate="many_to_one"
)
An inner merge excludes keys that appear in only one of the two DataFrames.
all_records = orders.merge(
customers,
on="customer_id",
how="outer",
indicator=True
)
print(
all_records["_merge"]
.value_counts()
)
The indicator column identifies rows originating from the left, right, or both DataFrames.
Merge different key names
combined = orders.merge(
customers,
left_on="customer_id",
right_on="id",
how="left",
suffixes=("_order", "_customer"),
validate="many_to_one"
)
Use suffixes=("_left", "_right") when both DataFrames
contain non-key columns with the same name. This prevents ambiguous
default names such as name_x and name_y.
Audit a merge
audit = orders.merge(
customers,
on="customer_id",
how="outer",
indicator="merge_source"
)
unmatched = audit.loc[
audit["merge_source"] != "both"
]
The indicator contains left_only,
right_only, or both.
unknown_customers = (
orders.merge(
customers,
on="customer_id",
how="left_anti"
)
)
A left anti join returns rows whose keys occur only in the left DataFrame.
Join by index
customer_index = (
customers
.set_index("customer_id")
)
order_index = (
orders
.set_index("customer_id")
)
joined = order_index.join(
customer_index,
how="left",
validate="many_to_one"
)
Concatenate rows and columns
monthly_frames = [
january_orders,
february_orders,
march_orders
]
quarter_orders = pd.concat(
monthly_frames,
ignore_index=True,
sort=False
)
Use ignore_index=True when the original row indexes
should not be preserved.
customer_features = pd.concat(
[
customer_totals,
customer_counts,
customer_averages
],
axis="columns",
join="inner"
)
Column-wise concatenation aligns rows by index rather than by their physical position.
Duplicate keys on both sides can produce a many-to-many Cartesian result and greatly increase the number of rows. Pandas also matches null merge keys with other null keys, which differs from typical SQL behavior.
Collect DataFrames in a list and call pd.concat() once.
Repeatedly concatenating inside a loop creates unnecessary copies and
is less efficient.
Reshape data for analysis
Pivot & Reshape
Convert data between long and wide formats, create spreadsheet-style summaries, reshape hierarchical indexes, expand list-like values, and build cross-tabulations.
Reshaping quick reference
| Task | Syntax | Result | Copy |
|---|---|---|---|
| Long to wide |
df.pivot(index="date", columns="region", values="sales")
|
Creates one column for each unique region. | |
| Pivot with aggregation |
df.pivot_table(index="region", values="sales", aggfunc="sum")
|
Aggregates duplicate index and column combinations. | |
| Wide to long |
df.melt(id_vars="product", var_name="month", value_name="sales")
|
Converts column labels into row values. | |
| Index level to columns |
df.unstack("region")
|
Moves an index level into the columns. | |
| Column level to index |
df.stack()
|
Moves a column level into the row index. | |
| Expand lists into rows |
df.explode("tags", ignore_index=True)
|
Creates one row for every list item. | |
| Frequency table |
pd.crosstab(df["region"], df["status"])
|
Counts combinations of two categorical variables. | |
| Indicator columns |
pd.get_dummies(df, columns=["region"], dtype="int8")
|
Converts categories into separate indicator columns. |
pivot() versus pivot_table()
Use pivot() when every index and column combination has
exactly one value. Use pivot_table() when duplicate
combinations must be aggregated.
Convert long data to wide format
sales = pd.DataFrame({
"date": [
"2026-01-01",
"2026-01-01",
"2026-01-02",
"2026-01-02"
],
"region": [
"East",
"West",
"East",
"West"
],
"sales": [
1250,
1480,
1320,
1510
]
})
wide_sales = sales.pivot(
index="date",
columns="region",
values="sales"
)
pivot() raises a ValueError when more than
one value exists for the same index and column combination. Use
pivot_table() with an aggregation function when duplicates
are expected.
Create an aggregated pivot table
sales_summary = df.pivot_table(
values="sales",
index="region",
columns="product",
aggfunc="sum",
fill_value=0
)
Missing region and product combinations are replaced with zero.
detailed_summary = df.pivot_table(
values="sales",
index="region",
columns="product",
aggfunc=["sum", "mean"],
fill_value=0,
margins=True,
margins_name="Total"
)
Multiple aggregation functions create hierarchical column labels.
Convert wide data to long format
monthly_sales = pd.DataFrame({
"product": [
"Keyboard",
"Mouse"
],
"jan": [
1250,
890
],
"feb": [
1380,
940
],
"mar": [
1420,
1010
]
})
long_sales = monthly_sales.melt(
id_vars="product",
value_vars=[
"jan",
"feb",
"mar"
],
var_name="month",
value_name="sales"
)
Stack and unstack index levels
grouped_sales = (
df.groupby(
["region", "product"]
)["sales"]
.sum()
)
wide_summary = grouped_sales.unstack(
level="product",
fill_value=0
)
unstack() moves a row-index level to the column axis.
long_summary = (
wide_summary
.stack()
.rename("sales")
.reset_index()
)
stack() moves a column level into the row index.
Expand list-like values
products = pd.DataFrame({
"product_id": [
101,
102
],
"tags": [
["office", "wireless"],
["gaming", "accessory"]
]
})
product_tags = products.explode(
"tags",
ignore_index=True
)
Create cross-tabulations
status_counts = pd.crosstab(
index=df["region"],
columns=df["status"],
margins=True,
margins_name="Total"
)
Without values and aggfunc,
crosstab() returns frequency counts.
status_percentages = pd.crosstab(
index=df["region"],
columns=df["status"],
normalize="index"
).mul(100).round(1)
normalize="index" makes every row total equal to one
before conversion to percentages.
Encode categorical variables
encoded = pd.get_dummies(
df,
columns=[
"region",
"status"
],
prefix={
"region": "region",
"status": "status"
},
dummy_na=True,
dtype="int8"
)
When using melt(), place identifiers such as product IDs,
dates, or customer IDs in id_vars. Only measurement
columns should normally be converted into variable and value rows.
pivot_table() uses observed=True by default
for categorical groupers. Set observed=False explicitly
when unobserved categories must also appear in the result.
Apply reusable transformations
Apply, Map & Transform
Map individual values, apply functions across rows or columns, and create transformed results that preserve the original DataFrame structure.
Function application quick reference
| Task | Syntax | Result | Copy |
|---|---|---|---|
| Map with dictionary |
df["region_name"] = df["region"].map(region_map)
|
Replaces Series values using a lookup mapping. | |
| Map with function |
df["sales_label"] = df["sales"].map(lambda value: f"${value:,.2f}")
|
Applies a scalar function to every Series value. | |
| Map every DataFrame cell |
numeric.map(lambda value: round(value, 2), na_action="ignore")
|
Applies a scalar function element by element. | |
| Apply to columns |
df[["sales", "cost"]].apply("mean", axis="index")
|
Calculates one mean for each selected column. | |
| Apply to rows |
df.apply(classify_order, axis="columns")
|
Passes each complete row to a function. | |
| Apply to a Series |
df["sales"].apply(calculate_tax)
|
Invokes a function on the Series values. | |
| Normalize a Series |
df["sales"].transform(lambda values: values / values.max())
|
Returns transformed values with the original index. | |
| Group transformation |
df.groupby("region")["sales"].transform("mean")
|
Returns each group’s mean beside every original row. |
Use map() for individual values, apply() for
complete rows or columns, and transform() when the result
must remain aligned with the original index.
Map values with a dictionary
region_map = {
"N": "North",
"S": "South",
"E": "East",
"W": "West"
}
df["region_name"] = (
df["region_code"]
.map(region_map)
.fillna("Unknown")
)
When a value is not present in the mapping dictionary,
Series.map() normally returns a missing value. Add
fillna() or validate the source categories when every
value requires a label.
Map values with functions
df["sales_label"] = (
df["sales"]
.map(
lambda value:
f"${value:,.2f}",
na_action="ignore"
)
)
The na_action="ignore" argument prevents the formatting
function from receiving missing values.
numeric = df[
[
"sales",
"cost",
"discount"
]
]
rounded = numeric.map(
lambda value: round(value, 2),
na_action="ignore"
)
DataFrame.map() applies a scalar function to every
selected cell.
DataFrame.applymap() was removed in Pandas 3. Use
DataFrame.map() for element-wise DataFrame operations.
Apply functions to columns
def value_range(column):
return column.max() - column.min()
column_ranges = df[
[
"sales",
"cost",
"discount"
]
].apply(
value_range,
axis="index"
)
Apply functions to rows
def classify_order(row):
if row["sales"] >= 1000:
return "High value"
if row["sales"] >= 500:
return "Medium value"
return "Standard"
df["order_class"] = df.apply(
classify_order,
axis="columns"
)
Row-wise apply() is useful for complex logic involving
several columns, but vectorized arithmetic, comparisons, and built-in
Pandas methods are usually faster.
Pass arguments to a custom function
def calculate_tax(
amount,
tax_rate
):
return amount * tax_rate
df["tax"] = df["sales"].apply(
calculate_tax,
tax_rate=0.20
)
Additional keyword arguments are passed directly to the custom function.
tax_rate = 0.20
df["tax"] = (
df["sales"]
.mul(tax_rate)
)
Direct vectorized arithmetic is simpler and usually faster for this type of calculation.
Transform values without changing alignment
df["sales_scaled"] = (
df["sales"]
.transform(
lambda values:
(
values - values.min()
)
/ (
values.max()
- values.min()
)
)
)
The transformed Series retains the original row index.
regional_mean = (
df.groupby("region")["sales"]
.transform("mean")
)
df["difference_from_mean"] = (
df["sales"]
- regional_mean
)
Group transformation broadcasts each regional mean back to its original rows.
Before writing a custom function, check whether a vectorized Pandas method already performs the operation. Built-in string, datetime, arithmetic, aggregation, and GroupBy methods are generally clearer and more efficient.
Detect and remove repeated records
Duplicate Data
Identify repeated rows, define the columns that make a record unique, keep the correct occurrence, audit duplicate groups, and detect duplicate index labels.
Duplicate data quick reference
| Task | Syntax | Result | Copy |
|---|---|---|---|
| Mark repeated rows |
df.duplicated()
|
Marks duplicates after their first occurrence. | |
| Mark every duplicate |
df.duplicated(keep=False)
|
Marks every row belonging to a duplicate group. | |
| Check selected columns |
df.duplicated(subset=["email"], keep=False)
|
Finds rows sharing the same email address. | |
| Remove duplicate rows |
df.drop_duplicates(ignore_index=True)
|
Keeps the first occurrence and creates a new index. | |
| Keep last occurrence |
df.drop_duplicates(subset="customer_id", keep="last")
|
Keeps the final row for each customer ID. | |
| Remove every repeated value |
df.drop_duplicates(subset="order_id", keep=False)
|
Keeps only order IDs that occur exactly once. | |
| Count duplicate rows |
df.duplicated().sum()
|
Returns the number of duplicates after first occurrences. | |
| Check index uniqueness |
df.index.is_unique
|
Returns whether every index label is unique. |
A completely repeated row and a repeated business key are not always
the same problem. Use subset to specify identifiers such
as an order ID, email address, or combination of customer and date.
Find completely duplicated rows
duplicate_mask = df.duplicated(
keep=False
)
duplicate_rows = (
df.loc[duplicate_mask]
.sort_values(
by=list(df.columns),
kind="stable"
)
)
duplicate_count = duplicate_mask.sum()
print(duplicate_rows)
print(f"Duplicate rows: {duplicate_count}")
Find duplicates using selected columns
duplicate_orders = df.loc[
df.duplicated(
subset="order_id",
keep=False
)
].sort_values(
"order_id",
kind="stable"
)
This returns every row connected to a repeated order ID.
key_columns = [
"customer_id",
"order_date",
"product_id"
]
duplicate_purchases = df.loc[
df.duplicated(
subset=key_columns,
keep=False
)
].sort_values(
key_columns,
kind="stable"
)
A composite key identifies duplicates using a combination of fields.
Remove duplicate rows
clean_orders = df.drop_duplicates(
subset="order_id",
keep="first",
ignore_index=True
)
The first row for each order ID is retained.
latest_orders = df.drop_duplicates(
subset="order_id",
keep="last",
ignore_index=True
)
The final row in the current DataFrame order is retained.
Sort the DataFrame by the relevant date or version column before using
keep="first" or keep="last". Otherwise, the
retained row may not represent the oldest or newest record.
Keep the newest record
df["updated_at"] = pd.to_datetime(
df["updated_at"],
errors="coerce",
utc=True
)
latest_customers = (
df.sort_values(
"updated_at",
kind="stable"
)
.drop_duplicates(
subset="customer_id",
keep="last",
ignore_index=True
)
)
Audit duplicate groups before removal
duplicate_audit = (
df.groupby(
"customer_id",
dropna=False
)
.size()
.rename("row_count")
.loc[lambda counts: counts > 1]
.sort_values(ascending=False)
.reset_index()
)
print(duplicate_audit)
Inspect or export duplicate groups before removing them from important datasets. This makes it possible to explain which records were removed and why.
Detect duplicate index labels
has_unique_index = df.index.is_unique
duplicate_index_rows = df.loc[
df.index.duplicated(
keep=False
)
]
print(has_unique_index)
print(duplicate_index_rows)
Duplicate index labels are separate from duplicate row values.
unique_index_df = df.loc[
~df.index.duplicated(
keep="first"
)
].copy()
Negating the duplicate mask keeps the first row for each index label.
Prevent duplicate labels
validated_df = (
unique_index_df
.set_flags(
allows_duplicate_labels=False
)
)
print(
validated_df.flags
.allows_duplicate_labels
)
Values such as "Alice@example.com",
"alice@example.com", and
"alice@example.com " are different strings. Normalize
case and whitespace before checking for duplicate text identifiers.
Save processed data
Export Data
Export DataFrames to CSV, Excel, JSON, Parquet, and SQL while controlling indexes, missing values, number formats, compression, and output structure.
Data export quick reference
| Format | Syntax | Use case | Copy |
|---|---|---|---|
| CSV |
df.to_csv("output.csv", index=False)
|
Portable tabular data for spreadsheets and other tools. | |
| Compressed CSV |
df.to_csv("output.csv.gz", index=False, compression="gzip")
|
Smaller text files for storage or transfer. | |
| Excel |
df.to_excel("report.xlsx", sheet_name="Sales", index=False)
|
Reports intended for Excel users. | |
| JSON records |
df.to_json("output.json", orient="records", indent=2)
|
Structured records for APIs and web applications. | |
| JSON Lines |
df.to_json("output.jsonl", orient="records", lines=True)
|
Streaming and large-data processing pipelines. | |
| Parquet |
df.to_parquet("output.parquet", index=False)
|
Efficient analytical storage with preserved data types. | |
| SQL table |
df.to_sql("sales", connection, if_exists="append", index=False)
|
Writes DataFrame records into a database table. | |
| Clipboard |
df.to_clipboard(index=False, excel=True)
|
Quickly pastes tabular data into a spreadsheet. |
Pandas exports the index by default in several formats. Use
index=False when the index is only an internal row counter.
Preserve or name it when it contains meaningful identifiers.
Export a clean CSV file
export_columns = [
"order_id",
"order_date",
"region",
"sales"
]
df.to_csv(
"sales-report.csv",
columns=export_columns,
index=False,
encoding="utf-8",
na_rep="",
float_format="%.2f",
date_format="%Y-%m-%d"
)
Export compressed CSV data
df.to_csv(
"sales-report.csv.gz",
index=False,
compression="infer"
)
Pandas infers gzip compression from the
.gz filename extension.
compression_options = {
"method": "zip",
"archive_name": "sales-report.csv"
}
df.to_csv(
"sales-report.zip",
index=False,
compression=compression_options
)
The archive contains a CSV file named
sales-report.csv.
Export to Excel
df.to_excel(
"sales-report.xlsx",
sheet_name="Sales",
index=False,
freeze_panes=(1, 0)
)
The first worksheet row remains visible while the user scrolls.
with pd.ExcelWriter(
"sales-workbook.xlsx"
) as writer:
df.to_excel(
writer,
sheet_name="Transactions",
index=False
)
regional_summary.to_excel(
writer,
sheet_name="Regional Summary",
index=False
)
product_summary.to_excel(
writer,
sheet_name="Product Summary",
index=False
)
The context manager closes and saves the workbook automatically.
Writing modern .xlsx files normally requires an optional
engine such as openpyxl or XlsxWriter.
Export JSON records
df.to_json(
"sales-report.json",
orient="records",
date_format="iso",
indent=2,
force_ascii=False
)
The result is a JSON array containing one object for each row.
df.to_json(
"sales-report.jsonl",
orient="records",
lines=True,
date_format="iso"
)
JSON Lines is useful when records are processed individually or in streaming workflows.
Export to Parquet
parquet_df = df.copy()
categorical_columns = parquet_df.select_dtypes(
include="category"
).columns
for column in categorical_columns:
parquet_df[column] = (
parquet_df[column]
.cat.remove_unused_categories()
)
parquet_df.to_parquet(
"sales-report.parquet",
index=False,
compression="snappy"
)
Pandas requires either pyarrow or
fastparquet to write Parquet files. Parquet is generally
preferable to CSV when preserving data types and efficient analytical
storage are important.
Export to a SQL database
import sqlite3
with sqlite3.connect(
"sales.db"
) as connection:
rows_written = df.to_sql(
name="sales",
con=connection,
if_exists="append",
index=False,
chunksize=1000,
method="multi"
)
print(
f"Rows written: {rows_written}"
)
if_exists="replace" carefully
The replace option drops the existing table before writing
the new data. Use append when new rows should be added, and
verify the target database and table before replacing production data.
Validate exported data
export_path = "sales-report.csv"
df.to_csv(
export_path,
index=False
)
check = pd.read_csv(
export_path
)
assert len(check) == len(df)
assert list(check.columns) == list(df.columns)
A round-trip check can detect missing rows or unexpected columns.
export_path = "sales-report.parquet"
df.to_parquet(
export_path,
index=False
)
check = pd.read_parquet(
export_path
)
assert check.shape == df.shape
print(check.dtypes)
Parquet normally preserves data types more reliably than CSV.
Select and order the output columns before exporting. Smaller, purpose-built files are easier to validate, transfer, document, and use in downstream systems.
Combine Pandas methods
Practical Pandas Examples
Use these complete workflows to import, clean, combine, summarize, reshape, validate, and export real-world datasets.
Practical workflow quick reference
| Workflow step | Syntax | Purpose | Copy |
|---|---|---|---|
| Import selected columns |
pd.read_csv("sales.csv", usecols=["date", "region", "sales"])
|
Loads only the columns required for analysis. | |
| Convert numeric data |
df["sales"] = pd.to_numeric(df["sales"], errors="coerce")
|
Converts valid values and marks invalid values as missing. | |
| Remove unusable rows |
df = df.dropna(subset=["order_id", "sales"])
|
Removes records missing required values. | |
| Summarize data |
df.groupby("region", as_index=False).agg(total_sales=("sales", "sum"))
|
Creates one aggregated row for each region. | |
| Add lookup data |
orders.merge(customers, on="customer_id", how="left", validate="many_to_one")
|
Adds customer details while validating the relationship. | |
| Create a pivot report |
df.pivot_table(index="month", columns="region", values="sales", aggfunc="sum")
|
Creates a monthly report with one column per region. | |
| Combine multiple files |
pd.concat(frames, ignore_index=True)
|
Stacks imported files into one DataFrame. | |
| Export the result |
summary.to_csv("regional-summary.csv", index=False)
|
Saves the final report without an extra index column. |
Example 1: Clean and summarize sales data
import pandas as pd
sales = pd.read_csv(
"sales.csv",
usecols=[
"order_id",
"order_date",
"region",
"customer_id",
"sales"
]
)
sales["order_date"] = pd.to_datetime(
sales["order_date"],
errors="coerce"
)
sales["sales"] = pd.to_numeric(
sales["sales"],
errors="coerce"
)
sales["region"] = (
sales["region"]
.astype("string")
.str.strip()
.str.title()
)
sales = (
sales.dropna(
subset=[
"order_id",
"order_date",
"region",
"sales"
]
)
.drop_duplicates(
subset="order_id",
keep="last"
)
.loc[lambda frame: frame["sales"] >= 0]
)
regional_summary = (
sales.groupby(
"region",
as_index=False
)
.agg(
total_sales=("sales", "sum"),
average_sale=("sales", "mean"),
order_count=("order_id", "nunique"),
customer_count=(
"customer_id",
"nunique"
)
)
.sort_values(
"total_sales",
ascending=False
)
)
regional_summary["average_sale"] = (
regional_summary["average_sale"]
.round(2)
)
regional_summary.to_csv(
"regional-sales-summary.csv",
index=False
)
print(regional_summary)
Parse dates and numeric values before applying business rules. This prevents string comparisons and invalid values from producing misleading results.
Example 2: Enrich orders with customer data
import pandas as pd
orders = pd.read_csv(
"orders.csv"
)
customers = pd.read_csv(
"customers.csv"
)
customers["email"] = (
customers["email"]
.astype("string")
.str.strip()
.str.lower()
)
duplicate_customers = customers.loc[
customers.duplicated(
subset="customer_id",
keep=False
)
]
if not duplicate_customers.empty:
raise ValueError(
"Customer IDs must be unique."
)
enriched_orders = orders.merge(
customers[
[
"customer_id",
"customer_name",
"email",
"segment"
]
],
on="customer_id",
how="left",
validate="many_to_one",
indicator="customer_match"
)
unmatched_orders = enriched_orders.loc[
enriched_orders["customer_match"]
== "left_only"
]
enriched_orders = enriched_orders.drop(
columns="customer_match"
)
print(
f"Unmatched orders: "
f"{len(unmatched_orders)}"
)
enriched_orders.to_parquet(
"enriched-orders.parquet",
index=False
)
A successful merge does not guarantee that every lookup key matched.
Use indicator to find orders without a corresponding
customer record.
Example 3: Build a monthly trend report
import pandas as pd
sales = pd.read_csv(
"sales.csv",
parse_dates=["order_date"]
)
monthly_sales = (
sales.dropna(
subset=[
"order_date",
"region",
"sales"
]
)
.groupby(
[
pd.Grouper(
key="order_date",
freq="ME"
),
"region"
],
as_index=False
)
.agg(
total_sales=("sales", "sum")
)
.rename(
columns={
"order_date": "month"
}
)
)
monthly_report = monthly_sales.pivot_table(
index="month",
columns="region",
values="total_sales",
aggfunc="sum",
fill_value=0
)
monthly_report["Total"] = (
monthly_report.sum(
axis="columns"
)
)
monthly_report = (
monthly_report
.reset_index()
)
monthly_report.to_excel(
"monthly-sales-report.xlsx",
sheet_name="Monthly Sales",
index=False
)
The ME frequency groups timestamps by calendar month and
labels each group with its month-end date.
Example 4: Combine monthly CSV files
from pathlib import Path
import pandas as pd
data_directory = Path(
"monthly-sales"
)
csv_files = sorted(
data_directory.glob("*.csv")
)
if not csv_files:
raise FileNotFoundError(
"No CSV files were found."
)
frames = []
for file_path in csv_files:
monthly_data = pd.read_csv(
file_path,
parse_dates=["order_date"]
)
monthly_data["source_file"] = (
file_path.name
)
frames.append(monthly_data)
all_sales = pd.concat(
frames,
ignore_index=True,
sort=False
)
all_sales = all_sales.drop_duplicates(
subset="order_id",
keep="last",
ignore_index=True
)
all_sales.to_parquet(
"combined-sales.parquet",
index=False
)
print(
f"Files combined: {len(csv_files)}"
)
print(
f"Rows exported: {len(all_sales)}"
)
concat() creates additional columns when input files use
different column names. Validate required columns before combining
files in production workflows.
Example 5: Generate a data quality report
import pandas as pd
quality_report = pd.DataFrame({
"dtype": df.dtypes.astype("string"),
"row_count": len(df),
"missing_count": df.isna().sum(),
"missing_percent": (
df.isna()
.mean()
.mul(100)
.round(2)
),
"unique_count": df.nunique(
dropna=True
)
})
quality_report["duplicate_rows"] = (
df.duplicated().sum()
)
quality_report = (
quality_report
.sort_values(
by=[
"missing_count",
"unique_count"
],
ascending=[
False,
True
]
)
.reset_index(
names="column"
)
)
quality_report.to_csv(
"data-quality-report.csv",
index=False
)
print(quality_report)
Keep raw input, cleaned data, aggregated reports, and exported files as distinct stages. This makes the workflow easier to test, debug, repeat, and audit.
Write faster and safer Pandas code
Performance & Best Practices
Improve speed and memory efficiency by loading less data, choosing appropriate data types, using vectorized operations, processing large files in chunks, and avoiding unnecessary intermediate objects.
Performance quick reference
| Practice | Syntax | Benefit | Copy |
|---|---|---|---|
| Use vectorized arithmetic |
df["margin"] = df["sales"] - df["cost"]
|
Avoids slow Python calls for every row. | |
| Load selected columns |
pd.read_csv("sales.csv", usecols=["region", "sales"])
|
Reduces input time and memory usage. | |
| Specify data types |
pd.read_csv("sales.csv", dtype={"region": "category"})
|
Avoids unnecessary type inference and may reduce memory. | |
| Measure memory |
df.memory_usage(deep=True).sort_values(ascending=False)
|
Identifies columns using the most memory. | |
| Read in chunks |
pd.read_csv("sales.csv", chunksize=100_000)
|
Processes files without loading every row at once. | |
| Concatenate once |
result = pd.concat(frames, ignore_index=True)
|
Avoids repeatedly copying a growing DataFrame. | |
Assign with loc |
df.loc[df["sales"] < 0, "sales"] = 0
|
Performs selection and assignment in one operation. | |
| Iterate efficiently |
for row in df.itertuples(index=False):
|
Is generally preferable when row iteration is unavoidable. |
First load less data, select efficient data types, and replace Python loops with built-in Pandas or NumPy operations. Measure the workflow before adding more advanced optimization techniques.
Prefer vectorized operations
def calculate_margin(row):
return (
row["sales"]
- row["cost"]
)
df["margin"] = df.apply(
calculate_margin,
axis="columns"
)
This calls a Python function separately for every DataFrame row.
df["margin"] = (
df["sales"]
- df["cost"]
)
df["margin_percent"] = (
df["margin"]
.div(df["sales"])
.mul(100)
.round(2)
)
Vectorized operations process complete arrays using optimized implementations.
Load only the required data
sales = pd.read_csv(
"sales.csv",
usecols=[
"order_id",
"order_date",
"region",
"sales"
],
dtype={
"order_id": "int64",
"region": "category",
"sales": "float64"
},
parse_dates=[
"order_date"
]
)
Specifying data types can reduce inference work and prevent mixed-type columns. Confirm that the chosen integer and floating-point types can represent every expected value.
Measure memory usage
memory_by_column = (
df.memory_usage(
index=True,
deep=True
)
.sort_values(
ascending=False
)
)
total_megabytes = (
memory_by_column.sum()
/ 1024**2
)
print(memory_by_column)
print(
f"Total memory: "
f"{total_megabytes:.2f} MB"
)
df.info(
memory_usage="deep",
show_counts=True
)
Use categorical data selectively
before = (
df["region"]
.memory_usage(deep=True)
)
df["region"] = (
df["region"]
.astype("category")
)
after = (
df["region"]
.memory_usage(deep=True)
)
print({
"before_bytes": before,
"after_bytes": after
})
Categories can reduce memory when a column contains many repeated values and relatively few unique labels.
unique_values = (
df["region"]
.nunique(dropna=True)
)
row_count = len(df)
unique_ratio = (
unique_values
/ row_count
)
print({
"rows": row_count,
"unique_values": unique_values,
"unique_ratio": unique_ratio
})
Categories may provide little benefit when almost every value is unique.
Process large CSV files in chunks
partial_totals = []
for chunk in pd.read_csv(
"large-sales.csv",
usecols=[
"region",
"sales"
],
dtype={
"region": "string",
"sales": "float64"
},
chunksize=100_000
):
chunk_totals = (
chunk.groupby(
"region"
)["sales"]
.sum()
)
partial_totals.append(
chunk_totals
)
regional_totals = (
pd.concat(partial_totals)
.groupby(level=0)
.sum()
.sort_values(
ascending=False
)
)
print(regional_totals)
Counts, sums, minimums, and maximums can be calculated for each chunk and combined later. Operations requiring the complete dataset may need a different strategy.
Concatenate once
result = pd.DataFrame()
for file_path in csv_files:
frame = pd.read_csv(
file_path
)
result = pd.concat(
[
result,
frame
],
ignore_index=True
)
Each iteration may copy an increasingly large amount of data.
frames = [
pd.read_csv(file_path)
for file_path in csv_files
]
result = pd.concat(
frames,
ignore_index=True,
sort=False
)
A single concatenation avoids repeatedly rebuilding the result.
Assign values safely
# Avoid this pattern
df[df["sales"] < 0]["sales"] = 0
This attempts to assign through an intermediate selected object.
negative_sales = (
df["sales"] < 0
)
df.loc[
negative_sales,
"sales"
] = 0
This clearly modifies the original DataFrame using one assignment.
Objects returned by indexing and DataFrame methods behave as independent
objects, while Pandas delays physical copies internally until they are
required. Chained assignment should still be replaced with one explicit
loc operation.
Iterate only when necessary
for row in df.itertuples(
index=False
):
process_order(
order_id=row.order_id,
sales=row.sales
)
Measure the real workflow with representative data. Techniques such as
eval(), query(), or JIT compilation can add
overhead and may not improve small or simple operations.
Diagnose common problems
Common Errors & Troubleshooting
Understand frequent Pandas errors involving column names, boolean conditions, chained assignment, incompatible data types, datetime accessors, string methods, pivots, and merges.
Error troubleshooting quick reference
| Problem | Typical fix | Explanation | Copy |
|---|---|---|---|
KeyError |
print(df.columns.tolist())
|
Inspect the exact available column labels. | |
| Ambiguous truth value |
if (df["sales"] > 0).any():
|
Reduces a boolean Series to one boolean value. | |
| Chained assignment |
df.loc[df["sales"] < 0, "sales"] = 0
|
Selects and updates the original DataFrame in one operation. | |
.dt accessor error |
df["date"] = pd.to_datetime(df["date"], errors="coerce")
|
Converts the column to a datetime-compatible data type. | |
.str accessor error |
df["name"] = df["name"].astype("string").str.strip()
|
Converts the column to Pandas string dtype before cleaning. | |
| Pivot duplicate error |
df.pivot_table(index="date", columns="region", values="sales", aggfunc="sum")
|
Aggregates duplicate index and column combinations. | |
| Merge type mismatch |
df["customer_id"] = df["customer_id"].astype("string")
|
Creates a consistent merge-key data type. | |
| Overlapping columns |
left.merge(right, on="id", suffixes=("_left", "_right"))
|
Adds clear suffixes to repeated non-key column names. |
Fix a missing column KeyError
# Raises KeyError if the real
# column is named "Sales "
total = df["sales"].sum()
Column names are case-sensitive and may contain hidden whitespace.
df.columns = (
df.columns
.str.strip()
.str.lower()
.str.replace(
" ",
"_",
regex=False
)
)
print(df.columns.tolist())
required_columns = {
"order_id",
"sales"
}
missing_columns = (
required_columns
- set(df.columns)
)
if missing_columns:
raise KeyError(
f"Missing columns: "
f"{sorted(missing_columns)}"
)
Validate required columns before starting the transformation.
Fix an ambiguous boolean condition
# Raises:
# ValueError: The truth value of a
# Series is ambiguous
if df["sales"] > 0:
print("Positive sales found")
The comparison returns one boolean value for every row.
positive_sales = (
df["sales"] > 0
)
if positive_sales.any():
print("At least one positive sale")
if positive_sales.all():
print("Every sale is positive")
positive_rows = df.loc[
positive_sales
]
Use any(), all(), or boolean filtering
according to the required result.
Fix chained assignment
# Does not update df correctly
df["sales"][
df["sales"] < 0
] = 0
With Copy-on-Write, chained assignment cannot update the original DataFrame.
negative_sales = (
df["sales"] < 0
)
df.loc[
negative_sales,
"sales"
] = 0
Selection and assignment now happen in one operation.
Older tutorials may discuss SettingWithCopyWarning.
Pandas 3 uses Copy-on-Write, and chained assignment produces a
ChainedAssignmentError warning because the pattern cannot
update the original object.
Fix datetime and string accessor errors
df["order_date"] = pd.to_datetime(
df["order_date"],
errors="coerce"
)
invalid_dates = df.loc[
df["order_date"].isna()
]
df["order_year"] = (
df["order_date"]
.dt.year
)
Invalid date values become NaT and can be inspected
separately.
df["customer_name"] = (
df["customer_name"]
.astype("string")
.str.strip()
.str.replace(
r"\s+",
" ",
regex=True
)
.str.title()
)
Pandas string dtype preserves missing values while enabling vectorized string methods.
Fix incompatible merge keys
orders["customer_id"] = (
orders["customer_id"]
.astype("string")
.str.strip()
)
customers["customer_id"] = (
customers["customer_id"]
.astype("string")
.str.strip()
)
combined = orders.merge(
customers,
on="customer_id",
how="left",
validate="many_to_one",
indicator=True
)
unmatched = combined.loc[
combined["_merge"]
== "left_only"
]
Pandas may match null keys from one DataFrame with null keys in the other DataFrame. Inspect or remove missing keys before merging when this is not the intended behavior.
Fix duplicate pivot combinations
# Raises an error when more than
# one sales value exists for the
# same date and region
wide_sales = df.pivot(
index="order_date",
columns="region",
values="sales"
)
pivot() cannot decide how duplicate values should be
combined.
wide_sales = df.pivot_table(
index="order_date",
columns="region",
values="sales",
aggfunc="sum",
fill_value=0
)
The aggregation function defines how repeated combinations are combined.
Diagnose unexpected merge results
print({
"orders_rows": len(orders),
"customers_rows": len(customers),
"customer_keys_unique": (
customers["customer_id"]
.is_unique
)
})
combined = orders.merge(
customers,
on="customer_id",
how="left",
suffixes=(
"_order",
"_customer"
),
validate="many_to_one",
indicator="merge_source"
)
print(
combined["merge_source"]
.value_counts()
)
print({
"combined_rows": len(combined)
})
Print column names, data types, DataFrame shapes, missing-value counts, unique-key counts, and a few affected rows. Small targeted checks are usually more useful than printing an entire DataFrame.
Frequently asked questions
Pandas FAQ
Find concise answers to common questions about DataFrames, indexing, missing data, combining datasets, performance, exporting files, and changes introduced in Pandas 3.
What is Pandas used for?
Pandas is a Python library for working with structured data. It is commonly used to import, clean, filter, combine, summarize, reshape, analyze, and export tabular datasets.
Common data sources include CSV files, Excel workbooks, JSON, Parquet files, and SQL databases.
What is the difference between a Series and a DataFrame?
A Series is a one-dimensional labeled collection of
values. A DataFrame is a two-dimensional table made
from rows and columns.
Selecting one column with df["sales"] normally returns
a Series, while selecting multiple columns with
df[["sales", "cost"]] returns a DataFrame.
What is the difference between loc and iloc?
loc selects rows and columns by their labels.
iloc selects them by integer position.
# Label-based selection
df.loc[10, "sales"]
# Position-based selection
df.iloc[0, 2]
Label slices with loc normally include the ending
label, while positional slices with iloc exclude the
ending position.
How do I handle missing values in Pandas?
Use isna() to detect missing values,
dropna() to remove affected rows, and
fillna(), ffill(),
bfill(), or interpolate() to replace
them.
The appropriate method depends on why the data is missing and how the completed values will be used.
What is the difference between merge, join, and concat?
Use merge() for database-style joins using one or more
columns. Use join() primarily to combine DataFrames by
their indexes. Use concat() to stack complete Pandas
objects by rows or columns.
When merging, use validate and
indicator to check key relationships and identify
unmatched records.
What is the difference between map, apply, and transform?
Use map() for element-wise operations or Series value
mappings. Use apply() when a function needs a complete
row or column. Use transform() when the output must
remain aligned with the original index.
Prefer vectorized Pandas operations whenever an existing method can express the calculation.
What happened to DataFrame.applymap in Pandas 3?
DataFrame.applymap() was deprecated in earlier
versions and removed in Pandas 3. Use
DataFrame.map() for element-wise DataFrame
transformations.
rounded = numeric_df.map(
lambda value: round(value, 2),
na_action="ignore"
)
Why does chained assignment fail in Pandas 3?
Copy-on-Write is always active in Pandas 3. An intermediate object created through chained indexing behaves independently, so assigning through that object cannot update the original DataFrame.
# Avoid
df["sales"][df["sales"] < 0] = 0
# Use
df.loc[
df["sales"] < 0,
"sales"
] = 0
Why does a Pandas boolean condition say the truth value is ambiguous?
A comparison such as df["sales"] > 0 returns one
boolean value for every row. Pandas cannot automatically decide
whether you mean any row, every row, or a filtered result.
condition = df["sales"] > 0
condition.any()
condition.all()
positive_rows = df.loc[condition]
Can Pandas process datasets larger than available memory?
Pandas is primarily designed for in-memory analysis. Large CSV and
JSON Lines files can sometimes be processed in chunks using the
chunksize parameter.
Loading only required columns, selecting efficient data types, and aggregating each chunk can substantially reduce memory requirements. Workflows requiring the entire dataset at once may need a larger-scale processing system.
When should I use categorical data?
The category dtype is useful when a column contains
many repeated values but relatively few unique labels, such as
regions, departments, statuses, or product categories.
It may save little memory when nearly every row contains a unique value, so compare memory usage before and after conversion.
Why does pivot fail when pivot_table works?
pivot() requires exactly one value for every index and
column combination. Duplicate combinations cause a
ValueError.
pivot_table() can handle duplicates because an
aggregation function such as sum, mean,
or count defines how the values should be combined.
Which export format should I use?
Use CSV for broad compatibility, Excel for human-facing workbook reports, JSON for web and API records, Parquet for efficient analytical storage, and SQL for database workflows.
CSV is simple but does not preserve Pandas data types as reliably as Parquet. Excel and Parquet may require optional writer dependencies.
Search for a method, task, or error message to find the corresponding examples elsewhere in this Pandas cheat sheet.
Printable reference
Download the Pandas Cheat Sheet PDF
Download the free printable Pandas cheat sheet for quick offline access to DataFrame operations, data cleaning, filtering, grouping, merging, reshaping, importing, exporting, and practical examples.
Free printable reference
Pandas Cheat Sheet
Get the complete 16-page Pandas 3.x reference with syntax tables, copy-ready examples, best practices, troubleshooting guidance, and a practical data analysis workflow.
- 16 printable A4 pages
- Pandas 3.x syntax and examples
- DataFrames, cleaning, GroupBy, merging, and reshaping
- Performance tips and troubleshooting
- No registration required
PDF format · 16 pages · Updated September 2026
Save it for offline reference or print only the pages you use most. The interactive web version includes searchable sections and working copy buttons.