Danh sách bài viết

Bài 34: Series và DataFrame — 2 cấu trúc cốt lõi

Pandas là thư viện xử lý dữ liệu bảng (tabular) chuẩn ngành cho data science và machine learning trong Python. Bài học mở đầu module 5 — giới thiệu hai cấu trúc cốt lõi: Series (1D có label index) và DataFrame (2D row index + column labels), cách tạo từ list / dict / ndarray, các attribute shape / columns / index / dtypes / values, label-based và position-based indexing với loc / iloc, set_index / reset_index, so sánh với NumPy ndarray và một số operation cột cơ bản.

24/05/2026
13 phút đọc
0 lượt xem
1

Mục tiêu bài học

Sau bài học, bạn sẽ:

  • Biết Pandas là gì, quan hệ với NumPy, và vai trò trong pipeline AI / ML.
  • Tạo được Series từ list, dict, ndarray và đọc các attribute cơ bản.
  • Tạo được DataFrame từ dict of lists, list of dicts, ndarray.
  • Dùng lociloc để truy cập theo label hoặc theo vị trí.
  • Đổi index với set_index / reset_index.
  • Chọn, thêm, drop cột; phân biệt khi nào nên dùng Pandas, khi nào NumPy.

Module 5 (bài 34 đến 41) là phần Pandas. Bài 34 mở đầu, đặt nền cho đọc dữ liệu (CSV / JSON / Excel) ở bài 35 và các thao tác clean / group / merge sau đó.

2

Pandas là gì

Pandas là thư viện xử lý dữ liệu dạng bảng (tabular data) cho Python, công bố lần đầu năm 2008 bởi Wes McKinney. Tên "pandas" xuất phát từ thuật ngữ kinh tế lượng panel data — dữ liệu quan sát theo nhiều đối tượng qua thời gian.

Pandas được dùng gần như mặc định cho:

  • Đọc và ghi dữ liệu từ CSV, JSON, Excel, Parquet, SQL.
  • Làm sạch dữ liệu: missing value, đổi kiểu, lọc, deduplicate.
  • Khám phá dữ liệu (EDA): thống kê, groupby, pivot.
  • Chuẩn bị feature trước khi đưa vào scikit-learn, PyTorch, TensorFlow.

Quan hệ với NumPy: Pandas xây trên NumPy — buffer dữ liệu bên dưới phần lớn là numpy.ndarray. So với NumPy, Pandas thêm vào bốn thứ:

  • Label-based index: hàng và cột có nhãn (string, datetime, tuple), không chỉ chỉ số nguyên 0, 1, 2, ...
  • Mixed dtype: các cột trong cùng một bảng có thể khác kiểu — cột tên là object (string), cột tuổi là int64, cột ngày là datetime64[ns].
  • Missing data: giá trị thiếu được biểu diễn bằng NaN (số) hoặc NaT (datetime) hoặc pd.NA, kèm bộ hàm xử lý (fillna, dropna, isna).
  • Time series: hỗ trợ resampling, rolling window, timezone, lịch nghỉ — không có trong NumPy thuần.

Phiên bản dùng trong bài: Pandas >= 2.0 (release 04/2023, hỗ trợ Python 3.9 đến 3.12). Pandas 2.x bổ sung backend Arrow cho dtype và cải thiện hiệu năng nhưng API cơ bản trong bài giữ nguyên so với 1.x.

3

Cài đặt và import

Trên máy local:

pip install pandas

Hoặc qua conda:

conda install pandas

Trên Google Colab và Kaggle, Pandas có sẵn — chỉ cần import. Convention chuẩn của cộng đồng:

import pandas as pd
import numpy as np

print(pd.__version__)   # ví dụ: 2.2.2

Alias pd được dùng trong tài liệu chính thức của Pandas, scikit-learn, statsmodels — không nên đổi.

4

Series — 1D có label index

Series là cấu trúc 1 chiều: một mảng giá trị đi kèm một index. Có thể hiểu Series như một numpy.ndarray 1D được dán nhãn cho từng phần tử.

import pandas as pd

s = pd.Series([10, 20, 30])
print(s)
# 0    10
# 1    20
# 2    30
# dtype: int64

Cột bên trái (0, 1, 2) là index mặc định RangeIndex — tương đương chỉ số nguyên. Có thể truyền index tuỳ ý:

s = pd.Series([10, 20, 30], index=["a", "b", "c"], name="score")
print(s)
# a    10
# b    20
# c    30
# Name: score, dtype: int64

Tạo từ dict — key của dict trở thành index:

s = pd.Series({"a": 10, "b": 20, "c": 30})
print(s["a"])    # 10

Attribute của Series

AttributeÝ nghĩa
valuesMảng giá trị thô — numpy.ndarray
indexObject Index chứa các label
dtypeKiểu dữ liệu của các phần tử
shapeTuple kích thước, ví dụ (3,)
sizeSố phần tử
nameTên của Series (tuỳ chọn)
s = pd.Series([10, 20, 30], index=["a", "b", "c"], name="score")
print(s.values)   # [10 20 30]
print(s.index)    # Index(['a', 'b', 'c'], dtype='object')
print(s.dtype)    # int64
print(s.shape)    # (3,)
print(s.size)     # 3
print(s.name)     # score

Truy cập phần tử: label vs vị trí

Có hai cách truy cập: theo label (giá trị trong index) hoặc theo vị trí (0, 1, 2, ...). Pandas khuyến nghị dùng lociloc để minh thị:

s = pd.Series([10, 20, 30], index=["a", "b", "c"])

# Theo label
print(s.loc["a"])    # 10

# Theo vị trí
print(s.iloc[0])     # 10
print(s.iloc[-1])    # 30 — phần tử cuối

Cú pháp s["a"]s[0] vẫn dùng được, nhưng dễ nhập nhằng khi index lại là số nguyên — vì vậy code sản xuất nên dùng loc / iloc cho rõ.

5

DataFrame — 2D row + column

DataFrame là cấu trúc 2 chiều: một bảng có nhãn cho cả hàng (row index) và cột (column labels). Mỗi cột là một Series; các cột có thể khác dtype.

Tạo từ dict of lists

Cách phổ biến nhất — mỗi key là tên cột, mỗi value là danh sách giá trị của cột đó:

import pandas as pd

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Carol"],
    "age":  [25, 30, 22],
    "city": ["Hanoi", "HCMC", "Da Nang"],
})
print(df)
#     name  age     city
# 0  Alice   25    Hanoi
# 1    Bob   30     HCMC
# 2  Carol   22  Da Nang

Tạo từ list of dicts

Mỗi dict là một hàng — key thành tên cột. Tiện khi dữ liệu đến từ JSON / API:

rows = [
    {"name": "Alice", "age": 25},
    {"name": "Bob",   "age": 30},
    {"name": "Carol", "age": 22},
]
df = pd.DataFrame(rows)
print(df.shape)   # (3, 2)

Tạo từ ndarray

Khi đã có dữ liệu số ở dạng NumPy, có thể bọc thành DataFrame và đặt tên cột:

import numpy as np

arr = np.array([
    [1.0, 2.0, 3.0],
    [4.0, 5.0, 6.0],
])
df = pd.DataFrame(arr, columns=["x", "y", "z"], index=["r1", "r2"])
print(df)
#       x    y    z
# r1  1.0  2.0  3.0
# r2  4.0  5.0  6.0

Convention đặt tên cột: lowercase, snake_case, tránh space và ký tự đặc biệt — vì cú pháp df.col_name chỉ chạy khi tên cột là Python identifier hợp lệ. Tên "first name" sẽ phải truy cập bằng df["first name"], không dùng được df.first name.

6

Attributes của DataFrame

AttributeÝ nghĩa
shapeTuple (rows, columns)
columnsObject Index chứa tên cột
indexObject Index chứa nhãn hàng
dtypesSeries ánh xạ tên cột tới dtype của cột đó
valuesUnderlying numpy.ndarray 2D — chỉ ý nghĩa khi mọi cột cùng dtype
sizeTổng số ô (rows * columns)
df = pd.DataFrame({
    "name": ["Alice", "Bob", "Carol"],
    "age":  [25, 30, 22],
})
print(df.shape)      # (3, 2)
print(df.columns)    # Index(['name', 'age'], dtype='object')
print(df.index)      # RangeIndex(start=0, stop=3, step=1)
print(df.dtypes)
# name    object
# age      int64
# dtype: object
print(df.size)       # 6

Cột name có dtype object — đây là cách Pandas lưu cột string (bên dưới là array các con trỏ Python string). Pandas 2.x có thêm dtype string chuyên dụng và backend Arrow, nhưng object vẫn là mặc định.

Lưu ý về .values: khi các cột khác dtype, kết quả sẽ là ndarray dtype object — chậm và mất ưu thế của NumPy. Pandas 1.0+ khuyến nghị dùng .to_numpy() để minh thị, và chỉ gọi khi đã chắc các cột cùng kiểu số.

7

Series là cột của DataFrame

Truy cập một cột của DataFrame bằng tên — kết quả là một Series chia sẻ cùng index với DataFrame:

df = pd.DataFrame({
    "name": ["Alice", "Bob", "Carol"],
    "age":  [25, 30, 22],
})

ages = df["age"]
print(type(ages))   # <class 'pandas.core.series.Series'>
print(ages)
# 0    25
# 1    30
# 2    22
# Name: age, dtype: int64

Chọn nhiều cột — truyền list, kết quả là DataFrame:

subset = df[["name", "age"]]
print(type(subset))   # <class 'pandas.core.frame.DataFrame'>

Điểm cần nhớ: df["age"] trả Series, df[["age"]] trả DataFrame một cột. Hai object khác nhau, dù in ra trông tương tự.

8

Index và set_index / reset_index

Khi tạo DataFrame mà không chỉ định, index là RangeIndex(0, 1, 2, ...). Có thể chuyển một cột thành index để truy vấn theo label cho gọn:

df = pd.DataFrame({
    "id":   [101, 102, 103],
    "name": ["Alice", "Bob", "Carol"],
    "age":  [25, 30, 22],
})

df2 = df.set_index("id")
print(df2)
#        name  age
# id
# 101   Alice   25
# 102     Bob   30
# 103   Carol   22

print(df2.loc[101])   # truy cập hàng có id=101

set_index mặc định trả về DataFrame mới, không sửa df gốc. Muốn sửa tại chỗ thì truyền inplace=True (không khuyến nghị vì khó debug) hoặc đơn giản là gán lại.

Đi ngược lại — đưa index về cột thường và quay lại RangeIndex:

df3 = df2.reset_index()
print(df3.columns.tolist())   # ['id', 'name', 'age']

Multi-level index (xem trước)

Index có thể có nhiều cấp — gọi là MultiIndex. Ví dụ ngắn để biết hình thù; chi tiết sẽ ở module groupby:

idx = pd.MultiIndex.from_tuples(
    [("HN", 2024), ("HN", 2025), ("HCMC", 2024)],
    names=["city", "year"],
)
s = pd.Series([100, 120, 200], index=idx)
print(s.loc[("HN", 2024)])   # 100

MultiIndex xuất hiện tự nhiên sau groupby hai khoá hoặc khi xếp panel data — bài groupby ở cuối module sẽ quay lại.

9

DataFrame vs NumPy ndarray

Hai cấu trúc nhìn giống nhau (đều bảng số 2D) nhưng vai trò khác hẳn:

Đặc điểmnumpy.ndarraypandas.DataFrame
dtype các cộtĐồng nhấtHỗn hợp (mỗi cột một dtype)
Label hàng / cộtKhông
Missing valuePhải tự xử lýNaN / NaT / pd.NA tích hợp
Time seriesKhôngCó (DatetimeIndex, resample, ...)
Tốc độ trên array số thuầnNhanh hơnCó overhead nhẹ (label, dtype hỗn hợp)
Bộ nhớTiết kiệm hơnTốn thêm cho index và metadata

Quy tắc thực tế khi viết pipeline AI:

  • Khám phá và preprocess (load, clean, feature engineering, EDA): dùng Pandas — label và mixed dtype giúp code dễ đọc, ít lỗi.
  • Train model (gradient descent, ma trận, GPU): chuyển sang NumPy hoặc tensor (PyTorch, TensorFlow) — đồng nhất kiểu, tận dụng SIMD và GPU.

Cầu nối giữa hai bên thường là df.to_numpy() hoặc df.values để lấy ndarray, sau đó torch.from_numpy(...) nếu cần tensor PyTorch.

10

Operations cơ bản với cột

Bốn thao tác cột xuất hiện ở gần như mọi notebook:

1. Chọn cột

df = pd.DataFrame({
    "name":  ["Alice", "Bob", "Carol"],
    "math":  [8.0, 6.5, 9.0],
    "lit":   [7.0, 7.5, 6.0],
})

print(df["math"])              # Series — 1 cột
print(df[["math", "lit"]])     # DataFrame — nhiều cột

2. Thêm cột mới

Gán cho khoá chưa tồn tại — Pandas vectorize phép tính, không cần loop:

df["avg"] = (df["math"] + df["lit"]) / 2
print(df)
#     name  math  lit   avg
# 0  Alice   8.0  7.0  7.50
# 1    Bob   6.5  7.5  7.00
# 2  Carol   9.0  6.0  7.50

3. Cột boolean từ điều kiện

df["passed"] = df["avg"] >= 7.0
print(df["passed"].dtype)   # bool

4. Drop cột

drop mặc định trả DataFrame mới, không sửa gốc:

df2 = df.drop(columns=["lit"])
print(df2.columns.tolist())   # ['name', 'math', 'avg', 'passed']
print(df.columns.tolist())    # vẫn còn 'lit' — df gốc không đổi

Dropt nhiều cột cùng lúc: df.drop(columns=["a", "b"]). Để drop hàng, dùng df.drop(index=[...]) hoặc df.drop(labels=[...], axis=0).

Use case AI thường gặp

  • Đọc CSV dataset (bài 35) thành DataFrame rồi df.head() để xem mẫu.
  • Tạo feature mới từ cột hiện có (df["bmi"] = df["weight"] / df["height"]**2).
  • groupby để tổng hợp theo nhóm (bài 41) — ví dụ doanh thu theo tháng.
  • Train / val / test split: tách DataFrame theo train_test_split rồi mới chuyển sang ndarray cho sklearn / PyTorch.
11

Bài tập

  1. Tạo một Series gồm 5 giá trị (ví dụ dân số ước lượng theo triệu), index là tên 5 thành phố tuỳ chọn. In shape, dtype, index.
  2. Tạo một DataFrame chứa thông tin học sinh với 3 cột: name, age, score; ít nhất 4 hàng.
  3. Thêm cột passed với giá trị True nếu score >= 5, ngược lại False.
  4. In shape, columns, dtypes của DataFrame sau khi đã thêm cột passed.
  5. Đổi index của DataFrame sang cột name bằng set_index, rồi truy cập hàng của một học sinh bằng loc.
  6. Chuyển df (chỉ các cột số age, score) sang ndarray bằng to_numpy() và in .shape, .dtype của kết quả.
Đáp án
  1. import pandas as pd
    
    cities = pd.Series(
        [8.5, 9.2, 1.2, 1.7, 1.1],
        index=["Hanoi", "HCMC", "Da Nang", "Hai Phong", "Can Tho"],
        name="population_mil",
    )
    print(cities.shape)    # (5,)
    print(cities.dtype)    # float64
    print(cities.index)
  2. df = pd.DataFrame({
        "name":  ["Alice", "Bob", "Carol", "Dan"],
        "age":   [15, 16, 15, 17],
        "score": [8.0, 4.5, 6.0, 9.0],
    })
  3. df["passed"] = df["score"] >= 5
    #     name  age  score  passed
    # 0  Alice   15    8.0    True
    # 1    Bob   16    4.5   False
    # 2  Carol   15    6.0    True
    # 3    Dan   17    9.0    True
  4. print(df.shape)      # (4, 4)
    print(df.columns)    # Index(['name', 'age', 'score', 'passed'], dtype='object')
    print(df.dtypes)
    # name      object
    # age        int64
    # score    float64
    # passed      bool
  5. df2 = df.set_index("name")
    print(df2.loc["Alice"])
    # age         15
    # score      8.0
    # passed    True
  6. arr = df[["age", "score"]].to_numpy()
    print(arr.shape)   # (4, 2)
    print(arr.dtype)   # float64 — bị promote vì có cột float
12

Tổng kết và bài tiếp theo

  • Pandas là thư viện tabular data của Python, xây trên NumPy, thêm label index, mixed dtype, missing data, time series.
  • Series là 1D có label; DataFrame là 2D có row index và column labels. Mỗi cột của DataFrame là một Series.
  • Tạo từ list, dict of lists, list of dicts hoặc ndarray. Convention đặt tên cột: lowercase snake_case.
  • Attribute hay dùng của DataFrame: shape, columns, index, dtypes, size, values / to_numpy().
  • Truy cập theo label dùng loc, theo vị trí dùng iloc. Đổi index với set_index / reset_index.
  • Pandas cho preprocess / EDA, NumPy / tensor cho training — chuyển qua lại bằng to_numpy().

Bài 35 đi vào thao tác phổ biến nhất: đọc dữ liệu từ CSV, JSON, Excel thành DataFrame; các tham số quan trọng của pd.read_csv (separator, encoding, parse_dates, dtype); và ghi ngược ra file.