Chuyển tới nội dung chính

Từ Dữ Liệu Thô Đến Thông Tin Hữu Ích – Khóa Học Phân Tích Dữ Liệu Thực Tế

· 10 phút để đọc

Từ Dữ Liệu Thô Đến Thông Tin Hữu Ích – Khóa Học Phân Tích Dữ Liệu Thực Tế

Bạn có biết rằng trong một hệ thống quản lý, có hàng nghìn bản ghi dữ liệu nhưng không biết cách kết nối chúng?

Ví dụ thực tế: Trong hệ thống PCCC, chúng ta có:

  • 702 thiết bị với địa chỉ lắp đặt
  • 221 cơ sở cần tọa độ địa lý
  • Làm sao để tự động match và cập nhật tọa độ?

Hình ảnh: Kết quả phân tích dữ liệu hiển thị trên bản đồ với các marker cho cơ sở và thiết bị

Khóa học Phân Tích Dữ Liệu sẽ dạy bạn: Kỹ thuật chuẩn hóa dữ liệu (normalize string) Fuzzy matching để tìm các bản ghi tương tự Tự động hóa quy trình cập nhật dữ liệu Xử lý dữ liệu lớn với Python và PostgreSQL

Hệ thống Phòng Cháy Chữa Cháy (PCCC)​

https://www.firstresponsecpr.com/wp-content/uploads/2023/03/Home-Fire-Safety-Tools-Checklist.png

https://upload.wikimedia.org/wikipedia/commons/8/8c/Map_Matching_Example_with_GraphHopper.png

https://www.pottersignal.com/product/images/PFC-6006.png

Trong 1 hệ thống quản lý PCCC điển hình, chúng ta có:

  • 702 thiết bị đã lắp đặt nhưng địa chỉ ghi không đồng nhất
  • 221 cơ sở chưa có tọa độ địa lý chính xác
  • Nhu cầu: Tự động đối chiếu địa chỉ lắp đặt và cập nhật tọa độ GPS mà không phải nhập tay

Câu hỏi đặt ra:

Làm sao để bot hoặc hệ thống có thể tự match địa chỉ và cập nhật tọa độ một cách tự động?


Fin3B và tư duy xử lý dữ liệu trong thực tế​

Đây chính là điểm mà các hệ thống thông minh như Fin3B trong mảng bot/trading hoặc monitoring đều phải đi qua: biến dữ liệu thô thành tín hiệu hoặc thông tin có thể hành động được.

Để làm được điều đó, trader hay kỹ sư dữ liệu cần làm chủ 3 khối:

  1. Thu thập dữ liệu thô (Raw Data Block)
  2. Chuẩn hóa và phân tích (Logic/AI Block)
  3. Giám sát và cập nhật liên tục (Monitoring Block)

Khóa học Phân Tích Dữ Liệu sẽ dạy bạn những gì?​

https://www.datameer.com/wp-content/uploads/2023/01/1.png

https://www.researchgate.net/publication/377386507/figure/fig1/AS%3A11431281217409492%401705204317835/Example-of-Fuzzy-String-Matching-GPM-Finding-the-similarity-between-the-terms-S1.png

https://www.altexsoft.com/static/blog-post/2023/11/a89125bc-af42-4bad-8cd6-256a680467c6.jpg

Trong khóa học, bạn sẽ thực hành trực tiếp các kỹ thuật quan trọng:

1. Chuẩn hóa dữ liệu​

  • Normalize chuỗi (loại bỏ ký tự thừa, viết hoa/viết thường, dấu câu sai lệch)

2. Fuzzy Matching​

  • Dùng thuật toán đo độ tương đồng chuỗi để tìm các bản ghi gần giống nhau
  • Nhận diện trùng lặp dữ liệu dù không khớp 100%

3. Tự động hóa quy trình cập nhật​

  • Xây dựng workflow tự đối chiếu – cập nhật mà không cần thao tác thủ công
  • Ứng dụng Python, n8n automation hoặc database triggers

4. Xử lý dữ liệu lớn​

  • Làm việc với tập dữ liệu hàng nghìn bản ghi
  • Dùng Python và PostgreSQL/MySQL để xử lý, lọc và tối ưu

Đi sâu: kỹ thuật match dữ liệu địa chỉ​

Bài toán "đối chiếu 702 thiết bị với 221 cơ sở" nghe có vẻ đơn giản, nhưng thực tế địa chỉ ghi trong hệ thống luôn khác nhau:

Cơ sở trong hệ thốngĐịa chỉ thiết bị ghiVấn đề
Công ty TNHH ABCCty TNHH ABCViết tắt
123 Nguyễn Trãi, P.5, Q.1123 Nguyễn Trãi Phường 5 Quận 1Dấu câu
Số 45 Lê Lợi45 Lê LợiTừ "Số"
KCN Tân Bình, Lô C3KCN Tan Binh Lo C3Mất dấu
Trường THCS Nguyễn DuTrường Trung học cơ sở Nguyễn DuViết đầy đủ

Không có thuật toán nào so khớp chính xác được những trường hợp này. Đó là lý do cần chuẩn hoá + fuzzy matching.

Bước 1 — Chuẩn hoá chuỗi (normalization)​

import re
import unicodedata
from functools import lru_cache

TU_VIET_TAT = {
"cty": "cong ty", "tnhh": "trach nhiem huu han",
"cp": "co phan", "tp": "thanh pho", "q": "quan",
"p": "phuong", "p.": "phuong", "qh": "quan huyen",
"tt": "thi tran", "xa": "xa", "phong": "phuong",
"kcn": "khu cong nghiep", "kcx": "khu che xuat",
"thcs": "trung hoc co so", "thpt": "trung hoc pho thong",
"so": "", "số": "",
}


@lru_cache(maxsize=10_000)
def chuan_hoa(dia_chi: str) -> str:
"""Chuẩn hoá địa chỉ thành dạng so sánh được."""
if not dia_chi:
return ""

# 1. Chuẩn hoá Unicode (quan trọng: NFC vs NFD gây lỗi so khớp)
s = unicodedata.normalize("NFC", str(dia_chi)).lower().strip()

# 2. Bỏ dấu tiếng Việt
s = "".join(
c for c in unicodedata.normalize("NFD", s)
if unicodedata.category(c) != "Mn"
).replace("đ", "d")

# 3. Bỏ mọi dấu câu, chuẩn hoá khoảng trắng
s = re.sub(r"[^a-z0-9\s/]", " ", s)
s = re.sub(r"\s+", " ", s).strip()

# 4. Mở rộng từ viết tắt (so khớp theo từ, không theo chuỗi con)
tu = s.split()
tu = [TU_VIET_TAT.get(t, t) for t in tu]
return " ".join(t for t in tu if t)

Chi tiết dễ bị bỏ qua nhưng gây lỗi: unicodedata.normalize("NFC", ...). macOS lưu tên file ở dạng NFD, Windows ở dạng NFC. Hai chuỗi nhìn giống hệt nhau có thể khác nhau ở mức byte. Nếu bạn không chuẩn hoá, việc so sánh sẽ thất bại một cách khó hiểu.

Bước 2 — Fuzzy matching​

from difflib import SequenceMatcher
from collections import defaultdict


def do_tuong_dong(a: str, b: str) -> float:
"""Độ tương đồng 0..1 giữa hai chuỗi đã chuẩn hoá."""
return SequenceMatcher(None, a, b).ratio()


def token_set_ratio(a: str, b: str) -> float:
"""So khớp theo tập từ — bỏ qua thứ tự từ và từ lặp.

Tốt hơn SequenceMatcher khi thứ tự từ khác nhau:
'nguyen trai quan 1' vs 'quan 1 nguyen trai' → 1.0
"""
ta, tb = set(a.split()), set(b.split())
if not ta or not tb:
return 0.0
giao = len(ta & tb)
return giao / max(len(ta), len(tb))


def diem_ket_hop(a: str, b: str, trong_so: float = 0.6) -> float:
"""Kết hợp hai thước đo."""
return trong_so * token_set_ratio(a, b) + (1 - trong_so) * do_tuong_dong(a, b)

Hai thước đo cho hai mục đích khác nhau:

Thước đoMạnh ởYếu ở
SequenceMatcherSai chính tả nhỏ, tên viết lệch ký tựThứ tự từ khác nhau
token_set_ratioThứ tự từ khác, có từ thừaSai chính tả
Kết hợpCả hai trường hợp—

Bước 3 — Đối chiếu và cập nhật​

import pandas as pd
from typing import NamedTuple


class KetQuaMatch(NamedTuple):
ma_thiet_bi: str
ma_co_so: str | None
diem: float
can_kiem_tra: bool
lat: float | None
lon: float | None


NGUONG_TU_DONG = 0.90 # >= ngưỡng này: tự động gán
NGUONG_NGHI_NGO = 0.75 # giữa hai ngưỡng: cần người kiểm tra


def doi_chieu(thiet_bi: pd.DataFrame, co_so: pd.DataFrame) -> pd.DataFrame:
"""Đối chiếu từng thiết bị với cơ sở phù hợp nhất."""
# Chuẩn hoá một lần cho toàn bộ (không chuẩn hoá lại trong vòng lặp)
thiet_bi = thiet_bi.copy()
co_so = co_so.copy()
thiet_bi["chuan_hoa"] = thiet_bi["dia_chi"].map(chuan_hoa)
co_so["chuan_hoa"] = co_so["dia_chi"].map(chuan_hoa)

ket_qua = []
for _, tb in thiet_bi.iterrows():
diem_tot_nhat, co_so_tot_nhat = 0.0, None

for _, cs in co_so.iterrows():
d = diem_ket_hop(tb["chuan_hoa"], cs["chuan_hoa"])
if d > diem_tot_nhat:
diem_tot_nhat, co_so_tot_nhat = d, cs

if diem_tot_nhat >= NGUONG_TU_DONG:
ket_qua.append(KetQuaMatch(
tb["ma_thiet_bi"], co_so_tot_nhat["ma_co_so"], diem_tot_nhat,
False, co_so_tot_nhat["lat"], co_so_tot_nhat["lon"],
))
elif diem_tot_nhat >= NGUONG_NGHI_NGO:
ket_qua.append(KetQuaMatch(
tb["ma_thiet_bi"], co_so_tot_nhat["ma_co_so"], diem_tot_nhat,
True, co_so_tot_nhat["lat"], co_so_tot_nhat["lon"],
))
else:
ket_qua.append(KetQuaMatch(tb["ma_thiet_bi"], None, diem_tot_nhat, True, None, None))

return pd.DataFrame(ket_qua)

⚠️ Nguyên tắc quan trọng nhất trong bài toán này: không bao giờ tự động gán khi điểm nằm giữa hai ngưỡng. Việc đưa vào danh sách "cần người kiểm tra" tốn ít thời gian hơn rất nhiều so với việc sửa một tọa độ GPS sai đã bị ghi vào hệ thống. Thiết kế hệ thống phải thừa nhận giới hạn của thuật toán, không giả vờ nó luôn đúng.

Bước 4 — Geocoding: từ địa chỉ suy ra tọa độ​

Khi đã match được cơ sở, tọa độ có thể đến từ ba nguồn:

NguồnĐộ chính xácChi phíGhi chú
Tọa độ có sẵn trong hệ thốngCao nhất0Ưu tiên dùng
Geocoding API (Google, Mapbox)CaoTheo requestCần API key, có giới hạn
Nominatim (OpenStreetMap)Trung bìnhMiễn phíGiới hạn 1 request/giây, phải có User-Agent
import time
import requests


def geocode_nominatim(dia_chi: str, cache: dict) -> tuple[float, float] | None:
"""Tra tọa độ qua Nominatim. BẮT BUỘC: tôn trọng rate limit."""
khoa = chuan_hoa(dia_chi)
if khoa in cache:
return cache[khoa]

try:
r = requests.get(
"https://nominatim.openstreetmap.org/search",
params={"q": dia_chi, "format": "json", "limit": 1, "countrycodes": "vn"},
headers={"User-Agent": "NoiBo-PCCC-Matching/1.0 (lien-he@congty.vn)"},
timeout=10,
)
r.raise_for_status()
du_lieu = r.json()

if not du_lieu:
cache[khoa] = None
return None

ket_qua = (float(du_lieu[0]["lat"]), float(du_lieu[0]["lon"]))
cache[khoa] = ket_qua
return ket_qua

except (requests.RequestException, ValueError, KeyError, IndexError):
cache[khoa] = None
return None
finally:
time.sleep(1.1) # TÔN TRỌNG rate limit 1 req/s

⚠️ Nominatim là dịch vụ miễn phí do cộng đồng OpenStreetMap tài trợ. Vi phạm giới hạn 1 request/giây có thể khiến IP của bạn bị chặn. Ngoài ra, dùng khối lượng lớn cho mục đích thương mại vi phạm điều khoản sử dụng — hãy dùng Google/Mapbox có trả phí trong trường hợp đó.

Kiểm tra kết quả — bước cuối không được bỏ qua​

def kiem_tra_toa_do(lat: float, lon: float) -> bool:
"""Kiểm tra tọa độ có nằm trong lãnh thổ Việt Nam không."""
return bool(lat and lon and 8.0 <= lat <= 23.5 and 102.0 <= lon <= 110.0)


def bao_cao_chat_luong(kq: pd.DataFrame) -> None:
tong = len(kq)
tu_dong = (~kq["can_kiem_tra"]).sum()
co_toa_do = kq["lat"].notna().sum()

print(f"Tổng thiết bị: {tong}")
print(f"Tự động gán được: {tu_dong} ({tu_dong / tong:.1%})")
print(f"Cần kiểm tra tay: {tong - tu_dong} ({(tong - tu_dong) / tong:.1%})")
print(f"Có tọa độ hợp lệ: {co_toa_do} ({co_toa_do / tong:.1%})")
print(f"Điểm trung bình: {kq['diem'].mean():.3f}")
print(f" - Trên 0.90: {(kq['diem'] >= 0.90).sum()}")
print(f" - 0.75 đến 0.90: {((kq['diem'] >= 0.75) & (kq['diem'] < 0.90)).sum()}")
print(f" - Dưới 0.75: {(kq['diem'] < 0.75).sum()}")

Điều cần kỳ vọng: với dữ liệu thực tế chưa được làm sạch, tỷ lệ tự động gán đúng thường chỉ đạt 60–80%. Con số 20–40% còn lại cần người kiểm tra. Đây là kết quả bình thường — đừng cố ép thuật toán đạt 100% bằng cách hạ ngưỡng, vì bạn sẽ đưa dữ liệu sai vào hệ thống.

Năm sai lầm phổ biến​

  1. Hạ ngưỡng để đạt tỷ lệ tự động cao — đổi lấy dữ liệu sai trong hệ thống.
  2. Không chuẩn hoá Unicode — lỗi so khớp khó hiểu giữa các máy khác hệ điều hành.
  3. Không cache kết quả geocoding — chạy lại tốn thời gian và có thể vượt rate limit.
  4. Dùng thuật toán O(n×m) với dữ liệu lớn — 702 × 221 = 155.000 phép so sánh, chấp nhận được; nhưng 100.000 × 50.000 là bất khả thi.
  5. Không kiểm tra tọa độ sau khi lấy — geocoding có thể trả về địa điểm cùng tên ở tỉnh khác.

💡 Khi dữ liệu lớn hơn nhiều, hãy dùng blocking: chia dữ liệu thành nhóm theo tiêu chí chắc chắn (quận/huyện, mã bưu chính) rồi chỉ so sánh trong từng nhóm. Điều này giảm số phép so sánh từ O(n×m) xuống gần O(n+m). Với 100.000 bản ghi, đây là khác biệt giữa vài giây và vài giờ.