Series & DataFrames

Pandas gives you two objects. A Series is a one-dimensional labelled array — think of one column. A DataFrame is a table of Series that share a common index — think of a spreadsheet sheet with named columns.

Creating them

import pandas as pd

s = pd.Series([10, 20, 30], index=["a", "b", "c"])
df = pd.DataFrame({
    "name": ["Asha", "Ravi", "Meera"],
    "city": ["Delhi", "Pune", "Delhi"],
    "sales": [120000, 95000, 143000],
})
df.head()
df.shape, df.columns, df.dtypes

Read files: pd.read_csv, pd.read_excel, pd.read_json, pd.read_sql.

Every column is a Series, and df["sales"] hands you that Series. Anything you learn about Series applies to columns, which is why the two concepts are taught together.

The index is the point

The index is not just row numbers; it is a label that pandas uses to align data automatically:

q1 = pd.Series([120, 95, 143], index=["Delhi", "Pune", "Nagpur"])
q2 = pd.Series([130, 150, 88], index=["Pune", "Delhi", "Nagpur"])
print(q1 + q2)     # Delhi 270, Nagpur 231, Pune 245 — matched by label, not position

NumPy would have added these element by element and given three wrong numbers. Alignment is the single biggest practical difference between pandas and plain arrays. The same rule applies when you assign a Series to a column: values land by index, and unmatched labels become NaN.

Set a meaningful index when rows have a natural key:

df = df.set_index("name")
df.loc["Ravi", "sales"]     # 95000
df = df.reset_index()       # back to a normal column

Inspecting a new table

Call Tells you
df.shape Rows and columns
df.head(10) / df.tail() First / last rows
df.info() Dtypes, non-null counts, memory
df.describe() Numeric summary (add include="all" for text)
df.columns / df.dtypes Column names and types
df["city"].value_counts() Frequency of each value
df["city"].unique() / nunique() Distinct values, and how many
df.sample(5) A random peek, safer than head on sorted data

Run info() first, every time. An object dtype on a column that should be numeric means text is hiding in it.

Useful Series operations

sales = df["sales"]
sales.sum(), sales.mean(), sales.median(), sales.std()
sales.rank(ascending=False)
sales.pct_change()
sales.astype("float64")
df["sales_lakh"] = (sales / 100000).round(2)   # add a derived column

Building DataFrames other ways

rows = [{"item": "Kaju katli", "kg": 4}, {"item": "Soan papdi", "kg": 9}]
pd.DataFrame(rows)                       # from a list of dicts (API responses)
pd.DataFrame(arr, columns=["a", "b"])    # from a NumPy array

Common mistakes

  • Confusing df["sales"] (a Series) with df[["sales"]] (a one-column DataFrame).
  • Expecting position-based addition and getting index alignment instead.
  • Forgetting that most methods return a new object: df.drop(columns="x") does not change df unless you assign.
  • Leaving a meaningless index after filtering; use reset_index(drop=True).

Practice

Build a DataFrame of eight employees with name, city, department and monthly salary. Print info(), describe() and value_counts() for city. Set name as the index, look up one person's salary with loc, add an annual-salary column, then reset the index.

B

Written by Bablu Kumar Jha

Founder, BeeProton AI · Mumbai, Maharashtra, India