Vốn dĩ thì phần này nên là phần nối tiếp của buổi 12 nhưng do phần ở sau liên quan tới các hàm mở rộng mặc dù nhìn thì đơn giản nhưng chính nhờ được hỗ trợ tận răng nên chúng ta bớt rất nhiều việc

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

Trong buổi trước chúng ta tìm hiểu về các kiến thức cơ bản về Pandas: Từ các 1Cấu trúc dữ liệu cơ bản trong pandas – đến các 2Chức năng cơ bản như Reindexing, Indexing, Drop axis, Selection và tìm hiểu một số 3Lỗi về indexing.

📙 Bài viết chi tiết Day 12:

Recap day 12

🔴 Buổi live Day 12

Livestream day 12

Trong ngày 13 này chúng ta cùng đi nốt các nội dung còn lại của thư viện pandas. Hy vọng mọi người sẽ vẫn tiếp tục ủng hộ và theo dõi mình hoàn thành tới ngày cuối của thử thách 30 ngày chinh phục Python for Data Analyst nhé.

🍀Các hàm mở rộng

🚀Arithemetic – Data alignment

🔥 Tính toán kèm Căn chỉnh dữ liệu

Pandas có thể giúp việc xử lý các đối tượng có index khác nhau trở nên đơn giản hơn rất nhiều. Ví dụ, khi bạn cộng các đối tượng, nếu có bất kỳ cặp index nào không giống nhau thì kết quả sẽ trả về NaN, việc này sẽ tốt hơn vì ta biết được rõ vị trí bị khuyết giá trị:

In [182]: s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=["a", "c", "d", "e"])

In [183]: s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1],
   .....:                index=["a", "c", "e", "f", "g"])

In [184]: s1
Out[184]: 
a    7.3
c   -2.5
d    3.4
e    1.5
dtype: float64

In [185]: s2
Out[185]: 
a   -2.1
c    3.6
e   -1.5
f    4.0
g    3.1
dtype: float64

In [186]: s1 + s2
Out[186]: 
a    5.2
c    1.1
d    NaN
e    0.0
f    NaN
g    NaN
dtype: float64

Nếu nghĩ theo cách tương đồng với SQL thì nó không như hàm JOIN

Note 💡

Căn chỉnh dữ liệu ("Data alignment") có thể được thực hiện với cả dòng và cột – dù ta không để ý tới nó, xem ví dụ sau:

In [187]: df1 = pd.DataFrame(np.arange(9.).reshape((3, 3)), columns=list("bcd")
   .....:                    index=["Ohio", "Texas", "Colorado"])

In [188]: df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)), columns=list("bde"),
   .....:                    index=["Utah", "Ohio", "Texas", "Oregon"])

In [189]: df1
Out[189]: 
            b    c    d
Ohio      0.0  1.0  2.0
Texas     3.0  4.0  5.0
Colorado  6.0  7.0  8.0

In [190]: df2
Out[190]: 
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

In [191]: df1 + df2
Out[191]: 
            b   c     d   e
Colorado  NaN NaN   NaN NaN
Ohio      3.0 NaN   6.0 NaN
Oregon    NaN NaN   NaN NaN
Texas     9.0 NaN  12.0 NaN
Utah      NaN NaN   NaN NaN

❗ Nếu bạn thêm các đối tượng DataFrame không có “nhãn” cột hoặc hàng chung, kết quả sẽ chứa tất cả các giá trị null:

In [192]: df1 = pd.DataFrame({"A": [1, 2]})

In [193]: df2 = pd.DataFrame({"B": [3, 4]})

In [194]: df1
Out[194]: 
   A
0  1
1  2

In [195]: df2
Out[195]: 
   B
0  3
1  4

In [196]: df1 + df2
Out[196]: 
    A   B
0 NaN NaN
1 NaN NaN

🔥 Tham số “fill_values”

Nếu bạn muốn gán một giá trị đặc biệt thay vì NaN trong trường hợp trên thì ta có thể làm như sau bằng hàm add() với tham số "fill_value":

In [197]: df1 = pd.DataFrame(np.arange(12.).reshape((3, 4)),
   .....:                    columns=list("abcd"))

In [198]: df2 = pd.DataFrame(np.arange(20.).reshape((4, 5)),
   .....:                    columns=list("abcde"))

In [199]: df2.loc[1, "b"] = np.nan

In [200]: df1
Out[200]: 
     a    b     c     d
0  0.0  1.0   2.0   3.0
1  4.0  5.0   6.0   7.0
2  8.0  9.0  10.0  11.0

In [201]: df2
Out[201]: 
      a     b     c     d     e
0   0.0   1.0   2.0   3.0   4.0
1   5.0   NaN   7.0   8.0   9.0
2  10.0  11.0  12.0  13.0  14.0
3  15.0  16.0  17.0  18.0  19.0

-- Trong TH binh thuong
In [202]: df1 + df2
Out[202]: 
      a     b     c     d   e
0   0.0   2.0   4.0   6.0 NaN
1   9.0   NaN  13.0  15.0 NaN
2  18.0  20.0  22.0  24.0 NaN
3   NaN   NaN   NaN   NaN NaN

-- Khi dung fill_value
In [203]: df1.add(df2, fill_value=0)
Out[203]: 
      a     b     c     d     e
0   0.0   2.0   4.0   6.0   4.0
1   9.0   5.0  13.0  15.0   9.0
2  18.0  20.0  22.0  24.0  14.0
3  15.0  16.0  17.0  18.0  19.0

Chú ý cách sử dụng np.nan để tạo giá trị null nhé

Note 💡

Bảng dưới đây liệt kê các phương thức toán học cho Series và DataFrame trong thư viện pandas. Mỗi phương thức có kèm theo một “counterpart” – Đại loại là một phương thức nghịch đảo giá trị bắt đầu bằng chữ r.

Flexible arithmetic methods

Ví dụ dưới đây cho bạn thấy hai câu lệnh sau là tương đồng:

In [204]: 1 / df1
Out[204]: 
       a         b         c         d
0    inf  1.000000  0.500000  0.333333
1  0.250  0.200000  0.166667  0.142857
2  0.125  0.111111  0.100000  0.090909

In [205]: df1.rdiv(1)
Out[205]: 
       a         b         c         d
0    inf  1.000000  0.500000  0.333333
1  0.250  0.200000  0.166667  0.142857
2  0.125  0.111111  0.100000  0.090909

Tất nhiên ta có thể dùng kèm tham số fill_value

In [206]: df1.reindex(columns=df2.columns, fill_value=0)
Out[206]: 
     a    b     c     d  e
0  0.0  1.0   2.0   3.0  0
1  4.0  5.0   6.0   7.0  0
2  8.0  9.0  10.0  11.0  0

Các phép tính giữa DataFrame và Series

Tương tự như các mảng NumPy có kích thước khác nhau, phép toán số học giữa DataFrame và Series cũng được đặc tả trước. Cùng xem ví dụ sau giữa mảng 2-chiều và mảng 1-chiều (lấy từ chính nó):

In [207]: arr = np.arange(12.).reshape((3, 4))

In [208]: arr
Out[208]: 
array([[ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.],
       [ 8.,  9., 10., 11.]])

In [209]: arr[0]
Out[209]: array([0., 1., 2., 3.])

In [210]: arr - arr[0]
Out[210]: 
array([[0., 0., 0., 0.],
       [4., 4., 4., 4.],
       [8., 8., 8., 8.]])

Khi chúng ta trừ arr[0] khỏi arr, phép trừ được thực hiện mỗi lần cho mỗi hàng. Đây là cơ chế broadcasting của NumPy mà mình từng đề cập.

Ví dụ sau đây, ta tạo ra DataFrame tên frame và Series từ chính nó bằng hàm iloc sẽ lấy các giá trị từ label “Utah” và sau đó thực hiện phép trừ giống như trên:

In [211]: frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),
   .....:                      columns=list("bde"),
   .....:                      index=["Utah", "Ohio", "Texas", "Oregon"])

In [212]: series = frame.iloc[0]

In [213]: frame
Out[213]: 
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

In [214]: series
Out[214]: 
b    0.0
d    1.0
e    2.0
Name: Utah, dtype: float64

In [215]: frame - series
Out[215]: 
          b    d    e
Utah    0.0  0.0  0.0
Ohio    3.0  3.0  3.0
Texas   6.0  6.0  6.0
Oregon  9.0  9.0  9.0

Tuy nhiên nếu kết hợp với Series có index không tồn tại tương ứng với cột của DataFrame thì sẽ trả về NaN

In [216]: series2 = pd.Series(np.arange(3), index=["b", "e", "f"])

In [217]: series2
Out[217]: 
b    0
e    1
f    2
dtype: int64

In [218]: frame + series2
Out[218]: 
          b   d     e   f
Utah    0.0 NaN   3.0 NaN
Ohio    3.0 NaN   6.0 NaN
Texas   6.0 NaN   9.0 NaN
Oregon  9.0 NaN  12.0 NaN

Hãy để ý khi khởi tạo index của Series và DataFrame thì vị trí của nó khi in ra màn hình hơi ngược nhau nhưng ý nghĩa vẫn là các chỉ mục.

Note 💡

❗ Trong trường hợp muốn broadcast theo cột thì ta cần bổ sung thêm tham số axis vào các hàm tính:

In [219]: series3 = frame["d"]

In [220]: frame
Out[220]: 
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

In [221]: series3
Out[221]: 
Utah       1.0
Ohio       4.0
Texas      7.0
Oregon    10.0
Name: d, dtype: float64

In [222]: frame.sub(series3, axis="index")
Out[222]: 
          b    d    e
Utah   -1.0  0.0  1.0
Ohio   -1.0  0.0  1.0
Texas  -1.0  0.0  1.0
Oregon -1.0  0.0  1.0

Tham số axis: {0 or ‘index’, 1 or ‘columns’} là trục để so sánh. Trong trường hợp ở trên sẽ broadcast dọc các cột – hơi ngược so với tên tham số truyền vào.

Note 💡

🚀 Áp hàm và Ánh xạ

Áp hàm ý chỉ các hàm .apply() hay .applymap(); Ánh xạ chỉ hàm .map()

Các bạn còn nhớ NumPy ufuncs (các phương thức mảng xử lý trên từng phần tử) chứ? Tụi nó vẫn hoạt động tốt với các đối tượng pandas:

In [223]: frame = pd.DataFrame(np.random.standard_normal((4, 3)),
   .....:                      columns=list("bde"),
   .....:                      index=["Utah", "Ohio", "Texas", "Oregon"])

In [224]: frame
Out[224]: 
               b         d         e
Utah   -0.204708  0.478943 -0.519439
Ohio   -0.555730  1.965781  1.393406
Texas   0.092908  0.281746  0.769023
Oregon  1.246435  1.007189 -1.296221

In [225]: np.abs(frame)
Out[225]: 
               b         d         e
Utah    0.204708  0.478943  0.519439
Ohio    0.555730  1.965781  1.393406
Texas   0.092908  0.281746  0.769023
Oregon  1.246435  1.007189  1.296221

Một phép tính cũng thường áp dụng một hàm cho các mảng một chiều trên mỗi cột hoặc hàng. Phương thức apply của DataFrame chính xác thực hiện điều này:

In [226]: def f1(x):
   .....:     return x.max() - x.min()

In [227]: frame.apply(f1)
Out[227]: 
b    1.802165
d    1.684034
e    2.689627
dtype: float64

Ở đây, hàm f, tính hiệu giữa giá trị lớn nhất và nhỏ nhất của mỗi Series, được gọi một lần cho mỗi cột trong frame. Kết quả là một Series có các cột của frame làm chỉ mục.

Tất nhiên cũng giống như trên thay vì xét trên cột (default) nếu muốn xét trên dòng thì ta làm như sau – Có thể hiểu đoạn dưới là: “apply corss the columns”:

In [228]: frame.apply(f1, axis="columns")
Out[228]: 
Utah      0.998382
Ohio      2.521511
Texas     0.676115
Oregon    2.542656
dtype: float64

❗ Trong thực tế thì ta cũng không cần phải viết các hàm apply cho các phép toán thống kê phổ biến như sum hay mean vì nó đã có sẵn

Gía trị trả về của apply không nhất thiết là giá trị vô hướng (“scalar value”) mà có thể trả về Series với nhiều giá trị:

In [229]: def f2(x):
   .....:     return pd.Series([x.min(), x.max()], index=["min", "max"])

In [230]: frame.apply(f2)
Out[230]: 
            b         d         e
min -0.555730  0.281746 -1.296221
max  1.246435  1.965781  1.393406

Ví dụ tiếp sau đây sẽ làm tròn số dự trên vị trí dấu-chấm-động (floating -point) bằng phương thức applymap

In [231]: def my_format(x):
   .....:     return f"{x:.2f}"

In [232]: frame.applymap(my_format)
Out[232]: 
            b     d      e
Utah    -0.20  0.48  -0.52
Ohio    -0.56  1.97   1.39
Texas    0.09  0.28   0.77
Oregon   1.25  1.01  -1.30

Lý do ta dùng phương thức này vì ta đang thực hiện một hàm element-wise functions và bản thân Series có phương thức map nên mới là applymap – ví dụ hàm map trong Series:

In [233]: frame["e"].map(my_format)
Out[233]: 
Utah      -0.52
Ohio       1.39
Texas      0.77
Oregon    -1.30
Name: e, dtype: object

🚀 Sorting và Ranking

🔥 Sorting

Sắp xếp một bộ dữ liệu theo một tiêu chí nào đó là một chức năng tích hợp quan trọng. Để sắp xếp theo thứ tự (lexicographically) dựa trên nhãn của dòng hoặc cột, ta sử dụng phương thức sort_index, nó sẽ trả về một đối tượng mới đã được sắp xếp:

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

In [235]: obj
Out[235]: 
d    0
a    1
b    2
c    3
dtype: int64

In [236]: obj.sort_index()
Out[236]: 
a    1
b    2
c    3
d    0
dtype: int64

Khi dùng DataFrame, bạn có thể sắp xếp dựa trên chỉ mục hoặc trục (“axis”)

In [237]: frame = pd.DataFrame(np.arange(8).reshape((2, 4)),
   .....:                      index=["three", "one"],
   .....:                      columns=["d", "a", "b", "c"])

In [238]: frame
Out[238]: 
       d  a  b  c
three  0  1  2  3
one    4  5  6  7

In [239]: frame.sort_index()
Out[239]: 
       d  a  b  c
one    4  5  6  7
three  0  1  2  3

In [240]: frame.sort_index(axis="columns")
Out[240]: 
       a  b  c  d
three  1  2  3  0
one    5  6  7  4

Mặc định sẽ là tăng dần (ascending), bạn cũng có thể thay đổi chiều sắp xếp:

In [241]: frame.sort_index(axis="columns", ascending=False)
Out[241]: 
       d  c  b  a
three  0  3  2  1
one    4  7  6  5

Riêng với Series thì ta dùng phương thức sort_values:

In [242]: obj = pd.Series([4, 7, -3, 2])

In [243]: obj.sort_values()
Out[243]: 
2   -3
3    2
0    4
1    7
dtype: int64

Bất kỳ giá trị rỗng nào sẽ được dồn về cuối khi sắp xếp:

In [244]: obj = pd.Series([4, np.nan, 7, np.nan, -3, 2])

In [245]: obj.sort_values()
Out[245]: 
4   -3.0
5    2.0
0    4.0
2    7.0
1    NaN
3    NaN
dtype: float64

Còn nếu bạn muốn để tụi nó lên đầu thì dùng na_position:

In [246]: obj.sort_values(na_position="first")
Out[246]: 
1    NaN
3    NaN
4   -3.0
5    2.0
0    4.0
2    7.0
dtype: float64

❗ Đối với DataFrame ta còn có thể chỉ định sắp xếp theo một cột cụ thể bằng phương thức sort_values():

In [247]: frame = pd.DataFrame({"b": [4, 7, -3, 2], "a": [0, 1, 0, 1]})

In [248]: frame
Out[248]: 
   b  a
0  4  0
1  7  1
2 -3  0
3  2  1

In [249]: frame.sort_values("b")
Out[249]: 
   b  a
2 -3  0
3  2  1
0  4  0
1  7  1

Còn khi ta muốn sắp xếp từ 2 cọt trở lên thì chỉ cần truyền vào danh sách:

In [250]: frame.sort_values(["a", "b"])
Out[250]: 
   b  a
2 -3  0
0  4  0
3  2  1
1  7  1

🔥 Ranking

Hàm rank() sẽ trả về thứ hạng của các phần tử trong từng Series hoặc từng dòng/cột trong DataFrame. Nếu giá trị phần tử giống nhau thì mặc định nó sẽ lấy thứ hạng trung bình của 2 phần tử đó.

In [251]: obj = pd.Series([7, -5, 7, 4, 2, 0, 4])

In [252]: obj.rank()
Out[252]: 
0    6.5
1    1.0
2    6.5
3    4.5
4    3.0
5    2.0
6    4.5
dtype: float64

Trong ví dụ trên thì có 2 số 7 nên nó sẽ không biết xếp vào thứ hạng 6 và 7 tương ứng cho số 7 nào vậy nên nó sẽ lấy hạng trung bình là 6.5 và gán cho cả hai.

Tất nhiên ta có thể dùng cách khác là “ai đến trước lấy trước”:

In [253]: obj.rank(method="first")
Out[253]: 
0    6.0
1    1.0
2    7.0
3    4.0
4    3.0
5    2.0
6    5.0
dtype: float64

Còn để xếp ngược lại:

In [254]: obj.rank(ascending=False)
Out[254]: 
0    1.5
1    7.0
2    1.5
3    3.5
4    5.0
5    6.0
6    3.5
dtype: float64

❗ Đối với DataFrame thì ta có thể tùy chọn xếp hạng theo dòng hay cột

In [255]: frame = pd.DataFrame({"b": [4.3, 7, -3, 2], "a": [0, 1, 0, 1],
   .....:                       "c": [-2, 5, 8, -2.5]})

In [256]: frame
Out[256]: 
     b  a    c
0  4.3  0 -2.0
1  7.0  1  5.0
2 -3.0  0  8.0
3  2.0  1 -2.5

In [257]: frame.rank(axis="columns")
Out[257]: 
     b    a    c
0  3.0  2.0  1.0
1  3.0  1.0  2.0
2  1.0  2.0  3.0
3  3.0  2.0  1.0
Tie-breaking methods with rank

🚀Axis Indexes with Duplicate Labels

Đến giờ, hầu hết các ví dụ mà chúng ta đã xem xét đều có nhãn trục độc nhất (giá trị chỉ mục không trùng lặp). Mặc dù nhiều hàm của pandas (như hàm reindex) yêu cầu các nhãn là duy nhất, điều này không bắt buộc. Hãy xem xét một Series nhỏ có các chỉ mục trùng lặp:

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

In [259]: obj
Out[259]: 
a    0
a    1
b    2
b    3
c    4
dtype: int64

Để kiểm tra chỉ mục có chứa nhãn trùng lặp không ta dùng hàm is_unique:

In [260]: obj.index.is_unique
Out[260]: False


Truy vấn dữ liệu là một trong những điều chính khác biệt khi có các giá trị trùng lặp ở chỉ mục. Hoặc là sẽ trả về 1-Series hoặc chỉ giá trị đơn:

In [261]: obj["a"]
Out[261]: 
a    0
a    1
dtype: int64

In [262]: obj["c"]
Out[262]: 4

Nói chung việc này sẽ khiến cho mọi thứ rắc rối hơn nhiều khi xét trên 1-DataFrame:

In [263]: df = pd.DataFrame(np.random.standard_normal((5, 3)),
   .....:                   index=["a", "a", "b", "b", "c"])

In [264]: df
Out[264]: 
          0         1         2
a  0.274992  0.228913  1.352917
a  0.886429 -2.001637 -0.371843
b  1.669025 -0.438570 -0.539741
b  0.476985  3.248944 -1.021228
c -0.577087  0.124121  0.302614

In [265]: df.loc["b"]
Out[265]: 
          0         1         2
b  1.669025 -0.438570 -0.539741
b  0.476985  3.248944 -1.021228

In [266]: df.loc["c"]
Out[266]: 
0   -0.577087
1    0.124121
2    0.302614
Name: c, dtype: float64

Bởi nên hãy tránh xảy ra việc này nhé!

🍀 Summarizing and Computing Descriptive Statistics

Các dối tượng (được tạo bởi pandas) đều có sẵn các phương thức thống kê và toán học cơ bản. Với thống kê thì sẽ rơi vào hai loại ra reduction statistics (giảm chiều-nhiễu-..) và summary statistics (Trả ra một giá trị thống kê như mean, sum từ Series hay trả ra 1-Series các giá trị từ các dòng hoặc cột của DataFrame)

🔥 Reduction method

📑Aggregation method

Vì bản chất pandas kế thừa NumPy nên nó sẽ có những phương thức khá tương đồng trong việc xử lý các dữ liệu bị thiếu. Xem ví dụ sau:

In [267]: df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5],
   .....:                    [np.nan, np.nan], [0.75, -1.3]],
   .....:                   index=["a", "b", "c", "d"],
   .....:                   columns=["one", "two"])

In [268]: df
Out[268]: 
    one  two
a  1.40  NaN
b  7.10 -4.5
c   NaN  NaN
d  0.75 -1.3

In [269]: df.sum()
Out[269]: 
one    9.25
two   -5.80
dtype: float64

Khi .sum()ta nhận về 1-Series với cột chứa các tổng của các cột trong DF trên. Còn nếu muốn tính tổng “dọc” theo các cột thì vẫn như phần trên ta dùng tham số axis="columns" hoặc axis=1

In [270]: df.sum(axis="columns")
Out[270]: 
a    1.40
b    2.60
c    0.00
d   -0.55
dtype: float64

Trong trường hợp toàn bộ dòng và cột đều chưa các giá trị NA thì tổng cho ra 0, còn nếu muốn kết quả trả về NA nếu có tồn tại giá trị NA trong dòng hay cột thì ta có thể dùng tham số skipna=False

In [271]: df.sum(axis="index", skipna=False)
Out[271]: 
one   NaN
two   NaN
dtype: float64

In [272]: df.sum(axis="columns", skipna=False)
Out[272]: 
a     NaN
b    2.60
c     NaN
d   -0.55
dtype: float64

Một vài hàm tổng hợp ("aggregation") cần đòi hỏi ít nhất một giá trị khác NA mới tính ("yield") được kết quả như hàm mean:

In [273]: df.mean(axis="columns")
Out[273]: 
a    1.400
b    1.300
c      NaN
d   -0.275
dtype: float64
Options for reduction methods

Một vài phương thức ít dùng như idxminidxmax trả về các thống kê gián tiếp mà các giá trị của nó có được:

In [274]: df.idxmax()
Out[274]: 
one    b
two    d
dtype: object

Một phương thức phổ biến khác đó là lũy tích ("accumulations"):

In [275]: df.cumsum()
Out[275]: 
    one  two
a  1.40  NaN
b  8.50 -4.5
c   NaN  NaN
d  9.25 -5.8

📑Describe method

Một vài phương thức dù không phải dạng reductions hay accumulations như describe là một ví dụ, nhưng lại cho ra rất nhiều chỉ số thống kê hữu ích:

In [276]: df.describe()
Out[276]: 
            one       two
count  3.000000  2.000000
mean   3.083333 -2.900000
std    3.493685  2.262742
min    0.750000 -4.500000
25%    1.075000 -3.700000
50%    1.400000 -2.900000
75%    4.250000 -2.100000
max    7.100000 -1.300000

Trong trường hợp dữ liệu không phải kiểu số, describe cho ra các chỉ số thống kê khác thay thế:

In [277]: obj = pd.Series(["a", "a", "b", "c"] * 4)

In [278]: obj.describe()
Out[278]: 
count     16
unique     3
top        a
freq       8
dtype: object
Descriptive and summary statistics

🔥 Correlation and Covariance

Một số chỉ số thống kê, như tương quan và hiệp phương sai, được tính từ các cặp đối số. Hãy xem xét một số Bảng dữ liệu về giá cổ phiếu và giá trị được lấy từ Yahoo! Finance

In [279]: price = pd.read_pickle("examples/yahoo_price.pkl")

In [280]: volume = pd.read_pickle("examples/yahoo_volume.pkl")

Giờ thử tính tỉ lệ thay đổi về giá bằng một hàm “time series” có sẵn trong pandas sau:

In [281]: returns = price.pct_change()

In [282]: returns.tail()
Out[282]: 
                AAPL      GOOG       IBM      MSFT
Date                                              
2016-10-17 -0.000680  0.001837  0.002072 -0.003483
2016-10-18 -0.000681  0.019616 -0.026168  0.007690
2016-10-19 -0.002979  0.007846  0.003583 -0.002255
2016-10-20 -0.000512 -0.005652  0.001719 -0.004867
2016-10-21 -0.003930  0.003011 -0.012474  0.042096


Phương thức corr của Series tính toán mức tương quan giữa các giá trị trùng lắp, không phải NA, được sắp xếp theo chỉ mục trong hai Series. Tương tự, phương thức cov tính toán hiệp phương sai:

In [283]: returns["MSFT"].corr(returns["IBM"])
Out[283]: 0.49976361144151166

In [284]: returns["MSFT"].cov(returns["IBM"])
Out[284]: 8.870655479703549e-05

Nếu dùng trực tiếp trên DataFrame, các phương thức corrcov của DataFrame trả về một ma trận chỉ số tương quan hoặc hiệp phương sai đầy đủ dưới dạng một DataFrame tương ứng:

In [285]: returns.corr()
Out[285]: 
          AAPL      GOOG       IBM      MSFT
AAPL  1.000000  0.407919  0.386817  0.389695
GOOG  0.407919  1.000000  0.405099  0.465919
IBM   0.386817  0.405099  1.000000  0.499764
MSFT  0.389695  0.465919  0.499764  1.000000

In [286]: returns.cov()
Out[286]: 
          AAPL      GOOG       IBM      MSFT
AAPL  0.000277  0.000107  0.000078  0.000095
GOOG  0.000107  0.000251  0.000078  0.000108
IBM   0.000078  0.000078  0.000146  0.000089
MSFT  0.000095  0.000108  0.000089  0.000215

Sử dụng phương thức corrwith của DataFrame, bạn có thể tính toán tương quan theo cặp giữa các cột hoặc hàng của DataFrame với một Series hoặc DataFrame khác. Việc truyền một Series sẽ trả về một Series với giá trị tương quan được tính toán cho mỗi cột:

In [287]: returns.corrwith(returns["IBM"])
Out[287]: 
AAPL    0.386817
GOOG    0.405099
IBM     1.000000
MSFT    0.499764
dtype: float64

Việc truyền một DataFrame sẽ tính chỉ số tương quan tương ứng với từng cột. Ở đây, ta tính toán các chỉ số tương quan giữa tỉ lệ thay đổi với volume:

In [288]: returns.corrwith(volume)
Out[288]: 
AAPL   -0.075565
GOOG   -0.007067
IBM    -0.204849
MSFT   -0.092950
dtype: float64

Việc truyền axis=”columns” thực hiện tính toán theo từng dòng thay vì theo cột. Trong tất cả các trường hợp, các điểm dữ liệu được căn chỉnh theo nhãn trước khi tính toán tương quan.

🔥 Unique Values, Value Counts, and Membership

Một số phương thức khác giúp trả ra thông tin về các giá trị chứa trong 1-chiều của Series khác như unique(), value_counts(). .. Xét ví dụ sau:

In [289]: obj = pd.Series(["c", "a", "d", "a", "a", "b", "b", "c", "c"])
In [290]: uniques = obj.unique()

In [291]: uniques
Out[291]: array(['c', 'a', 'd', 'b'], dtype=object)

Hàm unique trả về các giá trị độc nhất trong 1-Series. Ngoài ra sau khi lấy unique thì ta có thể sort vốn là một thao thác hay dùng (uniques.sort()).

Tương tự hàm value_counts() sẽ trả về tần suất của các giá trị xuất hiện

In [292]: obj.value_counts()
Out[292]: 
c    3
a    3
b    2
d    1
Name: count, dtype: int64

Bản thân hàm value_count có tham số sort giúp thay đổi cách sắp xếp mặc dình từ nhỏ tới lớn thành chiều ngược lại (các phương thức có hỗ trợ tham số này được gọi là “top-down method”):

In [293]: pd.value_counts(obj.to_numpy(), sort=False)
Out[293]: 
c    3
a    3
d    1
b    2
Name: count, dtype: int64

isin lại là một hàm thuộc nhóm các hàm kiểm tra theo véc-tơ rất hữu ích khi lọc một bộ dữ liệu xuống còn một phần giá trị trong một Series hoặc cột trong DataFrame

In [294]: obj
Out[294]: 
0    c
1    a
2    d
3    a
4    a
5    b
6    b
7    c
8    c
dtype: object

In [295]: mask = obj.isin(["b", "c"])

In [296]: mask
Out[296]: 
0     True
1    False
2    False
3    False
4    False
5     True
6     True
7     True
8     True
dtype: bool

In [297]: obj[mask]
Out[297]: 
0    c
5    b
6    b
7    c
8    c
dtype: object

Liên quan đến isin là phương thức Index.get_indexer, cho phép bạn nhận được một mảng chỉ mục từ một mảng “nondistinct” thành một mảng “distinct”:

In [298]: to_match = pd.Series(["c", "a", "b", "b", "c", "a"])

In [299]: unique_vals = pd.Series(["c", "b", "a"])

In [300]: indices = pd.Index(unique_vals).get_indexer(to_match)

In [301]: indices
Out[301]: array([0, 2, 1, 1, 0, 2])

Bạn có thể tính histogram (biểu đồ tần suất) trên nhiều cột liên quan trong cùng 1-DataFrame:

In [302]: data = pd.DataFrame({"Qu1": [1, 3, 4, 3, 4],
   .....:                      "Qu2": [2, 3, 1, 2, 3],
   .....:                      "Qu3": [1, 5, 2, 4, 4]})

In [303]: data
Out[303]: 
   Qu1  Qu2  Qu3
0    1    2    1
1    3    3    5
2    4    1    2
3    3    2    4
4    4    3    4

In [304]: data["Qu1"].value_counts().sort_index()
Out[304]: 
Qu1
1    1
3    2
4    2
Name: count, dtype: int64

Trong ví dụ trên ta tính value_counts trên 1-cột cụ thể. Để tính cho tất cả các cột thì ta dùng như sau:

In [305]: result = data.apply(pd.value_counts).fillna(0)

In [306]: result
Out[306]: 
   Qu1  Qu2  Qu3
1  1.0  1.0  1.0
2  0.0  2.0  1.0
3  2.0  2.0  0.0
4  2.0  0.0  2.0
5  0.0  0.0  1.0

Ở trên, hàm apply sẽ quét qua các giá trị xuất hiện trong DF, sau đó sắp xếp chúng từ nhỏ tới lớn theo chiều của cột rồi dùng value_counts để tính tần suất của chúng.

Cũng có một phương thức DataFrame.value_counts, nhưng nó tính toán số lần xuất hiện tương ứng mỗi dòng của DataFrame, nhìn hơi lạ nhưng luôn nhớ quy tắc là hàm sẽ luôn có sắp xếp từng phần là được:

In [307]: data = pd.DataFrame({"a": [1, 1, 1, 2, 2], "b": [0, 0, 1, 0, 0]})

In [308]: data
Out[308]: 
   a  b
0  1  0
1  1  0
2  1  1
3  2  0
4  2  0

In [309]: data.value_counts()
Out[309]: 
a  b
1  0    2
2  0    2
1  1    1
Name: count, dtype: int64

Trong trường hợp ở trên thì kết quả có chỉ mục để đại diện cho các dòng distinct (hierarchical index)

🍀Kết luận

Bài hôm nay cũng kết thức phần dạo đầu về thư viện pandas, một trong những thư viện vô cùng quan trọng để Phân tích dữ liệu. Đây là tiền đề để ta tiếp cận các chủ đề sau như clean, wrangling analysis, visualization. Trong chương tiếp theo mình sẽ nói qua cách để các bạn import dữ liệu bằng pandas với các datasources khác nhau.

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