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

Cũng đã 3 tuần rồi từ buổi Live lần trước, nghĩa là mình đã ăn Tết Tây sâu tới 2 tuần… Thôi thì nay comeback lại với mọi người ha chứ không mọi người lại quên Bò Cười mất.

Lần trước chúng ta tìm hiểu qua một vài cách để tải dữ liệu lên môi trường làm việc Jupyter và lưu trữ vào biến. Thật ra còn một phần nữa là làm việc với Database nhưng thú thật thì nếu có Database sẵn thì đa phần mọi người dùng SQL hơn là Python. Vì lý do đó nên mình tạm để dành phần kết nối đó thành một bài viết để các bạn tra cứu cho nhanh.

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

Day 14

Buổi Live ngày 14:


🍀Làm sạch và Chuẩn bị dữ liệu

Trong quá trình thực hiện phân tích và mô hình hóa dữ liệu, một lượng đáng kể thời gian được dành cho chuẩn bị dữ liệu: tải lên (nơi lưu trữ), làm sạch, biến đổi và sắp xếp lại. Những công việc như vậy thường được báo cáo chiếm đến 80% hoặc hơn thời gian của một nhà phân tích. Đôi khi cách dữ liệu được lưu trữ trong tệp hoặc cơ sở dữ liệu không đúng định dạng cho một nhiệm vụ cụ thể. Nhiều người chọn thực hiện xử lý tạm thời (ad hoc processing) dữ liệu từ một dạng sang dạng khác bằng một trong các ngôn ngữ lập trình đa năng như Python, Perl, R, hoặc Java, hoặc các công cụ xử lý văn bản Unix như sed hoặc awk. May mắn thay, thư viện pandas, cùng với các tính năng tích hợp sẵn của ngôn ngữ Python, mang lại cho bạn một bộ công cụ cấp cao, linh hoạt và nhanh chóng để bạn có thể biến đổi dữ liệu thành định dạng đúng.

Trong phần này, chúng ta thảo luận về các công cụ xử lý dữ liệu bị thiếu, dữ liệu trùng lặp, xử lý chuỗi và một số biến đổi dữ liệu phân tích khác. Trong chương tiếp theo, ta tập trung vào việc kết hợp và sắp xếp lại các bộ dữ liệu theo nhiều cách khác nhau.

Handling Missing Data – Xử lý dữ liệu thiếu

Missing Data thường xuyên xuất hiện trong nhiều ứng dụng phân tích dữ liệu. Một trong những mục tiêu của thư viện pandas là làm cho việc xử lý dữ liệu thiếu một cách dễ dàng nhất có thể. Ví dụ, tất cả các thống kê mô tả trên các đối tượng pandas mặc định loại bỏ dữ liệu thiếu.

Cách mà dữ liệu thiếu được biểu diễn trong các đối tượng pandas có vẻ không hoàn hảo, nhưng đó là đủ cho hầu hết các trường hợp sử dụng thực tế. Đối với dữ liệu có kiểu float64, pandas sử dụng giá trị số thực NaN (Not a Number) để biểu diễn dữ liệu thiếu.

Chúng ta gọi đây là giá trị báo hiệu (sentinel value): khi xuất hiện, nó cho biết một giá trị thiếu (hoặc null):

In [14]: float_data = pd.Series([1.2, -3.5, np.nan, 0])

In [15]: float_data
Out[15]: 
0    1.2
1   -3.5
2    NaN
3    0.0
dtype: float64

Hàm isnull trả về Sseries Boolean với null trả về True.

In [16]: float_data.isna()
Out[16]: 
0    False
1    False
2     True
3    False
dtype: bool


Trong pandas, chúng ta áp dụng một quy ước (được áp dụng trong ngôn ngữ lập trình R) tham chiếu Missing Data là NA, có nghĩa là không có sẵn (not available). Trong các ứng dụng thống kê, dữ liệu NA có thể là dữ liệu không tồn tại hoặc tồn tại nhưng không được quan tâm (thông qua vấn đề với việc thu thập dữ liệu, ví dụ). Khi làm sạch dữ liệu để phân tích, thường quan trọng ta cần thực hiện phân tích trên chính dữ liệu bị thiếu để xác định các vấn đề thu thập dữ liệu hoặc các độ chệch (bias) tiềm ẩn trong dữ liệu do dữ liệu thiếu.

Giá trị None tích hợp sẵn trong Python cũng tương tự như NA:

In [17]: string_data = pd.Series(["aardvark", np.nan, None, "avocado"])

In [18]: string_data
Out[18]: 
0    aardvark
1         NaN
2        None
3     avocado
dtype: object

In [19]: string_data.isna()
Out[19]: 
0    False
1     True
2     True
3    False
dtype: bool

In [20]: float_data = pd.Series([1, 2, None], dtype='float64')

In [21]: float_data
Out[21]: 
0    1.0
1    2.0
2    NaN
dtype: float64

In [22]: float_data.isna()
Out[22]: 
0    False
1    False
2     True
dtype: bool

Xem Bảng dưới để biết danh sách một số hàm liên quan đến xử lý dữ liệu thiếu.

Filtering Out Missing Data

Có một số cách để lọc ra dữ liệu thiếu. Trong khi bạn luôn có thể thực hiện nó bằng tay bằng cách sử dụng pandas.isna và chỉ mục Boolean, hàm dropna có thể hữu ích. Đối với một Series, nó trả về Series chỉ với dữ liệu không bị thiếu và giá trị chỉ mục:

In [23]: data = pd.Series([1, np.nan, 3.5, np.nan, 7])

In [24]: data.dropna()
Out[24]: 
0    1.0
2    3.5
4    7.0
dtype: float64

Một cách tương tự:

In [25]: data[data.notna()]
Out[25]: 
0    1.0
2    3.5
4    7.0
dtype: float64

Với đối tượng DataFrame, có các cách khác nhau để loại bỏ dữ liệu thiếu. Bạn có thể muốn loại bỏ các hàng hoặc cột mà tất cả đều là NA, hoặc chỉ những hàng hoặc cột chứa ít nhất một NA. Hàm dropna theo mặc định sẽ loại bỏ bất kỳ hàng nào chứa giá trị thiếu:

In [26]: data = pd.DataFrame([[1., 6.5, 3.], [1., np.nan, np.nan],
   ....:                      [np.nan, np.nan, np.nan], [np.nan, 6.5, 3.]])

In [27]: data
Out[27]: 
     0    1    2
0  1.0  6.5  3.0
1  1.0  NaN  NaN
2  NaN  NaN  NaN
3  NaN  6.5  3.0

In [28]: data.dropna()
Out[28]: 
     0    1    2
0  1.0  6.5  3.0

Ta có thể truyền tham số how="all" sẽ loại bỏ chỉ các dòng chứa toàn bộ NA:

In [29]: data.dropna(how="all")
Out[29]: 
     0    1    2
0  1.0  6.5  3.0
1  1.0  NaN  NaN
3  NaN  6.5  3.0

Hãy nhớ rằng những hàm này mặc định trả về các đối tượng mới và không sửa đổi nội dung của đối tượng gốc.

Để loại bỏ cột theo cùng cách, truyền tham số axis="columns":

In [30]: data[4] = np.nan

In [31]: data
Out[31]: 
     0    1    2   4
0  1.0  6.5  3.0 NaN
1  1.0  NaN  NaN NaN
2  NaN  NaN  NaN NaN
3  NaN  6.5  3.0 NaN

In [32]: data.dropna(axis="columns", how="all")
Out[32]: 
     0    1    2
0  1.0  6.5  3.0
1  1.0  NaN  NaN
2  NaN  NaN  NaN
3  NaN  6.5  3.0

Giả sử bạn chỉ muốn giữ lại những hàng chứa tối đa một giá trị NaN. Bạn có thể chỉ định điều này bằng tham số thresh:

In [33]: df = pd.DataFrame(np.random.standard_normal((7, 3)))

In [34]: df.iloc[:4, 1] = np.nan

In [35]: df.iloc[:2, 2] = np.nan

In [36]: df
Out[36]: 
          0         1         2
0 -0.204708       NaN       NaN
1 -0.555730       NaN       NaN
2  0.092908       NaN  0.769023
3  1.246435       NaN -1.296221
4  0.274992  0.228913  1.352917
5  0.886429 -2.001637 -0.371843
6  1.669025 -0.438570 -0.539741

In [37]: df.dropna()
Out[37]: 
          0         1         2
4  0.274992  0.228913  1.352917
5  0.886429 -2.001637 -0.371843
6  1.669025 -0.438570 -0.539741

In [38]: df.dropna(thresh=2)
Out[38]: 
          0         1         2
2  0.092908       NaN  0.769023
3  1.246435       NaN -1.296221
4  0.274992  0.228913  1.352917
5  0.886429 -2.001637 -0.371843
6  1.669025 -0.438570 -0.539741

Filling In Missing Data

Thay vì lọc bỏ dữ liệu thiếu (và có thể mất cả dữ liệu khác cùng với nó), bạn có thể muốn điền vào “lỗ hổng” bằng nhiều cách. Đối với hầu hết các mục đích, phương thức fillna là hàm chính để sử dụng. Gọi fillnavới một hằng số sẽ thay thế các giá trị thiếu bằng giá trị đó:

In [39]: df.fillna(0)
Out[39]: 
          0         1         2
0 -0.204708  0.000000  0.000000
1 -0.555730  0.000000  0.000000
2  0.092908  0.000000  0.769023
3  1.246435  0.000000 -1.296221
4  0.274992  0.228913  1.352917
5  0.886429 -2.001637 -0.371843
6  1.669025 -0.438570 -0.539741

Gọi hàm fillna với một dict bạn có thể sử dụng các giá trị khác nhau cho các cột khác nhau:

In [40]: df.fillna({1: 0.5, 2: 0})
Out[40]: 
          0         1         2
0 -0.204708  0.500000  0.000000
1 -0.555730  0.500000  0.000000
2  0.092908  0.500000  0.769023
3  1.246435  0.500000 -1.296221
4  0.274992  0.228913  1.352917
5  0.886429 -2.001637 -0.371843
6  1.669025 -0.438570 -0.539741

Các phương pháp nội suy giống (interpolation) để reindexing có thể được sử dụng trong tham số fillna:

In [41]: df = pd.DataFrame(np.random.standard_normal((6, 3)))

In [42]: df.iloc[2:, 1] = np.nan

In [43]: df.iloc[4:, 2] = np.nan

In [44]: df
Out[44]: 
          0         1         2
0  0.476985  3.248944 -1.021228
1 -0.577087  0.124121  0.302614
2  0.523772       NaN  1.343810
3 -0.713544       NaN -2.370232
4 -1.860761       NaN       NaN
5 -1.265934       NaN       NaN

In [45]: df.fillna(method="ffill")
Out[45]: 
          0         1         2
0  0.476985  3.248944 -1.021228
1 -0.577087  0.124121  0.302614
2  0.523772  0.124121  1.343810
3 -0.713544  0.124121 -2.370232
4 -1.860761  0.124121 -2.370232
5 -1.265934  0.124121 -2.370232

In [46]: df.fillna(method="ffill", limit=2)
Out[46]: 
          0         1         2
0  0.476985  3.248944 -1.021228
1 -0.577087  0.124121  0.302614
2  0.523772  0.124121  1.343810
3 -0.713544  0.124121 -2.370232
4 -1.860761       NaN -2.370232
5 -1.265934       NaN -2.370232

Với fillna bạn có thể làm nhiều thứ như thay thế đơn giản dữ liệu bằng cách sử dụng giá trị trung bình hoặc trung vị trong thống kê:

In [47]: data = pd.Series([1., np.nan, 3.5, np.nan, 7])

In [48]: data.fillna(data.mean())
Out[48]: 
0    1.000000
1    3.833333
2    3.500000
3    3.833333
4    7.000000
dtype: float64

Một số tham số của hàm fillna

Data Transformation – Biến đổi dữ liệu

Sau khi tìm hiểu về handling missing data. Filtering, cleaning và các biến đổi khác là những hoạt động quan trọng khác

Removing Duplicates

Các hàng trùng lặp có thể xuất hiện trong một DataFrame vì nhiều lý do khác nhau. Dưới đây là một ví dụ:

In [49]: data = pd.DataFrame({"k1": ["one", "two"] * 3 + ["two"],
   ....:                      "k2": [1, 1, 2, 3, 3, 4, 4]})

In [50]: data
Out[50]: 
    k1  k2
0  one   1
1  two   1
2  one   2
3  two   3
4  one   3
5  two   4
6  two   4

Phương thức duplicated của DataFrame trả về một Series Boolean chỉ ra xem mỗi hàng có phải là một bản sao trùng lặp (giá trị cột của nó hoàn toàn bằng với một hàng trước đó) hay không:

In [51]: data.duplicated()
Out[51]: 
0    False
1    False
2    False
3    False
4    False
5    False
6     True
dtype: bool


Liên quan đến điều này, drop_duplicates trả về một DataFrame với các hàng có giá trị trùng lặp là False được loại bỏ:

In [52]: data.drop_duplicates()
Out[52]: 
    k1  k2
0  one   1
1  two   1
2  one   2
3  two   3
4  one   3
5  two   4

Cả hai phương thức mặc định đều xem xét tất cả các cột; ngoài ra, bạn cũng có thể chỉ định một cột để phát hiện các giá trị trùng lặp. Giả sử chúng ta có một cột giá trị bổ sung và muốn lọc các bản sao dựa trên chỉ cột "k1":

In [53]: data["v1"] = range(7)

In [54]: data
Out[54]: 
    k1  k2  v1
0  one   1   0
1  two   1   1
2  one   2   2
3  two   3   3
4  one   3   4
5  two   4   5
6  two   4   6

In [55]: data.drop_duplicates(subset=["k1"])
Out[55]: 
    k1  k2  v1
0  one   1   0
1  two   1   1

Cả duplicateddrop_duplicates theo mặc định giữ giá trị đầu tiên quan sát được. Truyền tham số keep="last" sẽ trả về giá trị cuối cùng:

In [56]: data.drop_duplicates(["k1", "k2"], keep="last")
Out[56]: 
    k1  k2  v1
0  one   1   0
1  two   1   1
2  one   2   2
3  two   3   3
4  one   3   4
6  two   4   6

Transforming Data Using a Function or Mapping

Đối với nhiều bộ dữ liệu, bạn có thể muốn thực hiện một số biến đổi dựa trên các giá trị trong một mảng, Series hoặc cột trong DataFrame. Xem xét dữ liệu giả định sau về các loại thịt khác nhau:

In [57]: data = pd.DataFrame({"food": ["bacon", "pulled pork", "bacon",
   ....:                               "pastrami", "corned beef", "bacon",
   ....:                               "pastrami", "honey ham", "nova lox"],
   ....:                      "ounces": [4, 3, 12, 6, 7.5, 8, 3, 5, 6]})

In [58]: data
Out[58]: 
          food  ounces
0        bacon     4.0
1  pulled pork     3.0
2        bacon    12.0
3     pastrami     6.0
4  corned beef     7.5
5        bacon     8.0
6     pastrami     3.0
7    honey ham     5.0
8     nova lox     6.0

Giả sử bạn muốn thêm một cột chỉ ra loại động vật mà mỗi loại thực phẩm đến từ. Hãy viết ra một bảng ánh xạ của mỗi loại thịt riêng biệt đến loại động vật tương ứng:

meat_to_animal = {
  "bacon": "pig",
  "pulled pork": "pig",
  "pastrami": "cow",
  "corned beef": "cow",
  "honey ham": "pig",
  "nova lox": "salmon"
}

Phương thức map trên một Series chấp nhận một hàm hoặc đối tượng giống như dict chứa ánh xạ để thực hiện biến đổi các giá trị:

In [60]: data["animal"] = data["food"].map(meat_to_animal)

In [61]: data
Out[61]: 
          food  ounces  animal
0        bacon     4.0     pig
1  pulled pork     3.0     pig
2        bacon    12.0     pig
3     pastrami     6.0     cow
4  corned beef     7.5     cow
5        bacon     8.0     pig
6     pastrami     3.0     cow
7    honey ham     5.0     pig
8     nova lox     6.0  salmon

Chúng ta cũng có thể truyền một hàm thực hiện toàn bộ việc trên:

In [62]: def get_animal(x):
   ....:     return meat_to_animal[x]

In [63]: data["food"].map(get_animal)
Out[63]: 
0       pig
1       pig
2       pig
3       cow
4       cow
5       pig
6       cow
7       pig
8    salmon
Name: food, dtype: object

Sử dụng map là một cách thuận tiện để thực hiện các biến đổi theo phần tử và các hoạt động liên quan đến việc làm sạch dữ liệu khác.

Replace Values – Thay thế giá trị

Việc điền vào dữ liệu thiếu bằng phương thức fillna là một trường hợp đặc biệt của việc thay thế giá trị chung. Như bạn đã thấy, map có thể được sử dụng để sửa đổi một phần giá trị trong một đối tượng, nhưng replace cung cấp một cách đơn giản và linh hoạt hơn để làm điều này. Hãy xem xét Series sau:

In [64]: data = pd.Series([1., -999., 2., -999., -1000., 3.])

In [65]: data
Out[65]: 
0       1.0
1    -999.0
2       2.0
3    -999.0
4   -1000.0
5       3.0
dtype: float64

Các giá trị -999 có thể là giá trị báo hiệu cho dữ liệu thiếu. Để thay thế chúng bằng giá trị NA mà pandas hiểu, chúng ta có thể sử dụng replace, tạo ra một Series mới:

In [66]: data.replace(-999, np.nan)
Out[66]: 
0       1.0
1       NaN
2       2.0
3       NaN
4   -1000.0
5       3.0
dtype: float64

Nếu bạn muốn thay thế nhiều giá trị cùng một lúc, bạn có thể truyền một danh sách chứa các giá trị cần thay thế:

In [67]: data.replace([-999, -1000], np.nan)
Out[67]: 
0    1.0
1    NaN
2    2.0
3    NaN
4    NaN
5    3.0
dtype: float64

Để thay thế nhiều giá trị khác nhau, ta có thể truyền một danh sách các giá trị cần thay thế:

In [68]: data.replace([-999, -1000], [np.nan, 0])
Out[68]: 
0    1.0
1    NaN
2    2.0
3    NaN
4    0.0
5    3.0
dtype: float64

Ngoài ra ta cũng có thể truyền tham số dạng dict

In [69]: data.replace({-999: np.nan, -1000: 0})
Out[69]: 
0    1.0
1    NaN
2    2.0
3    NaN
4    0.0
5    3.0
dtype: float64

Phương thức data.replace khác với data.str.replace vì thực hiện thay thế chuỗi theo từng phần tử (element-wise string substitution or regex). Cụ thể:

(Nguồn: link)

  1. Purpose. Use str.replace for substring replacements on a single string column, and replace for any general replacement on one or more columns.
  2. Usagestr.replace can replace one thing at a time. replace lets you perform multiple independent replacements – conjunction with, i.e., replace many things at once.
  3. Default behaviorstr.replace enables regex replacement by default. replace only performs a full match unless the regex=True switch is used.

Renaming Axis Indexes

Như giá trị trong một Series, nhãn của trục (axis label) có thể được biến đổi tương tự bằng một hàm hoặc ánh xạ nào đó để tạo ra các đối tượng mới với nhãn khác nhau (Bạn cũng có thể sửa đổi các trục ngay tại chỗ mà không tạo ra một cấu trúc dữ liệu mới). Dưới đây là một ví dụ đơn giản:

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

Tương tự như một Series, các chỉ mục trục có phương thức map:

In [71]: def transform(x):
   ....:     return x[:4].upper()

In [72]: data.index.map(transform)
Out[72]: Index(['OHIO', 'COLO', 'NEW '], dtype='object')

Bạn có thể gán cho thuộc tính index, sửa đổi index của DataFrame ngay tại chỗ:

In [73]: data.index = data.index.map(transform)

In [74]: data
Out[74]: 
      one  two  three  four
OHIO    0    1      2     3
COLO    4    5      6     7
NEW     8    9     10    11

Nếu bạn muốn tạo ra một phiên bản được biến đổi của bộ dữ liệu mà không làm thay đổi bản gốc, một phương thức hữu ích là rename:

In [75]: data.rename(index=str.title, columns=str.upper)
Out[75]: 
      ONE  TWO  THREE  FOUR
Ohio    0    1      2     3
Colo    4    5      6     7
New     8    9     10    11

Đáng chú ý, rename có thể được sử dụng kết hợp (“conjunction with“) với một đối tượng giống như dict, cung cấp giá trị mới cho một phần của nhãn trục:

In [76]: data.rename(index={"OHIO": "INDIANA"},
   ....:             columns={"three": "peekaboo"})
Out[76]: 
         one  two  peekaboo  four
INDIANA    0    1         2     3
COLO       4    5         6     7
NEW        8    9        10    11

Phương thức rename giúp bạn tránh khỏi công việc sao chép DataFrame thủ công và gán giá trị mới cho các thuộc tínhindexcolumns của nó.

Discretization and Binning

Dữ liệu liên tục (Continuous data) trên thật tế lại khi phân tích ta cần phân tách (discretized) hoặc phân chia thành “khoảng” (bin). Giả sử bạn có dữ liệu về một nhóm người trong một nghiên cứu và bạn muốn nhóm họ vào các khoảng tuổi khác nhau:

In [77]: ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]


Hãy chia chúng thành các khoảng từ 18 đến 25, 26 đến 35, 36 đến 60, và cuối cùng là 61 tuổi trở lên. Để làm điều này, bạn phải sử dụng pandas.cut:

In [78]: bins = [18, 25, 35, 60, 100]

In [79]: age_categories = pd.cut(ages, bins)

In [80]: age_categories
Out[80]: 
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35,
 60], (35, 60], (25, 35]]
Length: 12
Categories (4, interval[int64, right]): [(18, 25] < (25, 35] < (35, 60] < (60, 10
0]]

Đối tượng mà pandas trả về là một đối tượng Categorical đặc biệt. Đầu ra mà bạn thấy mô tả các khoảng được tính bằng pandas.cut. Mỗi “bin” được xác định bởi một khoảng giá trị đặc biệt chứa giới hạn dưới và giới hạn trên của mỗi khoảng:

In [81]: age_categories.codes
Out[81]: array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)

In [82]: age_categories.categories
Out[82]: IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]], dtype='interval
[int64, right]')

In [83]: age_categories.categories[0]
Out[83]: Interval(18, 25, closed='right')

In [84]: pd.value_counts(age_categories)
Out[84]: 
(18, 25]     5
(25, 35]     3
(35, 60]     3
(60, 100]    1
Name: count, dtype: int64

Thuộc tính .codes đối với đối tượng Categories cho biết giá trị đó thuộc bin nào.

pd.value_counts(categories) là số lượng các phần tử thuộc từng bin đếm được từ kết quả của pandas.cut().

Trong biểu diễn chuỗi của khoảng ở trên, dấu ngoặc đơn nghĩa loại trừ, trong khi dấu ngoặc vuông nghĩa là bao gồm. Bạn có thể thay đổi phía nào được đóng cửa bằng cách truyền right=False:

In [85]: pd.cut(ages, bins, right=False)
Out[85]: 
[[18, 25), [18, 25), [25, 35), [25, 35), [18, 25), ..., [25, 35), [60, 100), [35,
 60), [35, 60), [25, 35)]
Length: 12
Categories (4, interval[int64, left]): [[18, 25) < [25, 35) < [35, 60) < [60, 100
)]

Bạn có thể ghi đè nhãn của các bin bằng cách truyền một danh sách hoặc mảng cho tham số labels:

In [86]: group_names = ["Youth", "YoungAdult", "MiddleAged", "Senior"]

In [87]: pd.cut(ages, bins, labels=group_names)
Out[87]: 
['Youth', 'Youth', 'Youth', 'YoungAdult', 'Youth', ..., 'YoungAdult', 'Senior', '
MiddleAged', 'MiddleAged', 'YoungAdult']
Length: 12
Categories (4, object): ['Youth' < 'YoungAdult' < 'MiddleAged' < 'Senior']


Tùy chọn precision=2 giới hạn độ chính xác thập phân là hai chữ số.

Một hàm liên quan chặt chẽ, pandas.qcut, chia dữ liệu dựa trên các phân vị (quantiles). Tùy thuộc vào phân phối của dữ liệu, việc sử dụng pandas.cut thường sẽ không dẫn đến mỗi bin có cùng số lượng điểm dữ liệu. Vì pandas.qcut sử dụng phân vị, bạn sẽ thu được các bin có kích thước gần như bằng nhau:

In [90]: data = np.random.standard_normal(1000)

In [91]: quartiles = pd.qcut(data, 4, precision=2)

In [92]: quartiles
Out[92]: 
[(-0.026, 0.62], (0.62, 3.93], (-0.68, -0.026], (0.62, 3.93], (-0.026, 0.62], ...
, (-0.68, -0.026], (-0.68, -0.026], (-2.96, -0.68], (0.62, 3.93], (-0.68, -0.026]
]
Length: 1000
Categories (4, interval[float64, right]): [(-2.96, -0.68] < (-0.68, -0.026] < (-0
.026, 0.62] <
                                           (0.62, 3.93]]

In [93]: pd.value_counts(quartiles)
Out[93]: 
(-2.96, -0.68]     250
(-0.68, -0.026]    250
(-0.026, 0.62]     250
(0.62, 3.93]       250
Name: count, dtype: int64

Tương tự như pandas.cut, bạn có thể truyền các phân vị riêng của mình (số nằm giữa 0 và 1, bao gồm cả hai đầu):

In [94]: pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.]).value_counts()
Out[94]: 
(-2.9499999999999997, -1.187]    100
(-1.187, -0.0265]                400
(-0.0265, 1.286]                 400
(1.286, 3.928]                   100
Name: count, dtype: int64

Chúng ta sẽ quay lại pandas.cutpandas.qcut sau trong các phép tính về aggregation hoặc grouping, vì những hàm phân tách này đặc biệt hữu ích cho chia tách tỉ lệ và nhóm.

Detecting and Filtering Outliers

Lọc hoặc biến đổi các giá trị ngoại lệ (outlier) chủ yếu liên quan đến việc áp dụng các phép toán mảng. Xem xét một DataFrame với một số dữ liệu theo phân phối chuẩn sau:

In [95]: data = pd.DataFrame(np.random.standard_normal((1000, 4)))

In [96]: data.describe()
Out[96]: 
                 0            1            2            3
count  1000.000000  1000.000000  1000.000000  1000.000000
mean      0.049091     0.026112    -0.002544    -0.051827
std       0.996947     1.007458     0.995232     0.998311
min      -3.645860    -3.184377    -3.745356    -3.428254
25%      -0.599807    -0.612162    -0.687373    -0.747478
50%       0.047101    -0.013609    -0.022158    -0.088274
75%       0.756646     0.695298     0.699046     0.623331
max       2.653656     3.525865     2.735527     3.366626

Giả sử bạn muốn tìm các giá trị trong một trong các cột vượt quá giá trị tuyệt đối là 3:

In [97]: col = data[2]

In [98]: col[col.abs() > 3]
Out[98]: 
41    -3.399312
136   -3.745356
Name: 2, dtype: float64

Để chọn tất cả các hàng có giá trị vượt quá 3 hoặc -3, bạn có thể sử dụng phương thức.any() trên một DataFrame Boolean:

In [99]: data[(data.abs() > 3).any(axis="columns")]
Out[99]: 
            0         1         2         3
41   0.457246 -0.025907 -3.399312 -0.974657
60   1.951312  3.260383  0.963301  1.201206
136  0.508391 -0.196713 -3.745356 -1.520113
235 -0.242459 -3.056990  1.918403 -0.578828
258  0.682841  0.326045  0.425384 -3.428254
322  1.179227 -3.184377  1.369891 -1.074833
544 -3.548824  1.553205 -2.186301  1.277104
635 -0.578093  0.193299  1.397822  3.366626
782 -0.207434  3.525865  0.283070  0.544635
803 -3.645860  0.255475 -0.549574 -1.907459

Dấu ngoặc đơn xung quanh data.abs() > 3 là cần thiết để gọi phương thức any trên kết quả của phép so sánh.

Các giá trị có thể được đặt dựa trên các tiêu chí này. Dưới đây là code để giới hạn giá trị nằm ngoài khoảng từ -3 đến 3:

In [100]: data[data.abs() > 3] = np.sign(data) * 3

In [101]: data.describe()
Out[101]: 
                 0            1            2            3
count  1000.000000  1000.000000  1000.000000  1000.000000
mean      0.050286     0.025567    -0.001399    -0.051765
std       0.992920     1.004214     0.991414     0.995761
min      -3.000000    -3.000000    -3.000000    -3.000000
25%      -0.599807    -0.612162    -0.687373    -0.747478
50%       0.047101    -0.013609    -0.022158    -0.088274
75%       0.756646     0.695298     0.699046     0.623331
max       2.653656     3.000000     2.735527     3.000000

np.sign(data) tạo ra các giá trị 1 và -1 dựa trên việc liệu các giá trị trong data có là dương hay âm:

In [102]: np.sign(data).head()
Out[102]: 
     0    1    2    3
0 -1.0  1.0 -1.0  1.0
1  1.0 -1.0  1.0 -1.0
2  1.0  1.0  1.0 -1.0
3 -1.0 -1.0  1.0 -1.0
4 -1.0  1.0 -1.0 -1.0

Permutation and Random Sampling

Việc hoán vị (đặt lại vị trí ngẫu nhiên) một Series hoặc các hàng trong một DataFrame có thể được thực hiện bằng cách sử dụng hàm numpy.random.permutation.

Ví dụ sau Hàm permutation() hoán vị và trả về mảng các số nguyên với thứ tự mới:

In [105]: sampler = np.random.permutation(5)

In [106]: sampler
Out[106]: array([3, 1, 4, 2, 0])

Mảng đó sau đó có thể được sử dụng trong trong việc reindexing bằng hàm take() hoặc hàm iloc():

In [103]: df = pd.DataFrame(np.arange(5 * 7).reshape((5, 7)))

In [104]: df
Out[104]: 
    0   1   2   3   4   5   6
0   0   1   2   3   4   5   6
1   7   8   9  10  11  12  13
2  14  15  16  17  18  19  20
3  21  22  23  24  25  26  27
4  28  29  30  31  32  33  34


In [107]: df.take(sampler)
Out[107]: 
    0   1   2   3   4   5   6
3  21  22  23  24  25  26  27
1   7   8   9  10  11  12  13
4  28  29  30  31  32  33  34
2  14  15  16  17  18  19  20
0   0   1   2   3   4   5   6

In [108]: df.iloc[sampler]
Out[108]: 
    0   1   2   3   4   5   6
3  21  22  23  24  25  26  27
1   7   8   9  10  11  12  13
4  28  29  30  31  32  33  34
2  14  15  16  17  18  19  20
0   0   1   2   3   4   5   6

Bằng cách gọi take với axis="columns", ta có thể hoán vị cột thay vì dòng:

In [109]: column_sampler = np.random.permutation(7)

In [110]: column_sampler
Out[110]: array([4, 6, 3, 2, 1, 0, 5])

In [111]: df.take(column_sampler, axis="columns")
Out[111]: 
    4   6   3   2   1   0   5
0   4   6   3   2   1   0   5
1  11  13  10   9   8   7  12
2  18  20  17  16  15  14  19
3  25  27  24  23  22  21  26
4  32  34  31  30  29  28  33

Để chọn một tập con ngẫu nhiên mà không bị thay thế (cùng một hàng không thể xuất hiện hai lần), bạn có thể sử dụng phương thức sample trên Series và DataFrame:

In [112]: df.sample(n=3)
Out[112]: 
    0   1   2   3   4   5   6
2  14  15  16  17  18  19  20
4  28  29  30  31  32  33  34
0   0   1   2   3   4   5   6

Để tạo một mẫu với bị thay thế (cho phép lựa chọn lặp lại), truyền replace=True cho phương thức sample:

In [113]: choices = pd.Series([5, 7, -1, 6, 4])

In [114]: choices.sample(n=10, replace=True)
Out[114]: 
2   -1
0    5
3    6
1    7
4    4
0    5
4    4
0    5
4    4
4    4
dtype: int64

Computing Indicator/Dummy Variables

Một loại biến đổi khác cho mô hình học thống kê hoặc máy học là chuyển đổi một biến phân loại (categorical variable) thành ma trận giả định hoặc chỉ mục (a dummy or indicator matrix) -> Đây là một bước quan trọng trong bài toán phân loại.

Nếu một cột trong DataFrame có k giá trị phân biệt, bạn sẽ tạo ra một ma trận hoặc DataFrame có k cột chứa toàn bộ giá trị 1 và 0. pandas có một hàm pandas.get_dummies để thực hiện điều này, tuy nhiên, bạn cũng có thể tạo ra một hàm như vậy. Hãy xem xét một ví dụ về DataFrame:

In [115]: df = pd.DataFrame({"key": ["b", "b", "a", "c", "a", "b"],
   .....:                    "data1": range(6)})

In [116]: df
Out[116]: 
  key  data1
0   b      0
1   b      1
2   a      2
3   c      3
4   a      4
5   b      5

In [117]: pd.get_dummies(df["key"], dtype=float)
Out[117]: 
     a    b    c
0  0.0  1.0  0.0
1  0.0  1.0  0.0
2  1.0  0.0  0.0
3  0.0  0.0  1.0
4  1.0  0.0  0.0
5  0.0  1.0  0.0


Ở đây, mình truyền dtype=float để thay đổi kiểu đầu ra từ boolean (mặc định trong các phiên bản gần đây của pandas) thành số dấu chấm động.

Trong một số trường hợp, bạn có thể muốn thêm một tiền tố vào các cột trong DataFrame indicator, sau đó có thể được gộp với dữ liệu khác. pandas.get_dummies có một đối số tiền tố để thực hiện điều này:

In [118]: dummies = pd.get_dummies(df["key"], prefix="key", dtype=float)

In [119]: df_with_dummy = df[["data1"]].join(dummies)

In [120]: df_with_dummy
Out[120]: 
   data1  key_a  key_b  key_c
0      0    0.0    1.0    0.0
1      1    0.0    1.0    0.0
2      2    1.0    0.0    0.0
3      3    0.0    0.0    1.0
4      4    1.0    0.0    0.0
5      5    0.0    1.0    0.0

Phương thức DataFrame.join sẽ được giải thích chi tiết hơn trong các phần tiếp theo.

Nếu một hàng trong DataFrame thuộc nhiều danh mục, chúng ta phải sử dụng một phương pháp khác để tạo các biến giả. Hãy xem xét bộ dữ liệu MovieLens 1M sau:

In [121]: mnames = ["movie_id", "title", "genres"]

In [122]: movies = pd.read_table("datasets/movielens/movies.dat", sep="::",
   .....:                        header=None, names=mnames, engine="python")

In [123]: movies[:10]
Out[123]: 
   movie_id                               title                        genres
0         1                    Toy Story (1995)   Animation|Children's|Comedy
1         2                      Jumanji (1995)  Adventure|Children's|Fantasy
2         3             Grumpier Old Men (1995)                Comedy|Romance
3         4            Waiting to Exhale (1995)                  Comedy|Drama
4         5  Father of the Bride Part II (1995)                        Comedy
5         6                         Heat (1995)         Action|Crime|Thriller
6         7                      Sabrina (1995)                Comedy|Romance
7         8                 Tom and Huck (1995)          Adventure|Children's
8         9                 Sudden Death (1995)                        Action
9        10                    GoldenEye (1995)     Action|Adventure|Thriller

pandas đã triển khai một phương thức đặc biệt của Series là str.get_dummies (phương thức bắt đầu bằng str.sẽ được thảo luận chi tiết hơn trong phần Xử lý Chuỗi sau này) để xử lý tình huống biến đổi nhưng gặp delimited string như ví dụ này là cột genres:

In [124]: dummies = movies["genres"].str.get_dummies("|")

In [125]: dummies.iloc[:10, :6]
Out[125]: 
   Action  Adventure  Animation  Children's  Comedy  Crime
0       0          0          1           1       1      0
1       0          1          0           1       0      0
2       0          0          0           0       1      0
3       0          0          0           0       1      0
4       0          0          0           0       1      0
5       1          0          0           0       0      1
6       0          0          0           0       1      0
7       0          1          0           1       0      0
8       1          0          0           0       0      0
9       1          1          0           0       0      0

Sau đó bạn có thể kết hợp DF này với cột movies trong khi thêm một “Genre_” vào tên cột trong DataFrame dummies bằng phương thức add_prefix:

In [126]: movies_windic = movies.join(dummies.add_prefix("Genre_"))

In [127]: movies_windic.iloc[0]
Out[127]: 
movie_id                                       1
title                           Toy Story (1995)
genres               Animation|Children's|Comedy
Genre_Action                                   0
Genre_Adventure                                0
Genre_Animation                                1
Genre_Children's                               1
Genre_Comedy                                   1
Genre_Crime                                    0
Genre_Documentary                              0
Genre_Drama                                    0
Genre_Fantasy                                  0
Genre_Film-Noir                                0
Genre_Horror                                   0
Genre_Musical                                  0
Genre_Mystery                                  0
Genre_Romance                                  0
Genre_Sci-Fi                                   0
Genre_Thriller                                 0
Genre_War                                      0
Genre_Western                                  0
Name: 0, dtype: object

Đối với dữ liệu lớn hơn, phương pháp xây dựng các indicator variables với nhiều thành viên không đặc biệt nhanh. Sẽ tốt hơn nếu viết một hàm cấp thấp hơn mà ghi trực tiếp vào một mảng NumPy, và sau đó bọc kết quả vào một DataFrame.

Lưu ý


Một công thức hữu ích cho các ứng dụng thống kê là kết hợp pandas.get_dummies với một hàm phân tách như pandas.cut:

In [128]: np.random.seed(12345) # to make the example repeatable

In [129]: values = np.random.uniform(size=10)

In [130]: values
Out[130]: 
array([0.9296, 0.3164, 0.1839, 0.2046, 0.5677, 0.5955, 0.9645, 0.6532,
       0.7489, 0.6536])

In [131]: bins = [0, 0.2, 0.4, 0.6, 0.8, 1]

In [132]: pd.get_dummies(pd.cut(values, bins))
Out[132]: 
   (0.0, 0.2]  (0.2, 0.4]  (0.4, 0.6]  (0.6, 0.8]  (0.8, 1.0]
0       False       False       False       False        True
1       False        True       False       False       False
2        True       False       False       False       False
3       False        True       False       False       False
4       False       False        True       False       False
5       False       False        True       False       False
6       False       False       False       False        True
7       False       False       False        True       False
8       False       False       False        True       False
9       False       False       False        True       False

Kết luận

Hôm nay mình dừng tại đây trước, bài hôm nay mình cũng đi qua một loạt các phương thức biến đổi thông dụng cũng như các ví dụ kèm theo. Trên thật tế thì ta phải thật bắt tay vào làm thì mới quen được. Nếu các bạn có bất cứ bổ sung nào trong bài viết thì cứ nhắn tin cho mình nhé.

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