🍀Tóm tắt buổi trước

Okay bữa trước chúng ta học xong về NumPy dù là cần phải thực hành thêm thì chúng ta mới quen hơn. Nhưng không sao chúng ta cứ đi những kiến thức cơ bản cho đầy đủ rồi ráp vào các bài toán cụ thể thì sẽ dễ nhớ hơn

Bạn có thể coi lại bài trước của mình:

🔴 Buổi Live ngày 11:

🍀Khái quát về thư viện pandas

pandas chứa các cấu trúc dữ liệu kèm công cụ thao tác với dữ liệu để làm sạch và phân tích dữ liệu nhanh chóng và thuận tiện.

Nó thường được kết hợp sử dụng với các thư viện khác tùy vào mục đích như để 1tính toán đại số: NumPySciPy, để 2phân tích: statsmodelsscikit-learn và các thư viện 3trực quan hóa dữ liệu: matplotlib.

Có thể bạn “đã biết” thì pandas thừa hưởng tính năng đặc trưng của NumPy là tính toán-dựa trên mảng array-based computing, bên cạnh đó là sự bổ sung của các hàm đặc biệt xử lý trên mảng.

Mặc dù pandas sử dụng nhiều quy ước mã hóa từ NumPy, nhưng điểm khác biệt lớn nhất là pandas được thiết kế để làm việc với dữ liệu bảng hoặc dữ liệu không đồng nhất. Ngược lại, NumPy phù hợp nhất để làm việc với dữ liệu mảng số có kiểu đồng nhất.

Kể từ khi trở thành một dự án mã nguồn mở vào năm 2010, pandas đã phát triển thành một thư viện khá lớn được áp dụng trong một tập hợp rộng lớn các trường hợp sử dụng trong thế giới thực. Cộng đồng phát triển đã phát triển lên hơn 2.500 cộng tác viên riêng biệt, những người đã giúp xây dựng dự án khi họ sử dụng nó để giải quyết các vấn đề dữ liệu hàng ngày của mình. Cộng đồng phát triển và người dùng pandas sôi động là một phần quan trọng trong thành công của nó.

Convention code: Trong suốt những buổi còn lại thì mình sẽ tuân theo một mẫu import thư viện như sau – đây cũng được xem là tiêu chuẩn trong cộng động:

In [1]: import numpy as np

In [2]: import pandas as pd

Nên sau này nếu bạn thấy pd nghĩa là nó đang tham chiếu tới pandas. Đôi khi một số trường hợp bạn sẽ thấy chúng ta chỉ sử dụng một vài module từ thư viện được thêm vào local namespace vì chúng ta chỉ cần bao nhiêu đó thôi (tối giản):

In [3]: from pandas import Series, DataFrame

🍀 Các cấu trúc dữ liệu của pandas

Trong pandas có hai cấu trúc dữ liệu “cây nhà lá vườn” của nó mà ta nên làm quen trước khi đi xa hơn: Series DataFrame.

Tất nhiên như mọi cấu trúc dữ liệu khác, tụi nó không phải là giải pháp toàn vẹn cho mọi vấn đề nên không hẳn lúc nào dân Data cũng lưu dữ liệu vào nó để tiếp tục xử lý ở các bước tiếp theo. Nhưng ít nhất thì tụi nó cho chúng ta nhiều cách xử lý đáng tin cậy cho khá nhiều các tác vụ dữ liệu khác nhau.

🚀Series

Bản chất là một mảng 1-chiều các dữ liệu chung kiểu và gắn với mảng các nhãn dữ liệu index (nó cũng là một sequence object và khá tương tự với kiểu NumPy)

Điểm đặc biệt trong Series khác với index trong các cấu trúc dữ liệu khác là ta có thể thay đổi không dùng hệ 0-index.

Note
In [14]: obj = pd.Series([4, 7, -5, 3])

In [15]: obj
Out[15]: 
0    4
1    7
2   -5
3    3
dtype: int64

🔥 PandasArray object

Phần in ra màn hình cho thấy cột bên trái là index còn bên phải tương ứng là giá trị. Mặc định khi ta không định nghĩa trước thì sẽ đánh index từ 0 tới N-1 với N là độ dài của dữ liệu. Bạn có thể lấy riêng ra các đối tượng index hay value bằng thuộc tính tương ứng indexarray:

In [16]: obj.array
Out[16]: 
<PandasArray>
[4, 7, -5, 3]
Length: 4, dtype: int64

In [17]: obj.index
Out[17]: RangeIndex(start=0, stop=4, step=1)

Kết quả trả về của thuộc tính .array là một PandasArray – một đối tượng về bản chất chứa một mảng NumPy, mà tính chất của mảng NumPy là đồng nhất về kiểu dữ liệu.

Vấn đề ở đây là nếu mảng đó có một giá trị Null thì sao? May sao gần đây thì pandas có phát triển các loại dữ liệu mở rộng giúp linh hoạt hơn.

🔥 labels thay vì index

Lưu ý: Bản thân nhãn hay chỉ mục cũng đều mang ý nghĩa giống nhau nhưng mình muốn dùng riêng hai tên này để phân biệt 0-indextext-index

Mặc định label của Series theo 0-index nhưng ta có thể tuỳ biến gắn từng giá trị với các nhãn khác nhau

In [18]: obj2 = pd.Series([4, 7, -5, 3], index=["d", "b", "a", "c"])

In [19]: obj2
Out[19]: 
d    4
b    7
a   -5
c    3
dtype: int64

In [20]: obj2.index
Out[20]: Index(['d', 'b', 'a', 'c'], dtype='object')

Để cho dễ hình dung ta có thể xem Series giống một Dictionary nhưng cố định độ dài và có sắp xếp thứ tự,

Note

Bạn cũng hoàn toàn có thể thay đổi trực tiếp index bằng labels

🔥 index hoặc indices

So sánh với mảng NumPy, nhờ tính chất tương đồng với dictionary nên ta khá linh hoạt khi chọn các giá trị đơn lẻ hoặc nhóm các nhãn:

In [21]: obj2["a"]
Out[21]: -5

In [22]: obj2["d"] = 6

In [23]: obj2[["c", "a", "d"]]
Out[23]: 
c    3
a   -5
d    6
dtype: int64

Ở đây ["c", "a", "d"] được hiểu là một danh sách các chỉ mục, ngay cả khi nó chứa các chuỗi thay vì các số nguyên.

🔥 Liên kết index-value

Thực hiện tính toán biến đổi bằng các hàm NumPy hoặc các toán tử NumPy, như lọc theo mảng Boolean, tích vô hướng hoặc áp dụng các hàm toán học, vẫn giữa nguyên liên kết giữa nhãn-giá trị

In [24]: obj2[obj2 > 0]
Out[24]: 
d    6
b    7
c    3
dtype: int64

In [25]: obj2 * 2
Out[25]: 
d    12
b    14
a   -10
c     6
dtype: int64

In [26]: import numpy as np

In [27]: np.exp(obj2)
Out[27]: 
d     403.428793
b    1096.633158
a       0.006738
c      20.085537
dtype: float64

🔥 Từ khóa “in”

Ta cũng có những cú pháp giống với dictionary:

In [28]: "b" in obj2
Out[28]: True

In [29]: "e" in obj2
Out[29]: False

🔥 Ép kiểu “dict” thành “series”

Điều thú vị là ta hoàn toàn có thế biến dictionary thành series:

In [30]: sdata = {"Ohio": 35000, "Texas": 71000, "Oregon": 16000, "Utah": 5000}

In [31]: obj3 = pd.Series(sdata)

In [32]: obj3
Out[32]: 
Ohio      35000
Texas     71000
Oregon    16000
Utah       5000
dtype: int64

Và ta cũng có chiều ngược lại

In [33]: obj3.to_dict()
Out[33]: {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}

🔥 Sắp xếp lại thứ tự nhãn

Bạn có thể thay đổi vị trí nhãn bằng tham số index. Nhưng nếu truyền vào một nhãn không tồn tại trong danh sách các khóa của dictionary thì nó sẽ trả về NaN (Not a Number) – đánh dấu là giá trị missing hoặc NA trong pandas.

In [34]: states = ["California", "Ohio", "Oregon", "Texas"]

In [35]: obj4 = pd.Series(sdata, index=states)

In [36]: obj4
Out[36]: 
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
dtype: float64

🔥 Làm việc với giá trị rỗng

Hai hàm isnanotna trong pandas để xác định các giá trị NA

In [37]: pd.isna(obj4)
Out[37]: 
California     True
Ohio          False
Oregon        False
Texas         False
dtype: bool

In [38]: pd.notna(obj4)
Out[38]: 
California    False
Ohio           True
Oregon         True
Texas          True
dtype: bool

Bản thân đối tượng cũng có phương thức thực thể (instance methods) isna của riêng nó:

In [39]: obj4.isna()
Out[39]: 
California     True
Ohio          False
Oregon        False
Texas         False
dtype: bool

Vì bản thân việc xử lý dữ liệu rỗng là một chủ đề rộng nên chúng ta sẽ còn quay trở lại vài kỳ sau.

🔥 Data alignment – Join trong Series

Có một đặc tính hay ho của Series là nó tự động bắt cặp các nhãn giống nhau khi tính toán giữa các Series:

In [40]: obj3
Out[40]: 
Ohio      35000
Texas     71000
Oregon    16000
Utah       5000
dtype: int64

In [41]: obj4
Out[41]: 
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
dtype: float64

In [42]: obj3 + obj4
Out[42]: 
California         NaN
Ohio           70000.0
Oregon         32000.0
Texas         142000.0
Utah               NaN
dtype: float64

Tính chất này có nét tương đồng với cách hoạt động của toán tử JOIN khi làm việc với CSDL

Note

🔥 Thuộc tính “name”

Cả đối tượng Series hay thuộc tính index của nó đều có một thuộc tính con là name mà ta sẽ dùng tới ở các chức năng khác của pandas. Ở đây trước mắt ta gán name cho 2 đối tượng trên:

In [43]: obj4.name = "population"

In [44]: obj4.index.name = "state"

In [45]: obj4
Out[45]: 
state
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
Name: population, dtype: float64

🚀 DataFrame

DataFrame biểu diễn một bảng dữ liệu “hình chữ nhật” – Ta hay gọi là tabular, chứa tập hợp các cột được đặt tên và có thứ tự, mỗi cột có thể có một kiểu giá trị khác nhau (số, chuỗi, Boolean, v.v.). DataFrame có cả chỉ mục hàng và cột; nó có thể được coi là một “dictionary của các Series có cùng một chỉ mục“.

Mặc dù DataFrame về mặt vật lý là hai chiều, bạn có thể sử dụng nó để biểu diễn dữ liệu nhiều chiều hơn trong định dạng tabular bằng cách sử dụng chỉ mục phân cấp ("hierachical indexing")

🔥 Cách khởi tạo

Có nhiều cách để tạo DataFrame, mặc dù một trong nhưng cách phổ dụng nhất là từ dictionary có các key kích thước giống nhau hoặc từ một mảng NumPy

data = {"state": ["Ohio", "Ohio", "Ohio", "Nevada", "Nevada", "Nevada"],
        "year": [2000, 2001, 2002, 2001, 2002, 2003],
        "pop": [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
frame = pd.DataFrame(data)

Kết quả ở trên cho ra DataFrame với thứ tự các cột tương ứng với thứ tự các khóa trong dictionary (giống như Series các chỉ mục được tự động gán qua).

In [50]: frame
Out[50]: 
    state  year  pop
0    Ohio  2000  1.5
1    Ohio  2001  1.7
2    Ohio  2002  3.6
3  Nevada  2001  2.4
4  Nevada  2002  2.9
5  Nevada  2003  3.2

🔥 Truy vấn dữ liệu từ DataFrame

Một hoạt động chúng ta thường hay làm là Data Profiling, và với Python chúng ta có một số hàm giúp ta làm việc này

📑 Phương thức head chọn ra 5 dòng đầu tiên của DataFrame

In [51]: frame.head()
Out[51]: 
    state  year  pop
0    Ohio  2000  1.5
1    Ohio  2001  1.7
2    Ohio  2002  3.6
3  Nevada  2001  2.4
4  Nevada  2002  2.9

📑 Tương tự tail trả về 5 dòng cuối

In [52]: frame.tail()
Out[52]: 
    state  year  pop
1    Ohio  2001  1.7
2    Ohio  2002  3.6
3  Nevada  2001  2.4
4  Nevada  2002  2.9
5  Nevada  2003  3.2

📑 Nếu bạn chỉ định một số cột nhất định khi truyền vào tham số columns thì ta nhận được tương ứng số cột với thứ tự được sắp xếp

In [53]: pd.DataFrame(data, columns=["year", "state", "pop"])
Out[53]: 
   year   state  pop
0  2000    Ohio  1.5
1  2001    Ohio  1.7
2  2002    Ohio  3.6
3  2001  Nevada  2.4
4  2002  Nevada  2.9
5  2003  Nevada  3.2

📑 Nếu ta lấy một cột không tồn tại thì ta nhận được cột đó chứa các giá trị rỗng (NaN)

In [54]: frame2 = pd.DataFrame(data, columns=["year", "state", "pop", "debt"])

In [55]: frame2
Out[55]: 
   year   state  pop debt
0  2000    Ohio  1.5  NaN
1  2001    Ohio  1.7  NaN
2  2002    Ohio  3.6  NaN
3  2001  Nevada  2.4  NaN
4  2002  Nevada  2.9  NaN
5  2003  Nevada  3.2  NaN

In [56]: frame2.columns
Out[56]: Index(['year', 'state', 'pop', 'debt'], dtype='object')

📑 Một cột còn có thể được lấy lên bằng 2 phương pháp:

  • dictionary-like notation (dùng ký hiệu truy vấn khóa)
  • dot attribute notation (chấm gọi thuộc tính)
In [57]: frame2["state"]
Out[57]: 
0      Ohio
1      Ohio
2      Ohio
3    Nevada
4    Nevada
5    Nevada
Name: state, dtype: object

In [58]: frame2.year
Out[58]: 
0    2000
1    2001
2    2002
3    2001
4    2002
5    2003
Name: year, dtype: int64

Trong ví dụ trên frame2[column] có thể thực hiện với bất cứ cột nào nhưng frame2.column sẽ báo lỗi nên tên cột không đúng theo quy tắc như có khoảng trắng (thay bằng _) hoặc ký hiệu đặc biệt, thậm chí nếu tên giống với các phương thức của DataFrame

Note 💡

Lưu ý: rằng kết quả trả về là Series có cùng chỉ mục giống DataFrame và thuộc tính name được tự động gán bằng tên cột.

📑 Bạn có thể truy xuất theo tên hoặc theo vị trí cột bằng hai thuộc tính đặc biệt lociloc

In [59]: frame2.loc[1]
Out[59]: 
year     2001
state    Ohio
pop       1.7
debt      NaN
Name: 1, dtype: object

In [60]: frame2.iloc[2]
Out[60]: 
year     2002
state    Ohio
pop       3.6
debt      NaN
Name: 2, dtype: object

📑 DataFrame có thể xem là mutuble object vì ta có thể sử đổi dữ liệu bằng cách gán trực tiếp giá trị

In [61]: frame2["debt"] = 16.5

In [62]: frame2
Out[62]: 
   year   state  pop  debt
0  2000    Ohio  1.5  16.5
1  2001    Ohio  1.7  16.5
2  2002    Ohio  3.6  16.5
3  2001  Nevada  2.4  16.5
4  2002  Nevada  2.9  16.5
5  2003  Nevada  3.2  16.5

In [63]: frame2["debt"] = np.arange(6.)

In [64]: frame2
Out[64]: 
   year   state  pop  debt
0  2000    Ohio  1.5   0.0
1  2001    Ohio  1.7   1.0
2  2002    Ohio  3.6   2.0
3  2001  Nevada  2.4   3.0
4  2002  Nevada  2.9   4.0
5  2003  Nevada  3.2   5.0

❗ Nếu gán bằng list hoặc array thì độ dài nhất định phải khớp với độ dài của DataFrame. Còn nếu gán bằng Series thì nhãn của nó tự động bắt với index của DataFrame, thậm chí ta còn cấu hình được thêm vào các vị trí xác định

In [65]: val = pd.Series([-1.2, -1.5, -1.7], index=[2, 4, 5])

In [66]: frame2["debt"] = val

In [67]: frame2
Out[67]: 
   year   state  pop  debt
0  2000    Ohio  1.5   NaN
1  2001    Ohio  1.7   NaN
2  2002    Ohio  3.6  -1.2
3  2001  Nevada  2.4   NaN
4  2002  Nevada  2.9  -1.5
5  2003  Nevada  3.2  -1.7

❗ Nếu gán vào một cột không tồn tại thì nó sẽ tự tạo ra một cột mới với giá trị và tên tương ứng

📑 Ta có thể dùng hàm del để xóa cột, khác chút là ta để nó giống như một keyword hơn là những cách gọi hàm thông thường

In [68]: frame2["eastern"] = frame2["state"] == "Ohio"

In [69]: frame2
Out[69]: 
   year   state  pop  debt  eastern
0  2000    Ohio  1.5   NaN     True
1  2001    Ohio  1.7   NaN     True
2  2002    Ohio  3.6  -1.2     True
3  2001  Nevada  2.4   NaN    False
4  2002  Nevada  2.9  -1.5    False
5  2003  Nevada  3.2  -1.7    False

In [70]: del frame2["eastern"]

In [71]: frame2.columns
Out[71]: Index(['year', 'state', 'pop', 'debt'], dtype='object')

Trong ví dụ trên ta tạo ra một cột Boolean mới tên “eastern” dựa trên logic cột “state” trả về True khi là “Ohio”.

  • Trong ví dụ trên không thể dùng cú pháp frame2.eastern để tạo cột mới như cách trên
  • Khi indexing (ví dụ bằng iloc hay loc) thì Series trả về là một view ánh xạ chính DataFrame đó chứ không phải bản copy nên việc sửa đổi sẽ thay đổi trực tiếp lên bản gốc
Note 💡

📑 Một dạng khác của data là nested dict của dict

In [72]: populations = {"Ohio": {2000: 1.5, 2001: 1.7, 2002: 3.6},
   ....:                "Nevada": {2001: 2.4, 2002: 2.9}}

Với trường hợp này thì pandas sẽ hiểu khóa của dict bên ngoài là cột còn khóa của dict bên trong là chỉ mục

In [73]: frame3 = pd.DataFrame(populations)

In [74]: frame3
Out[74]: 
      Ohio  Nevada
2000   1.5     NaN
2001   1.7     2.4
2002   3.6     2.9

📑 Bạn có thể chuyển vị DataFrame (hoán chuyển dòng thành cột) với cú pháp tương tự một mảng NumPy

In [75]: frame3.T
Out[75]: 
        2000  2001  2002
Ohio     1.5   1.7   3.6
Nevada   NaN   2.4   2.9

Lưu ý rằng việc chuyển vị sẽ loại bỏ các kiểu dữ liệu cột nếu các cột không có cùng kiểu dữ liệu, vì vậy việc chuyển vị và sau đó chuyển vị lại có thể làm mất thông tin kiểu trước đó.

Các cột trở thành mảng các đối tượng Python thuần túy trong trường hợp này. (Là đối tượng không được định nghĩa bới bất kỳ lớp nào bởi người dùng)

Các khóa 2 dict ở trên tự động kết hợp để tạo thành index chung của DF mặc dù 1-trong-2 không có đầy đủ keys. Nhưng nếu index được chỉ định rõ ràng thì khác:

In [76]: pd.DataFrame(populations, index=[2001, 2002, 2003])
Out[76]: 
      Ohio  Nevada
2001   1.7     2.4
2002   3.6     2.9
2003   NaN     NaN

Tương tự với dict của Series:

In [77]: pdata = {"Ohio": frame3["Ohio"][:-1],
   ....:          "Nevada": frame3["Nevada"][:2]}

In [78]: pd.DataFrame(pdata)
Out[78]: 
      Ohio  Nevada
2000   1.5     NaN
2001   1.7     2.4
Possible data inputs to the DataFrame constructor

📑 Nếu indexcolumns (vốn là 2 Series) của DataFrame có khai báo thuộc tính name thì nó cũng sẽ được hiển thị:

In [79]: frame3.index.name = "year"

In [80]: frame3.columns.name = "state"

In [81]: frame3
Out[81]: 
state  Ohio  Nevada
year               
2000    1.5     NaN
2001    1.7     2.4
2002    3.6     2.9

Không giống Series, DataFrame không có thuộc tính name. Khi ta dùng hàm to_numpy sẽ trả về dữ liệu của nó nhưng được đặt trong ndarray 2-chiều:

In [82]: frame3.to_numpy()
Out[82]: 
array([[1.5, nan],
       [1.7, 2.4],
       [3.6, 2.9]])

Nếu các cột của DataFrame có các kiểu dữ liệu khác nhau, mảng 2-chiều trả về sẽ được chọn để chứa đủ thêm tên các cột

In [83]: frame2.to_numpy()
Out[83]: 
array([[2000, 'Ohio', 1.5, nan],
       [2001, 'Ohio', 1.7, nan],
       [2002, 'Ohio', 3.6, -1.2],
       [2001, 'Nevada', 2.4, nan],
       [2002, 'Nevada', 2.9, -1.5],
       [2003, 'Nevada', 3.2, -1.7]], dtype=object)

🚀Index objects

“Các đối tượng chỉ mục” của pandas chịu trách nhiệm giữ các nhãn trục tọa độ (gồm các tên cột của DataFrame) và các metadata khác (như trên trục hoặc thuộc tính names).

Bất kỳ mảng hoặc labels sequence nào cũng có thể dùng để làm Index cho Series hoặc DataFrame thông qua tham số index.

In [84]: obj = pd.Series(np.arange(3), index=["a", "b", "c"])

In [85]: index = obj.index

In [86]: index
Out[86]: Index(['a', 'b', 'c'], dtype='object')

In [87]: index[1:]
Out[87]: Index(['b', 'c'], dtype='object')

Index object là một immutable và không thể sửa đổi:

index[1] = "d"  # TypeError

Tính chất immutability đảm bảo tính toàn vẹn khi ta dùng chung giữa các cấu trúc dữ liệu với nhau:

In [88]: labels = pd.Index(np.arange(3))

In [89]: labels
Out[89]: Index([0, 1, 2], dtype='int64')

In [90]: obj2 = pd.Series([1.5, -2.5, 0], index=labels)

In [91]: obj2
Out[91]: 
0    1.5
1   -2.5
2    0.0
dtype: float64

In [92]: obj2.index is labels
Out[92]: True

Bổ sung thêm là khi đối tượng ở dạng mảng, đối tượng Index có các hành vi của một set cố định kích thước

In [93]: frame3
Out[93]: 
state  Ohio  Nevada
year               
2000    1.5     NaN
2001    1.7     2.4
2002    3.6     2.9

In [94]: frame3.columns
Out[94]: Index(['Ohio', 'Nevada'], dtype='object', name='state')

In [95]: "Ohio" in frame3.columns
Out[95]: True

In [96]: 2003 in frame3.index
Out[96]: False

Nhưng không giống sets của Python, pandas Index có thể chứa các nhãn trùng lặp:

In [97]: pd.Index(["foo", "foo", "bar", "bar"])
Out[97]: Index(['foo', 'foo', 'bar', 'bar'], dtype='object')

Mỗi đối tượng index có một số phương thức và thuộc tính logic để trả lời các câu hỏi liên quan tới dữ liệu nó chứa ví dụ is_unique , is_monotonic

Some Index methods and properties

🍀Các chức năng cơ bản


Phần này sẽ hướng dẫn bạn cách tương tác cơ bản với dữ liệu trong Series hoặc DataFrame. Trong các phần sau, chúng ta sẽ đi sâu hơn vào các chủ đề phân tích và thao tác dữ liệu bằng pandas.

🚀 Reindexing

📑 Đúng như cái tên ta có hàm reindex để tạo một object mới với các giá trị được tái sắp xếp bằng bộ index mới

In [98]: obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=["d", "b", "a", "c"])

In [99]: obj
Out[99]: 
d    4.5
b    7.2
a   -5.3
c    3.6
dtype: float64

In [100]: obj2 = obj.reindex(["a", "b", "c", "d", "e"])

In [101]: obj2
Out[101]: 
a   -5.3
b    7.2
c    3.6
d    4.5
e    NaN
dtype: float64

reindex Series ở trên sẽ trả ra NaN nếu có bất kỳ giá trị index nào không tồn tại

📑 Đối với các dữ liệu có đặc tính trình tự như time series, bạn có thể muốn thực hiện một số phép nội suy (“interpolation”) tính toán hoặc lắp các giá trị bị thiếu khi reindexing.

Ta có thể dùng tham số method trong reindex để sử dụng nhưng cách khác nhau để điền giá trị, ví dụ sau đây dùng ffill để điền giá trị liền trước (“forawrd-fills”)

In [102]: obj3 = pd.Series(["blue", "purple", "yellow"], index=[0, 2, 4])

In [103]: obj3
Out[103]: 
0      blue
2    purple
4    yellow
dtype: object

In [104]: obj3.reindex(np.arange(6), method="ffill")
Out[104]: 
0      blue
1      blue
2    purple
3    purple
4    yellow
5    yellow
dtype: object

Trong DataFrame, reindex có thể thay đổi (row) index, columns hoặc cả hai. Khi truyền vào một sequence, nó reindex các dòng tương ứng

In [105]: frame = pd.DataFrame(np.arange(9).reshape((3, 3)),
   .....:                      index=["a", "c", "d"],
   .....:                      columns=["Ohio", "Texas", "California"])

In [106]: frame
Out[106]: 
   Ohio  Texas  California
a     0      1           2
c     3      4           5
d     6      7           8

In [107]: frame2 = frame.reindex(index=["a", "b", "c", "d"])

In [108]: frame2
Out[108]: 
   Ohio  Texas  California
a   0.0    1.0         2.0
b   NaN    NaN         NaN
c   3.0    4.0         5.0
d   6.0    7.0         8.0

Khi muốn reindex các cột thì ta dùng keyword columns

In [109]: states = ["Texas", "Utah", "California"]

In [110]: frame.reindex(columns=states)
Out[110]: 
   Texas  Utah  California
a      1   NaN           2
c      4   NaN           5
d      7   NaN           8

Kết quả trên có thể thấy do “Ohio” không có trong danh sách states nên cột đó trả về rỗng

Cũng trong ví dụ trên, một cách khác để reindex một trục nhất định (rows hoặc columns) ta có thể dùng tham số axis

In [111]: frame.reindex(states, axis="columns")
Out[111]: 
   Texas  Utah  California
a      1   NaN           2
c      4   NaN           5
d      7   NaN           8
reindex function arguments

Ở chức năng về Selection ta sẽ được biết hàm loc cũng có khả năng reindex nhưng nó chỉ chạy được khi các index labels có tồn tại trong DF (khác với reindex sẽ thêm các giá trị rỗng vào.

In [112]: frame.loc[["a", "d", "c"], ["California", "Texas"]]
Out[112]: 
   California  Texas
a           2      1
d           8      7
c           5      4

🚀 Dropping Entries from an Axis

việc bỏ một hoặc nhiều mục mặc dù có thể làm bằng hai hàm reindex hoặc .loc nhưng ta cũng có thể dùng hàm drop để chỉ đích dành dòng muốn bỏ

In [113]: obj = pd.Series(np.arange(5.), index=["a", "b", "c", "d", "e"])

In [114]: obj
Out[114]: 
a    0.0
b    1.0
c    2.0
d    3.0
e    4.0
dtype: float64

In [115]: new_obj = obj.drop("c")

In [116]: new_obj
Out[116]: 
a    0.0
b    1.0
d    3.0
e    4.0
dtype: float64

In [117]: obj.drop(["d", "c"])
Out[117]: 
a    0.0
b    1.0
e    4.0
dtype: float64

Tất nhiên ta cũng có thể bỏ cả dòng và cột, xem ví dụ sau:

# Create new DataFrame
In [118]: data = pd.DataFrame(np.arange(16).reshape((4, 4)),
   .....:                     index=["Ohio", "Colorado", "Utah", "New York"],
   .....:                     columns=["one", "two", "three", "four"])

In [119]: data
Out[119]: 
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15


# Drop rows
In [120]: data.drop(index=["Colorado", "Ohio"])
Out[120]: 
          one  two  three  four
Utah        8    9     10    11
New York   12   13     14    15

# Drop columns
In [121]: data.drop(columns=["two"])
Out[121]: 
          one  three  four
Ohio        0      2     3
Colorado    4      6     7
Utah        8     10    11
New York   12     14    15

Còn một cách khác nữa là dùng tham số axis

In [122]: data.drop("two", axis=1) # like NumPy
Out[122]: 
          one  three  four
Ohio        0      2     3
Colorado    4      6     7
Utah        8     10    11
New York   12     14    15

In [123]: data.drop(["two", "four"], axis="columns")
Out[123]: 
          one  three
Ohio        0      2
Colorado    4      6
Utah        8     10
New York   12     14

🚀Indexing

Cú pháp Indexing của Series (obj[...]) hoạt động tương tự NumPy chỉ khác là ta vừa có thể dùng dạng index labels hoặc index integers.

In [124]: obj = pd.Series(np.arange(4.), index=["a", "b", "c", "d"])

In [125]: obj
Out[125]: 
a    0.0
b    1.0
c    2.0
d    3.0
dtype: float64

In [126]: obj["b"]
Out[126]: 1.0

In [127]: obj[1]
Out[127]: 1.0

In [128]: obj[2:4]
Out[128]: 
c    2.0
d    3.0
dtype: float64

In [129]: obj[["b", "a", "d"]]
Out[129]: 
b    1.0
a    0.0
d    3.0
dtype: float64

In [130]: obj[[1, 3]]
Out[130]: 
b    1.0
d    3.0
dtype: float64

In [131]: obj[obj < 2]
Out[131]: 
a    0.0
b    1.0
dtype: float64

🔥.loc hoặc iloc

Thường thì nếu dùng index labels ta ưu tiên dùng hàm loc hơn và tương tự nếu dùng dạng index integers thì ta dùng hàm iloc

In [132]: obj.loc[["b", "a", "d"]]
Out[132]: 
b    1.0
a    0.0
d    3.0
dtype: float64


In [139]: obj1.iloc[[0, 1, 2]]
Out[139]: 
2    1
0    2
1    3
dtype: int64

In [140]: obj2.iloc[[0, 1, 2]]
Out[140]: 
a    1
b    2
c    3
dtype: int64

Nếu dùng lộn thì ta sẽ gặp lỗi này:

In [134]: obj2.loc[[0, 1]]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
/tmp/ipykernel_804589/4185657903.py in <module>
----> 1 obj2.loc[[0, 1]]

^ LONG EXCEPTION ABBREVIATED ^

KeyError: "None of [Int64Index([0, 1], dtype="int64")] are in the [index]"

💡 Ta có thể slice bằng labels nhưng khác với việc slicing thuần túy trong Python kết quả sẽ bao gồm luôn endpoint

In [141]: obj2.loc["b":"c"]
Out[141]: 
b    2
c    3
dtype: int64

Như đã đề cập thì việc sửa đổi sẽ ánh xạ lên phần đó của Series

In [142]: obj2.loc["b":"c"] = 5

In [143]: obj2
Out[143]: 
a    1
b    5
c    5
dtype: int64

Các bạn mới có thể sẽ hay nhầm lần việc gọi iloc hay loc giống như cách gọi hàm thay vì dùng ký hiệu ngoặc vuông. Bản chất việc này giống như cách ta slicing với việc ta có thể indexing into nhiều cột/dòng trong DataFrame.

Note

🔥Single value or Sequence values

In [144]: data = pd.DataFrame(np.arange(16).reshape((4, 4)),
   .....:                     index=["Ohio", "Colorado", "Utah", "New York"],
   .....:                     columns=["one", "two", "three", "four"])

In [145]: data
Out[145]: 
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

In [146]: data["two"]
Out[146]: 
Ohio         1
Colorado     5
Utah         9
New York    13
Name: two, dtype: int64

In [147]: data[["three", "one"]]
Out[147]: 
          three  one
Ohio          2    0
Colorado      6    4
Utah         10    8
New York     14   12

🔥Boolean array

Ta cũng có thể slicing hoặc selecting với mảng Boolean

In [148]: data[:2]
Out[148]: 
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7

In [149]: data[data["three"] > 5]
Out[149]: 
          one  two  three  four
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

Khi ta làm phép so sánh DataFrame với giá trị đại số ta cũng tạo ra một Boolean DataFrame mới:

In [150]: data < 5
Out[150]: 
            one    two  three   four
Ohio       True   True   True   True
Colorado   True  False  False  False
Utah      False  False  False  False
New York  False  False  False  False

Tận dụng cách trên ta có thể gán 0 cho các giá trị True

In [151]: data[data < 5] = 0

In [152]: data
Out[152]: 
          one  two  three  four
Ohio        0    0      0     0
Colorado    0    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

🚀Selection

Phổ biến nhất ta có thể dùng iloc hoặc loc để lấy dữ liệu mong muốn. Hãy nhớ bản chất DataFrame là cấu trúc 2 chiều

🔥Dùng .loc

In [153]: data
Out[153]: 
          one  two  three  four
Ohio        0    0      0     0
Colorado    0    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

In [154]: data.loc["Colorado"]
Out[154]: 
one      0
two      5
three    6
four     7
Name: Colorado, dtype: int64

Để chọn nhiều dòng ta truyền vào 1-sequence

In [155]: data.loc[["Colorado", "New York"]]
Out[155]: 
          one  two  three  four
Colorado    0    5      6     7
New York   12   13     14    15

Thú vị là ta có thể kết hợp cả hai chỉ bằng dấu ,

In [156]: data.loc["Colorado", ["two", "three"]]
Out[156]: 
two      5
three    6
Name: Colorado, dtype: int64

🔥Dùng .iloc

Ta có thể làm việc tương tự với iloc

In [157]: data.iloc[2]
Out[157]: 
one       8
two       9
three    10
four     11
Name: Utah, dtype: int64

In [158]: data.iloc[[2, 1]]
Out[158]: 
          one  two  three  four
Utah        8    9     10    11
Colorado    0    5      6     7

In [159]: data.iloc[2, [3, 0, 1]]
Out[159]: 
four    11
one      8
two      9
Name: Utah, dtype: int64

In [160]: data.iloc[[1, 2], [3, 0, 1]]
Out[160]: 
          four  one  two
Colorado     7    0    5
Utah        11    8    9

🔥Dùng slices

Cả hai hàm indexing để có cơ chế slicing

In [161]: data.loc[:"Utah", "two"]
Out[161]: 
Ohio        0
Colorado    5
Utah        9
Name: two, dtype: int64

In [162]: data.iloc[:, :3][data.three > 5]
Out[162]: 
          one  two  three
Colorado    0    5      6
Utah        8    9     10
New York   12   13     14

🔥Boolean array

Lưu ý là Boolean arrays chỉ dùng cho loc

In [163]: data.loc[data.three >= 2]
Out[163]: 
          one  two  three  four
Colorado    0    5      6     7
Utah        8    9     10    11
New York   12   13     14    15
 Indexing options with DataFrame

🚀Lỗi interger indexing

Một vài lỗi thông dụng vì đôi khi ta hay nhầm lẫn với các cấu trúc dữ liệu khác như list hay tuple

In [164]: ser = pd.Series(np.arange(3.))

In [165]: ser
Out[165]: 
0    0.0
1    1.0
2    2.0
dtype: float64

In [166]: ser[-1]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
~/miniforge-x86/envs/book-env/lib/python3.10/site-packages/pandas/core/indexes/ra
nge.py in get_loc(self, key)
    344             try:
--> 345                 return self._range.index(new_key)
    346             except ValueError as err:
ValueError: -1 is not in range
The above exception was the direct cause of the following exception:
KeyError                                  Traceback (most recent call last)
<ipython-input-166-44969a759c20> in <module>
----> 1 ser[-1]
~/miniforge-x86/envs/book-env/lib/python3.10/site-packages/pandas/core/series.py 
in __getitem__(self, key)
   1010 
   1011         elif key_is_scalar:
-> 1012             return self._get_value(key)
   1013 
   1014         if is_hashable(key):
~/miniforge-x86/envs/book-env/lib/python3.10/site-packages/pandas/core/series.py 
in _get_value(self, label, takeable)
   1119 
   1120         # Similar to Index.get_value, but we do not fall back to position
al
-> 1121         loc = self.index.get_loc(label)
   1122 
   1123         if is_integer(loc):
~/miniforge-x86/envs/book-env/lib/python3.10/site-packages/pandas/core/indexes/ra
nge.py in get_loc(self, key)
    345                 return self._range.index(new_key)
    346             except ValueError as err:
--> 347                 raise KeyError(key) from err
    348         self._check_indexing_error(key)
    349         raise KeyError(key)
KeyError: -1

Mặc dù pandas có thể “fall back” khi indexing integer nhưng chỉ đúng khi bạn dùng label indexing

In [168]: ser2 = pd.Series(np.arange(3.), index=["a", "b", "c"])

In [169]: ser2[-1]
Out[169]: 2.0

Nhưng nếu bạn dùng iloc hay loc thì sẽ khác

In [170]: ser.iloc[-1]
Out[170]: 2.0

Ngoài ra thì khi slicing bằng interger thì có tính chất interger oriented:

In [171]: ser[:2]
Out[171]: 
0    0.0
1    1.0
dtype: float64

🚀Lỗi với chain indexing

Ngoài việc dùng lociloc cho việc selection thì ta còn có thể dùng để modify nhưng cần lưu ý cẩn thận

In [172]: data.loc[:, "one"] = 1

In [173]: data
Out[173]: 
          one  two  three  four
Ohio        1    0      0     0
Colorado    1    5      6     7
Utah        1    9     10    11
New York    1   13     14    15

In [174]: data.iloc[2] = 5

In [175]: data
Out[175]: 
          one  two  three  four
Ohio        1    0      0     0
Colorado    1    5      6     7
Utah        5    5      5     5
New York    1   13     14    15

In [176]: data.loc[data["four"] > 5] = 3

In [177]: data
Out[177]: 
          one  two  three  four
Ohio        1    0      0     0
Colorado    3    3      3     3
Utah        5    5      5     5
New York    3    3      3     3

Một sai lầm phổ biến đối với người dùng pandas mới là liên kết selections khi gán, như thế này:

In [177]: data.loc[data.three == 5]["three"] = 6

<ipython-input-11-0ed1cf2155d5>:1: SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

Cú pháp trên khá là quen khi học NumPy nhỉ nhưng ở đây không áp dụng được

Tùy thuộc vào nội dung dữ liệu, điều này có thể in ra một cảnh báo đặc biệt SettingWithCopyWarning, cảnh báo bạn rằng bạn đang cố gắng sửa đổi một giá trị tạm thời (kết quả nonempty của data.loc[data.three == 5]) thay vì data ban đầu. Như vậy, DataFrame data không được sửa đổi:

In [179]: data
Out[179]: 
          one  two  three  four
Ohio        1    0      0     0
Colorado    3    3      3     3
Utah        5    5      5     5
New York    3    3      3     3

Còn nếu muốn sửa thì ta cần viết như sau:

In [180]: data.loc[data.three == 5, "three"] = 6

In [181]: data
Out[181]: 
          one  two  three  four
Ohio        1    0      0     0
Colorado    3    3      3     3
Utah        5    5      6     5
New York    3    3      3     3

🍀Kết luận

Bài hôm nay chúng ta cũng hình dung được DataFrame như thế nào và làm việc với các chức năng cơ bản nhất là Indexing và Selection. Ở bài sau ta sẽ tìm hiểu thêm về các chức năng tính toán, viết hàm ufuncs, sắp xếp và xếp hạng trong DF. Sau đó thử áp dụng một vài hàm thống kê cơ bản đảm bảo sẽ thú vị hơn

Các kênh cộng đồng của mình

  • Facebook: https://www.facebook.com/bocuoilamdata
  • Youtube: https://www.youtube.com/@CarrY4U_VN
  • Website: https://carry4u.org/

© Bản quyền thuộc về CarrY4U và Bò Cười Làm Data
© Copyright by CarrY4U and bocuoilamdata ☞ Do not reup