Bài viết Câu hỏi About RongvangIT
profile Pic
0
0

Đăng ngày:

  51 Lượt xem

Không chỉ là siêu nhanh! Lý do muốn sử dụng Polars thay thế cho Pandas

PolarspandasPython

Thư viện được gọi là Polars, đã làm cho Pandas trở nên nhanh hơn khoảng 100 lần và nó rất tốt nên tôi sẽ tuyên truyền về nó. Polars là một thư viện DataFrame dựa trên Rust, nhưng trong bài viết này, chúng ta sẽ nói về nó trong ngữ cảnh của Python.

Nhân tiện, “polars” có nghĩa là “gấu trắng”. Đó chắc chắn là một quyết định đúng, bởi vì “gấu trắng nhanh và mạnh mẽ hơn so với gấu trúc”.

Số liệu là quan điểm cá nhân. Tôi đã sử dụng Polars khi làm việc với dữ liệu có quy mô vừa phải trong khoảng một tháng với mục đích giải trí. Ngược lại, có thể nói rằng đây là một thư viện tuyệt vời đến mức tôi trở thành một tín đồ trong khoảng thời gian ngắn đó.

Tại sao nên chọn Polars?

Có ba điểm chính tôi muốn giới thiệu

  • Nhanh chóng!
  • Tiện lợi!
  • Dễ viết!

1. Nhanh chóng



Hình ảnh là đánh giá Benchmark của TPCH (màu tím là Polars).

Mặc dù có nhiều bài viết bằng tiếng Nhật về điều này nên tôi sẽ không đi sâu vào, nhưng nó sử dụng nhiều thư viện như Rust và Apache Arrow, nó rất nhanh chóng. Vấn đề của MemoryError cũng đã được giải quyết. Bạn cũng có thể tham khảo tweet thông minh của nhà phát triển Ritchie dưới đây.


Polas.png

Lược dịch:
(1) Pandas đang sao chép đầy đủ DataFrame trong phần màu vàng, đó là điều không tốt!
(2) Ngược lại, Polars đang sao chép ở phần màu vàng thứ hai!

2. Tiện lợi

pip install polars

Chỉ cần điều này là bạn có thể bắt đầu. Điều này thuận tiện hơn so với việc sử dụng thư viện xử lý DataFrame nhanh như cuDF (sử dụng GPU) hoặc pyspark (sử dụng Apache Spark). Trong Google Colab, nó được cài đặt sẵn, chỉ cần import polars as pl và bạn có thể sử dụng bình thường.

Ngoài ra, nó cũng khá giống với cách viết của Pandas, điều này có thể là điểm lợi.

Tuy nhiên, do có cách viết thông minh hơn như sau, nên không khuyến khích việc sử dụng. Do các lý do như khó hưởng lợi từ tốc độ nhanh, API giống với pandas đã được đặt là không khuyến khích, và đã bắt đầu xuất hiện cảnh báo hoặc lỗi trên trang chính thức.

3. Dễ viết

Thực tế, điều này là điểm quan trọng nhất nhưng có vẻ ít được nói đến. Vì vậy, trong bài viết này, chúng ta sẽ chủ yếu nói về sự dễ dàng khi viết code với Polars!!

…Trước hết, hãy đưa ra một số thông tin tổng quan.

Hướng dẫn sử dụng Polars

Hiểu Polars trong 10 dòng

Hãy thử viết một đoạn code xử lý dữ liệu mẫu từ bộ dữ liệu iris với một số thao tác ngẫu nhiên.

import polars as pl

df = pl.read_csv("https://j.mp/iriscsv")                         # Đọc dữ liệu
df_agg = (
    df
    .select([pl.col("^sepal_.*$"), pl.col("species")])           # Chọn cột
    .with_columns((pl.col("sepal_width") * 2).alias("new_col"))  # Thêm cột
    .filter(pl.col("sepal_length") > 5)                          # Chọn hàng
    .group_by("species")                                         # Nhóm dữ liệu
    .agg(pl.all().mean())                                        # Tính trung bình cho tất cả các cột
)

Polars thường thấy được viết theo kiểu mã mắt được kết nối bằng phương thức, giống như pipe trong dplyr của R. Nói chung, thứ tự của các phương thức được viết theo thứ tự của mã, vì vậy bạn chỉ cần liên kết chúng mà không cần nghĩ gì cả.

Những gì đã được thực hiện ở trên là:

  • read_csv: Đọc dữ liệu
  • select: Chọn cột
  • with_columns: Thêm cột
  • filter: Chọn hàng
  • groupby: Nhóm dữ liệu
  • agg: Tính toán trên tất cả các cột

Trang web tham khảo

Khi học về Polars, User Guide chính thức là phổ biến nhất và dễ hiểu nhất. Trang web thứ ba so sánh Polars với các thao tác tương tự trong pandas. Trang web thứ tư là một tờ rơi ghi chú.

Nếu bạn muốn đọc bằng tiếng Nhật, đây là một số nguồn tham khảo tổng hợp khá tốt. Hai đầu tiên cung cấp hướng dẫn từ cơ bản đến phức tạp, thứ ba chứa giải thích về quá trình tối ưu hóa truy vấn, và thứ tư cung cấp một số mẹo bao gồm cả các phiên bản gần đây (tính đến ngày 18/2/23).

Nếu bạn muốn học bằng cách thực hành giải quyết vấn đề, có những lựa chọn sau đây.

Hai cái đầu tiên là những bài viết do tôi viết, dựa trên 100 bài toán thực hành (Phần tiền xử lý). Nếu bạn chỉ muốn xem ví dụ, hãy xem những cái này!

Sử dụng pl.Expr

Trong Polars, trung tâm của sự thuận tiện trong việc viết code chính là polars.Expression. Nó được mô tả như sau:

a mapping from a series to a series

Nghĩa là, nó là một cách để mô tả cách xử lý từ một cột sang một cột khác. Bạn cũng có thể ánh xạ từ nhiều cột. Các thao tác chính trên DataFrame bao gồm select, with_columns, agg, filter, v.v.[^context], nhưng bạn có thể sử dụng pl.Expr ở bất kỳ đâu. Điều này có ý nghĩa gì?

[^context]: Đôi khi được gọi là context. Xem thêm: https://pola-rs.github.io/polars-book/user-guide/concepts/contexts/

Ví dụ, quá trình chuyển đổi cột cost chứa chuỗi thành số nguyên có thể được thực hiện bằng pl.Expr như sau:

pl.col("cost").str.extract("\$(.*)").cast(pl.Int64)

Ở đây, pl.col được sử dụng để chọn cột cost, sau đó, str.extract được áp dụng để trích xuất phần sau dấu $ từ chuỗi, cuối cùng là cast để chuyển đổi sang kiểu số nguyên. Bạn có thể tái sử dụng nó ở nhiều nơi khác nhau.

# Thêm cột đã chuyển đổi sang số nguyên
df.with_columns(pl.col("cost").str.extract("\$(.*)").cast(pl.Int64).alias("cost_int"))

# Chọn các dòng có chi phí lớn hơn 100 đô la
df.filter(pl.col("cost").str.extract("\$(.*)").cast(pl.Int64) > 100)

# Tính tổng chi phí cho mỗi cửa hàng
df.group_by("store").agg(pl.col("cost").str.extract("\$(.*)").cast(pl.Int64).sum())

Điều mà tôi muốn nhấn mạnh ở đây là khả năng tái sử dụng cùng một biểu diễn. Quan trọng là, bạn có thể xử lý bất kỳ thứ gì theo cách tiếp cận đồng nhất, không gặp vấn đề với cách bạn viết mã và dễ duy trì.

Với việc sử dụng pl.Expression như vậy, bạn có thể tạo ra các biểu diễn một cách linh hoạt, chính vì vậy nó được khuyến khích sử dụng trong API chính thức của Polars.

So sánh với Pandas

Đối với việc xử lý DataFrame trong Python, Pandas có lẽ là tiêu chuẩn de facto. Tuy nhiên, có những người không ưa Pandas, đặc biệt là những người chuyển từ dplyr sang Pandas, như tác giả của bài viết này[^ill-iden].

[^ill-iden]: Đọc thêm ở đây: https://ill-identified.hatenablog.com/entry/2021/09/18/130716

Nhiều thư viện tăng tốc khác nhấn mạnh việc viết code giống Pandas. Tuy nhiên, Polars không chỉ có thể làm điều đó mà còn có những điểm mạnh khác, hãy so sánh chúng.

Sự khác biệt trong các khái niệm

1. Không có chỉ số (Index)
Tác giả cá nhân thấy đó là một nguồn gốc của nhiều vấn đề. Mặc dù có những tình huống mà chỉ số rất hữu ích, nhưng thậm chí không có cũng không gây khó khăn lớn.

2. Không có mô hình kỳ lạ cho phép sự hỗn hợp giữa các loại dữ liệu trên cùng một cột
Điều này liên quan đến các tham số như low_memory trong pd.read_csv. Nếu bạn nhận ra điều này ở giữa chừng, đó có thể là một vấn đề khó khăn. Polars giải quyết vấn đề này một cách tự nhiên.

3. Có thể chấp nhận đánh giá trễ
Chi tiết sẽ được giải thích ở phần sau.

4. Dễ dàng chỉ định các cột
Pandas có thể đòi hỏi bạn phải viết tên DataFrame nhiều lần trong một dòng lệnh. Ví dụ, bạn thường xem mã như sau:

df_customer[(df_customer["A"] > 0) & (df_customer["B"] == "XXX")]

Đối với Polars, khi bạn sử dụng pl.col, bạn có thể đơn giản chọn cột trực tiếp cho DataFrame bạn đang thao tác.

Hãy xem xét các ví dụ cụ thể ở phần sau.

Ví dụ cụ thể 1: Xử lý tổng hợp phức tạp

Hãy xem xét một ví dụ cụ thể lớn về sự khác biệt giữa Pandas và Polars. Đầu tiên, hãy xem xét một quá trình tổng hợp phức tạp một chút. Giả sử chúng ta muốn tính:

  • Giá trị lớn nhất của cột A
  • Giá trị nhỏ nhất của cột A
  • Giá trị trung bình của “Hiệu của cột A và cột B”

cho mỗi nhóm được tạo bởi cột G, và muốn đặt tên cột một cách dễ đọc.

▼ Trong trường hợp của Pandas

df["A_B_diff"] = df["A"] - df["B"]
df_agg = df.groupby("G").agg({"A": ["min", "max"], "A_B_diff": "mean"})
df_agg.columns = ["A_min", "A_max", "A_B_diff_mean"]

Mặc dù có nhiều cách viết khác nhau, nhưng có thể cảm thấy hơi rối mắt.

▼ Trong trường hợp của Polars

df.group_by("G").agg([
    pl.col("A").min().alias("A_min"),
    pl.col("A").max().alias("A_max"),
    (pl.col("A") - pl.col("B")).mean().alias("A_B_diff_mean"),
])

Đây là một cách viết sáng sủa hơn. Bạn có thể truyền nhiều cột một cách dễ dàng thông qua pl.Expr. Việc đặt tên cột ngay tại chỗ cũng là một điều thuận lợi.

Ví dụ cụ thể 2: Xử lý apply

Trong Pandas, khi bạn muốn tạo một cột phức tạp, bạn thường cần sử dụng apply. Ví dụ, hãy tạo một cột C thỏa mãn điều kiện sau:

  • Khi tổng của cột A và cột B là số chẵn hoặc cột A là số 3: "い"
  • Khi cột A là số chẵn: "ろ"
  • Trong trường hợp khác: "は"

▼ Trong trường hợp của Pandas

def make_col_c(a, b):
    if (a + b) % 2 == 0 or a == 3:
        return "い"
    elif a % 2 == 0:
        return "ろ"
    else:
        return "は"

df["C"] = df.apply(lambda x: make_col_c(x["A"], x["B"]), axis=1)

Trong Pandas, bạn thường phải sử dụng apply khi muốn viết mã phức tạp như vậy. Tuy nhiên, việc thực hiện apply trên DataFrame là rất chậm^apply. Mặc dù có thể viết mà không cần apply cho ví dụ như trên, nhưng có một sự đánh đổi giữa dễ viết và dễ đọc với apply.

▼ Trong trường hợp của Polars

df.with_columns(
    pl.when(((pl.col("A") + pl.col("B")) % 2 == 0) | (pl.col("A") == 3))
    .then("い")
    .when(pl.col("A") % 2 == 0)
    .then("ろ")
    .otherwise("は")
    .alias("C")
)

pl.when(...).then(...).otherwise(...) rõ ràng tương đương với câu lệnh if…else trong Python. Bạn có thể viết mà không cần apply (và viết một cách tự nhiên) và vẫn giữ được tốc độ xử lý.

Vậy nên, mặc dù trên bề mặt có vẻ không có nhiều sự khác biệt, nhưng Polars thể hiện sức mạnh của mình khi thực hiện các thao tác phức tạp.

:::note warn
Tính năng apply của polars đã thay đổi từ phiên bản 0.19 thành map_elements và các thay đổi khác.

https://pola-rs.github.io/polars/releases/upgrade/0.19/#groupby-renamed-to-group_by
:::

Đánh giá trễ

Đánh giá trễ là gì

Cho đến khi bạn rõ ràng yêu cầu Polars thực hiện tính toán, nó sẽ không thực hiện tính toán và thay vào đó, nó sẽ thực hiện tất cả các phép toán đã thực hiện cho đến thời điểm đó một cách thông minh và song song. “Thông minh” ở đây có nghĩa là Polars sẽ thực hiện tối ưu hóa câu truy vấnthực hiện song song[^lazy].

Điều này không có trong Pandas. Mặc dù có một số điểm giống với Dask, nhưng Dask không thực hiện tối ưu hóa câu truy vấn.

[^lazy]: Bạn có thể thấy đồ thị thực hiện bằng cách gọi .show_graph(optimized=True) cho một truy vấn. Cách nhanh chóng là tham khảo bài viết này: https://pola-rs.github.io/polars-book/user-guide/lazy/optimizations/ , và cách khác là đọc bài

viết này: https://towardsdatascience.com/understanding-lazy-evaluation-in-polars-b85ccb864d0c

Cách thức đánh giá trễ

Chỉ cần thêm lazy() vào chuỗi các phép toán, sau đó sử dụng collect() (hoặc fetch()) để thực hiện nó, đơn giản như vậy.

(df.
    .lazy()            # <= Tất cả các phép toán sau đây đều được chuyển sang đánh giá trễ
    .select(...)
    .with_columns(...)
    .group_by(...)
    .agg(...)
    .with_columns([..., ...])
    .collect()         # <= Thực hiện tất cả các phép toán đã được chuyển sang đánh giá trễ
)

Đối với mục đích gỡ lỗi hoặc khi bạn chỉ muốn thực hiện một số dòng giới hạn, bạn có thể sử dụng fetch thay vì collect.
Ngoài ra, nếu bạn muốn đánh giá trễ từ khi đọc dữ liệu, bạn có thể sử dụng scan_csv thay vì read_csv.

df = (
    scan_csv("path/to/your/data.csv")  # <= Đánh giá trễ ngay từ khi đọc dữ liệu
    .with_columns(...)
    .group_by(...)
    .agg(...)
    .collect()         # <= Thực hiện tất cả các phép toán đã được chuyển sang đánh giá trễ
)

Dĩ nhiên, tốc độ cải thiện có thể thay đổi tùy thuộc vào từng trường hợp, nhưng khi tôi thử nghiệm, thời gian thực hiện giảm xuống khoảng một nửa. Những lợi ích từ đánh giá trễ như vậy cũng phụ thuộc vào việc việc viết một chuỗi phương pháp liên tiếp là khả thi hay không.

Lưu ý và Một số Điểm

a. Sử dụng với matplotlib và sklearn

matplotlib/seaborn

Có thể sử dụng một cách thông thường.

import seaborn as sns
import matplotlib.pyplot as plt
df = pl.read_csv("https://j.mp/iriscsv")

# Trong trường hợp của matplotlib
plt.scatter(df["sepal_length"], df["petal_length"])

# Trong trường hợp của seaborn
sns.scatterplot(data=df, x="sepal_length", y="petal_length");

plotly

Từ phiên bản 5.16 trở đi, có thể sử dụng trực tiếp^plotly.

import plotly.express as px

# Cả hai cách viết đều OK
px.scatter(x=df["sepal_length"], y=df["petal_length"])
px.scatter(df, x="sepal_length", y="petal_length")

scikit-learn

Có thể sử dụng trực tiếp (không chắc chắn với tất cả các phương thức của sklearn).

from sklearn.linear_model import LinearRegression

model = LinearRegression()
model.fit(df.select(pl.all().exclude(["petal_width", "species"])), df["petal_width"])

Lưu ý rằng LightGBM hoạt động tốt với sklearn-API, nhưng đối với Training-API, có vẻ như bạn cần chuyển to_numpy() đối với y. X vẫn có thể sử dụng nguyên.

Statsmodels hiện vẫn chỉ hỗ trợ một số chức năng như add_constant.

b. Lưu ý với with_columns

Vì lý do thực hiện song song, bạn không thể sử dụng cột được tạo trong cùng một with_columns để tạo một cột khác. Bạn cần sử dụng with_columns riêng biệt cho điều này.

# Đúng
(df
    .with_columns([
        pl.col("A").cast(pl.Float64).alias("hoge"),
        pl.col("B").mean().over("G").alias("fuga")
    ])
    .with_columns([pl.col("hoge") + pl.col("fuga").alias("piyo")])
)

# Sai (cần phải tách ra thành nhiều with_columns)
(df
    .with_columns([
        pl.col("A").cast(pl.Float64).alias("hoge"),
        pl.col("B").mean().over("G").alias("fuga")
        (pl.col("hoge") + pl.col("fuga")).alias("piyo"))
    ])
)

Ngoài ra, bạn có thể sử dụng alias() thay vì alias().

df.with_columns([
    hoge=pl.col("A").cast(pl.Float64),
    fuga=pl.col("B").mean().over("G")
])

:::note warn
with_column đã được thay thế bằng with_columns.
:::

c. Lỗi thường gặp

  • Duplicate("Column with name: 'col' has more than one occurrences")
    Chỉ là cảnh báo về việc không thể có tên cột trùng nhau. Hãy sử dụng alias() để đặt tên cột khác nhau.

  • Gặp phải lỗi crash đôi khi?
    Có khả năng là nó sẽ crash khi xuất hiện một số lỗi cụ thể khi xử lý dữ liệu lớn. Đối mặt với sự cố, bạn có thể giảm kích thước dữ liệu hoặc sử dụng fetch để kiểm tra. Trong trường hợp của tôi, nó xuất hiện khi cố gắng thực hiện with_column với số lượng hàng không khớp với DataFrame.

d. Khác

Dưới đây là một số điều lẻ tẻ mà tôi gặp khi sử dụng Polars.

Xử lý phức tạp tương đương với transform của pandas

def mean_by_department(col: str) -> pl.Expre:
    return pl.col(col).mean().over(pl.col("department"))

(df
    .with_columns(mean_by_department("salary").alias("avg_salary_by_department")
    .filter(mean_by_department("overtime_hours") > 100)
)

Việc sử dụng pl.Exprover giúp thực hiện các phép toán tương đương với pandas. (Mặc dù cách sử dụng hàm trả về pl.Expr trong ví dụ như trên không phải là phổ biến nhưng tôi cảm thấy nó khá hữu ích).

Phân biệt giữa NaN và null

Phương thức phân biệt giữa NaN và null.

Xử lý theo chiều ngang

Đối với DataFrame, bạn có thể sử dụng axis=1.

df.select(pl.col("^sepal_.*$")).sum(axis=1)

Nếu sử dụng pl.Expr, các phương thức mới như sum_horizonal sau phiên bản v0.18.8 sẽ giúp thực hiện các phép toán theo chiều ngang^hor.
Ngoài ra, bạn có thể sử dụng kiểu dữ liệu List hoặc fold.

Gom nhóm không thực hiện phép toán tổng hợp

Thay vì sử dụng agg, bạn có thể sử dụng select.

df.select([
    pl.col("sepal_length").min().alias("length_min"),
    pl.col("sepal_length").median().alias("length_med"),
])

Tổng hợp theo thời gian

Các thao tác tổng hợp theo thời gian có thể được thực hiện bằng group_by_dynamic. Lưu ý rằng bạn cần sắp xếp DataFrame trước khi thực hiện để đảm bảo đúng đắn.

df.group_by_dynamic("Date", every="1y").agg(pl.col("A").mean())

Rolling.

:::note warn
groupby đã thay đổi thành group_by, và groupby_dynamic đã thay đổi thành group_by_dynamic.
Hãy đồng nhất trong cách gọi hàm bằng cách sử dụng dấu gạch dưới _ để ngăn cách từng từ trong tiếng Anh.
:::

Thêm hằng số và mảng numpy vào làm cột

Trong trường hợp của hằng số, bạn có thể sử dụng pl.lit, còn với các mảng như np.array hoặc danh sách, bạn có thể sử dụng pl.Series.

df.with_columns([
    pl.lit(3.14).alias("pie"),
    pl.lit("B").alias("const"),
    pl.Series(np.random.randn(4)).alias('np_random'),
    pl.Series([4,3,2,1]).alias('from_list'),
])

Thay đổi một số phần tử cụ thể

Việc gán tương đương với loc trong pandas hoặc xử lý where có thể được thực hiện bằng cách sử dụng when..then..otherwise hoặc các phương pháp như map_dicts.

Một số phương thức với tên hơi khác biệt so với pandas

Dưới đây là một số phương thức mà tôi chú ý được có tên khác nhau một chút (tất nhiên không phải tất cả).

pandas polars
isnull is_null
notnull is_not_null
nunique n_unique
groupby group_by
astype cast

Trong polars, việc sử dụng dấu gạch dưới _ để ngăn cách từng từ trong tiếng Anh có thể giúp bạn dễ dàng theo dõi.

Kết luận

Polars thường được giới thiệu với sự nhanh chóng của nó, nhưng tôi muốn đưa ra lập luận về tính dễ đọc và sử dụng của nó.

Thực sự, rất nhiều cách để viết mã dễ đọc tương tự như pandas, nhưng đôi khi hiệu suất giảm đi hoặc cách viết thông thường không hiệu quả hoặc không phổ biến. Trong Polars, bạn có thể dễ dàng đến với mã tối ưu mà không cần phải suy nghĩ nhiều. Điều này làm cho Polars trở thành lựa chọn tốt, không chỉ vì sự lớn mạnh của dữ liệu mà còn vì đơn giản và hiệu quả trong việc viết mã.

Mặc dù Polars xuất hiện vào khoảng năm 2020 và là một thư viện mới, nhưng với sự phát triển mạnh mẽ và sự hỗ trợ từ cộng đồng, có thể dự kiến nó sẽ trở nên mạnh mẽ hơn trong tương lai. Tôi tin rằng sẽ có những trường hợp nơi chúng ta chọn Polars ngay từ đầu thay vì chọn nó vì “dữ liệu lớn và khó xử lý với Pandas”.

dev_pro_it
Đang làm IT tại Japan

Bình luận

Bài viết chưa có bình luận. Hãy trở thành người bình luận đầu tiên!
Sign up for free and join this conversation.
Sign Up
If you already have a RongvangIT account Login
Danh sách thư mục
Bắt đầu ngay với RồngVàngIT - nền tảng chia sẻ kiến thức lập trình tuyệt vời cho kỹ sư Việt Nam!

Hãy đăng nhập để sử dụng hàng loạt các chức năng tuyệt vời của RồngVàngIT !

  1. 1. Bạn sẽ nhận được các bài viết phù hợp bằng chức năng theo dõi tag và người dùng.
  2. 2. Bạn có thể đọc lại các thông tin hữu ích bằng chức năng lưu trữ nội dung.
  3. 3. Chia sẻ kiến thức, đặt câu hỏi và ghi lại quá trình trưởng thành của mình cùng RồngVàngIT !
Tạo tài khoản Đăng nhập
profile Pic