BitBootcampBitBootcamp ← all explainers 🔄 Review ⚙ Settings

PD-01-018-P · Primer — apply, map, datetime & a First Plot

The idea in one sentence

When the built-in vectorized operators aren't enough, apply runs your own function over every value in a column, map swaps each value for a lookup, pd.to_datetime turns text like "2021-03-15" into a real date you can do date-math on through the .dt accessor, and .plot draws a chart straight from the DataFrame — four small tools that together take you from raw text to a picture.

Why it exists

You already know how to transform a column with a plain expression — df['price'] * 0.9 scales every number at once. But sooner or later the rule you need doesn't fit into a single operator: "label each score pass or fail", "replace each user id with the team it belongs to", "figure out which month each signup happened in." Those rules live in your head, not in the * and + keys, so pandas gives you apply (run any function you write over each value) and map (swap each value using a dict or Series). Dates are a special headache of their own — they arrive as text, which pandas can't sort chronologically or subtract, so pd.to_datetime upgrades that text into a genuine datetime dtype and the .dt accessor unlocks .year, .month, .day_name() and more. And once your columns are clean and typed, .plot turns the whole frame into a chart in one line, so you can see the shape of your data instead of squinting at rows. These are the tools that carry you from "the data is loaded" to "I understand what it says."

How it works

Start with a column and reach for the smallest tool that fits. apply takes a function and calls it once per value: df['score'].apply(grade) runs your grade function on 88, then 91, then 79, and collects the returned labels into a new Series. map is the lookup cousin — hand it a dict and each value is replaced by its match, perfect for category-to-category swaps. Dates get the special treatment: pd.to_datetime(df['joined']) reads each string and returns a datetime64 column, and only then can you crack it open with the .dt accessor — .dt.month_name(), .dt.year, .dt.day_name() — to pull calendar parts out. Finally .plot reads the frame and draws it. Here it is end to end:

import pandas as pd

df = pd.DataFrame({
    'name':   ['Ada', 'Grace', 'Alan'],
    'joined': ['2021-03-15', '2021-07-02', '2022-01-20'],  # dates as TEXT
    'score':  [88, 91, 79],
})

# apply: run your own function on every value
def grade(s):
    return 'pass' if s >= 80 else 'fail'
df['result'] = df['score'].apply(grade)

# to_datetime: upgrade the text column into real dates
df['joined'] = pd.to_datetime(df['joined'])

# .dt accessor: now the calendar parts are reachable
df['month'] = df['joined'].dt.month_name()

print(df)
#     name     joined  score result    month
# 0    Ada 2021-03-15     88   pass    March
# 1  Grace 2021-07-02     91   pass     July
# 2   Alan 2022-01-20     79   fail  January

# .plot: one line draws a chart (score over time)
ax = df.plot(x='joined', y='score', kind='line', title='Scores over time')

Notice the joined column now prints without quotes — that's the visual tell that it became a real datetime, which is what makes .dt.month_name() and the plotted time axis work at all.

The one gotcha

.dt only exists after to_datetime — on a text column it isn't there, and pandas throws AttributeError: Can only use .dt accessor with datetimelike values. The seductive mistake is to skip the conversion: your joined column looks like dates on screen, so you reach straight for df['joined'].dt.year — and it explodes, because the column is still plain object text and text has no .dt. The same trap hides in sorting and math: on a text date column, sort_values() orders "2022-01-20" before "2021-07-02"? No — it happens to sort right only because ISO strings sort alphabetically, but subtract two of them and you get a TypeError, and any other date format ("03/15/2021") sorts into nonsense. So the rule is: convert first, then use .dt. One more foot-gun with apply: it runs your Python function once per row, so on a big table it's far slower than a vectorized expression — reach for apply only when no plain operator or .dt/.str method will do the job. Convert your dates the moment they arrive, and the rest of your analysis stops fighting you.

See it — text dates get upgraded, then the toolbox opens

flowchart TD
    A["joined column<br/>['2021-03-15', ...]<br/>dtype: object (text)"] --> B{"Call<br/>pd.to_datetime?"}
    B -->|"No"| C[".dt.year fails<br/>AttributeError"]
    B -->|"Yes"| D["dtype: datetime64<br/>real dates"]
    D --> E[".dt accessor works<br/>.month_name(), .year"]
    D --> F[".plot draws a<br/>real time axis"]

The one-line takeaway

Use apply/map when your rule won't fit an operator, always pd.to_datetime a text-date column before reaching for .dt, and let .plot turn the cleaned-up frame into a chart in one line.

Where you'll meet it

You'll meet apply and map the first time a business rule is too specific for arithmetic — bucketing scores into grades, mapping product codes to human names, tagging rows high/low — and reaching for a custom function feels like unlocking a new gear. You'll meet to_datetime and .dt on essentially every real dataset, because dates always arrive as text and every "signups per month" or "how many days since signup" question is impossible until you convert. And you'll meet .plot at the happiest moment of an analysis — when one line turns a table into a trend you can actually see. Interviewers lean on all four: "how would you label each row without a loop?", "why does .dt.year raise an error here?", "how do you get the month name out of a date?", and "show me signups over time." Get fluent with convert-then-.dt, and reserve apply for the rules that truly need it, and you'll move from loaded data to insight — and a chart — faster than anyone still writing loops.

Named concepts: apply/map, to_datetime, .dt accessor, .plot