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:
data = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd']) data['a']
1
Ngoài ra ta còn có thể sử dụng một số biểu thức của Dictionary như sau:
print("'c' trong data: ", 'c' in data) print("keys: ", data.keys()) print("values: ", list(data.items()))
'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:
data['Zaidap.com'] = 9999 data
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):
# Array slicing, lấy giá trị trong khoảng index từ a -> c data['a': 'c']
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:
# Array Slicing, lấy giá trị trong khoảng index từ 0 -> 3 data[0:3]
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:
# Lấy phần tử có index là 0 và 1, không lấy 2 data[0:2]
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:
# Lấy các hàng chia hết cho 3 data[data % 3 == 0]
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:
# explicit index data[['b', 'd']]
b 2 d 4 dtype: int64
Và với implicit index:
# implicit index data[[1, 4]]
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:
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])
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:
Vì đ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 và 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:
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])
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:
# 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])
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:
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
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:
data['Dân số']
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:
data['population'] = data['Dân số'] data.population
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:
data['Mật độ'] = data['Dân số'] / data['Diện tích'] data
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:
data.values
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):
data.T
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:
print("Sử dụng chuyển vị: ", data.T.values[0]) print("Sử dụng index: ", data['Dân số'].values)
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 và 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:
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])
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:
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 độ'])
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ư:
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 độ']])
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:
# 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)
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:
# Indexing print("Indexing: ", data['Dân số']) # Slicing print(" Slicing: ",data['Hanoi':'Quang Tri'])
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:
print(data[0:2])
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:
print(data[data['Dân số'] > 1000])
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.