Drill the skill in short exercises, then apply it in a real-world scenario. Each module ladders up from practice to application.
seconds_to_minutes(total) that converts a total number of seconds into a string like "4m8s" — whole minutes plus leftover seconds. Use // for the integer-divided minutes and % for the leftover seconds, then stitch them into a string.
Tempo Catalog Warmup
with Sarah·· 300 XPcelsius_to_fahrenheit(celsius) that takes a list of Celsius numbers and returns a NumPy array of the matching Fahrenheit values. Formula: F = C * 9/5 + 32.
Example. [0, 100] → array([32., 212.]).
Notes. Use NumPy broadcasting — no for-loops. Empty input → empty array.row_sums(matrix) that takes a 2D list (list of lists) and returns a NumPy array with one entry per row — the sum of that row.
Example. [[1,2,3],[4,5,6]] → array([6, 15]).
Notes. Use .sum() with the right axis argument. Rows with negative values still sum normally (no special-casing). A single-row input still returns a 1-element array.pick_columns(matrix, cols) that takes a 2D list and a list of column indices, and returns a NumPy array with just those columns, in the order given.
Example. matrix=[[1,2,3],[4,5,6]], cols=[0,2] → array([[1, 3], [4, 6]]).
Notes. cols=[2,0] returns columns in that swapped order, not sorted. cols=[] returns a 2D array with 0 columns (one empty row per input row).build_scores_df(names, scores) that returns a DataFrame with two columns, name and score, where each row pairs a name with its score (same index).
Example. names=["Alice","Bob"], scores=[85,92] →
| name | score |
|-------|-------|
| Alice | 85 |
| Bob | 92 |
Notes. Both inputs are always the same length. Empty inputs → empty DataFrame with both columns present.describe_shape(df) that returns a dict with three keys: "rows" (number of rows), "columns" (number of columns), "column_names" (list of column names in their original order).
Example. A 2×3 DataFrame with columns a, b, c → {"rows": 2, "columns": 3, "column_names": ["a", "b", "c"]}.
Notes. Works on any DataFrame, including empty ones (0 rows but columns still defined).n, return only the first n rows.
Your task. Write first_n(df, n) that returns a DataFrame containing the first n rows of df, preserving the original column order.
Example. A 5-row df with n=2 → the first 2 rows as a DataFrame.
Notes. If n is larger than the number of rows, return all rows. If n is 0, return an empty DataFrame (same columns, no rows).parse_csv(csv_text) that takes a CSV-formatted string (header row present) and returns a DataFrame.
Example. "a,b\n1,2\n3,4" → 2-row DataFrame with columns a and b.
Notes. Use io.StringIO to wrap the string so pd.read_csv can read it like a file. Always has a header row.to_csv_string(df) that returns the DataFrame serialized as a CSV string with header, without the index column.
Example. A df with name/score → "name,score\nAlice,85\nBob,92\n".
Notes. Include header. Skip the index column (otherwise pandas prepends an unnamed column with row numbers). Trailing newline is part of pandas' default output.parse_and_filter(csv_text, min_amount) that parses the CSV and returns only rows where amount >= min_amount.
Example. CSV of 4 orders with amounts [100, 30, 75, 10], min_amount=50 → rows with 100 and 75.
Notes. Preserve row order from the CSV. min_amount higher than every value → empty DataFrame.get_scores(df) that returns the score column as a pandas Series (not a single-column DataFrame).
Example. A df with name and score → the score Series.
Notes. df["score"] returns a Series; df[["score"]] (double-bracket) would return a DataFrame — we want the first. Empty df → empty Series.rows_by_position(df, start, stop) that returns rows from position start (inclusive) to stop (exclusive), using positional indexing.
Example. A 5-row df with start=1, stop=3 → rows at positions 1 and 2.
Notes. Same semantics as Python list slicing. start == stop → empty DataFrame. stop > len(df) → returns through end.rows_where_score_ge(df, threshold) that returns the rows of df where score >= threshold. Preserve original row order.
Example. scores [90, 75, 85, 60], threshold=80 → rows with scores 90 and 85.
Notes. Threshold higher than every score → empty DataFrame. Threshold lower than every score → all rows.orders_in_regions(df, regions) that returns rows where df["region"] is in regions (a list of region codes).
Example. regions ["A", "C"] → rows tagged A or C, in original order.
Notes. Preserve original df order. Empty regions list → empty DataFrame.amounts_between(df, low, high) that returns rows where df["amount"] is between low and high, inclusive on both ends.
Example. amounts [10, 25, 50, 75, 100], low=25, high=75 → rows with 25, 50, 75.
Notes. Both endpoints inclusive. low == high → only rows matching that exact value. Empty result if no values qualify.emails_from_domain(df, domain) that returns rows where df["email"] contains domain as a substring (case-insensitive).
Example. domain "gmail" matches "alice@gmail.com" and "carol@GMAIL.com" both.
Notes. Case-insensitive match. No matches → empty DataFrame.complete_rows(df) that returns rows where no column has a NaN.
Example. A row with NaN in any single column is dropped, even if all other columns are filled.
Notes. No NaNs anywhere → all rows kept. Every row has at least one NaN → empty DataFrame.age entries; fill them with a default so downstream math doesn't break.
Your task. Write fill_missing_ages(df, default) that returns a new DataFrame where NaN values in the age column are replaced with default. Other columns are untouched.
Example. age [30, NaN, 25, NaN], default=0 → [30, 0, 25, 0].
Notes. Only the age column is modified. df with no NaN in age → unchanged.nan_counts(df) that returns a Series indexed by column name, with the count of NaN values in each column.
Example. A df with 2 NaN in age and 1 NaN in city → {age: 2, city: 1, name: 0}.
Notes. Include columns with zero NaN. Empty df → Series with one zero per column.price as floats so you can add them up.
Your task. Write prices_as_float(df) that returns the price column as a Series of floats.
Example. ["10.5", "20"] → [10.5, 20.0].
Notes. Use .astype() to convert dtype. Empty input → empty Series." alice ", "BOB"); clean each to a consistent format.
Your task. Write clean_names(df) that returns the name column with each value stripped of surrounding whitespace and converted to title case.
Example. " alice " → "Alice". "BOB" → "Bob". " JANE doe" → "Jane Doe".
Notes. Use .apply() to run a function across the column. Empty df → empty Series."US", "UK", "IN") but the final report needs full names.
Your task. Write expand_country_codes(df, mapping) that returns the country column with each code replaced by its full name from the mapping dict.
Example. codes ["US", "IN"], mapping {"US": "United States", "IN": "India"} → ["United States", "India"].
Notes. Codes not in the mapping are left unchanged. Empty mapping → all codes pass through unchanged.sales_per_region(df) that returns a pandas Series indexed by region, where each value is the sum of the amount column for that region.
Example.
Input df:
| region | amount |
| ------ | ------ |
| North | 100 |
| South | 50 |
| North | 30 |
Returns a Series:
region
North 130
South 50
Notes.
- Region order in the result doesn't matter — we only compare keys → values.
- Empty DataFrame → return an empty Series.
- A region with only one order is still a valid group (its total is just that one value).orders_per_customer(df) that returns a Series indexed by customer_id with the count of orders per customer.
Example. Input rows (C1, O1), (C2, O2), (C1, O3) → Series {C1: 2, C2: 1}.
Notes. Order of customers in the result doesn't matter. Empty DataFrame → empty Series.avg_rating_per_genre(df) that returns a Series indexed by genre, with the mean of the rating column for that genre.
Example. Genre Action has ratings [4, 5] → entry Action: 4.5.
Notes. Genre order in the result doesn't matter. A genre with one rating returns that rating as its mean. Empty DataFrame → empty Series.sort_by_revenue_desc(df) that returns the DataFrame sorted by revenue in descending order.
Example. revenues [150, 50, 200, 100] → reordered as 200, 150, 100, 50.
Notes. Stable sort — equal revenues keep their original relative order. Empty df → empty df.top_n(df, n) that returns the n rows with the largest values in the sales column, ordered from highest to lowest.
Example. 5 products, n=3 → top 3 by sales.
Notes. n larger than the df → returns all rows in sorted order. n=0 → empty DataFrame.sort_grade_then_name(df) that returns the DataFrame sorted by grade ascending, then name ascending.
Example. grades [B, A, A, B], names [Bob, Alice, Cher, Dan] → Alice/A, Cher/A, Bob/B, Dan/B.
Notes. grade is the primary key; name is the tiebreaker. Empty df → empty df.attach_customer_name(orders, customers) that returns an inner-joined DataFrame on customer_id. Only customers present in both tables are kept.
Example. orders has customer_ids 1, 2, 3; customers has 1, 2, 4 → result has only rows for IDs 1 and 2.
Notes. Inner join. Result preserves the orders-table row order for matched rows. No overlap → empty DataFrame."Unknown".
Your task. Write attach_or_unknown(orders, customers) that left-joins on customer_id and replaces missing names with "Unknown".
Example. orders for customers 10, 20, 30; customers has only 10, 20 → 3 rows, with name="Unknown" for the order with customer 30.
Notes. Same row order as orders. Every order matched → no "Unknown" substitution.combine(dfs) that takes a list of DataFrames and returns them concatenated vertically with the index reset to 0..N-1.
Example. combine([df_mon, df_tue, df_wed]) → all rows stacked, re-indexed.
Notes. Empty list → empty DataFrame (pd.concat([]) raises, so handle that). All dfs share the same columns.
CityBikes Ride Log
with Devon·· 260 XPdrop_duplicate_rows(df) that returns a DataFrame with duplicate rows removed, keeping the first occurrence of each.
Example. If (Alice, NYC) appears twice, the result keeps just the first one.
Notes. A row is a duplicate only if every column matches. Preserve the order of the rows that are kept. No duplicates → df unchanged.tidy_headers(df) that returns the DataFrame with every column name cleaned: surrounding whitespace stripped, lowercased, and inner spaces replaced with underscores.
Example. " First Name " → "first_name", "AGE" → "age".
Notes. Only the headers change — the row data is untouched. Already-clean headers stay as they are.price column was read from CSV as text because the numbers contain thousands separators ("1,200"). You can't do math on it until it's numeric.
Your task. Write fix_price_column(df) that returns the DataFrame with the price column converted to integers — strip the commas first, then change the type.
Example. "1,200" → 1200, "980" → 980.
Notes. Only the price column changes. Every value is a clean integer once the commas are gone.email column has inconsistent entries — stray spaces and mixed casing — so the same address looks different across rows.
Your task. Write normalize_emails(df) that returns the email column as a Series, with surrounding whitespace stripped and every value lowercased.
Example. " Alice@MAIL.com " → "alice@mail.com".
Notes. Return a Series, not a DataFrame. Already-clean values pass through unchanged.phone column has numbers written with all kinds of separators — hyphens and spaces — and you need them as plain digit strings.
Your task. Write clean_phone_numbers(df) that returns the phone column as a Series with every hyphen (-) and space removed.
Example. "98-765 43210" → "9876543210".
Notes. Return a Series. Remove both hyphens and spaces. Values with no separators pass through unchanged.label column mixes free text with a four-digit year somewhere inside each value. You want just the year.
Your task. Write extract_year(df) that returns a Series with the first four-digit number pulled out of each label.
Example. "Report 2021" → "2021", "Q3 2022 update" → "2022".
Notes. Return a Series of strings. If a label has more than one number, take the first match. Every label contains at least one four-digit run.date column was read from CSV as plain text. Date math and .dt operations only work once it's a real datetime type.
Your task. Write to_datetime_column(df) that returns the date column converted to a pandas datetime Series.
Example. the text "2021-03-15" becomes a real datetime value.
Notes. Return the converted Series. Every value is a valid YYYY-MM-DD date string.extract_months(df) that parses the date column and returns a Series of month numbers (1-12).
Example. "2021-03-15" → 3, "2021-12-01" → 12.
Notes. Return a Series of integers. Parse the text to datetime first, then reach for the month.rows_in_year(df, year) that returns the rows of df whose date falls in the given year. Keep the date column exactly as it came in.
Example. year=2021 keeps only rows dated in 2021, in their original order.
Notes. Parse the dates to compare years, but don't change the returned date values. No matching rows → empty DataFrame.sales_pivot(df) that returns a pivot table with one row per region, one column per quarter, and the sum of amount in each cell. Turn the region index back into a regular column.
Example. rows for East/Q1 and East/Q2 collapse to a single East row with a Q1 and a Q2 column.
Notes. Several rows can share the same region+quarter — sum them. Every region/quarter combination in the data has at least one row.to_long(df) that melts the wide table into columns student, subject, and score.
Example.
| student | math | science |
|---------|------|---------|
| Alice | 90 | 85 |
becomes rows (Alice, math, 90) and (Alice, science, 85).
Notes. student stays as an identifier column. Every other column becomes a subject value.to_wide(df) that pivots the long table so each student is a row and each subject is its own column holding the score. Turn the student index back into a regular column.
Example. rows (Alice, math, 90) and (Alice, science, 85) collapse to one Alice row with math and science columns.
Notes. Each student-subject pair appears exactly once. Every student has a score for every subject in the data.
Wanderstay Booking Log Cleanup
with Nadia·· 340 XPmake_line_chart(months, revenue) that creates a figure, draws revenue over months as a line, and returns the axes.
Example. fig, ax = plt.subplots() gives you a figure and axes; the line goes on the axes.
Notes. Return ax, not fig. One line only — don't add extra plots.make_bar_chart(categories, sales) that draws one bar per category with its sales as the height, and returns the axes.
Example. Toys gets a bar of height 500, Books one of height 320, and so on.
Notes. Keep the categories in the order given. Return ax.make_scatter(hours, scores) that draws one dot per (hours, scores) pair and returns the axes.
Example. The student with 1 hour and score 52 becomes the dot at (1, 52).
Notes. Use a scatter, not a line — dots live in ax.collections, not ax.lines.make_labeled_chart(days, temps) that plots temperature over days as a line, sets a title mentioning "Temperature", labels both axes, and returns the axes.
Example. Title "Daily Temperature", x-label "Day", y-label "Temperature (C)".
Notes. The title just has to contain the word Temperature — the exact wording is up to you. Both axis labels must be non-empty.make_legend_chart(months, online, store) that plots two lines — online labeled "Online", store labeled "Store" (in that order) — adds a legend, and returns the axes.
Example. Pass label="Online" to the first plot call, then ax.legend() picks the labels up.
Notes. Plot Online first, Store second — the legend order follows plotting order. Labels are case-sensitive.make_styled_line(months, sales) that plots sales as a red line, sets a title mentioning "Sales", and returns the axes.
Example. color="red" in the plot call; title like "Monthly Sales".
Notes. Use the color name "red" exactly (not a hex code) — the checker reads back the color you set.make_grid(revenue, orders) that creates a 1×2 grid with plt.subplots(1, 2), plots revenue as a line in the left panel and orders as a line in the right panel, and returns both fig, axes.
Example. fig, axes = plt.subplots(1, 2) — then axes[0] is the left panel, axes[1] the right.
Notes. Return the pair: return fig, axes. One line per panel.make_dashboard(months, revenue, regions, region_sales) — a 1×2 grid with a revenue line in the left panel and one bar per region in the right panel. Return fig, axes.
Example. axes[0].plot(...) for the trend, axes[1].bar(...) for the regions.
Notes. Each panel has its own plotting methods — nothing is shared between them.make_titled_grid(revenue, orders) — a 1×2 grid, revenue line left titled exactly "Revenue", orders line right titled exactly "Orders". Return fig, axes.
Example. axes[0].set_title("Revenue") titles just the left panel.
Notes. Panel titles are set on each axes, not on the figure. Exact spelling matters here.make_hist(df) that draws a seaborn histogram of the rating column with 5 bins, and returns the axes.
Example. sns.histplot(...) with x="rating" and bins=5.
Notes. Seaborn functions return the matplotlib axes they drew on — return that directly.make_box(df) that draws a seaborn box plot with team on x and score on y, returning the axes; (2) set median_a to team A's median score, as a float.
Example. The line inside team A's box sits exactly at median_a.
Notes. For the median: filter the team A rows, take the score column, call .median(), wrap in float(...).make_grouped_hist(df) that draws a seaborn histogram of steps with 4 bins, split by plan using hue, and returns the axes.
Example. hue="plan" colors each plan separately — and adds the legend for you.
Notes. No manual legend() call needed; seaborn builds it from the hue values.make_scatter(df) that draws a seaborn scatter plot with ad_spend on x and sales on y, and returns the axes.
Example. sns.scatterplot(...) — the column names become the axis labels automatically.
Notes. Pass the DataFrame via data= and name the columns via x= / y=.make_trend(df) that draws a seaborn line plot of visitors over week, gives the chart a title (any wording), and returns the axes.
Example. sns.lineplot(...) then ax.set_title("Weekly Visitors").
Notes. sns.lineplot returns the axes — keep a reference so you can title it.make_segment_scatter(df) that draws a seaborn scatter of spend over visits, colored by segment using hue, and returns the axes.
Example. hue="segment" — each segment gets its own color and a legend entry.
Notes. The legend appears automatically once hue is set.countplot counts and draws in one call.
Your task. Write make_status_count(df) that draws a seaborn count plot of the status column and returns the axes.
Example. sns.countplot(...) needs only x="status" — no counting on your side.
Notes. Bars follow first-appearance order in the data: delivered, pending, cancelled.sns.barplot aggregates for you.
Your task. Write make_class_averages(df) that draws a seaborn bar plot of score per class with errorbar=None, sets a title mentioning "Average", and returns the axes.
Example. Class A's bar lands at 80 — the mean of 78 and 82.
Notes. barplot shows the mean per category by default. errorbar=None hides the uncertainty whiskers.annot=True keeps the numbers visible.
Your task. Write make_heatmap(grid) that draws a seaborn heatmap of the grid with the values printed in each cell, sets a title, and returns the axes.
Example. sns.heatmap(grid, annot=True) — the grid is already a table, so it goes in as-is.
Notes. No melting or pivoting needed here; heatmap wants exactly this rows-by-columns shape.
FitPulse Session Cleanup
with Lena·· 410 XP
Marketly Revenue Report
with Marcus·· 540 XP