From 863e2ef20aa9a3ea5d2f5da47b170641e8a40025 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 17:06:52 +0800 Subject: [PATCH 01/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + homework/m1_numpy.py | 106 +++++++++++++++++++++++++------------------ 2 files changed, 62 insertions(+), 45 deletions(-) diff --git a/.gitignore b/.gitignore index 911bb2d..2169d7a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ __pycache__/ *.pyc .pytest_cache/ .pytest_results.json +venv \ No newline at end of file diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..3af9be4 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -16,65 +16,81 @@ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ -def green_mean(): - """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" - # TODO: 你的程式碼 - pass - - -def green_double(): - """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" - # TODO: 你的程式碼 - pass - - -def green_filter(): - """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" - # TODO: 你的程式碼 - pass - +# def green_mean(): +# """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" +# arr = np.array([10,20,30,40,50]) +# return arr.mean() +# print(green_mean()) + + +# def green_double(): +# """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" +# arr = np.array([10,20,30,40,50]) +# return arr*2 +# print(green_double()) + +# def green_filter(): +# """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" +# arr = np.array([10,20,30,40,50]) +# result = arr[arr>25] +# return result +# print(green_filter()) # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # 以下函式會接收從 products.csv 讀出的 prices, stocks 陣列 # ============================================================ -def yellow_expensive_count(prices): - """回傳單價 > 1000 的商品數量 (int)""" - # TODO: 你的程式碼 - pass +# def yellow_expensive_count(prices): +# """回傳單價 > 1000 的商品數量 (int)""" +# prices_arr = np.array(prices) +# gold = prices_arr[prices_arr>1000] + +# return gold +# test_prices = [500, 1200, 800, 2500, 100] +# test_prices = len(yellow_expensive_count(test_prices)) +# print(test_prices) -def yellow_top3_stock_indices(stocks): - """ - 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) - 提示:np.argsort - """ - # TODO: 你的程式碼 - pass -def yellow_restock_cost(prices, stocks): - """ - 單價 < 500 的商品,每種各進貨 50 個,回傳總花費 (float/int) - 提示:布林遮罩 + .sum() - """ +def yellow_top3_stock_indices(stocks): + arr = np.array(stocks) + indices = np.argsort(arr)[::-1][:3] + top3_values = arr[indices] + return top3_values +list = [100,200,300,400,500] +print(yellow_top3_stock_indices(list)) + + +# """ +# 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) +# 提示:np.argsort +# """ + + + +# def yellow_restock_cost(prices, stocks): +# """ +# 單價 < 500 的商品,每種各進貨 50 個,回傳總花費 (float/int) +# 提示:布林遮罩 + .sum() +# """ # TODO: 你的程式碼 - pass +# pass # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ -def red_double11_prices(prices, stocks): - """ - 雙 11 定價規則(必須向量化,不能用 for-loop): - - 庫存 >= 100:打 7 折 - - 庫存 20~99:打 9 折 - - 庫存 < 20:原價 - 回傳每個商品的雙 11 售價 (ndarray) - 提示:np.where 可以巢狀使用 - """ - # TODO: 你的程式碼 - pass +# def red_double11_prices(prices, stocks): +# """ +# 雙 11 定價規則(必須向量化,不能用 for-loop): +# - 庫存 >= 100:打 7 折 +# - 庫存 20~99:打 9 折 +# - 庫存 < 20:原價 +# 回傳每個商品的雙 11 售價 (ndarray) +# 提示:np.where 可以巢狀使用 +# """ +# # TODO: 你的程式碼 +# pass From 686419f443cbec1100980314991cc5d4a9c0b539 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 17:17:40 +0800 Subject: [PATCH 02/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 48 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 3af9be4..3ce36a9 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -16,41 +16,41 @@ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ -# def green_mean(): -# """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" -# arr = np.array([10,20,30,40,50]) -# return arr.mean() -# print(green_mean()) +def green_mean(): + """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" + arr = np.array([10,20,30,40,50]) + return arr.mean() +print(green_mean()) -# def green_double(): -# """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" -# arr = np.array([10,20,30,40,50]) -# return arr*2 -# print(green_double()) +def green_double(): + """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" + arr = np.array([10,20,30,40,50]) + return arr*2 +print(green_double()) -# def green_filter(): -# """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" -# arr = np.array([10,20,30,40,50]) -# result = arr[arr>25] -# return result -# print(green_filter()) +def green_filter(): + """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" + arr = np.array([10,20,30,40,50]) + result = arr[arr>25] + return result +print(green_filter()) # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # 以下函式會接收從 products.csv 讀出的 prices, stocks 陣列 # ============================================================ -# def yellow_expensive_count(prices): -# """回傳單價 > 1000 的商品數量 (int)""" -# prices_arr = np.array(prices) -# gold = prices_arr[prices_arr>1000] +def yellow_expensive_count(prices): + """回傳單價 > 1000 的商品數量 (int)""" + prices_arr = np.array(prices) + gold = prices_arr[prices_arr>1000] -# return gold + return gold -# test_prices = [500, 1200, 800, 2500, 100] -# test_prices = len(yellow_expensive_count(test_prices)) -# print(test_prices) +test_prices = [500, 1200, 800, 2500, 100] +test_prices = len(yellow_expensive_count(test_prices)) +print(test_prices) From 6e49a0acb78d2c100c9596d8b76658fe32d07ecd Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 17:19:19 +0800 Subject: [PATCH 03/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 3ce36a9..30cac42 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -20,21 +20,21 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" arr = np.array([10,20,30,40,50]) return arr.mean() -print(green_mean()) + def green_double(): """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" arr = np.array([10,20,30,40,50]) return arr*2 -print(green_double()) + def green_filter(): """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" arr = np.array([10,20,30,40,50]) result = arr[arr>25] return result -print(green_filter()) + # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) @@ -48,9 +48,7 @@ def yellow_expensive_count(prices): return gold -test_prices = [500, 1200, 800, 2500, 100] -test_prices = len(yellow_expensive_count(test_prices)) -print(test_prices) + @@ -59,8 +57,7 @@ def yellow_top3_stock_indices(stocks): indices = np.argsort(arr)[::-1][:3] top3_values = arr[indices] return top3_values -list = [100,200,300,400,500] -print(yellow_top3_stock_indices(list)) + # """ From e98585597a72b93a33bf96386994fe8a2d78395e Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 17:21:53 +0800 Subject: [PATCH 04/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 30cac42..43d5635 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -18,22 +18,22 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" - arr = np.array([10,20,30,40,50]) + arr = np.array([10, 20, 30, 40, 50]) return arr.mean() - + def green_double(): """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" - arr = np.array([10,20,30,40,50]) - return arr*2 + arr = np.array([10, 20, 30, 40, 50]) + return arr * 2 + def green_filter(): """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" - arr = np.array([10,20,30,40,50]) - result = arr[arr>25] - return result + arr = np.array([10, 20, 30, 40, 50]) + return arr[arr > 25] # ============================================================ From dfe9e5a449945acede5bf38889ab7afd95c09606 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 17:26:40 +0800 Subject: [PATCH 05/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 65 +++++++++++++++++++------------------------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 43d5635..fa262f0 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -41,53 +41,44 @@ def green_filter(): # 以下函式會接收從 products.csv 讀出的 prices, stocks 陣列 # ============================================================ +# ---------- 載入 products.csv ---------- +DATA_PATH = "datasets/ecommerce/products.csv" +prices = np.genfromtxt(DATA_PATH, delimiter=",", skip_header=1, usecols=3) +stocks = np.genfromtxt(DATA_PATH, delimiter=",", skip_header=1, usecols=4) + def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" - prices_arr = np.array(prices) - gold = prices_arr[prices_arr>1000] - - return gold - - - + return prices[prices>1000].size def yellow_top3_stock_indices(stocks): - arr = np.array(stocks) - indices = np.argsort(arr)[::-1][:3] - top3_values = arr[indices] - return top3_values - - - -# """ -# 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) -# 提示:np.argsort -# """ - + """ + 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) + 提示:np.argsort + """ + return np.argsort(stocks)[::-1][:3] -# def yellow_restock_cost(prices, stocks): -# """ -# 單價 < 500 的商品,每種各進貨 50 個,回傳總花費 (float/int) -# 提示:布林遮罩 + .sum() -# """ - # TODO: 你的程式碼 -# pass +def yellow_restock_cost(prices, stocks): + """ + 單價 < 500 的商品,每種各進貨 50 個,回傳總花費 (float/int) + 提示:布林遮罩 + .sum() + """ + return (prices[prices < 500] * 50).sum() # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ -# def red_double11_prices(prices, stocks): -# """ -# 雙 11 定價規則(必須向量化,不能用 for-loop): -# - 庫存 >= 100:打 7 折 -# - 庫存 20~99:打 9 折 -# - 庫存 < 20:原價 -# 回傳每個商品的雙 11 售價 (ndarray) -# 提示:np.where 可以巢狀使用 -# """ -# # TODO: 你的程式碼 -# pass +def red_double11_prices(prices, stocks): + """ + 雙 11 定價規則(必須向量化,不能用 for-loop): + - 庫存 >= 100:打 7 折 + - 庫存 20~99:打 9 折 + - 庫存 < 20:原價 + 回傳每個商品的雙 11 售價 (ndarray) + 提示:np.where 可以巢狀使用 + """ + return np.where(stocks >= 100, prices * 0.7, + np.where((stocks >= 20) & (stocks <= 99), prices * 0.9, prices)) \ No newline at end of file From bd3b6a072125945f1157cd43c6d5bb832f4f3d50 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 18:37:25 +0800 Subject: [PATCH 06/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m2_pandas_cleaning.py | 42 ++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..77e699b 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -9,7 +9,6 @@ """ import pandas as pd - # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ @@ -19,8 +18,8 @@ def green_read_csv(): 讀取 orders_raw.csv,回傳原始 DataFrame(不做任何清理) 提示:pd.read_csv() """ - # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_raw.csv") + return df def green_shape(df): @@ -28,8 +27,7 @@ def green_shape(df): 回傳 DataFrame 的 (列數, 欄數) tuple 提示:df.shape """ - # TODO: 你的程式碼 - pass + return df.shape def green_dtypes(df): @@ -37,9 +35,7 @@ def green_dtypes(df): 回傳 DataFrame 的欄位型別 (Series) 提示:df.dtypes """ - # TODO: 你的程式碼 - pass - + return df.dtypes # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) @@ -51,9 +47,15 @@ def yellow_clean_columns(df): 回傳清理後的 DataFrame(不要修改原始 df) 提示:df.columns.str.strip().str.lower() """ - # TODO: 你的程式碼 - pass + df2 = df.copy() + df2.columns = df.columns.str.strip().str.lower() + return df2 +# 1. 執行讀取與清理 +df = yellow_clean_columns(green_read_csv()) +# 2. 打印結果 +print(df.columns.tolist()) # 查看清理後的欄位名稱 +print(df.head()) # 查看資料內容 def yellow_clean_amount(df): """ @@ -62,9 +64,10 @@ def yellow_clean_amount(df): 回傳清理後的 DataFrame(不要修改原始 df) 提示:.str.replace() + .astype(float) """ - # TODO: 你的程式碼 - pass - + df2 = df.copy() + df2.columns = df.columns.str.strip().str.lower() + df2["amount"] = df2["amount"].str.replace("$", "").str.replace(",", "").astype(float) + return df2 def yellow_drop_duplicates(df): """ @@ -72,8 +75,8 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - pass - + df2 = df.copy() + return df2.drop_duplicates() # ============================================================ # 🔴 挑戰題(25 分) @@ -92,5 +95,10 @@ def red_clean_orders(path): 回傳:清理後的 DataFrame 提示:pd.to_datetime(errors='coerce') """ - # TODO: 你的程式碼 - pass + df = pd.read_csv(path) + df.columns = df.columns.str.strip().str.lower() + df["amount"] = df["amount"].str.replace("$", "").str.replace(",", "").astype(float) + df["order_date"] = pd.to_datetime(df["order_date"], errors="coerce") + df.dropna(subset=["amount", "order_date"], inplace=True) + df.drop_duplicates(inplace=True) + return df From ef973029b1f81006404d6e659bda08b5a20300de Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 18:44:29 +0800 Subject: [PATCH 07/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M2=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m3_pandas_advanced.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..9de4a08 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -24,9 +24,16 @@ def green_load_and_merge(): 提示:pd.merge(how='left') """ # TODO: 你的程式碼 - pass - - + path_prefix = r"C:\Users\W11 HOME\Desktop\python-da-homework-2026\datasets\ecommerce/" + + df_orders = pd.read_csv(path_prefix + "orders_clean.csv") + df_customers = pd.read_csv(path_prefix + "customers.csv") + df_products = pd.read_csv(path_prefix + "products.csv") + merged_df = pd.merge(df_orders, df_customers, on='customer_id', how='left') + final_df = pd.merge(merged_df, df_products, on='product_id', how='left') + + return final_df + def green_row_count(df): """回傳 DataFrame 的列數 (int)""" # TODO: 你的程式碼 From c29feba9ad9e2671ddc01db97657968141532515 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 19:25:04 +0800 Subject: [PATCH 08/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m3_pandas_advanced.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 9de4a08..3cb24f1 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -23,17 +23,21 @@ def green_load_and_merge(): - 再 LEFT JOIN products.csv ON product_id 提示:pd.merge(how='left') """ - # TODO: 你的程式碼 + path_prefix = r"C:\Users\W11 HOME\Desktop\python-da-homework-2026\datasets\ecommerce/" df_orders = pd.read_csv(path_prefix + "orders_clean.csv") df_customers = pd.read_csv(path_prefix + "customers.csv") df_products = pd.read_csv(path_prefix + "products.csv") + + + df_orders['order_date'] = pd.to_datetime(df_orders['order_date']) + merged_df = pd.merge(df_orders, df_customers, on='customer_id', how='left') final_df = pd.merge(merged_df, df_products, on='product_id', how='left') return final_df - + def green_row_count(df): """回傳 DataFrame 的列數 (int)""" # TODO: 你的程式碼 From 916f7541627fc55a55959329c81e705641661550 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 19:30:23 +0800 Subject: [PATCH 09/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m3_pandas_advanced.py | 64 ++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 3cb24f1..1760cbb 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -11,7 +11,6 @@ """ import pandas as pd - # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ @@ -21,18 +20,18 @@ def green_load_and_merge(): 讀取三張表,合併成一張完整的 DataFrame 並回傳 - orders_clean.csv LEFT JOIN customers.csv ON customer_id - 再 LEFT JOIN products.csv ON product_id - 提示:pd.merge(how='left') """ - path_prefix = r"C:\Users\W11 HOME\Desktop\python-da-homework-2026\datasets\ecommerce/" + # 讀取資料 df_orders = pd.read_csv(path_prefix + "orders_clean.csv") df_customers = pd.read_csv(path_prefix + "customers.csv") df_products = pd.read_csv(path_prefix + "products.csv") - + # 轉換日期格式 (確保 RFM 計算正常) df_orders['order_date'] = pd.to_datetime(df_orders['order_date']) + # 合併:先併客戶,再併產品 merged_df = pd.merge(df_orders, df_customers, on='customer_id', how='left') final_df = pd.merge(merged_df, df_products, on='product_id', how='left') @@ -40,14 +39,12 @@ def green_load_and_merge(): def green_row_count(df): """回傳 DataFrame 的列數 (int)""" - # TODO: 你的程式碼 - pass + return len(df) def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" - # TODO: 你的程式碼 - pass + return df.columns.tolist() # ============================================================ @@ -58,30 +55,27 @@ def yellow_top_category(df): """ 哪個商品類別 (category) 的總營收最高? 回傳該類別名稱 (str) - 提示:groupby('category')['amount'].sum() """ - # TODO: 你的程式碼 - pass + return df.groupby('category')['amount'].sum().idxmax() def yellow_gold_vip_stats(df): """ Gold VIP 客戶總共下了幾張訂單?總金額多少? 回傳 tuple: (訂單數 int, 總金額 float) - 提示:df[df['vip_level'] == 'Gold'] """ - # TODO: 你的程式碼 - pass + gold_df = df[df['vip_level'] == 'Gold'] + order_count = len(gold_df) + total_amount = gold_df['amount'].sum() + return (order_count, float(total_amount)) def yellow_region_avg_amount(df): """ 計算每個地區 (region) 的平均訂單金額 回傳 Series(index=region, values=平均金額) - 提示:groupby('region')['amount'].mean() """ - # TODO: 你的程式碼 - pass + return df.groupby('region')['amount'].mean() # ============================================================ @@ -91,18 +85,34 @@ def yellow_region_avg_amount(df): def red_rfm_top5(df): """ RFM 分析:找出最有價值的前 5 位客戶 - 計算每位客戶的: - R (Recency):最近一次下單日期 - F (Frequency):訂單總數 - M (Monetary):消費總金額 - - 回傳 DataFrame: - - 欄位:customer_id, customer_name, R, F, M - - 按 M 由大到小排序 - - 只取前 5 筆 - - 提示:groupby('customer_id').agg(...) """ - # TODO: 你的程式碼 - pass + # 聚合計算 RFM + rfm = df.groupby(['customer_id', 'customer_name']).agg( + R=('order_date', 'max'), + F=('order_id', 'count'), + M=('amount', 'sum') + ).reset_index() + + # 按 M (Monetary) 由大到小排序,取前 5 筆 + return rfm.sort_values('M', ascending=False).head(5) + +# ============================================================ +# 🏁 測試與驗收區 +# ============================================================ +if __name__ == "__main__": + try: + main_df = green_load_and_merge() + print(f"✅ 成功載入 {green_row_count(main_df)} 筆資料") + print(f"📊 營收最高類別:{yellow_top_category(main_df)}") + + vip_count, vip_sum = yellow_gold_vip_stats(main_df) + print(f"💎 Gold VIP:共 {vip_count} 筆訂單,總計 ${vip_sum:,.0f}") + + print("\n🏆 RFM 最有價值客戶 Top 5:") + print(red_rfm_top5(main_df)) + except Exception as e: + print(f"❌ 執行出錯:{e}") \ No newline at end of file From baad50f6e97dff5738510eac932e6424904be992 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 19:32:29 +0800 Subject: [PATCH 10/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m3_pandas_advanced.py | 54 +++++++++++++++------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 1760cbb..bf3ec33 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -11,6 +11,7 @@ """ import pandas as pd + # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ @@ -20,18 +21,19 @@ def green_load_and_merge(): 讀取三張表,合併成一張完整的 DataFrame 並回傳 - orders_clean.csv LEFT JOIN customers.csv ON customer_id - 再 LEFT JOIN products.csv ON product_id + 提示:pd.merge(how='left') """ - path_prefix = r"C:\Users\W11 HOME\Desktop\python-da-homework-2026\datasets\ecommerce/" + # 使用相對路徑,pytest 才能在專案根目錄執行成功 + path = "datasets/ecommerce/" - # 讀取資料 - df_orders = pd.read_csv(path_prefix + "orders_clean.csv") - df_customers = pd.read_csv(path_prefix + "customers.csv") - df_products = pd.read_csv(path_prefix + "products.csv") + df_orders = pd.read_csv(path + "orders_clean.csv") + df_customers = pd.read_csv(path + "customers.csv") + df_products = pd.read_csv(path + "products.csv") - # 轉換日期格式 (確保 RFM 計算正常) + # 轉換訂單日期為 datetime 格式 (RFM 分析必備) df_orders['order_date'] = pd.to_datetime(df_orders['order_date']) - # 合併:先併客戶,再併產品 + # 進行兩次合併 merged_df = pd.merge(df_orders, df_customers, on='customer_id', how='left') final_df = pd.merge(merged_df, df_products, on='product_id', how='left') @@ -55,6 +57,7 @@ def yellow_top_category(df): """ 哪個商品類別 (category) 的總營收最高? 回傳該類別名稱 (str) + 提示:groupby('category')['amount'].sum() """ return df.groupby('category')['amount'].sum().idxmax() @@ -63,17 +66,19 @@ def yellow_gold_vip_stats(df): """ Gold VIP 客戶總共下了幾張訂單?總金額多少? 回傳 tuple: (訂單數 int, 總金額 float) + 提示:df[df['vip_level'] == 'Gold'] """ gold_df = df[df['vip_level'] == 'Gold'] - order_count = len(gold_df) - total_amount = gold_df['amount'].sum() - return (order_count, float(total_amount)) + order_count = int(len(gold_df)) + total_amount = float(gold_df['amount'].sum()) + return (order_count, total_amount) def yellow_region_avg_amount(df): """ 計算每個地區 (region) 的平均訂單金額 回傳 Series(index=region, values=平均金額) + 提示:groupby('region')['amount'].mean() """ return df.groupby('region')['amount'].mean() @@ -85,34 +90,23 @@ def yellow_region_avg_amount(df): def red_rfm_top5(df): """ RFM 分析:找出最有價值的前 5 位客戶 + 計算每位客戶的: - R (Recency):最近一次下單日期 - F (Frequency):訂單總數 - M (Monetary):消費總金額 + + 回傳 DataFrame: + - 欄位:customer_id, customer_name, R, F, M + - 按 M 由大到小排序 + - 只取前 5 筆 + + 提示:groupby('customer_id').agg(...) """ - # 聚合計算 RFM rfm = df.groupby(['customer_id', 'customer_name']).agg( R=('order_date', 'max'), F=('order_id', 'count'), M=('amount', 'sum') ).reset_index() - # 按 M (Monetary) 由大到小排序,取前 5 筆 - return rfm.sort_values('M', ascending=False).head(5) - -# ============================================================ -# 🏁 測試與驗收區 -# ============================================================ -if __name__ == "__main__": - try: - main_df = green_load_and_merge() - print(f"✅ 成功載入 {green_row_count(main_df)} 筆資料") - print(f"📊 營收最高類別:{yellow_top_category(main_df)}") - - vip_count, vip_sum = yellow_gold_vip_stats(main_df) - print(f"💎 Gold VIP:共 {vip_count} 筆訂單,總計 ${vip_sum:,.0f}") - - print("\n🏆 RFM 最有價值客戶 Top 5:") - print(red_rfm_top5(main_df)) - except Exception as e: - print(f"❌ 執行出錯:{e}") \ No newline at end of file + return rfm.sort_values('M', ascending=False).head(5) \ No newline at end of file From 6b1b26485c4e8413ba2bf8febe9b6af301f9a049 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 23:44:12 +0800 Subject: [PATCH 11/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BD=9C=E6=A5=AD=20-?= =?UTF-8?q?=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m3_pandas_advanced.py | 7 +- homework/m4_timeseries.py | 50 +++++++++----- "homework/\346\270\254\350\251\246.ipynb" | 82 +++++++++++++++++++++++ 3 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 "homework/\346\270\254\350\251\246.ipynb" diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index bf3ec33..1fd2064 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -23,17 +23,16 @@ def green_load_and_merge(): - 再 LEFT JOIN products.csv ON product_id 提示:pd.merge(how='left') """ - # 使用相對路徑,pytest 才能在專案根目錄執行成功 + path = "datasets/ecommerce/" df_orders = pd.read_csv(path + "orders_clean.csv") df_customers = pd.read_csv(path + "customers.csv") df_products = pd.read_csv(path + "products.csv") - # 轉換訂單日期為 datetime 格式 (RFM 分析必備) - df_orders['order_date'] = pd.to_datetime(df_orders['order_date']) - # 進行兩次合併 + df_orders['order_date'] = pd.to_datetime(df_orders['order_date']) + merged_df = pd.merge(df_orders, df_customers, on='customer_id', how='left') final_df = pd.merge(merged_df, df_products, on='product_id', how='left') diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..1212577 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -15,29 +15,32 @@ def _load_data(): parse_dates=["order_date"]) return df +df = _load_data() +print(df.head()) + # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ -def green_avg_by_month(): +def green_avg_by_month(df): """ 計算每個月份 (1~12) 的平均訂單金額 回傳 Series(index=月份 1~12, values=平均金額) 提示:df['order_date'].dt.month """ - # TODO: 你的程式碼 - pass - + df['order_date'] = pd.to_datetime(df['order_date']) + return df.groupby(df['order_date'].dt.month)['amount'].mean() + -def green_top3_dates(): +def green_top3_dates(df): """ 找出訂單數最多的前 3 個日期 回傳 Series(index=日期, values=訂單數, 由多到少排序) 提示:value_counts().head(3) """ - # TODO: 你的程式碼 - pass + df['order_date'] = pd.to_datetime(df['order_date']) + return df['order_date'].value_counts().head(3) def green_date_range(): @@ -45,8 +48,11 @@ def green_date_range(): 回傳資料的日期範圍 tuple: (最早日期, 最晚日期) 格式為 pandas Timestamp """ - # TODO: 你的程式碼 - pass + df = _load_data() + df['order_date'] = pd.to_datetime(df['order_date']) + min_date = df['order_date'].min() + max_date = df['order_date'].max() + return (min_date, max_date) # ============================================================ @@ -59,8 +65,9 @@ def yellow_monthly_revenue(): 回傳 Series(index=月底日期 period, values=總營收) 提示:set_index('order_date').resample('ME')['amount'].sum() """ - # TODO: 你的程式碼 - pass + df = _load_data() + df['order_date'] = pd.to_datetime(df['order_date']) + return df.set_index('order_date').resample('ME')['amount'].sum() def yellow_rolling_avg(monthly_revenue): @@ -70,8 +77,7 @@ def yellow_rolling_avg(monthly_revenue): 回傳 Series(同樣 index,values=移動平均,前 2 筆可為 NaN) 提示:.rolling(window=3).mean() """ - # TODO: 你的程式碼 - pass + return monthly_revenue.rolling(window=3).mean() def yellow_category_median(df): @@ -80,8 +86,7 @@ def yellow_category_median(df): 回傳 Series(index=category, values=中位數) 提示:groupby + median + sort_values """ - # TODO: 你的程式碼 - pass + return df.groupby('category')['amount'].median().sort_values(ascending=False) # ============================================================ @@ -100,5 +105,16 @@ def red_monthly_report(): index 為月份 (period 或 datetime) 提示:resample + agg + pct_change """ - # TODO: 你的程式碼 - pass + df = _load_data() + df['order_date'] = pd.to_datetime(df['order_date']) + + monthly_report = df.set_index('order_date').resample('ME').agg( + order_count=('order_id', 'count'), + revenue=('amount', 'sum'), + active_customers=('customer_id', 'nunique') + ) + + monthly_report['avg_order_value'] = monthly_report['revenue'] / monthly_report['order_count'] + monthly_report['revenue_growth'] = monthly_report['revenue'].pct_change() * 100 + + return monthly_report diff --git "a/homework/\346\270\254\350\251\246.ipynb" "b/homework/\346\270\254\350\251\246.ipynb" new file mode 100644 index 0000000..448b443 --- /dev/null +++ "b/homework/\346\270\254\350\251\246.ipynb" @@ -0,0 +1,82 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 5, + "id": "98ddd952", + "metadata": {}, + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'pandas'", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mModuleNotFoundError\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 9\u001b[39m\n\u001b[32m 5\u001b[39m 產出月報級別的商業洞察。\n\u001b[32m 6\u001b[39m \n\u001b[32m 7\u001b[39m 資料路徑:datasets/ecommerce/orders_enriched.csv\n\u001b[32m 8\u001b[39m \"\"\"\n\u001b[32m----> \u001b[39m\u001b[32m9\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m pandas \u001b[38;5;28;01mas\u001b[39;00m pd\n\u001b[32m 10\u001b[39m \n\u001b[32m 11\u001b[39m \n\u001b[32m 12\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m _load_data():\n", + "\u001b[31mModuleNotFoundError\u001b[39m: No module named 'pandas'" + ] + } + ], + "source": [ + "\"\"\"\n", + "M4 時間序列與 EDA — 課後作業\n", + "==============================\n", + "情境:用合併好的訂單資料做時間維度分析,\n", + "產出月報級別的商業洞察。\n", + "\n", + "資料路徑:datasets/ecommerce/orders_enriched.csv\n", + "\"\"\"\n", + "import pandas as pd\n", + "\n", + "\n", + "def _load_data():\n", + " \"\"\"輔助函式:讀取並解析日期\"\"\"\n", + " df = pd.read_csv(\"datasets/ecommerce/orders_enriched.csv\",\n", + " parse_dates=[\"order_date\"])\n", + " return df\n", + "\n", + "\n", + "df = _load_data()\n", + "print(_load_data().head()) # 顯示前 5 筆資料\n", + "\n", + "\n", + "# ============================================================\n", + "# 🟢 送分題(每題 10 分,共 30 分)\n", + "# ============================================================\n", + "\n", + "def green_avg_by_month(df):\n", + " \"\"\"\n", + " 計算每個月份 (1~12) 的平均訂單金額\n", + " 回傳 Series(index=月份 1~12, values=平均金額)\n", + " 提示:df['order_date'].dt.month\n", + " \"\"\"\n", + " df['order_date'] = pd.to_datetime(df['order_date'])\n", + " return df.groupby(df['order_date'].dt.month)['amount'].mean()\n", + "\n", + "print(green_avg_by_month(df))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.10" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From a9094b860c014cdf5e58453a285b6a57495cc549 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 23:46:44 +0800 Subject: [PATCH 12/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BD=9C=E6=A5=AD=20-?= =?UTF-8?q?=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m4_timeseries.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 1212577..f54de71 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -29,8 +29,8 @@ def green_avg_by_month(df): 回傳 Series(index=月份 1~12, values=平均金額) 提示:df['order_date'].dt.month """ - df['order_date'] = pd.to_datetime(df['order_date']) - return df.groupby(df['order_date'].dt.month)['amount'].mean() + df['month'] = df['order_date'].dt.month + return df.groupby('month')['amount'].mean() def green_top3_dates(df): @@ -39,8 +39,8 @@ def green_top3_dates(df): 回傳 Series(index=日期, values=訂單數, 由多到少排序) 提示:value_counts().head(3) """ - df['order_date'] = pd.to_datetime(df['order_date']) - return df['order_date'].value_counts().head(3) + + return df['order_date'].dt.date.value_counts().head(3) def green_date_range(): From 9d6dad202f1e92ae76de5d26245b0385cb43f091 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 23:48:56 +0800 Subject: [PATCH 13/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BD=9C=E6=A5=AD=20-?= =?UTF-8?q?=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m4_timeseries.py | 2 +- "homework/\346\270\254\350\251\246.ipynb" | 52 ++++++++++++++--------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index f54de71..154117b 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -40,7 +40,7 @@ def green_top3_dates(df): 提示:value_counts().head(3) """ - return df['order_date'].dt.date.value_counts().head(3) + return df['order_date'].value_counts().head(3) def green_date_range(): diff --git "a/homework/\346\270\254\350\251\246.ipynb" "b/homework/\346\270\254\350\251\246.ipynb" index 448b443..abdb0dd 100644 --- "a/homework/\346\270\254\350\251\246.ipynb" +++ "b/homework/\346\270\254\350\251\246.ipynb" @@ -2,19 +2,26 @@ "cells": [ { "cell_type": "code", - "execution_count": 5, + "execution_count": 42, "id": "98ddd952", "metadata": {}, "outputs": [ { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'pandas'", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mModuleNotFoundError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[5]\u001b[39m\u001b[32m, line 9\u001b[39m\n\u001b[32m 5\u001b[39m 產出月報級別的商業洞察。\n\u001b[32m 6\u001b[39m \n\u001b[32m 7\u001b[39m 資料路徑:datasets/ecommerce/orders_enriched.csv\n\u001b[32m 8\u001b[39m \"\"\"\n\u001b[32m----> \u001b[39m\u001b[32m9\u001b[39m \u001b[38;5;28;01mimport\u001b[39;00m pandas \u001b[38;5;28;01mas\u001b[39;00m pd\n\u001b[32m 10\u001b[39m \n\u001b[32m 11\u001b[39m \n\u001b[32m 12\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m _load_data():\n", - "\u001b[31mModuleNotFoundError\u001b[39m: No module named 'pandas'" + "name": "stdout", + "output_type": "stream", + "text": [ + " order_id customer_id product_id qty order_date amount customer_name \\\n", + "0 5064 2022 1026 4.0 2025-03-26 8600.0 Victor Lin \n", + "1 5023 2026 1021 5.0 2025-01-05 1355.0 Zoe Huang \n", + "\n", + " region signup_date vip_level product_name category unit_price stock_qty \n", + "0 North 2023-02-27 Gold Dumbbell 5kg Sports 2150 51 \n", + "1 South 2023-05-16 Platinum Throw Pillow Home 271 150 \n", + "order_date\n", + "2025-01-02 3\n", + "2025-08-09 3\n", + "2025-08-25 3\n", + "Name: count, dtype: int64\n" ] } ], @@ -31,30 +38,37 @@ "\n", "\n", "def _load_data():\n", - " \"\"\"輔助函式:讀取並解析日期\"\"\"\n", - " df = pd.read_csv(\"datasets/ecommerce/orders_enriched.csv\",\n", - " parse_dates=[\"order_date\"])\n", + " \"\"\"輔助函式:使用絕對路徑讀取並解析日期\"\"\"\n", + " # 直接指向檔案在電腦裡的完整位置\n", + " path = r\"C:\\Users\\W11 HOME\\Desktop\\python-da-homework-2026\\datasets\\ecommerce\\orders_enriched.csv\"\n", + " df = pd.read_csv(path, parse_dates=[\"order_date\"])\n", " return df\n", "\n", "\n", "df = _load_data()\n", - "print(_load_data().head()) # 顯示前 5 筆資料\n", + "print(_load_data().head(2)) # 顯示前 5 筆資料\n", "\n", "\n", "# ============================================================\n", "# 🟢 送分題(每題 10 分,共 30 分)\n", "# ============================================================\n", "\n", - "def green_avg_by_month(df):\n", + "def green_top3_dates(df):\n", " \"\"\"\n", - " 計算每個月份 (1~12) 的平均訂單金額\n", - " 回傳 Series(index=月份 1~12, values=平均金額)\n", - " 提示:df['order_date'].dt.month\n", + " 找出訂單數最多的前 3 個日期\n", + " 回傳 Series(index=日期, values=訂單數, 由多到少排序)\n", + " 提示:value_counts().head(3)\n", " \"\"\"\n", + " # 1. 確保 order_date 欄位已轉換為 datetime 格式\n", " df['order_date'] = pd.to_datetime(df['order_date'])\n", - " return df.groupby(df['order_date'].dt.month)['amount'].mean()\n", + " \n", + " # 2. 使用 value_counts() 計算每個日期的出現次數(即訂單數)\n", + " # 3. 使用 head(3) 取得前三名並回傳\n", + " return df['order_date'].value_counts().head(3)\n", "\n", - "print(green_avg_by_month(df))" + "# --- 測試執行 (請確保這兩行在最左邊,不要縮排) ---\n", + "df = _load_data() \n", + "print(green_top3_dates(df))" ] } ], From 05f889b2732948b87090509f235368898ed39708 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Sun, 3 May 2026 23:51:02 +0800 Subject: [PATCH 14/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BD=9C=E6=A5=AD=20-?= =?UTF-8?q?=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m4_timeseries.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 154117b..6615c4b 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -23,26 +23,27 @@ def _load_data(): # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ -def green_avg_by_month(df): +def green_avg_by_month(): """ 計算每個月份 (1~12) 的平均訂單金額 回傳 Series(index=月份 1~12, values=平均金額) 提示:df['order_date'].dt.month """ - df['month'] = df['order_date'].dt.month - return df.groupby('month')['amount'].mean() + df = _load_data() + df['order_date'] = pd.to_datetime(df['order_date']) + return df.groupby(df['order_date'].dt.month)['amount'].mean() -def green_top3_dates(df): +def green_top3_dates(): """ 找出訂單數最多的前 3 個日期 回傳 Series(index=日期, values=訂單數, 由多到少排序) 提示:value_counts().head(3) """ - + df = _load_data() + df['order_date'] = pd.to_datetime(df['order_date']) return df['order_date'].value_counts().head(3) - def green_date_range(): """ 回傳資料的日期範圍 tuple: (最早日期, 最晚日期) From d646c1be6a5c565b0d971b8c5d70280d4bd06d4e Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 12:21:49 +0800 Subject: [PATCH 15/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 28 ++++--- homework/m4_timeseries.py | 6 +- "homework/\346\270\254\350\251\246.ipynb" | 96 ----------------------- 3 files changed, 17 insertions(+), 113 deletions(-) delete mode 100644 "homework/\346\270\254\350\251\246.ipynb" diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index fa262f0..106523b 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -19,21 +19,21 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" arr = np.array([10, 20, 30, 40, 50]) - return arr.mean() - - + return arr def green_double(): """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" arr = np.array([10, 20, 30, 40, 50]) - return arr * 2 + arr = arr*2 + return arr def green_filter(): """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" arr = np.array([10, 20, 30, 40, 50]) - return arr[arr > 25] + arr = arr[arr>25] + return arr # ============================================================ @@ -48,24 +48,25 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" - return prices[prices>1000].size - + rrrr = len(prices[prices>1000]) + return rrrr def yellow_top3_stock_indices(stocks): """ 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) 提示:np.argsort """ - return np.argsort(stocks)[::-1][:3] - + GGGG = np.argsort(stocks)[::-1][:3] + return GGGG def yellow_restock_cost(prices, stocks): """ 單價 < 500 的商品,每種各進貨 50 個,回傳總花費 (float/int) 提示:布林遮罩 + .sum() """ - return (prices[prices < 500] * 50).sum() - + rrrr = (prices[prices<500]*50).sum() + + return rrrr # ============================================================ # 🔴 挑戰題(25 分) @@ -80,5 +81,6 @@ def red_double11_prices(prices, stocks): 回傳每個商品的雙 11 售價 (ndarray) 提示:np.where 可以巢狀使用 """ - return np.where(stocks >= 100, prices * 0.7, - np.where((stocks >= 20) & (stocks <= 99), prices * 0.9, prices)) \ No newline at end of file + new_prices = np.where(stocks>=100 ,prices * 0.7,np.where((stocks>=20)&(stocks<100),prices*0.9,prices)) + print(new_prices) + return new_prices diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 6615c4b..e213e55 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -15,10 +15,7 @@ def _load_data(): parse_dates=["order_date"]) return df -df = _load_data() -print(df.head()) - - +print(_load_data().head()) # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ @@ -29,6 +26,7 @@ def green_avg_by_month(): 回傳 Series(index=月份 1~12, values=平均金額) 提示:df['order_date'].dt.month """ + df = _load_data() df['order_date'] = pd.to_datetime(df['order_date']) return df.groupby(df['order_date'].dt.month)['amount'].mean() diff --git "a/homework/\346\270\254\350\251\246.ipynb" "b/homework/\346\270\254\350\251\246.ipynb" deleted file mode 100644 index abdb0dd..0000000 --- "a/homework/\346\270\254\350\251\246.ipynb" +++ /dev/null @@ -1,96 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 42, - "id": "98ddd952", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " order_id customer_id product_id qty order_date amount customer_name \\\n", - "0 5064 2022 1026 4.0 2025-03-26 8600.0 Victor Lin \n", - "1 5023 2026 1021 5.0 2025-01-05 1355.0 Zoe Huang \n", - "\n", - " region signup_date vip_level product_name category unit_price stock_qty \n", - "0 North 2023-02-27 Gold Dumbbell 5kg Sports 2150 51 \n", - "1 South 2023-05-16 Platinum Throw Pillow Home 271 150 \n", - "order_date\n", - "2025-01-02 3\n", - "2025-08-09 3\n", - "2025-08-25 3\n", - "Name: count, dtype: int64\n" - ] - } - ], - "source": [ - "\"\"\"\n", - "M4 時間序列與 EDA — 課後作業\n", - "==============================\n", - "情境:用合併好的訂單資料做時間維度分析,\n", - "產出月報級別的商業洞察。\n", - "\n", - "資料路徑:datasets/ecommerce/orders_enriched.csv\n", - "\"\"\"\n", - "import pandas as pd\n", - "\n", - "\n", - "def _load_data():\n", - " \"\"\"輔助函式:使用絕對路徑讀取並解析日期\"\"\"\n", - " # 直接指向檔案在電腦裡的完整位置\n", - " path = r\"C:\\Users\\W11 HOME\\Desktop\\python-da-homework-2026\\datasets\\ecommerce\\orders_enriched.csv\"\n", - " df = pd.read_csv(path, parse_dates=[\"order_date\"])\n", - " return df\n", - "\n", - "\n", - "df = _load_data()\n", - "print(_load_data().head(2)) # 顯示前 5 筆資料\n", - "\n", - "\n", - "# ============================================================\n", - "# 🟢 送分題(每題 10 分,共 30 分)\n", - "# ============================================================\n", - "\n", - "def green_top3_dates(df):\n", - " \"\"\"\n", - " 找出訂單數最多的前 3 個日期\n", - " 回傳 Series(index=日期, values=訂單數, 由多到少排序)\n", - " 提示:value_counts().head(3)\n", - " \"\"\"\n", - " # 1. 確保 order_date 欄位已轉換為 datetime 格式\n", - " df['order_date'] = pd.to_datetime(df['order_date'])\n", - " \n", - " # 2. 使用 value_counts() 計算每個日期的出現次數(即訂單數)\n", - " # 3. 使用 head(3) 取得前三名並回傳\n", - " return df['order_date'].value_counts().head(3)\n", - "\n", - "# --- 測試執行 (請確保這兩行在最左邊,不要縮排) ---\n", - "df = _load_data() \n", - "print(green_top3_dates(df))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.10" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From 958caf416173209a4cbda0a6cd7a1c2f51908f35 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 16:34:49 +0800 Subject: [PATCH 16/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 3 ++- homework/m2_pandas_cleaning.py | 45 +++++++++++++++------------------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 106523b..e225f8d 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -18,7 +18,8 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" - arr = np.array([10, 20, 30, 40, 50]) + arr = np.array([10, 20, 30, 40, 50]).mean() + print(arr) return arr def green_double(): diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index 77e699b..984b86a 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -16,9 +16,9 @@ def green_read_csv(): """ 讀取 orders_raw.csv,回傳原始 DataFrame(不做任何清理) - 提示:pd.read_csv() + 提示:pd.read_csv(s) """ - df = pd.read_csv("datasets/ecommerce/orders_raw.csv") + df = pd.read_csv('datasets/ecommerce/orders_raw.csv') return df @@ -27,7 +27,7 @@ def green_shape(df): 回傳 DataFrame 的 (列數, 欄數) tuple 提示:df.shape """ - return df.shape + return df.shape def green_dtypes(df): @@ -47,15 +47,8 @@ def yellow_clean_columns(df): 回傳清理後的 DataFrame(不要修改原始 df) 提示:df.columns.str.strip().str.lower() """ - df2 = df.copy() - df2.columns = df.columns.str.strip().str.lower() - return df2 -# 1. 執行讀取與清理 -df = yellow_clean_columns(green_read_csv()) - -# 2. 打印結果 -print(df.columns.tolist()) # 查看清理後的欄位名稱 -print(df.head()) # 查看資料內容 + clear = df.columns.str.strip().str.lower() + return clear def yellow_clean_amount(df): """ @@ -64,25 +57,26 @@ def yellow_clean_amount(df): 回傳清理後的 DataFrame(不要修改原始 df) 提示:.str.replace() + .astype(float) """ - df2 = df.copy() - df2.columns = df.columns.str.strip().str.lower() - df2["amount"] = df2["amount"].str.replace("$", "").str.replace(",", "").astype(float) - return df2 + new_df = df.copy() + new_df['amount'] = new_df['amount'].str.replace('$','',regex=False).str.replace(',','',regex=False).astype(float) + return new_df def yellow_drop_duplicates(df): """ 移除完全重複的列,回傳去重後的 DataFrame 提示:df.drop_duplicates() """ - # TODO: 你的程式碼 - df2 = df.copy() - return df2.drop_duplicates() + new_df = df.copy() + new_df = new_df.drop_duplicates(subset=None, keep='first', inplace=False) + return new_df + + # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ -def red_clean_orders(path): +def red_clean_orders(): """ 完整清理 pipeline:一個函式搞定所有清理步驟 1. 讀取 CSV @@ -95,10 +89,11 @@ def red_clean_orders(path): 回傳:清理後的 DataFrame 提示:pd.to_datetime(errors='coerce') """ - df = pd.read_csv(path) + df = pd.read_csv('datasets/ecommerce/orders_raw.csv') df.columns = df.columns.str.strip().str.lower() - df["amount"] = df["amount"].str.replace("$", "").str.replace(",", "").astype(float) - df["order_date"] = pd.to_datetime(df["order_date"], errors="coerce") - df.dropna(subset=["amount", "order_date"], inplace=True) - df.drop_duplicates(inplace=True) + df['amount'] = df['amount'].str.replace('$','',regex=False).str.replace(',','',regex=False).astype(float) + df['order_date'] = pd.to_datetime(df['order_date'],errors='coerce') + df = df.dropna(subset= ['amount','order_date']) + df = df.drop_duplicates(subset=None, keep='first', inplace=False) return df + From df8c1e8fe0d5aa5a98f71d9835b8ac2e25380161 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 16:47:55 +0800 Subject: [PATCH 17/28] Update m2_pandas_cleaning.py --- homework/m2_pandas_cleaning.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index 984b86a..837a591 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -47,8 +47,9 @@ def yellow_clean_columns(df): 回傳清理後的 DataFrame(不要修改原始 df) 提示:df.columns.str.strip().str.lower() """ - clear = df.columns.str.strip().str.lower() - return clear + new_df = df.copy() + new_df.columns = new_df.columns.str.strip().str.lower() + return new_df def yellow_clean_amount(df): """ @@ -76,7 +77,7 @@ def yellow_drop_duplicates(df): # 🔴 挑戰題(25 分) # ============================================================ -def red_clean_orders(): +def red_clean_orders(path): """ 完整清理 pipeline:一個函式搞定所有清理步驟 1. 讀取 CSV @@ -89,7 +90,7 @@ def red_clean_orders(): 回傳:清理後的 DataFrame 提示:pd.to_datetime(errors='coerce') """ - df = pd.read_csv('datasets/ecommerce/orders_raw.csv') + df = pd.read_csv(path) df.columns = df.columns.str.strip().str.lower() df['amount'] = df['amount'].str.replace('$','',regex=False).str.replace(',','',regex=False).astype(float) df['order_date'] = pd.to_datetime(df['order_date'],errors='coerce') From 1dc88244e2b5c4b00f8ea35619bb74b909847c93 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 18:27:23 +0800 Subject: [PATCH 18/28] Update m3_pandas_advanced.py --- homework/m3_pandas_advanced.py | 66 ++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 1fd2064..dbe2f08 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -29,36 +29,50 @@ def green_load_and_merge(): df_orders = pd.read_csv(path + "orders_clean.csv") df_customers = pd.read_csv(path + "customers.csv") df_products = pd.read_csv(path + "products.csv") - - - df_orders['order_date'] = pd.to_datetime(df_orders['order_date']) - - merged_df = pd.merge(df_orders, df_customers, on='customer_id', how='left') - final_df = pd.merge(merged_df, df_products, on='product_id', how='left') - - return final_df + + m1 = pd.merge(df_orders,df_customers,on='customer_id',how = 'left') + m2 = pd.merge(m1,df_products,on='product_id',how = 'left') + + return m2 + + + def green_row_count(df): """回傳 DataFrame 的列數 (int)""" return len(df) - def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" return df.columns.tolist() - # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ +# def yellow_top_category(df): +# """ +# 哪個商品類別 (category) 的總營收最高? +# 回傳該類別名稱 (str) +# 提示:groupby('category')['amount'].sum() +# """ +# # return df.groupby('category')['amount'].sum().idxmax() +# df = df.groupby('category')['amount'].sum().idxmax() +# print(df) +# return df +# print(green_load_and_merge()) + + def yellow_top_category(df): """ 哪個商品類別 (category) 的總營收最高? - 回傳該類別名稱 (str) - 提示:groupby('category')['amount'].sum() """ - return df.groupby('category')['amount'].sum().idxmax() + + category_totals = df.groupby('category')['amount'].sum().idxmax() + + return category_totals + + def yellow_gold_vip_stats(df): @@ -67,10 +81,11 @@ def yellow_gold_vip_stats(df): 回傳 tuple: (訂單數 int, 總金額 float) 提示:df[df['vip_level'] == 'Gold'] """ + gold_df = df[df['vip_level'] == 'Gold'] - order_count = int(len(gold_df)) - total_amount = float(gold_df['amount'].sum()) - return (order_count, total_amount) + order_count = len(gold_df) + total_amount = gold_df['amount'].sum() + return (int(order_count), float(total_amount)) def yellow_region_avg_amount(df): @@ -79,7 +94,8 @@ def yellow_region_avg_amount(df): 回傳 Series(index=region, values=平均金額) 提示:groupby('region')['amount'].mean() """ - return df.groupby('region')['amount'].mean() + return df.groupby('category')['amount'].mean() + # ============================================================ @@ -102,10 +118,14 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ - rfm = df.groupby(['customer_id', 'customer_name']).agg( - R=('order_date', 'max'), - F=('order_id', 'count'), - M=('amount', 'sum') - ).reset_index() + + rrr = df.groupby(['customer_id','customer_name']).agg( + {'order_date':'max','order_id':'count','amount':'sum'} + ).reset_index() + rrr.columns = ['customer_id','customer_name','R','F','M'] + result = rrr.sort_values(by='M',ascending=False).head(5) + return result - return rfm.sort_values('M', ascending=False).head(5) \ No newline at end of file + + + From 2c34d853c714f31ac2d9e64fbe21233db8041682 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 18:29:33 +0800 Subject: [PATCH 19/28] Update m3_pandas_advanced.py --- homework/m3_pandas_advanced.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index dbe2f08..3b2cf4d 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -94,7 +94,7 @@ def yellow_region_avg_amount(df): 回傳 Series(index=region, values=平均金額) 提示:groupby('region')['amount'].mean() """ - return df.groupby('category')['amount'].mean() + return df.groupby('region')['amount'].mean() From 16029fdc2ded125a14d66fa0af641db1a35afe15 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 19:27:33 +0800 Subject: [PATCH 20/28] Update m4_timeseries.py --- homework/m4_timeseries.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index e213e55..ff8dccb 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -15,7 +15,7 @@ def _load_data(): parse_dates=["order_date"]) return df -print(_load_data().head()) + # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ @@ -29,8 +29,9 @@ def green_avg_by_month(): df = _load_data() df['order_date'] = pd.to_datetime(df['order_date']) - return df.groupby(df['order_date'].dt.month)['amount'].mean() - + df = df.groupby(df['order_date'].dt.month)['amount'].mean() + print(df) + return df def green_top3_dates(): """ @@ -40,8 +41,9 @@ def green_top3_dates(): """ df = _load_data() df['order_date'] = pd.to_datetime(df['order_date']) + print(df) return df['order_date'].value_counts().head(3) - +green_top3_dates() def green_date_range(): """ 回傳資料的日期範圍 tuple: (最早日期, 最晚日期) From 62e93fb163d5d9b62294f9324ebec4c3777a64ac Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 20:30:48 +0800 Subject: [PATCH 21/28] Update m4_timeseries.py --- homework/m4_timeseries.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index ff8dccb..dc467df 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -43,7 +43,7 @@ def green_top3_dates(): df['order_date'] = pd.to_datetime(df['order_date']) print(df) return df['order_date'].value_counts().head(3) -green_top3_dates() + def green_date_range(): """ 回傳資料的日期範圍 tuple: (最早日期, 最晚日期) @@ -53,8 +53,9 @@ def green_date_range(): df['order_date'] = pd.to_datetime(df['order_date']) min_date = df['order_date'].min() max_date = df['order_date'].max() - return (min_date, max_date) + return (min_date,max_date) +green_date_range() # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) @@ -68,8 +69,9 @@ def yellow_monthly_revenue(): """ df = _load_data() df['order_date'] = pd.to_datetime(df['order_date']) - return df.set_index('order_date').resample('ME')['amount'].sum() - + df = df.set_index(['order_date']).resample('ME')['amount'].sum() + return df + def yellow_rolling_avg(monthly_revenue): """ @@ -78,7 +80,9 @@ def yellow_rolling_avg(monthly_revenue): 回傳 Series(同樣 index,values=移動平均,前 2 筆可為 NaN) 提示:.rolling(window=3).mean() """ + return monthly_revenue.rolling(window=3).mean() + def yellow_category_median(df): @@ -87,7 +91,13 @@ def yellow_category_median(df): 回傳 Series(index=category, values=中位數) 提示:groupby + median + sort_values """ - return df.groupby('category')['amount'].median().sort_values(ascending=False) + df = _load_data() + df = df.groupby('category')['amount'].median().sort_values(ascending=False) + print(df.head()) + return df + + + # ============================================================ From 22c8d8973188890cfb73ff5cc38e838a75da2792 Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 20:39:20 +0800 Subject: [PATCH 22/28] Update m5_visualization.py --- homework/m5_visualization.py | 104 ++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 15 deletions(-) diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..f621aa5 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -28,9 +28,15 @@ def green_bar_category(): 回傳 matplotlib Figure 物件 提示:sns.countplot 或 value_counts().plot.bar() """ - # TODO: 你的程式碼 - pass - + df = _load_data() + plt.figure(figsize=(8, 5)) + sns.countplot(x='category', data=df) + plt.title('Number of Orders by Category') + plt.xlabel('Category') + plt.ylabel('Number of Orders') + plt.xticks(rotation=45) + plt.tight_layout() + return plt.gcf() def green_hist_amount(): """ @@ -38,8 +44,14 @@ def green_hist_amount(): 回傳 matplotlib Figure 物件 提示:sns.histplot(bins=20) 或 plt.hist() """ - # TODO: 你的程式碼 - pass + df = _load_data() + plt.figure(figsize=(8, 5)) + sns.histplot(df['amount'], bins=20, kde=False) + plt.title('Distribution of Order Amount') + plt.xlabel('Order Amount') + plt.ylabel('Frequency') + plt.tight_layout() + return plt.gcf() def green_set_labels(): @@ -50,8 +62,14 @@ def green_set_labels(): - Y 軸標籤 (ylabel) 回傳 matplotlib Figure 物件 """ - # TODO: 你的程式碼 - pass + df = _load_data() + plt.figure(figsize=(8, 5)) + sns.countplot(x='region', data=df) + plt.title('Number of Orders by Region') + plt.xlabel('Region') + plt.ylabel('Number of Orders') + plt.tight_layout() + return plt.gcf() # ============================================================ @@ -67,8 +85,18 @@ def yellow_line_region_trend(): 回傳 matplotlib Figure 物件 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ - # TODO: 你的程式碼 - pass + df = _load_data() + df['order_date'] = pd.to_datetime(df['order_date']) + df.set_index('order_date', inplace=True) + monthly_revenue = df.groupby(['region', pd.Grouper(freq='M')'])['amount'].sum().reset_index() + plt.figure(figsize=(10, 6)) + sns.lineplot(x='order_date', y='amount', hue='region', data=monthly_revenue) + plt.title('Monthly Revenue Trend by Region') + plt.xlabel('Month') + plt.ylabel('Total Revenue') + plt.legend(title='Region') + plt.tight_layout() + return plt.gcf() def yellow_box_vip(): @@ -77,8 +105,15 @@ def yellow_box_vip(): 回傳 matplotlib Figure 物件 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ - # TODO: 你的程式碼 - pass + df = _load_data() + plt.figure(figsize=(8, 5)) + sns.boxplot(x='vip_level', y='amount', data=df) + plt.title('Order Amount Distribution by VIP Level') + plt.xlabel('VIP Level') + plt.ylabel('Order Amount') + plt.tight_layout() + return plt.gcf() + def yellow_scatter_price_amount(): @@ -87,8 +122,14 @@ def yellow_scatter_price_amount(): 回傳 matplotlib Figure 物件 提示:plt.scatter() 或 sns.scatterplot() """ - # TODO: 你的程式碼 - pass + df = _load_data() + plt.figure(figsize=(8, 5)) + sns.scatterplot(x='unit_price', y='amount', data=df) + plt.title('Scatter Plot of Unit Price vs Order Amount') + plt.xlabel('Unit Price') + plt.ylabel('Order Amount') + plt.tight_layout() + return plt.gcf() # ============================================================ @@ -106,5 +147,38 @@ def red_category_dashboard(category="Electronics"): 回傳 matplotlib Figure 物件 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ - # TODO: 你的程式碼 - pass + df = _load_data() + df['order_date'] = pd.to_datetime(df['order_date']) + category_df = df[df['category'] == category] + + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + + # 左上:月營收趨勢 + monthly_revenue = category_df.set_index('order_date').resample('M')['amount'].sum() + sns.lineplot(x=monthly_revenue.index, y=monthly_revenue.values, ax=axes[0, 0]) + axes[0, 0].set_title(f'Monthly Revenue Trend for {category}') + axes[0, 0].set_xlabel('Month') + axes[0, 0].set_ylabel('Total Revenue') + + # 右上:各地區營收 + region_revenue = category_df.groupby('region')['amount'].sum().reset_index() + sns.barplot(x='region', y='amount', data=region_revenue, ax=axes[0, 1]) + axes[0, 1].set_title(f'Revenue by Region for {category}') + axes[0, 1].set_xlabel('Region') + axes[0, 1].set_ylabel('Total Revenue') + + # 左下:Top 5 商品營收 + top_products = category_df.groupby('product_name')['amount'].sum().nlargest(5).reset_index() + sns.barplot(x='amount', y='product_name', data=top_products, ax=axes[1, 0], orient='h') + axes[1, 0].set_title(f'Top 5 Products by Revenue for {category}') + axes[1, 0].set_xlabel('Total Revenue') + axes[1, 0].set_ylabel('Product Name') + + # 右下:訂單金額分佈 + sns.histplot(category_df['amount'], bins=20, kde=False, ax=axes[1, 1]) + axes[1, 1].set_title(f'Order Amount Distribution for {category}') + axes[1, 1].set_xlabel('Order Amount') + axes[1, 1].set_ylabel('Frequency') + + plt.tight_layout() + return fig From 42c990233613929ccbc243c33e823b22d954ad2d Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 20:41:27 +0800 Subject: [PATCH 23/28] Update m6_plotly_capstone.py --- homework/m6_plotly_capstone.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..8cff170 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -25,8 +25,11 @@ def green_plotly_bar(): 回傳 plotly Figure 物件 提示:px.bar() """ - # TODO: 你的程式碼 - pass + df = _load_data() + category_revenue = df.groupby('category')['amount'].sum() + fig = px.bar(x=category_revenue.index, y=category_revenue.values, title='Total Revenue by Category') + return fig + def green_plotly_line(): From 5467374130841200d2d1901b58512fc6423c181f Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 20:49:11 +0800 Subject: [PATCH 24/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 2 +- homework/m2_pandas_cleaning.py | 2 +- homework/m3_pandas_advanced.py | 1 + homework/m4_timeseries.py | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index e225f8d..c48b408 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -15,7 +15,7 @@ # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ - + def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" arr = np.array([10, 20, 30, 40, 50]).mean() diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index 837a591..b0c8462 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -97,4 +97,4 @@ def red_clean_orders(path): df = df.dropna(subset= ['amount','order_date']) df = df.drop_duplicates(subset=None, keep='first', inplace=False) return df - + diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 3b2cf4d..f1e7246 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -124,6 +124,7 @@ def red_rfm_top5(df): ).reset_index() rrr.columns = ['customer_id','customer_name','R','F','M'] result = rrr.sort_values(by='M',ascending=False).head(5) + print(result) return result diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index dc467df..f63682b 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -129,3 +129,4 @@ def red_monthly_report(): monthly_report['revenue_growth'] = monthly_report['revenue'].pct_change() * 100 return monthly_report + \ No newline at end of file From f1479b5ae7a36a125e3c39a26c0bca65c629393d Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 20:53:50 +0800 Subject: [PATCH 25/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=20M1=20=E4=BD=9C?= =?UTF-8?q?=E6=A5=AD=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m5_visualization.py | 131 ++++++++++++++--------------------- 1 file changed, 52 insertions(+), 79 deletions(-) diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index f621aa5..7bfbef1 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -29,14 +29,9 @@ def green_bar_category(): 提示:sns.countplot 或 value_counts().plot.bar() """ df = _load_data() - plt.figure(figsize=(8, 5)) - sns.countplot(x='category', data=df) - plt.title('Number of Orders by Category') - plt.xlabel('Category') - plt.ylabel('Number of Orders') - plt.xticks(rotation=45) - plt.tight_layout() - return plt.gcf() + fig, ax = plt.subplots() + sns.countplot(x='category', data=df, ax=ax) + return fig def green_hist_amount(): """ @@ -45,13 +40,9 @@ def green_hist_amount(): 提示:sns.histplot(bins=20) 或 plt.hist() """ df = _load_data() - plt.figure(figsize=(8, 5)) - sns.histplot(df['amount'], bins=20, kde=False) - plt.title('Distribution of Order Amount') - plt.xlabel('Order Amount') - plt.ylabel('Frequency') - plt.tight_layout() - return plt.gcf() + fig, ax = plt.subplots() + sns.histplot(df['amount'], bins=20, ax=ax) + return fig def green_set_labels(): @@ -63,13 +54,12 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ df = _load_data() - plt.figure(figsize=(8, 5)) - sns.countplot(x='region', data=df) - plt.title('Number of Orders by Region') - plt.xlabel('Region') - plt.ylabel('Number of Orders') - plt.tight_layout() - return plt.gcf() + fig, ax = plt.subplots() + df['category'].value_counts().plot.bar(ax=ax) + ax.set_title("Order Count by Category") + ax.set_xlabel("Category") + ax.set_ylabel("Count") + return fig # ============================================================ @@ -86,17 +76,18 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ df = _load_data() - df['order_date'] = pd.to_datetime(df['order_date']) - df.set_index('order_date', inplace=True) - monthly_revenue = df.groupby(['region', pd.Grouper(freq='M')'])['amount'].sum().reset_index() - plt.figure(figsize=(10, 6)) - sns.lineplot(x='order_date', y='amount', hue='region', data=monthly_revenue) - plt.title('Monthly Revenue Trend by Region') - plt.xlabel('Month') - plt.ylabel('Total Revenue') - plt.legend(title='Region') - plt.tight_layout() - return plt.gcf() + # 建立月份欄位 (如: 2025-01) + df['month'] = df['order_date'].dt.to_period('M').astype(str) + + # 過濾地區並計算月營收 + df_filtered = df[df['region'].isin(['North', 'South'])] + monthly_revenue = df_filtered.groupby(['month', 'region'])['amount'].sum().reset_index() + + fig, ax = plt.subplots(figsize=(10, 6)) + sns.lineplot(x='month', y='amount', hue='region', data=monthly_revenue, ax=ax, marker='o') + plt.xticks(rotation=45) + ax.legend(title='Region') + return fig def yellow_box_vip(): @@ -106,14 +97,9 @@ def yellow_box_vip(): 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ df = _load_data() - plt.figure(figsize=(8, 5)) - sns.boxplot(x='vip_level', y='amount', data=df) - plt.title('Order Amount Distribution by VIP Level') - plt.xlabel('VIP Level') - plt.ylabel('Order Amount') - plt.tight_layout() - return plt.gcf() - + fig, ax = plt.subplots() + sns.boxplot(x='vip_level', y='amount', data=df, ax=ax) + return fig def yellow_scatter_price_amount(): @@ -123,13 +109,9 @@ def yellow_scatter_price_amount(): 提示:plt.scatter() 或 sns.scatterplot() """ df = _load_data() - plt.figure(figsize=(8, 5)) - sns.scatterplot(x='unit_price', y='amount', data=df) - plt.title('Scatter Plot of Unit Price vs Order Amount') - plt.xlabel('Unit Price') - plt.ylabel('Order Amount') - plt.tight_layout() - return plt.gcf() + fig, ax = plt.subplots() + sns.scatterplot(x='unit_price', y='amount', data=df, ax=ax) + return fig # ============================================================ @@ -148,37 +130,28 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ df = _load_data() - df['order_date'] = pd.to_datetime(df['order_date']) - category_df = df[df['category'] == category] + cat_df = df[df['category'] == category].copy() + cat_df['month'] = cat_df['order_date'].dt.to_period('M').astype(str) fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle(f"Dashboard for {category}", fontsize=16) - # 左上:月營收趨勢 - monthly_revenue = category_df.set_index('order_date').resample('M')['amount'].sum() - sns.lineplot(x=monthly_revenue.index, y=monthly_revenue.values, ax=axes[0, 0]) - axes[0, 0].set_title(f'Monthly Revenue Trend for {category}') - axes[0, 0].set_xlabel('Month') - axes[0, 0].set_ylabel('Total Revenue') - - # 右上:各地區營收 - region_revenue = category_df.groupby('region')['amount'].sum().reset_index() - sns.barplot(x='region', y='amount', data=region_revenue, ax=axes[0, 1]) - axes[0, 1].set_title(f'Revenue by Region for {category}') - axes[0, 1].set_xlabel('Region') - axes[0, 1].set_ylabel('Total Revenue') - - # 左下:Top 5 商品營收 - top_products = category_df.groupby('product_name')['amount'].sum().nlargest(5).reset_index() - sns.barplot(x='amount', y='product_name', data=top_products, ax=axes[1, 0], orient='h') - axes[1, 0].set_title(f'Top 5 Products by Revenue for {category}') - axes[1, 0].set_xlabel('Total Revenue') - axes[1, 0].set_ylabel('Product Name') - - # 右下:訂單金額分佈 - sns.histplot(category_df['amount'], bins=20, kde=False, ax=axes[1, 1]) - axes[1, 1].set_title(f'Order Amount Distribution for {category}') - axes[1, 1].set_xlabel('Order Amount') - axes[1, 1].set_ylabel('Frequency') - - plt.tight_layout() - return fig + # 1. 左上:該類別月營收趨勢 (折線圖) + cat_df.groupby('month')['amount'].sum().plot(ax=axes[0, 0], marker='s') + axes[0, 0].set_title("Monthly Revenue Trend") + + # 2. 右上:該類別各地區營收 (長條圖) + cat_df.groupby('region')['amount'].sum().plot(kind='bar', ax=axes[0, 1]) + axes[0, 1].set_title("Revenue by Region") + + # 3. 左下:該類別 Top 5 商品營收 (水平長條圖) + top5 = cat_df.groupby('product_name')['amount'].sum().sort_values(ascending=False).head(5) + top5.sort_values().plot(kind='barh', ax=axes[1, 0]) + axes[1, 0].set_title("Top 5 Products by Revenue") + + # 4. 右下:該類別訂單金額分佈 (直方圖) + sns.histplot(cat_df['amount'], bins=15, ax=axes[1, 1], kde=True) + axes[1, 1].set_title("Order Amount Distribution") + + plt.tight_layout(rect=[0, 0.03, 1, 0.95]) + return fig \ No newline at end of file From 499d5c6fcdff8171fda0b08127761b4e708f477b Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 20:57:34 +0800 Subject: [PATCH 26/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BD=9C=E6=A5=AD=20-?= =?UTF-8?q?=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m6_plotly_capstone.py | 86 +++++++++++++++++++++++++++------- 1 file changed, 70 insertions(+), 16 deletions(-) diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 8cff170..77fa423 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -25,13 +25,12 @@ def green_plotly_bar(): 回傳 plotly Figure 物件 提示:px.bar() """ - df = _load_data() - category_revenue = df.groupby('category')['amount'].sum() - fig = px.bar(x=category_revenue.index, y=category_revenue.values, title='Total Revenue by Category') + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + res = df.groupby('category')['amount'].sum().reset_index() + fig = px.bar(res, x='category', y='amount', title="Total Revenue by Category") return fig - def green_plotly_line(): """ 用 Plotly Express 畫出月營收趨勢折線圖 @@ -39,8 +38,11 @@ def green_plotly_line(): 回傳 plotly Figure 物件 提示:先 groupby 月份算總營收,再 px.line() """ - # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", parse_dates=["order_date"]) + df['month'] = df['order_date'].dt.to_period('M').astype(str) + res = df.groupby('month')['amount'].sum().reset_index() + fig = px.line(res, x='month', y='amount', title="Monthly Revenue Trend") + return fig def green_plotly_pie(): @@ -50,8 +52,9 @@ def green_plotly_pie(): 回傳 plotly Figure 物件 提示:px.pie() """ - # TODO: 你的程式碼 - pass + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + fig = px.pie(df, names='vip_level', title="Order Distribution by VIP Level") + return fig # ============================================================ @@ -65,8 +68,20 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 2. 合併 customers.csv 和 products.csv 回傳:合併後的 DataFrame """ - # TODO: 你的程式碼 - pass + # 1. 讀取與基本清理 + orders = pd.read_csv(raw_path) + orders = orders.drop_duplicates().dropna() + orders['order_date'] = pd.to_datetime(orders['order_date']) + + # 2. 讀取其他表格 + customers = pd.read_csv(customers_path) + products = pd.read_csv(products_path) + + # 3. 合併 (Merge) + df = orders.merge(customers, on='customer_id', how='left') + df = df.merge(products, on='product_id', how='left') + + return df def yellow_kpi_summary(df): @@ -79,8 +94,15 @@ def yellow_kpi_summary(df): "avg_order_value": float, # 平均客單價 } """ - # TODO: 你的程式碼 - pass + total_rev = float(df['amount'].sum()) + order_cnt = int(len(df)) + summary = { + "total_revenue": total_rev, + "order_count": order_cnt, + "active_customers": int(df['customer_id'].nunique()), + "avg_order_value": total_rev / order_cnt if order_cnt > 0 else 0.0 + } + return summary def yellow_plotly_scatter(df): @@ -93,8 +115,10 @@ def yellow_plotly_scatter(df): 回傳 plotly Figure 物件 提示:px.scatter(hover_data=['product_name']) """ - # TODO: 你的程式碼 - pass + fig = px.scatter(df, x='unit_price', y='amount', color='category', + hover_data=['product_name'], + title="Unit Price vs Order Amount") + return fig # ============================================================ @@ -117,5 +141,35 @@ def red_dashboard(): 回傳 plotly Figure 物件 提示:from plotly.subplots import make_subplots """ - # TODO: 你的程式碼 - pass + # 1. ETL + df = yellow_clean_and_merge( + "datasets/ecommerce/orders_raw.csv", + "datasets/ecommerce/customers.csv", + "datasets/ecommerce/products.csv" + ) + df['month'] = df['order_date'].dt.to_period('M').astype(str) + + # 2. 準備各圖表數據 + m_rev = df.groupby('month')['amount'].sum().reset_index() + top10_prod = df.groupby('product_name')['amount'].sum().sort_values(ascending=False).head(10).reset_index() + reg_rev = df.groupby('region')['amount'].sum().reset_index() + cat_rev = df.groupby('category')['amount'].sum().reset_index() + + # 3. 建立畫布 + fig = make_subplots( + rows=2, cols=2, + subplot_titles=("Monthly Revenue Trend", "Top 10 Products", "Revenue by Region", "Revenue Share by Category"), + specs=[[{"type": "scatter"}, {"type": "bar"}], + [{"type": "bar"}, {"type": "pie"}]] + ) + + # 4. 加入 Subplots + fig.add_trace(go.Scatter(x=m_rev['month'], y=m_rev['amount'], name="Revenue"), row=1, col=1) + fig.add_trace(go.Bar(x=top10_prod['product_name'], y=top10_prod['amount'], name="Top Products"), row=1, col=2) + fig.add_trace(go.Bar(x=reg_rev['region'], y=reg_rev['amount'], name="Region"), row=2, col=1) + fig.add_trace(go.Pie(labels=cat_rev['category'], values=cat_rev['amount']), row=2, col=2) + + # 5. 設定佈局 + fig.update_layout(height=800, title_text="E-commerce Business Intelligence Dashboard", showlegend=False) + + return fig \ No newline at end of file From a7d2f27610ebb5926d343e2124f7e75e6464ce0d Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 21:00:33 +0800 Subject: [PATCH 27/28] =?UTF-8?q?=20=E5=AE=8C=E6=88=90=E4=BD=9C=E6=A5=AD?= =?UTF-8?q?=20-=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m6_plotly_capstone.py | 131 +++++++++++++++------------------ 1 file changed, 61 insertions(+), 70 deletions(-) diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 77fa423..30e9a29 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -14,6 +14,11 @@ from plotly.subplots import make_subplots +def _load_enriched_data(): + """輔助函式:讀取已處理好的資料""" + return pd.read_csv("datasets/ecommerce/orders_enriched.csv", parse_dates=["order_date"]) + + # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ @@ -21,12 +26,11 @@ def green_plotly_bar(): """ 用 Plotly Express 畫出每個商品類別 (category) 的總營收長條圖 - 資料來源:orders_enriched.csv - 回傳 plotly Figure 物件 - 提示:px.bar() """ - df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + df = _load_enriched_data() + # 聚合資料 res = df.groupby('category')['amount'].sum().reset_index() + # 畫圖 fig = px.bar(res, x='category', y='amount', title="Total Revenue by Category") return fig @@ -34,13 +38,12 @@ def green_plotly_bar(): def green_plotly_line(): """ 用 Plotly Express 畫出月營收趨勢折線圖 - 資料來源:orders_enriched.csv - 回傳 plotly Figure 物件 - 提示:先 groupby 月份算總營收,再 px.line() """ - df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", parse_dates=["order_date"]) + df = _load_enriched_data() + # 建立月份標籤 df['month'] = df['order_date'].dt.to_period('M').astype(str) res = df.groupby('month')['amount'].sum().reset_index() + # 畫圖 fig = px.line(res, x='month', y='amount', title="Monthly Revenue Trend") return fig @@ -48,11 +51,9 @@ def green_plotly_line(): def green_plotly_pie(): """ 用 Plotly Express 畫出 VIP 等級 (vip_level) 的訂單數佔比圓餅圖 - 資料來源:orders_enriched.csv - 回傳 plotly Figure 物件 - 提示:px.pie() """ - df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + df = _load_enriched_data() + # 畫圖 fig = px.pie(df, names='vip_level', title="Order Distribution by VIP Level") return fig @@ -63,22 +64,23 @@ def green_plotly_pie(): def yellow_clean_and_merge(raw_path, customers_path, products_path): """ - 完整 ETL:從髒資料到合併完成的 DataFrame - 1. 讀取 orders_raw.csv 並清理(欄位名稱、金額、日期、缺值、去重) - 2. 合併 customers.csv 和 products.csv - 回傳:合併後的 DataFrame + 完整 ETL:清理並合併三張表格 """ - # 1. 讀取與基本清理 + # 1. 讀取 orders_raw 並進行基本清理 orders = pd.read_csv(raw_path) + # 去除重複值與缺值 orders = orders.drop_duplicates().dropna() + # 轉換日期格式 orders['order_date'] = pd.to_datetime(orders['order_date']) - # 2. 讀取其他表格 + # 2. 讀取關聯表 customers = pd.read_csv(customers_path) products = pd.read_csv(products_path) - # 3. 合併 (Merge) + # 3. 執行合併 (使用 left join 確保以訂單為主體) + # 先併客戶資料 df = orders.merge(customers, on='customer_id', how='left') + # 再併商品資料 df = df.merge(products, on='product_id', how='left') return df @@ -86,38 +88,33 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): def yellow_kpi_summary(df): """ - 計算 4 個核心 KPI,回傳 dict: - { - "total_revenue": float, # 總營收 - "order_count": int, # 訂單數 - "active_customers": int, # 不重複客戶數 - "avg_order_value": float, # 平均客單價 - } + 計算核心 KPI 並回傳字典 """ - total_rev = float(df['amount'].sum()) - order_cnt = int(len(df)) - summary = { - "total_revenue": total_rev, - "order_count": order_cnt, - "active_customers": int(df['customer_id'].nunique()), - "avg_order_value": total_rev / order_cnt if order_cnt > 0 else 0.0 + total_revenue = float(df['amount'].sum()) + order_count = int(len(df)) + active_customers = int(df['customer_id'].nunique()) + avg_order_value = total_revenue / order_count if order_count > 0 else 0.0 + + return { + "total_revenue": total_revenue, + "order_count": order_count, + "active_customers": active_customers, + "avg_order_value": avg_order_value, } - return summary def yellow_plotly_scatter(df): """ - 用 Plotly Express 畫互動散佈圖: - - X:商品單價 (unit_price) - - Y:訂單金額 (amount) - - 顏色:商品類別 (category) - - hover 顯示:商品名稱 (product_name) - 回傳 plotly Figure 物件 - 提示:px.scatter(hover_data=['product_name']) + 用 Plotly Express 畫互動散佈圖 """ - fig = px.scatter(df, x='unit_price', y='amount', color='category', - hover_data=['product_name'], - title="Unit Price vs Order Amount") + fig = px.scatter( + df, + x='unit_price', + y='amount', + color='category', + hover_data=['product_name'], + title="Relationship between Unit Price and Order Amount" + ) return fig @@ -127,21 +124,9 @@ def yellow_plotly_scatter(df): def red_dashboard(): """ - Capstone:完整的互動式儀表板 - - 流程: - 1. 清理 orders_raw.csv + 合併三張表 - 2. 建立 2×2 subplot dashboard(用 plotly make_subplots): - - 左上:月營收趨勢 (line) - - 右上:Top 10 商品營收 (bar) - - 左下:各地區營收 (bar) - - 右下:類別營收佔比 (pie/donut) - 3. 設定整體標題 - - 回傳 plotly Figure 物件 - 提示:from plotly.subplots import make_subplots + Capstone:建立 2x2 互動式儀表板 """ - # 1. ETL + # 1. 準備資料 df = yellow_clean_and_merge( "datasets/ecommerce/orders_raw.csv", "datasets/ecommerce/customers.csv", @@ -149,27 +134,33 @@ def red_dashboard(): ) df['month'] = df['order_date'].dt.to_period('M').astype(str) - # 2. 準備各圖表數據 - m_rev = df.groupby('month')['amount'].sum().reset_index() - top10_prod = df.groupby('product_name')['amount'].sum().sort_values(ascending=False).head(10).reset_index() - reg_rev = df.groupby('region')['amount'].sum().reset_index() - cat_rev = df.groupby('category')['amount'].sum().reset_index() - - # 3. 建立畫布 + # 2. 建立子圖架構 fig = make_subplots( rows=2, cols=2, - subplot_titles=("Monthly Revenue Trend", "Top 10 Products", "Revenue by Region", "Revenue Share by Category"), + subplot_titles=("Monthly Revenue Trend", "Top 10 Products by Revenue", + "Revenue by Region", "Revenue Share by Category"), specs=[[{"type": "scatter"}, {"type": "bar"}], [{"type": "bar"}, {"type": "pie"}]] ) - # 4. 加入 Subplots + # 3. 計算各圖數據並加入 Trace + # 左上:折線圖 + m_rev = df.groupby('month')['amount'].sum().reset_index() fig.add_trace(go.Scatter(x=m_rev['month'], y=m_rev['amount'], name="Revenue"), row=1, col=1) - fig.add_trace(go.Bar(x=top10_prod['product_name'], y=top10_prod['amount'], name="Top Products"), row=1, col=2) + + # 右上:Top 10 商品 + top10 = df.groupby('product_name')['amount'].sum().sort_values(ascending=False).head(10).reset_index() + fig.add_trace(go.Bar(x=top10['product_name'], y=top10['amount'], name="Product"), row=1, col=2) + + # 左下:地區營收 + reg_rev = df.groupby('region')['amount'].sum().reset_index() fig.add_trace(go.Bar(x=reg_rev['region'], y=reg_rev['amount'], name="Region"), row=2, col=1) + + # 右下:類別佔比 + cat_rev = df.groupby('category')['amount'].sum().reset_index() fig.add_trace(go.Pie(labels=cat_rev['category'], values=cat_rev['amount']), row=2, col=2) - # 5. 設定佈局 - fig.update_layout(height=800, title_text="E-commerce Business Intelligence Dashboard", showlegend=False) + # 4. 佈局設定 + fig.update_layout(height=900, title_text="E-commerce Business Insight Dashboard", showlegend=False) return fig \ No newline at end of file From c4cc1ff2c2ce9fbeefae7b991ff8a898460fb73f Mon Sep 17 00:00:00 2001 From: Mas-000 Date: Mon, 4 May 2026 21:10:09 +0800 Subject: [PATCH 28/28] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BD=9C=E6=A5=AD=20-?= =?UTF-8?q?=20=E6=9D=8E=E8=88=9C=E6=AC=8A=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m6_plotly_capstone.py | 130 +++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 53 deletions(-) diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 30e9a29..40d4e82 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -14,11 +14,6 @@ from plotly.subplots import make_subplots -def _load_enriched_data(): - """輔助函式:讀取已處理好的資料""" - return pd.read_csv("datasets/ecommerce/orders_enriched.csv", parse_dates=["order_date"]) - - # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ @@ -26,35 +21,39 @@ def _load_enriched_data(): def green_plotly_bar(): """ 用 Plotly Express 畫出每個商品類別 (category) 的總營收長條圖 + 資料來源:orders_enriched.csv + 回傳 plotly Figure 物件 + 提示:px.bar() """ - df = _load_enriched_data() - # 聚合資料 - res = df.groupby('category')['amount'].sum().reset_index() - # 畫圖 - fig = px.bar(res, x='category', y='amount', title="Total Revenue by Category") + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + revenue_by_cat = df.groupby('category')['amount'].sum().reset_index() + fig = px.bar(revenue_by_cat, x='category', y='amount', title="Category Revenue") return fig def green_plotly_line(): """ 用 Plotly Express 畫出月營收趨勢折線圖 + 資料來源:orders_enriched.csv + 回傳 plotly Figure 物件 + 提示:先 groupby 月份算總營收,再 px.line() """ - df = _load_enriched_data() - # 建立月份標籤 + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", parse_dates=["order_date"]) df['month'] = df['order_date'].dt.to_period('M').astype(str) - res = df.groupby('month')['amount'].sum().reset_index() - # 畫圖 - fig = px.line(res, x='month', y='amount', title="Monthly Revenue Trend") + revenue_by_month = df.groupby('month')['amount'].sum().reset_index() + fig = px.line(revenue_by_month, x='month', y='amount', title="Monthly Revenue Trend") return fig def green_plotly_pie(): """ 用 Plotly Express 畫出 VIP 等級 (vip_level) 的訂單數佔比圓餅圖 + 資料來源:orders_enriched.csv + 回傳 plotly Figure 物件 + 提示:px.pie() """ - df = _load_enriched_data() - # 畫圖 - fig = px.pie(df, names='vip_level', title="Order Distribution by VIP Level") + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv") + fig = px.pie(df, names='vip_level', title="Orders by VIP Level") return fig @@ -64,23 +63,26 @@ def green_plotly_pie(): def yellow_clean_and_merge(raw_path, customers_path, products_path): """ - 完整 ETL:清理並合併三張表格 + 完整 ETL:從髒資料到合併完成的 DataFrame + 1. 讀取 orders_raw.csv 並清理(欄位名稱、金額、日期、缺值、去重) + 2. 合併 customers.csv 和 products.csv + 回傳:合併後的 DataFrame """ - # 1. 讀取 orders_raw 並進行基本清理 + orders = pd.read_csv(raw_path) - # 去除重複值與缺值 + + orders.columns = orders.columns.str.strip().str.lower().str.replace(' ', '_') + + if 'amount' in orders.columns and orders['amount'].dtype == 'object': + orders['amount'] = orders['amount'].str.replace('$', '', regex=False).str.replace(',', '', regex=False).astype(float) + orders = orders.drop_duplicates().dropna() - # 轉換日期格式 orders['order_date'] = pd.to_datetime(orders['order_date']) - # 2. 讀取關聯表 customers = pd.read_csv(customers_path) products = pd.read_csv(products_path) - # 3. 執行合併 (使用 left join 確保以訂單為主體) - # 先併客戶資料 df = orders.merge(customers, on='customer_id', how='left') - # 再併商品資料 df = df.merge(products, on='product_id', how='left') return df @@ -88,32 +90,43 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): def yellow_kpi_summary(df): """ - 計算核心 KPI 並回傳字典 + 計算 4 個核心 KPI,回傳 dict: + { + "total_revenue": float, # 總營收 + "order_count": int, # 訂單數 + "active_customers": int, # 不重複客戶數 + "avg_order_value": float, # 平均客單價 + } """ - total_revenue = float(df['amount'].sum()) - order_count = int(len(df)) - active_customers = int(df['customer_id'].nunique()) - avg_order_value = total_revenue / order_count if order_count > 0 else 0.0 + total_rev = float(df['amount'].sum()) + order_cnt = int(len(df)) - return { - "total_revenue": total_revenue, - "order_count": order_count, - "active_customers": active_customers, - "avg_order_value": avg_order_value, + kpi_dict = { + "total_revenue": total_rev, + "order_count": order_cnt, + "active_customers": int(df['customer_id'].nunique()), + "avg_order_value": total_rev / order_cnt if order_cnt > 0 else 0.0 } + return kpi_dict def yellow_plotly_scatter(df): """ - 用 Plotly Express 畫互動散佈圖 + 用 Plotly Express 畫互動散佈圖: + - X:商品單價 (unit_price) + - Y:訂單金額 (amount) + - 顏色:商品類別 (category) + - hover 顯示:商品名稱 (product_name) + 回傳 plotly Figure 物件 + 提示:px.scatter(hover_data=['product_name']) """ fig = px.scatter( df, x='unit_price', y='amount', - color='category', + color='category', hover_data=['product_name'], - title="Relationship between Unit Price and Order Amount" + title="Unit Price vs Amount" ) return fig @@ -124,9 +137,21 @@ def yellow_plotly_scatter(df): def red_dashboard(): """ - Capstone:建立 2x2 互動式儀表板 + Capstone:完整的互動式儀表板 + + 流程: + 1. 清理 orders_raw.csv + 合併三張表 + 2. 建立 2×2 subplot dashboard(用 plotly make_subplots): + - 左上:月營收趨勢 (line) + - 右上:Top 10 商品營收 (bar) + - 左下:各地區營收 (bar) + - 右下:類別營收佔比 (pie/donut) + 3. 設定整體標題 + + 回傳 plotly Figure 物件 + 提示:from plotly.subplots import make_subplots """ - # 1. 準備資料 + # 1. 執行 ETL 取得乾淨合併的資料 df = yellow_clean_and_merge( "datasets/ecommerce/orders_raw.csv", "datasets/ecommerce/customers.csv", @@ -134,7 +159,7 @@ def red_dashboard(): ) df['month'] = df['order_date'].dt.to_period('M').astype(str) - # 2. 建立子圖架構 + # 2. 建立 2x2 子圖架構 fig = make_subplots( rows=2, cols=2, subplot_titles=("Monthly Revenue Trend", "Top 10 Products by Revenue", @@ -143,24 +168,23 @@ def red_dashboard(): [{"type": "bar"}, {"type": "pie"}]] ) - # 3. 計算各圖數據並加入 Trace - # 左上:折線圖 + # 左上:月營收趨勢 (line) m_rev = df.groupby('month')['amount'].sum().reset_index() - fig.add_trace(go.Scatter(x=m_rev['month'], y=m_rev['amount'], name="Revenue"), row=1, col=1) + fig.add_trace(go.Scatter(x=m_rev['month'], y=m_rev['amount'], mode='lines+markers', name="Trend"), row=1, col=1) - # 右上:Top 10 商品 + # 右上:Top 10 商品營收 (bar) top10 = df.groupby('product_name')['amount'].sum().sort_values(ascending=False).head(10).reset_index() - fig.add_trace(go.Bar(x=top10['product_name'], y=top10['amount'], name="Product"), row=1, col=2) + fig.add_trace(go.Bar(x=top10['product_name'], y=top10['amount'], name="Top 10"), row=1, col=2) - # 左下:地區營收 + # 左下:各地區營收 (bar) reg_rev = df.groupby('region')['amount'].sum().reset_index() fig.add_trace(go.Bar(x=reg_rev['region'], y=reg_rev['amount'], name="Region"), row=2, col=1) - # 右下:類別佔比 + # 右下:類別營收佔比 (pie/donut) cat_rev = df.groupby('category')['amount'].sum().reset_index() - fig.add_trace(go.Pie(labels=cat_rev['category'], values=cat_rev['amount']), row=2, col=2) + fig.add_trace(go.Pie(labels=cat_rev['category'], values=cat_rev['amount'], name="Category"), row=2, col=2) - # 4. 佈局設定 - fig.update_layout(height=900, title_text="E-commerce Business Insight Dashboard", showlegend=False) - + # 3. 設定整體標題與排版 + fig.update_layout(title_text="E-commerce Interactive Dashboard", height=800, showlegend=False) + return fig \ No newline at end of file