Khóa học
Pandas là bộ công cụ mặc định cho các nhà khoa học dữ liệu Python để đơn giản hóa phân tích dữ liệu: bạn có thể dùng nó, chẳng hạn, trước khi bắt đầu phân tích để thu thập, khám phá và định dạng dữ liệu. Pandas giúp những bước này trở nên nhẹ nhàng với vô số hàm I/O và thao tác dữ liệu tiện lợi. Hơn nữa, như bạn sẽ thấy trong phần sau của hướng dẫn này, nó cũng có khả năng trực quan hóa (thông qua matplotlib).
Mặc dù các cấu trúc dữ liệu của Pandas rất mạnh và linh hoạt, chúng cũng có một số phức tạp có thể khiến việc phân tích trở nên khó khăn hơn, đặc biệt với người mới bắt đầu. Trong hướng dẫn này, bạn sẽ tập trung vào cách tự “giải toả đau đầu” và viết mã Pandas mang tính idiomatic hơn. Bên cạnh việc học cách tải, khám phá và làm sạch dữ liệu, bạn sẽ tìm hiểu sâu hơn về năm chủ đề sau:
Tất nhiên, hướng dẫn này không hề bao quát toàn bộ; Gói Pandas rất phong phú và chắc chắn còn nhiều cách khác để bạn cải thiện mã Pandas để trở nên idiomatic hơn.
Đừng chần chờ nữa, bắt đầu thôi!
Bước đầu: Tải và khám phá dữ liệu
Như thường lệ, phân tích dữ liệu của bạn bắt đầu bằng việc tải và khám phá dữ liệu. Phần này là khởi động: bạn sẽ dùng hàm read_csv() để tải bảng xếp hạng các trường đại học trên thế giới, sau đó nhanh chóng khám phá dữ liệu bằng các hàm head() và describe(). Nếu bạn muốn học phần nhập môn đầy đủ về Pandas, hãy cân nhắc khóa học ba phần của DataCamp, bắt đầu với Pandas Foundations.
Nếu bạn đã biết các nội dung này, bạn có thể bỏ qua phần này và xem cách thực hiện indexing với Pandas theo hướng idiomatic hơn.
Nhập các gói
Để bắt đầu, hãy nhập các gói Python quen thuộc cho khoa học dữ liệu. Như bạn có thể đoán, Pandas nằm trong số đó.
# If you're working with a notebook, don't forget to use Matplotlib magic!
%matplotlib inline
# Import pandas under the alias pd import pandas as pd # Import seaborn under the alias sns import seaborn as sns # Set the Seaborn theme if desired sns.set_style('darkgrid')
Ngoài ra, bạn cũng có thể dùng gói watermark: đây là công cụ hay giúp sổ tay (notebook) của bạn dễ tái lập hơn. Tính tái lập rất quan trọng khi cộng tác với đồng nghiệp. Bản thân bạn trong tương lai cũng sẽ cảm ơn bạn! Bạn có thể đọc thêm về khoa học dữ liệu có thể tái lập tại đây.
Dữ liệu
Để bắt đầu hướng dẫn này, bạn cũng cần lấy một ít dữ liệu “thực”. Trong hướng dẫn này, bạn sẽ làm việc với một bộ dữ liệu từ Kaggle. Nếu bạn chưa quen, Kaggle là một trong những cộng đồng lớn nhất về khoa học dữ liệu và học máy. Ngoài ra, đây cũng là nơi tốt để học thêm về khoa học dữ liệu khi bạn đã nắm được các nền tảng.
Bộ dữ liệu bạn sẽ dùng là World University Rankings.
Như đã đề cập, Pandas có nhiều phương thức tiện lợi để đọc dữ liệu từ các nguồn khác nhau (bạn có thể tìm hiểu thêm tại đây). Vì dữ liệu ở định dạng CSV, chúng ta sẽ dùng phương thức .read_csv. Nhưng trước tiên, bạn cần tải dữ liệu từ Kaggle (hoặc lấy từ kho mã nguồn trong thư mục data nếu bạn không muốn tạo tài khoản).
Sau khi tải dữ liệu, bạn sẽ có một thư mục data chứa các tệp csv khác nhau.
Tiếp theo, bạn sẽ làm việc với dữ liệu xếp hạng của Times Higher Education World University Rankings (gọi tắt là Times) và Academic Ranking of World Universities (gọi tắt là Shanghai). Có một hệ thống xếp hạng thứ ba trong thư mục data là CWUR nhưng ít được biết đến hơn, nên bạn có thể tạm thời bỏ qua.
# Import Times Higher Education World University Rankings data
times_df = pd.read_csv('data/timesData.csv', thousands=",")
# Import Academic Ranking of World Universities data
shanghai_df = pd.read_csv('data/shanghaiData.csv')
Khám sát dữ liệu nhanh
Như bạn đã học trong hướng dẫn Phân tích Khám phá (EDA) của DataCamp, Pandas cung cấp các phương thức để kiểm tra nhanh DataFrame, cụ thể là .head() để xem n dòng đầu (n mặc định là 5) và .describe() để lấy thống kê tóm tắt nhanh.
Ôn lại các hàm này bằng cách chạy các dòng mã sau. Dữ liệu đã được nạp sẵn vào các biến times_df và shanghai_df:
Bây giờ bạn đã tải dữ liệu, hãy bắt đầu nghĩ về cách thao tác dữ liệu với Pandas theo hướng idiomatic!
Mẹo viết mã Pandas theo chuẩn idiomatic
1. Indexing
Trước hết, viết mã Pandas mang tính idiomatic hơn đồng nghĩa với việc tận dụng sức mạnh của indexing. Indexing có nghĩa là bạn chọn các tập con từ DataFrame của mình.
Khi mới bắt đầu với Pandas, bạn có thể cần thời gian để quen với cách hoạt động của chỉ mục. Thực tế, nếu bạn đã làm việc với danh sách (list) trong Python, bạn có thể đã quen với việc dùng ngoặc vuông [] kết hợp với dấu hai chấm : để chọn phần tử. Cách này cũng dùng được trên DataFrame.
Ngoài ra, có hai cách khác idiomatic hơn để chọn tập con của DataFrame, đó là iloc và loc:
locdựa trên nhãn (label-based). Nghĩa là nếu bạn viếtloc[2], bạn đang tìm các giá trị có chỉ mục được gắn nhãn2.ilocdựa trên vị trí (position-based). Nghĩa là nếu bạn viếtiloc[2], bạn đang tìm các giá trị ở vị trí chỉ mục2.
Hãy thử trong khối DataCamp Light bên dưới bằng cách gõ times_df.loc[2] và times_df.iloc[2] trong bảng điều khiển IPython để thấy sự khác biệt. Sau đó, thử giải bài tập phía dưới với sự trợ giúp của loc, iloc và ngoặc vuông truyền thống!
Tất nhiên, việc khám phá có thể đi xa hơn nhiều so với chỉ lọc hay chọn hàng và cột. Mọi thứ sẽ thú vị hơn khi bạn kết hợp loc và iloc với, chẳng hạn, mảng boolean.
Bạn thấy rằng điều này vượt ra ngoài việc chọn dữ liệu theo cột hay chỉ mục; Nó khá giống như đang truy vấn dữ liệu!
loc và iloc rất hữu ích để đánh chỉ mục dữ liệu, nhưng hãy cẩn thận khi bạn bắt đầu dùng hai cặp ngoặc vuông liên tiếp! Pandas có thể trả về một bản sao của một view và trong những trường hợp như vậy, bạn sẽ không biết mình đang làm việc với gì! Hãy cân nhắc kỹ khi dùng hai cặp ngoặc vuông kết hợp với loc hoặc iloc.
Lưu ý thêm rằng để truy vấn dữ liệu, Pandas cũng cung cấp hàm query() giúp bạn kiểm tra nhanh dữ liệu (có từ phiên bản 0.13). Ví dụ, bạn có thể soạn truy vấn để xem những trường đại học có total_score hơi dưới 50:
Tất nhiên, còn rất nhiều khả năng khi truy vấn dữ liệu! Sao bạn không thử nghĩ ra vài truy vấn và kiểm tra chúng trong bảng điều khiển IPython của khối DataCamp Light ở trên?
Nếu thiếu ý tưởng, hãy thử các truy vấn trả về các trường có hạng quốc gia đứng đầu và hạng thế giới đứng đầu. Ngoài ra, bạn có thể truy vấn số lượng cựu sinh viên (alumni) của các trường đại học.
Bạn bối rối khi soạn truy vấn? Xem một số ví dụ dưới đây:
shanghai_df.query("national_rank == 1 and world_rank == 1")
shanghai_df.query("alumni < 20")
2. Xâu chuỗi phương thức
Method chaining là đặc trưng của Pandas, nhưng khi mới bắt đầu với gói thao tác dữ liệu Python này, có thể bạn chưa thấy rõ cách hoạt động. Về cơ bản, đó là gọi liên tiếp các phương thức trên một đối tượng.
Trong phần này, bạn sẽ đi sâu vào method chaining bằng cách tạo các pipeline dữ liệu với pipe().
Như bạn đã thấy, các bộ dữ liệu khá khác nhau. Một vài khác biệt:
- Bộ Times có 14 cột trong khi Shanghai có 11
- Bộ Times có 2603 hàng còn Shanghai có 4897
- Một cột lớn bị thiếu ở Shanghai là quốc gia của trường đại học. Nếu cần, bạn sẽ lấy thông tin này từ bộ Times sau.
Do đó, để dùng cả hai DataFrame, ta chỉ chọn các cột chung. Hy vọng các cột chung này có nội dung tương đồng.
Các cột chung là:
total_score: Điểm dùng để xác định hạng. Như mô tả trên trang Kaggle, chúng có các hạng dạng khoảng (tức là giữa hai giá trị) và hạng bằng nhau (có dấu=phía trước)university_name: Tên trường đại họcworld_rank: Hạng của trườngyear: Năm xếp hạng
Để nối hai bộ dữ liệu, ta cần làm sạch trước. Hãy áp dụng toàn bộ pipeline để làm sạch dữ liệu:
Chắc bạn đã để ý, tôi dùng phương thức .pipe rất nhiều trong hướng dẫn này. Nếu bạn đến từ thế giới R, có lẽ bạn sẽ thấy quen thuộc phần nào.
Đây là một phương thức tiện lợi mới của Pandas (từ phiên bản 0.16.2) cho phép bạn xâu chuỗi các thao tác và loại bỏ nhu cầu tạo các DataFrame trung gian. Mã của bạn trở nên dễ đọc hơn.
Thực tế, nếu không có toán tử này, thay vì viết df.pipe(f).pipe(g).pipe(h) bạn sẽ viết: h(g(f(df))). Khi số hàm lồng nhau tăng, việc theo dõi sẽ khó hơn nhiều.
Để tìm hiểu thêm, tôi rất khuyến nghị đọc bài viết sau.
Giờ cả hai DataFrame đã sạch, chúng ta có thể nối chúng thành một DataFrame duy nhất. Đồng thời, đừng quên làm sạch dữ liệu khuyết:
Vì khoảng 38% dữ liệu bị thiếu ở cột total_score, tốt hơn là loại bỏ cột này bằng phương thức .drop.
3. Tối ưu bộ nhớ
Một cách thứ ba để làm cho mã Pandas của bạn “idiomatic” hơn — ở đây cũng đồng nghĩa “hiệu năng” hơn — là tối ưu sử dụng bộ nhớ. Đặc biệt khi bạn bắt đầu tạo pipeline dữ liệu bằng method chaining, bạn sẽ thấy bộ nhớ có thể ảnh hưởng lớn đến tốc độ chạy pipeline. Bạn sẽ thấy thêm về điều này tại đây.
Có thể hơi thừa với bộ dữ liệu nhỏ này nhưng thường thì việc ép kiểu một số cột về kiểu cụ thể để tối ưu bộ nhớ là thực hành tốt. Bắt đầu bằng cách kiểm tra lượng bộ nhớ phân bổ cho ranking_df bằng phương thức .info.
Kết quả dòng mã đầu tiên cho thấy dữ liệu chiếm 144 KB. Tuy nhiên, khi xem các tham số của phương thức .info, bạn sẽ thấy một tham số tùy chọn là memory_usage mặc định là None. Điều gì xảy ra khi bạn đặt memory_usage thành deep?
Dấu chân bộ nhớ mới lớn hơn 6 lần — chính xác là 5.6 — so với kết quả ban đầu.
Chuyện gì đã xảy ra?
Sự khác biệt trong ước tính bộ nhớ đến từ việc nếu không bật cờ “deep”, Pandas sẽ không ước tính mức dùng bộ nhớ cho kiểu object. Kiểu (Python) này chiếm nhiều chỗ hơn so với các dtype tối ưu của numpy. Vì vậy, nên ép kiểu object sang kiểu phù hợp hơn (ví dụ category khi xử lý dữ liệu phân loại).
Hãy làm điều đó!
def memory_change(input_df, column, dtype):
df = input_df.copy()
old = round(df[column].memory_usage(deep=True) / 1024, 2) # In KB
new = round(df[column].astype(dtype).memory_usage(deep=True) / 1024, 2)# In KB
change = round(100 * (old - new) / (old), 2)
report = ("The inital memory footprint for {column} is: {old}KB.\n"
"The casted {column} now takes: {new}KB.\n"
"A change of {change} %.").format(**locals())
return report
print(memory_change(ranking_df,'world_rank', 'int16'))
print(memory_change(ranking_df,'university_name', 'category'))
print(memory_change(ranking_df,'name', 'category'))
Giờ bạn biết rằng, bằng cách đổi world_rank sang int16 chẳng hạn, bộ nhớ sẽ được dùng hiệu quả hơn, đã đến lúc áp dụng. Bạn có thể dùng hàm astype() để làm việc này. Kết thúc bằng việc kiểm tra lại mức dùng bộ nhớ hiện tại:
Đã tốt hơn nhiều. Bạn có thể tối ưu thêm bằng cách ép cột year xuống int32.
4. GroupBy
Bây giờ chúng ta có một bộ dữ liệu được định hình tốt (tức là ở trạng thái tidy) với dấu chân bộ nhớ tối ưu, có thể bắt đầu phân tích.
Có vài câu hỏi bạn có thể quan tâm:
- Top 5 trường đại học theo mỗi hệ thống xếp hạng qua các năm là những trường nào?
- Các bảng xếp hạng này khác nhau thế nào qua từng năm?
Hãy bắt đầu với câu hỏi đầu tiên bằng cách dùng các điểm gợi ý Pandas idiomatic mà bạn đã thấy ở các phần trước!
Trước khi bắt đầu, lưu ý rằng ‘Massachusetts Institute of Technology (MIT)’ và ‘Massachusetts Institute of Technology’ là hai bản ghi khác nhau của cùng một trường. Do đó, bạn đổi tên thứ nhất thành tên thứ hai.
Lưu ý rằng trong các trường hợp như vậy, hàm loc mà bạn đã thấy trước đó đặc biệt hữu ích!
Để tìm 5 (nói chung là n) trường hàng đầu qua các năm cho mỗi hệ thống xếp hạng, làm như giả mã sau:
- Với mỗi năm (trong cột
year) và mỗi hệ thống xếp hạng (trong cộtname): - Chọn tập con dữ liệu cho năm và hệ thống đó
- Chọn 5 trường đứng đầu và lưu vào danh sách
- Lưu kết quả vào từ điển với khóa (year, name) và giá trị là danh sách các trường (theo thứ tự giảm dần)
Hãy áp dụng.
Giờ đã có từ điển xếp hạng, hãy xem hai phương pháp xếp hạng khác nhau bao nhiêu (tính theo phần trăm) qua các năm: hai phương pháp giống tập hợp 100% nếu 5 trường được chọn trùng nhau, dù thứ tự xếp hạng có thể khác.
Như bạn thấy, cách này khá mất công. Có cách nào idiomatic hơn với Pandas không?
Tất nhiên là có! Trước hết, lọc DataFrame xếp hạng để chỉ giữ 5 trường hàng đầu cho mỗi năm và mỗi phương pháp xếp hạng. Kiểm tra kết quả với head():
Giờ bạn đã có DataFrame phù hợp để làm việc, hãy dùng phương thức .groupby để so sánh hai phương pháp xếp hạng bằng độ tương đồng tập hợp như đã định nghĩa.
Bạn nhận được cùng kết quả như trên nhưng bớt phiền hơn.
5. Trực quan hóa
Sau khi đã thực hiện một số phân tích ban đầu, đã đến lúc khám phá dữ liệu trực quan. Mã Pandas idiomatic cũng có nghĩa là bạn tận dụng tích hợp vẽ biểu đồ của Pandas với Matplotlib. Nhờ vậy, bạn sẽ tạo được biểu đồ đẹp trong thời gian ngắn!
Ở phần này, bạn sẽ quay lại hai bộ times_df và shanghai_df để tạo vài trực quan cơ bản với Matplotlib và Seaborn.

Biểu đồ trên cho thấy ngay rằng có một số giá trị 0 ở cột alumni. Đây là điều chắc chắn cần lưu ý khi bạn khám phá dữ liệu: với sự trợ giúp của data profiling, bạn sẽ thấy các giá trị 0 này và biết cách xử lý. Nếu muốn biết thêm, xem Hướng dẫn Data Profiling của DataCamp.
Hoặc, nếu bạn có các giá trị không phù hợp trong bộ dữ liệu, bạn có thể đơn giản thay chúng bằng NaN rồi loại bỏ toàn bộ giá trị NaN ở các cột khiến việc vẽ khó khăn hơn. Lưu ý đây chỉ là cách điều chỉnh nhanh và có lẽ bạn cần bước data profiling kỹ hơn để đảm bảo chất lượng dữ liệu vì ở biểu đồ dưới vẫn còn giá trị 0 ở cột num_students:

Tất nhiên, bạn không cần giới hạn ở Matplotlib để trực quan hóa. Cũng có các thư viện khác giúp bạn nhanh chóng trực quan dữ liệu, như Seaborn.
Công cụ vẽ của Seaborn chủ yếu dùng để tạo các biểu đồ thống kê đẹp mắt (như trên trang chính thức). Thực tế, trước phiên bản 2.0 của Matplotlib, việc tạo biểu đồ đẹp khá vất vả (dù vẫn có thể).
Kết hợp Pandas với Seaborn cũng cho phép bạn lặp nhanh qua dữ liệu bằng các trực quan.
Hãy xem một vài ví dụ!
Xem biểu đồ sau: bạn đếm số lần một quốc gia cụ thể xuất hiện trong dữ liệu, lấy 10 quan sát đầu tiên và đảm bảo sắp xếp số đếm từ cao xuống thấp khi vẽ barplot:

Sau đó, bạn có thể vẽ các quốc gia này cùng điểm trung bình của chúng:

Biểu đồ của bạn cũng có thể khá phức tạp. Xem cặp biểu đồ (pairplot) sau, được thiết kế để hiển thị rõ hơn các mối quan hệ cặp trong một bộ dữ liệu:
import numpy as np
np.seterr(invalid='ignore')
sns.pairplot(times_df, hue='country')
plt.show()

Tương tự, bạn có thể xem kết hợp FacetGrid và regplot dưới đây, cho thấy một số hiểu biết về diễn biến total_score theo năm cho 5 quốc gia hàng đầu:

Cuối cùng, biểu đồ tương quan cũng hữu ích khi bạn khám phá dữ liệu. Bạn có thể dùng hàm heatmap() của Seaborn để thực hiện:

Bạn có thể tiếp tục mãi với các trực quan này, nên có lẽ tạm dừng ở đây :)
Các bước tiếp theo
Vậy là xong cho lần này, hy vọng bạn thích học một số kỹ thuật trung cấp khi làm việc với Pandas và cụ thể là cách viết mã Pandas mang tính idiomatic hơn.
Nếu bạn thích bài viết này và muốn đào sâu về hiệu năng trong Pandas, tôi rất khuyến nghị các tài liệu sau:
- Hướng dẫn Modern Pandas của Tom Augspurger,
- Bài nói Pandas From The Inside của Stephen Simmons, và
- Nếu bạn muốn tìm hiểu thêm cách hiện thực thao tác groupby bằng các công cụ Python khác, rất nên đọc bài viết sau.