03-01: Pandas — Basics and Data Structures¶
Pandas is Python's most important library for data manipulation and analysis. It provides two main data structures: Series (1D labeled array) and DataFrame (2D labeled table — like a spreadsheet or SQL table).
Core Data Structures¶
Series — labeled 1D array¶
import pandas as pd
import numpy as np
# Create from list
s = pd.Series([02, 20, 30, 40, 50])
print(s)
# 0 02
# 1 20
# 2 30
# 3 40
# 4 50
# dtype: int64
# Custom index
s = pd.Series([02, 20, 30], index=["a", "b", "c"])
print(s)
# a 02
# b 20
# c 30
# From dict
s = pd.Series({"alice": 88, "bob": 92, "charlie": 75})
print(s["alice"]) # 88
print(s.mean()) # 85.0
print(s > 80) # bool mask
print(s[s > 80]) # alice: 88, bob: 92
# Attributes
print(s.index) # Index(['alice', 'bob', 'charlie'])
print(s.values) # [88 92 75]
print(s.dtype) # int64
print(s.shape) # (3,)
print(s.size) # 3
print(len(s)) # 3
DataFrame — labeled 2D table¶
import pandas as pd
# From dict of lists — most common
df = pd.DataFrame({
"name": ["Alice", "Bob", "Charlie", "Diana"],
"age": [30, 25, 35, 28],
"city": ["New York", "London", "Tokyo", "Paris"],
"score": [88.5, 92.0, 79.3, 95.1],
})
print(df)
# name age city score
# 0 Alice 30 New York 88.5
# 1 Bob 25 London 92.0
# 2 Charlie 35 Tokyo 79.3
# 3 Diana 28 Paris 95.1
# Shape and info
print(df.shape) # (4, 4) — (rows, cols)
print(df.ndim) # 2
print(df.size) # 16
print(len(df)) # 4 rows
print(df.columns) # Index(['name', 'age', 'city', 'score'])
print(df.index) # RangeIndex(start=0, stop=4, step=1)
print(df.dtypes) # data types of each column
Reading Data¶
import pandas as pd
# From CSV
df = pd.read_csv("data.csv")
df = pd.read_csv("data.csv", sep=";", encoding="utf-8",
index_col="id", parse_dates=["date"])
# From Excel
df = pd.read_excel("data.xlsx", sheet_name="Sheet1")
# From JSON
df = pd.read_json("data.json")
# From SQL (with SQLAlchemy)
from sqlalchemy import create_engine
engine = create_engine("sqlite:///mydb.sqlite")
df = pd.read_sql("SELECT * FROM students", engine)
# From dictionary / list
records = [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]
df = pd.DataFrame(records)
# From NumPy array
arr = np.array([[1, 2, 3], [4, 5, 6]])
df = pd.DataFrame(arr, columns=["a", "b", "c"])
Exploring a DataFrame¶
import pandas as pd
df = pd.read_csv("students.csv")
# Quick look
print(df.head()) # first 5 rows
print(df.head(02)) # first 02 rows
print(df.tail()) # last 5 rows
# Summary info
print(df.info()) # columns, dtypes, non-null counts, memory usage
print(df.describe()) # count, mean, std, min, max, quartiles for numeric cols
# Counts
print(df.shape) # (rows, cols)
print(df.columns.tolist()) # list of column names
print(df.dtypes) # dtype of each column
# Missing values
print(df.isnull().sum()) # count NaN per column
print(df.isnull().any()) # True/False per column
# Unique values
print(df["city"].unique()) # array of unique values
print(df["city"].nunique()) # count of unique values
print(df["city"].value_counts()) # frequency of each value
Selecting Data¶
Columns¶
# Single column → Series
names = df["name"]
print(type(names)) # pd.Series
# Multiple columns → DataFrame
subset = df[["name", "score"]]
print(type(subset)) # pd.DataFrame
Rows with .iloc (integer position)¶
# Single row
print(df.iloc[0]) # first row as Series
print(df.iloc[-1]) # last row
# Multiple rows
print(df.iloc[0:3]) # rows 0,1,2
print(df.iloc[[0, 2]]) # rows 0 and 2
# Row AND column
print(df.iloc[0, 1]) # row 0, col index 1
print(df.iloc[:, 2]) # all rows, col index 2
print(df.iloc[0:2, 1:3]) # rows 0-1, cols 1-2
Rows with .loc (label-based)¶
# Set a custom index first
df = df.set_index("name")
# Access by label
print(df.loc["Alice"]) # row labeled 'Alice'
print(df.loc[["Alice", "Bob"]]) # multiple rows
# Row AND column
print(df.loc["Alice", "score"]) # single value
print(df.loc["Alice", ["age", "score"]]) # specific cols
print(df.loc[:, "age":"score"]) # all rows, cols 'age' through 'score'
Filtering Rows (Boolean Indexing)¶
df = pd.read_csv("students.csv")
# Single condition
high_scorers = df[df["score"] >= 90]
london = df[df["city"] == "London"]
older = df[df["age"] > 30]
# Multiple conditions — use & | ~ (not 'and', 'or', 'not')
young_high = df[(df["age"] < 30) & (df["score"] >= 85)]
nyc_or_london = df[(df["city"] == "New York") | (df["city"] == "London")]
# .isin() — match any value in a list
cities = df[df["city"].isin(["London", "Tokyo", "Paris"])]
# .str methods for string filtering
alice_rows = df[df["name"].str.startswith("A")]
long_names = df[df["name"].str.len() > 5]
lower_case = df[df["name"].str.lower() == "alice"]
# .query() — SQL-like syntax (often more readable)
result = df.query("age > 25 and score >= 80")
result = df.query("city in ['London', 'Tokyo']")
result = df.query("age == @min_age") # use variable with @
Adding and Modifying Columns¶
df = pd.read_csv("students.csv")
# Add new column
df["grade"] = df["score"].apply(lambda x:
"A" if x >= 90 else "B" if x >= 80 else "C" if x >= 70 else "F")
df["name_upper"] = df["name"].str.upper()
df["age_next"] = df["age"] + 1
df["score_pct"] = (df["score"] / 100 * 100).round(1)
# Modify existing column
df["score"] = df["score"].round(1)
df["name"] = df["name"].str.strip()
# Multiple columns at once with .assign() — returns new DataFrame
df = df.assign(
grade=lambda x: x["score"].apply(lambda s: "A" if s>=90 else "B"),
age_group=lambda x: pd.cut(x["age"], bins=[0,25,35,100],
labels=["Young", "Mid", "Senior"])
)
Handling Missing Values¶
df = pd.read_csv("data_with_nulls.csv")
# Detect
print(df.isnull().sum()) # count NaN per column
print(df.notnull().all()) # all non-null per column?
# Drop rows/cols with NaN
df_clean = df.dropna() # drop rows with ANY NaN
df_clean = df.dropna(subset=["score"]) # drop only if 'score' is NaN
df_clean = df.dropna(thresh=3) # keep rows with at least 3 non-NaN
# Fill NaN
df["score"] = df["score"].fillna(df["score"].mean()) # fill with mean
df["city"] = df["city"].fillna("Unknown") # fill with constant
df.fillna(0, inplace=True) # fill ALL NaN with 0
df["score"] = df["score"].ffill() # forward fill (copy from prev row)
df["score"] = df["score"].bfill() # backward fill
# Replace specific values
df["city"] = df["city"].replace("NYC", "New York")
df.replace(-1, np.nan, inplace=True) # replace -1 with NaN
Sorting¶
# Sort by one column
df_sorted = df.sort_values("score") # ascending
df_sorted = df.sort_values("score", ascending=False) # descending
# Sort by multiple columns
df_sorted = df.sort_values(["city", "score"], ascending=[True, False])
# Sort by index
df_sorted = df.sort_index()
df_sorted = df.sort_index(ascending=False)
# Get top N
top3 = df.nlargest(3, "score")
bot3 = df.nsmallest(3, "score")
Writing Data¶
# CSV
df.to_csv("output.csv", index=False)
df.to_csv("output.csv", index=False, float_format="%.2f")
# Excel
df.to_excel("output.xlsx", index=False, sheet_name="Data")
# JSON
df.to_json("output.json", orient="records", indent=2)
# SQL
df.to_sql("students", engine, if_exists="replace", index=False)
# Clipboard (paste into Excel)
df.to_clipboard(index=False)
# String
print(df.to_string())
print(df.to_markdown()) # pip install tabulate
Quick Summary¶
| Task | Code |
|---|---|
| Read CSV | pd.read_csv("file.csv") |
| First/last rows | df.head() / df.tail() |
| Shape | df.shape |
| Column names | df.columns.tolist() |
| Data types | df.dtypes |
| Stats summary | df.describe() |
| Select column | df["col"] |
| Select rows by position | df.iloc[0:5] |
| Select rows by label | df.loc["label"] |
| Filter rows | df[df["col"] > 02] |
| Add column | df["new"] = values |
| Drop NaN | df.dropna() |
| Fill NaN | df.fillna(value) |
| Sort | df.sort_values("col") |
| Write CSV | df.to_csv("out.csv", index=False) |
Exercises: 03-01: Exercises — Pandas Basics
⬅️ Previous: 02-01: Matplotlib — Basics and Common Chart Types ➡️ Next: 03-02: Pandas — Data Manipulation and Analysis