06/04/2021, 14:46

Pivot Tables trong Pandas - 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ự đó ...

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.

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ệ:

In[1]
import pandas as pd
import numpy as np

titanic = pd.read_csv('./titanic.csv')

print(titanic)
Out[1]
     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):

In[2]
print(titanic.isna().sum())
Out[2]
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 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:

In[3]
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))
Out[3]
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:

In[4]
print("Tuổi trung bình theo khoang của những người trên tàu Titanic: 
", titanic.groupby(['class'])[['age']].mean())
Out[4]
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:

In[5]
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: 
", avg_age_by_class_sex)
Out[5]
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:

In[6]
print(avg_age_by_class_sex.unstack())
Out[6]
              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:

In[7]
print(titanic.pivot_table('age', index='sex', columns='class'))
Out[7]
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:

In[8]
print(titanic.pivot_table('survived', index='sex', columns='class'))
Out[8]
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:

In[9]
print(titanic.pivot_table('survived', index='sex', columns='class', margins=True))
Out[9]
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:

In[10]
print(titanic.pivot_table(['fare', 'survived'], index='sex', columns='class', aggfunc={'fare': 'sum', 'survived': 'mean'}, margins=True))
Out[10]
              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:

In[11]
print(titanic.pivot_table(['fare'], index='sex', columns='class', aggfunc='count', margins=True))
Out[11]
        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:

In[12]
print(titanic.pivot_table(['fare'], index='sex', columns='class', margins=True))
Out[12]
              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:

In[13]
# 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: 
", age.describe())

print("
Thống kê về độ tuổi, kiểu khoang với tỉ lệ sống sót: 
", titanic.pivot_table('survived', ['sex', age], 'class'))
Out[13]
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:

In[14]
age = pd.cut(titanic['age'], [0, 18, 80])
fare = pd.qcut(titanic['fare'], 2)

print(titanic.pivot_table('survived', ['sex', age], [fare, 'class']))
Out[14]
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é.

0