Không gian tự học dành cho sinh viên UET

Chào mừng đến với UETệ

Tìm bài giảng theo học phần, hiểu lại những kiến thức nền và ôn tập theo nhịp học của bạn.

Bài giảng
Đọc Notes, ôn nhanh với Slides và luyện tập qua các bài có lời giải.
Wiki kiến thức
Tra cứu thuật ngữ và bổ sung kiến thức nền ngay khi cần.
Góc học tập
Ôn bằng flashcard, lưu ghi chú và hẹn giờ tập trung.

Bạn có thể xem lại phần giới thiệu ở cuối trang.

Buy me a coffee

Website hoàn toàn miễn phí. Nếu thấy hữu ích, bạn có thể tặng tôi một cốc cà phê theo thông tin dưới đây nhé!

Mã QR chuyển khoản Momo
Ngân hàng
Momo
Tên người nhận
MOMO - TKTH DO MINH TRI
Số tài khoản
01MMTTT0060405229
Skip to content

Công việc xử lý dữ liệu và môi trường Python

Bài 1Cập nhật 18/10/2026Ghi chú của bạn

1. Công việc xử lý dữ liệu trong thực tế ​

1.1. Thực tế dữ liệu và các dạng dữ liệu phổ biến ​

Bạn nhận hai tệp: Danh sách đơn hàng và bảng thanh toán. Cả hai cùng ghi doanh thu, nhưng tổng tiền không khớp. Trước khi cộng cột, ta cần xác định một hàng đại diện cho điều gì và hai bảng liên kết bằng khóa nào.

Trong các bài tập mẫu, người học thường tiếp cận dữ liệu dưới dạng các bảng số liệu sạch sẽ, ngay ngắn và đồng nhất. Tuy nhiên, trong môi trường sản xuất thực tế, dữ liệu thô hiếm khi ở trạng thái sẵn sàng cho mô hình hóa. Dữ liệu thực tế thường đối mặt với ba vấn đề:

  • Phân mảnh và đa dạng nguồn: Một phần dữ liệu nằm trong nhật ký máy chủ bán cấu trúc, một phần nằm trong cơ sở dữ liệu quan hệ SQL và một phần được truyền qua API dưới dạng JSON lồng nhau. Sự đa dạng này đòi hỏi nhiều kỹ thuật kết nối khác nhau.
  • Nhiễu loạn và suy hao: Các trường số học bị lẫn ký tự tiền tệ hoặc đơn vị đo lường, ngày tháng bị xung đột giữa các quy ước quốc tế, và nhiều bản ghi bị trùng lặp do lỗi kết nối mạng khi người dùng gửi yêu cầu nhiều lần.
  • Xung đột định nghĩa nghiệp vụ: Cùng một chỉ số kinh doanh, hai phòng ban có thể áp dụng hai công thức tính toán khác nhau, dẫn tới những con số vênh nhau nếu không được chuẩn hóa ngay từ tầng dữ liệu thô.

Dữ liệu trong phân tích hiện đại thường phân bố ở bốn nhóm cấu trúc chính:

  1. Dữ liệu dạng bảng (Tabular data): Cấu trúc phổ biến gồm các hàng biểu diễn cho từng quan sát và các cột mang tên nhãn cụ thể, trong đó mỗi cột có thể chứa một kiểu dữ liệu riêng biệt.
  2. Dữ liệu chuỗi thời gian (Time series): Tập hợp các điểm dữ liệu được gắn nhãn thời gian cố định hoặc biến đổi, đòi hỏi các phép toán trượt cửa sổ, dịch chuyển thời gian và đổi tần suất lấy mẫu.
  3. Mảng nhiều chiều và ma trận số học: Cấu trúc số học đồng nhất nhiều chiều dùng trong xử lý hình ảnh, tín hiệu số, hoặc làm đầu vào cho các thuật toán học máy.
  4. Các bảng liên kết (Relational tables): Nhiều bảng dữ liệu độc lập nhưng liên kết với nhau thông qua các trường khóa chung, cần các phép ghép nối (join, merge) để tạo thành tập dữ liệu phân tích hoàn chỉnh.

1.2. Chu trình xử lý dữ liệu từ dữ liệu thô đến quyết định ​

Quá trình phân tích dữ liệu là một chuỗi biến đổi khép kín gồm các bước: Thu thập, làm sạch, chuyển dạng, tổng hợp, trực quan hóa và kiểm tra kết luận.

Trước khi mô hình hóa, ta cần kiểm tra dữ liệu có phù hợp với câu hỏi hay không. Một phép tính có thể chạy đúng nhưng trả lời sai câu hỏi nếu bảng bị lặp hàng, dùng nhầm đơn vị hoặc chọn khác tập quan sát. Vì vậy, làm sạch và kiểm chứng phải gắn với ý nghĩa của từng cột.

Mục tiêu của môn học là trang bị năng lực biến những tập dữ liệu thô hỗn loạn thành dữ liệu sạch, đáng tin cậy và có thể kiểm chứng được bằng mã nguồn minh bạch.


2. Vì sao chọn Python cho phân tích dữ liệu? ​

2.1. Python trong vai trò ngôn ngữ điều phối và kết nối ​

Python là một ngôn ngữ thông dịch (interpreted language). Tốc độ duyệt vòng lặp thuần túy của Python chậm hơn đáng kể so với các ngôn ngữ biên dịch như C hay C++. Dù vậy, Python được ứng dụng rộng rãi trong kỹ thuật dữ liệu nhờ khả năng đóng vai trò ngôn ngữ điều phối và kết nối các thành phần tính toán.

Ngăn xếp tính toán khoa học kết hợp hai ưu thế:

  1. Tầng tương tác bậc cao: Python cung cấp cú pháp gần gũi với tư duy toán học và ngôn ngữ tự nhiên, giúp người phân tích diễn đạt ý tưởng nhanh chóng mà không cần bận tâm về việc quản lý con trỏ hay cấp phát bộ nhớ thủ công.
  2. Tầng tính toán biên dịch: Bên dưới giao diện lập trình, các thư viện như NumPy và SciPy ủy thác các phép toán ma trận nặng cho các thư viện biên dịch sẵn bằng C, Fortran hoặc C++ (như BLAS, LAPACK khi có sẵn).

Khi thực hiện phép tính trên một mảng dữ liệu, Python đóng vai trò điều phối gửi chỉ thị xuống khối mã C đã được biên dịch tối ưu cho phần cứng CPU. Nhờ đó, chương trình vừa giữ được sự linh hoạt trong phát triển mã nguồn, vừa đạt tốc độ tính toán của mã nhị phân tối ưu.

2.2. Giải quyết bài toán hai ngôn ngữ (Two-Language Problem) ​

Trước khi Python phổ biến trong khoa học dữ liệu, các tổ chức nghiên cứu thường đối mặt với bài toán hai ngôn ngữ:

  • Giai đoạn nghiên cứu mẫu thử (prototype) được thực hiện trên một ngôn ngữ chuyên sâu về thống kê hoặc toán học (như R, MATLAB, SAS).
  • Khi đưa vào vận hành, một số dự án phải chuyển một phần hoặc cả chương trình sang công cụ khác để đáp ứng yêu cầu về hiệu năng hay tích hợp.

Quy trình hai bước này gây lãng phí nguồn lực kỹ thuật, dễ phát sinh lỗi chuyển đổi công thức và làm chậm tiến độ triển khai sản phẩm. Python thống nhất cả hai giai đoạn: Một nhà phân tích có thể xây dựng mô hình thử nghiệm trong sổ tay tương tác, và chính đoạn mã đó có thể được đóng gói trực tiếp vào dịch vụ vận hành thực tế.

2.3. Ranh giới và giới hạn của Python ​

Bên cạnh các ưu thế rõ rệt, người làm kỹ thuật dữ liệu cần nhận thức rõ những giới hạn của Python:

  • Khóa thông dịch toàn cục (Global Interpreter Lock, GIL): Trong bản dựng CPython có bật GIL, tại một thời điểm chỉ một luồng được thực thi mã Python trong cùng trình thông dịch. Điều này không buộc luồng luôn nằm trên một lõi CPU. Một số thư viện có thể nhả GIL khi tính toán, còn bản dựng free-threaded có cơ chế khác. Vì vậy phải xác định môi trường và loại công việc trước khi chọn luồng hay tiến trình.
  • Chi phí vòng lặp thông dịch: Việc viết vòng lặp for duyệt qua từng phần tử trên tập dữ liệu lớn bằng Python thuần sẽ gây suy giảm hiệu năng đáng kể. Nhiều phép biến đổi quy mô lớn cần được chuyển thành phép toán vector hóa của NumPy, pandas, hoặc xử lý theo khối đệm.
  • Giới hạn bộ nhớ RAM: Khi nạp cả bảng vào pandas, dữ liệu và các kết quả trung gian cần bộ nhớ. Nếu không đủ RAM, chương trình có thể chậm do phân trang hoặc không cấp phát được dữ liệu. Đọc theo khối, chọn cột, đổi kiểu phù hợp hay truy vấn tại nơi lưu dữ liệu là những cách cần cân nhắc trước khi mở rộng công cụ.

3. Ngăn xếp tính toán khoa học Python (Scientific Python Stack) ​

Hệ sinh thái xử lý dữ liệu hiện đại được xây dựng dựa trên ngăn xếp các thư viện chuyên biệt:

  1. NumPy (Numerical Python): Là nền móng của hệ sinh thái tính toán số học. NumPy cung cấp đối tượng mảng đa chiều đồng nhất ndarray, các hàm toán học vector hóa (ufuncs), cùng các công cụ đại số tuyến tính và sinh số ngẫu nhiên.
  2. pandas: Thư viện xử lý dữ liệu dạng bảng. pandas cung cấp hai cấu trúc dữ liệu chính là Series (mảng một chiều có nhãn) và DataFrame (bảng hai chiều có nhãn hàng và cột). pandas sở hữu bộ công cụ phong phú để nạp tệp, làm sạch dữ liệu khuyết thiếu, nối bảng, chuyển đổi hình dạng và tổng hợp theo nhóm. Ngoài vùng đệm NumPy truyền thống, pandas hiện đại còn hỗ trợ các định dạng lưu trữ như PyArrow.
  3. Matplotlib & Seaborn: Bộ đôi trực quan hóa dữ liệu. Matplotlib kiểm soát chi tiết từng thành phần của trục tọa độ và hình vẽ, trong khi Seaborn cung cấp giao diện bậc cao để tạo các đồ thị thống kê trực quan.
  4. IPython & Jupyter: Cung cấp môi trường thực thi tương tác dòng lệnh và giao diện sổ tay nghiên cứu, hỗ trợ quy trình khám phá dữ liệu lặp đi lặp lại.
  5. SciPy: Mở rộng các thuật toán khoa học chuyên sâu dựa trên NumPy, bao gồm tối ưu hóa hàm số, tích phân số trị, xử lý tín hiệu và các phân phối xác suất thống kê.
  6. scikit-learn & statsmodels: Scikit-learn là bộ công cụ học máy phổ biến (phân loại, hồi quy, gom cụm), còn statsmodels tập trung vào các mô hình thống kê suy diễn cổ điển, kinh lượng học và kiểm định giả thuyết.

4. Kiến trúc Môi trường Tính toán: Kịch bản Python và Sổ tay Jupyter ​

4.1. Phân biệt tệp mã nguồn .py và sổ tay .ipynb ​

Người lập trình có thể thực thi cùng một biểu thức tính toán trong tệp kịch bản .py hoặc trong một ô của Jupyter Notebook. Điểm khác biệt cốt lõi nằm ở định dạng tệp và cách thức lưu trữ:

  • Tệp mã nguồn .py: Là tệp văn bản thuần (plain text). Khi thực thi bằng lệnh python chuong_trinh.py, trình thông dịch đọc tuần tự các câu lệnh từ đầu đến cuối tệp. Đầu ra in ra màn hình hoặc tệp nhật ký, hoàn toàn không được ghi ngược vào tệp mã nguồn. Tệp .py là định dạng chuẩn để xây dựng các mô-đun, thư viện và đường ống xử lý tự động chạy ngầm.
  • Sổ tay tính toán .ipynb: Thực chất là một tệp văn bản chứa dữ liệu có cấu trúc theo định dạng JSON (JavaScript Object Notation). Cấu trúc tệp bao gồm:
    • Trường cells: Danh sách các ô tính toán. Mỗi ô mã chứa nội dung mã (source), số thứ tự thực thi (execution_count), và toàn bộ kết quả đầu ra tương ứng (outputs). Đầu ra có thể là văn bản, bảng HTML, hoặc ảnh biểu đồ mã hóa Base64.
    • Trường metadata: Thông tin về môi trường kernel, phiên bản ngôn ngữ và cấu hình hiển thị.

Nhờ lưu trữ đồng thời cả mã lệnh lẫn kết quả đầu ra, tệp .ipynb cho phép người đọc xem trực tiếp biểu đồ và bảng kết quả mà không cần chạy lại toàn bộ mã nguồn.

4.2. Kiến trúc tương tác ba tầng của Jupyter ​

Khi bạn thao tác trong Jupyter Notebook trên trình duyệt web, mã lệnh không được thực thi trực tiếp trên trang web đó. Kiến trúc vận hành của Jupyter gồm ba thành phần tách biệt:

  1. Giao diện trình duyệt (Client): Tiếp nhận thao tác gõ mã của người dùng và hiển thị kết quả đầu ra.
  2. Máy chủ Jupyter (Jupyter Server): Ứng dụng web đóng vai trò trung gian, quản lý các tệp sổ tay trên đĩa và điều phối các phiên làm việc.
  3. Hạt nhân tính toán (IPython Kernel): Một tiến trình Python độc lập chạy ngầm trên hệ điều hành, chịu trách nhiệm thực thi các khối mã và duy trì trạng thái các biến trong bộ nhớ RAM.

Khi người dùng nhấn tổ hợp phím Shift + Enter, trình duyệt gửi mã qua giao thức WebSocket tới Jupyter Server, sau đó máy chủ chuyển tiếp mã tới IPython Kernel qua thư viện ZeroMQ. Kernel thực thi xong sẽ gửi trả kết quả về trình duyệt để vẽ giao diện.

4.3. Thứ tự thực thi In [ ], trạng thái RAM và bẫy biến ma ​

Cần phân biệt rạch ròi giữa tệp notebook lưu trên đĩa và trạng thái tính toán trong RAM:

  • Nhãn In [ ]: Ô chưa từng được thực thi kể từ khi khởi động kernel.
  • Nhãn In [*]: Ô đang trong trạng thái xử lý tính toán.
  • Nhãn In [n]: Số nguyên n phản ánh thứ tự thực thi của ô đó theo thời gian thực. Nhãn này không phản ánh vị trí hình học từ trên xuống dưới trên trang màn hình.

Mọi ô mã trong cùng một sổ tay chia sẻ chung một không gian tên toàn cục (global namespace) trong RAM. Nếu bạn chạy một ô mã gán x = 10, sau đó chạy một ô mã khác tăng x = x + 5 ba lần liên tiếp, giá trị của x trong RAM sẽ là 25, bất kể các ô mã đó nằm ở vị trí nào trên trang.

Cạm bẫy biến ma (Ghost Variable Trap) ​

Giả sử bạn tạo một ô mã khai báo biến tạm:

python
du_lieu_tam = [10, 20, 30]

Sau khi chạy ô mã này, biến du_lieu_tam đã nằm trong RAM của kernel. Bạn viết tiếp các ô mã bên dưới sử dụng biến này. Sau đó, bạn xóa ô khai báo du_lieu_tam khỏi giao diện notebook. Vì tiến trình kernel vẫn đang chạy, các ô bên dưới vẫn hoạt động bình thường trong phiên làm việc hiện tại của bạn.

Tuy nhiên, khi gửi tệp .ipynb cho đồng nghiệp hoặc chạy trên hệ thống kiểm tra tự động, kernel mới được tạo ra từ đầu. Khi chạy tuần tự, chương trình sẽ lập tức sập với lỗi:

text
NameError: name 'du_lieu_tam' is not defined

Kỷ luật vàng khi làm việc với Notebook

Trước khi chia sẻ notebook, hãy thử khởi động lại kernel và chạy các ô từ trên xuống. Việc này giúp phát hiện phụ thuộc vào trạng thái cũ trong phiên. Muốn chạy lại cả phân tích, còn cần cùng dữ liệu, cấu hình, thư viện và các điều kiện ngẫu nhiên đã được ghi nhận.


5. Quản trị môi trường làm việc với venv và pip ​

5.1. Cơ chế cách ly gói thư viện và phòng tránh xung đột ​

Khi cài đặt Python lên máy tính, hệ điều hành chỉ cung cấp một thư mục mặc định duy nhất để chứa các thư viện bên thứ ba, gọi là site-packages. Nếu người dùng cài đặt gói bằng lệnh pip install <ten-goi> mà không dùng môi trường ảo, các thư viện sẽ được chép đè vào thư mục dùng chung này.

Thiết kế dùng chung thư mục bộc lộ hai rủi ro:

  1. Xung đột phụ thuộc giữa các dự án (Dependency Collision): Dự án cũ của bạn cần pandas 1.5.3, trong khi dự án mới yêu cầu pandas 2.2.0. Việc cài đè phiên bản mới sẽ làm gãy mã nguồn của dự án cũ do nhiều hàm đã bị thay đổi cú pháp hoặc loại bỏ.
  2. Nguy cơ làm hỏng Python hệ thống: Trên các hệ điều hành như Linux (Ubuntu, Debian) và macOS, nhiều tiến trình quản trị hệ thống phụ thuộc trực tiếp vào phiên bản Python của hệ thống. Cài đặt bừa bãi với quyền quản trị viên (sudo pip install) có thể phá vỡ các công cụ quản lý mạng và gói phần mềm của hệ điều hành.

Môi trường ảo (virtual environment hay venv) giải quyết vấn đề này bằng cách tạo ra một thư mục biệt lập cho từng dự án, chứa:

  • Bản sao hoặc liên kết biểu tượng (symlink) trỏ tới tệp thực thi python.
  • Thư mục site-packages riêng biệt, hoàn toàn cách ly với hệ điều hành và các dự án khác.
  • Các kịch bản kích hoạt (activation scripts) để tạm thời điều chỉnh biến môi trường hệ thống PATH.

5.2. Quy trình làm việc với venv, requirements.txt và .python-version ​

Quy trình thiết lập môi trường cho một dự án dữ liệu gồm bốn bước:

bash
# Bước 1: Điều hướng vào thư mục dự án và khởi tạo môi trường ảo mang tên .venv
python -m venv .venv

# Bước 2: Kích hoạt môi trường ảo
# Trên hệ điều hành Windows PowerShell:
.venv\Scripts\Activate.ps1
# Trên hệ điều hành macOS / Linux:
source .venv/bin/activate

Kích hoạt môi trường thường thêm tiền tố (.venv) và ưu tiên đường dẫn chương trình của môi trường trong PATH. Hãy kiểm tra python -c "import sys; print(sys.executable)" và python -m pip --version để biết đúng trình thực thi đang dùng.

bash
# Bước 3: Cài đặt các gói thư viện cần thiết
pip install numpy pandas matplotlib

# Bước 4: Khóa danh sách phụ thuộc ra tệp cấu hình
pip freeze > requirements.txt

Tệp requirements.txt lưu trữ danh sách tên gói và phiên bản chính xác, ví dụ:

text
numpy==1.26.4
pandas==2.2.2
matplotlib==3.8.4

Một số công cụ như pyenv hoặc uv đọc .python-version để chọn phiên bản Python. Tệp này không tự điều khiển Python hay venv và không mặc nhiên có nghĩa là phiên bản tối thiểu. Khi nhận dự án, cần chọn trình thực thi phù hợp, tạo môi trường rồi cài các gói đã ghi nhận:

bash
pip install -r requirements.txt

Quy tắc quản lý phiên bản với Git

Thư mục .venv chứa nhiều tệp nhị phân và mã nguồn thư viện với dung lượng lớn. Không đưa thư mục .venv lên kho lưu trữ Git. Hãy thêm dòng .venv/ vào tệp .gitignore. Kho lưu trữ chỉ cần lưu mã nguồn của bạn và tệp requirements.txt.


5.3. Môi trường Conda ​

Conda là một lựa chọn khác để quản lý trình thực thi và các gói. Sau khi cài Miniconda hoặc một bản phân phối Conda phù hợp hệ điều hành, mở terminal được cấu hình cho Conda rồi tạo môi trường riêng:

bash
conda create -n study-data python numpy pandas matplotlib jupyterlab
conda activate study-data
python -c "import sys; print(sys.executable)"
conda env export --from-history > environment.yml

Trên Windows có thể dùng terminal đi kèm bản phân phối. Trên macOS và Linux, cần terminal đã được khởi tạo cho Conda. Hãy theo hướng dẫn cài đặt hiện hành của bản phân phối thay vì dùng một tên bộ cài cố định.

venv và Conda là hai cách tổ chức môi trường, không cần lồng chúng cho bài học này. Tệp môi trường ghi các yêu cầu đã chọn. Khi cần lặp lại chính xác một phân tích, còn phải ghi phiên bản và kiểm tra khả năng cài lại trên hệ thống đích.

6. Quy ước nhập thư viện và tổ chức mã nguồn ​

Cộng đồng phân tích dữ liệu đã thống nhất các quy ước viết tắt khi nhập các thư viện cốt lõi. Việc tuân thủ các bí danh chuẩn giúp mã nguồn dễ đọc, dễ bảo trì và tương thích với tài liệu kỹ thuật:

python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

Các quy tắc thực hành tốt cần duy trì:

  • Gom toàn bộ các câu lệnh nhập thư viện (import) lên đầu tệp mã nguồn hoặc ô đầu tiên của sổ tay tính toán.
  • Tránh sử dụng cú pháp from module import * vì cách viết này làm ô nhiễm không gian tên, gây xung đột hàm và khiến người đọc không thể truy vết nguồn gốc của các phương thức.
  • Khi viết mã trong sổ tay tương tác, chia nhỏ logic thành các ô độc lập, mỗi ô giải quyết một bước biến đổi cụ thể và có chú thích rõ ràng.

Tài liệu tham khảo ​

  • Wes McKinney. Python for Data Analysis: Data Wrangling with pandas, NumPy, and Jupyter, tái bản lần 3. O'Reilly Media, 2022. Chương 1: Preliminaries.
  • Project Jupyter Documentation. Jupyter and the Future of Interactive Computing. https://docs.jupyter.org/
  • Python Software Foundation. PEP 668 – Marking Python base environments as "externally managed". https://peps.python.org/pep-0668/

7. Bài tập thực hành ​

Bài tập 1: Theo dõi trạng thái kernel và thứ tự thực thi ô mã

Một kỹ sư dữ liệu khởi động một phiên làm việc với IPython Kernel hoàn toàn mới và nhập ba ô mã sau vào notebook:

  • Ô 1:
    python
    x = 10
  • Ô 2:
    python
    x = x - 3
  • Ô 3:
    python
    x = x * 2 + 5

Kỹ sư này thực hiện chạy các ô mã theo trình tự sau: Chạy Ô 1 một lần, sau đó chạy Ô 3 hai lần liên tiếp, và cuối cùng chạy Ô 2 một lần.

  1. Xác định giá trị cuối cùng của biến x trong RAM sau khi kết thúc chuỗi thao tác trên.
  2. Giả sử sau đó kỹ sư xóa hoàn toàn Ô 1 khỏi giao diện notebook và chạy lại Ô 2 thêm một lần nữa. Lệnh này có báo lỗi không, và giá trị mới của x là bao nhiêu?
  3. Nếu khởi động lại kernel hoàn toàn (Restart Kernel) và chỉ chạy duy nhất Ô 2, điều gì sẽ xảy ra?
Lời giải
  1. Lần theo từng bước thực thi trong không gian tên toàn cục:
    • Bước 1 (Ô 1): Khởi tạo biến x = 10.
    • Bước 2 (Ô 3 lần thứ nhất): Tính x = 10 * 2 + 5 = 25. Giá trị của x trong RAM cập nhật thành 25.
    • Bước 3 (Ô 3 lần thứ hai): Tính x = 25 * 2 + 5 = 55. Giá trị của x cập nhật thành 55.
    • Bước 4 (Ô 2): Tính x = 55 - 3 = 52. Kết quả cuối cùng trong RAM là 52.
  2. Xóa Ô 1 trên giao diện chỉ làm thay đổi văn bản của tệp tài liệu, hoàn toàn không xóa biến x đang thường trực trong tiến trình kernel. Khi chạy lại Ô 2, chương trình không báo lỗi và tiếp tục lấy giá trị hiện tại để tính toán: x = 52 - 3 = 49.
  3. Khi khởi động lại kernel, toàn bộ trạng thái trong RAM bị xóa sạch. Nếu chỉ bấm chạy Ô 2 mà chưa chạy Ô 1, biểu thức x - 3 cố gắng truy xuất biến x chưa từng tồn tại trong không gian tên mới, dẫn đến lỗi:
    text
    NameError: name 'x' is not defined
    Hiện tượng này minh họa cạm bẫy biến ma khi các ô mã phụ thuộc vào trạng thái ẩn của các phiên chạy trước.

Bài tập 2: Kiểm tra tính nhất quán của môi trường qua tập tin requirements.txt

Cho nội dung tệp kê khai phụ thuộc requirements.txt như sau:

text
pandas==2.2.0
numpy>=1.26.0
matplotlib==3.8.2
scipy

Hãy viết một hàm Python phan_tich_yeu_cau(dong_cau_hinh) nhận vào một chuỗi cấu hình và trả về một tuple gồm (ten_goi, toan_tu, phien_ban). Nếu gói không chỉ định phiên bản, toán tử và phiên bản nhận giá trị None. Áp dụng hàm trên cho từng dòng và in ra danh sách các gói có yêu cầu phiên bản cố định chính xác (==).

Lời giải
python
def phan_tich_yeu_cau(dong):
    dong = dong.strip()
    if not dong or dong.startswith("#"):
        return None
    for toan_tu in ["==", ">=", "<=", ">", "<"]:
        if toan_tu in dong:
            ten, phien_ban = dong.split(toan_tu, 1)
            return ten.strip(), toan_tu, phien_ban.strip()
    return dong.strip(), None, None

du_lieu_req = [
    "pandas==2.2.0",
    "numpy>=1.26.0",
    "matplotlib==3.8.2",
    "scipy"
]

ket_qua = [phan_tich_yeu_cau(dong) for dong in du_lieu_req]
cac_goi_co_dinh = [ten for ten, op, ver in ket_qua if op == "=="]

print("Kết quả phân tích:", ket_qua)
print("Các gói cố định phiên bản:", cac_goi_co_dinh)

Kết quả in ra:

  • ket_qua: [('pandas', '==', '2.2.0'), ('numpy', '>=', '1.26.0'), ('matplotlib', '==', '3.8.2'), ('scipy', None, None)].
  • cac_goi_co_dinh: ['pandas', 'matplotlib'].

Bài tập 3: Đo lường tỷ lệ dữ liệu khuyết thiếu và doanh thu hợp lệ

Một hệ thống thu thập thông tin đơn hàng trả về danh sách các giao dịch thô như sau:

python
giao_dich = [
    {"id": "GD01", "so_tien": 150000},
    {"id": "GD02", "so_tien": None},
    {"id": "GD03", "so_tien": 200000},
    {"id": "GD04", "so_tien": 0}
]
  1. Xác định số lượng giao dịch bị thiếu thông tin số tiền (None). Tính tỷ lệ phần trăm dữ liệu khuyết thiếu trên tổng số giao dịch.
  2. Tính tổng số tiền thu được và giá trị giao dịch trung bình trên các giao dịch hợp lệ (có số tiền xác định). Lưu ý: Giao dịch có so_tien = 0 là một giao dịch hợp lệ có phát sinh giá trị bằng không, không phải giá trị thiếu.
Lời giải
python
giao_dich = [
    {"id": "GD01", "so_tien": 150000},
    {"id": "GD02", "so_tien": None},
    {"id": "GD03", "so_tien": 200000},
    {"id": "GD04", "so_tien": 0}
]

tong_so = len(giao_dich)
giao_dich_thieu = [g for g in giao_dich if g["so_tien"] is None]
so_thieu = len(giao_dich_thieu)
ty_le_thieu = (so_thieu / tong_so) * 100

gia_tri_hop_le = [g["so_tien"] for g in giao_dich if g["so_tien"] is not None]
tong_doanh_thu = sum(gia_tri_hop_le)
trung_binh = tong_doanh_thu / len(gia_tri_hop_le)

print(f"Số lượng giao dịch thiếu: {so_thieu} ({ty_le_thieu:.1f}%)")
print(f"Tổng doanh thu hợp lệ: {tong_doanh_thu:,} đ")
print(f"Giá trị giao dịch trung bình: {trung_binh:,.2f} đ")

Kết quả:

  • Số lượng giao dịch thiếu: 1 giao dịch trên tổng số 4 bản ghi, chiếm tỷ lệ 25,0%.
  • Các giao dịch hợp lệ gồm 150000, 200000 và 0, tổng cộng có đúng 3 giao dịch hợp lệ.
  • Tổng doanh thu hợp lệ bằng 150000+200000+0=350000 đồng.
  • Giá trị trung bình trên 3 giao dịch hợp lệ đạt 3500003≈116666,67 đồng.

Lab 01: Môi trường tính toán tương tác và cấu trúc dữ liệu bảng ​

Một notebook có thể chạy được trong phiên hiện tại nhưng báo lỗi sau khi khởi động lại. Các bài dưới đây giúp kiểm tra thứ tự chạy, trạng thái biến và cách đọc kết quả trên một bảng dữ liệu nhỏ.

Thông tin dữ liệu thực hành ​

Bộ bài tập sử dụng dữ liệu chỗ ở Inside Airbnb với ảnh chụp dữ liệu (snapshot) ngày 29/06/2026 tại thành phố Santiago (Chile). Có thể tải bảng chỗ ở rút gọn của snapshot để thử lại với dữ liệu đầy đủ.

Nhằm phục vụ kiểm thử nhanh, độc lập môi trường và chạy trực tiếp không phụ thuộc kết nối mạng, các bài thực hành dưới đây sử dụng tập dữ liệu kiểm thử nhỏ gồm 6 dòng giả lập. Tập dữ liệu này phản ánh đúng cấu trúc và các trường hợp đặc biệt như giá trị khuyết thiếu hoặc giá trị bằng không:

python
DEMO_CSV = (
    "id,name,neighbourhood,room_type,price,number_of_reviews,last_review\n"
    "1,Phong A,Centro,Entire home/apt,100,0,\n"
    "2,Phong B,Centro,Private room,50,2,2026-05-10\n"
    "3,Phong C,Norte,Entire home/apt,,0,\n"
    "4,Phong D,Norte,Entire home/apt,150,1,2026-06-01\n"
    "5,Phong E,Sur,Private room,0,3,2026-05-30\n"
    "6,Phong F,Sur,Entire home/apt,300,2,2026-06-10\n"
)

Lưu ý: Các số liệu tính toán trên 6 dòng này là dữ liệu kiểm thử giả lập, không đại diện cho toàn bộ quy mô thị trường thực tế của thành phố Santiago.


Danh mục bài tập thực hành ​

Bài 1: Thứ tự thực thi ô mã nguồn và ngoại lệ NameError ​

Tìm hiểu nguyên lý vận hành của không gian tên (namespace) trong tiến trình thực thi của sổ tay tương tác. Hãy giải thích vì sao khi thực thi một ô mã nguồn truy xuất biến trước khi ô định nghĩa biến đó được chạy, môi trường sẽ ném ra ngoại lệ NameError.

Yêu cầu kỹ thuật:

  • Tình huống: Ô A định nghĩa biến so_phong_gia_dinh = 4. Ô B thực hiện lệnh print("Số phòng gia đình hiện có:", so_phong_gia_dinh).
  • Thao tác: Quan sát hiện tượng khi chạy ô B trước ô A trong một phiên làm việc mới. Sau đó chạy tuần tự ô A rồi đến ô B.
  • Đầu ra: Trình bày cơ chế bắt lỗi và truy xuất an toàn thông qua việc kiểm tra không gian tên.
  • Trường hợp biên: Phiên làm việc vừa khởi động lại (Restart session) làm mất sạch các biến lưu trữ trong bộ nhớ tạm.
Lời giải

Cách 1: Chạy tuần tự theo trật tự tự nhiên từ trên xuống dưới ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

# Bắt đầu lời giải

# Thực thi tuần tự từ Cell A đến Cell B
so_phong_gia_dinh = 4
print("Số phòng gia đình hiện có:", so_phong_gia_dinh)

# Kết thúc lời giải

Giải thích cách 1 ​

Phép gán đưa tên so_phong_gia_dinh vào không gian tên của phiên làm việc. Khi chạy dòng print, Python tra cứu tên đó rồi truyền giá trị 4 cho hàm in. print ghép các đối số bằng một dấu cách theo mặc định, nên ta thấy nhãn và số trên cùng một dòng.

Thứ tự trên trang không quyết định trạng thái thực tế. Nếu chỉ chạy ô có print trong một phiên mới, Python chưa có tên biến và phát sinh NameError. Hãy chạy ô gán trước, rồi mới chạy ô đọc biến. Khởi động lại kernel xóa trạng thái của tiến trình cũ, nên cần chạy lại các ô khởi tạo.

Kết quả cách 1 ​

text
Số phòng gia đình hiện có: 4

Cách 2: Kiểm tra sự tồn tại của biến trong không gian tên với try và except ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

# Bắt đầu lời giải


def safe_access_variable(var_name: str, scope: dict) -> object:
    """Truy xuất biến an toàn kèm xử lý ngoại lệ NameError khi biến chưa tồn tại.

    Tham số var_name: Tên biến cần tra cứu.
    Tham số scope: Không gian tên (namespace) dùng để tra cứu biến.
    """
    try:
        if var_name not in scope:
            raise NameError(
                f"Biến '{var_name}' chưa được định nghĩa trong phiên làm việc."
            )
        return scope[var_name]
    except NameError as error:
        print(f"Bắt ngoại lệ thành công: {error}")
        return None


khong_gian_ten = {"so_phong_gia_dinh": 4}
ket_qua = safe_access_variable("so_phong_gia_dinh", khong_gian_ten)
print(f"Giá trị truy xuất: {ket_qua}")

# Kết thúc lời giải

Giải thích cách 2 ​

Hàm nhận tên cần tìm ở var_name và một từ điển tra cứu ở scope. Biểu thức var_name not in scope kiểm tra khóa, còn scope[var_name] lấy giá trị. Ví dụ truyền một từ điển tự tạo chứa giá trị 4, vì vậy hàm trả về 4 và không đi vào nhánh xử lý lỗi.

Nếu khóa vắng mặt, raise NameError(...) chủ động tạo ngoại lệ để khối except NameError as error bắt lại, in thông báo và trả về None. Đây là phép tra cứu trên từ điển được truyền vào, không tự quét mọi phạm vi của Python. Khi Python tra cứu một tên thông thường, phạm vi tích hợp sẵn cũng có thể được xét sau các phạm vi cục bộ, bao quanh và toàn cục.

Kết quả cách 2 ​

text
Giá trị truy xuất: 4

So sánh hai cách ​

Cách 1 giải quyết việc phụ thuộc giữa các ô bằng thứ tự chạy. Cách 2 minh họa cách tra cứu một tên và phản hồi khi tên vắng mặt. Hàm tra cứu không thay thế việc khởi tạo đúng dữ liệu và không làm notebook tự chạy những ô bị bỏ qua.


Bài 2: Trạng thái tích lũy của biến trong tiến trình tính toán ​

Thực hiện thí nghiệm quan sát tính cộng dồn của biến trạng thái khi một ô mã nguồn được chạy lặp lại nhiều lần. Đối chiếu giá trị của biến đếm trong hai tình huống: Chạy một ô tăng biến 3 lần liên tiếp và chạy toàn bộ từ đầu đến cuối (Restart session and Run all).

Yêu cầu kỹ thuật:

  • Đầu vào: Biến đếm dem khởi tạo bằng 0, ô tăng biến thực hiện phép toán dem = dem + 1.
  • Thao tác: Dự đoán chính xác giá trị của dem sau khi kích hoạt ô tăng biến 3 lần. Tiếp theo, dự đoán giá trị của dem khi người dùng chọn lệnh Restart & Run all.
  • Đầu ra: Hai số nguyên đại diện cho kết quả dự đoán du_doan_3_lan và du_doan_run_all.
  • Trường hợp biên: Tránh nhầm lẫn giữa việc chạy lại một ô đơn lẻ với việc khởi động lại toàn bộ tiến trình.
Lời giải

Cách 1: Ghi nhận kết quả dự đoán thủ công qua việc theo dõi phiên chạy ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

# Bắt đầu lời giải

dem = 0
for _ in range(3):
    dem += 1
du_doan_3_lan = dem

dem_run_all = 0
dem_run_all += 1
du_doan_run_all = dem_run_all

print(f"Sau 3 lần chạy cell tăng: {du_doan_3_lan}")
print(f"Sau khi Restart và chạy lại toàn bộ: {du_doan_run_all}")

# Kết thúc lời giải

Giải thích cách 1 ​

dem = 0 tạo trạng thái ban đầu. range(3) sinh ba lượt lặp, và mỗi lượt dem += 1 lấy giá trị hiện tại cộng thêm một. Vì thế chuỗi giá trị lần lượt là 1, 2, 3. Biến du_doan_3_lan giữ kết quả sau cả ba lượt.

Phần sau dùng biến mới dem_run_all, khởi tạo lại từ 0 và chỉ tăng một lần. Hai biến riêng giúp thấy rõ hai kịch bản. Trong notebook thực tế, chạy lại riêng ô tăng dùng tiếp trạng thái hiện có, còn Restart rồi Run all bắt đầu từ trạng thái sạch và chạy mỗi ô một lần.

Kết quả cách 1 ​

text
Sau 3 lần chạy cell tăng: 3
Sau khi Restart và chạy lại toàn bộ: 1

Cách 2: Lập trình hàm mô phỏng cơ chế lưu trữ trạng thái của kernel ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

# Bắt đầu lời giải


def simulate_counter(num_runs: int, reset_initial: bool = False) -> int:
    """Mô phỏng cơ chế lưu trạng thái biến tích lũy của kernel qua nhiều lần thực thi.

    Tham số num_runs: Số lượt thực thi cell tăng biến.
    Tham số reset_initial: Cờ tái khởi động trạng thái bộ nhớ về 0.
    """
    counter_state = 0
    for _ in range(num_runs):
        counter_state += 1
    return counter_state


ket_qua_3_lan = simulate_counter(3)
ket_qua_run_all = simulate_counter(1)
print(f"Mô phỏng 3 lượt: {ket_qua_3_lan} | Mô phỏng một lượt sạch: {ket_qua_run_all}")

# Kết thúc lời giải

Giải thích cách 2 ​

simulate_counter nhận num_runs là số lượt tăng và trả về một số nguyên. Biến counter_state nằm trong thân hàm, được tạo lại với giá trị 0 mỗi lần gọi. Do đó simulate_counter(3) trả về 3, còn lời gọi độc lập simulate_counter(1) trả về 1.

Vòng for _ in range(num_runs) không sử dụng số thứ tự của từng lượt, nên đặt tên _ cho biến lặp. Hàm mô phỏng số lần tăng trong một lần gọi, không tự lưu trạng thái giữa các lần gọi. Muốn mô phỏng một kernel duy trì trạng thái, phải truyền trạng thái cũ vào hoặc giữ nó trong một đối tượng riêng.

Kết quả cách 2 ​

text
Mô phỏng 3 lượt: 3 | Mô phỏng một lượt sạch: 1

So sánh hai cách ​

Cách 1 thể hiện hai tình huống bằng những biến cụ thể. Cách 2 cho phép đổi số lượt chạy qua đối số để thử thêm tình huống. Việc đặt phép tính trong hàm giúp kiểm tra độc lập, không phải một tối ưu về tốc độ.


Bài 3: Đọc kích thước bảng và xác định nhóm chiếm đa số ​

Nạp dữ liệu danh sách chỗ ở vào cấu trúc bảng DataFrame của thư viện pandas. Hãy trích xuất tổng số lượng bản ghi (số dòng) và xác định tên loại phòng xuất hiện với tần suất lớn nhất trong bảng dữ liệu đang sử dụng.

Yêu cầu kỹ thuật:

  • Đầu vào: Chuỗi dữ liệu phân tách bằng dấu phẩy DEMO_CSV chứa 6 dòng chỗ ở mẫu.
  • Thao tác: Sử dụng pd.read_csv() để chuyển đổi văn bản sang DataFrame. Dùng thuộc tính shape để lấy số dòng và phương thức value_counts() trên cột room_type.
  • Đầu ra: Biến số nguyên so_dong và biến chuỗi loai_nhieu_nhat.
  • Trường hợp biên: Trường hợp bảng rỗng không có dòng nào hoặc nhiều loại phòng có cùng số lượng cao nhất.
Lời giải

Cách 1: Sử dụng các thuộc tính và phương thức cơ bản của pandas ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

DEMO_CSV = (
    "id,name,neighbourhood,room_type,price,number_of_reviews,last_review\n"
    "1,Phong A,Centro,Entire home/apt,100,0,\n"
    "2,Phong B,Centro,Private room,50,2,2026-05-10\n"
    "3,Phong C,Norte,Entire home/apt,,0,\n"
    "4,Phong D,Norte,Entire home/apt,150,1,2026-06-01\n"
    "5,Phong E,Sur,Private room,0,3,2026-05-30\n"
    "6,Phong F,Sur,Entire home/apt,300,2,2026-06-10\n"
)

from io import StringIO
import pandas as pd

ds = pd.read_csv(StringIO(DEMO_CSV))

# Bắt đầu lời giải

from io import StringIO
import pandas as pd

ds = pd.read_csv(StringIO(DEMO_CSV))
so_dong = ds.shape[0]
dem_loai = ds["room_type"].value_counts()
loai_nhieu_nhat = dem_loai.idxmax()

print(f"Tổng số dòng: {so_dong}")
print(f"Loại phòng phổ biến nhất: {loai_nhieu_nhat}")

# Kết thúc lời giải

Giải thích cách 1 ​

StringIO(DEMO_CSV) biến chuỗi CSV thành một đối tượng đọc được giống tệp. pd.read_csv đọc dòng tiêu đề thành tên cột và sáu dòng sau thành DataFrame. ds.shape là bộ (6, 7), nên ds.shape[0] cho số dòng 6.

ds["room_type"] chọn một cột dưới dạng Series. value_counts() đếm từng loại và mặc định sắp xếp giảm dần theo số lượng, bỏ qua giá trị thiếu. Series đếm có các nhãn là tên loại phòng. idxmax() trả về nhãn gắn với số đếm lớn nhất, chứ không trả về chính số đếm. Mẫu có bốn nguyên căn và hai phòng riêng, nên đáp án là Entire home/apt.

Kết quả cách 1 ​

text
Tổng số dòng: 6
Loại phòng phổ biến nhất: Entire home/apt

Cách 2: Xây dựng hàm trích xuất hồ sơ bảng có kiểm tra ca biên ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

DEMO_CSV = (
    "id,name,neighbourhood,room_type,price,number_of_reviews,last_review\n"
    "1,Phong A,Centro,Entire home/apt,100,0,\n"
    "2,Phong B,Centro,Private room,50,2,2026-05-10\n"
    "3,Phong C,Norte,Entire home/apt,,0,\n"
    "4,Phong D,Norte,Entire home/apt,150,1,2026-06-01\n"
    "5,Phong E,Sur,Private room,0,3,2026-05-30\n"
    "6,Phong F,Sur,Entire home/apt,300,2,2026-06-10\n"
)

from io import StringIO
import pandas as pd

ds = pd.read_csv(StringIO(DEMO_CSV))

# Bắt đầu lời giải


def get_table_profile(df: pd.DataFrame, category_col: str) -> dict:
    """Hồ sơ hóa kích thước bảng và nhận diện danh mục chiếm tỷ trọng lớn nhất.

    Tham số df: Bảng dữ liệu pandas DataFrame.
    Tham số category_col: Tên cột danh mục cần đếm tần suất.
    """
    total_rows = len(df)
    if total_rows == 0 or category_col not in df.columns:
        return {"total_rows": 0, "top_category": None, "frequency": 0}
    counts = df[category_col].value_counts()
    if counts.empty:
        return {"total_rows": total_rows, "top_category": None, "frequency": 0}
    top_name = str(counts.index[0])
    top_count = int(counts.iloc[0])
    return {"total_rows": total_rows, "top_category": top_name, "frequency": top_count}


thong_tin = get_table_profile(ds, "room_type")
print(f"Hồ sơ bảng: {thong_tin}")

# Kết thúc lời giải

Giải thích cách 2 ​

Hàm get_table_profile(df, category_col) đóng gói số dòng, nhóm đứng đầu và tần suất vào một từ điển. len(df) đếm dòng. Với cột có dữ liệu, counts.index[0] lấy tên nhóm đầu tiên và counts.iloc[0] lấy số đếm tại vị trí đầu tiên. Ép qua str và int làm rõ kiểu của hai giá trị trả về.

Nhánh counts.empty xử lý cột toàn giá trị thiếu. Việc kiểm tra trước khi lấy phần tử đầu tránh lỗi truy cập Series rỗng. Với nhiều nhóm hòa điểm, đoạn mã chọn nhóm đứng đầu Series đếm. Nếu ứng dụng cần quy tắc hòa điểm cố định theo tên, phải sắp xếp thêm theo tên thay vì suy ra từ tần suất.

Kết quả cách 2 ​

text
Hồ sơ bảng: {'total_rows': 6, 'top_category': 'Entire home/apt', 'frequency': 4}
room_typeSố dòng
Entire home/apt4
Private room2

So sánh hai cách ​

Cách 1 phù hợp để đọc trực tiếp một bảng đã biết cấu trúc. Cách 2 tạo kết quả có tên trường để dùng trong báo cáo và bổ sung các nhánh xử lý dữ liệu thiếu. Chúng dùng cùng phép đếm, nên không có cơ sở kết luận cách đóng gói thành hàm chạy nhanh hơn.


Bài 4: Tính tỷ lệ phần trăm theo nhóm và làm tròn số thập phân ​

Từ Series số lượng dem_loai đã tính ở Bài 3, hãy lập trình tính toán Series ty_le biểu diễn tỷ lệ phần trăm (thang đo từ 0 đến 100) của từng loại phòng. Kết quả phải được làm tròn đến đúng 1 chữ số thập phân.

Yêu cầu kỹ thuật:

  • Đầu vào: Series dem_loai chứa số lượng phòng theo từng loại.
  • Thao tác: Lấy từng phần tử chia cho tổng số quan sát, nhân với 100, sau đó gọi phương thức làm tròn round(1).
  • Đầu ra: Đối tượng pd.Series có chỉ mục giữ nguyên tên loại phòng và giá trị là số thực phần trăm.
  • Trường hợp biên: Tổng tỷ lệ cộng lại phải nằm trong khoảng cho phép từ 99.5% đến 100.5% để xử lý sai số làm tròn số học.
Lời giải

Cách 1: Tính toán bằng biểu thức vector hóa trực tiếp ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

DEMO_CSV = (
    "id,name,neighbourhood,room_type,price,number_of_reviews,last_review\n"
    "1,Phong A,Centro,Entire home/apt,100,0,\n"
    "2,Phong B,Centro,Private room,50,2,2026-05-10\n"
    "3,Phong C,Norte,Entire home/apt,,0,\n"
    "4,Phong D,Norte,Entire home/apt,150,1,2026-06-01\n"
    "5,Phong E,Sur,Private room,0,3,2026-05-30\n"
    "6,Phong F,Sur,Entire home/apt,300,2,2026-06-10\n"
)

from io import StringIO
import pandas as pd

ds = pd.read_csv(StringIO(DEMO_CSV))

dem_loai = ds["room_type"].value_counts()

# Bắt đầu lời giải

# Tính toán tỷ lệ phần trăm trực tiếp trên Series đếm tần suất
ty_le = (dem_loai / dem_loai.sum() * 100).round(1)
print("Tỷ lệ phần trăm theo loại phòng:")
print(ty_le)

# Kết thúc lời giải

Giải thích cách 1 ​

dem_loai.sum() cộng hai tần suất để có tổng 6. Phép chia Series cho số 6 áp dụng cho từng nhóm mà vẫn giữ chỉ mục tên loại phòng. Nhân 100 đổi tỷ lệ trên thang 0–1 thành phần trăm trên thang 0–100.

round(1) tạo Series kết quả với mỗi giá trị làm tròn một chữ số thập phân. Bốn trên sáu dòng thành 66.7, còn hai trên sáu thành 33.3. Các giá trị vẫn là số thực, không phải chuỗi có dấu %. Chỉ thêm ký hiệu phần trăm khi trình bày để tránh làm mất khả năng tính toán tiếp.

Kết quả cách 1 ​

room_typeTỷ lệ (%)
Entire home/apt66.7
Private room33.3

Cách 2: Đóng gói hàm tính toán an toàn có kiểm tra mẫu số ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

DEMO_CSV = (
    "id,name,neighbourhood,room_type,price,number_of_reviews,last_review\n"
    "1,Phong A,Centro,Entire home/apt,100,0,\n"
    "2,Phong B,Centro,Private room,50,2,2026-05-10\n"
    "3,Phong C,Norte,Entire home/apt,,0,\n"
    "4,Phong D,Norte,Entire home/apt,150,1,2026-06-01\n"
    "5,Phong E,Sur,Private room,0,3,2026-05-30\n"
    "6,Phong F,Sur,Entire home/apt,300,2,2026-06-10\n"
)

from io import StringIO
import pandas as pd

ds = pd.read_csv(StringIO(DEMO_CSV))

dem_loai = ds["room_type"].value_counts()

# Bắt đầu lời giải


def calculate_percentages(counts: pd.Series, decimals: int = 1) -> pd.Series:
    """Tính toán phân phối tỷ lệ phần trăm từ Series tần suất với kiểm tra mẫu số an toàn.

    Tham số counts: Series chứa số lượng quan sát của từng nhóm.
    Tham số decimals: Số chữ số làm tròn sau dấu phẩy thập phân.
    """
    if not isinstance(counts, pd.Series):
        raise TypeError("Đầu vào phải là một đối tượng pandas Series.")
    total_observations = counts.sum()
    if total_observations == 0:
        return pd.Series(0.0, index=counts.index, dtype=float)
    percentages = (counts / total_observations) * 100.0
    return percentages.round(decimals)


ty_le_chuan = calculate_percentages(dem_loai, decimals=1)
print(f"Kết quả tỷ lệ chuẩn hóa:\n{ty_le_chuan}")

# Kết thúc lời giải

Giải thích cách 2 ​

calculate_percentages nhận Series tần suất ở counts và số chữ số làm tròn ở decimals. isinstance(counts, pd.Series) kiểm tra loại đối tượng trước khi gọi các phương thức của Series. Nếu tổng tần suất bằng 0, hàm trả Series số 0.0 với cùng chỉ mục, tránh tạo kết quả chia cho không.

Ở nhánh còn lại, hàm tính từng tỷ lệ bằng cùng biểu thức của Cách 1 và gọi round(decimals). Đối số decimals=1 trong lời gọi cho đúng mức làm tròn của đề. Hàm giả định các tần suất không âm. Với nhiều nhóm, tổng các tỷ lệ đã làm tròn có thể lệch khỏi 100, nên cần kiểm tra quy tắc làm tròn theo số nhóm thay vì coi tổng luôn chính xác.

Kết quả cách 2 ​

room_typeTỷ lệ (%)
Entire home/apt66.7
Private room33.3

So sánh hai cách ​

Cách 1 ngắn và trực diện trên một Series hợp lệ. Cách 2 phù hợp khi nhận dữ liệu từ nhiều nơi vì có kiểm tra kiểu và quy ước khi tổng bằng không. Cả hai trả cùng phần trăm trên mẫu đang xét.


Bài 5: Trực quan hóa phân phối tần suất bằng biểu đồ thanh ngang ​

Thực hiện vẽ biểu đồ thanh ngang (horizontal bar chart) trực quan hóa số lượng chỗ ở theo từng loại phòng từ Series dem_loai. Biểu đồ cần được sắp xếp theo thứ tự tăng dần từ dưới lên trên để nhóm chiếm đa số hiển thị nổi bật ở vị trí cao nhất.

Yêu cầu kỹ thuật:

  • Đầu vào: Series dem_loai.
  • Thao tác: Gọi phương thức sắp xếp sort_values() và vẽ đồ thị thanh ngang thông qua plot.barh().
  • Đầu ra: Hình vẽ matplotlib hoàn chỉnh có tiêu đề, nhãn trục hoành và nhãn trục tung rõ ràng.
  • Trường hợp biên: Kiểm soát kích thước hình vẽ vừa vặn, không để chữ tràn ra ngoài viền lề.
Lời giải

Cách 1: Sử dụng phương thức vẽ đồ thị tích hợp của pandas ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

DEMO_CSV = (
    "id,name,neighbourhood,room_type,price,number_of_reviews,last_review\n"
    "1,Phong A,Centro,Entire home/apt,100,0,\n"
    "2,Phong B,Centro,Private room,50,2,2026-05-10\n"
    "3,Phong C,Norte,Entire home/apt,,0,\n"
    "4,Phong D,Norte,Entire home/apt,150,1,2026-06-01\n"
    "5,Phong E,Sur,Private room,0,3,2026-05-30\n"
    "6,Phong F,Sur,Entire home/apt,300,2,2026-06-10\n"
)

from io import StringIO
import pandas as pd

ds = pd.read_csv(StringIO(DEMO_CSV))

dem_loai = ds["room_type"].value_counts()

# Bắt đầu lời giải

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(6, 3))
dem_loai.sort_values().plot.barh(ax=ax, color="#1E93AB")
ax.set_title("Số lượng chỗ ở theo loại phòng")
ax.set_xlabel("Số lượng chỗ ở")
ax.set_ylabel("Loại phòng")
fig.tight_layout()

# Kết thúc lời giải

plt.show()

Giải thích cách 1 ​

plt.subplots(figsize=(6, 3)) trả về một hình fig và một hệ trục ax, với kích thước tính bằng inch. sort_values() sắp số đếm tăng dần trước khi plot.barh(ax=ax, ...) vẽ thanh ngang trên hệ trục đã tạo.

Vị trí đầu tiên của thanh ngang nằm dưới cùng, nên phòng riêng có số đếm 2 ở dưới và nguyên căn có số đếm 4 ở trên. set_title, set_xlabel, set_ylabel đặt tiêu đề và đơn vị đọc trục. fig.tight_layout() điều chỉnh khoảng cách để nhãn vừa trong hình. Trong notebook, hình được hiển thị ở đầu ra ô mã, không phải bảng văn bản.

Kết quả cách 1 ​

Biểu đồ có hai thanh. Bảng sau ghi đúng dữ liệu và thứ tự của các thanh để đối chiếu với hình:

Vị trí thanhLoại phòngĐộ dài
DướiPrivate room2
TrênEntire home/apt4

Cách 2: Tùy biến biểu đồ qua giao diện hướng đối tượng của matplotlib ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

import matplotlib.pyplot as plt

DEMO_CSV = (
    "id,name,neighbourhood,room_type,price,number_of_reviews,last_review\n"
    "1,Phong A,Centro,Entire home/apt,100,0,\n"
    "2,Phong B,Centro,Private room,50,2,2026-05-10\n"
    "3,Phong C,Norte,Entire home/apt,,0,\n"
    "4,Phong D,Norte,Entire home/apt,150,1,2026-06-01\n"
    "5,Phong E,Sur,Private room,0,3,2026-05-30\n"
    "6,Phong F,Sur,Entire home/apt,300,2,2026-06-10\n"
)

from io import StringIO
import pandas as pd

ds = pd.read_csv(StringIO(DEMO_CSV))

dem_loai = ds["room_type"].value_counts()

# Bắt đầu lời giải


def plot_horizontal_distribution(
    counts: pd.Series, title: str, xlabel: str, ylabel: str
):
    """Vẽ biểu đồ thanh ngang kèm nhãn số liệu trực tiếp trên từng thanh.

    Tham số counts: Series chứa số lượng từng nhóm.
    Tham số title: Tiêu đề biểu đồ.
    Tham số xlabel, ylabel: Nhãn các trục tọa độ.
    """
    sorted_counts = counts.sort_values()
    fig, ax = plt.subplots(figsize=(7, 3.2))
    bars = ax.barh(
        sorted_counts.index, sorted_counts.values, color="#1E93AB", edgecolor="none"
    )
    ax.bar_label(bars, padding=4, fmt="%d", fontsize=9)
    ax.set_title(title, fontsize=11, fontweight="bold", pad=10)
    ax.set_xlabel(xlabel, fontsize=10)
    ax.set_ylabel(ylabel, fontsize=10)
    ax.spines["top"].set_visible(False)
    ax.spines["right"].set_visible(False)
    fig.tight_layout()
    return fig, ax


fig, ax = plot_horizontal_distribution(
    dem_loai,
    title="Phân bố cơ cấu loại phòng chỗ ở",
    xlabel="Số lượng chỗ ở",
    ylabel="Loại phòng",
)

# Kết thúc lời giải

plt.show()

Giải thích cách 2 ​

Hàm nhận Series đếm cùng ba chuỗi dùng làm tiêu đề và nhãn trục. ax.barh(sorted_counts.index, sorted_counts.values, ...) truyền tường minh tên nhóm cho trục dọc và số lượng cho chiều dài thanh. Giá trị trả về bars là tập các hình chữ nhật dùng để gắn nhãn.

ax.bar_label(bars, padding=4, fmt="%d") in số nguyên ở đầu thanh và đặt nhãn cách thanh bốn điểm. ax.spines[...] truy cập đường viền của hệ trục, còn set_visible(False) ẩn hai viền trên và phải. Hàm trả (fig, ax) để nơi gọi tiếp tục lưu hình hoặc điều chỉnh trục. Hàm không tính lại tần suất và không đổi dữ liệu đếm.

Kết quả cách 2 ​

Các nhãn 2 và 4 xuất hiện ngay cạnh đầu thanh:

Vị trí thanhLoại phòngĐộ dài
DướiPrivate room2
TrênEntire home/apt4

So sánh hai cách ​

Cách 1 dùng pandas để chuyển Series thành hình với ít thao tác. Cách 2 dùng giao diện đối tượng của Matplotlib để điều chỉnh nhãn và đường viền. Khác biệt nằm ở khả năng trình bày và tái sử dụng, không ở kết quả đếm.


Bài 6: Đóng gói hàm phân tích tùy biến theo cấu trúc bảng kết quả ​

Viết hàm analyze_question(data: pd.DataFrame) -> dict nhận bảng chỗ ở và trả một từ điển gồm đúng ba khóa. Trường question là chuỗi câu hỏi, table là DataFrame kết quả và unit là chuỗi đơn vị. Bảng kết quả phải có ít nhất một cột, các tên cột là chuỗi không rỗng và không trùng nhau.

Yêu cầu kỹ thuật:

  • Đầu vào: DataFrame data chứa các cột như neighbourhood, room_type, price, number_of_reviews.
  • Thao tác: Lựa chọn câu hỏi thực tế (ví dụ: Quận nào sở hữu số lượng chỗ ở nhiều nhất?). Tổng hợp số liệu từ tham số data, tuyệt đối không đọc biến toàn cục bên ngoài và không chỉnh sửa dữ liệu gốc tại chỗ (in-place mutation).
  • Đầu ra: Từ điển chứa đúng ba khóa {"question": str, "table": pd.DataFrame, "unit": str}.
  • Trường hợp biên: Khi đầu vào là DataFrame rỗng 0 dòng data.iloc[:0], hàm phải trả về bảng kết quả 0 dòng nhưng phải bảo toàn đầy đủ các cột đã định nghĩa.
Lời giải

Cách 1: Sử dụng value_counts và reset_index định danh cột ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

DEMO_CSV = (
    "id,name,neighbourhood,room_type,price,number_of_reviews,last_review\n"
    "1,Phong A,Centro,Entire home/apt,100,0,\n"
    "2,Phong B,Centro,Private room,50,2,2026-05-10\n"
    "3,Phong C,Norte,Entire home/apt,,0,\n"
    "4,Phong D,Norte,Entire home/apt,150,1,2026-06-01\n"
    "5,Phong E,Sur,Private room,0,3,2026-05-30\n"
    "6,Phong F,Sur,Entire home/apt,300,2,2026-06-10\n"
)

from io import StringIO
import pandas as pd

ds = pd.read_csv(StringIO(DEMO_CSV))

# Bắt đầu lời giải


def analyze_question(data: pd.DataFrame) -> dict:
    """Phân tích số lượng chỗ ở theo từng quận huyện từ dữ liệu bảng.

    Cột bảng kết quả:
    - neighbourhood (str): Tên khu vực hành chính.
    - count (int): Số lượng chỗ ở ghi nhận.
    """
    if not isinstance(data, pd.DataFrame):
        raise TypeError("Dữ liệu đầu vào phải là một pandas DataFrame.")
    if data.empty or "neighbourhood" not in data.columns:
        empty_table = pd.DataFrame(columns=["neighbourhood", "count"])
        return {
            "question": "Quận nào sở hữu số lượng chỗ ở niêm yết nhiều nhất?",
            "table": empty_table,
            "unit": "chỗ ở",
        }
    counts = data["neighbourhood"].value_counts().reset_index()
    counts.columns = ["neighbourhood", "count"]
    return {
        "question": "Quận nào sở hữu số lượng chỗ ở niêm yết nhiều nhất?",
        "table": counts,
        "unit": "chỗ ở",
    }


ket_qua_e1 = analyze_question(ds)
print(f"Câu hỏi: {ket_qua_e1['question']} | Đơn vị: {ket_qua_e1['unit']}")
print(ket_qua_e1["table"])

# Kết thúc lời giải

Giải thích cách 1 ​

analyze_question nhận bảng ở tham số data, kiểm tra đây là DataFrame rồi đếm cột neighbourhood. value_counts() tạo Series mà chỉ mục là khu vực. reset_index() đưa chỉ mục đó thành cột, và phép gán counts.columns đặt đúng hai tên cột yêu cầu.

Từ điển trả về tách ba vai trò. Trường question nói bảng trả lời câu hỏi gì, table chứa số liệu có thể tính tiếp, và unit ghi đơn vị. Nhánh bảng rỗng tạo DataFrame có hai tên cột nhưng không có dòng. Không thêm một dòng toàn giá trị thiếu để giả làm bảng rỗng.

Kết quả cách 1 ​

neighbourhoodcount
Centro2
Norte2
Sur2

unit bằng "chỗ ở". Nếu truyền ds.iloc[:0], kết quả table.shape là (0, 2) với hai cột neighbourhood, count.

Cách 2: Nhóm dữ liệu với groupby và tạo bản sao bảo vệ dữ liệu gốc ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

DEMO_CSV = (
    "id,name,neighbourhood,room_type,price,number_of_reviews,last_review\n"
    "1,Phong A,Centro,Entire home/apt,100,0,\n"
    "2,Phong B,Centro,Private room,50,2,2026-05-10\n"
    "3,Phong C,Norte,Entire home/apt,,0,\n"
    "4,Phong D,Norte,Entire home/apt,150,1,2026-06-01\n"
    "5,Phong E,Sur,Private room,0,3,2026-05-30\n"
    "6,Phong F,Sur,Entire home/apt,300,2,2026-06-10\n"
)

from io import StringIO
import pandas as pd

ds = pd.read_csv(StringIO(DEMO_CSV))

# Bắt đầu lời giải


def analyze_question(data: pd.DataFrame) -> dict:
    """Phân tích tổng hợp số chỗ ở theo quận kèm kiểm soát tính toàn vẹn dữ liệu.

    Bảo đảm không sửa đổi đối số truyền vào và duy trì cấu trúc cột nhất quán.
    """
    cols = ["neighbourhood", "count"]
    if not isinstance(data, pd.DataFrame):
        raise TypeError("Đối số data phải là pandas DataFrame.")
    if data.empty:
        return {
            "question": "Số lượng chỗ ở phân bổ theo từng khu vực địa lý là bao nhiêu?",
            "table": pd.DataFrame(columns=cols),
            "unit": "chỗ ở",
        }
    # Sử dụng bản sao để không làm biến đổi dữ liệu truyền vào
    working_df = data.copy(deep=False)
    summary = (
        working_df.groupby("neighbourhood", as_index=False)
        .size()
        .rename(columns={"size": "count"})
        .sort_values(by="count", ascending=False)
        .reset_index(drop=True)
    )
    return {
        "question": "Số lượng chỗ ở phân bổ theo từng khu vực địa lý là bao nhiêu?",
        "table": summary[cols],
        "unit": "chỗ ở",
    }


ket_qua_nang_cao = analyze_question(ds)
print(ket_qua_nang_cao["table"].head(3))

# Kết thúc lời giải

Giải thích cách 2 ​

groupby("neighbourhood", as_index=False).size() đếm số dòng trong mỗi nhóm và giữ tên nhóm ở một cột thông thường. rename(columns={"size": "count"}) đổi tên cột đếm. sort_values(..., ascending=False) xếp nhóm đông hơn trước, rồi reset_index(drop=True) tạo chỉ mục dòng liên tiếp và bỏ chỉ mục cũ.

Các phép tổng hợp này tạo bảng kết quả thay vì gán lại cột của đầu vào. copy(deep=False) tạo một đối tượng DataFrame khác nhưng không mặc nhiên sao chép mọi vùng dữ liệu. Vì thế lý do dữ liệu gốc được giữ nguyên trong hàm này là không có thao tác sửa nó. Hàm trả đúng ba trường và chọn lại summary[cols] để khóa thứ tự cột.

Kết quả cách 2 ​

neighbourhoodcount
Centro2
Norte2
Sur2

Ba khu vực đều có hai dòng. Vì hòa điểm, bảng này chưa cho một khu vực đứng đầu duy nhất.

So sánh hai cách ​

Cách 1 diễn đạt trực tiếp thao tác đếm một cột. Cách 2 đưa cùng phép đếm vào quy trình nhóm dữ liệu, thuận tiện nếu sau này cần thêm tổng hay trung vị. Bảng có sáu dòng vẫn cho ba nhóm bằng nhau, nên không nên diễn giải rằng một khu vực có nguồn cung lớn nhất riêng biệt.


Bài 7: Phân tích suy luận: Tỷ lệ niêm yết so với lượt đặt phòng và giới hạn dữ liệu giả lập ​

Thực hiện phân tích bản chất kinh tế và suy luận thống kê từ dữ liệu. Hãy giải thích rõ hai nội dung:

  1. Vì sao tỷ lệ chỗ ở niêm yết (listings) trên nền tảng không đồng nhất với tỷ lệ lượt đặt phòng thành công thực tế (bookings)?
  2. Vì sao tuyệt đối không được sử dụng các con số tính toán từ tập dữ liệu giả lập 6 dòng để kết luận về thị trường du lịch Santiago?

Yêu cầu kỹ thuật:

  • Đầu vào: Số liệu quan sát từ bảng chỗ ở và các cột liên quan đến hoạt động như number_of_reviews và last_review.
  • Thao tác: Xây dựng khung so sánh định tính và định lượng giữa chỉ số nguồn cung tĩnh (stock) và chỉ số dòng giao dịch động (flow).
  • Đầu ra: Ma trận phân tích đối chiếu rõ ràng các yếu tố tác động và mã nguồn kiểm tra tính hoạt động của các phòng.
  • Ranh giới kiểm chứng: Phân biệt rõ dữ liệu mẫu kiểm thử phục vụ xác thực kỹ thuật với dữ liệu khảo sát thực địa diện rộng.
Lời giải

Cách 1: Phân tích định tính cấu trúc thị trường và bản chất biến số ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

DEMO_CSV = (
    "id,name,neighbourhood,room_type,price,number_of_reviews,last_review\n"
    "1,Phong A,Centro,Entire home/apt,100,0,\n"
    "2,Phong B,Centro,Private room,50,2,2026-05-10\n"
    "3,Phong C,Norte,Entire home/apt,,0,\n"
    "4,Phong D,Norte,Entire home/apt,150,1,2026-06-01\n"
    "5,Phong E,Sur,Private room,0,3,2026-05-30\n"
    "6,Phong F,Sur,Entire home/apt,300,2,2026-06-10\n"
)

from io import StringIO
import pandas as pd

ds = pd.read_csv(StringIO(DEMO_CSV))

# Bắt đầu lời giải

# Ma trận phân tích sự khác biệt giữa nguồn cung niêm yết và giao dịch thực tế
bang_so_sanh = [
    {
        "Tieu_chi": "Bản chất khái niệm",
        "Niem_yet_Listings": "Nguồn cung tiềm năng (Stock)",
        "Dat_phong_Bookings": "Giao dịch phát sinh thực tế (Flow)",
    },
    {
        "Tieu_chi": "Mức độ hoạt động",
        "Niem_yet_Listings": "Bao gồm cả bài đăng cũ hoặc không nhận khách",
        "Dat_phong_Bookings": "Có khách lưu trú và thanh toán thành công",
    },
    {
        "Tieu_chi": "Chỉ số thay thế",
        "Niem_yet_Listings": "Tổng số dòng trong listings.csv",
        "Dat_phong_Bookings": "Số đánh giá mới hoặc lịch kín phòng",
    },
]
df_so_sanh = pd.DataFrame(bang_so_sanh)
print(df_so_sanh)

# Kết thúc lời giải

Giải thích cách 1 ​

Danh sách bang_so_sanh gồm ba từ điển, mỗi từ điển là một dòng mô tả. pd.DataFrame(bang_so_sanh) ghép các khóa thành tên cột và các giá trị thành ô. Bảng đặt hai đại lượng cạnh nhau để thấy đơn vị đếm khác nhau: Niêm yết đếm bài đăng chỗ ở, còn đặt phòng đếm giao dịch trong một khoảng thời gian.

Đây là bảng khái niệm, không phải phép đo số giao dịch. Một niêm yết có thể nhận nhiều lượt đặt hoặc chưa nhận lượt nào. Ngày bị chặn trên lịch và số đánh giá cũng không đồng nhất với số lượt đặt phòng. Muốn ước lượng giao dịch từ các chỉ số gián tiếp phải nêu thêm giả định và kiểm chứng chúng.

Kết quả cách 1 ​

Tieu_chiNiem_yet_ListingsDat_phong_Bookings
Bản chất khái niệmNguồn cung tiềm năng (Stock)Giao dịch phát sinh thực tế (Flow)
Mức độ hoạt độngBao gồm cả bài đăng cũ hoặc không nhận kháchCó khách lưu trú và thanh toán thành công
Chỉ số thay thếTổng số dòng trong listings.csvSố đánh giá mới hoặc lịch kín phòng

Cách 2: Lập trình kiểm tra chỉ số thay thế về mức độ hoạt động thực tế ​

python
# Dữ liệu mẫu và các hàm hỗ trợ

DEMO_CSV = (
    "id,name,neighbourhood,room_type,price,number_of_reviews,last_review\n"
    "1,Phong A,Centro,Entire home/apt,100,0,\n"
    "2,Phong B,Centro,Private room,50,2,2026-05-10\n"
    "3,Phong C,Norte,Entire home/apt,,0,\n"
    "4,Phong D,Norte,Entire home/apt,150,1,2026-06-01\n"
    "5,Phong E,Sur,Private room,0,3,2026-05-30\n"
    "6,Phong F,Sur,Entire home/apt,300,2,2026-06-10\n"
)

from io import StringIO
import pandas as pd

ds = pd.read_csv(StringIO(DEMO_CSV))

# Bắt đầu lời giải


def kiem_tra_hoat_dong(df: pd.DataFrame) -> dict:
    """Đánh giá sơ bộ tỷ lệ phòng có dấu hiệu hoạt động dựa trên chỉ số đánh giá.

    Tham số df: Bảng dữ liệu chỗ ở chứa các cột đánh giá.
    """
    tong_so = len(df)
    if tong_so == 0:
        return {"tong_phong": 0, "ty_le_chua_danh_gia": 0.0}
    # Lọc các dòng có 0 review
    chua_danh_gia = df[df["number_of_reviews"].astype(str) == "0"]
    ty_le_trong = len(chua_danh_gia) / tong_so * 100.0
    return {
        "tong_phong": tong_so,
        "so_phong_chua_danh_gia": len(chua_danh_gia),
        "ty_le_chua_danh_gia": round(ty_le_trong, 1),
    }


bao_cao_hoat_dong = kiem_tra_hoat_dong(ds)
print(f"Báo cáo sơ bộ hoạt động: {bao_cao_hoat_dong}")

# Kết thúc lời giải

Giải thích cách 2 ​

kiem_tra_hoat_dong nhận bảng và dùng len(df) làm mẫu số. astype(str) chuyển cột số đánh giá sang chuỗi để so sánh với "0" trong ví dụ. Mặt nạ chọn hai dòng có mã 1 và 3, nên số dòng chưa có đánh giá là 2 trên tổng 6. round(..., 1) hiển thị phần trăm thành 33.3.

Tên báo cáo không làm thay đổi giới hạn của dữ liệu. Không có đánh giá không chứng minh không có khách, và có đánh giá cũng không cho biết đầy đủ giao dịch. Tập sáu dòng được dựng để thử chương trình, không được dùng ước lượng thị trường Santiago. Khi bảng rỗng, hàm quy ước tỷ lệ bằng 0.0, tức là giá trị xử lý kỹ thuật chứ không phải tỷ lệ thị trường đã đo.

Kết quả cách 2 ​

TrườngGiá trị
tong_phong6
so_phong_chua_danh_gia2
ty_le_chua_danh_gia33.3

So sánh hai cách ​

Cách 1 làm rõ câu hỏi có thể trả lời bằng từng loại dữ liệu. Cách 2 tính một chỉ số quan sát được là tỷ lệ dòng chưa có đánh giá. Hai phần bổ trợ nhau, nhưng không phải hai thuật toán thay thế để suy ra lượt đặt phòng. Không có phương pháp nào ở đây chứng minh quy mô giao dịch thực tế.


Tài liệu tham khảo ​

  1. McKinney, W. (2022). Python for Data Analysis: Data Wrangling with pandas, NumPy, and Jupyter (3rd ed.). O'Reilly Media.
  2. Inside Airbnb. (2026). Santiago Data Assumptions and Data Dictionary. Inside Airbnb Open Data.
  3. Kluyver, T., et al. (2016). Jupyter Notebooks: A Publishing Format for Reproducible Computational Workflows. Positioning and Power in Academic Publishing: Players, Agents and Agendas, pp. 87–90.

Tài liệu tham khảo ​

  • Wes McKinney. Python for Data Analysis: Data Wrangling with pandas, NumPy, and Jupyter, tái bản lần 3. O'Reilly Media, 2022. Chương 1: Preliminaries.

  • Project Jupyter Documentation. Jupyter and the Future of Interactive Computing. https://docs.jupyter.org/

  • Python Software Foundation. PEP 668 – Marking Python base environments as "externally managed". https://peps.python.org/pep-0668/

  • IAI. Notebook thực hành và học liệu thực hành 2627-1. Các yêu cầu kỹ thuật được đối chiếu, biên tập và bổ sung lời giải cùng dữ liệu minh họa. Phần thích nghi từ học liệu IAI áp dụng giấy phép CC BY-NC-SA 4.0.

Ảnh & sơ đồ
100%
Ảnh tài liệu được phóng to