Pandas vs Polars: 11 sự khác biệt trong cú pháp mà bạn nên biết

Chắc hẳn đa số mọi người đều biết Pandas là gì (một thư viện Python mạnh mẽ cho phân tích và xử lý dữ liệu) nhưng có thể chưa quen thuộc với Polars. Polars ra đời khoảng 3-4 năm trước (năm 2020) và được quảng cáo là một sự thay thế hoàn hảo cho Pandas. Viết bằng Rust, nó được thiết kế để khắc phục nhiều điểm yếu được cho là của Pandas trong khi vẫn giữ nguyên các chức năng cốt lõi của nó.

Như thường lệ khi bạn có hai công nghệ cạnh tranh như thế này, cuộc tranh luận về ưu và nhược điểm tương đối của mỗi công nghệ luôn diễn ra.

Với Polars, có 2 điểm nổi bật mà người dùng Polars luôn tự hào:

  1. Cải thiện hiệu suất cho ứng dụng của bạn khi sử dụng Polars so với việc sử dụng Pandas. Điều này đến từ việc Polars có thể tận dụng tất cả các lõi máy tính của bạn trong khi Pandas chỉ sử dụng một lõi duy nhất.
  2. Tương đồng cú pháp. Polars thường được quảng cáo là một lựa chọn gần như thay thế hoàn toàn cho Pandas.

Mặc dù vậy nhưng vẫn có một số sự khác biệt đáng kể trong cú pháp thao tác với dữ liệu dataframe mà bạn nên biết. Bài viết này sẽ nêu bật 10 điểm khác biệt phổ biến nhất.

Bắt đầu thôi. Chúng ta sẽ sử dụng Jupyter Notebook để chạy toàn bộ code. Để bắt đầu, chúng ta sẽ tạo hai dataframe, một cho Pandas và một cho Polars, để chạy từ file input chung và hiển thị các sự khác biệt về cú pháp.

Ở mỗi phần, tôi sẽ viết cả phần code cho Pandas tiếp theo là code của Polars.

Bạn nhớ đã cài đặt cả 2 thư viện Polars và Pandas nhé! Nếu chưa biết cài thế nào thì đây, hãy sử dụng đoạn code sau để cài đặt chúng từ Jupyter.

!pip install pandas
!pip install polars

1. Tạo dataframe

Cú pháp của cả 2 là giống nhau.

import pandas as pd
df1 = pd.DataFrame({'A': [1, 2, 3], 'B': ['a', 'b', 'c']})

import polars as pl
df2 = pl.DataFrame({'X': [4, 5, 6], 'Y': ['x', 'y', 'z']})

Điểm khác biệt là phần dữ liệu của hàm head().

Pandas:

df1.head()


  A B
0 1 a
1 2 b
2 3 c

Polars :

df2.head()

shape: (3, 2)
X   Y
i64 str
4   "x"
5   "y"
6   "z"

Output của Polars là kiểu giữ liệu của các cột,, nếu kiểu dữ liệu là text tự động được có thêm dấu ngoặc kép và không có đánh số dòng tự động như Pandas.

2. Lọc các hàng

Trong Pandas, để lọc các hàng theo một điều kiện nào đó thì cú pháp bên dưới là điều luôn được tin dùng. Ví dụ ở đây chúng ta lọc những hàng dữ liệu ở cột A có giá trị lớn hơn 1.

df1[df1['A'] > 1]

  A B
1 2 b
2 3 c

Ở Polars, cú pháp sẽ như thế này :

df2.filter(df2['X'] > 4)

shape: (2, 2)
X   Y
i64 str
5   "y"
6   "z"

3. Đổi tên cột

Pandas:

df1.rename(columns={'A': 'X'})

  X B
0 1 a
1 2 b
2 3 c

Polars:

df2.rename(mapping={'X':'A'})

shape: (3, 2)

A   Y
i64 str
4   "x"
5   "y"
6   "z"

4. Xóa một cột

Pandas:

df1.drop(columns='A')

  B
0 a
1 b
2 c

Polars:

df2.drop(['X'])



shape: (3, 1)

Y
str
"x"
"y"
"z"

5. GroupBy

Pandas:

df1 = pd.DataFrame({'A': [1, 2, 3, 4], 'B': ['a', 'a', 'b','b']})
print(df1)

   A  B
0  1  a
1  2  a
2  3  b
3  4  b


df1=df1.groupby("B").mean()
print(df1)

    A
B 
a   1.5
b   3.5

Polars:

df2 = pl.DataFrame({'A': [1, 2, 3, 4], 'B': ['a', 'a', 'b','b']})

df2.group_by('B').agg(pl.col('A').mean().alias('mean_B'))



shape: (2, 2)

B   mean_B
str f64
"b" 3.5
"a" 1.5

6. Joining hai dataframe

Đầu tiên chúng ta phải tiến hành khởi tạo 2 dataframe đã do cả 2 sử dụng 2 loại dataframe khác nhau. Với Pandas là Pandas DataFrame, với Polars là Polars DataFrame:

pandas_df1 = pd.DataFrame({
    'key': ['A', 'B', 'C', 'D'],
    'value1': [1, 2, 3, 4]
})

pandas_df2 = pd.DataFrame({
    'key': ['C', 'D', 'E', 'F'],
    'value2': [5, 6, 7, 8]
})

polars_df1 = pl.DataFrame({
    'key': ['A', 'B', 'C', 'D'],
    'value1': [1, 2, 3, 4]
})

polars_df2 = pl.DataFrame({
    'key': ['C', 'D', 'E', 'F'],
    'value2': [5, 6, 7, 8]
})

Rồi, giờ thì bắt đầu join 2 bảng lại nào

Pandas:

pandas_joined = pandas_df1.merge(pandas_df2, on='key', how='inner')
pandas_joined

   key value1 value2
0   C   3     5
1   D   4     6

Polars:

polars_joined = polars_df1.join(polars_df2, on='key', how='inner')
polars_joined


shape: (2, 3)

key value1 value2
str i64   i64
"C" 3     5
"D" 4     6

7. Sắp xếp

Sắp xếp lại các row data theo một thứ tự nhất định (Cột nào đó từ nhỏ tới lớn, từ lớn tới nhỏ…)

Pandas:

df1.sort_values(by='A',ascending=False) #Sắp xếp theo cột A thứ tự từ lớn tới nhỏ

  A B
2 3 c
1 2 b
0 1 a

Polars:

df2.sort('X',descending=True)



shape: (3, 2)

X   Y
i64 str
6   "z"
5   "y"
4   "x"

8. Apply hàm vào một cột

Chạy một hàm áp vào một cột dữ liệu, dữ liệu của cột đó nhân 2 lên (Ví dụ giá trị là 2 thì sau khi áp hàm vào thành 4)

Pandas:

df1['A'].apply(lambda x: x*2)

0    2
1    4
2    6

Name: A, dtype: int64

Polars:

df2['X'].apply(lambda x: x*2).alias('double_X')


C:\users\AppData\Local\Temp\ipykernel_48636\2326993985.py:1: DeprecationWarning: `apply` is deprecated. It has been renamed to `map_elements`.
  df2['X'].apply(lambda x: x*2).alias('double_X')
C:\users\AppData\Local\Temp\ipykernel_48636\2326993985.py:1: PolarsInefficientMapWarning: 
Series.map_elements is significantly slower than the native series API.
Only use if you absolutely CANNOT implement your logic otherwise.
In this case, you can replace your `map_elements` with the following:
  - s.map_elements(lambda x: ...)
  + s * 2

  df2['X'].apply(lambda x: x*2).alias('double_X')




shape: (3,)

double_X
i64
8
10
12

Cảnh báo lỗi trên là do việc sử dụng phương thức apply trong Polars đã bị loại bỏ và thay thế bằng map_elements. Thêm vào đó, còn có cảnh báo về hiệu suất, cho biết rằng việc sử dụng map_elements có thể chậm hơn so với API chuẩn của Series. Đề xuất rằng chỉ nên sử dụng map_elements nếu không thể thực hiện logic của bạn theo cách khác.

df2['X'].map_elements(lambda x: x*2).alias('double_X')

Vì vậy, bạn có thể thay thế dòng mã của mình bằng cách sử dụng phép nhân trực tiếp với hằng số để tránh cảnh báo hiệu suất:

9. Xử lí chuỗi

Xử lí dữ liệu, ở đây tôi chuyển chuỗi thành dạng in hoa (UPPERCASE) với hàm

Pandas:

df1['B'].str.upper()

0    A
1    B
2    C

Name: B, dtype: object

Polars:

df2['Y'].str.to_uppercase().alias('upper_Y')

shape: (3,)
upper_Y
str
"X"
"Y"
"Z"

10. Tạo cột mới từ 2 cột cũ

Ở đây tôi tạo một cột thứ ba từ 2 cột có sẵn bằng cách cộng chúng lại

Pandas:

df1['C'] =df1['A'] + df1['B']
df1


    A   B   C
0   1   4   5
1   2   5   7
2   3   6   9

Polars:

df2.with_columns((df2['X'] + df2['Y']).alias('Z'))



shape: (3, 3)

X   Y   Z
i64 i64 i64
4   7   11
5   8   13
6   9   15

11. Xử lí NaN

Pandas:

import pandas as pd
import numpy as np

# Create a DataFrame
data = {
    'A': [1, 2, np.nan, 4],
    'B': [5, np.nan, 7, 8],
    'C': [9, 10, 11, 12]
}

df1 = pd.DataFrame(data)

print(df1)

   A    B     C
0  1.0  5.0   9
1  2.0  NaN  10
2  NaN  7.0  11
3  4.0  8.0  12


df1.fillna(0, inplace=True)
df1

   A   B   C
0  1.0 5.0 9
1  2.0 0.0 10
2  0.0 7.0 11
3  4.0 8.0 12

Polars:

import polars as pl
import numpy as np

# Create a Polars DataFrame with NaN's
data = {
    'A': [1, 2, np.nan, 4],
    'B': [5, np.nan, 7, 8],
    'C': [9, 10, 11, 12]
}

df2 = pl.DataFrame(data)

print(df2)

shape: (4, 3)
┌─────┬─────┬─────┐
│ A   ┆ B   ┆ C   │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ i64 │
╞═════╪═════╪═════╡
│ 1.0 ┆ 5.0 ┆ 9   │
│ 2.0 ┆ NaN ┆ 10  │
│ NaN ┆ 7.0 ┆ 11  │
│ 4.0 ┆ 8.0 ┆ 12  │
└─────┴─────┴─────┘


df2 = df2.fill_nan(0.0)
print(df2)

shape: (4, 3)
┌─────┬─────┬─────┐
│ A   ┆ B   ┆ C   │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ i64 │
╞═════╪═════╪═════╡
│ 1.0 ┆ 5.0 ┆ 9   │
│ 2.0 ┆ 0.0 ┆ 10  │
│ 0.0 ┆ 7.0 ┆ 11  │
│ 4.0 ┆ 8.0 ┆ 12  │
└─────┴─────┴─────┘

Vậy là blog kết thúc rồi, hi vọng sau khi đọc xong bài này bạn có thể thông thạo ngay Polars, cũng không khó lắm phải không nào.
Còn với người mới, nếu phải chọn thì lời khuyên của tôi là nên học Pandas trước rồi hẵn học Polars vì nhiều lí do. Tôi sẽ viết 1 bài viết khác về việc tại sao tôi lại nói vậy và so sánh hiệu suất của cả 2 một cách kĩ càng hơn nhé.

Blog hôm nay đã dài rồi, cảm ơn các bạn đã đọc tới đây. Hẹn gặp lại ở một blog khác nhé!

Add a Comment

Scroll Up