Chuyển đến nội dung chính

Phân tích Khám phá Dữ liệu về Bia Thủ công: Data Profiling

Trong hướng dẫn này, bạn sẽ tìm hiểu về phân tích khám phá dữ liệu (EDA) trong Python, cụ thể hơn là data profiling với pandas.
· 14 phút đọc

Khám phá cùng AI

Lời của biên tập viên: Jean-Nicholas Hould là nhà khoa học dữ liệu tại Intel Security ở Montreal và anh ấy dạy cách bắt đầu với khoa học dữ liệu trên blog của mình.

Phân tích khám phá dữ liệu (EDA) là một phương pháp thống kê nhằm khám phá và tóm tắt một tập dữ liệu. Ở bước này của quy trình khoa học dữ liệu, bạn muốn khám phá cấu trúc của tập dữ liệu, các biến và mối quan hệ giữa chúng.

Trong bài viết này, bạn sẽ tập trung vào một khía cạnh của phân tích khám phá dữ liệu: data profiling.

Data profiling là tóm tắt tập dữ liệu của bạn thông qua thống kê mô tả. Bạn muốn sử dụng nhiều thước đo để hiểu rõ hơn về tập dữ liệu. Kiểu dữ liệu, giá trị thiếu, trung bình, trung vị và độ lệch chuẩn chỉ là một vài trong số nhiều yếu tố bạn cần thu thập khi profiling một tập dữ liệu. Mục tiêu của data profiling là nắm vững dữ liệu của bạn để sau đó bạn có thể bắt đầu truy vấn và trực quan hóa dữ liệu theo nhiều cách khác nhau.

Biết dữ liệu của bạn đến từ đâu

Trước khi lao vào bất kỳ EDA nào, bạn nên biết càng nhiều càng tốt về nguồn gốc của dữ liệu bạn đang phân tích. Bạn cần hiểu dữ liệu được thu thập như thế nào và đã được xử lý ra sao. Có bất kỳ biến đổi nào trước đây trên dữ liệu có thể ảnh hưởng đến phân tích của bạn không?

Bạn nên có thể trả lời những câu hỏi sau về tập dữ liệu của mình:

  • Nó được thu thập như thế nào?
  • Đó có phải là một mẫu không?
  • Mẫu có được lấy đúng cách không?
  • Tập dữ liệu có bị biến đổi theo bất kỳ cách nào không?
  • Có vấn đề đã biết nào với tập dữ liệu không?

Nếu bạn không hiểu dữ liệu đến từ đâu, bạn sẽ khó rút ra bất kỳ kết luận có ý nghĩa nào từ tập dữ liệu. Bạn cũng có nguy cơ mắc những sai lầm phân tích rất nghiêm trọng.

Ngoài ra, bạn nên đảm bảo tập dữ liệu được cấu trúc theo một cách tiêu chuẩn hóa. Định dạng khuyến nghị là dạng chuẩn ba, còn được gọi là tidy data. Một tập dữ liệu “tidy” có các đặc điểm sau:

  • Mỗi biến tạo thành một cột và chứa các giá trị
  • Mỗi quan sát tạo thành một hàng
  • Mỗi kiểu đơn vị quan sát tạo thành một bảng

Tuân thủ định dạng tiêu chuẩn này sẽ tăng tốc phân tích vì nó tương thích với nhiều công cụ và thư viện.

Data Profiling

Trong bài viết này, bạn sẽ sử dụng một tập dữ liệu về Bia Thủ công từ trang CraftCans. Tập dữ liệu này chỉ chứa dữ liệu từ các loại bia đóng lon của các nhà máy bia ở Hoa Kỳ. Không rõ từ trang web liệu tập dữ liệu này có ghi nhận mọi loại bia đóng lon được sản xuất ở Mỹ hay không. Để an toàn, bạn sẽ coi tập dữ liệu này là một mẫu có thể chứa sai lệch.

Dưới đây là cấu trúc của các tập dữ liệu bạn sẽ sử dụng:

Beers:

  • ID: Định danh duy nhất của loại bia.
  • Name: Tên của loại bia.
  • ABV: Nồng độ cồn theo thể tích của bia.
  • IBU: Đơn vị độ đắng quốc tế của bia.
  • Style: Phong cách (kiểu) bia.
  • Ounces: Số ounce bia.

Breweries:

  • ID: Định danh duy nhất của nhà máy bia.
  • Name: Tên của nhà máy bia.
  • City: Thành phố nơi nhà máy bia đặt trụ sở.
  • State: Bang nơi nhà máy bia đặt trụ sở.

Kiểu dữ liệu

Bước đầu tiên là hiểu thành phần của tập dữ liệu.

Bạn đang làm việc với những biến nào?

Nhìn chung bạn có thể xếp dữ liệu vào một trong các nhóm sau:

  • Số (Numerical)
  • Phân loại (Categorical)
  • Văn bản (Text)
  • Ngày (Date)

Trước tiên bạn sẽ nhập các tập dữ liệu có thể tìm thấy trong kho này bằng hàm from_csv của pandas. Bạn cũng sẽ nối hai tập dữ liệu beers và breweries lại với nhau để thuận tiện cho phân tích về sau.

import pandas as pd

beers = pd.DataFrame.from_csv("https://raw.githubusercontent.com/nickhould/craft-beers-dataset/master/data/craftcans_beers.csv")
breweries = pd.DataFrame.from_csv("https://raw.githubusercontent.com/nickhould/craft-beers-dataset/master/data/craftcans_breweries.csv")

beers_and_breweries = pd.merge(beers, 
                               breweries, 
                               how='inner', 
                               left_on="brewery_id", 
                               right_on="id", 
                               sort=True,
                               suffixes=('_beer', '_brewery'))

Với thư viện pandas, bạn có thể chạy hàm dtypes để liệt kê từng cột và kiểu dữ liệu của chúng.

beers.dtypes

Điều này cho bạn kết quả sau:

abv           float64
ibu           float64
id              int64
name           object
style          object
brewery_id      int64
ounces        float64
dtype: object

Như bạn thấy ở trên, hàm đó không gom nhóm sạch các kiểu dữ liệu khác nhau. Các kiểu dữ liệu số khác nhau (float64 và int64) không được gom vào một nhóm duy nhất như chúng ta mong muốn. Ngoài ra, một số cột được liệt kê là object, điều này không hữu ích lắm.

Để khắc phục, bạn có thể xây dựng hàm của riêng mình để xác định nhóm của từng cột trong một DataFrame.

def get_var_category(series):
    unique_count = series.nunique(dropna=False)
    total_count = len(series)
    if pd.api.types.is_numeric_dtype(series):
        return 'Numerical'
    elif pd.api.types.is_datetime64_dtype(series):
        return 'Date'
    elif unique_count==total_count:
        return 'Text (Unique)'
    else:
        return 'Categorical'

def print_categories(df):
    for column_name in df.columns:
        print(column_name, ": ", get_var_category(df[column_name]))

Biến của Beers

print_categories(beers)

Lệnh trên trả về kết quả sau:

abv :  Numerical
ibu :  Numerical
id :  Numerical
name :  Categorical
style :  Categorical
brewery_id :  Numerical
ounces :  Numerical

Biến của Breweries

print_categories(breweries)

Điều này cho kết quả sau:

name :  Categorical
city :  Categorical
state :  Categorical
id :  Numerical

Với thông tin này, bạn đã có thể hiểu rõ hơn về tập dữ liệu. Bạn biết rằng mình chỉ làm việc với dữ liệu phân loại và dữ liệu số. Các biến số có thể dùng để trích xuất nhiều thước đo khác nhau như trung bình, độ lệch chuẩn, v.v. Các biến phân loại thường là cách thú vị để phân đoạn và nhóm dữ liệu. Ví dụ, bạn có thể muốn hiểu IBU khác nhau thế nào giữa các phong cách bia.

Thống kê mô tả

Trong phần này, bạn sẽ đi qua nhiều thống kê mô tả có thể được dùng để hiểu dữ liệu tốt hơn. Bạn sẽ nhận thấy mỗi thước đo khi đứng riêng lẻ thì không quá hữu ích. Sự kết hợp của các thước đo khác nhau mới là nơi bạn có thể trích xuất nhiều giá trị nhất.

Bạn sẽ tập trung vào biến IBU vì đây là biến số. Kiểu biến này cung cấp nhiều thước đo phong phú hơn so với biến phân loại. Bạn vẫn có thể chạy các phép đo trên biến phân loại nhưng sẽ bị hạn chế hơn nhiều.

Độ dài

Hàm len đếm số lượng quan sát trong một Series. Hàm sẽ đếm tất cả quan sát, bất kể có giá trị thiếu hay null.

length = len(beers["ibu"])
print(length)

Trong Series này, chúng ta có tổng cộng 2410 quan sát.

Số lượng (Count)

Hàm count sẽ trả về số lượng quan sát không phải NA/không phải null trong một Series.

count = beers["ibu"].count()
print(count)

Như bạn thấy, bạn có 1405 quan sát không null trong Series.

Giá trị thiếu

Với Length và Count, giờ đây chúng ta có thể tính số lượng giá trị thiếu. Số giá trị thiếu là hiệu giữa Length và Count.

number_of_missing_values = length - count
pct_of_missing_values = float(number_of_missing_values / length)
pct_of_missing_values = "{0:.1f}%".format(pct_of_missing_values*100)
print(pct_of_missing_values)

Để xuất phần trăm giá trị thiếu, bạn chỉ cần chia số giá trị thiếu cho tổng số quan sát, tức độ dài. Hàm float được dùng để đảm bảo phần thập phân được giữ trong phép chia. Hàm format dùng để định dạng đẹp con số dưới dạng phần trăm.

Trong trường hợp này, bạn thiếu gần 42% biến IBU. Điều này quan trọng vì nó sẽ ảnh hưởng đến phân tích. Hầu hết các thống kê mô tả sẽ bỏ qua các giá trị thiếu và điều này chắc chắn gây sai lệch.

Giá trị nhỏ nhất/lớn nhất

Giá trị nhỏ nhất và lớn nhất của một tập dữ liệu có thể dễ dàng lấy được với các hàm min và max trên một Series.

print("Minimum value: ", beers["ibu"].min())
print("Maximum value: ", beers["ibu"].max())

Giá trị min/max hữu ích để hiểu phạm vi giá trị của một biến. Trong trường hợp này, IBU dao động từ 4 đến 138.

Mode

Mode là giá trị xuất hiện thường xuyên nhất trong một tập dữ liệu. Có thể lấy được bằng cách dùng hàm mode trên một Series.

print(beers["ibu"].mode())

Trong một phân phối chuẩn, mode bằng với mean và median.

Trong trường hợp này, mode của biến IBU là 20. Đây là giá trị IBU xuất hiện thường xuyên nhất trong tập dữ liệu.

Mean

Trung bình là một thước đo xu hướng trung tâm. Nó là tổng các giá trị chia cho số lượng quan sát không thiếu.

Có thể lấy bằng hàm mean trên một Series.

mean = beers["ibu"].mean()

Trung bình dễ bị ảnh hưởng bởi ngoại lệ. Một vài giá trị cực đoan có thể làm thay đổi trung bình đáng kể, kéo nó lên hoặc xuống.

Median

Trung vị cũng là một thước đo xu hướng trung tâm. Đó là số nằm chính giữa trong một danh sách các giá trị số đã được sắp xếp.

median = beers["ibu"].median()

Trong trường hợp phân phối lệch, trung vị là thước đo xu hướng trung tâm tốt hơn nhiều so với trung bình.

Với phân phối IBU, trung bình và trung vị ở cùng bậc độ lớn.

Độ lệch chuẩn

Độ lệch chuẩn là một thước đo độ phân tán. Độ lệch chuẩn cao cho thấy các điểm dữ liệu phân tán trên một phạm vi giá trị rộng. Độ lệch chuẩn được biểu diễn cùng đơn vị với các giá trị.

standarddev = beers["ibu"].std()

Trong trường hợp này, độ lệch chuẩn xấp xỉ 26. Chính xác là 25.954065911259324. Nếu phân phối của IBU là phân phối chuẩn, bạn sẽ biết rằng khoảng ~68% số quan sát nằm trong một độ lệch chuẩn quanh trung bình.

Thống kê phân vị

Các phân vị là những điểm cắt chia một phân phối thành các phần bằng nhau. Nhiều phân vị có tên riêng. Nếu bạn chia một phân phối thành bốn nhóm bằng nhau, phân vị bạn tạo ra được gọi là quartile. Bạn có thể dễ dàng tạo phân vị bằng hàm quantile trên một Series. Bạn có thể truyền vào hàm đó một mảng các phân vị cần tính. Ở ví dụ dưới đây, chúng ta muốn chia phân phối thành bốn nhóm bằng nhau.

quantile = beers["ibu"].quantile([.25, .5, .75])
0.2521.0
0.5035.0
0.7564.0
Name: ibu,dtype: float64

Như bạn thấy ở trên, phân vị 50% bằng trung vị. Đây là giá trị chia tập dữ liệu làm đôi. Bạn cũng có thể nhận thấy rằng 75% số quan sát nhỏ hơn hoặc bằng 64 IBU. Hơn nữa, 50% phân phối nằm giữa 21 và 64 IBU. Lưu ý rằng các giá trị thiếu không được tính trong các chỉ số này.

Biểu đồ phân phối

Trực quan hóa rất hữu ích trong phân tích khám phá dữ liệu. Trong bài viết này, chúng ta sẽ không đi sâu vào chủ đề trực quan hóa. Tuy nhiên, không thể nói về data profiling mà không nhắc tới tầm quan trọng của biểu đồ tần suất phân phối. Đây là một trong những hình trực quan đơn giản nhưng mạnh mẽ nhất. Nó cho thấy tần suất của từng giá trị trong tập dữ liệu.

Để tạo hình trực quan này, chúng ta dùng thư viện seaborn`` với hàmdisplot. Hàm này kỳ vọng mộtSeries` không có giá trị thiếu.

import seaborn as sns
sns.set(color_codes=True)
sns.set_palette(sns.color_palette("muted"))

sns.distplot(beers["ibu"].dropna());

distribution plot

Trong biểu đồ phân phối này, bạn có thể thấy rõ một vài giá trị chúng ta đã tính trước đó. Giá trị nhỏ nhất gần 0 IBU và giá trị lớn nhất gần 140 IBU. Bạn thấy rõ giá trị xuất hiện thường xuyên nhất gần 20 IBU. Bên cạnh đó, chúng ta còn thấy một đỉnh gần 60 IBU.

Tại sao có hai đỉnh trong phân phối này?

Điều gì có thể giải thích điều đó? Đây là khía cạnh mà chúng ta có thể khám phá trong giai đoạn thứ hai của phân tích khám phá dữ liệu.

Tương quan

Tương quan là cách tuyệt vời để khám phá mối quan hệ giữa các biến số. Có nhiều cách tính tương quan. Hệ số tương quan Pearson là phương pháp được dùng rộng rãi đo lường sự phụ thuộc tuyến tính giữa hai biến. Hệ số tương quan nằm trong khoảng từ -1 đến 1. Tương quan bằng 1 là tương quan dương hoàn toàn, bằng -1 là tương quan âm hoàn toàn và bằng 0 là không có tương quan tuyến tính. Chúng ta có thể thực hiện phép tính này bằng hàm corr trên một Series. Mặc định, hàm này dùng cách tính hệ số tương quan Pearson. Có thể dùng các phương pháp tính khác với hàm này.

beers[["abv", "ibu", "ounces"]].corr()
 abvibuounces
abv10.6706210.172529
ibu0.67062110.054691
ounces0.1725290.0546911

Như bạn thấy ở trên, tương quan giữa IBU và chính nó là 1. Dĩ nhiên, các biến số hoàn toàn tương quan với chính chúng. Thú vị hơn, bạn có thể thấy tương quan giữa ABV và IBU bằng 0.670621. Dù không phải tương quan dương hoàn toàn, mức này vẫn là khá cao. Đây là một khía cạnh đáng để chúng ta khám phá sâu hơn.

Một vài lưu ý về biến không phải số

Các chỉ số đã trích xuất trước đó chủ yếu áp dụng cho giá trị số. Nếu bạn đang xử lý các kiểu dữ liệu khác như dữ liệu phân loại, bạn vẫn có thể thu thập vài phép đo thú vị. Bạn có thể tính tần suất của từng giá trị trong tập dữ liệu.

DataFrame có một hàm tên là describe để tóm tắt tập dữ liệu. Nếu DataFrame của bạn chỉ có giá trị phân loại hoặc văn bản, bản tóm tắt sẽ được điều chỉnh đặc thù cho kiểu dữ liệu này.

beers[["name", "style"]].describe()
 namestyle
count24102405
unique230599
topNonstop Hef HopAmerican IPA
freq12424

Thư viện Profiling

Như bạn đã thấy ở trên, việc thu thập thống kê mô tả có thể là một quá trình tẻ nhạt. May mắn thay, có các thư viện thực hiện toàn bộ việc tính toán cho bạn. Chúng xuất ra một hồ sơ rất rõ ràng về dữ liệu của bạn. pandas-profiling là một trong số đó. Thư viện này cung cấp profiling thống kê sẵn sàng cho tập dữ liệu của bạn. Vì tập dữ liệu chúng ta dùng là tidy và tiêu chuẩn hóa, chúng ta có thể dùng thư viện ngay trên dữ liệu của mình.

import pandas_profiling 

pandas_profiling.ProfileReport(beers_and_breweries)

Nhiều câu hỏi hơn

Thường thì, khi bạn đã profiling xong tập dữ liệu, bạn sẽ có nhiều câu hỏi hơn trước. Điều này rất tốt vì những câu hỏi mới đó sẽ tiếp nhiên liệu cho phân tích khám phá dữ liệu của bạn.

Dưới đây là một vài câu hỏi chúng tôi đã tổng hợp khi thực hiện profiling:

  • 41,7% giá trị IBU bị thiếu. Tại sao vậy? Điều đó có thể ảnh hưởng gì đến phân tích của chúng ta?
  • Có hai đỉnh trong phân phối IBU. Điều gì giải thích điều này?
  • Điều gì giải thích mối tương quan giữa IBU và ABV? Ảnh hưởng của phong cách bia trong tương quan này là gì?
  • Có sự khác biệt nào về IBU, ABV hoặc Style giữa các vùng địa lý không? Còn Bờ Đông so với Bờ Tây thì sao?

Data profiling không phải là một quá trình tuyến tính. Khi bạn lọc và phân đoạn tập dữ liệu, bạn sẽ quay lại và thu thập thống kê mô tả trên các nhóm con của dữ liệu.

Các bước tiếp theo

Trong bài viết này, bạn đã thấy cách profiling một tập dữ liệu. Giờ bạn biết cách gán các biến vào các nhóm kiểu dữ liệu cụ thể. Bạn cũng đã tính các thống kê mô tả khác nhau trên tập dữ liệu và hiểu cách diễn giải các thước đo đó. Cuối cùng, bạn đã thấy rằng có một số thư viện có thể giúp bạn thực hiện việc tính toán để profiling tập dữ liệu. Quan trọng hơn, bạn đã tạo ra nhiều câu hỏi hơn bao giờ hết để tiếp nhiên liệu cho phân tích khám phá dữ liệu.

Hãy xem Hướng dẫn Phân tích Khám phá Dữ liệu bằng Python của DataCamp.

Chủ đề

Liên quan