Nhảy đến nội dung

Một số cách sử dụng apply và lambda điển hình ở Python

Có lẽ apply và lamda là những thứ hay ho nhất mà bạn sẽ nhớ tới khi bắt đầu tìm hiểu gói pandas. Và sau này khi sử dụng thành thạo apply và lambda để tạo các cột hay lọc giá trị cột của dataframe bạn sẽ nhận ra sức mạnh của các công cụ này. 

Bài viết sử dụng bộ dữ liệu của 1000 bộ phim nổi tiếng trên IMDB trong 10 năm qua (Kaggle Kernel). 

Bạn có thể tạo một cột mới bằng nhiều cách khác nhau.

Đó có thể là tổng của các cột khác nhau ví dụ:

df['AvgRating'] = (df['Rating'] + df['Metascore']/10)/2

Nhưng đôi khi bạn có thể cần xây dựng một logic phức tạp khi tạo thêm những cột mới cho df, như ở dưới đây:

Có một thể loại phim đó là kinh_di, và giờ bạn muốn cộng thêm một điểm đánh giá cho bộ phim thể loại này, nhưng điểm đánh giá phải thấp hơn hoặc bằng 10, và với thể loại phim hai_kich, bạn muốn trừ đi một điểm đánh giá.

Trước hết bạn sẽ viết một hàm (def)

def danh_gia(the_loai, danh_gia):
    if 'kinh_di' in the_loai:
        return min(10,rating+1)
    elseif 'hai_kich' in the_loai:
        return max(0,rating-1)
    else:
        return rating
        
df['DanhGiaRieng'] = df.apply(lambda x: danh_gia(x['Genre'],x['Rating']),axis=1)

Cấu trúc chung:

  • Viết một hàm, hàm đó lấy giá trị của cột bạn muốn thay đổi theo logic, ở đây bạn lấy giá trị từ hai cột là thể loại và đánh giá.
  • Dùng lambda để áp dụng hàm này vào các cột của df, theo hàng axis =1 
df.apply(lambda x: func(x['col1'],x['col2']),axis=1)

Lọc môt df

Sử dụng pandas để lọc hay chia df khá là đơn giản, sử dụng &, |, hoặc ~ 

# Điều kiện đơn: df với tất cả các bộ phim có đánh giá lớn hơn hoặc bằng 8
df_gt_8 = df[df['Rating']>=8]

# Kiểu kiện kép hoặc nhiều hơn: df với tất cả các bộ phim có đánh giá nhiều hơn hoặc bằng 8 và có ho2n 100000 đánh giá. Ở đây sử dung & cho 2 điều kiện

And_df = df[(df['Rating']>=8) & (df['Votes']>100000)]

# Kiểu kiện kép hoặc nhiều hơn: df với tất cả các bộ phim có đánh giá nhiều hơn hoặc bằng 8 HOẶC có điểm đánh giá META lớn hơn 80. Ở đây sử dụng |.

Or_df = df[(df['Rating']>=8) | (df['Metascore']>80)]

# Kiểu kiện kép hoặc nhiều hơn: df với tất cả các bộ phim có đánh giá nhiều hơn hoặc bằng 80 HOẶC có điểm META lớn hơn 90 thì loại bỏ.

Not_df = df[~((df['Rating']>8) | (df['Metascore']>80))]

Trên đây là các bước lọc cơ bản, tất nhiên bạn sẽ thực hiện một số các lọc phức tạp hơn, và có những thao tác mà chúng ta không thể thực hiện với định dạng như trên.

Một ví dụ đó là mình muốn lọc hàng mà số lượng từ trong tiêu đề phim nhiều hơn hoặc bằng 4.

Lỗi phổ biến là dòng mã.

new_df = df[len(df['Tieu_de'].split(" "))>=4]
-------------------------------------------
AttributeError: 'Series' object has no attribute 'split'

Có một cách đó là tạo thêm một cột bao gồm số lượng từ của tiêu đề và lọc theo cột đó, ví dụ: tiêu đề phim là The Incredible Hulk thì sẽ trả số 3. 

# Tạo một cột mới
df['so_tu'] = df.apply(lambda x : len(x['Tieu_de'].split(" ")),axis=1)
#simple filter on new column
new_df = df[df['so_tu']>=4]

Hoặc bạn có thể kết hợp lại thành một dòng cột

new_df = df[df.apply(lambda x : len(x['Tieu_de'].split(" "))>=4,axis=1)]

Sử dụng hàm .apply() để trả lại một boolean, mà boolean này sử dụng để lọc tieu_de, với cột mới vừa tạo bạn có thể sử dụng hàm/logic áp dụng cho những logic phức tạp hơn.

 

Dưới đây là một hàm logic có cấu trúc phức tạp hơn.

Giờ mình muốn tìm những bộ phim mà doanh thu thấp hơn doanh thu trung bình vào một năm nào đó.

year_revenue_dict = df.groupby(['Year']).agg({'Rev_M':np.mean}).to_dict()['Rev_M']

def bool_provider(revenue, year):
    return revenue < year_revenue_dict[year]
    
new_df = df[df.apply(lambda x : bool_provider(x['Rev_M'],x['Year']),axis=1)]

Như vậy chúng ta có một hàm từ đó có thể viết bất kỳ logic nào, điều này đem lại hiệu quả cao với những thao tác lọc nâng cao cho những biến đơn giản. 

Thay đổi kiểu dữ liệu cột

Bạn sẽ sử dụng .astype() khá nhiều để đổi kiểu dữ liệu của cột ví dụ từ str sang float or int như ví dụ dưới đây, mình có một cột giá định dạng str giờ muốn chuyển sang int

df['Gia'] = newDf['Gia'].astype('int')

Nhưng đôi khi bạn sẽ gặp phải lỗi vì trong str có , vì vậy cần loại bỏ dấu , phân cách này (10,000), trường hợp này bạn có thể sử dụng

df['Price'] = df.apply(lambda x: int(x['Price'].replace(',', '')),axis=1)

Cuối cùng là sử dụng progress_apply 

progress_apply nằm trong gói tqdm 

Khi có khá nhiều hàng trong df, bạn sẽ phải viết các hàm phức tạp và mất khá nhiều thời gian và bạn muốn xem mức độ hoàn thành với progress_apply

from tqdm import tqdm, tqdm_notebook
tqdm_notebook().pandas()
df.progress_apply(lambda x: custom_rating_function(x['Genre'],x['Rating']),axis=1)

Và bạn sẽ có một thanh chạy hiện thị mức độ hoàn thành.

Lời kết

Khi thao túng dữ liệu, bạn sẽ sử dụng apply và lambda để chăm sóc các hàm phức tạp.