Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.
Pivot Tables trong Pandas
Trong bài này chúng ta sẽ tìm hiểu Pivot Tables trong Pandas, xem cách sử dụng Pivot Tables Pandas dưới đây.
Nếu như bạn đã từng làm việc với Excel hay các phần mềm xử lý bảng tính (spreadsheets) thì cũng không lạ gì với Pivot Table. Với Pandas thì thư viện cũng cung cấp một chức năng tương tự đó là phương thức pivot_table. Trong bài này chúng ta sẽ cùng đi tìm hiểu về Pivot Tables trong Pandas cũng như cách sử dụng của chúng.
1. Giới thiệu về Pivot Tables
Pivot Table là một trong những tính năng hữu dụng và mạnh mẽ nhất của Excel hoặc các phần mềm tương tự có mục đích dùng để tổng hợp, trích lọc, phân tích dữ liệu dễ dàng và nhanh chóng.
Trong bài trước, chúng ta đã cùng nhau tìm hiểu về GroupBy và tác dụng của nó. Với Pivot Table trong Pandas, bạn có thể hiểu rằng Pivot Table khá tương đồng với GroupBy: lấy dữ liệu theo chiều cột đơn dữ liệu đầu vào và nhóm các mục lại, đều áp dụng nguyên lý split-apply-combine giống nhau, tuy nhiên dữ liệu sẽ được phân tích và tổng hợp dưới dạng đa chiều (mulitdimensional) chứ không phải là một chiều.
Bài viết này được đăng tại [free tuts .net]
Giới thiệu về dữ liệu sử dụng
Trong bài này, chúng ta sẽ sử dụng bộ dữ liệu của vụ đắm tàu Titanic để làm ví dụ thao tác với Pivot Table, các bạn có thể download ở đây.
Đầu tiên, chúng ta import các thư viện và dữ liệu vào như thường lệ:
import pandas as pd import numpy as np titanic = pd.read_csv('./titanic.csv') print(titanic)
survived pclass sex age sibsp parch fare embarked class \ 0 0 3 male 22.0 1 0 7.2500 S Third 1 1 1 female 38.0 1 0 71.2833 C First 2 1 3 female 26.0 0 0 7.9250 S Third 3 1 1 female 35.0 1 0 53.1000 S First 4 0 3 male 35.0 0 0 8.0500 S Third .. ... ... ... ... ... ... ... ... ... 886 0 2 male 27.0 0 0 13.0000 S Second 887 1 1 female 19.0 0 0 30.0000 S First 888 0 3 female NaN 1 2 23.4500 S Third 889 1 1 male 26.0 0 0 30.0000 C First 890 0 3 male 32.0 0 0 7.7500 Q Third who adult_male deck embark_town alive alone 0 man True NaN Southampton no False 1 woman False C Cherbourg yes False 2 woman False NaN Southampton yes True 3 woman False C Southampton yes False 4 man True NaN Southampton no True .. ... ... ... ... ... ... 886 man True NaN Southampton no True 887 woman False B Southampton yes True 888 woman False NaN Southampton no False 889 man True C Cherbourg yes True 890 man True NaN Queenstown no True [891 rows x 15 columns]
Đầu tiên ta sẽ là kiểm tra xem có bao nhiêu hàng chứa NA (giá trị null):
print(titanic.isna().sum())
survived 0 pclass 0 sex 0 age 177 sibsp 0 parch 0 fare 0 embarked 2 class 0 who 0 adult_male 0 deck 688 embark_town 2 alive 0 alone 0 dtype: int64
Ta thấy ở cột age,deck và embark_town có chứa NA, trong nó cột deck có khá nhiều NA (688 / 891 cột).
Thông thường thì tuỳ vào mục đích cuối cùng của việc tổng hợp, thống kê dữ liệu mà ta sẽ có các phương án xử lý NA. Do đây là một bài hướng dẫn nên mình loại bỏ cột deck do có quá nhiều NA, còn lại mình sẽ xoá đi các hàng chứa NA khác:
print("Số hàng ban đầu: ", len(titanic)) # Xoá cột "deck" titanic.drop(labels='deck', axis=1, inplace=True) # Xoá bất cứ hàng nào chứa giá trị NA titanic.dropna(how='any', axis=0, inplace=True) print("Số hàng còn lại: ", len(titanic))
Số hàng ban đầu: 891 Số hàng còn lại: 712
Ta thấy số hàng còn lại là 712, khá đủ để chúng ta làm việc rồi.
Tổng hợp dữ liệu bằng GroupBy và Pivot Tables
Trong bài trước chúng ta đã học được cách tổng hợp dữ liệu bằng GroupBy. Giả sử chúng ta cần tìm tuổi trung bình của hành khác theo từng khoang ta thì ta có thể sử dụng GroupBy như sau:
print("Tuổi trung bình theo khoang của những người trên tàu Titanic: \n", titanic.groupby(['class'])[['age']].mean())
Tuổi trung bình theo khoang của những người trên tàu Titanic: age class First 38.105543 Second 29.877630 Third 25.140620
Dựa vào kết quả trên, ta có thể thấy là tuổi trung bình của hành khách giảm dần theo từng khoang. Khoang hạng nhất có độ tuổi khá lớn (~38 tuổi) còn khoang hạng ba thì nhìn chung trẻ hơn (~25 tuổi).
Trên mới chỉ là một kiểu tổng hợp khá đơn giản và ta cần một số ví dụ phức tạp hơn, chẳng hạn giờ ta cần tìm tuổi trung bình theo từng giới tính cụ thể ở từng khoang:
avg_age_by_class_sex = titanic.groupby(['sex', 'class'])[['age']].mean() print("Tuổi trung bình theo giới tính ở từng khoang: \n", avg_age_by_class_sex)
Tuổi trung bình theo giới tính ở từng khoang: age sex class female First 34.240964 Second 28.722973 Third 21.750000 male First 41.281386 Second 30.740707 Third 26.507589
Do đây là dữ liệu ở dạng MultiIndex, để biết được số chiều cụ thể cũng như trực quan hoá dữ liệu ta sẽ dùng phương thức unstack như đã nói ở các bài đầu tiên:
print(avg_age_by_class_sex.unstack())
age class First Second Third sex female 34.240964 28.722973 21.750000 male 41.281386 30.740707 26.507589
Trên tàu Titanic, nhìn chung độ tuổi của các hành khách ở khoang hạng nhất là những người trung niên, khá đúng vì đây cũng là độ tuổi mà phần lớn có sự nghiệp và kinh tế ổn định. Trái ngược lại là khoang hạng ba, hầu hết những người ở đây đều là những người trẻ.
Với việc phân tích như trên ta có thể thấy được độ tuổi phân bố cụ thể của từng nhóm giới tính theo từng khoang một cách khá trực quan, tuy nhiên vấn đề ở đây là để thao tác và xuất ra dữ liệu nhiều chiều với GroupBy thì code khá dài dòng và khó nắm bắt:
titanic.groupby(['sex', 'class'])[['age']].mean().unstack()
Để giải quyết việc này thì tất nhiên như nội dung trong bài này, ta sẽ dùng Pivot Tables. Dưới đây là đoạn code sử dụng Pivot Tables trả về giá trị tương đương về tuổi trung bình của hành khách giảm dần theo từng khoang:
print(titanic.pivot_table('age', index='sex', columns='class'))
class First Second Third sex female 34.240964 28.722973 21.750000 male 41.281386 30.740707 26.507589
Như ta có thể thấy việc sử dụng Pivot Tables trực quan hơn nhiều so với việc xử lý dữ liệu nhiều chiều bằng GroupBy. Trong phần tiếp theo chúng ta sẽ cùng nhau tìm hiểu về cú pháp cũng như cách sử dụng Pivot Tables.
2. Cú pháp và cách sử dụng Pivot Tables
Cú pháp của Pivot Tables
Dưới đây là cú pháp của Pivot Tables:
pandas.pivot_table(data, values=None, index=None, columns=None, aggfunc='mean', fill_value=None, margins=False, dropna=True, margins_name='All', observed=False)
- data: DataFrame
- values (optional): cột để tổng hợp
- index: cột, Group hoặc mảng. Nếu mảng được truyền vào thì phải có độ dài bằng với dữ liệu
- columns: cột, Group hoặc mảng. Tương tự như index
- aggfunc: (default: mean): hàm tổng hợp dữ liệu
- fill_value: giá trị được điền vào các ô dữ liệu NA (sau khi đã tính toán)
- margins: (bool, default: False). Thêm một cột tính tất cả các giá trị của các cột còn lại theo hàm tổng hợp
- margins_name (string, default: 'All'): Tên của cột margins
- dropna: (bool, default: False). Bỏ đi các hàng có chứa NA
- observed: (bool, default: False). Chỉ áp dụng nếu tất cả các nhóm dữ liệu đều là Categoricals (đọc thêm: Categorical data — pandas 1.2.0 documentation (pydata.org)) Nếu là True: chỉ hiển thị các giá trị quan sát được cho các nhóm phân loại. Nếu là False: hiển thị tất cả các giá trị cho các nhóm phân loại.
Một lưu ý nhỏ là ở dữ liệu trả về, các cấp trong Pivot Tables sẽ dùng MultiIndex để xác định chỉ mục và cột của DataFrame.
Sử dụng Pivot Tables
Sau khi đã tìm hiểu xong cú pháp của Pivot Tables thì ta sẽ đi đến một số ví dụ cụ thể. Đầu tiên nên là một ví dụ đơn giản, chẳng hạn như tính tỉ lệ sống sót của các khoang theo giới tính:
print(titanic.pivot_table('survived', index='sex', columns='class'))
class First Second Third sex female 0.963855 0.918919 0.460784 male 0.396040 0.151515 0.150198
Từ kết quả trên ta có thể thấy rằng phụ nữ ở khoang hạng nhất và hạng hai có tỉ lệ sống sót cao nhất (~96% và ~92%), còn nam giới ở khoang hạng hai và hạng ba có tỉ lệ sống sót thấp nhất (~15%).
Nếu như ta truyền thêm tham số margins=True vào ta sẽ có kết quả sau:
print(titanic.pivot_table('survived', index='sex', columns='class', margins=True))
class First Second Third All sex female 0.963855 0.918919 0.460784 0.752896 male 0.396040 0.151515 0.150198 0.205298 All 0.652174 0.479769 0.239437 0.404494
Cột All cho ta thấy tỉ lệ sống sót của nữ giới ở cả 3 khoang là 75%, với nam là 20%. Còn hàng All cho ta biết khoang thứ nhất tỉ lệ sống sót của hành khách là 65%, khoang thứ hai là 48% và khoang thứ 3 là 24%. Tổng kết ở điểm giao giữa hàng và cột All chỉ ra rằng tỉ lệ sống sót của tất cả các hành khách trên tàu là 40%. Kết quả trên còn cho ta thấy một điều khá thực tế: tỉ lệ sống sót của bạn phụ thuộc vào việc bạn ở khoang nào với việc tỉ lệ sống sót ở khoang hạng nhất gần gấp 3 lần khoang hạng 3.
Nhắc đến sự phân chia giai cấp này thì không thể không kể đến giá vé (cột fare trong dữ liệu). Trong Pivot Tables, chúng ta có thể truyền vào nhiều hơn 1 cột cũng như áp dụng từng hàm tổng hợp cụ thể lên các cột đó, chẳng hạn với kết quả trên ta muốn thêm vào tổng tiền đã chi cho vé ở từng khoang:
print(titanic.pivot_table(['fare', 'survived'], index='sex', columns='class', aggfunc={'fare': 'sum', 'survived': 'mean'}, margins=True))
fare survived \ class First Second Third All First Second sex female 9015.4334 1624.3792 1619.2876 12259.1002 0.963855 0.918919 male 7185.4209 2090.2000 3077.1619 12352.7828 0.396040 0.151515 All 16200.8543 3714.5792 4696.4495 24611.8830 0.652174 0.479769 class Third All sex female 0.460784 0.752896 male 0.150198 0.205298 All 0.239437 0.404494
Kết quả cho ta thấy rằng số tiền mà khoang hạng nhất chi nhiều gần gấp 4 lần ở cả khoang hạng hai và ba. Một điều mà bạn có thể để ý là khoang hạng hai lại có tổng số tiền vé ít hơn khoang hạng ba, ta có thể giải thích bằng cách xem số lượng hành khách trên từng khoang:
print(titanic.pivot_table(['fare'], index='sex', columns='class', aggfunc='count', margins=True))
fare class First Second Third All sex female 83 74 102 259 male 101 99 253 453 All 184 173 355 712
Số lượng hành khách ở khoang hạng 3 là khá nhiều, gần bằng cả hai khoang kia cộng lại. Để có một cái nhìn rõ nét về giá vé cho từng khoang thì ta có thể tính trung bình giá vé lên mỗi người ở từng khoang như sau:
print(titanic.pivot_table(['fare'], index='sex', columns='class', margins=True))
fare class First Second Third All sex female 108.619680 21.951070 15.875369 47.332433 male 71.142781 21.113131 12.162695 27.268836 All 88.048121 21.471556 13.229435 34.567251
Như ta thấy giá vé trung bình mà mỗi khách hạng nhất chi nhiều hơn hẳn so với hai khoang còn lại, và có một điều khá thú vị: khách nữ ở mọi khoang phải chi nhiều tiền hơn nam giới để có một tấm vé lên tàu Titanic.
Chúng ta sẽ cùng nhau đến với các ví dụ phức tạp hơn. Cũng giống như GroupBy, chúng ta có thể nhóm dữ liệu với nhiều cấp khác nhau và với các tuỳ chọn cụ thể. Chẳng hạn chúng ta muốn tìm tỉ lệ sống sót của 2 nhóm tuổi: 0-18 và trên 18:
# Dùng phương thức cut trong Pandas để chia dữ liệu titanic theo cột age age = pd.cut(titanic['age'], [0, 18, 80]) print("Mô tả về độ tuổi: \n", age.describe()) print("\nThống kê về độ tuổi, kiểu khoang với tỉ lệ sống sót: \n", titanic.pivot_table('survived', ['sex', age], 'class'))
Mô tả về độ tuổi: count 712 unique 2 top (18, 80] freq 573 Name: age, dtype: object # Mô tả cho ta thấy số người trên 18 tuổi chiếm khá nhiều với số lượng là 573 người Thống kê về độ tuổi, kiểu khoang với tỉ lệ sống sót: class First Second Third sex age female (0, 18] 0.909091 1.000000 0.511628 (18, 80] 0.972222 0.900000 0.423729 male (0, 18] 0.800000 0.600000 0.215686 (18, 80] 0.375000 0.071429 0.133663
Ở kết quả này cho ta thấy một kết quả khá thú vị: ở khoang hạng nhất nữ giới trên 18 tuổi có tỉ lệ sống sót cao hơn dưới 18 tuổi, tỉ lệ sống sót của nữ dưới 18 tuổi ở khoang hạng hai là 100%, và đặc biệt cho ta một con số đáng lưu tâm đó là tỉ lệ sống sót của nam giới trên 18 tuổi ở khoang hạng hai và hạng ba chỉ là 7% và 13%. Nếu như ai nằm trong hai nhóm này còn sống sót thì hẳn là rất may mắn.
Ngoài phương thức pd.cut để tách các giá trị trong cột ra thành từng khoảng, Pandas còn hỗ trợ một phương thức dùng để cắt là pd.qcut, phương thức này sẽ tự động cắt ra những khoảng giá trị với tần số xuất hiện như nhau, chẳng hạn:
print(pd.qcut([24, 7, 2, 25, 22, 29], 3).value_counts())
(1.999, 17.0] 2 (17.0, 24.333] 2 (24.333, 29.0] 2 dtype: int64
Sử dụng phương thức này, ta có thể tính toán sự tương quan giữa giá vé, độ tuổi, giới tính với tỉ lệ sống sót như sau:
age = pd.cut(titanic['age'], [0, 18, 80]) fare = pd.qcut(titanic['fare'], 2) print(titanic.pivot_table('survived', ['sex', age], [fare, 'class']))
fare (-0.001, 15.646] (15.646, 512.329] \ class First Second Third First sex age female (0, 18] NaN 1.000000 0.652174 0.909091 (18, 80] NaN 0.884615 0.435897 0.972222 male (0, 18] NaN 0.200000 0.260870 0.800000 (18, 80] 0.0 0.094340 0.122222 0.391304 fare class Second Third sex age female (0, 18] 1.000000 0.350000 (18, 80] 0.911765 0.400000 male (0, 18] 0.800000 0.178571 (18, 80] 0.032258 0.227273
Ta có thể thấy sử dụng Pivot Tables cho phép chúng ta xử lý từ những tác vụ tính toán đơn giản đến phức tạp (như kết quả trên dữ liệu ở dạng 4 chiều).
3. Tổng kết
Trong bài này chúng ta đã tìm hiểu về Pivot Tables trong Pandas. Đây thực sự là một tính năng rất hữu ích và vô cùng mạnh mẽ, giúp ta tìm mối quan hệ giữa dữ liệu ở nhiều chiều khác nhau trong khi chỉ cần sử dụng những cú pháp khá đơn giản.
Với bộ dữ liệu về Titanic trên vẫn còn nhiều thao tác khá thú vị, bạn nên tìm hiểu và tìm ra thêm những mối quan hệ phức tạp hơn để có thêm kinh nghiệm về Pivot Tables nhé. Bộ dữ liệu này vẫn còn khá đơn giản, chúng ta sẽ làm quen với những dữ liệu phức tạp hơn trong các bài sau. Hẹn gặp bạn ở đó nhé.