Pandas DataFrames and Series
Master selection, filtering, column operations, and the core DataFrame API for daily data manipulation work.
Real-World Scenario
A data analyst at an e-commerce company receives a CSV of 500,000 customer orders. Before any analysis, they need to select relevant columns, filter to completed orders, add computed columns like order value, and fix data quality issues. These are DataFrame operations — the daily workflow of every data practitioner.
Selecting Columns
import pandas as pd
df = pd.DataFrame({
"order_id": [1001, 1002, 1003, 1004, 1005],
"customer": ["Alice", "Bob", "Carol", "Dave", "Eve"],
"product": ["Laptop", "Mouse", "Laptop", "Keyboard", "Monitor"],
"quantity": [1, 2, 1, 3, 1],
"unit_price":[1200.0, 25.0, 1200.0, 45.0, 350.0],
"status": ["shipped", "delivered", "pending", "delivered", "shipped"],
})
# Single column — returns a Series
product_col = df["product"]
print(type(product_col)) # <class 'pandas.core.series.Series'>
# Multiple columns — returns a DataFrame (double brackets)
subset = df[["customer", "product", "unit_price"]]
print(subset.head())
# Attribute access — works for columns with valid Python identifiers
# Avoid for column names with spaces or matching a DataFrame method
print(df.status.value_counts())
Row Selection with loc and iloc
import pandas as pd
df = pd.DataFrame({
"product": ["Laptop", "Mouse", "Keyboard", "Monitor", "Webcam"],
"category": ["Electronics", "Accessories", "Accessories", "Electronics", "Accessories"],
"price": [1200.0, 25.0, 45.0, 350.0, 80.0],
"in_stock": [True, True, False, True, True],
}, index=["P001", "P002", "P003", "P004", "P005"]) # string index
# loc — label-based: [row_label, col_label]
print(df.loc["P001"]) # entire row P001 as a Series
print(df.loc["P001", "price"]) # single value: 1200.0
print(df.loc["P001":"P003"]) # rows P001 through P003 inclusive
print(df.loc[["P001", "P004"], ["product", "price"]]) # specific rows & cols
# iloc — position-based: [row_int, col_int]
print(df.iloc[0]) # first row
print(df.iloc[0, 2]) # row 0, col 2 (price)
print(df.iloc[:3]) # first 3 rows
print(df.iloc[:3, [0, 2]]) # first 3 rows, cols 0 and 2
Boolean Filtering
import pandas as pd
df = pd.DataFrame({
"customer": ["Alice", "Bob", "Carol", "Dave", "Eve", "Frank"],
"country": ["US", "UK", "US", "DE", "US", "UK"],
"revenue": [4500, 1200, 8900, 3400, 6700, 2100],
"orders": [12, 3, 25, 8, 18, 5],
"tier": ["Gold", "Bronze", "Platinum", "Silver", "Gold", "Bronze"],
})
# Single condition
us_customers = df[df["country"] == "US"]
print(us_customers)
# Multiple conditions — use & (and), | (or), ~ (not)
high_value_us = df[(df["country"] == "US") & (df["revenue"] > 5000)]
print(high_value_us)
# isin — check membership in a list
english_speaking = df[df["country"].isin(["US", "UK"])]
print(english_speaking)
# String contains
gold_plus = df[df["tier"].isin(["Gold", "Platinum"])]
# query() — readable alternative for complex filters
result = df.query("country == 'US' and revenue > 5000")
print(result)
# query with a variable
min_revenue = 3000
result2 = df.query("revenue > @min_revenue") # @ references a local variable
Adding and Modifying Columns
import pandas as pd
import numpy as np
df = pd.DataFrame({
"product": ["Laptop", "Mouse", "Keyboard", "Monitor"],
"cost": [800.0, 15.0, 30.0, 200.0],
"quantity": [50, 300, 200, 80],
"category": ["Electronics", "Accessories", "Accessories", "Electronics"],
})
# Add a computed column
df["price"] = df["cost"] * 1.5 # 50% markup
df["total_value"] = df["price"] * df["quantity"]
# Conditional column with np.where
df["high_value"] = np.where(df["total_value"] > 5000, True, False)
# Multiple conditions with np.select
conditions = [
df["total_value"] > 10000,
df["total_value"] > 5000,
df["total_value"] > 1000,
]
choices = ["High", "Medium", "Low"]
df["value_tier"] = np.select(conditions, choices, default="Minimal")
# apply — custom function per row or per column
df["price_formatted"] = df["price"].apply(lambda x: f"${x:,.2f}")
# String operations via .str accessor
df["category_upper"] = df["category"].str.upper()
df["category_short"] = df["category"].str[:5]
print(df[["product", "price_formatted", "value_tier", "category_upper"]])
# Drop a column
df.drop(columns=["category_upper"], inplace=True)
# Rename columns
df.rename(columns={"cost": "unit_cost", "quantity": "units_available"}, inplace=True)
Sorting
import pandas as pd
df = pd.DataFrame({
"name": ["Alice", "Bob", "Carol", "Dave", "Eve"],
"dept": ["Eng", "Eng", "Sales", "Sales", "Eng"],
"salary": [95000, 88000, 72000, 76000, 105000],
"years": [5, 3, 7, 4, 8],
})
# Sort by a single column
print(df.sort_values("salary", ascending=False))
# Sort by multiple columns — dept ascending, salary descending within dept
print(df.sort_values(["dept", "salary"], ascending=[True, False]))
# Sort by index
df_shuffled = df.sample(frac=1, random_state=42) # shuffle
print(df_shuffled.sort_index()) # restore original row order
Value Counts and Basic Exploration
import pandas as pd
df = pd.read_csv("orders.csv") # assume a real dataset
# Value counts — frequency of each unique value
print(df["status"].value_counts())
print(df["status"].value_counts(normalize=True)) # as proportions
# Unique values
print(df["country"].unique())
print(df["country"].nunique()) # count of unique values
# Check for nulls
print(df.isnull().sum()) # null count per column
print(df.isnull().sum() / len(df)) # null rate per column
# Basic statistics
print(df["revenue"].describe())
print(df.describe(include="all")) # includes string columns too Frequently Asked Questions
What is the difference between loc and iloc?
loc selects by label — row index labels and column names. iloc selects by integer position — 0, 1, 2 regardless of the actual index. When your index is a RangeIndex (0, 1, 2...) they look the same, but they diverge when your index is dates, strings, or non-contiguous integers.
Why does chained indexing like df['col'][0] = value sometimes not work?
Chained indexing creates a temporary intermediate object. The assignment may go to the temporary copy instead of the original DataFrame, and Pandas emits a SettingWithCopyWarning. Always use df.loc[row, col] = value for assignments.