Skip to main content
Back to module
Intro to Data Analysis with Python · Pandas in Anger

Loading a CSV

Reading1h 12mLesson 1 of 4 in this module

Load a CSV into a pandas DataFrame and inspect its shape, columns, types and values before analysing it.

Why it matters

Almost every analysis starts the same way: someone hands you a file, usually a CSV, and asks a question about it. Before you can answer anything you need to get that file into a DataFrame and check what you actually have. Skipping that check is how analysts end up averaging a column that was secretly text.

The DataFrame in one minute

A pandas DataFrame is a table: rows, named columns, and an index (row labels, by default 0, 1, 2, ...). Each column is a Series, a one-dimensional array with a single data type (dtype), such as int64, float64, bool, datetime64 or text.

import pandas as pd

df = pd.DataFrame({
    "sample": ["S1", "S2", "S3"],
    "temp_c": [21.5, 22.1, 20.8],
})
df["temp_c"]          # a Series
df["temp_c"].mean()   # 21.466...

Loading a CSV

pd.read_csv reads a file path, a URL or any file-like object:

df = pd.read_csv("readings.csv")
df = pd.read_csv("https://example.org/data/readings.csv")

In the browser exercises there is no file system, so the data lives in a string and io.StringIO makes it look like a file:

import io

RAW = """station,date,temp_c,humidity
A,2026-03-01,4.5,81
B,2026-03-01,6.1,77
A,2026-03-02,5.2,79
"""
df = pd.read_csv(io.StringIO(RAW))

Options you will use often:

OptionUse it when
sep=";"the file uses semicolons or tabs (sep="\t")
usecols=["a", "b"]you only need some columns
parse_dates=["date"]a column holds dates
na_values=["-", "ERR"]the file marks missing values with custom text (common markers such as empty cells, NA, n/a and NaN are recognised by default)
dtype={"zip": str}codes with leading zeros must stay text
nrows=1000you want a quick look at a huge file
encoding="latin-1"you get a UnicodeDecodeError

The first five checks

Run these every time you load data:

df.shape        # (rows, columns), e.g. (3, 4)
df.head()       # first 5 rows (df.tail() for the last)
df.columns      # the column names
df.dtypes       # the type of each column
df.info()       # all of the above plus non-null counts

Then summarise the numbers:

df.describe()                  # count, mean, std, min, quartiles, max
df["station"].value_counts()   # how often each value appears
df["station"].nunique()        # number of distinct values

Each answer is a question about your data. Is the row count what you expected? Are the numeric columns really numeric? Does count in describe() show missing values? Does value_counts() reveal "A" and "a " as different stations?

Selecting numeric columns

select_dtypes filters columns by type, which is handy before summarising:

numeric = df.select_dtypes(include="number")
list(numeric.columns)   # ['temp_c', 'humidity']

Reading dtypes

If a column you expected to be numeric shows as object or str, at least one value in it is not a clean number: a stray "ERR" or "-", a comma used as a decimal point, a unit such as "5 kg". pandas then keeps the whole column as text, and .mean() will fail or give nonsense. You will fix this in the next lesson; for now, notice it.

bad = pd.read_csv(io.StringIO("x\n1\n2\n-\n"))
bad.dtypes                   # x is text, because of "-"
fixed = pd.read_csv(io.StringIO("x\n1\n2\n-\n"), na_values=["-"])
fixed.dtypes                 # x float64, with NaN for the missing value

A worked example

df = pd.read_csv(io.StringIO(RAW), parse_dates=["date"])
print(df.shape)                       # (3, 4)
print(df.dtypes)                      # station text, date datetime64, temp_c float64, humidity int64
print(df["temp_c"].mean().round(2))   # 5.27
print(df["station"].value_counts())   # A 2, B 1

In four lines you know the size, the types, a headline number and the balance of stations.

Common mistakes

  • Not looking at the data. Always run head() and info() before computing anything.
  • Lost leading zeros. ZIP codes and IDs like 00742 become 742 unless you pass dtype={"zip": str}.
  • Header problems. If the first row is data, pass header=None and set names=[...]. If there are notes above the header, use skiprows.
  • Calling df.shape(). shape is an attribute, not a method: df.shape.
  • Assuming dates are dates. Without parse_dates or pd.to_datetime, a date column is just text.

Quick recap

  • pd.read_csv loads a file, URL or io.StringIO string into a DataFrame.
  • shape, head, dtypes, info, describe and value_counts are the first checks.
  • A numeric column showing as text means at least one dirty value.
  • select_dtypes(include="number") picks the numeric columns.

Try it

In the Do tab you will load a small weather-station CSV and record its shape, column names, numeric columns and a mean temperature.

Also in this lesson: