Tự Học Data Science · 17/12/2023 0

03.09 Pivot Tables

Khuyến khích Bảng xoay

Cho các ví dụ trong phần này, chúng tôi sẽ sử dụng cơ sở dữ liệu hành khách trên chiếc tàu Titanic, có sẵn thông qua thư viện Seaborn (xem Visualation With Seaborn):

import numpy as npimport pandas as pdimport seaborn as snstitanic = sns.load_dataset('titanic')
titanic.head()

Đoạn mã HTML này chứa một số thông tin quý giá về từng hành khách trên chuyến đi định mệnh đó, bao gồm giới tính, tuổi, hạng vé, giá vé trả, và nhiều thông tin khác.

Bảng tổng hợp bằng tay

Để bắt đầu học thêm về dữ liệu này, chúng ta có thể bắt đầu bằng cách nhóm theo giới tính, trạng thái sống còn hoặc một sự kết hợp nào đó.Nếu bạn đã đọc phần trước, bạn có thể cảm thấy muốn áp dụng một hoạt động GroupBy, ví dụ: hãy xem tỷ lệ sống sót theo giới tính:

titanic.groupby('sex')[['survived']].mean()

Điều này ngay lập tức mang lại cho chúng ta một số hiểu biết: tổng cộng, ba trên bốn nữ giới được lên tàu đã sống sót, trong khi chỉ có một trong năm nam giới sống sót!

Điều này rất hữu ích, nhưng chúng ta có thể muốn đi sâu hơn và xem xét sự sống còn theo cả giới tính và, ví dụ, lớp học.Bằng từ vựng của GroupBy, chúng ta có thể tiếp tục bằng cách sử dụng một cái gì đó như thế này:chúng ta nhóm theo lớp và giới tính, chọn sự sống còn, áp dụng một phép tổng trung bình, kết hợp các nhóm kết quả, và sau đó unstack chỉ số phân cấp để tiết lộ đa chiều ẩn. Trong code:

titanic.groupby(['sex', 'class'])['survived'].aggregate('mean').unstack()

Điều này cho chúng ta một cái nhìn tốt hơn về cách cả giới tính và tầng lớp ảnh hưởng đến khả năng sống sót, nhưng mã này đang trở nên hơi lộn xộn một chút.Mặc dù mỗi bước trong thiết lập này có ý nghĩa trong ngữ cảnh của các công cụ đã được thảo luận trước đó, chuỗi mã dài không được dễ đọc hoặc sử dụng.Hai chiều GroupBy này là khá phổ biến trong Pandas, nên thư viện đã bao gồm một công cụ thuận tiện, pivot_table, giúp xử lý tổng hợp đa chiều như này một cách ngắn gọn.

Cú pháp Bảng lật dữ liệu

Đây là cách tương đương với phép toán trước bằng cách sử dụng phương thức pivot_table của DataFrame:

titanic.pivot_table('survived', index='sex', columns='class')

Đoạn mã này rất dễ đọc hơn phương pháp groupby, và tạo ra cùng kết quả.Như bạn có thể mong đợi của một du thuyền xuyên Đại Tây Dương vào đầu thế kỷ 20, đường dốc tỉ lệ tồn tại thuận lợi cho cả phụ nữ và các tầng lớp cao hơn.Phụ nữ ở hạng nhất sống sót với khả năng gần như chắc chắn (xin chào, Rose!), trong khi chỉ có một trong mười người đàn ông hạng ba sống sót (xin lỗi, Jack!).

Bảng tổng hợp đa cấp

Giống như trong GroupBy, việc nhóm trong các bảng pivot có thể được xác định với nhiều cấp độ và thông qua một số tùy chọn.Ví dụ, chúng ta có thể quan tâm đến tuổi như một chiều thứ ba.Chúng ta sẽ chia nhóm tuổi bằng cách sử dụng hàm pd.cut:

age = pd.cut(titanic['age'], [0, 18, 80])titanic.pivot_table('survived', ['sex', age], 'class')

Chúng ta có thể áp dụng chiến lược tương tự khi làm việc với cột; hãy thêm thông tin về giá vé đã trả bằng cách sử dụng pd.qcut để tự động tính toán các phân vị:

fare = pd.qcut(titanic['fare'], 2)titanic.pivot_table('survived', ['sex', age], [fare, 'class'])

Kết quả là một tổng hợp bốn chiều với các chỉ mục phân cấp (xem Hierarchical Indexing), được hiển thị trong một lưới minh họa mối quan hệ giữa các giá trị.

Các tùy chọn bổ sung cho bảng tổng hợp

Dạng đầy đủ của chữ ký cuộc gọi của phương thức pivot_table của đối tượng DataFrame là như sau:

# call signature as of Pandas 0.18DataFrame.pivot_table(data, values=None, index=None, columns=None,                      aggfunc='mean', fill_value=None, margins=False,                      dropna=True, margins_name='All')

Chúng ta đã thấy ví dụ về ba đối số đầu tiên; ở đây chúng ta sẽ xem nhanh các đối số còn lại.Hai trong số các tùy chọn, fill_valuedropna, liên quan đến dữ liệu thiếu và khá đơn giản; chúng tôi sẽ không hiển thị ví dụ về chúng ở đây.

Tham số aggfunc điều khiển loại tổng hợp được áp dụng, mặc định là trung bình.Tương tự như trong GroupBy, thông số tổng hợp có thể là một chuỗi đại diện cho một số lựa chọn phổ biến (ví dụ như 'sum', 'mean', 'count', 'min', 'max', v.v.) hoặc một hàm thực hiện tổng hợp (ví dụ như np.sum(), min(), sum(), v.v.).Ngoài ra, nó có thể được chỉ định dưới dạng một từ điển ánh xạ cột tới bất kỳ tùy chọn nào được trên:

titanic.pivot_table(index='sex', columns='class',                    aggfunc={'survived':sum, 'fare':'mean'})

Lưu ý ở đây là chúng tôi đã bỏ qua từ khóa values; khi chỉ định một ánh xạ cho aggfunc, điều này được xác định tự động.

Đôi khi việc tính tổng trên mỗi nhóm là rất hữu ích. Điều này có thể được thực hiện thông qua từ khóa margins:

titanic.pivot_table('survived', index='sex', columns='class', margins=True)

Tại đây, điều này tự động cung cấp cho chúng ta thông tin về tỷ lệ sống sót không phụ thuộc vào lớp theo giới tính, tỷ lệ sống sót không phụ thuộc vào giới tính theo lớp, và tỷ lệ sống sót tổng thể là 38%.Nhãn margin có thể được chỉ định bằng từ khóa margins_name, mặc định là "All".

Ví dụ: Dữ liệu về tỷ lệ sinh

Như một ví dụ thú vị hơn, chúng ta hãy xem vào dữ liệu về số sinh tại Hoa Kỳ, được cung cấp miễn phí bởi Trung tâm Kiểm soát và Phòng ngừa Bệnh tật (CDC).Dữ liệu này có thể được tìm thấy tại https://raw.githubusercontent.com/jakevdp/data-CDCbirths/master/births.csv(tập dữ liệu này đã được phân tích khá khái quát bởi Andrew Gelman và nhóm nghiên cứu của ông; xem, ví dụ như bài đăng trên blog này):

# shell command to download the data:# !curl -O https://raw.githubusercontent.com/jakevdp/data-CDCbirths/master/births.csv
births = pd.read_csv('data/births.csv')

Thăm dò dữ liệu, chúng ta thấy rằng nó tương đối đơn giản – nó chứa số lượng sinh theo ngày và giới tính:

births.head()

Chúng ta có thể bắt đầu hiểu dữ liệu này một chút nữa bằng cách sử dụng một bảng tổng hợp.Hãy thêm một cột thập kỷ và xem xét số lượng sinh nam và sinh nữ theo thập kỷ:

births['decade'] = 10 * (births['year'] // 10)births.pivot_table('births', index='decade', columns='gender', aggfunc='sum')

Chúng ta ngay lập tức nhận thấy số lượng trẻ sinh nam vượt trội so với trẻ sinh nữ trong mỗi thập kỷ.Để nhìn thấy xu hướng này rõ hơn một chút, chúng ta có thể sử dụng các công cụ vẽ đồ thị tích hợp sẵn trong Pandas để minh họa tổng số trẻ sinh theo năm (xem Giới thiệu về Matplotlib để thảo luận về việc vẽ đồ thị với Matplotlib):

%matplotlib inlineimport matplotlib.pyplot as pltsns.set()  # use Seaborn stylesbirths.pivot_table('births', index='year', columns='gender', aggfunc='sum').plot()plt.ylabel('total births per year');
ảnh ví dụ - data science lại blog của lưu

Với một bảng dữ liệu dễ dùng và phương thức plot(), chúng ta có thể ngay lập tức nhìn thấy xu hướng hàng năm về số trẻ sinh theo giới tính. Bằng cách nhìn bằng mắt, có vẻ trong 50 năm qua, số trẻ sinh nam vượt qua số trẻ sinh nữ khoảng 5%.

Sự khám phá dữ liệu sâu hơn

Mặc dù điều này không liên quan trực tiếp đến bảng xoay, nhưng chúng ta có thể trích xuất một số tính năng thú vị khác từ bộ dữ liệu này bằng cách sử dụng các công cụ Pandas đã được đề cập đến ở mức độ này.Chúng ta phải bắt đầu bằng cách làm sạch dữ liệu một chút, loại bỏ các giá trị ngoại lệ do nhập sai ngày (ví dụ như ngày 31 tháng 6) hoặc giá trị bị thiếu (ví dụ như ngày 99 tháng 6).Một cách dễ dàng để loại bỏ tất cả những giá trị này cùng một lúc là từ chối giá trị ngoại lệ; chúng tôi sẽ thực hiện điều này thông qua một hoạt động cắt bỏ sigma mạnh:

quartiles = np.percentile(births['births'], [25, 50, 75])mu = quartiles[1]sig = 0.74 * (quartiles[2] - quartiles[0])

Đoạn mã HTML trên chứa một đoạn dịch tiếng Việt về xác suất và thống kê trong ngành thiên văn học, đề cập đến một cuốn sách có liên quan. Tại đây, tôi sẽ giữ nguyên cấu trúc HTML, không thay đổi thẻ nào và giữ nguyên giá trị của các thuộc tính. Tuy nhiên, để giữ nguyên các thuật ngữ IT thông dụng bằng tiếng Anh (như package, command-line,…), tôi sẽ không dịch chúng ra tiếng Việt.

Với điều này, chúng ta có thể sử dụng phương thức query() (được thảo luận thêm trong High-Performance Pandas: eval()query()) để lọc các hàng có số lần sinh ra nằm ngoài các giá trị này:

births = births.query('(births > @mu - 5 * @sig) & (births < @mu + 5 * @sig)')

Tiếp theo, chúng ta đặt cột day thành số nguyên; trước đó, cột này đã là một chuỗi vì một số cột trong bộ dữ liệu chứa giá trị 'null':

# set 'day' column to integer; it originally was a string due to nullsbirths['day'] = births['day'].astype(int)

Cuối cùng, chúng ta có thể kết hợp ngày, tháng và năm để tạo một chỉ mục Ngày (xem Làm việc với Chuỗi Thời gian).Điều này cho phép chúng ta nhanh chóng tính toán ngày trong tuần tương ứng với mỗi hàng:

# create a datetime index from the year, month, daybirths.index = pd.to_datetime(10000 * births.year +                              100 * births.month +                              births.day, format='%Y%m%d')births['dayofweek'] = births.index.dayofweek

Sử dụng điều này, chúng ta có thể vẽ biểu đồ về số ngày sinh vào mỗi ngày trong tuần trong một số thập kỷ:

import matplotlib.pyplot as pltimport matplotlib as mplbirths.pivot_table('births', index='dayofweek',                    columns='decade', aggfunc='mean').plot()plt.gca().set_xticklabels(['Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat', 'Sun'])plt.ylabel('mean births by day');
ảnh ví dụ - data science lại blog của lưu

Có vẻ như việc sinh con ít phổ biến hơn vào cuối tuần so với các ngày trong tuần! Lưu ý rằng những năm 1990 và 2000 bị thiếu vì dữ liệu của CDC chỉ chứa thông tin về tháng sinh bắt đầu từ năm 1989.

Một quan điểm thú vị khác là vẽ biểu đồ số lượng trung bình của các sinh ra theo ngày trong năm.Hãy trước tiên nhóm dữ liệu theo tháng và ngày riêng biệt:

births_by_date = births.pivot_table('births',                                     [births.index.month, births.index.day])births_by_date.head()
1  1    4009.225   2    4247.400   3    4500.900   4    4571.350   5    4603.625Name: births, dtype: float64

Kết quả là một đa chỉ mục trên các tháng và ngày.Để làm cho việc vẽ đồ thị dễ dàng, hãy chuyển các tháng và ngày này thành một ngày bằng cách kết hợp chúng với một biến năm giả (đảm bảo chọn một năm nhuận để xử lý chính xác ngày 29 tháng 2!)

births_by_date.index = [pd.datetime(2012, month, day)                        for (month, day) in births_by_date.index]births_by_date.head()
2012-01-01    4009.2252012-01-02    4247.4002012-01-03    4500.9002012-01-04    4571.3502012-01-05    4603.625Name: births, dtype: float64

Tập trung chỉ vào tháng và ngày, chúng ta hiện có một chuỗi thời gian phản ánh số lượng trung bình sinh con theo ngày trong năm.Từ đó, chúng ta có thể sử dụng phương thức plot để vẽ biểu đồ dữ liệu. Nó cho thấy một số xu hướng thú vị:

# Plot the resultsfig, ax = plt.subplots(figsize=(12, 4))births_by_date.plot(ax=ax);
ảnh ví dụ - data science lại blog của lưu

Theo đặc điểm đáng chú ý của đồ thị này là sự giảm mạnh của tỷ lệ sinh vào các ngày lễ tại Mỹ (ví dụ: ngày Độc lập, ngày Quốc tế Lao động, lễ tạ ơn, Giáng sinh, Tết Dương lịch), mặc dù điều này có thể phản ánh xu hướng sinh thông qua lịch trình hoặc những nguyên nhân nhất định không ảnh hưởng sâu sắc tới sinh tự nhiên.Để biết thêm thảo luận về xu hướng này, xem phân tích và các liên kết trong bài viết trên blog của Andrew Gelman về chủ đề này.Chúng ta sẽ trở lại với hình này trong Ví dụ: Tác động của các ngày lễ tới việc sinh tại Mỹ, nơi chúng ta sẽ sử dụng các công cụ của Matplotlib để chú thích cho đồ thị này.

Nhìn vào ví dụ ngắn này, bạn có thể thấy rằng nhiều công cụ Python và Pandas mà chúng ta đã thấy được kết hợp và sử dụng để thu thập thông tin từ nhiều bộ dữ liệu khác nhau.Chúng ta sẽ thấy một số ứng dụng phức tạp hơn của các phép biến đổi dữ liệu này trong các phần tiếp theo!