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

03.10 Vectorized String Operations

Giới thiệu về Các phép toán chuỗi trong Pandas

Chúng ta đã thấy trong các phần trước đây như làm thế nào các công cụ như NumPy và Pandas tổng quát hoá các phép toán số học để chúng ta có thể dễ dàng và nhanh chóng thực hiện cùng một phép toán trên nhiều phần tử mảng. Ví dụ:

import numpy as npx = np.array([2, 3, 5, 7, 11, 13])x * 2
array([ 4,  6, 10, 14, 22, 26])

Phép toán vectorization này đơn giản hóa cú pháp của việc thao tác trên mảng dữ liệu: chúng ta không cần phải quan tâm đến kích thước hoặc hình dạng của mảng, mà chỉ quan tâm đến phép toán chúng ta muốn thực hiện.Đối với mảng chuỗi, NumPy không cung cấp quyền truy cập đơn giản như vậy, và do đó bạn phải sử dụng cú pháp vòng lặp dài hơn:

data = ['peter', 'Paul', 'MARY', 'gUIDO'][s.capitalize() for s in data]
['Peter', 'Paul', 'Mary', 'Guido']

Đây có thể là đủ để làm việc với một số dữ liệu, nhưng nó sẽ bị hỏng nếu có bất kỳ giá trị nào bị thiếu.Ví dụ:

data = ['peter', 'Paul', None, 'MARY', 'gUIDO'][s.capitalize() for s in data]
---------------------------------------------------------------------------AttributeError                            Traceback (most recent call last)<ipython-input-3-fc1d891ab539> in <module>()      1 data = ['peter', 'Paul', None, 'MARY', 'gUIDO']----> 2 [s.capitalize() for s in data]<ipython-input-3-fc1d891ab539> in <listcomp>(.0)      1 data = ['peter', 'Paul', None, 'MARY', 'gUIDO']----> 2 [s.capitalize() for s in data]AttributeError: 'NoneType' object has no attribute 'capitalize'

Pandas bao gồm các tính năng để xử lý cả hai nhu cầu này về các phép toán chuỗi vector hóa cũng như xử lý đúng dữ liệu bị thiếu thông qua thuộc tính str của đối tượng Series và Index trong Pandas chứa các chuỗi.Vì vậy, ví dụ, giả sử chúng ta tạo một Series Pandas với dữ liệu này:

import pandas as pdnames = pd.Series(data)names
0    peter1     Paul2     None3     MARY4    gUIDOdtype: object

Bây giờ chúng ta có thể gọi một phương thức duy nhất để viết hoa tất cả các mục nhập, trong khi bỏ qua bất kỳ giá trị thiếu:

names.str.capitalize()
0    Peter1     Paul2     None3     Mary4    Guidodtype: object

Sử dụng hoàn thành bằng tab trên thuộc tính str sẽ liệt kê tất cả các phương thức chuỗi vector hóa có sẵn trong Pandas.

Bảng phương pháp String của Pandas

Nếu bạn hiểu về việc xử lý chuỗi trong Python, phần lớn cú pháp chuỗi của Pandas khá dễ hiểu đến mức có lẽ chỉ cần liệt kê một bảng các phương thức có sẵn là đủ; chúng tôi sẽ bắt đầu bằng việc này, trước khi khám phá sâu hơn một số khía cạnh tinh vi.Các ví dụ trong phần này sử dụng chuỗi tên sau:

monte = pd.Series(['Graham Chapman', 'John Cleese', 'Terry Gilliam',                   'Eric Idle', 'Terry Jones', 'Michael Palin'])

Các phương thức tương đồng với các phương thức xâu trong Python

Gần như tất cả các phương thức chuỗi được tích hợp sẵn trong Python đều được phản ánh bởi một phương thức chuỗi vectorized trong Pandas. Dưới đây là danh sách các phương thức str của Pandas phản ánh phương thức chuỗi của Python:

Chú ý rằng các hàm này có các giá trị trả về khác nhau. Một số, như lower(), trả về một chuỗi các ký tự:

monte.str.lower()
0    graham chapman1       john cleese2     terry gilliam3         eric idle4       terry jones5     michael palindtype: object

Nhưng một số khác trả về các số:

monte.str.len()
0    141    112    133     94    115    13dtype: int64

Hoặc giá trị Boolean:

monte.str.startswith('T')
0    False1    False2     True3    False4     True5    Falsedtype: bool

Vẫn còn một số phương thức trả về danh sách hoặc các giá trị kết hợp khác cho mỗi phần tử:

monte.str.split()
0    [Graham, Chapman]1       [John, Cleese]2     [Terry, Gilliam]3         [Eric, Idle]4       [Terry, Jones]5     [Michael, Palin]dtype: object

Chúng ta sẽ thấy các thao tác tiếp theo trên loại đối tượng dạng danh sách này trong quá trình thảo luận tiếp theo của chúng ta.

Các phương pháp sử dụng biểu thức chính quy

Ngoài ra, còn có một số phương thức chấp nhận biểu thức chính quy để kiểm tra nội dung của mỗi phần tử chuỗi, và tuân theo một số quy ước API của module re tích hợp sẵn của Python:

Với những thứ này, bạn có thể thực hiện một loạt các thao tác thú vị.Ví dụ, chúng ta có thể trích xuất tên đầu tiên từ mỗi yêu cầu bằng cách yêu cầu một nhóm các ký tự liền nhau ở đầu mỗi phần tử:

monte.str.extract('([A-Za-z]+)', expand=False)
0     Graham1       John2      Terry3       Eric4      Terry5    Michaeldtype: object

Hoặc chúng ta có thể làm một số điều phức tạp hơn, như tìm tất cả các tên bắt đầu và kết thúc bằng một phụ âm, sử dụng các ký tự biểu thức chính quy bắt đầu của chuỗi (^) và kết thúc của chuỗi ($):

monte.str.findall(r'^[^AEIOU].*[^aeiou]$')
0    [Graham Chapman]1                  []2     [Terry Gilliam]3                  []4       [Terry Jones]5     [Michael Palin]dtype: object

Khả năng áp dụng biểu thức chính quy một cách ngắn gọn cho các mục nhập của Series hoặc Dataframe mở ra nhiều khả năng cho phân tích và làm sạch dữ liệu.

Các phương pháp khác

Cuối cùng, có một số phương thức linh tinh khác cho phép thực hiện các thao tác tiện ích khác:

Truy cập phần tử và cắt vector hóa

Các hoạt động get()slice(), đặc biệt là, cho phép truy cập các phần tử theo kiểu vector từ mỗi mảng.Ví dụ, chúng ta có thể lấy một phần của ba ký tự đầu tiên của mỗi mảng bằng cách sử dụng str.slice(0, 3).Lưu ý rằng hành vi này cũng có sẵn thông qua cú pháp chỉ mục thông thường của Python – ví dụ: df.str.slice(0, 3) tương đương với df.str[0:3]:

monte.str[0:3]
0    Gra1    Joh2    Ter3    Eri4    Ter5    Micdtype: object

Indexing qua df.str.get(i)df.str[i] tương tự nhau.

Các phương thức get()slice() cũng cho phép bạn truy cập các phần tử của mảng được trả về bởi phương thức split().Ví dụ, để trích xuất họ của mỗi mục nhập, chúng ta có thể kết hợp split()get():

monte.str.split().str.get(-1)
0    Chapman1     Cleese2    Gilliam3       Idle4      Jones5      Palindtype: object

Biến chỉ số

Một phương pháp khác đòi hỏi một chút giải thích bổ sung là phương thức get_dummies().Điều này hữu ích khi dữ liệu của bạn có một cột chứa một loại chỉ báo đã được mã hóa.Ví dụ, chúng ta có thể có một bộ dữ liệu chứa thông tin dưới dạng mã, như A = “sinh ra ở Mỹ,” B = “sinh ra ở Vương quốc Anh,” C = “thích phô mai,” D = “thích mì xúc xích”:

full_monte = pd.DataFrame({'name': monte,                           'info': ['B|C|D', 'B|D', 'A|C',                                    'B|D', 'B|C', 'B|C|D']})full_monte

Phương thức get_dummies() cho phép bạn nhanh chóng tách các biến chỉ thị này thành một DataFrame:

full_monte['info'].str.get_dummies('|')

Với những thao tác này như những khối xây dựng, bạn có thể tạo ra một loạt các thủ tục xử lý chuỗi vô tận khi làm sạch dữ liệu của bạn.

Chúng ta sẽ không đi sâu vào các phương pháp này ở đây, nhưng tôi khuyến khích bạn đọc qua “Làm việc với dữ liệu văn bản” trong tài liệu trực tuyến của Pandas, hoặc tham khảo các tài liệu được liệt kê trong Các tài liệu tham khảo thêm.

Ví dụ: Cơ sở dữ liệu công thức

Các hoạt động chuỗi vector trở nên hữu ích nhất trong quá trình làm sạch dữ liệu thực tế lộn xộn.Ở đây tôi sẽ đi qua một ví dụ về điều đó, sử dụng một cơ sở dữ liệu công thức nấu ăn được biên soạn từ các nguồn khác nhau trên Web.Mục tiêu của chúng tôi sẽ là phân tích dữ liệu công thức thành danh sách nguyên liệu, để chúng ta có thể nhanh chóng tìm thấy một công thức dựa trên một số nguyên liệu chúng ta có sẵn.

Các mã script được sử dụng để biên dịch điều này có thể tìm thấy tại https://github.com/fictivekin/openrecipes, và liên kết đến phiên bản hiện tại của cơ sở dữ liệu cũng được tìm thấy ở đó.

Tính đến mùa Xuân năm 2016, cơ sở dữ liệu này có kích thước khoảng 30 MB và có thể được tải về và giải nén bằng các lệnh sau:

# !curl -O http://openrecipes.s3.amazonaws.com/recipeitems-latest.json.gz# !gunzip recipeitems-latest.json.gz

Cơ sở dữ liệu ở định dạng JSON, vì vậy chúng ta sẽ thử sử dụng pd.read_json để đọc nó:

try:    recipes = pd.read_json('recipeitems-latest.json')except ValueError as e:    print("ValueError:", e)
ValueError: Trailing data

Rất tiếc! Chúng ta đã nhận được một ValueError đề cập đến việc “trailing data”.Tìm kiếm về văn bản của lỗi này trên Internet, có vẻ như nó xảy ra do sử dụng một tập tin trong đó mỗi dòng của nó là một JSON hợp lệ, nhưng tập tin đầy đủ không phải vậy.Hãy kiểm tra xem giải thích này có đúng không:

with open('recipeitems-latest.json') as f:    line = f.readline()pd.read_json(line).shape
(2, 12)

Ừ, có vẻ như mỗi dòng là một JSON hợp lệ, vì vậy chúng ta sẽ cần ghép chúng lại với nhau.Một cách chúng ta có thể làm điều này là thực tế tạo một chuỗi biểu diễn chứa tất cả các mục JSON này, sau đó tải toàn bộ nó bằng pd.read_json:

# read the entire file into a Python arraywith open('recipeitems-latest.json', 'r') as f:    # Extract each line    data = (line.strip() for line in f)    # Reformat so each line is the element of a list    data_json = "[{0}]".format(','.join(data))# read the result as a JSONrecipes = pd.read_json(data_json)
recipes.shape
(173278, 17)

Chúng ta thấy có gần 200.000 công thức nấu ăn và 17 cột.Hãy xem một hàng để xem chúng ta có gì:

recipes.iloc[0]
_id                                {'$oid': '5160756b96cc62079cc2db15'}cookTime                                                          PT30Mcreator                                                             NaNdateModified                                                        NaNdatePublished                                                2013-03-11description           Late Saturday afternoon, after Marlboro Man ha...image                 http://static.thepioneerwoman.com/cooking/file...ingredients           Biscuits\n3 cups All-purpose Flour\n2 Tablespo...name                                    Drop Biscuits and Sausage GravyprepTime                                                          PT10MrecipeCategory                                                      NaNrecipeInstructions                                                  NaNrecipeYield                                                          12source                                                  thepioneerwomantotalTime                                                           NaNts                                             {'$date': 1365276011104}url                   http://thepioneerwoman.com/cooking/2013/03/dro...Name: 0, dtype: object

Có rất nhiều thông tin ở đây, nhưng phần lớn nó được trình bày trong một dạng rất lộn xộn, như thường thấy khi dữ liệu được lấy từ Web.Đặc biệt, danh sách thành phần được trình bày dưới dạng chuỗi; chúng ta sẽ phải cẩn thận trích xuất thông tin mà chúng ta quan tâm.Hãy bắt đầu bằng việc xem xét kỹ hơn về các thành phần:

recipes.ingredients.str.len().describe()
count    173278.000000mean        244.617926std         146.705285min           0.00000025%         147.00000050%         221.00000075%         314.000000max        9067.000000Name: ingredients, dtype: float64

Phần danh sách thành phần trung bình có chiều dài 250 ký tự, với một giá trị tối thiểu là 0 và một giá trị tối đa gần 10.000 ký tự!

Chỉ vì tò mò, hãy xem xem công thức nào có danh sách thành phần dài nhất:

recipes.name[np.argmax(recipes.ingredients.str.len())]
'Carrot Pineapple Spice &amp; Brownie Layer Cake with Whipped Cream &amp; Cream Cheese Frosting and Marzipan Carrots'

Điều đó chắc chắn trông giống một công thức phức tạp.

Chúng ta có thể thực hiện những khám phá tổng hợp khác; ví dụ, hãy xem có bao nhiêu công thức là thức ăn sáng:

recipes.description.str.contains('[Bb]reakfast').sum()
3524

Hoặc có bao nhiêu công thức liệt kê quế là nguyên liệu:

recipes.ingredients.str.contains('[Cc]innamon').sum()
10526

Chúng ta có thể kiểm tra xem liệu có một số công thức nào viết sai từ nguyên liệu thành “cinamon” hay không:

recipes.ingredients.str.contains('[Cc]inamon').sum()
11

Đây là loại khám phá dữ liệu cần thiết mà có thể thực hiện được với các công cụ chuỗi của Pandas.Đó là việc mài dũa dữ liệu như thế này mà Python thật sự vượt trội.

Một người giới thiệu công thức đơn giản

Hãy đi đến một bước xa hơn, và bắt đầu làm việc trên một hệ thống gợi ý công thức nấu ăn đơn giản: cho trước một danh sách các nguyên liệu, tìm một công thức nấu ăn sử dụng tất cả các nguyên liệu đó.Mặc dù khá đơn giản về mặt khái niệm, nhiệm vụ này lại phức tạp bởi sự đa dạng của dữ liệu: không có phép toán dễ dàng, ví dụ như trích xuất danh sách sạch sẽ của nguyên liệu từ mỗi hàng.Vì vậy chúng ta sẽ “trói mắt” một chút: chúng ta sẽ bắt đầu với một danh sách các nguyên liệu phổ biến, và đơn giản chỉ tìm kiếm xem chúng có trong danh sách nguyên liệu của mỗi công thức nấu ăn hay không.Vì tính đơn giản, chúng ta chỉ sẽ tạm thời tập trung vào các loại gia vị:

spice_list = ['salt', 'pepper', 'oregano', 'sage', 'parsley',              'rosemary', 'tarragon', 'thyme', 'paprika', 'cumin']

Sau đó, chúng ta có thể xây dựng một DataFrame kiểu Boolean gồm các giá trị True và False, biểu thị xem thành phần này có xuất hiện trong danh sách hay không:

import respice_df = pd.DataFrame(dict((spice, recipes.ingredients.str.contains(spice, re.IGNORECASE))                             for spice in spice_list))spice_df.head()

Bây giờ, ví dụ như, chúng ta muốn tìm một công thức sử dụng cần tây, ớt paprika và húng quế.Chúng ta có thể tính toán điều này rất nhanh bằng cách sử dụng phương thức query() của các DataFrame, đã được thảo luận trong High-Performance Pandas: eval()query():

selection = spice_df.query('parsley & paprika & tarragon')len(selection)
10

Chúng tôi chỉ tìm thấy 10 công thức nấu ăn với sự kết hợp này; hãy sử dụng chỉ mục được trả về bởi lựa chọn này để khám phá tên của những công thức nấu ăn có sự kết hợp này:

recipes.name[selection.index]
2069      All cremat with a Little Gem, dandelion and wa...74964                         Lobster with Thermidor butter93768      Burton's Southern Fried Chicken with White Gravy113926                     Mijo's Slow Cooker Shredded Beef137686                     Asparagus Soup with Poached Eggs140530                                 Fried Oyster Po’boys158475                Lamb shank tagine with herb tabbouleh158486                 Southern fried chicken in buttermilk163175            Fried Chicken Sliders with Pickles + Slaw165243                        Bar Tartine Cauliflower SaladName: name, dtype: object

Bây giờ chúng ta đã thu hẹp lựa chọn công thức nấu ăn chỉ còn khoảng 20.000, chúng ta có thể đưa ra quyết định thông minh hơn về món chúng ta muốn nấu cho bữa tối.

Đi tiếp với các công thức

Hy vọng ví dụ này đã mang lại cho bạn một chút ý tưởng về các hoạt động làm sạch dữ liệu mà Pandas giúp thực hiện một cách hiệu quả.