Danh sách bài viết

Bài 35: Đọc CSV / JSON / Excel với Pandas

Đọc dữ liệu từ CSV, JSON, Excel vào DataFrame với pd.read_csv / read_json / read_excel. Các tham số quan trọng (sep, header, dtype, parse_dates, encoding, na_values), đọc từ URL, ghi file, encoding pitfall, chunksize cho file lớn và dtype optimization.

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

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

  • Đọc CSV, JSON, Excel vào DataFrame với pd.read_csv, pd.read_json, pd.read_excel.
  • Nắm các tham số hay dùng: sep, header, names, index_col, usecols, dtype, parse_dates, encoding, na_values, skiprows, nrows.
  • Ghi DataFrame ra file (to_csv, to_json, to_excel, to_parquet).
  • Biết encoding pitfall, đọc file lớn bằng chunksize, tối ưu dtype.
2

Vì sao bước đọc dữ liệu quan trọng

Mọi project data — từ EDA đến train model ML — đều bắt đầu bằng một bước: đưa dữ liệu từ file (CSV / JSON / Excel / Parquet) vào Python dưới dạng DataFrame. Đọc sai (sai encoding, sai dtype, không parse được ngày, mất cột) thường khiến code phía sau debug rất khó vì lỗi nằm ở giai đoạn loading chứ không phải logic.

Pandas cung cấp họ hàm pd.read_* rất giàu tham số. Bài này tập trung 3 format phổ biến nhất với AI Engineer: CSV (Kaggle dataset, log export), JSON (API response), Excel (báo cáo doanh nghiệp).

3

CSV — pd.read_csv

CSV (Comma-Separated Values) là format text đơn giản nhất, mỗi dòng là 1 record, các cột phân cách bằng dấu phẩy.

import pandas as pd

# Đọc file CSV cơ bản
df = pd.read_csv("data/sales.csv")
print(df.shape)   # (rows, cols)
print(df.head())  # 5 dòng đầu

Mặc định: separator ,, dòng đầu là header, index tự sinh RangeIndex(0..n-1), encoding utf-8.

4

Các tham số quan trọng của read_csv

Bảng tham số hay dùng nhất:

  • sep="," — separator. File .tsv dùng sep="\t"; export từ Excel EU dùng sep=";".
  • header=0 — dòng dùng làm header (mặc định dòng đầu). Truyền header=None nếu file không có header.
  • names=["a","b","c"] — tự đặt tên cột (dùng kèm header=None khi file không có header).
  • index_col=0 hoặc index_col="id" — chọn cột làm index thay vì sinh RangeIndex.
  • usecols=["name","price"] — chỉ đọc các cột cần, tiết kiệm RAM với file rộng.
  • dtype={"id": "int32", "price": "float32"} — ép kiểu khi đọc, tránh pandas tự đoán sai.
  • parse_dates=["created_at"] — parse cột thành datetime64[ns] luôn lúc load.
  • encoding="utf-8" — mặc định utf-8. File Việt từ Excel hay cần utf-8-sig hoặc cp1252.
  • na_values=["NA", "?", "-"] — coi các chuỗi này là NaN.
  • skiprows=3 — bỏ 3 dòng đầu (file có metadata text trước header).
  • nrows=1000 — chỉ đọc 1000 dòng đầu, hữu ích để preview nhanh file lớn.
import pandas as pd

df = pd.read_csv(
    "data/sales.csv",
    sep=";",                       # file EU dùng dấu chấm phẩy
    header=0,
    usecols=["order_id", "amount", "created_at"],
    dtype={"order_id": "int32", "amount": "float32"},
    parse_dates=["created_at"],    # đổi thành datetime ngay khi đọc
    na_values=["NA", "?", ""],
    encoding="utf-8-sig",
    nrows=5000,
)
print(df.dtypes)

Lưu ý: parse_dates chỉ parse khi format tương đối chuẩn (ISO 8601). Format lạ thì đọc xong gọi pd.to_datetime(df["col"], format="%d/%m/%Y") riêng.

5

JSON — pd.read_jsonjson_normalize

JSON dùng nhiều khi nhận response từ REST API. Pandas hỗ trợ qua read_json:

import pandas as pd

# JSON dạng list of records — phổ biến nhất
df = pd.read_json("data/users.json", orient="records")

Tham số orient cho biết JSON ở dạng nào: "records" (list of dicts), "split" ({columns, index, data}), "index" ({index: row}), "columns" ({column: values}), "values" (mảng 2D). Mặc định pandas tự đoán; nếu lỗi thì chỉ định rõ.

Với JSON nested (object lồng object), read_json sẽ để nguyên dict trong cell — không tiện cho phân tích. Dùng pd.json_normalize để flatten:

import json
import pandas as pd

data = [
    {"id": 1, "name": "An",  "addr": {"city": "HN", "zip": "100000"}},
    {"id": 2, "name": "Binh","addr": {"city": "HCM","zip": "700000"}},
]

df = pd.json_normalize(data, sep="_")
print(df.columns.tolist())
# ['id', 'name', 'addr_city', 'addr_zip']

json_normalize còn nhận record_path để khai báo mảng con cần explode, và meta để giữ trường cha — tiện cho response API thực tế.

6

Excel — pd.read_excel

Excel cần package phụ trợ. Với .xlsx cần openpyxl; với .xls cũ cần xlrd.

pip install openpyxl
import pandas as pd

# Đọc 1 sheet cụ thể (theo tên hoặc index)
df = pd.read_excel("report.xlsx", sheet_name="Sales")
df0 = pd.read_excel("report.xlsx", sheet_name=0)

# Đọc TẤT CẢ sheet → dict {sheet_name: DataFrame}
all_sheets = pd.read_excel("report.xlsx", sheet_name=None)
print(list(all_sheets.keys()))

# Bỏ qua header rườm rà (logo, meta) ở 3 dòng đầu
df2 = pd.read_excel(
    "report.xlsx",
    sheet_name="Q1",
    skiprows=3,
    usecols="B:F",          # Excel column letters
    header=0,
)

Các tham số skiprows, usecols, header, dtype, parse_dates dùng tương tự CSV. Riêng usecols của Excel chấp nhận thêm dạng letter range ("B:F") hoặc list letter (["A","C","E"]).

7

Đọc trực tiếp từ URL

read_csv / read_json nhận trực tiếp URL — pandas sẽ tự download. Tiện cho dataset công khai trên GitHub, Kaggle public link, S3 URL.

import pandas as pd

# Titanic dataset trên GitHub
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
df = pd.read_csv(url)

print(df.shape)
print(df.columns.tolist())

Với file lớn nên download về local 1 lần rồi đọc, tránh request lại mỗi lần chạy notebook.

8

Ghi file — to_csv / to_json / to_excel

import pandas as pd

df = pd.DataFrame({
    "name":  ["An", "Binh", "Cuong"],
    "score": [8.5, 7.0, 9.2],
})

# CSV — index=False để không ghi cột RangeIndex thừa
df.to_csv("out.csv", index=False, encoding="utf-8")

# JSON — orient="records" + indent cho dễ đọc
df.to_json("out.json", orient="records", indent=2, force_ascii=False)

# Excel — chọn sheet_name, cần openpyxl
df.to_excel("out.xlsx", index=False, sheet_name="Data")

Điểm cần nhớ:

  • index=False gần như luôn cần khi ghi CSV / Excel — nếu không sẽ thừa cột Unnamed: 0 mỗi lần đọc lại.
  • force_ascii=False để JSON giữ tiếng Việt thay vì escape ra á....
  • Muốn ghi nhiều sheet vào cùng 1 file Excel dùng pd.ExcelWriter.
import pandas as pd

df1 = pd.DataFrame({"x": [1, 2, 3]})
df2 = pd.DataFrame({"y": [4, 5, 6]})

with pd.ExcelWriter("multi.xlsx", engine="openpyxl") as writer:
    df1.to_excel(writer, sheet_name="A", index=False)
    df2.to_excel(writer, sheet_name="B", index=False)
9

Encoding pitfall

Lỗi UnicodeDecodeError khi đọc CSV là chuyện thường gặp với file Việt:

  • utf-8 — chuẩn hiện đại, mặc định của pandas.
  • utf-8-sig — UTF-8 có BOM (Excel "Save As CSV UTF-8" hay sinh ra). Đọc bằng utf-8 thường mã ký tự đầu lệch.
  • cp1252 / windows-1252 — file Excel cũ từ Windows non-Unicode.
  • latin-1 — fallback khi không rõ encoding (đọc được mọi byte nhưng có thể sai chữ).
import pandas as pd

# Thử lần lượt encoding hay gặp khi đọc file Excel export
for enc in ("utf-8", "utf-8-sig", "cp1252", "latin-1"):
    try:
        df = pd.read_csv("vietnamese.csv", encoding=enc)
        print(f"OK với encoding={enc}")
        break
    except UnicodeDecodeError:
        continue

Khi ghi file để Excel mở được tiếng Việt: df.to_csv("out.csv", index=False, encoding="utf-8-sig").

10

Memory — chunksize cho file lớn

File CSV vài GB không load nguyên vào RAM được. Dùng chunksize để stream theo block:

import pandas as pd

total_amount = 0.0
# Mỗi chunk là 1 DataFrame 100k dòng
for chunk in pd.read_csv("big.csv", chunksize=100_000, usecols=["amount"]):
    total_amount += chunk["amount"].sum()

print(f"Total: {total_amount:,.2f}")

Khi vẫn không đủ, cân nhắc:

  • Pandas 2.x + PyArrow backend: pd.read_csv(path, engine="pyarrow", dtype_backend="pyarrow") — nhanh hơn, tiết kiệm RAM hơn so với NumPy backend mặc định.
  • Polars: API gần Pandas, viết bằng Rust, multi-thread sẵn.
  • Dask: chia DataFrame lớn thành nhiều partition, xử lý lazy.
11

dtype optimization và Parquet

Pandas mặc định đoán int64 / float64 / object — tốn RAM. Ép dtype hẹp hơn khi biết miền giá trị:

import pandas as pd
import numpy as np

df = pd.read_csv(
    "logs.csv",
    dtype={
        "user_id":   "int32",     # đủ chứa < 2 tỉ
        "device_id": "int16",
        "score":     "float32",   # nửa RAM so với float64
        "country":   "category",  # cột ít giá trị unique → category cực gọn
    },
)
print(df.memory_usage(deep=True).sum() / 1e6, "MB")

Quy tắc nhanh:

  • Số nguyên nhỏ < 32k → int16; < 128 → int8.
  • Float ML thường float32 đủ chính xác.
  • Cột string với ít giá trị unique (gender, country, status) → category — RAM giảm 5–20x.

Parquet nên là format lưu trung gian thay cho CSV: columnar, có schema (giữ dtype), nén tốt, đọc nhanh hơn nhiều lần. Cần pyarrow.

pip install pyarrow
import pandas as pd

df = pd.read_csv("logs.csv")

# Ghi Parquet — giữ dtype, nén snappy mặc định
df.to_parquet("logs.parquet", index=False)

# Đọc lại nhanh hơn nhiều
df2 = pd.read_parquet("logs.parquet")
12

Use case AI

  • Train ML model: read_csv file Kaggle (Titanic, House Prices) rồi train_test_split trên DataFrame.
  • Đánh giá LLM: response từ API trả về JSON → json_normalize để có 1 row mỗi sample, cột là metric.
  • Báo cáo doanh nghiệp: Excel nhiều sheet với header phức tạp → read_excel(skiprows=, sheet_name=None) rồi concat.
  • Pipeline preprocessing: đọc CSV thô → clean → ghi to_parquet. Bước train sau đó read_parquet nhanh hơn ~5–10x.
13

Code Python tổng hợp

Tạo file CSV mẫu, đọc lại, đọc thêm 1 URL public, rồi ghi DataFrame sang JSON:

import pandas as pd

# 1) Tạo DataFrame mẫu rồi ghi ra CSV
students = pd.DataFrame({
    "id":    [1, 2, 3, 4, 5],
    "name":  ["An", "Binh", "Cuong", "Dung", "Hoa"],
    "score": [8.5, 7.0, 9.2, 6.5, 8.0],
    "dob":   ["2003-01-15", "2002-07-22", "2003-11-05",
              "2001-03-30", "2002-09-12"],
})
students.to_csv("students.csv", index=False)

# 2) Đọc lại CSV — parse cột dob thành datetime, dùng id làm index
df = pd.read_csv(
    "students.csv",
    index_col="id",
    parse_dates=["dob"],
    dtype={"score": "float32"},
)
print(df.dtypes)
print(df.head())

# 3) Đọc Titanic từ URL public
url = "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
titanic = pd.read_csv(url, nrows=10)   # preview 10 dòng
print(titanic[["Name", "Age", "Survived"]])

# 4) Ghi DataFrame ra JSON (orient="records", giữ tiếng Việt)
df.reset_index().to_json(
    "students.json",
    orient="records",
    indent=2,
    force_ascii=False,
    date_format="iso",
)
14

Bài tập

  1. Tạo file students.csv với 5 dòng (id, name, score, dob), đọc lại bằng read_csv và in ra df.dtypes, df.head().
  2. Đọc lại file students.csv với index_col="id"parse_dates=["dob"]; xác nhận index là iddf.dob.dtypedatetime64[ns].
  3. Tạo 2 DataFrame (scores, attendance), ghi cả hai vào cùng 1 file report.xlsx ở 2 sheet khác nhau bằng pd.ExcelWriter. Đọc lại với sheet_name=None để xác minh.
  4. (Mở rộng) Đọc 1 file CSV bất kỳ > 50MB, so sánh RAM trước/sau khi ép dtype hẹp + chuyển 1 cột string sang category (dùng df.memory_usage(deep=True).sum()).
15

Tóm tắt

  • pd.read_csv là cửa ngõ phổ biến nhất; nhớ sep, header, index_col, usecols, dtype, parse_dates, encoding, na_values, skiprows, nrows.
  • JSON nested → pd.json_normalize(data, sep="_") để có DataFrame phẳng.
  • Excel cần openpyxl; dùng sheet_name=None để lấy dict tất cả sheet, ExcelWriter để ghi nhiều sheet.
  • URL truyền thẳng vào read_csv được; với file lớn nên cache về local.
  • Encoding hay gặp: utf-8, utf-8-sig, cp1252. Ghi tiếng Việt cho Excel mở → utf-8-sig.
  • File lớn → chunksize, hoặc Pandas 2.x + PyArrow, Polars, Dask.
  • Ép dtype (int32, float32, category) và lưu trung gian bằng Parquet để pipeline đọc nhanh và đỡ tốn đĩa.