Getting data in
read_csv, and the four calls to run before you trust a single row.
pd.read_csvparse_datesdtype=usecols.info()to_parquetWatch it happen
Play it through, or step back and forth yourself.
pd.read_csv("orders.csv")pd.read_csv("orders.csv") and you have a DataFrame. It takes a path, a URL, or anything file-like, and it guesses a great deal — the delimiter, the header row, and every column's type.
The idea
pd.read_csv takes a path, a URL, or anything file-like, and hands back a DataFrame. It also guesses a great deal — the delimiter, the header row, and every column's type — which is why the next paragraph matters more than this one.
Look at it before you trust it
Four calls, every time, before writing any analysis:
df.head() # do the values look like what you expected?
df.shape # did you get all the rows?
df.dtypes # is anything text that should be a number or a date?
df.info() # dtypes, non-null counts and memory, all at onceThen df.describe() for ranges — a -999 hiding in a column of ages shows up here — and df.isna().sum() for how much is missing, per column.
Five seconds of looking prevents the failure mode where you build an entire analysis on a date column that's actually strings.

Tell it what you meant
Fixing types on the way in is cheaper than fixing them afterwards:
pd.read_csv(
"orders.csv",
parse_dates=["date"], # real timestamps, not strings
dtype={"cups": "Int64"}, # nullable integer, not float
index_col="date", # use it as the index straight away
)
Files that fight back
Real CSVs are rarely tidy, and there's a keyword for each way they misbehave:
sep=";" # a different delimiter
skiprows=3 # junk above the header
header=None, names=[...] # no header row at all
na_values=["N/A", "-"] # what counts as missing in this file
decimal="," # European decimal commas
thousands="," # 1,234 as a number
encoding="latin-1" # when UnicodeDecodeError appearsna_values is the one people miss. If a file writes missing data as "N/A" or "-", pandas reads it as text — and your numeric column silently becomes strings.
Big files
usecols=["date", "cups"] # only the columns you need
nrows=1000 # sample while developing
chunksize=100_000 # stream it in piecesusecols is the biggest easy win — most analyses touch a handful of columns out of dozens.
Other sources, and writing back
pd.read_excel / read_json / read_parquet / read_sql / read_html
df.to_csv("out.csv", index=False) # index=False, nearly always
df.to_parquet("out.parquet") # smaller, faster, keeps dtypesTwo habits worth forming. Pass index=False to to_csv unless the index means something, or you'll grow a stray Unnamed: 0 column every round trip. And for anything you'll read more than once, use Parquet — it's columnar, compressed, and preserves dtypes exactly, so none of the guessing above has to happen again.
See it run
The lesson's code, ready to run and to fiddle with.
Putting the kettle on…
Starting up…
Worked example
not gradedAlready written and ready to go — press Run to see what it does, then change a number, a column name, anything, and run it again.
tryusecols=["date", "cups"] and see what comes back.
Your turn
4 exercises. Write the code yourself, then press Check — a nudge and the answer are there if you want them.
Read "orders.csv" and return its shape.
Read "orders.csv" parsing date as a real date, and return the dtypes.
Read only the date and cups columns from "orders.csv", and return the result's columns.
After reading "orders.csv", return how many values are missing per column.
