Mục lục
- Mục tiêu bài học
- Vì sao phải quan sát dữ liệu trước
head()vàtail()shape,columns,indexdtypes— kiểu dữ liệu từng cộtinfo()— tổng quan DataFramedescribe()— thống kê mô tảvalue_counts()cho cột categoricalnunique()vàunique()memory_usage()— đo RAMisna().sum()— đếm missingcorr()— correlation matrix- Quy trình EDA chuẩn 6 bước
- Use case AI/ML
- Code Python tổng hợp
- Bài tập
- Tóm tắt
Mục tiêu bài học
- Dùng
head,tail,shape,columns,index,dtypesđể xem nhanh cấu trúc DataFrame. - Dùng
info(),describe(),value_counts(),nunique(),memory_usage()cho bước EDA (Exploratory Data Analysis). - Phát hiện missing data bằng
isna().sum(), xem tương quan cột số bằngcorr(). - Ghi nhớ quy trình EDA 6 bước áp dụng cho mọi dataset mới.
Vì sao phải quan sát dữ liệu trước
EDA (Exploratory Data Analysis) là bước nằm giữa đọc file và train model. Bỏ qua bước này, bạn sẽ gặp các tình huống điển hình: feature lẽ ra là số nhưng đang ở kiểu object nên model train xong vẫn lỗi; cột target lệch lớp nghiêm trọng (95% lớp 0 - 5% lớp 1) nhưng model vẫn báo accuracy 95%; outlier ở max kéo lệch chuẩn hoá; missing 30% mà code vẫn chạy do silent fillna.
Pandas cung cấp một nhóm method "quan sát nhanh" — gọi xong là biết ngay cấu trúc, missing, outlier sơ bộ. Đây là 10 phút phải bỏ ra với mọi dataset trước khi viết bất kỳ dòng feature engineering nào.
head() và tail()
Hai method đơn giản nhất, gọi đầu tiên sau khi đọc file để xác nhận pandas parse đúng:
import pandas as pd
df = pd.read_csv("students.csv")
df.head() # 5 dòng đầu (mặc định)
df.head(10) # 10 dòng đầu
df.tail() # 5 dòng cuối
df.tail(3) # 3 dòng cuối
Use case chính:
- Kiểm tra
headercó đúng tên cột hay đang bị shift sang dòng dữ liệu. - Xem ngay format số / chuỗi / ngày có đọc đúng không.
tail()hữu ích khi file có dòng tổng (footer) màread_csvkhông tự bỏ.
shape, columns, index
df.shape # (rows, cols) — tuple
df.columns # Index(['id', 'name', 'score', ...])
df.index # RangeIndex(start=0, stop=N, step=1)
len(df) # số dòng — tương đương df.shape[0]
df.columns.tolist() # đổi sang list Python thuần
Ba thuộc tính này là attribute (không có dấu ngoặc), không phải method. Nắm rõ để không gõ nhầm df.shape() rồi gặp TypeError.
dtypes — kiểu dữ liệu từng cột
df.dtypes
# id int64
# name object
# score float64
# is_active bool
# joined datetime64[ns]
# dtype: object
Các dtype hay gặp:
int64,int32,int16,int8— số nguyên.float64,float32— số thực.object— gần như luôn là chuỗi Python (Pandas chưa convert sang dtype riêng); cũng có thể là cột mix nhiều kiểu.bool— true/false.datetime64[ns]— thời gian (sauparse_dateshoặcpd.to_datetime).category— chuỗi có ít giá trị unique (gender, country, status). Tốn ít RAM hơnobject5–20 lần.
Cảnh báo: nếu một cột số xuất hiện ở object là dấu hiệu có ký tự lạ trong file (khoảng trắng, dấu phẩy phân nhóm, ký hiệu tiền tệ). Cần clean rồi astype lại, nếu không các thống kê số sau đó sẽ bỏ qua cột này.
# Convert sang category để tiết kiệm RAM
df["country"] = df["country"].astype("category")
df["country"].dtype # CategoricalDtype(...)
info() — tổng quan DataFrame
df.info() in cùng lúc: số dòng, số cột, tên cột, non-null count, dtype, memory usage. Đây là method "1 dòng nắm cả DataFrame".
df.info()
# <class 'pandas.core.frame.DataFrame'>
# RangeIndex: 1000 entries, 0 to 999
# Data columns (total 4 columns):
# # Column Non-Null Count Dtype
# --- ------ -------------- -----
# 0 id 1000 non-null int64
# 1 name 1000 non-null object
# 2 score 987 non-null float64 ← 13 missing
# 3 country 1000 non-null object
# dtypes: float64(1), int64(1), object(2)
# memory usage: 31.4+ KB
Mẹo đọc nhanh: nhìn cột Non-Null Count. Cột nào nhỏ hơn tổng số rows tức là có missing — biết ngay phải xử lý cột đó. Cột memory usage có dấu + nghĩa là chưa tính sâu phần object/string; gọi df.info(memory_usage="deep") để có con số chính xác.
describe() — thống kê mô tả
Mặc định describe() chỉ tính cho cột số: count, mean, std, min, percentile 25/50/75, max.
df.describe()
# score
# count 987.000000
# mean 7.412345
# std 1.205000
# min 2.000000
# 25% 6.500000
# 50% 7.500000
# 75% 8.400000
# max 10.000000
Với cột categorical, truyền tham số include:
df.describe(include="object")
# name country
# count 1000 1000
# unique 812 5
# top An VN
# freq 7 612
df.describe(include="all") # cả số và categorical chung 1 bảng
Đọc nhanh: min/max giúp phát hiện outlier (score = 250 trong khi thang điểm 10), std rất lớn so với mean báo hiệu phân tán rộng, freq sát count báo hiệu cột categorical gần như chỉ có 1 giá trị.
value_counts() cho cột categorical
Method bắt buộc thuộc cho mọi cột phân loại. Trả về Series sắp xếp giảm dần theo count:
df["country"].value_counts()
# VN 612
# US 180
# JP 120
# KR 50
# CN 38
# Name: count, dtype: int64
df["country"].value_counts(normalize=True) # tỉ lệ thay vì count
# VN 0.612
# US 0.180
# ...
df["country"].value_counts(dropna=False) # đếm cả NaN
Use case AI quan trọng nhất: kiểm tra cột target trong bài toán phân loại. Nếu lớp 0 chiếm 95%, mọi model "đoán 0" cũng đạt accuracy 95% — đó là class imbalance và sẽ ảnh hưởng tới chọn metric, kỹ thuật resampling.
y = df["churn"]
y.value_counts(normalize=True)
# 0 0.952
# 1 0.048 ← imbalance nặng, cần SMOTE / class_weight
nunique() và unique()
df["country"].nunique() # 5 — số giá trị khác nhau
df["country"].unique() # array(['VN', 'US', 'JP', 'KR', 'CN'])
df.nunique() # áp dụng cho mọi cột
# id 1000
# name 812
# score 47
# country 5
Quan sát nhanh:
nunique == len(df)với cộtid→ cột này là khoá unique, dùng làm index được.nuniquenhỏ (vài chục) trong khi dtype làobject→ chuyển sangcategoryđể tiết kiệm RAM.nunique == 1→ cột constant, vứt được, không mang thông tin cho model.
memory_usage() — đo RAM
df.memory_usage() # bytes mỗi cột (không sâu)
df.memory_usage(deep=True) # bao gồm cả bytes thật của string
# Tổng RAM tính bằng MB
total_mb = df.memory_usage(deep=True).sum() / 1e6
print(f"DataFrame RAM = {total_mb:.2f} MB")
Với cột object chứa chuỗi, deep=False chỉ đếm pointer (8 byte/dòng) — sai số rất lớn. Luôn dùng deep=True khi muốn biết RAM thực.
Use case: trước khi train model trên VRAM hạn chế, ép dtype hẹp (int32, float32, category) rồi gọi lại memory_usage(deep=True).sum() để xác nhận mức giảm. Việc này gắn liền với phần dtype optimization ở Bài 35.
isna().sum() — đếm missing
df.isna() # DataFrame bool cùng shape
df.isna().sum() # số missing mỗi cột
df.isna().sum().sum() # tổng missing toàn DataFrame
df.isna().mean() # tỉ lệ missing mỗi cột (0..1)
# Top 5 cột có nhiều missing nhất
df.isna().sum().sort_values(ascending=False).head(5)
Đây là preview. Chiến lược xử lý missing (dropna, fillna, mean/median/mode imputation, KNN imputer) sẽ deep ở Bài 40 trong nhóm này.
corr() — correlation matrix
df.corr(numeric_only=True)
# score age gpa
# score 1.00 0.12 0.78
# age 0.12 1.00 0.05
# gpa 0.78 0.05 1.00
Cho biết hệ số tương quan Pearson (mặc định) giữa các cột số. Giá trị trong khoảng [-1, 1]: gần 1/-1 là tương quan tuyến tính mạnh, gần 0 là không có tương quan tuyến tính.
Use case: feature selection sơ bộ — nếu 2 cột có |corr| > 0.95 thì gần như trùng lặp thông tin, có thể bỏ 1. Phân tích sâu (Pearson vs Spearman, partial correlation, multicollinearity) sẽ ở Series 2 (Machine Learning).
Quy trình EDA chuẩn 6 bước
Mỗi lần load 1 dataset mới, chạy y nguyên 6 bước này:
df.shape— biết kích thước (cảnh báo nếu file đọc thiếu / dư dòng).df.head()— xem 5 dòng đầu, check parse header đúng.df.info()— dtype, non-null, memory.df.describe(include="all")— stats số + categorical.df.isna().sum()— cột nào missing, mức độ bao nhiêu.df["target"].value_counts(normalize=True)(với bài toán classification) — phát hiện imbalance.
6 dòng code, < 30 giây chạy, nhưng tránh được rất nhiều bug phía sau pipeline.
Use case AI/ML
- Phát hiện missing bằng
info()+isna().sum()→ chọn strategydropna(nếu < 5% và random) hoặcfillnabằng mean/median/mode/KNN. - Phát hiện dtype sai bằng
dtypes→ cột số đang ởobjectphải clean ký tự vàastypelại. - Phát hiện outlier sơ bộ qua
min/maxcủadescribe()→ quyết định clip, winsorize, hay điều tra ghi nhận lỗi. - Phát hiện class imbalance qua
value_counts(normalize=True)cột target → chọn metric (F1, ROC-AUC thay vì accuracy), kỹ thuật resampling (SMOTE),class_weight. - Tối ưu RAM trước khi train trên GPU/máy yếu: xem
memory_usage(deep=True), épcategory/int32/float32. - Sanity check sau preprocessing: chạy lại 6 bước EDA sau khi clean để chắc chắn không vô tình tạo missing mới hoặc đổi nhầm dtype.
Code Python tổng hợp
Tạo DataFrame sinh viên có chủ đích lẫn missing + imbalance, chạy đầy đủ pipeline EDA:
import numpy as np
import pandas as pd
# 1) Tạo dữ liệu mẫu — có NaN, mixed dtype
rng = np.random.default_rng(42)
df = pd.DataFrame({
"id": range(1, 21),
"name": [f"SV{i:02d}" for i in range(1, 21)],
"score": rng.normal(7, 1.2, size=20).round(2),
"country": rng.choice(["VN", "US", "JP"], size=20, p=[0.7, 0.2, 0.1]),
"passed": rng.choice([0, 1], size=20, p=[0.15, 0.85]), # imbalance
"dob": pd.to_datetime("2003-01-01") + pd.to_timedelta(
rng.integers(0, 365, size=20), unit="D"),
})
# Cố tình thêm vài NaN ở score
df.loc[[3, 7, 15], "score"] = np.nan
# 2) Quy trình EDA 6 bước
print("shape:", df.shape)
print(df.head())
df.info()
print(df.describe(include="all"))
print("missing:")
print(df.isna().sum())
print("target distribution:")
print(df["passed"].value_counts(normalize=True))
# 3) Quan sát thêm
print("dtypes:\n", df.dtypes)
print("nunique:\n", df.nunique())
print("memory (MB):", df.memory_usage(deep=True).sum() / 1e6)
# 4) Convert country -> category, đo lại memory
df["country"] = df["country"].astype("category")
print("memory sau category (MB):",
df.memory_usage(deep=True).sum() / 1e6)
# 5) Correlation giữa các cột số
print(df.corr(numeric_only=True))
Chạy đoạn này trên notebook một lần là bạn đã có toàn bộ thông tin cần để quyết định bước preprocessing tiếp theo: xử lý 3 NaN ở score, encode country, chọn metric cho cột passed bị imbalance.
Bài tập
- Tạo 1 DataFrame 10 dòng với 4 cột mixed dtype (int, float, string, datetime). Chạy
df.info()vàdf.describe(include="all"), xác nhận các cột categorical xuất hiện ở output. - Cho 1 dataset bất kỳ (ví dụ Titanic tải từ URL ở Bài 35), in ra 5 cột có nhiều missing nhất kèm số missing và tỉ lệ missing.
na_summary = pd.concat([ df.isna().sum().rename("count"), df.isna().mean().rename("ratio"), ], axis=1).sort_values("count", ascending=False).head(5) print(na_summary) - Tính tổng memory (MB) của DataFrame Titanic bằng
memory_usage(deep=True). Convert cộtSexvàEmbarkedsangcategory, đo lại memory và in ra phần trăm giảm. - (Mở rộng) Viết hàm
quick_eda(df)nhận DataFrame, in lần lượt 6 bước EDA chuẩn. Tham số tuỳ chọntarget=None; nếu có target thì in thêmvalue_counts(normalize=True).
Tóm tắt
head()/tail()— xem nhanh dòng đầu/cuối, dùng để check parse header đúng.shape,columns,index— attribute, không phải method.dtypes— phát hiện cột số đang ởobject(lỗi parse) và cột nên convert sangcategory.info()— 1 lệnh nắm shape, dtype, non-null, memory.describe()— stats số mặc định; thêminclude="object"hoặcinclude="all"cho categorical.value_counts(normalize=True, dropna=False)— đếm tần suất, phát hiện class imbalance.nunique()/unique()— đánh giá cardinality, chọn cột cho category, vứt cột constant.memory_usage(deep=True)— đo RAM thực, dẫn đường cho dtype optimization.isna().sum()— preview missing; chiến lược xử lý ở Bài 40.corr()— tương quan tuyến tính giữa cột số, dùng feature selection sơ bộ.- Quy trình EDA chuẩn 6 bước:
shape→head→info→describe→isna→value_counts(target).
- Pandas Docs - DataFrame.head
- Pandas Docs - DataFrame.tail
- Pandas Docs - DataFrame.info
- Pandas Docs - DataFrame.describe
- Pandas Docs - DataFrame.dtypes
- Pandas Docs - Series.value_counts
- Pandas Docs - DataFrame.nunique
- Pandas Docs - DataFrame.memory_usage
- Pandas Docs - DataFrame.isna
- Pandas Docs - DataFrame.corr
- Pandas User Guide - dtypes
- Pandas User Guide - Categorical data
