By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
A Hyper-Practical, Zero-Fluff Study Guide
You’re working on a data cleaning pipeline for a retail analytics dashboard. Your dataset has: - Messy product names ("iPhone 13 (64GB, Space Gray)" vs. "Apple iPhone13 64 GB SpaceGray").- Inconsistent customer IDs (some are strings, some are integers).- Missing values in a column that should be boolean ("Yes"/"No" vs. True/False).
"iPhone 13 (64GB, Space Gray)"
"Apple iPhone13 64 GB SpaceGray"
"Yes"
"No"
True
False
You could loop through every row with a for loop, but: ❌ It’s slow (Python loops are 100–1000x slower than vectorized operations).❌ It’s error-prone (off-by-one errors, edge cases with NaN values).❌ It’s unmaintainable (nested loops for simple transformations? No thanks).
for
NaN
This is where apply, map, and vectorized string operations come in.- apply: Run a custom function on every row/column (flexible but slower than vectorized ops).- map: Transform values using a dictionary, function, or Series (great for categorical data).- Vectorized string ops: Fast, built-in string methods (.str.upper(), .str.contains()) that work on entire columns.
apply
map
.str.upper()
.str.contains()
Why this matters in production:- Performance: Vectorized ops use C under the hood (pandas is built on NumPy). A for loop on 1M rows? 10 seconds. A vectorized op? 100ms.- Readability: One line of code (df["name"].str.lower()) vs. 10 lines of loops.- Scalability: Works seamlessly with Dask, Modin, or Spark for big data.- Debugging: Fewer edge cases (e.g., NaN handling is automatic).
df["name"].str.lower()
Real-world scenario:You’re handed a 10GB CSV of user logs. The "timestamp" column is a string like "2023-05-15 14:30:00 UTC", but your ML model needs a datetime object. You also need to extract the hour (14) for a time-of-day analysis. Do you write a loop, or use vectorized ops?
"timestamp"
"2023-05-15 14:30:00 UTC"
datetime
14
apply()
python df["price"].apply(lambda x: x * 1.1) # 10% price increase
map()
"M"
"Male"
python df["gender"] = df["gender"].map({"M": "Male", "F": "Female"})
.str
python df["name"] = df["name"].str.lower().str.strip()
applymap()
replace()
python df["status"] = df["status"].replace({"active": 1, "inactive": 0})
np.where()
IF
python df["is_expensive"] = np.where(df["price"] > 100, "Yes", "No")
str.extract()
python df["hour"] = df["timestamp"].str.extract(r"(\d{2}):\d{2}:\d{2}")[0]
str.split()
expand=True
"First Last"
["First", "Last"]
python df[["first_name", "last_name"]] = df["name"].str.split(" ", expand=True)
pip install pandas
You have a CSV with: - Product names ("iPhone 13 (64GB, Space Gray)").- Prices as strings ("$699.99").- Categories with typos ("Electronics", "electronics", "Elec").
"$699.99"
"Electronics"
"electronics"
"Elec"
Goal: Clean the data for analysis (e.g., extract GB, convert prices to floats, standardize categories).
import pandas as pd # Sample data (replace with your CSV) data = { "product": ["iPhone 13 (64GB, Space Gray)", "Galaxy S22 (128GB)", "MacBook Pro 16\""], "price": ["$699.99", "$799.00", "$2499.99"], "category": ["Electronics", "electronics", "Elec"] } df = pd.DataFrame(data)
Problem: Extract the storage size (e.g., 64GB → 64).Solution: Use str.extract() with regex.
64GB
64
df["storage_gb"] = df["product"].str.extract(r"(\d+)GB")[0].astype(int) print(df[["product", "storage_gb"]])
Output:
product storage_gb 0 iPhone 13 (64GB, Space Gray) 64 1 Galaxy S22 (128GB) 128 2 MacBook Pro 16" NaN
Problem: Prices are strings with $ signs.Solution: Use str.replace() + astype().
$
str.replace()
astype()
df["price"] = df["price"].str.replace("$", "").astype(float) print(df["price"])
0 699.99 1 799.00 2 2499.99 Name: price, dtype: float64
Problem: Categories are inconsistent ("Electronics", "electronics", "Elec").Solution: Use str.lower() + map().
str.lower()
df["category"] = ( df["category"] .str.lower() .map({"electronics": "Electronics", "elec": "Electronics"}) .fillna("Other") ) print(df["category"])
0 Electronics 1 Electronics 2 Electronics Name: category, dtype: object
Problem: Apply a 10% discount to prices > $1000.Solution: Use apply() with a lambda.
df["discounted_price"] = df["price"].apply(lambda x: x * 0.9 if x > 1000 else x) print(df[["price", "discounted_price"]])
price discounted_price 0 699.99 699.99 1 799.00 799.00 2 2499.99 2249.99
print(df.dtypes) print(df.isna().sum())
Expected Output:
product object price float64 category object storage_gb int64 discounted_price float64 dtype: object storage_gb 1 # MacBook Pro has no GB value dtype: int64
np.where
int8
swifter
pip install swifter
python import swifter df["column"] = df["column"].swifter.apply(lambda x: x * 2)
python df["clean_name"] = ( df["name"] .str.lower() .str.strip() .str.replace(r"[^a-z0-9]", "", regex=True) )
rename()
python df = df.rename(columns={"old_name": "new_name"})
.fillna()
.dropna()
errors="coerce"
python df["age"] = pd.to_numeric(df["age"], errors="coerce") # Invalid → NaN
pytest
python def test_clean_price(): assert clean_price("$100") == 100.0
AttributeError
.fillna("")
map({"old": "new"})
Answer: Vectorized ops (.str) are 100x faster.
Edge Cases: "How does str.contains() handle NaN?"
str.contains()
Answer: Returns False (unlike str.extract(), which returns NaN).
Regex: "Extract the year from 2023-05-15 using str.extract()."
2023-05-15
Answer: df["date"].str.extract(r"(\d{4})").
df["date"].str.extract(r"(\d{4})")
Mapping: "Convert ["M", "F", "M"] to ["Male", "Female", "Male"]."
["M", "F", "M"]
["Male", "Female", "Male"]
df["gender"].map({"M": "Male", "F": "Female"})
replace
You have a column "user_id" with values like "user_123". Extract the numeric part (123) and convert it to an integer.
"user_id"
"user_123"
123
df["user_id_num"] = df["user_id"].str.extract(r"(\d+)")[0].astype(int)
Why it works: - str.extract(r"(\d+)") captures one or more digits.- [0] selects the first capture group.- .astype(int) converts the string to an integer.
str.extract(r"(\d+)")
[0]
.astype(int)
df["col"].str.lower()
df["col"].str.extract(r"(\d+)")
df["col"].str.split(" ", expand=True)
df["col"].replace({"old": "new"})
regex=True
np.where(df["col"] > 100, "Yes", "No")
df["col"].map({"M": "Male"})
df["col"].apply(lambda x: x * 2)
df["col"].str.contains("abc")
df["col"].str.strip()
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.