Computer ScienceChapter 66 min read

Ch6. Python — Data Analysis with NumPy & pandas

O
OIYO EditorialContributor
6/8

Why NumPy and pandas?

Python’s standard library handles general programming well, but data analysis demands specialized tools. Two libraries dominate the field:

LibraryPurposeKey Abstraction
NumPyNumerical computation, multi-dimensional arraysndarray
pandasStructured / tabular data manipulationDataFrame
pip install numpy pandas

NumPy Arrays

import numpy as np

# Create from a list
arr = np.array([1, 2, 3, 4, 5])
print(arr)          # [1 2 3 4 5]
print(arr.dtype)    # int64
print(arr.shape)    # (5,)

# 2-D array
matrix = np.array([[1, 2, 3],
                   [4, 5, 6]])
print(matrix.shape)  # (2, 3)
print(matrix.ndim)   # 2
print(matrix.size)   # 6

Convenience constructors

np.zeros((3, 4))           # 3×4 array of 0.0
np.ones((2, 3))            # 2×3 array of 1.0
np.eye(3)                  # 3×3 identity matrix
np.arange(0, 10, 2)        # [0 2 4 6 8]
np.linspace(0, 1, 5)       # [0. 0.25 0.5 0.75 1.]
np.random.randint(1, 7, (2, 3))  # 2×3 random ints 1–6

Vectorized Operations

NumPy’s biggest advantage: no loops needed.

a = np.array([1, 2, 3, 4, 5])
b = np.array([10, 20, 30, 40, 50])

print(a + b)    # [11 22 33 44 55]
print(a * 2)    # [ 2  4  6  8 10]
print(b / a)    # [10. 10. 10. 10. 10.]
print(a ** 2)   # [ 1  4  9 16 25]

# Compare with plain Python:
# [x**2 for x in [1,2,3,4,5]]  — loop required

Indexing and Boolean Filtering

arr = np.array([10, 20, 30, 40, 50])

print(arr[0])     # 10
print(arr[-1])    # 50
print(arr[1:4])   # [20 30 40]

# 2-D slicing
mat = np.arange(1, 10).reshape(3, 3)
print(mat[:, 1])       # second column: [2 5 8]
print(mat[0:2, 0:2])   # 2×2 sub-matrix

# Boolean indexing
scores = np.array([85, 92, 68, 74, 90, 55, 88])
print(scores[scores >= 80])   # [85 92 90 88]

NumPy Statistics

data = np.array([15, 22, 8, 34, 27, 12, 45, 19])

print(f"Sum:    {np.sum(data)}")           # 182
print(f"Mean:   {np.mean(data):.2f}")      # 22.75
print(f"Median: {np.median(data)}")        # 20.5
print(f"Std:    {np.std(data):.2f}")       # 11.51
print(f"Min:    {np.min(data)} at [{np.argmin(data)}]")
print(f"Max:    {np.max(data)} at [{np.argmax(data)}]")

pandas: Series

import pandas as pd

# Series — a labelled 1-D array
s = pd.Series([85, 92, 78, 90],
              index=["Math", "English", "Korean", "Science"])

print(s["Math"])            # 85
print(s[["Math", "English"]])   # two-item subset
print(s.mean())             # 86.25

pandas: DataFrame

data = {
    "name":  ["Alice", "Bob", "Carol", "Dan"],
    "age":   [25, 30, 22, 28],
    "city":  ["Seoul", "Busan", "Incheon", "Seoul"],
    "score": [85, 92, 78, 90],
}

df = pd.DataFrame(data)
print(df)
#     name  age     city  score
# 0  Alice   25    Seoul     85
# 1    Bob   30    Busan     92
# 2  Carol   22  Incheon     78
# 3    Dan   28    Seoul     90

print(df.shape)    # (4, 4)
print(df.dtypes)

Exploring a DataFrame

print(df.info())       # column types, non-null counts
print(df.describe())   # numeric statistics

print(df.head(2))   # first 2 rows
print(df.tail(2))   # last 2 rows

# Column access
print(df["name"])              # Series
print(df[["name", "score"]])   # DataFrame

# Row access
print(df.iloc[0])     # position-based
print(df.loc[2])      # label-based

Filtering and Sorting

# Single condition
high = df[df["score"] >= 85]

# Multiple conditions
seoul_high = df[(df["city"] == "Seoul") & (df["score"] >= 85)]

# Sort descending
sorted_df = df.sort_values("score", ascending=False)

# Top 2 by score
top2 = df.nlargest(2, "score")

Adding Columns and Grouping

# Derived column
df["grade"] = df["score"].apply(
    lambda x: "A" if x >= 90 else ("B" if x >= 80 else "C")
)

# Group aggregation
city_avg = df.groupby("city")["score"].mean()
print(city_avg)

# Value counts
print(df["city"].value_counts())

Missing Values

df2 = pd.DataFrame({
    "A": [1, 2, None, 4],
    "B": [5, None, 7, 8],
})

print(df2.isnull().sum())   # missing count per column

df2.dropna()                # drop rows with any NaN
df2.fillna(0)               # replace NaN with 0
df2.fillna(df2.mean())      # replace NaN with column mean

CSV and JSON Workflows

# Save DataFrame
df.to_csv("students.csv", index=False)
df.to_json("students.json", orient="records", indent=2)

# Load DataFrame
df_csv  = pd.read_csv("students.csv")
df_json = pd.read_json("students.json")

Practical Example — Grade Report

import numpy as np
import pandas as pd

np.random.seed(42)
n = 20
df = pd.DataFrame({
    "id":      [f"S{i:03d}" for i in range(1, n + 1)],
    "math":    np.random.randint(50, 101, n),
    "english": np.random.randint(50, 101, n),
    "science": np.random.randint(50, 101, n),
})

subjects = ["math", "english", "science"]
df["avg"]   = df[subjects].mean(axis=1).round(1)
df["grade"] = df["avg"].apply(
    lambda x: "A" if x >= 90 else ("B" if x >= 80 else ("C" if x >= 70 else "D"))
)

print(f"Class average: {df['avg'].mean():.1f}")
print("\nSubject means:")
print(df[subjects].mean().round(1))
print("\nGrade distribution:")
print(df["grade"].value_counts().sort_index())
print("\nTop 5 students:")
print(df.nlargest(5, "avg")[["id", "avg", "grade"]])

Quick Reference

TaskCode
Create arraynp.array([...])
Array shape.shape, .ndim, .size
Vectorized matharr * 2, arr + arr2
Boolean filterarr[arr > 4]
NumPy statsnp.mean(), np.std(), np.sum()
Create DataFramepd.DataFrame(dict)
Column selectdf["col"] / df[["c1","c2"]]
Row filterdf[df["col"] > val]
Sortdf.sort_values("col")
Groupbydf.groupby("col")["val"].mean()
Missing values.isnull(), .dropna(), .fillna()
CSV round-trip.to_csv() / pd.read_csv()

Practice Quiz

Q1. What is “vectorized operation” and why is it faster than a Python loop?

A1. A vectorized operation applies a computation to an entire NumPy array at once, without a Python-level loop. NumPy delegates the work to compiled C code, bypassing Python’s per-element overhead. For example, arr * 2 multiplies every element simultaneously, while [x * 2 for x in lst] interprets each iteration in Python.


Q2. What is the difference between df["score"] and df[["score"]]?

A2. df["score"] returns a Series (1-D). df[["score"]] returns a DataFrame (2-D, one column). Use the double-bracket form when you need a DataFrame or when selecting multiple columns: df[["math", "english"]].


Q3. Write one line that selects students whose average is above 85 and whose city is “Seoul”.

A3.

result = df[(df["avg"] > 85) & (df["city"] == "Seoul")]

Q4. What does np.array([[1,2,3],[4,5,6]]).reshape(3, 2) produce? What are its .shape, .ndim, and .size?

A4. A 3-row × 2-column matrix:

[[1 2]
 [3 4]
 [5 6]]

.shape(3, 2), .ndim2, .size6.


Q5. What are two strategies for dealing with NaN values in a DataFrame, and when would you choose each?

A5.

  • dropna() — removes rows (or columns) containing any NaN. Choose when missing data is rare and you can afford to lose those rows.
  • fillna(value) — replaces NaN with a constant or derived value (e.g., column mean). Choose when you want to retain all rows and missing values can be reasonably estimated.

Chapter 7 Preview

Next: Web Scraping & Automation — requests, BeautifulSoup, file automation with os and shutil, and scheduling scripts.

O

OIYO Editorial

Editorial Desk

The OIYO editorial desk researches money, law, lifestyle, and self-understanding topics against primary sources and public statistics. Every piece carries source notes and is reviewed on a regular cycle for accuracy and usefulness.