06/04/2021, 14:46

Data Selection và Indexing trong Pandas - Pandas

Trong bài này chúng ta sẽ tìm hiểu Data Selection và Indexing trong Pandas, thông qua hai đối tượng DataFrame và Series. Trong chương NumPy, chúng ta đã tìm hiểu các cách để truy cập vào các phần tử / mảng con như: indexing (vd: arr[2]), slicing (vd: ...

Trong bài này chúng ta sẽ tìm hiểu Data Selection và Indexing trong Pandas, thông qua hai đối tượng DataFrame và Series.

Trong chương NumPy, chúng ta đã tìm hiểu các cách để truy cập vào các phần tử / mảng con như:

  • indexing (vd: arr[2]),
  • slicing (vd: arr[:, 2]),
  • masking (vd: arr[arr % 2 == 0),
  • fancy indexing (vd: arr[[2, 1, 5]])
  • và kết hợp các kiểu trên (vd: arr[[1, 2, 5], :]).

Vì Pandas được xây dựng từ NumPy, nên cách truy cập và chỉnh sửa giá trị phần tử của 2 thư viện không khác nhau mấy, nếu bạn đã làm quen với NumPy thì sẽ khá dễ để làm quen. Dù vậy, sẽ có một số sự khác biệt mà trong bài mình sẽ đề cập đến

1. Data Selection và Indexing trong Series

Như mình đã nói ở bài trước, ta có thể hình dung Series trong Pandas chính là mảng 1 chiều trong NumPy hoặc là Dictionary trong Python, từ các cách hiểu đó thì ta sẽ đi xem xét từng trường hợp cụ thể:

Series là Dictionary

Giống như Dictionary, Series cho phép ta map các cặp key - value tương ứng với nhau:

In[2]
data = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])

data['a']
Out[2]
1

Ngoài ra ta còn có thể sử dụng một số biểu thức của Dictionary như sau:

In[3]
print("'c' trong data: ", 'c' in data)
print("keys: ", data.keys())
print("values: ", list(data.items()))
Out[3]
'c' trong data:  True
keys:  Index(['a', 'b', 'c', 'd'], dtype='object')
values:  [('a', 1), ('b', 2), ('c', 3), ('d', 4)]

Tương tự như Dictionary, bạn hoàn toàn có thể thêm 1 giá trị mới vào series bằng cách gán một cặp key - value tương ứng:

In[4]
data['Zaidap.com'] = 9999

data
Out[4]
a              1
b              2
c              3
d              4
Zaidap.com    9999
dtype: int64

Pandas sẽ đưa ra quyết định về bố trí bộ nhớ cũng như cách xử lý nó và chúng ta hầu như không cần quan tâm về vấn đề này. Đây là một trong những điểm rất tiện dụng của Pandas.

Series là mảng 1 chiều

Với cách hình dung series là mảng 1 chiều, ta sẽ thao tác với nó không khác việc thao tác với NumPy là mấy, ta sẽ tìm hiểu một số thao tác cơ bản:

Array Slicing

Array slicing trong Series tương tự như NumPy, tuy nhiên có 2 kiểu slicing, đầu tiên là slicing với index chỉ định (explicit index - nhãn của các phần tử trong dãy):

In[5]
# Array slicing, lấy giá trị trong khoảng index từ a -> c
data['a': 'c']
Out[5]
a    1
b    2
c    3
dtype: int64

Thứ 2 là slicing với index ngầm định (implicit index - vị trí của các phần tử trong dãy), chẳng hạn với ví dụ trên, ta có slicing tương tự sau:

In[7]
# Array Slicing, lấy giá trị trong khoảng index từ 0 -> 3
data[0:3]
Out[7]
a    1
b    2
c    3
dtype: int64

Đây cũng là điểm cần lưu ý trong Pandas, đó là khi ta dùng slicing với index thì cặp index và giá trị tương ứng cuối cùng sẽ được trả về (cụ thể ở ví dụ trên là c - 3), trong khi nếu như sử dụng với index ngầm định thì không:

In[8]
# Lấy phần tử có index là 0 và 1, không lấy 2
data[0:2]
Out[8]
a    1
b    2
dtype: int64

Array masking

Cũng tương tự như trong NumPy, ta có thể sử dụng masks lên series như sau:

In[9]
# Lấy các hàng chia hết cho 3
data[data % 3 == 0]
Out[9]
c              3
Zaidap.com    9999
dtype: int64

Fancy Indexing

Giống như array slicing, ta có 2 kiểu fancy indexing, đó là sử dụng với explicit index:

In[10]
# explicit index
data[['b', 'd']]
Out[10]
b    2
d    4
dtype: int64

Và với implicit index:

In[11]
# implicit index
data[[1, 4]]
Out[11]
b              2
Zaidap.com    9999
dtype: int64

Indexing trong Series

Có thể thấy rằng explicit index và implicit index khá là dễ nhầm lẫn với nhau, mình có một ví dụ minh hoạ như sau:

In[12]
data = pd.Series(['F', 'r', 'e', 'e', 't', 'u', 't', 's'], index=[1, 3, 5, 6, 7, 9, 10, 12])

print("Data: ", data)

# explicit index
print("data[1]: ", data[1])

# implicit index 
print("data[0:5]: ", data[1:3])
Out[12]
Data: 
1     F
3     r
5     e
6     e
7     t
9     u
10    t
12    s
dtype: object

data[1]:  F
data[0:5]:  
3    r
5    e
dtype: object

Ảnh sau mô tả quan hệ giữa implicit index và explicit index:

1 pngVì điều này nên Pandas cung cấp cho ta một số thuộc tính được gọi là indexers gồm loc, iloc ix, các thuộc tính này sẽ cho phép ta biết được ta đang sử dụng loại index gì mà không bị nhầm lẫn.

Đầu tiên là loc, thuộc tính này cho phép chúng ta indexing và slicing bằng explicit index:

In[13]
print("Lấy giá trị có index tương ứng là 1:", data.loc[1])

print("Lấy khoảng giá trị có index nằm trong khoảng [1, 6]:", data.loc[1:6])
Out[13]
Lấy giá trị có index tương ứng là 1: F

Lấy khoảng giá trị có index nằm trong khoảng [1, 6]: 
1    F
3    r
5    e
6    e
dtype: object

Còn iloc thì tương tự như loc nhưng là với implicit index:

In[14]
# Implicit index = 1 tương ứng với vị trí thứ 2 trong mảng
print("Lấy giá trị có implicit index = 1:", data.iloc[1])

# Implicit index = [1,6] tương ứng với vị trí thứ 1, 2, 3, 4, 5 trong mảng
print("Lấy khoảng giá trị có implicit index nằm trong khoảng [1, 6]:", data.iloc[1:6])
Out[14]
Lấy giá trị có implicit index = 1: r
Lấy khoảng giá trị có implicit index nằm trong khoảng [1, 6]:
3    r
5    e
6    e
7    t
9    u
dtype: object

Với ix thì nó sẽ là sự kết hợp của cả 2 thuộc tính trên, và nó cũng không khác gì với việc ta indexing truyền thống - dễ gây nhầm lẫn khi sử dụng 2 kiểu index trên, do vậy nên từ phiên bản pandas 0.20.0, thuộc tính ix đã deprecated và không còn khuyến khích sử dụng nữa. Vì vậy trong bài này và trong series pandas nói chung, mình sẽ không đề cập đến thuộc tính này.

2. Data Selection và Indexing trong DataFrame

Cũng giống như Series và đã nhắc ở bài trước, với DataFrame thì ta sẽ hình dung nó như là một mảng 2 chiều hoặc như structured array trong NumPy.

Ngoài ra, ta có thể xem DataFrame giống như là một dictionary chứa các series có chung index với nhau (khá là giống với excel). Ta sẽ tìm hiểu từng trường hợp cụ thể sau:

DataFrame là Dictionary

Quay lại với ví dụ ở bài 2, ta có một DataFrame chứa các Series là dữ liệu dân số và diện tích của một số tỉnh / thành phố ở Việt Nam sau:

In[15]
population = pd.Series({'TP.HCM': 8993, 'Hanoi': 8053, 'Lam Dong': 1297, 'Quang Tri': 623})
area = pd.Series({'TP.HCM': 2061, 'Hanoi': 3359, 'Lam Dong': 9765, 'Quang Tri': 4746})

data = pd.DataFrame({'Dân số': population, 'Diện tích': area})

data
Out[15]
              Dân số	Diện tích
TP.HCM	       8993	2061
Hanoi	       8053	3359
Lam Dong       1297	9765
Quang Tri	623	4746

Từng Series trong DataFrame có thể được truy cập theo kiểu dictionary bằng cách index vào tên của cột tương ứng:

In[15]
data['Dân số']
Out[15]
TP.HCM       8993
Hanoi        8053
Lam Dong     1297
Quang Tri     623
Name: Dân số, dtype: int64

Ngoài cách trên, pandas cho phép ta truy cập trực tiếp vào các Series như là một thuộc tính của object:

In[16]
data['population'] = data['Dân số']

data.population
Out[16]
TP.HCM       8993
Hanoi        8053
Lam Dong     1297
Quang Tri     623
Name: population, dtype: int64

Và ta cũng có thể thêm các cặp dữ liệu mới bằng cách tương tự như Series:

In[17]
data['Mật độ'] = data['Dân số'] / data['Diện tích']

data
Out[17]
          Dân số	Diện tích	Mật độ
TP.HCM	   8993	            2061	4.363416
Hanoi	    8053	    3359	2.397440
Lam Dong    1297	    9765	0.132821
Quang Tri    623	    4746	0.131268

DataFrame là mảng 2 chiều

Với cách hình dung DataFrame là mảng 2 chiều thì cũng giống như Series là mảng 1 chiều, ta sẽ có một mảng 2 chiều hoạt động khá tương tự với NumPy. Chẳng hạn muốn lấy dữ liệu thô thì ta có thể truy cập qua thuộc tính values:

In[18]
data.values
Out[18]
array([[8.99300000e+03, 2.06100000e+03, 4.36341582e+00],
       [8.05300000e+03, 3.35900000e+03, 2.39743971e+00],
       [1.29700000e+03, 9.76500000e+03, 1.32821301e-01],
       [6.23000000e+02, 4.74600000e+03, 1.31268437e-01]])

Nếu muốn lấy mảng chuyển vị (hoán đổi cột và hàng), ta có thể truy cập vào thuộc tính T (viết tắt cho transpose):

In[19]
data.T
Out[19]
           TP.HCM  Hanoi  Lam Dong  Quang Tri
Dân số       8993   8053      1297        623
Diện tích    2061   3359      9765       4746

Như vậy ta có thể thấy rằng sẽ có 2 cách để lấy mảng dữ liệu (dạng NumPy) từ một cột trong Pandas như sau:

In[20]
print("Sử dụng chuyển vị: ", data.T.values[0])
print("Sử dụng index: ", data['Dân số'].values)
Out[20]
Sử dụng chuyển vị:  [8993. 8053. 1297.  623.]
Sử dụng index:  [8993 8053 1297  623]

Indexing trong DataFrame

Giống với Series, ta sẽ sử dụng 2 thuộc tính là loc iloc. Với iloc, ta sẽ thao tác không khác gì một mảng 2 chiều trong NumPy với các hàng và cột tương ứng vậy, chẳng hạn:

In[22]
print(data)

# Lấy hàng đầu tiên
print("
Dữ liệu của TP.HCM:
 ",data.iloc[0])

# Lấy dữ liệu 3 hàng đầu tiên
print("
Dữ liệu của TP.HCM, Hà Nội và Lâm Đồng:
 ",data.iloc[:3])

# Lấy dữ liệu 2 hàng và 2 cột đầu tiên
print("
Dữ liệu dân số và diện tích của TP.HCM và Hà Nội:
 ",data.iloc[:2, :2])

# Lấy dữ liệu tất cả các hàng và cột cuối cùng
print("
Mật độ dân số của các tỉnh / thành phố:
 ",data.iloc[:, -1])
Out[22]
           Dân số  Diện tích    Mật độ
TP.HCM       8993       2061  4.363416
Hanoi        8053       3359  2.397440
Lam Dong     1297       9765  0.132821
Quang Tri     623       4746  0.131268

Dữ liệu của TP.HCM:
Dân số       8993.000000
Diện tích    2061.000000
Mật độ          4.363416
Name: TP.HCM, dtype: float64

Dữ liệu của TP.HCM, Hà Nội và Lâm Đồng:
            Dân số  Diện tích    Mật độ
TP.HCM      8993       2061  4.363416
Hanoi       8053       3359  2.397440
Lam Dong    1297       9765  0.132821

Dữ liệu dân số và diện tích của TP.HCM và Hà Nội:
          Dân số  Diện tích
TP.HCM    8993       2061
Hanoi     8053       3359

Mật độ dân số của các tỉnh / thành phố:
TP.HCM       4.363416
Hanoi        2.397440
Lam Dong     0.132821
Quang Tri    0.131268
Name: Mật độ, dtype: float64

Với thuộc tính loc thì ta cũng sử dụng tương tự, nhưng thay vì với implicit index thì ta sử dụng với explicit index tương ứng:

In[23]
print(data)

# Lấy hàng đầu tiên
print("
Dữ liệu của Quảng Trị:
 ",data.loc['Quang Tri'])

# Lấy dữ liệu 3 hàng đầu tiên
print("
Dữ liệu của TP.HCM, Hà Nội và Lâm Đồng:
 ",data.loc[:'Lam Dong'])

# Lấy dữ liệu 2 hàng và 2 cột đầu tiên
print("
Dữ liệu dân số và diện tích của TP.HCM và Hà Nội:
 ",data.loc[:'Hanoi', :'Diện tích'])

# Lấy dữ liệu tất cả các hàng và cột cuối cùng
print("
Mật độ dân số của các tỉnh / thành phố:
 ",data.loc[:, 'Mật độ'])
Out[23]
           Dân số  Diện tích    Mật độ
TP.HCM       8993       2061  4.363416
Hanoi        8053       3359  2.397440
Lam Dong     1297       9765  0.132821
Quang Tri     623       4746  0.131268

Dữ liệu của Quảng Trị:
Dân số        623.000000
Diện tích    4746.000000
Mật độ          0.131268
Name: Quang Tri, dtype: float64

Dữ liệu của TP.HCM, Hà Nội và Lâm Đồng:
            Dân số  Diện tích    Mật độ
TP.HCM      8993       2061  4.363416
Hanoi       8053       3359  2.397440
Lam Dong    1297       9765  0.132821

Dữ liệu dân số và diện tích của TP.HCM và Hà Nội:
          Dân số  Diện tích
TP.HCM    8993       2061
Hanoi     8053       3359

Mật độ dân số của các tỉnh / thành phố:
TP.HCM       4.363416
Hanoi        2.397440
Lam Dong     0.132821
Quang Tri    0.131268
Name: Mật độ, dtype: float64

Ta có thể kết hợp hai tính năng quan trọng trong NumPy là Masks và Fancy Indexing để tạo ra những câu truy vấn phức tạp hơn, ví dụ như:

In[24]
print("
Dân số và mật độ của các tỉnh / TP có diện tích > 4000km2:
", data.loc[data['Diện tích'] > 4000, ['Dân số', 'Mật độ']])

print("
Diện tích của các tỉnh / TP có mật độ < 1000 người/km2 và dân số < 1 triệu người:
", data.loc[(data['Mật độ'] < 1) & (data['Dân số'] < 1000), ['Dân số', 'Mật độ']])
Out[24]
Dân số và mật độ của các tỉnh / TP có diện tích > 4000km2:
            Dân số    Mật độ
Lam Dong     1297  0.132821
Quang Tri     623  0.131268

Diện tích của các tỉnh / TP có mật độ < 1000 người/km2 và dân số < 1 triệu người:
            Dân số    Mật độ
Quang Tri     623  0.131268

Và ta cũng có thể sử dụng bất kỳ thuộc tính nào trong 2 thuộc tính trên để thay đổi giá trị trong DataFrame:

In[25]
# Thay đổi dữ liệu mật độ dân số của tỉnh Quảng Trị bằng iloc
data.iloc[3, 2] = 1312

print("
Dữ liệu tỉnh Quảng Trị được cập nhật: 
", data)

# Thay đổi dữ liệu dân số của tỉnh Lâm Đồng bằng loc
data.loc['Lam Dong', 'Dân số'] = 1312

print("
Dữ liệu tỉnh Lâm Đồng được cập nhật: 
", data)
Out[25]
Dữ liệu tỉnh Quảng Trị được cập nhật: 
            Dân số  Diện tích       Mật độ
TP.HCM       8993       2061     4.363416
Hanoi        8053       3359     2.397440
Lam Dong     1312       9765     0.132821
Quang Tri     623       4746  1312.000000

Dữ liệu tỉnh Lâm Đồng được cập nhật: 
            Dân số  Diện tích       Mật độ
TP.HCM       8993       2061     4.363416
Hanoi        8053       3359     2.397440
Lam Dong     1312       9765     0.132821
Quang Tri     623       4746  1312.000000

Có một vài quy ước trong Pandas không hoàn toàn giống NumPy và ta nên nắm rõ vì nó khá hữu dụng trong thực tế. Đầu tiên đó là indexing thường chỉ đến cột, còn slicing thường dành cho hàng:

In[26]
# Indexing 
print("Indexing: 
", data['Dân số'])

# Slicing
print("
Slicing: 
",data['Hanoi':'Quang Tri'])
Out[26]
Indexing: 
TP.HCM       8993
Hanoi        8053
Lam Dong     1312
Quang Tri     623
Name: Dân số, dtype: int64

Slicing: 
            Dân số  Diện tích       Mật độ
Hanoi        8053       3359     2.397440
Lam Dong     1312       9765     0.132821
Quang Tri     623       4746  1312.000000

Tiếp theo, nếu như dùng slicing thì pandas sẽ mặc định tham chiếu đến implicit index:

In[27]
print(data[0:2])
Out[27]
        Dân số  Diện tích    Mật độ
TP.HCM    8993       2061  4.363416
Hanoi     8053       3359  2.397440

Và cuối cùng, tương tự slicing thì masking sẽ tham chiếu đến hàng tương ứng thay vì cột:

In[28]
print(data[data['Dân số'] > 1000])
Out[29]
          Dân số  Diện tích    Mật độ
TP.HCM      8993       2061  4.363416
Hanoi       8053       3359  2.397440
Lam Dong    1312       9765  0.132821

3. Tổng kết

Qua bài trên ta đã tìm hiểu về data selection và indexing trong Pandas. Đây là một bài khá quan trọng, cung cấp cho ta cách để truy cập và lấy những dữ liệu cần thiết, bạn nên tạo một số bộ data ngẫu nhiên và thử các kiểu index cũng như lấy dữ liệu trên, kết hợp với những phương pháp đã học ở NumPy (masking, fancy indexing) để thêm kinh nghiệm nhé, hẹn gặp bạn ở bài tiếp theo.

0