Để thực hành viết code theo tài liệu này cũng như để làm các bài tập, sử dụng Google Colab notebook sau đây:
https://colab.research.google.com/drive/1p1uup8o_wOFoqLEzTPzjhJmTNnIvs3IV?usp=sharing
dict)Là loại dữ liệu có sẵn trong Python và là tập hợp các cặp key-value (a collection of key-value pairs). Từ Python 3.7 trở đi thì dictionary trong Python là 1 ordered object, tức là ta có thể truy cập các phần tử trong dictionary thông qua index.
my_dict = {'key1': 'value1', 'key2': 'value2'}
Dict là cấu trúc dữ liệu loại mutable
Các Keys phải là duy nhất (unique, không được trùng lặp nhau).
Keys phải là immutable. VD: strings, numbers, tuples, …
Các Values thì có thể là bất kỳ kiểu dữ liệu nào.
Truy cập các key và value trong dict:
source_folders = {
"dev": "dwh-dev",
"stg": "dwh-stg",
"prd": "datawarehouse",
}
# Dùng ngoặc vuông
print(source_folders["prd"])## datawarehouse
# Dùng các method keys() và .values() để truy cập các key và value
for i in source_folders.keys():
print(i)## dev
## stg
## prd
## dwh-dev
## dwh-stg
## datawarehouse
# Dùng method .items() để trả ra các cặp key-value trong dict
for k, v in source_folders.items():
print(f"key is: {k} and value is {v}")## key is: dev and value is dwh-dev
## key is: stg and value is dwh-stg
## key is: prd and value is datawarehouse
## dwh-stg
# Truy cập value ứng với 1 key nhất định, nếu key không tồn tại thì trả về 1 giá trị default
print(source_folders.get("stggg", "key not exist"))## key not existThêm và xoá các cặp key-value trong dict
# Thêm cặp key-value bằng cách dùng ngoặc vuông
source_folders["demo"] = "dwh-demo"
print(source_folders)## {'dev': 'dwh-dev', 'stg': 'dwh-stg', 'prd': 'datawarehouse', 'demo': 'dwh-demo'}
# Thêm cặp key-value bằng .update() method
source_folders.update({"demo2":"dwh-demo2", "demo3":"dwh-demo3"})
print(source_folders)## {'dev': 'dwh-dev', 'stg': 'dwh-stg', 'prd': 'datawarehouse', 'demo': 'dwh-demo', 'demo2': 'dwh-demo2', 'demo3': 'dwh-demo3'}
## {'dev': 'dwh-dev', 'stg': 'dwh-stg', 'prd': 'datawarehouse', 'demo2': 'dwh-demo2', 'demo3': 'dwh-demo3'}
# Xoá cặp key-value bằng method .popitem()
removed_item = source_folders.popitem() # xoá và trả ra giá trị của cặp key-value cuối cùng dưới dạng tuple
print(source_folders)## {'dev': 'dwh-dev', 'stg': 'dwh-stg', 'prd': 'datawarehouse', 'demo2': 'dwh-demo2'}
## Removed item: ('demo3', 'dwh-demo3')
# Xoá cặp key-value bằng method .pop()
removed_value = source_folders.pop("demo2") # xoá cặp key-value dựa trên key và trả ra value của cặp bị xoá
print(source_folders)## {'dev': 'dwh-dev', 'stg': 'dwh-stg', 'prd': 'datawarehouse'}
## Removed value: dwh-demo2dict comprehension: đây là một cách ngắn gọn để tạo 1 dict mới trong Python thay vì phải viết lệnh sử dụng vòng lặp thông thường
Syntax:
{key_expression: value_expression for item in iterable}
Ví dụ:
# create a new dict called "source_folders_test" from the "source_folders" dict
source_folders_test = {k: f"{v}_test" for (k, v) in source_folders.items()}
print(source_folders_test)## {'dev': 'dwh-dev_test', 'stg': 'dwh-stg_test', 'prd': 'datawarehouse_test'}Python dict thường được sử dụng để thể hiện dữ liệu khi mà key và value có mối quan hệ với nhau, thường dùng làm dạng lookups dựa trên các keys.
Ví dụ: Ta cần viết code để load dữ liệu từ file .txt thành 1 pandas dataframe. Code này sẽ cần chạy được trên 3 môi trường khác nhau, bao gồm “development”, “staging”, và “production”. Ở mỗi môi trường thì folder chứa file .txt cần load sẽ khác nhau, ta phải viết code sao cho code chạy được trên cả 3 môi trường thay vì phải sửa code cho mỗi môi trường.
import pandas as pd
# Khai báo biến thể hiện môi trường chạy code: dev, stg (staging), prd (production)
# trong thực tế sẽ có 1 cơ chế để truyền giá trị vào cho biến ENV tuỳ vào môi trường chạy code,
# còn ở ví dụ này, ta hard-code gán luôn giá trị cho biến ENV để chạy
# Truy cập google colab notebook theo link ở phần đầu của lesson này để
# chạy thử code với các giá trị ENV khác nhau
ENV = "prd"
assert ENV in ("dev", "stg", "prd") # check để đảm bảo ENV chỉ được nhận các giá trị "dev", "stg", "prd"
# Khai báo dict để lưu tên của folder chứa file tuỳ vào môi trường
source_folders = {
"dev": "dwh-dev",
"stg": "dwh-stg",
"prd": "datawarehouse",
}
# Load dữ liệu tuỳ thuộc vào môi trường
# Tuỳ vào giá trị của biến ENV mà giá trị biến file_url sẽ thay đổi theo
main_url = "https://raw.githubusercontent.com/tuanphan92/datatute-datasets/main/data-engineering-booster/sample_file/"
file_url = f"{main_url}{source_folders[ENV]}/{ENV}_sample_file.txt"
df = pd.read_csv(
file_url,
sep=","
)
print(df)## name age
## 0 Tuan 34
## 1 Tu 24
## 2 Tung 14
list)Là loại dữ liệu có sẵn trong Python và là tập hợp của các phần tử. Mỗi phần tử trong list có thể thuộc bất kỳ một kiểu dữ liệu nào.
## [1, 2, 3, 'four', 5.0]
List là cấu trúc dữ liệu loại mutable
Các phần tử trong list có thể thuộc những kiểu dữ liệu khác nhau
List là 1 ordered object (các phần tử trong list có thứ tự và có thể được truy cập thông qua index)
Truy cập các phần tử trong list bằng index (indexing and slicing)
print(f"""
Luôn nhớ rằng trong python thì index bắt đầu bằng 0 thay vì 1
Đây là phần tử ở vị trí có index=0: {my_list[0]}
Đây là phần tử ở vị trí có index=3: {my_list[3]}
Đây là các phần tử ở vị trí có index=1 và index=2: {my_list[1:3]}
Đây là các phần tử ở vị trí có index=1 cho đến hết: {my_list[1:]}
"""
)##
## Luôn nhớ rằng trong python thì index bắt đầu bằng 0 thay vì 1
## Đây là phần tử ở vị trí có index=0: 1
## Đây là phần tử ở vị trí có index=3: four
## Đây là các phần tử ở vị trí có index=1 và index=2: [2, 3]
## Đây là các phần tử ở vị trí có index=1 cho đến hết: [2, 3, 'four', 5.0]Thêm phần tử vào list và xoá phần tử khỏi list
## [1, 2, 3, 'four', 5.0, 'six']
# thêm phần tử vào 1 vị trí nhất định trong list
my_list.insert(4, "number four") # thêm giá trị "number four" vào vị trí index=4 (trước giá trị "5.0")
print(my_list)## [1, 2, 3, 'four', 'number four', 5.0, 'six']
# xoá phần tử khỏi list dựa theo giá trị (nếu có nhiều phần tử cùng giá trị thì chỉ xoá phần tử đầu tiên từ trái sang)
my_list.remove("number four")
print(my_list)## [1, 2, 3, 'four', 5.0, 'six']
# xoá phần tử khỏi list dựa theo index và trả ra giá trị của phần tử bị xoá
removed_element = my_list.pop(5) # xoá giá trị có index=5
print(f"giá trị bị xoá là: {removed_element}")## giá trị bị xoá là: six
## [1, 2, 3, 'four', 5.0]Mở rộng list
## [1, 2, 3, 'four', 5.0, 1, 2, 3, 4, 5]Copy list
## [1, 2, 3, 'four', 5.0, 1, 2, 3, 4, 5]Count, Sort và Reverse list
# count số lần xuất hiện của 1 giá trị trong list
print(f"số lần xuất hiện của phần tử có giá trị = 3 là: {my_list_2.count(3)}")## số lần xuất hiện của phần tử có giá trị = 3 là: 2
# lọc ra các phần tử thuộc kiểu dữ liệu integer trước khi sort list
my_list_int = list(filter(lambda x: isinstance(x, int), my_list_2))
print(f"list gồm các phần tử loại integer: {my_list_int}")## list gồm các phần tử loại integer: [1, 2, 3, 1, 2, 3, 4, 5]
# reverse list
my_list_int.reverse()
print(f"list gồm các phần tử loại integer sau khi reverse: {my_list_int}")## list gồm các phần tử loại integer sau khi reverse: [5, 4, 3, 2, 1, 3, 2, 1]
# sort list gồm các phần tử loại integer
my_list_int.sort()
print(f"list gồm các phần tử loại integer đã xếp thứ tự: {my_list_int}")## list gồm các phần tử loại integer đã xếp thứ tự: [1, 1, 2, 2, 3, 3, 4, 5]List comprehension
## [1, 1, 4, 4, 9, 9, 16, 25]Chuyển list thành dict
# Trong thực tế, thường ta sẽ có 1 list các keys và 1 list các values
# Ta cần gộp chúng lại với nhau thành từng cặp key-value để tạo dict
# Cách dễ nhất là dùng hàm zip()
keys = ["key1", "key2", "key3", "key4"]
values = [
"value1",
("value2 item1", "value2 item2", "value2 item3"),
"value3",
"value4",
]
my_dict = dict(zip(keys, values))
print(my_dict)## {'key1': 'value1', 'key2': ('value2 item1', 'value2 item2', 'value2 item3'), 'key3': 'value3', 'key4': 'value4'}List rất phù hợp để lưu trữ, biến đổi, sắp xếp một tập hợp các giá trị.
Ví dụ: dùng dict và list khi làm việc với json files
# import các thư viện cần thiết
import json, requests
# load dữ liệu từ json file
url = 'https://raw.githubusercontent.com/tuanphan92/datatute-datasets/main/data-engineering-booster/sample_file/sample.json'
response = requests.get(url)
print(f"""Dưới đây là text lấy từ sample json file:
{response.text}""")## Dưới đây là text lấy từ sample json file:
## [
## {"name": "Nguyen Van A", "age": 30, "city": "Hanoi"},
## {"name": "Le Thi B", "age": 25, "city": "Da Nang"},
## {"name": "Dang Van C", "age": 35, "city": "HCM City"}
## ]
# chuyển text thành python object
# ta thấy python object này là 1 list gồm các dict
data = json.loads(response.text)
print(f"Biến data là 1 object có type là: {type(data)}")## Biến data là 1 object có type là: <class 'list'>
## Type của các phần tử trong biến data:
## {'name': 'Nguyen Van A', 'age': 30, 'city': 'Hanoi'}: <class 'dict'>
## {'name': 'Le Thi B', 'age': 25, 'city': 'Da Nang'}: <class 'dict'>
## {'name': 'Dang Van C', 'age': 35, 'city': 'HCM City'}: <class 'dict'>
tuple)Là loại dữ liệu có sẵn trong Python và là 1 tập hợp các phần tử. Tuple khá giống với list nhưng khác ở chỗ tuple là immutable, nghĩa là ta không thể thay đổi các phần tử bên trong tuple như với list được.
## (1, 2, 3, 'a', 'b')
Tuple là cấu trúc dữ liệu loại mutable
Các phần tử trong tuple có thể thuộc những kiểu dữ liệu khác nhau
Tuple là 1 ordered object (các phần tử trong tuple có thứ tự và có thể được truy cập thông qua index)
Truy cập các phần tử trong tuple bằng index (indexing and slicing)
print(f"""
Luôn nhớ rằng trong python thì index bắt đầu bằng 0 thay vì 1
Đây là phần tử ở vị trí có index=0: {my_tuple[0]}
Đây là phần tử ở vị trí có index=3: {my_tuple[3]}
Đây là các phần tử ở vị trí có index=1 và index=2: {my_tuple[1:3]}
Đây là các phần tử ở vị trí có index=1 cho đến hết: {my_tuple[1:]}
"""
)##
## Luôn nhớ rằng trong python thì index bắt đầu bằng 0 thay vì 1
## Đây là phần tử ở vị trí có index=0: 1
## Đây là phần tử ở vị trí có index=3: a
## Đây là các phần tử ở vị trí có index=1 và index=2: (2, 3)
## Đây là các phần tử ở vị trí có index=1 cho đến hết: (2, 3, 'a', 'b')Mở rộng tuple
# mở rộng tuple bằng cách ghép thêm 1 tuple khác vào
# vì tuple là immutable nên kết quả của hành động này là 1 tuple mới được tạo ra
print(f"id trên memory của my_tuple: {id(my_tuple)}")## id trên memory của my_tuple: 5627946352
# tạo tuple mới (nhưng vẫn gán với biến my_tuple) bằng cách ghép my_tuple với 1 tuple khác
my_tuple = my_tuple + ("c", "d")
print(f"""
các phần tử của my_tuple mới: {my_tuple}
id trên memory của my_tuple mới: {id(my_tuple)}
Ta thấy rằng id này đã thay đổi, chứng tỏ rằng 1 tuple mới đã được tạo ra
""")##
## các phần tử của my_tuple mới: (1, 2, 3, 'a', 'b', 'c', 'd')
## id trên memory của my_tuple mới: 5627444864
## Ta thấy rằng id này đã thay đổi, chứng tỏ rằng 1 tuple mới đã được tạo raCount
## Số lượng phần tử trong my_tuple là: 7Tuple comprehension
## ('1_new', '2_new', '3_new', 'a_new', 'b_new', 'c_new', 'd_new')Chuyển tuple thành dict
list_of_tuples = [("key_1", 13), ("key_2", [1, 2, 3, 4]), ("key_3", 1150)]
dict_from_tuple = dict(list_of_tuples)
print(dict_from_tuple)## {'key_1': 13, 'key_2': [1, 2, 3, 4], 'key_3': 1150}
# lưu ý rằng trong trường hợp có list nằm bên trong tuple:
# ta không thể thay thế list bằng 1 object khác
# vì tuple là immutable object.
# Câu lệnh sau sẽ báo lỗi
try:
list_of_tuples[1][1] = "this is a string"
except Exception as e:
print(f"An error occurred: {e}")## An error occurred: 'tuple' object does not support item assignment
# nhưng ta có thể thay đổi các phần tử bên trong list đó
list_of_tuples[1][1][0] = "this is a string"
dict_from_tuple = dict(list_of_tuples)
print(dict_from_tuple)## {'key_1': 13, 'key_2': ['this is a string', 2, 3, 4], 'key_3': 1150}Ta thường tạo các hàm và khi hàm trả ra nhiều hơn 1 giá trị thì các giá trị đó sẽ được gộp vào thành 1 tuple. Ví dụ tạo hàm xử lý dữ liệu và trả ra các giá trị cần thiết:
# Bước 1: tạo hàm xử lý dữ lệu và trả ra các giá trị cần thiết dưới dạng tuple
import pandas as pd
def check_null_in_column(input_df: pd.DataFrame, col_name: str) -> tuple:
# count number of null records
number_of_null_records = df[col_name].isnull().sum()
# calculate percentage of null records
pct_null_records = round((number_of_null_records * 100) / len(df), 2)
return number_of_null_records, pct_null_records
# Hàm bên trên trả ra giá trị dưới dạng 1 tuple gồm 2 phần tử.
# Ta kiểm chứng điều này như sau:
print(check_null_in_column.__annotations__["return"])## <class 'tuple'>
# Bước 2: sử dụng hàm đã tạo
# Tạo 1 pandas DataFrame
data_dict = {
"cust_id": [1, 2, 3, 4, 5, 6, 7],
"cust_name": ["A", None, "C", "D", "E", None, None]
}
df = pd.DataFrame(data_dict)
# Gọi hàm check_null_in_column và gán giá trị của từng phần tử
# trong tuple trả ra từ hàm cho 2 biến
# Cách gán giá trị này gọi là tuple unpacking
number_of_null_records, pct_null_records = check_null_in_column(df, "cust_name")
# In giá trị của 2 biến vừa tạo để kiểm tra
print(f"Number of null records is: {number_of_null_records}")## Number of null records is: 3
## Percentage of null records is: 42.86%
set)Là loại dữ liệu có sẵn trong Python và là tập hợp của các phần tử không được sắp xếp thứ tự. Mỗi phần tử trong set có thể thuộc bất kỳ một kiểu dữ liệu nào.
my_set = {2, 3, 1, 5.0, "four"}
# or
another_set = set([2, 3, 5.0, "four", "one hundred"])
print(my_set)## {1, 2, 3, 5.0, 'four'}
## {2, 3, 5.0, 'one hundred', 'four'}
Set là cấu trúc dữ liệu loại mutable
Các phần tử trong set có thể thuộc những kiểu dữ liệu khác nhau
Set là 1 unordered object (các phần tử trong set không có thứ tự), vì vậy ta không thể truy cập các phần tử trong set bằng index như với list và tuple.
Kiểm tra phần tử có tồn tại trong set không
## 1
## 2
## 3
## 5.0
## four
## FalseThêm và xoá phần tử khỏi set
## {1, 2, 3, 5.0, 'seven', 'four'}
# xoá phần tử khỏi set, không báo lỗi nếu không tìm thấy phần tử
my_set.discard("number seven")
print(my_set) # my_set không có gì thay đổi## {1, 2, 3, 5.0, 'seven', 'four'}
## {1, 2, 3, 5.0, 'four'}
# xoá phần tử khỏi set, báo lỗi nếu không tìm thấy phần tử
try:
my_set.remove("number seven")
except Exception as e:
print(f"An error occurred: {e}")## An error occurred: 'number seven'Mở rộng set
# mở rộng set bằng cách đưa thêm 1 set khác vào set hiện tại
# method này được thực hiện "in place", tức là set hiện tại sẽ bị thay đổi
additional_set = ("one hundred", 100, 200)
my_set.update(additional_set)
print(my_set)## {1, 2, 3, 5.0, 200, 100, 'one hundred', 'four'}
# mở rộng set bằng cách union với 1 set khác
# method này tạo ra 1 set mới thay vì thay đổi set hiện tại
additional_set = ("two hundred", 100, 200)
my_set = my_set.union(additional_set)
print(my_set)## {1, 2, 3, 100, 5.0, 'one hundred', 200, 'two hundred', 'four'}Set comprehension
# lọc ra các phần tử thuộc kiểu dữ liệu integer
my_set_int = set(filter(lambda x: isinstance(x, int), my_set))
print(f"set gồm các phần tử loại integer: {my_set_int}")## set gồm các phần tử loại integer: {1, 2, 3, 100, 200}
## {40000, 1, 4, 9, 10000}So sánh các set với nhau
# tìm các phần tử chung của cả 2 set
another_set_int = {3, 100, 50, 10}
intersection_set = my_set_int.intersection(another_set_int)
print(intersection_set)## {3, 100}
# tìm các phần tử khác nhau giữa 2 set
difference_set = my_set_int.symmetric_difference(another_set_int)
print(difference_set)## {1, 2, 200, 10, 50}
# tìm các phần tử ở set bên trái mà không có ở set bên phải
difference_set = my_set_int.difference(another_set_int)
print(difference_set)## {200, 1, 2}Lọc lấy các giá trị duy nhất và kiểm tra giá trị được nhập vào hoặc được truyền vào
data_dict = {
"category_code": ["C1", "C2", "C3", "C4", "C5", None, "C7"],
"product_name": ["san pham A", None, "san pham C", "san pham D", "san pham E", None, None]
}
# lấy ra các giá trị not None và unique trong cust_name
unique_category_codes = set(filter(lambda x: x is not None, data_dict["category_code"]))
print(unique_category_codes)## {'C1', 'C5', 'C3', 'C7', 'C2', 'C4'}
# kiểm tra giá trị nhập vào có nằm trong danh sách các category_code hay không
# hãy thử thay giá trị "C7" dưới đây bằng 1 category code không trong danh sách, 1 AssertionError sẽ được raise
input = ("C7", "san pham XYZ")
assert input[0] in unique_category_codes, "The inputted category code is not in category code list"So sánh các cặp giá trị (ví dụ như so sánh sự thay đổi schema của 1 bảng)
# schema của bảng (set gồm các tuple)
schema_original = {
("column 1", "integer", True), # column name, datatype, is nullable or not
("column 2", "string", True),
("column 3", "string", True)
}
# schema mới của bảng
schema_new = {
("column 1", "integer", True),
("column 2", "string", False),
("column 3", "string", True),
("column 4", "string", True)
}
# print ra khác biệt về schema
schema_difference = schema_original.symmetric_difference(schema_new)
for i in schema_difference:
print(i)## ('column 2', 'string', True)
## ('column 4', 'string', True)
## ('column 2', 'string', False)| Mutable | Ordered | Indexing | Allow duplicate data | |
|---|---|---|---|---|
| dict | yes | yes | yes | no duplicate key |
| list | yes | yes | yes | yes |
| tuple | no | yes | yes | yes |
| set | yes | no | no | no |
## 200
## 1
## 306
# convert thành 1 dict
dict_data = [("key1", "value1"), ("key2", "value2")]
print(
dict(dict_data)
)## {'key1': 'value1', 'key2': 'value2'}
## [1, 2, 3, 100, 5.0, 'one hundred', 200, 'two hundred', 'four']
## (1, 2, 3, 100, 5.0, 'one hundred', 200, 'two hundred', 'four')
## {1, 2, 3, 4, 5.0, 'four'}
##
## a set of integer: {1, 2, 3, 100, 200}
## a sorted set of integer: [1, 2, 3, 100, 200]
Hàm map(function, iterables) hoạt động như sau:
Áp dụng function lên tất cả các phần tử của iterable (vd: dict, list, tuple, set)
Trả ra kết quả là 1 iterator. -> Vì vậy ta cần dùng 1 conversion function như dict(), list(), tuple(), hay set() để chuyển kết quả về thành 1 python collection để dễ sử dụng tiếp
print(f"""
a list of integer: {my_list_int}
the list after multiplying each item by 2: {list(map(lambda x: x*2, my_list_int))}
"""
)##
## a list of integer: [1, 1, 2, 2, 3, 3, 4, 5]
## the list after multiplying each item by 2: [2, 2, 4, 4, 6, 6, 8, 10]
Ta thấy ở ví dụ trên khi sử dụng hàm map(), ta không đưa vào map() tên 1 function cụ thể mà lại đưa vào 1 lambda function (lambda x: x*2).
Lambda function trong Python là cách để tạo ra nhanh một hàm ngắn gọn (thường được thể hiện chỉ trong 1 dòng code duy nhất) để thực hiện một hành động đơn giản nào đó. Ta thường dùng lambda function bên trong 1 hàm khác, như trường hợp hàm map() bên trên chẳng hạn.
Đọc thêm về lambda function: https://www.w3schools.com/python/python_lambda.asp
Hàm filter(function, iterable) dùng để lọc ra các phần
tử trong iterable dựa trên điều kiện lọc là function
và hoạt động như sau:
Áp dụng function (điều kiện lọc) lên tất cả các phần tử
của iterable và trả ra kết quả là true hoặc
false
Trả ra kết quả là 1 iterator bao gồm tất cả các phần tử của
iterable mà function trả ra kết quả là
true. -> Vì vậy ta cần dùng 1 conversion function như
dict(), list(), tuple(), hay set() để chuyển kết quả về thành 1 python
collection để dễ sử dụng tiếp
print(f"""
a list: {my_list}
create a set of integer from the above list: {set(filter(lambda x: isinstance(x, int), my_list))}
"""
)##
## a list: [1, 2, 3, 'four', 5.0, 1, 2, 3, 4, 5]
## create a set of integer from the above list: {1, 2, 3, 4, 5}
# lọc ra các phần tử > 5 trong tuple
int_tuple = (2, 8, 3, 7, 6, 5)
filtered_tuple = tuple(filter(lambda x: x > 5, int_tuple))
print(filtered_tuple)## (8, 7, 6)
enumerate() là một hàm hữu ích khi ta muốn duyệt qua từng phần tử của một collection (iterate over a collection) và đồng thời theo dõi được index của phần tử đang được duyệt. Hàm này trả ra kết quả là 1 object gồm các tuples, mỗi tuple chứa index và phần tử tại index đó của collection.
## [1, 2, 3, 'four', 5.0, 1, 2, 3, 4, 5]
## (0, 1)
## (1, 2)
## (2, 3)
## (3, 'four')
## (4, 5.0)
## (5, 1)
## (6, 2)
## (7, 3)
## (8, 4)
## (9, 5)
## Index 0: 1
## Index 1: 2
## Index 2: 3
## Index 3: four
## Index 4: 5.0
## Index 5: 1
## Index 6: 2
## Index 7: 3
## Index 8: 4
## Index 9: 5
Trong thực tế, ta thường phải tự tạo một số dataframe và code một vài đoạn code nhỏ để kiểm tra thử xem logic xử lý có chính xác hay không trước khi áp dụng logic đó vào dữ liệu thực tế. Ngoài ra, trong một số trường hợp, ta cũng có thể chọn lưu dữ liệu dưới dạng code và khi cần thì chuyển code đó thành dataframe thay vì phải tạo hẳn 1 bảng dữ liệu vật lý trong database. Vì vậy, việc biết cách tự tạo dữ liệu và tự tạo ra các dataframe là cần thiết đối với một Data Engineer. Cách tốt nhất để làm việc này là sử dụng các python collections đã nêu ở phần đầu của bài học này.
Có 3 cách phổ biến đã tạo 1 dataframe từ python collection như dưới đây. Các ví dụ dưới đây là cho pandas dataframe, nhưng logic lưu trữ dữ liệu và tạo dataframe thì áp dụng được tương tự với spark dataframe.
# Lưu dữ liệu cho dataframe dưới dạng 1 list gồm các tuple
# Mỗi tuple lưu dữ liệu cho 1 dòng của dataframe
data = [
('Tuan', 25, 'Hanoi'),
('Hung', 30, 'HCM'),
('Linh', 35, 'Hai Phong')
]
# Tạo dataframe sử dụng dữ liệu đã khai báo bên trên và cung cấp tên các cột
df = pd.DataFrame(data, columns=['Name', 'Age', 'City'])
df## Name Age City
## 0 Tuan 25 Hanoi
## 1 Hung 30 HCM
## 2 Linh 35 Hai Phong
# Lưu dữ liệu cho dataframe dưới dạng 1 dict
# Key là tên các cột, value của mỗi key là một list chứa dữ liệu cho cột đó
data = {
"Name": ["Tuan", "Hung", "Linh"],
"Age": [25, 30, 35],
"City": ["Hanoi", "HCM", "Hai Phong"]
}
# Tạo dataframe sử dụng dữ liệu đã khai báo bên trên
# Trong trường hợp này ta không cần cung cấp tên các cột nữa
df = pd.DataFrame(data)
df## Name Age City
## 0 Tuan 25 Hanoi
## 1 Hung 30 HCM
## 2 Linh 35 Hai Phong
# Lưu dữ liệu cho dataframe dưới dạng 1 list gồm các dict
# Mỗi dict lưu dữ liệu cho 1 dòng của dataframe
data = [
{"Name": "Tuan", "Age": 25, "City": "Hanoi"},
{"Name": "Hung", "Age": 30, "City": "HCM"},
{"Name": "Linh", "Age": 35, "City": "Hai Phong"}
]
# Tạo dataframe sử dụng dữ liệu đã khai báo bên trên
# Trong trường hợp này ta không cần cung cấp tên các cột nữa
df = pd.DataFrame(data)
df## Name Age City
## 0 Tuan 25 Hanoi
## 1 Hung 30 HCM
## 2 Linh 35 Hai Phong
Tuỳ vào tình huống cụ thể mà ta chọn cách khai báo dữ liệu, khai báo tên cột để tạo dataframe sao cho phù hợp. Nếu chỉ để tạo 1 dataframe nhanh, gọn nhằm kiểm tra thử logic xử lý dữ liệu thì dùng cách nào cũng được, tuy nhiên ta nên lưu ý một số điểm sau đây khi triển khai code vào thực tế khi xây dựng các data pipeline:
Để code rõ ràng, tường minh hơn và cũng để hạn chế sai sót không đáng có, ta nên khai báo schema cho dataframe. Nếu không khai báo schema (tên cột, loại dữ liệu) thì pandas sẽ tự đoán kiểu dữ liệu cho từng cột dựa (infer the data type) trên dữ liệu mà ta cung cấp. Việc ta khai báo schema sẽ hạn chế sai sót và khiến thông tin của dataframe rõ ràng, tường minh hơn. Ví dụ ta có tình huống khai báo sai giá trị của một dòng trong cột “Age” như dưới đây (do nhập liệu hoặc copy paste sai quên sửa lại chẳng hạn) khiên cột “Age” khi được tạo ra bị nhận kiểu dữ liệu là “float” thay vì “integer”.
# Khai báo dữ liệu cho dataframe, giá trị Age của dòng 1 chính ra là 25 thì lại điền là 25.0
data = [
('Tuan', 25.0, 'Hanoi'),
('Hung', 30, 'HCM'),
('Linh', 35, 'Hai Phong')
]
# Tạo dataframe sử dụng dữ liệu đã khai báo bên trên và cung cấp tên các cột
df = pd.DataFrame(data, columns=['Name', 'Age', 'City'])
df## Name Age City
## 0 Tuan 25.0 Hanoi
## 1 Hung 30.0 HCM
## 2 Linh 35.0 Hai Phong
# Do lỗi nhập liệu (25.0) mà pandas xác định cột "Age" có kiểu dữ liệu là float thay vì integer
df.dtypes## Name object
## Age float64
## City object
## dtype: object
Nếu như ta khai báo schema và ép pandas dataframe phải được tạo nên với đúng schema như vậy (enforce schema) thì ta sẽ tránh được lỗi như trên cho dù có lỗi nhập liệu 25.0. Cách làm này ép người Data Engineer phải kiểm tra sơ bộ dữ liệu và hiểu về dữ liệu mình sử dụng để tạo dataframe cho chính xác, và đảm bảo dataframe được tạo ra có các cột với data type đúng như yêu cầu.
# Khai báo dữ liệu cho dataframe
data = [
('Tuan', 25.0, 'Hanoi'),
('Hung', 30, 'HCM'),
('Linh', 35, 'Hai Phong')
]
# Khai báo schema
schema = {
"Name": "str",
"Age": "int",
"City": "str"
}
# Tạo dataframe sử dụng dữ liệu và theo schema đã khai báo
df = pd.DataFrame(data, columns=schema.keys()).astype(schema)
df## Name Age City
## 0 Tuan 25 Hanoi
## 1 Hung 30 HCM
## 2 Linh 35 Hai Phong
## Name object
## Age int64
## City object
## dtype: object
Tuy nhiên, nhược điểm của cách này là code dài hơn, và trong trường hợp dữ liệu thực sự phải là kiểu “float” (ví dụ như dữ liệu là giá trị hợp đồng, hoặc giả sử cột “Age” được lưu theo kiểu thập phân và có các giá trị như 20.5 tuổi, 31.7 tuổi, …) thì khi ép schema thành kiểu “integer”, mặc định pandas sẽ bỏ phần thập phân đi và chỉ lấy phần nguyên (chứ không phải là làm tròn) nên kết quả sẽ chưa được chính xác. Xem ví dụ dưới đây (30.9 không được làm tròn thành 31 mà lại là 30).
# Khai báo dữ liệu cho dataframe
data = [
('Tuan', 25.0, 'Hanoi'),
('Hung', 30.9, 'HCM'),
('Linh', 35, 'Hai Phong')
]
# Khai báo schema
schema = {
"Name": "str",
"Age": "int",
"City": "str"
}
# Tạo dataframe sử dụng dữ liệu và theo schema đã khai báo
df = pd.DataFrame(data, columns=schema.keys()).astype(schema)
df## Name Age City
## 0 Tuan 25 Hanoi
## 1 Hung 30 HCM
## 2 Linh 35 Hai Phong
# Kiểm tra kiểu dữ liệu của các cột trong dataframe
# Cột "Age" đảm bảo vẫn là kiểu int như schema đã khai báo
# Tuy nhiên ta lưu ý là các giá trị ở cột "Age" là phần nguyên của các số thập phân
# chứ không phải là làm tròn các số thập phân đó
df.dtypes## Name object
## Age int64
## City object
## dtype: object
Nếu ta cần lưu dữ liệu của dataframe dưới dạng code (thay vì tạo hẳn 1 bảng vật lý hoặc lưu dưới dạng file như json, csv, …) mà dataframe có kha khá nhiều cột thì ta nên lưu dữ liệu dưới dạng 1 list gồm các dict, như vậy khi nhìn vào các dòng dữ liệu bằng code đó sẽ dễ theo dõi hơn và tránh sai sót khi cần chỉnh sửa. Hãy so sánh 2 cách lưu dữ liệu dưới dạng code sau xem có đúng như vậy không nhé.
Cách 1: lưu dữ liệu dưới dạng list gồm các tuple
# Khai báo dữ liệu
data = [
("Tuan", 25, "Hanoi", 1000, "Married", "Data Engineer", "Vietnam"),
("Hung", 30, "HCM", 2000, "Single", "Investment banker", "USA"),
("Linh", 35, "Hai Phong", 3000, "Single", "Management consultant", "Vietnam")
]
# Khai báo schema
schema = {
"Name": "str",
"Age": "int",
"City": "str",
"Salary": "float",
"MaritalStatus":"str",
"Job": "str",
"Nationality": "str"
}
# Tạo dataframe sử dụng dữ liệu và theo schema đã khai báo
df = pd.DataFrame(data, columns=schema.keys()).astype(schema)
df## Name Age City ... MaritalStatus Job Nationality
## 0 Tuan 25 Hanoi ... Married Data Engineer Vietnam
## 1 Hung 30 HCM ... Single Investment banker USA
## 2 Linh 35 Hai Phong ... Single Management consultant Vietnam
##
## [3 rows x 7 columns]
Cách 2: lưu dữ liệu dưới dạng list gồm các dict
# Khai báo dữ liệu
data = [
{"Name": "Tuan", "Age": 25, "City": "Hanoi", "Salary": 1000, "MaritalStatus":"Married", "Job": "Data Engineer", "Nationality": "Vietnam"},
{"Name": "Hung", "Age": 30, "City": "HCM", "Salary": 2000, "MaritalStatus":"Single", "Job": "Investment banker", "Nationality": "USA"},
{"Name": "Linh", "Age": 35, "City": "Hai Phong", "Salary": 3000, "MaritalStatus":"Single", "Job": "Management consultant", "Nationality": "Vietnam"}
]
# Khai báo schema
schema = {
"Name": "str",
"Age": "int",
"City": "str",
"Salary": "float",
"MaritalStatus":"str",
"Job": "str",
"Nationality": "str"
}
# Tạo dataframe sử dụng dữ liệu và theo schema đã khai báo
df = pd.DataFrame(data, columns=schema.keys()).astype(schema)
df## Name Age City ... MaritalStatus Job Nationality
## 0 Tuan 25 Hanoi ... Married Data Engineer Vietnam
## 1 Hung 30 HCM ... Single Investment banker USA
## 2 Linh 35 Hai Phong ... Single Management consultant Vietnam
##
## [3 rows x 7 columns]