🍀Tóm tắt buổi trước
Kết thúc buổi hôm trước chúng ta đã hoàn thành xong những bước tiền đề để bắt đầu đi vào các chủ đề chính trong công việc phân tích dữ liệu. Điểm lại những gì ta đã học gồm:
- Các kiến thức cơ bản về dữ liệu và các thuật ngữ;
- Làm quen với Cấu trúc dữ liệu cơ bản;
- Cách sử dụng Hàm và đọc một số file dữ liệu phổ biến;
- Tìm hiểu về thư viện numpy
- Cách tạo mảng đa chiều bằng numpy
- Thực hiện phép toán đại số trên các kiểu dữ liệu nparray
- Làm quen với Slicing, Boolean và Fancy indexing
- Hoán vị và Đảo chiều các trục
- Tìm hiểu về thư viện pandas
- Tìm hiểu 3 cấu trúc dữ liệu trong pandas
- Thực hành với các hàm cơ bản của pandas
- Làm quen với một số hàm thống kê và tổng hợp
Các bạn có thể theo dõi lại toàn bộ các tập trước nếu có quên chỗ nào hen
🍀Đọc và Ghi dữ liệu dạng văn bản
Việc đọc dữ liệu hay thiết lập cho nó truy cập/tải lên được (“data loading”) là bước đầu tiên quan trọng để có thể sử dụng hầu hết các công cụ phân tích chuyên sâu. Thuật ngữ “parsing” đôi khi cũng được sử dụng để mô tả việc tải dữ liệu văn bản lên và hợp lại thành dạng bảng hoặc các định dạng khác.
Thường thì dữ liệu đầu vào và đầu ra sẽ có vài dạng chính:
Các files văn bản (csv, excel, ..); Các dữ liệu từ CSDL hoặc Các nguồn dữ liệu thông qua Web APIs dưới dạng JSON, ..
pandas có rất nhiều hàm để đọc dữ liệu dạng bảng thành đối tượng DataFrame (trong đó pandas.read_csv là hàm thường dùng nhất). Ngoài ra nó còn cung cấp các hàm đọc dữ liệu nhị phân như pandas.ExcelFile hay pandas.HDFStore mà mình sẽ đề cập trong buổi sau.
Dưới đây là các hàm đọc dữ liệu dạng tabular phổ biến:
Dễ dàng nhận thấy các hàm trên có chung mục đích là chuyển dạng văn bản về DataFrame. Bên trong các hàm luôn có các tham số tùy chọn thêm, chúng thuộc một vài loại như:
- Chỉ mục (Indexing): Liên quan việc xử lý các cột của DataFrame trả về. Ví dụ: Ta có lấy tên gốc của cột, hoặc tùy biến sửa lại.
- Phân loại và Chuyển đổi kiểu (Type inference and data conversion): Chuyển đổi thành các giá trị định nghĩa trước và đánh dấu các giá trị thiếu.
- Diễn giải cấu trúc ngày và thời gian (Date and time parsing): Gộp nhất cột thời gian chung nếu các giá trị thời gian bị phân tán thành nhiều cột
- Duyệt (Iterating): Hỗ trợ duyệt qua các phần nhỏ của tệp lớn để giảm tải khi khai phá dữ liệu.
- Làm sạch dữ liệu (Unclean data): Các bước tiền xử lý như bỏ qua các dòng lỗi hoặc phần footer, các ghi chú, hoặc các vấn đề nhỏ khác như dữ liệu số với phân tách hàng nghìn bằng dấu phẩy…
Bởi vì dữ liệu trong thế giới thực có thể rất lộn xộn, nên một số hàm đọc ghi (đặc biệt là
Note 💡pandas.read_csv) đã tích hợp một danh sách dài các tham số tùy chọn đa dạng qua thời gian (pandas.read_csvcó khoảng 50 tham số).
Tài liệu pandas có nhiều ví dụ về cách mỗi tham số hoạt động nên mỗi khi suy nghĩ tới một trường hợp cần xử lý thì hãy thử tìm kiếm xem liệu có tham số nào đáp ứng cho ta không trước khi tự sáng tạo một cái mới.
Một số hàm trên có thể thực hiện phân loại kiểu ("type inference"), vì kiểu dữ liệu cột (“the column data types”) không phải là một phần của các định dạng dữ liệu (“data format”) – Nghe hơi lùng bùng nhưng ví dụ như JSON là data format nó không kèm thông tin dữ liệu trong nó thuộc data type nào! Điều này có nghĩa là bạn không nhất thiết phải chỉ định rõ cột nào là numeric, integer, Boolean, hoặc string. Các định dạng dữ liệu khác như HDF5, ORC và Parquet có thông tin kiểu dữ liệu được nhúng trong định dạng.
Xử lý ngày và các loại dữ liệu tùy chỉnh khác có thể đòi hỏi công sức nhiều hơn. Giờ Hãy bắt đầu với một tệp văn bản giá trị được phân tách bằng dấu phẩy (CSV):
In [10]: !cat examples/ex1.csv
a,b,c,d,message
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,fooLưu ý: cat là shell command Linux viết tắt concatenate dùng để hiển thị nội dung của file trong terminal. Nếu trong môi trường Windows thì có thể dùng
Note%pycatđể thay!cat
In [11]: df = pd.read_csv("examples/ex1.csv")
In [12]: df
Out[12]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 fooĐôi khi thì file không có dòng tiêu đề cột:
In [13]: !cat examples/ex2.csv
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,fooĐể đọc file này thì có vài lựa chọn như gán giá trị mặc định cho tên các cột hoặc tự định nghĩa trước:
In [14]: pd.read_csv("examples/ex2.csv", header=None)
Out[14]:
0 1 2 3 4
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
In [15]: pd.read_csv("examples/ex2.csv", names=["a", "b", "c", "d", "message"])
Out[15]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 fooGỉa sử trong ví dụ trên bạn muốn cột message là cột index cho DF thì có thể chỉ định index 4 hoặc message trong tham số index_col
In [16]: names = ["a", "b", "c", "d", "message"]
In [17]: pd.read_csv("examples/ex2.csv", names=names, index_col="message")
Out[17]:
a b c d
message
hello 1 2 3 4
world 5 6 7 8
foo 9 10 11 12Trong trường hợp bạn muốn lập chỉ mục phân cấp (“hierachical index”) – Chúng ta sẽ còn quay lại phần này sau. Thì có thể làm như sau:
In [18]: !cat examples/csv_mindex.csv
key1,key2,value1,value2
one,a,1,2
one,b,3,4
one,c,5,6
one,d,7,8
two,a,9,10
two,b,11,12
two,c,13,14
two,d,15,16
In [19]: parsed = pd.read_csv("examples/csv_mindex.csv",
....: index_col=["key1", "key2"])
In [20]: parsed
Out[20]:
value1 value2
key1 key2
one a 1 2
b 3 4
c 5 6
d 7 8
two a 9 10
b 11 12
c 13 14
d 15 16Đôi khi file sẽ dùng ký tự khác để phân tách các trường (thường sẽ dùng ký tự trắng hoặc một mẫu khác để phân tách các trường)
In [21]: !cat examples/ex3.txt
A B C
aaa -0.264438 -1.026059 -0.619500
bbb 0.927272 0.302904 -0.032399
ccc -0.264273 -0.386314 -0.217601
ddd -0.871858 -0.348382 1.100491Gặp trường hợp này thì hoặc bạn có thể biến đổi “muging” bằng tay chuyển về dạng dấu phẩy hoặc dùng tham số sau kèm theo là biểu thức regular expression:
In [22]: result = pd.read_csv("examples/ex3.txt", sep="\s+")
In [23]: result
Out[23]:
A B C
aaa -0.264438 -1.026059 -0.619500
bbb 0.927272 0.302904 -0.032399
ccc -0.264273 -0.386314 -0.217601
ddd -0.871858 -0.348382 1.100491Trong trường hợp này có ít hơn 1 tên cột xuất hiện nên mặc định pandas sẽ tự phân cột đầu làm cột index.
Một ví dụ khác khi bạn muốn bỏ qua dòng 1st 3rd và 4th bằng tham số skiprows
In [24]: !cat examples/ex4.csv
# hey!
a,b,c,d,message
# just wanted to make things more difficult for you
# who reads CSV files with computers, anyway?
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
In [25]: pd.read_csv("examples/ex4.csv", skiprows=[0, 2, 3])
Out[25]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 fooXử lý giá trị thiếu là bước quan trọng và thường gặp. Dữ liệu bị thiếu thường bị để rỗng (chuỗi trống) hoặc được đánh dấu bằng một giá trị báo hiệu "sentinel" (giữ chỗ – "placeholder"). Theo mặc định, pandas sử dụng một tập hợp các giá trị báo hiệu phổ biến, như NA và NULL:
In [26]: !cat examples/ex5.csv
something,a,b,c,d,message
one,1,2,3,4,NA
two,5,6,,8,world
three,9,10,11,12,foo
In [27]: result = pd.read_csv("examples/ex5.csv")
In [28]: result
Out[28]:
something a b c d message
0 one 1 2 3.0 4 NaN
1 two 5 6 NaN 8 world
2 three 9 10 11.0 12 fooMột hàm quen thuộc ta từng dùng để tìm giá trị Null
In [29]: pd.isna(result)
Out[29]:
something a b c d message
0 False False False False False True
1 False False False True False False
2 False False False False False FalseTrong trường hợp nếu đó là một “placeholder” thì ta dùng tham số sau để xác định (ví dụ thêm)
In [30]: result = pd.read_csv("examples/ex5.csv", na_values=["NULL"])
In [31]: result
Out[31]:
something a b c d message
0 one 1 2 3.0 4 NaN
1 two 5 6 NaN 8 world
2 three 9 10 11.0 12 fooNếu bạn muốn giữ nguyên giá trị gốc thì ta có thể dùng keep_default_na hoặc ta cũng có thể kết hợp các tham số với nhau:
In [32]: result2 = pd.read_csv("examples/ex5.csv", keep_default_na=False)
In [33]: result2
Out[33]:
something a b c d message
0 one 1 2 3 4 NA
1 two 5 6 8 world
2 three 9 10 11 12 foo
In [34]: result2.isna()
Out[34]:
something a b c d message
0 False False False False False False
1 False False False False False False
2 False False False False False False
In [35]: result3 = pd.read_csv("examples/ex5.csv", keep_default_na=False,
....: na_values=["NA"])
In [36]: result3
Out[36]:
something a b c d message
0 one 1 2 3 4 NaN
1 two 5 6 8 world
2 three 9 10 11 12 foo
In [37]: result3.isna()
Out[37]:
something a b c d message
0 False False False False False True
1 False False False False False False
2 False False False False False FalseTa còn có thể trực tiếp ghi đè giá trị “sentinels” lên
In [38]: sentinels = {"message": ["foo", "NA"], "something": ["two"]}
In [39]: pd.read_csv("examples/ex5.csv", na_values=sentinels,
....: keep_default_na=False)
Out[39]:
something a b c d message
0 one 1 2 3 4 NaN
1 NaN 5 6 8 world
2 three 9 10 11 12 NaN🚀Reading Text Files in Pieces
Khi xử lý các tệp rất lớn hoặc tìm hiểu bộ tham số có đúng khi áp vào không, ta nên đọc chỉ một phần nhỏ của tệp hoặc lặp qua các phần nhỏ của tệp.
Trước khi chúng ta xem xét một tệp lớn, chúng ta sẽ điều chỉnh cài đặt hiển thị của pandas để nó trở nên gọn gàng hơn:
In [40]: pd.options.display.max_rows = 10Xong rồi thì ta sẽ thấy như sau:
In [41]: result = pd.read_csv("examples/ex6.csv")
In [42]: result
Out[42]:
one two three four key
0 0.467976 -0.038649 -0.295344 -1.824726 L
1 -0.358893 1.404453 0.704965 -0.200638 B
2 -0.501840 0.659254 -0.421691 -0.057688 G
3 0.204886 1.074134 1.388361 -0.982404 R
4 0.354628 -0.133116 0.283763 -0.837063 Q
... ... ... ... ... ..
9995 2.311896 -0.417070 -1.409599 -0.515821 L
9996 -0.479893 -0.650419 0.745152 -0.646038 E
9997 0.523331 0.787112 0.486066 1.093156 K
9998 -0.362559 0.598894 -1.843201 0.887292 G
9999 -0.096376 -1.012999 -0.657431 -0.573315 0
[10000 rows x 5 columns]
Dấu chấm ba ... cho biết rằng các dòng ở giữa DataFrame đã bị bỏ qua.
Nếu bạn chỉ muốn đọc một số dòng nhỏ (tránh đọc toàn bộ tệp), hãy chỉ định điều đó bằng cách sử dụng nrows:
In [43]: pd.read_csv("examples/ex6.csv", nrows=5)
Out[43]:
one two three four key
0 0.467976 -0.038649 -0.295344 -1.824726 L
1 -0.358893 1.404453 0.704965 -0.200638 B
2 -0.501840 0.659254 -0.421691 -0.057688 G
3 0.204886 1.074134 1.388361 -0.982404 R
4 0.354628 -0.133116 0.283763 -0.837063 QHoặc có thể dùng tham số sau
In [44]: chunker = pd.read_csv("examples/ex6.csv", chunksize=1000)
In [45]: type(chunker)
Out[45]: pandas.io.parsers.readers.TextFileReaderĐối tượng TextFileReader được trả về bởi pandas.read_csv cho phép bạn lặp qua các phần của tệp theo kích thước của chunksize. Ví dụ, chúng ta có thể lặp qua ex6.csv, tổng hợp đếm giá trị trong cột “key” như sau:
chunker = pd.read_csv("examples/ex6.csv", chunksize=1000)
tot = pd.Series([], dtype='int64')
for piece in chunker:
tot = tot.add(piece["key"].value_counts(), fill_value=0)
tot = tot.sort_values(ascending=False)Cho ra kết quả sau:
In [47]: tot[:10]
Out[47]:
key
E 368.0
X 364.0
L 346.0
O 343.0
Q 340.0
M 338.0
J 337.0
F 335.0
K 334.0
H 330.0
dtype: float64TextFileReader cũng được trang bị một phương thức là get_chunk, cho phép bạn đọc các phần với kích thước tùy ý.
🚀Writing Data to Text Format
Ở trên ta học qua cách đọc file, còn giờ ta sẽ tìm hiểu cách ghi file thành dạng delimited format (định dạng phân cách). Xem ví dụ về file CSV đọc ở trên:
In [48]: data = pd.read_csv("examples/ex5.csv")
In [49]: data
Out[49]:
something a b c d message
0 one 1 2 3.0 4 NaN
1 two 5 6 NaN 8 world
2 three 9 10 11.0 12 fooGiờ thử ghi ngược lại thành file CSV dưới một tên khác bằng hàm to_csv:
In [50]: data.to_csv("examples/out.csv")
In [51]: !cat examples/out.csv
,something,a,b,c,d,message
0,one,1,2,3.0,4,
1,two,5,6,,8,world
2,three,9,10,11.0,12,fooBạn có thể xem trước kết quả của hàm to_csv bằng tham số sys.stdout sẽ in kết quả ra màn hình console thay vì ghi ra file.
In [52]: import sys
In [53]: data.to_csv(sys.stdout, sep="|")
|something|a|b|c|d|message
0|one|1|2|3.0|4|
1|two|5|6||8|world
2|three|9|10|11.0|12|fooCác giá trị trống có thể thay thế bằng giá trị “sentinel”:
In [54]: data.to_csv(sys.stdout, na_rep="NULL")
,something,a,b,c,d,message
0,one,1,2,3.0,4,NULL
1,two,5,6,NULL,8,world
2,three,9,10,11.0,12,fooBạn thậm chỉ có thể bỏ đi phần tiêu đề cột và chỉ mục của dòng
In [55]: data.to_csv(sys.stdout, index=False, header=False)
one,1,2,3.0,4,
two,5,6,,8,world
three,9,10,11.0,12,fooHoặc chỉ ghi một phần của dữ liệu ra file:
In [56]: data.to_csv(sys.stdout, index=False, columns=["a", "b", "c"])
a,b,c
1,2,3.0
5,6,
9,10,11.0🚀Working with Other Delimited Formats
Mặc dù có thể tải lên hầu hết các dữ liệu tabular bằng hàm pandas.read_csv. Tuy nhiên đôi khi sẽ có một vài dòng “dị dạng” và khiến hàm .read_csv bị lỗi. Vậy nên ta có thể sử dụng một hàm khác được tích hợp trong module csvcó sẵn là csv.reader() để mở các file bất kỳ có dấu phân tách ký tự/
In [57]: !cat examples/ex7.csv
"a","b","c"
"1","2","3"
"1","2","3"
In [58]: import csv
In [59]: f = open("examples/ex7.csv")
In [60]: reader = csv.reader(f)Bản thân hàm reader()nhận bất kỳ file nào đã “mở” hoặc các đối tượng là file và đối tượng _csv.reader có thể duyệt qua (“iterating”) các giá trị của nó và trả về danh sách chứa từng giá trị.
In [61]: for line in reader:
....: print(line)
['a', 'b', 'c']
['1', '2', '3']
['1', '2', '3']
In [62]: f.close()Bạn để ý là nó bỏ qua các ký tự dấu nháy trong file gốc, đồng thời ghi nhận riêng từng dòng thành các danh sách độc lập.
Note
Wrangling with csv.reader
Và tất nhiên bạn hoàn toàn có thể “wrangling” nếu thấy cần thiết để chuyển về dạng mong muốn. Quay trở lại ví dụ nãy, lần này ta gom hết tụi nó về cùng danh sách:
In [63]: with open("examples/ex7.csv") as f:
....: lines = list(csv.reader(f))
Out[63]: [['a', 'b', 'c'], ['1', '2', '3'], ['1', '2', '3']]Sau đó ta tách các dòng riêng ra thành dòng tiêu đề và dòng dữ liệu
In [64]: header, values = lines[0], lines[1:]Chúng ta cũng có thể tạo ra dict bằng biểu dictionary comprehension kết hợp với biểu thức zip(*value) (Lưu ý là hạn chế dùng cách này vì nó ngốn rất nhiều bộ nhớ khi làm việc với file lớn). Cách này giúp đảo chiều dòng thành cột:
In [65]: data_dict = {h: v for h, v in zip(header, zip(*values))}
In [66]: data_dict
Out[66]: {'a': ('1', '1'), 'b': ('2', '2'), 'c': ('3', '3')}Bạn nào quên cách dùng dictionary comprehension có thể đọc lại bài này và công dụng của hàm zip ở đây
Note
Định nghĩa định dạng mới
Các tệp CSV có nhiều định dạng khác nhau. Để định nghĩa một định dạng mới với một ký tự phân cách khác biệt – ví dụ ta quy ước dấu bọc ký tự (“string quoting”), ký tự đánh dấu kết thúc dòng (“line terminator”), ta có thể định nghĩa một subclass của class csv.Dialect – official document !
class my_dialect(csv.Dialect):
lineterminator = "\n"
delimiter = ";"
quotechar = '"'
quoting = csv.QUOTE_MINIMALreader = csv.reader(f, dialect=my_dialect)Ta cũng có thể đưa “dialect parameter” vào hàm csv.reader ở trên mà không cần định nghĩa riêng:
reader = csv.reader(f, delimiter="|")Dialect parameter là tham số định nghĩa cách mà các trường dữ liệu được phân cách, cách mà các chuỗi được trích dẫn, và các quy tắc khác liên quan đến việc đọc hoặc ghi dữ liệu.

Đối với các files có ký tự phân cách phức tạp hơn hoặc ký tự phân cách có độ dài cố định, bạn sẽ không thể sử dụng modulecsv. Trong những trường hợp như vậy, bạn sẽ phải thực hiện phân tách dòng và làm sạch dữ liệu bằngsplit()hoặcre.split().May mắn là
pandas.read_csvcó khả năng thực hiện gần như mọi công việc bạn cần nếu bạn truyền các tham số cần thiết, do đó bạn hiếm khi cần phải parse files thủ công.
Ghi delimited files
Ta có thể ghi thủ công bằng hàm csv.writer. Hàm này chấp có các options và dialect tương tự như csv.reader
with open("mydata.csv", "w") as f:
writer = csv.writer(f, dialect=my_dialect)
writer.writerow(("one", "two", "three"))
writer.writerow(("1", "2", "3"))
writer.writerow(("4", "5", "6"))
writer.writerow(("7", "8", "9"))JSON Data
JSON (viết tắt của JavaScript Object Notation) là một trong những chuẩn định dạng file phổ biến nhất khi trao đổi dữ liệu giữa các giao thức trên mạng như phổ biến nhất là HTTP request hay giao thức giữa các ứng dụng với nhau.
Bản thân JSON linh hoạt hơn các dạng tabular text như CSV rất nhiều. Cho ví dụ sau:
obj = """
{"name": "Wes",
"cities_lived": ["Akron", "Nashville", "New York", "San Francisco"],
"pet": null,
"siblings": [{"name": "Scott", "age": 34, "hobbies": ["guitars", "soccer"]},
{"name": "Katie", "age": 42, "hobbies": ["diving", "art"]}]
}
"""
JSON gần giống Python code, với một số đặc điểm như sau:
- Định nghĩa giá trị rỗng là
null
- Không cho phép dấu phẩy ở cuối cùng của danh sách.
- Có các loại dữ liệu cơ bản như (dictionaries), arrays (lists), strings, numbers, Booleans, và nulls.
- Tất cả các khóa (keys) trong một đối tượng phải là chuỗi (strings).
Chuyển đổi sang Dictionaries
Có nhiều thư viện Python để đọc và ghi dữ liệu JSON. Trong đó thư việnjson đã được tích hợp vào bộ thư viện tiêu chuẩn của Python. Để chuyển đổi JSON (đang ở dạng string) thành dạng Python (dạng dictionary vì nó sát định dạng nhất), ta sử dụng json.loads:
In [68]: import json
In [69]: result = json.loads(obj)
In [70]: result
Out[70]:
{'name': 'Wes',
'cities_lived': ['Akron', 'Nashville', 'New York', 'San Francisco'],
'pet': None,
'siblings': [{'name': 'Scott',
'age': 34,
'hobbies': ['guitars', 'soccer']},
{'name': 'Katie', 'age': 42, 'hobbies': ['diving', 'art']}]}Còn json.dumps thì làm chiều ngược lại:
In [71]: asjson = json.dumps(result)
In [72]: asjson
Out[72]: '{"name": "Wes", "cities_lived": ["Akron", "Nashville", "New York", "San
Francisco"], "pet": null, "siblings": [{"name": "Scott", "age": 34, "hobbies": [
"guitars", "soccer"]}, {"name": "Katie", "age": 42, "hobbies": ["diving", "art"]}
]}'Chuyển đổi sang DataFrame
Cách bạn chuyển đổi một đối tượng JSON hoặc danh sách các đối tượng JSON thành một DataFrame hoặc một cấu trúc dữ liệu khác để phân tích sẽ phụ thuộc vào bạn.
Một cách thuận tiện là bạn có thể truyền một danh sách các dictionaries (trước đó là các đối tượng JSON) vào hàm tạo DataFrame (constructor) và chọn một phần của các trường dữ liệu:
In [73]: siblings = pd.DataFrame(result["siblings"], columns=["name", "age"])
In [74]: siblings
Out[74]:
name age
0 Scott 34
1 Katie 42Và pandas.read_json có thể tự động chuyển tập dữ liệu JSON thành được sắp xếp theo cách cụ thể thành một Series hoặc DataFrame.
In [75]: !cat examples/example.json
[{"a": 1, "b": 2, "c": 3},
{"a": 4, "b": 5, "c": 6},
{"a": 7, "b": 8, "c": 9}]
In [76]: data = pd.read_json("examples/example.json")
In [77]: data
Out[77]:
a b c
0 1 2 3
1 4 5 6
2 7 8 9Options mặc định của hàm sẽ mặc nhiên từng đối tượng trong mảng JSON là mỗi dòng trong bảng.
Nếu bạn muốn export data từ pandas sang JSON thì ta dùng to_json cho Series và DataFrame:
In [78]: data.to_json(sys.stdout)
{"a":{"0":1,"1":4,"2":7},"b":{"0":2,"1":5,"2":8},"c":{"0":3,"1":6,"2":9}}
In [79]: data.to_json(sys.stdout, orient="records")
[{"a":1,"b":2,"c":3},{"a":4,"b":5,"c":6},{"a":7,"b":8,"c":9}]XML và HTML: Web Scraping
Đây là phần khá thú vị và cũng không hẳn lúc nào cũng làm được, đặc biệt với các framework web development mới như hiện nay.
Thử dùng thư hàm read_html
Python có nhiều thư viện để đọc và ghi dữ liệu trong các định dạng HTML và XML phổ biến. Một số ví dụ bao gồm lxml, Beautiful Soup và html5lib. Trong khi lxml thường nhanh hơn so với các thư viện khác, các thư viện khác có thể xử lý tốt hơn các tệp HTML hoặc XML không đúng cú pháp.
Thư viện pandas có một hàm tích hợp, pandas.read_html, sử dụng tất cả các thư viện này để tự động phân tích các bảng từ tệp HTML thành đối tượng DataFrame. Để minh họa cách hoạt động này, mình đã tải xuống một tệp HTML từ Cơ quan Bảo hiểm Tiền gửi Liên bang Mỹ (US FDIC) hiển thị sự cố về ngân hàng.
Đầu tiên bạn cần cài đặt một số thư viện bổ sung được sử dụng bởi read_html:
conda install lxml beautifulsoup4 html5libNếu bạn không sử dụng conda, pip install lxml cũng có thể hoạt động.
Hàm pandas.read_html có nhiều tùy chọn, nhưng mặc định nó tìm kiếm và cố gắng phân tích tất cả dữ liệu bảng chứa trong thẻ <table>. Kết quả là một danh sách các đối tượng DataFrame:
In [80]: tables = pd.read_html("examples/fdic_failed_bank_list.html")
In [81]: len(tables)
Out[81]: 1
In [82]: failures = tables[0]
In [83]: failures.head()
Out[83]:
Bank Name City ST CERT
0 Allied Bank Mulberry AR 91 \
1 The Woodbury Banking Company Woodbury GA 11297
2 First CornerStone Bank King of Prussia PA 35312
3 Trust Company Bank Memphis TN 9956
4 North Milwaukee State Bank Milwaukee WI 20364
Acquiring Institution Closing Date Updated Date
0 Today's Bank September 23, 2016 November 17, 2016
1 United Bank August 19, 2016 November 17, 2016
2 First-Citizens Bank & Trust Company May 6, 2016 September 6, 2016
3 The Bank of Fayette County April 29, 2016 September 6, 2016
4 First-Citizens Bank & Trust Company March 11, 2016 June 16, 2016 Vì bảng “failures” có nhiều cột, pandas chèn một ký tự xuống dòng \.
Từ đây chúng ta có thể tiếp tục thực hiện một số thao tác làm sạch và phân tích dữ liệu, như tính toán số lượng sự cố ngân hàng theo năm:
In [84]: close_timestamps = pd.to_datetime(failures["Closing Date"])
In [85]: close_timestamps.dt.year.value_counts()
Out[85]:
Closing Date
2010 157
2009 140
2011 92
2012 51
2008 25
...
2004 4
2001 4
2007 3
2003 3
2000 2
Name: count, Length: 15, dtype: int64Parsing XML với lxml.objectify
XML là một định dạng dữ liệu có cấu trúc phổ biến khác hỗ trợ dữ liệu có cấu trúc, các dữ liệu lồng ghép với siêu dữ liệu (metadata).
Có thể bạn chưa biết nhưng nhiều sách online bản chất là large XML documents
Trước đó, ta đã biết tới hàm pandas.read_html, sử dụng lxml hoặc Beautiful Soup bên trong để phân tích dữ liệu từ HTML. XML và HTML có cấu trúc tương tự, nhưng XML chung chung hơn.
Cho dữ liệu sau: Trong nhiều năm, Cơ quan Vận tải Công cộng New York Metropolitan (MTA) đã xuất bản một loạt dữ liệu về dịch vụ xe buýt và tàu hỏa của mình trong định dạng XML.
Dưới đây, chúng ta sẽ xem qua một loạt các tệp XML. Mỗi dịch vụ tàu hoặc xe buýt có một tệp riêng (ví dụ như Performance_MNR.xml đại diện cho Đường sắt Metro-Phía Bắc) chứa dữ liệu hàng tháng dưới dạng một loạt các bản ghi XML như sau:
<INDICATOR>
<INDICATOR_SEQ>373889</INDICATOR_SEQ>
<PARENT_SEQ></PARENT_SEQ>
<AGENCY_NAME>Metro-North Railroad</AGENCY_NAME>
<INDICATOR_NAME>Escalator Availability</INDICATOR_NAME>
<DESCRIPTION>Percent of the time that escalators are operational
systemwide. The availability rate is based on physical observations performed
the morning of regular business days only. This is a new indicator the agency
began reporting in 2009.</DESCRIPTION>
<PERIOD_YEAR>2011</PERIOD_YEAR>
<PERIOD_MONTH>12</PERIOD_MONTH>
<CATEGORY>Service Indicators</CATEGORY>
<FREQUENCY>M</FREQUENCY>
<DESIRED_CHANGE>U</DESIRED_CHANGE>
<INDICATOR_UNIT>%</INDICATOR_UNIT>
<DECIMAL_PLACES>1</DECIMAL_PLACES>
<YTD_TARGET>97.00</YTD_TARGET>
<YTD_ACTUAL></YTD_ACTUAL>
<MONTHLY_TARGET>97.00</MONTHLY_TARGET>
<MONTHLY_ACTUAL></MONTHLY_ACTUAL>
</INDICATOR>Dùng lxml.objectify ta có thể parse file ày và lấy các phần liên quan tới các node gốc của file XML với hàm getroot:
In [86]: from lxml import objectify
In [87]: path = "datasets/mta_perf/Performance_MNR.xml"
In [88]: with open(path) as f:
....: parsed = objectify.parse(f)
In [89]: root = parsed.getroot()root.INDICATOR trả về một generator sản sinh từng phần tử XML <INDICATOR>. Đối với mỗi bản ghi, chúng ta có thể điền vào một dict các tag (như YTD_ACTUAL) với giá trị dữ liệu tương ứng (loại bỏ vài thẻ) bằng cách chạy đoạn mã sau:
data = []
skip_fields = ["PARENT_SEQ", "INDICATOR_SEQ",
"DESIRED_CHANGE", "DECIMAL_PLACES"]
for elt in root.INDICATOR:
el_data = {}
for child in elt.getchildren():
if child.tag in skip_fields:
continue
el_data[child.tag] = child.pyval
data.append(el_data)Cuối cùng ta parse danh sách dictionaries này thành một DataFrame:
In [91]: perf = pd.DataFrame(data)
In [92]: perf.head()
Out[92]:
AGENCY_NAME INDICATOR_NAME
0 Metro-North Railroad On-Time Performance (West of Hudson) \
1 Metro-North Railroad On-Time Performance (West of Hudson)
2 Metro-North Railroad On-Time Performance (West of Hudson)
3 Metro-North Railroad On-Time Performance (West of Hudson)
4 Metro-North Railroad On-Time Performance (West of Hudson)
DESCRIPTION
0 Percent of commuter trains that arrive at their destinations within 5 m... \
1 Percent of commuter trains that arrive at their destinations within 5 m...
2 Percent of commuter trains that arrive at their destinations within 5 m...
3 Percent of commuter trains that arrive at their destinations within 5 m...
4 Percent of commuter trains that arrive at their destinations within 5 m...
PERIOD_YEAR PERIOD_MONTH CATEGORY FREQUENCY INDICATOR_UNIT
0 2008 1 Service Indicators M % \
1 2008 2 Service Indicators M %
2 2008 3 Service Indicators M %
3 2008 4 Service Indicators M %
4 2008 5 Service Indicators M %
YTD_TARGET YTD_ACTUAL MONTHLY_TARGET MONTHLY_ACTUAL
0 95.0 96.9 95.0 96.9
1 95.0 96.0 95.0 95.0
2 95.0 96.3 95.0 96.9
3 95.0 96.8 95.0 98.3
4 95.0 96.6 95.0 95.8 Hài cái làm nãy giờ nhưng hàm pandas.read_xml có thể xử lý gọn nhẹ:
In [93]: perf2 = pd.read_xml(path)
In [94]: perf2.head()
Out[94]:
INDICATOR_SEQ PARENT_SEQ AGENCY_NAME
0 28445 NaN Metro-North Railroad \
1 28445 NaN Metro-North Railroad
2 28445 NaN Metro-North Railroad
3 28445 NaN Metro-North Railroad
4 28445 NaN Metro-North Railroad
INDICATOR_NAME
0 On-Time Performance (West of Hudson) \
1 On-Time Performance (West of Hudson)
2 On-Time Performance (West of Hudson)
3 On-Time Performance (West of Hudson)
4 On-Time Performance (West of Hudson)
DESCRIPTION
0 Percent of commuter trains that arrive at their destinations within 5 m... \
1 Percent of commuter trains that arrive at their destinations within 5 m...
2 Percent of commuter trains that arrive at their destinations within 5 m...
3 Percent of commuter trains that arrive at their destinations within 5 m...
4 Percent of commuter trains that arrive at their destinations within 5 m...
PERIOD_YEAR PERIOD_MONTH CATEGORY FREQUENCY DESIRED_CHANGE
0 2008 1 Service Indicators M U \
1 2008 2 Service Indicators M U
2 2008 3 Service Indicators M U
3 2008 4 Service Indicators M U
4 2008 5 Service Indicators M U
INDICATOR_UNIT DECIMAL_PLACES YTD_TARGET YTD_ACTUAL MONTHLY_TARGET
0 % 1 95.00 96.90 95.00 \
1 % 1 95.00 96.00 95.00
2 % 1 95.00 96.30 95.00
3 % 1 95.00 96.80 95.00
4 % 1 95.00 96.60 95.00
MONTHLY_ACTUAL
0 96.90
1 95.00
2 96.90
3 98.30
4 95.80 Tất nhiên với các cấu trúc tài liệu XML phức tạp thì ta nên đọc tài liệu về hàm pandas.read_xml để biết cách selec hay filter những phần mong muốn.
🍀Tổng kết
Xong phần đầu tiên này ta có được khái quát cách đọc và ghi nhưng định dạng dữ liệu cơ bản. Ở phần sau ta sẽ tiếp tục tìm hiểu về cách truy cập và đọc những loại dữ liệu thường thấy hơn như Database, API hay Excel (bản chất là Binary Format). Hy vọng mọi người vẫn đón xem và ủng hộ
á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




Thank you for this great post! I found the information you provided to be very helpful and well-explained. Your writing is engaging and easy to follow, making it a pleasure to read. I appreciate your hard work and dedication to creating valuable content. Keep it up!”