# LAB 02 : Advanced MapReduce & Spark Structured APIs
---
## I. NỘI DUNG ĐỀ BÀI
### 1. Phần Mở Đầu (Preliminaries)
* Đề làm được lab này, nhóm cần phải biết các ý tưởng kiến trúc cơ bản của **Spark** và **Structured APIs**.
* Nhóm cần phải đọc kỹ các kiến thức nền tảng trong:
* Bài báo Spark’s structured API’s : [link here](https://medium.com/@ravi.g/sparks-structured-api-s-cdeb381f6407)
* Tham khảo hướng dẫn chính thức từ Apache : [link here](https://spark.apache.org/docs/latest/api/python/getting_started/quickstart_df.html) $\to$ hướng dẫn này chủ yếu được viết băng ngôn ngữ **Python** nhưng nó vẫn giống như các ngôn ngữ khác về luồng thực thi.
#### 1.1. Lịch sử phát triển của Spark
* **Ngày đầu:** Spark đã được thao tác trực tiếp trên RDD, điều này khó có thể để viết và tối ưu hóa tác vụ.
* **DataFrame và Structured APIs:** Với sự phát triển vượt trội của DataFrame và Structured APIs, việc viết các công việc Spark đã được tự động tối ưu kế hoạch thực thi, dễ dàng thực thi (code ngắn gọn, dễ đọc hơn) và đạt được hiệu suất cao.
* Để hiểu được đầy đủ và chính xác nhất tất cả các khái niệm hỗ trợ Spark như **Catalyst optimizer**, **Whole-stage Code Gen**, .... giảng viên khuyên nhóm nên đọc thêm trong cuốn sách có tên **Spark: The Definitive Guide** (không bắt buộc, nhưng bạn nên tham khảo).
* Tính năng mới : Sau đó, ngày càng có nhiều tính năng được giới thiệu, chẳng hạn như **Adaptive Query Execution (AQE)** được giới thiệu trong phiên bản **3.0** để tối ưu hóa các tác vụ Spark, giúp chúng chạy nhanh hơn đáng kể trong **hầu hết các trường hợp**.
#### 1.2. DataFrame trong Spark
* DataFrame trong Spark có các đặc tính sau:
* **Schema-based**
* Mỗi DataFrame đều có schema, yêu cầu khai báo tên và kiểu dữ liệu của các cột trong DataFrame (đó là lý do tại sao chúng được gọi là API có cấu trúc). Hỗ trợ nhiều kiểu dữ liệu nguyên thủy cũng như các kiểu dữ liệu phức tạp khác.
* **Immutable (Không thể thay đổi được)**
* DataFrame trong Spark không thể được chỉnh sửa sau khi được tạo. Tuy nhiên, chúng ta có thể chuyển đổi chúng thành một khung dữ liệu khác thông qua **phép biến đổi (transformations)** như `filter`, `select`, và `groupBy`.
* **Lazy Evaluation (Đánh giá lười)**
* Các phép biến đổi **(transformation)** chỉ được xây dựng thành kế hoạch tính toán **(logical plan)**, chưa thực thi. Khi gặp hành động **(action)** như `count()`, `collect()`, **Spark** sẽ tối ưu hóa lược đồ thực thi **(through Catalyst)** rồi mới chạy.
* Khi người dùng chưa thực hiện một **hành động** **(action)** nào, các **chuyển đổi** trước đó (trong cùng một giai đoạn) chưa được chạy. Khi một hành động được kích hoạt, trình tối ưu hóa **Catalyst** sẽ xem xét kế hoạch thực thi, sau đó tối ưu hóa toàn bộ giai đoạn, rồi thực hiện xử lý trên kế hoạch đã được tối ưu hóa.
#### 1.3. So sánh cách thức hoạt động giữa Spark và MapReduce
* **Spark DataFrames** và **MapReduce** đều cho phép xử lý **dữ liệu phân tán** (distributed data processing) nhưng khác biệt đáng kể về **khả năng thực thi (execution)** và **hiệu quả (efficiency)**. Cả hai nền tảng đều xử lý các tập dữ liệu quy mô lớn song song trên các cụm, chia nhỏ các phép tính thành các tác vụ nhỏ hơn chạy trên nhiều nodes.
* Tuy nhiên, **MapReduce** sử dụng mô hình xử lý hàng loạt theo lô dựa trên đĩa **(a disk-based, batch processing model)**, trong đó các kết quả trung gian được ghi vào đĩa giữa các pha map và reduce, khiến quá trình chậm hơn do các thao tác I/O thường xuyên. Ngược lại, **Spark DataFrames** tận dụng xử lý trong bộ nhớ, giảm đáng kể I/O đĩa và cải thiện tốc độ.
* Một điểm khác biệt quan trọng nữa là **MapReduce** yêu cầu người dùng (user) phải định nghĩa rõ ràng các hàm **map** và **reduce**, trong khi **DataFrames** cung cấp API cấp cao hơn giống SQL (a higher-level, SQL-like API), tóm tắt các chi tiết này, cho phép viết mã dễ đọc và ngắn gọn hơn.
* Ngoài ra, **Spark DataFrames** còn được hưởng lợi từ **Catalyst Optimizer** và **Tungsten Engine**, áp dụng tối ưu hóa truy vấn tự động và thực thi hiệu quả về bộ nhớ, trong khi **MapReduce** không có tối ưu hóa truy vấn tích hợp và chỉ dựa vào logic do người dùng (user) xác định.
* Bất chấp những khác biệt này, cả hai framework đều thực hiện các hoạt động cơ bản tương tự nhau, chẳng hạn như ánh xạ (mapping), xáo trộn (shuffling) và giảm dữ liệu (reducing data), nhưng **Spark DataFrames** hợp lý hóa quy trình này với các kế hoạch thực thi được tối ưu hóa và API thân thiện với người dùng hơn, khiến chúng trở thành giải pháp thay thế hiệu quả và linh hoạt hơn cho **Map Reduce**.
### 2. Đề bài (Problem Statements)
Trong tất cả bài tập dưới đây, nhóm được chọn ngôn ngữ lập trình yêu thích để làm việc là: **Java**, **Scala**, hoặc **Python**.
Chú ý: nhóm chỉ có thể được phép sử dụng mã gốc (native code) hoặc thư viện (libraries) của ngôn ngữ đó nếu API tích hợp của Apache Spark không đủ để đạt được các mục tiêu cần thiết.
* Ví dụ, **`Numpy`** được coi là thư viện gốc (native libraries) nếu **`Python`** là ngôn ngữ lập trình bạn chọn.
Tiếp tục với tinh thần của **Lab 01**, nhóm sẽ được thưởng thêm **điểm bonus** cho mỗi bài toán (problems) nếu nhóm chứng minh được khả năng giải quyết vấn đề đó bằng ngôn ngữ **`Scala`**. Nhóm có thể tìm thấy thêm thông tin chi tiết trong tiêu chí chấm điểm ở phần cuối của bộ đề này.
Trong tất cả bài tập dưới đây, nhóm sẽ sử dụng cùng bộ dữ liệu [Amazone Sale Report](https://www.kaggle.com/datasets/thedevastator/unlock-profits-with-e-commerce-sales-data) (được format dưới dạng **`asr.csv`** và được cung cấp trong file **.Zip** đề bài). Các môi trường cho phép thực thi là môi trường mà nhóm đã cài đặt sẵn trên máy từ Lab 01, các môi trường thực thi khác như Google Colab không được cho phép trong yêu cầu Lab này.
Benchmark (nếu có): nên chạy tối thiểu 5 lần và các phép đo thu được sẽ được tính trung bình trong các lần chạy này, giá trị trung bình số (numerical means) và độ lệch chuẩn (standard deviations) sẽ được đưa vào báo cáo cuối cùng cho kết quả đánh giá benchmark của nhóm.
#### 2.1. Bài toán với Map Reduce
Nhóm sẽ dùng **framework MapReduce** để thực thi một số vấn đề như sau:
##### 2.1.1. Vấn đề 01: Sliding Window Size
Thực hiện và cài đặt bước tính toán sliding window $\to$ xác định **kich cỡ (size)** được mua nhiều nhất ở mỗi `state` (trạng thái) trong vòng tối đa 7 ngày trước ngày hiện tại (từ ít nhất d-7 đến d-1, chiều dài window có thể ngắn hơn 7 (< 7) nếu không có đơn đặt hàng trước đó phù hợp).
Một mặt hàng được coi là **`bought`** nếu đơn hàng có liên quan có trạng thái "shipped" (**`status`**) và số lượng mặt hàng này (**`quantity`**) phải $> 0$.
Cửa sổ trượt (sliding window) sẽ trượt qua mỗi $1$ ngày, do đó timestamp không nhìn thấy (unseen timestamp) có thể xuất hiện trong kết quả mong đợi của sliding window $\to$ kết quả có thể có timestamp trùng hoặc thiếu, đúng theo đặc tính của **sliding window**.
Bài toán này tập trung vào khái niệm gọi là ==cửa sổ trượt **(sliding window)**== nên nhóm có thể cần xem lại kiến thức liên quan trước khi giải.
:::spoiler **Bản gốc** :100:
Implement a sliding window computation that identifies the size that is mostly bought at each state within maximum 7 days prior to the current date (from at least d-7 to d-1, window length may be less than 7 if there is no appropriate past orders). An item is considered “bought” if the associated order has a “shipped” in its status and the quantity is non-zero. The window should slide by 1 day at a time, thus unseen timestamp may arise in the result which is expected for a sliding window’s result. This problem is centered around a concept called *sliding window* so you may need to review relevant knowledge prior to working on it.
:::
##### 2.1.2. Vấn đề 02: Median Variety By State & Month
**“Sự đa dạng”** (**`variety`**) của một phong cách được định nghĩa là số lượng **SKU** riêng biệt liên quan đến style đó trong một khoảng thời gian cụ thể và ở một khu vực địa lý cụ thể nhất định.
Sự đa dạng trung vị (`median variety`) được sử dụng để ước lượng độ đa dạng của sản phẩm được mua trong khoảng thời gian không gian (timespace) này và được tính là giá trị trung vị (median value) của tất cả style đáp ứng một điều kiện cụ thể.
Bây giờ, đối với mỗi tháng (ví dụ, tháng 7 bắt đầu từ 07-01 và kết thúc vào 07-31), nhóm được yêu cầu phải tính toán mức độ đa dạng trung vị ở mức độ trạng thái (state-level median variety) của tất cả các style đã phục vụ kích cỡ (size) tối thiểu là XXL (ví dụ: XXL, 3XL, 4XL, v.v.) $\to$ chỉ tính các style có size $\geq$ XXL (XXL, 3XL, 4XL, ...)
:::spoiler **Bản gốc** :100:
The “variety” of a style is defined as the number of distinct SKU associated with that style within a specific time interval and in a specific geographical region. Median variety is used to estimate the variety of goods purchased within this timespace interval and it is computed as the median value of all style that satisfies a specific condition. Now, for each month (for example, July starts at 07-01 and ends at 07-31), you are required to calculate the state-level median variety of all style which has served a size of least XXL (for example, XXL, 3XL, 4XL, .etc).
:::
##### 2.1.3. Yêu cầu chung
* **Đầu ra (Output)**
* Đối với mỗi yêu cầu của vấn đề trên, hãy xuất kết quả cuối cùng (final results) vào một tệp CSV duy nhất có thể đọc được bằng hệ thống tệp thông thường (thay vì các tệp dành riêng cho Hadoop (Hadoop-specific files)).
* Tên file được chỉ định theo tiêu chí chấm điểm của phần cuối cùng của bộ problemset này trong khi nhóm có thể tự do lựa chọn bố cục các files và thư mục phù hợp.
* **Báo cáo (Report)**
* Nhóm phải cung cấp phân tích chi tiết (detailed analysis) về các truy vấn (queries) được yêu cầu, bao gồm:
* Không giới hạn ở cách nhóm hiểu và định hình các truy vấn (queries).
* Cách nhóm phân tích truy vấn.
* Chiến lược triển khai cho từng bước phân tích.
#### 2.2. Bài toán với Structured APIs
Sử dụng Apache Spark's **Structured APIs**, trong đó **Spark Dataset** được phép sử dụng cho các giải pháp **Scala** và/hoặc **Java** trong khi **Spark SQL** không được chấp nhận để chấm điểm (mặc dù **Spark SQL** có thể được sử dụng để minh họa sự hiểu biết của nhóm về các truy vấn (queries) hoặc như các bước trung gian dẫn đến các giải pháp cuối cùng):
##### 2.2.1. Vấn đề 03: Tỷ lệ hủy đơn theo thành phố
**Vấn đề 03** sẽ được yêu cầu như sau:
* Đối với mỗi thành phố, hãy tính tỷ lệ phần trăm (%) đơn hàng bị hủy (**`cancelled`**) cho ==Standard service level== (mức độ dịch vụ tiêu chuẩn). Mức độ dịch vụ này có ít nhất 3 chương trình khuyến mãi diễn ra đồng thời, trong khi có số tiền mua ít hơn số tiền trung bình của các đơn hàng hoàn thiện đơn hàng của thương gia tại state liên kết có trạng thái chuyển phát nhanh là "Shipped".
* Lưu ý:
* Tất cả các chương trình khuyến mãi do Amazon phát hành có chữ “Amazon” trong tên đều không được tính vào tiêu chí 3 chương trình khuyến mãi diễn ra đồng thời nêu trên.
**English version**
:::spoiler **Bản gốc** :100:
For each city, calculates the percentage of cancelled orders of Standard service level that possess at least 3 simulatenous promotions while having the purchased amount less than the average amount of the associated state’s merchantfulfillment orders which have a courier status of “Shipped”. Note that, all promotions issued by Amazon which have “Amazon” in their names are not counted towards the above criterion of 3 simultaneous ones.
:::
##### 2.2.2. Vấn đề 04: Độ lệch chuẩn số tiền theo SKU và tháng
* Đối với mỗi **SKU** trong mỗi tháng, độ lệch chuẩn (standard deviation) của số lượng đơn hàng có số lượng khuyến mãi cao thứ năm trong tập hợp các đơn hàng cho SKU đó trong tháng là bao nhiêu?
* Số lượng khuyến mãi cao thứ năm có nghĩa là số lượng khuyến mãi mà đơn hàng ở vị trí thứ 5 sở hữu sau khi được xếp hạng so với số lượng khuyến mãi giảm dần và có thể có nhiều hơn một đơn hàng có số lượng khuyến mãi như vậy.
* **Lưu ý**
* Nếu có ít hơn 5 đơn hàng cho SKU trong tháng, số lượng khuyến mãi của những đơn hàng có giá trị thấp nhất sẽ được sử dụng.
* Nếu chỉ có một đơn hàng có số lượng khuyến mãi tìm được, độ lệch sẽ được đặt về $0$. Độ tự do (degree of free (DoF)) cho phép tính độ lệch chuẩn là $0$.
English version
:::spoiler **Bản gốc** :100:
For each SKU within each month, what is the standard deviation of the amount of orders whose number of promotions is fifth-highest within the set of orders for that SKU in the month? The fifth-highest number of promotions means the number of promotions possessed by the order at 5-th position after being ranked against the number of promotions descendingly, and there may exist more than one order having such number of promotions. Note that, if there are less than 5 orders for the SKU in the month then the number of promotions of those lowest will be used, and if there is only one such order with the found number of promotions, the deviation is set to zero. The degree of freedom for standard deviations’calculations is zero.
:::
##### 2.2.3. Yêu cầu chung
* **Đầu ra (Output)**
* Đối với mỗi vấn đề nêu trên, hãy xuất kết quả cuối cùng vào một file **PARQUET** duy nhất có thể được phân tích cú pháp bởi Pandas hoặc Spark ở chế độ local và trong hệ thống file thông thường (thay vì các file dành riêng cho Hadoop).
* Tên file được chỉ định theo tiêu chí chấm điểm của phần cuối cùng của bộ problemset này trong khi nhóm có thể tự do lựa chọn bố cục các files và thư mục phù hợp.
* **Báo cáo (Report)**
* Nhóm phải cung cấp phân tích chi tiết về các truy vấn (queries) được yêu cầu, bao gồm nhưng không giới hạn ở cách nhóm hiểu và định hình các truy vấn (query), cách nhóm phân tích chúng cũng như chiến lược triển khai cho từng bước phân tích.
### 3. Hướng dẫn nộp bài
Bài lab này yêu cầu nộp theo nhóm, trong khi đó các công việc của các thành viên trong nhóm được nén lại thành một file đơn tổng và chỉ có một thành viên đại diện phải nộp file này trên Moodle.
Lưu ý: Với sự khác biệt so với Lab01, Lab02 yêu cầu nhóm hợp tác để hoàn thành các vấn đề đã nêu trên thay vì các thành viên trong nhóm phải làm việc riêng lẻ, do đó, giải pháp được nộp phải là một giải pháp thống nhất và duy nhất.
File nộp bài phải chứa một thư mục duy nhất có tên <RepresentiveID> trong đó mã số sinh viên (Student ID) là mã số sinh viên của bất kỳ thành viên nào đại diện nộp bài trong nhóm của bạn.
Cấu trúc bên trong của thư mục như mô tả trên chứa các bài tập thực hành của nhóm cho mỗi bài toán. Cụ thể cấu trúc sẽ như sau:
```text
<RepresentativeID>
├── src
│ ├── Task_1-1
│ │ ├── source # Code files here
│ │ └── ...
│ ├── Task_1-2
│ │ ├── source # Code files here
│ │ └── ...
│ ├── Task_2-1
│ │ ├── source # Code files here
│ │ └── ...
│ └── Task_2-2
│ ├── source # Code files here
│ └── ...
└── docs
├── Report.pdf
├── drive_link.txt
└── README (optional, instructions to run the code)
```
File `drive_link.txt` chứa một link liên kết duy nhất đến thư mục trong Google Drive được sắp xếp theo cấu trúc như mô tả dưới đây. Tất nhiên, bất kỳ chỉnh sửa nào sau thời hạn đã định được công bố trên Moodle sẽ làm mất hiệu lực kết quả của nhóm.
```text
<RepresentativeID>
├── Task_1-1.csv
├── Task_1-2.csv
├── Task_2-1.parquet
└── Task_2-2.parquet
```
### 4. Tiêu chí chấm điểm
**Tiêu chí chấm điểm** cho mỗi bài toán trong phần **Problem Statements** được tóm tắt trong bảng dưới đây với tổng điểm của mỗi bài toán là **3 điểm**.
| ==Yêu cầu chi tiết== | ==Điểm số== |
| ------------------------------------------------------------------------------ |:-----------:|
| **Mỗi bài toán** | **3** |
| - Phân tích chính xác những truy vấn (queries) | 0.5 |
| - Tách thành công bài toán thành các bước cơ bản | 0.5 |
| - Thành công giải thích lý do đằng sau việc tách nhỏ các bước | 0.5 |
| - Thành công thực thi (code) các bước trên sau khi tách | 0.5 |
| - Kiểm thử lại với bộ dữ liệu ban đầu và xuất thành công kết quả | 0.25 |
| - Kết quả xuất ra là chính xác | 0.25 |
| - Giải pháp của nhóm là benchmark về thời gian thực thi và bộ nhớ (mean & std) | 0.25 |
| - Giải pháp được code bằng Scala (phải chạy được và cho kết quả đúng) | 0.25 |
| **Tổng điểm của 4 bài toán** | **12** |
**Lưu ý**
* Nhóm phải tuân thủ nghiêm ngặt cấu trúc file đã được đề cập phía trên và nén toàn bộ thư mục vào file ZIP có tên <RepresentativeID>.zip, đây là file cuối cùng nhóm sẽ gửi lên Moodle.
* Đảm bảo code của nhóm được ghi chép đầy đủ với các chú thích (comment) rõ ràng.
* Mỗi task có thể được thực hiện trong các môi trường phức tạp và programming languages khác nhau, hãy nhớ cung cấp hướng dẫn để chạy từng task nếu cần.
---
## II. QUẢN LÝ DỰ ÁN
### 1. Tài nguyên dự án
* **GG Drive**
* Src + Docs: [link here](https://drive.google.com/drive/folders/1rtSVFEyHZBT2RPciq0y_4YvS32pu1Xiw?usp=sharing)
* drive_link.txt: [link here](https://drive.google.com/drive/folders/1LCdT2FN97TK-Dlpfm7tWKUbxjNXBLock?usp=drive_link)
* **Report Latex:** [link here](https://www.overleaf.com/5915544686bgjmpbysppqp#27c772)
* **Github:** [link here](https://github.com/PhuocPhat1005/LAB02-Advanced-MapReduce-Spark-Structured-APIs.git)
### 2. Phân công dự án
| MSSV | Họ và Tên | Công việc được giao | Mức độ hoàn thành |
|:--------:| --------------- |:-------------------:|:-----------------:|
| 22127174 | Ngô Văn Khải | Task 1.1 | $100\%$ |
| 22127208 | Nguyễn Anh Khôi | Task 1.2 | $100\%$ |
| 22127260 | Bùi Công Mậu | Task 2.1 | $100\%$ |
| 22127322 | Lê Phước Phát | Task 2.2 | $100\%$ |
### 3. Quy trình dự án
#### 3.1. Task 1.1
##### Bước 01:
##### Bước 02:
#### 3.2. Task 1.2
Điền nội dung ở đây ...
#### 3.3. Task 2.1
Với mỗi thành phố (ship-city), tính phần trăm các đơn có:
* `ship-service-level` = "Standard"
* `Status` = "Canceled"
* có ít nhất 3 `promotions` đồng thời (không tính các promotions chứa chữ 'Amazon') $\to$
* Amount < (average Amount của các đơn Merchant-fulfillment AND Courier Status = 'Shipped' trong cùng ship-state)
Kết quả cuối cùng là một bảng `(ship-city, pct_cancelled_qualified)` trong đó

##### Bước 01: Đọc dữ liệu vào Relation `Orders` (Load Orders)
* Nạp toàn bộ thông tin vào đơn hàng từ file CSV vào bộ nhớ của Spark để bắt đầu xử lý.
* Cách làm:
* Sử dụng `spark.read.option("header","true").csv(inputPath)` để đọc.
* Kết quả: một DataFrame `orders` có tất cả cột nguyên bản: [Order ID, Date, SKU, ship-city, ship-state, ship-service-level, Status, Fulfilment, Courier Status, Amount, promotion-ids, …]
##### Bước 02: Tách & lọc promotions để tính promo_count
* Xác định số lượng khuyến mãi thực sự áp dụng, loại bỏ các khuyến mãi có chữ "Amazon ..." không tính.
* Cách làm:
* Đầu tiên ta sẽ split các giá trị trong cột `promotion-ids` để chia chuỗi thành các mảng con
* Loại bỏ các phần tử rỗng (NAN) hoặc null (promo.isNotNull() && promo != "").
* Loại bỏ phần tử `promo` khi `lower(promo).contains("amazon")`.
* Tính độ dài: `size(array_filter(...))` → số lượng khuyến mãi hợp lệ, lưu vào cột `promo_count` (Integer).
* Kết quả đầu ra:
* promotions: Array<String> (danh sách khuyến mãi hợp lệ)
* promo_count: Int (số khuyến mãi)
* Lý do: Task yêu cầu “ít nhất 3 promotions không tính Amazon”, nên cần cột promo_count.
##### Bước 03: Tính trung bình `Amount` theo `ship-state` (avgState)
* Tìm giá trị trung bình của `Amount` cho nhóm `Fulfilment = Merchant` và `Courier Status = Shipped` trong cùng một tiểu bang (ship-state), để so sánh với `Amount` của orders có `Status = Shipped`
* Cách làm
* Chỉ giữa các record thỏa `Fulfilment = 'Merchant'` và `Courier Status = 'Shipped'`.
* Sau đó, groupby theo nhóm `ship-state`.
* Cuối cùng là tính giá trị trung bình bằng cách avg_amt = AVG(Amount.cast("double")).
* Đầu ra: Một DataFrame `avgState(ship-state, avg_amt: Double)`.
* Lý do: Phải cần so sánh điều kiện `Amount < avg of Merchant-Shipped trong cùng một state`.
##### Bước 04: Nối avgState vào DataFrame Orders chính
* Đưa cái giá trị trung bình `avg_amt` xuống mỗi records orders tương ứng state để tiện so sánh.
##### Bước 05: Lọc đơn Standard & Canceled
* Xác định các orders ở `ship-service-level = "Standard"` và `Status = Canceled`.
* Cách làm
* Tạo cột Boolean `is_standard = (ship-service-level == "Standard")`
* Tạo cột Boolean `is_canceled = (Status == "Canceled")`.
* Đầu ra: dataframe `flaged` có thêm 2 cột `is_standard` và `is_canceled`.
* Lý do: Phân biệt trước loại đơn này để dùng trong cả việc đếm tổng “Standard” và đếm “Standard+Canceled” sau.
##### Bước 06: Xác định đơn "thỏa" điều kiện cho trước
* Xác định đơn Standard + Canceled có promo_count >= 3 và Amount < avg_amt
* Đầu ra: cột boolean meets $\to$ dễ đếm số đơn "đúng" theo yêu cầu một cách rõ ràng.
##### Bước 07: Đếm tổng "Standard" theo `ship-city`
* Tính mẫu số của tỷ lệ phần trăm (%): Tổng số đơn có is_standard = True trong mỗi thành phố
* Đầu ra: DataFrame chứa tổng số đơn "Standard" cho mỗi city.
* Lý do: tính tổng mẫu của tỷ lệ (%).
##### Bước 08: Đếm số đơn "Standard + Canceled" thỏa điều kiện theo `ship-city` (cntGood).
* Tính tử số cho tỷ lệ phần trăm (%): số đơn có is_standard = true và is_canceled = true và meets = true trong mỗi thành phố.
* Đầu ra: DataFrame `cntGood(ship-city, cnt_good)`.
##### Bước 09: Tính tỷ lệ
* Tính tỷ lệ phần trăm:



#### 3.4. Task 2.2
Với **mỗi SKU** và **mỗi tháng**:
* Tính số khuyến mãi `promo_count` cho mỗi orders.
* Xác định giá trị thứ 5 cao nhất của promo_count trong nhóm orders của chính SKU-tháng đó. (nếu nhóm có < 5 đơn thì dùng giá trị thấp nhất).
* Lọc hết tất cả các đơn có promo_count = giá trị vừa tìm được.
* Tính độ lệch chuẩn (stddev) của Amount trên tập con đó.
* Nếu tập con chỉ có 1 đơn, đặt `stddev = 0`, tính với `degree of freedom = 0`.
Kết quả: bảng DataFrame `(SKU, order_month, stddev_amount)`
##### Bước 01: Đọc & khởi tạo dữ liệu gốc
* Nạp file CSV vào DataFrame `Orders`
##### Bước 02: Tạo cột promo_count
* Tách các chuỗi ký tự trong promotion-ids
* Loại bỏ các phần tử rỗng / null
* Đếm kích thước mảng $\to$ cột số nguyên `promo_count`.
##### Bước 03: Tạo cột order_month
* Chuyển Date (string) sang kiểu date nếu cần.
* Dùng substring(Date,1,7) hoặc date_format(...,"yyyy-MM") → order_month.
##### Bước 04: Gom list và xác định cut off

##### Bước 05: Join & lọc theo `cutoff`

##### Bước 06: Tính `stddev_amount`

##### Bước 07: Xuất kết quả


---
## III. GIẢI ĐÁP THẮC MẮC
### Câu hỏi 1
:::info
Cho em hỏi là task 2.3 trong file Lab02 của thầy là gì vậy ạ? Với lại, đối với tên file source code thì tụi em có quyền đặt tên tùy ý tụi em đúng không ạ?
:::
:::spoiler **Answer**
Chào bạn, thầy bị mấy dòng task 2.3, các bạn skip cái đó trong submission guideline nhé. Còn tên file trong source các bạn được đặt tùy ý, miễn đúng folder đúng task thôi nhé
:::
### Câu hỏi 2
:::info
(1) Về việc code, nếu như nhóm em quyết định code Python thì việc khi compile, tụi em có nhất thiết phải compile bằng lệnh "hdfs dfs ..." không hay có thể dùng lệnh "python file.py ..." ạ?
(2) Nếu như tụi em có code thêm Scala nữa thì tụi em để chung với file code Python vẫn được phải không ạ?
:::
:::spoiler **Answer**
(1) Chào bạn, submission guideline có file readme thì bạn đưa các hướng dẫn tương tự vào file này nhé.
(2) Cái này các bạn tự tổ chức trong file source, nếu tự tin code scala đúng thì không có python vẫn đủ điểm
:::
### Câu hỏi 3
:::info
(1) Cho em hỏi ở Task1.1, state ở đây là ship-state hả thầy? Nếu đúng thì 2 state Odisha và ODISHA là giống nhau hay khác nhau ạ?
(2) Với lại trong dữ liệu có những missing value, nếu gặp phải record như thế thì mình sẽ bỏ qua phải không ạ?
(3) Với lại status ở đây là 1 trong 2 cột Status và Courier Status phải không ạ?
:::
:::spoiler **Answer**
(1) Chào bạn, state là ship-state còn case lowercase hay uppercase đều xem như 1 state nhé. Chào bạn, status là Status còn khi nào là courier status thì thầy có ghi riêng nhé.
(2) Sliding window với missing value thì bạn tìm hiểu lại và đọc kỹ đề nhé. (Phản hồi: ý em ko phải là datetime không có (trong đề em có thấy rồi ạ), ý em missing value ở đây là mấy giá trị nan trong dữ liệu ấy ạ! $\to$ Chào bạn, giá trị nan thì bạn ignore nhé, xem như không có dữ liệu đó khi tính sliding window là được)
(3) Chào bạn, status là Status còn khi nào là courier status thì thầy có ghi riêng nhé
:::
#### Câu hỏi 4
:::info
Ở task 2.1 (tính % đơn canceled ở mỗi city) em có 2 thắc mắc cần thầy giải đáp ạ:
1. Tỷ lệ % ở đây = (Số lượng đơn hàng huỷ của Standard service thoả các điều kiện như trong đề) / (Tổng số lượng đơn hàng của thành phố) hay là (Số lượng đơn hàng huỷ của Standard service thoả các điều kiện như trong đề) / (Tổng sớ lượng Số lượng đơn hàng huỷ của Standard service trong thành phố) thôi ạ ?
2. Em nhận thấy có nhiều dòng "Order ID" của chúng bằng nhau nhưng sản phẩm lại khác nhau (có thể là cả amount và promotion-ids). Vậy mình xử lý bằng cách gộp tất cả các Order ID bằng nhau lại thành 1 rồi mới áp dụng các tính toán để giải quyết điều kiện đề giao đúng không ạ ?
Em cảm ơn thầy đã giải đáp ạ!
:::
:::spoiler **Answer**
Chào bạn, (1) thì trên tổng số lượng đơn hủy của standard service chứ không tính các đơn service khác nhé, (2) các dòng có order ID khác nhau xem như cùng 1 đơn hàng nhé bạn, còn bạn xử lý bằng cách nào là lựa chọn của nhóm bạn nhé.
:::
#### Câu hỏi 5
::: info
(1) Với lại theo như đề vậy thì tụi em không cần phải post preprocessing để bỏ ra những unseen timestamp đúng không ạ?
(2) Dạ, em có một câu hỏi nữa liên quan đến Task 1.1 ạ! Đó là số lượng "bought" của 1 size sẽ được tính bằng tổng số quantity của size đó ở các order hay là tổng số order liên quan đến size đó ạ?
(3) Dạ thầy ơi, ở task 1.2, có những order có size là "Free", lúc này khi so sánh để lấy những size >= "XXL" thì mình cũng bỏ qua "Free" phải không ạ?
:::
:::spoiler **Answer**
(1) Đúng rồi đó bạn
(2) Là tổng số quantity được mua ở tất cả order nhé bạn
(3) Được nha bạn, để cho thống nhất thì các nhóm khác cũng bỏ qua free luôn nhé
:::
#### Câu hỏi 6
:::info
(1) Cho em hỏi là với các step như xử lý các order ID giống nhau, khử nan,... thì nhóm em làm riêng thành 1 file tạm gọi là preprocess data để tạo ra file tạm gọi là preprocessed_asr.csv rồi sau đó mới dùng file này để tiến hành query có được không ạ?
(2) Dạ ý thầy là tụi em không được để file preprocess riêng mà phải preprocess trong từng file query ở các task hả thầy?
:::
:::spoiler **Answer**
(1) Các bạn làm 1 file thôi là được, mỗi step chỉ cần mô tả rõ ràng trong báo cáo như nãy meeting thầy có giải thích thôi.
(2) Mỗi task có 1 folder source để các bạn chứa code, thì từng task đó các bạn code bao nhiêu file cũng được mà. Còn output theo guideline thì bạn để ra file riêng trên drive như đề đã hướng dẫn thôi. Preprocess cũng là 1 step trong quá trình xử lý thôi mà nhỉ
:::
#### Câu hỏi 7
:::info

(1) cho em hỏi chỗ yêu cầu này cho task 1.1 và 1.2 thì mình tự viết script riêng lẻ cho mapper/reducer ròi dùng hadoop streaming hay là có thể được dùng thư viện Mrjob (nếu viết bằng python) ạ
(2) với cả Thầy có trả lời câu hỏi của bạn Nam, nhưng em đọc xong vẫn ko hiểu, là tụi em có bắt buộc phải chạy lệnh "hdfs dfs ..." không hay là được xài lệnh "python ..." (nếu viết bằng python) để chạy file ạ
:::
:::spoiler **Answer**
(1) chào bạn, trong buổi meeting thầy có nói là các bạn không được dùng streaming rồi nhé. Nên phương án mrjob được đề nghị hơn
(2) còn file code thì bạn chỉ cần ghi code chính thôi, nếu phải chạy thêm lệnh gì để code đó chạy được thì là hướng dẫn chạy rồi bạn. Mà hướng dẫn chạy thì bạn để vào readme chứ
:::
#### Câu hỏi 8
:::info

(1) Dạ thầy @Huỳnh Lâm Hải Đăng ơi cho em hỏi ở phần benchmark này thì tất cả các metrics liên quan đến runtime và memory đều phải được chạy và note lại vào report hả thầy hay mình có cần sử dụng các metrics cụ thể nào không ạ?
(2) dạ phương án thầy nói ở đây là tụi em sẽ so sánh giữa việc chạy MapReduce với Spark hay là mỗi task tụi em phải code nhiều version khác nhau để so sánh ạ?
:::
:::spoiler **Answer**
(1) chào bạn, bạn lựa chọn một số metrics time - mem quan trọng để benchmark lại và phân tích giải thích nhé, tại sao runtime phương án này nhanh hơn phương án kia hay vì sao cái này dùng nhiều memory hơn v.v.
(2) là mỗi task bạn so sánh các phương án khác nhau nhé, ví dụ filter trước join hay join trước filter ..., nghĩa là so sánh trong cùng task của nha bạn còn MR với Spark là chia thành 2 bài rồi mà.
:::
#### Câu hỏi 9
:::info
cái so sánh các phương án khác nhau là sao em vẫn chưa hiểu ạ, là mình thay đổi thứ tự các bước làm trong 1 file code python rồi đo time với memory khi chạy file code này hay là với 1 task mình sẽ phải làm nhiều cách (và với mỗi cách này là 1 code python thì có cần phải nộp 5 file code không ạ)
:::
:::spoiler **Answer**
với 1 task làm nhiều cách nhưng chỉ khác biệt một chút để so sánh thôi nhé, với lại 2 3 cách là được không tới 5 đâu
:::
#### Câu hỏi 10
:::info
Mỗi task là nhận một bài code trong team thôi đúng không thầy ?
:::
:::spoiler **Answer**
Mỗi bạn chia nhau ra làm, chứ không phải mỗi bạn làm hết.
:::
#### Câu hỏi 11
:::info
Task 1.1 và 1.2 thì không được dùng Spark để xử lý.
Task 2.1 và 2.2 thì dùng Spark Structured API (ko dùng Spark SQL) (Spark DataFrame và Spark Dataset).
:::
#### Câu hỏi 12
:::info
Nếu lab1 dùng Hadoop trên Linux giờ lab2 dùng Hadoop trên docker có được không ?
:::
:::spoiler **Answer**
Nếu chuyển trên docker và chạy lại hệ thống verified được cái script hồi lab 01. Nhớ xác định lại địa chỉ MAC
:::
#### Câu hỏi 13
:::info
Thầy giải thích về yêu cầu của viết Report được không ?
:::
:::spoiler **Answer**
Viết rõ ràng quá trình phân tích câu truy vấn như môn CSDL viết câu SQL (phân tích câu truy vấn dựa trên hiểu biết của các bạn). Cắt nghĩa ra từng statement rồi biểu diễn dưới dạng relational rồi chuyển sang API của Spark để translate truy vấn đầu vào thành structured API. Không phải tự nhiên từ câu đề bài cho ra thẳng code thì ko được.
Cách hiểu từng term như nào.
:::
#### Câu hỏi 14
:::info
Em dùng WSL Ubuntu.
Mình nên chạy theo kiểu chia 2 file mapper và reducer như vầy:
hadoop jar $HADOOP_HOME/share/hadoop/tools/lib/hadoop-streaming-*.jar \
-input /words.txt \
-output /output \
-mapper mapper.py \
-reducer reducer.py \
-file mapper.py \
-file reducer.py
Hay là có thể gom mapper reducer trong 1 file (dùng thử viện mr.job) rồi command như vầy ạ:
python3 wordcount.py words.txt > result.csv
:::
:::spoiler **Answer**
MapReduce của lab1 lab2 ko được dùng Streaming của Hadoop.
Nên recommend phương án command python3.
:::
#### Câu hỏi 15
:::info
dạ thầy @Huỳnh Lâm Hải Đăng cho em hỏi ở phần benchmark mỗi phương án em phải chạy ít nhất 5 lần hay sao ạ?
:::
:::spoiler **Answer**
chào bạn, 5 lần chạy này là để tránh các yếu tố ngẫu nhiên đó bạn, mỗi phương án chạy 5 lần là đúng rồi nhé
:::
#### Câu hỏi 16
:::info
Nói lại cái đoạn tính calculates the percentage ở phần Structured APIs.
:::
:::spoiler **Answer**
Đọc hiểu và phân tích cái queries, nhóm trình bày được hiểu cái domains như nào, ...
Điều kiện có gì ? Percentage giữa cái gì với cái gì ? Mẫu số của nó là số lượng các orders bị cancelled of Standard service level. Rồi mới phân tích tới tử số.
Nhớ decompose từng bước nhỏ (step-by-step). Nên tính toán như nào ... rồi trình bày ra rõ ...
:::
#### Câu hỏi 17
:::info
Implement a sliding window computation that identifies the size that is mostly bought at each state within maximum 7 days prior to the current date
cho em hỏi state ở đây là cột Status phải không thầy?
:::
:::spoiler **Answer**
State ở đây là ship state chứ không phải status (nếu là `Status` thì thầy đã ghi rõ).
:::
####
---
## IV. TÀI LIỆU THAM KHẢO
[1] ...
[2] ...