🍀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:

  1. Các kiến thức cơ bản về dữ liệu và các thuật ngữ;
  2. Làm quen với Cấu trúc dữ liệu cơ bản;
  3. Cách sử dụng Hàm và đọc một số file dữ liệu phổ biến;
  4. 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
  5. 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:

Text and binary data loading functions in pandas

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ư:

  1. 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.
  2. 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.
  3. 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
  4. 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.
  5. 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à 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_csv có khoảng 50 tham số).

Note 💡

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,foo

Lư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 %pycat để thay !cat

Note
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     foo

Gỉ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  12

Trong 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.100491

Gặ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.100491

Trong 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     foo

Xử 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ư NANULL:

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     foo

Mộ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    False

Trong 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     foo

Nế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    False

Ta 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
Some pandas.read_csv function arguments

🚀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 = 10

Xong 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   Q

Hoặ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: float64

TextFileReader 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     foo

Giờ 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,foo

Bạ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|foo

Cá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,foo

Bạ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,foo

Hoặ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.Dialectofficial document !

class my_dialect(csv.Dialect):
    lineterminator = "\n"
    delimiter = ";"
    quotechar = '"'
    quoting = csv.QUOTE_MINIMAL
reader = 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.

CSV dialect options


Đố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 module csv. 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ằng split() hoặc re.split().

May mắn là pandas.read_csv có 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   42

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  9

Options 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 html5lib

Nế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: int64

Parsing 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