From a4885af2a3b0dfb0f248ccf2cc488bc0a88f4517 Mon Sep 17 00:00:00 2001 From: arku02 <97019633+arku02@users.noreply.github.com> Date: Sun, 3 May 2026 18:12:20 +0800 Subject: [PATCH 1/3] home test1 to test6 --- .github/.gitattributes | 2 + homework/m1_numpy.py | 98 ++++++----- homework/m2_pandas_cleaning.py | 168 ++++++++++-------- homework/m3_pandas_advanced.py | 165 ++++++++++-------- homework/m4_timeseries.py | 170 ++++++++++-------- homework/m5_visualization.py | 243 +++++++++++++++++--------- homework/m6_plotly_capstone.py | 307 ++++++++++++++++++++++++--------- 7 files changed, 737 insertions(+), 416 deletions(-) create mode 100644 .github/.gitattributes diff --git a/.github/.gitattributes b/.github/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.github/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..7255d93 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -15,66 +15,76 @@ # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ +class green: + def green_mean(): + """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" + # TODO: 你的程式碼 + arr = np.array([10, 20, 30, 40, 50]) + return float(arr.mean()) -def green_mean(): - """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" - # TODO: 你的程式碼 - pass + def green_double(): + """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" + # TODO: 你的程式碼 + arr = np.array([10, 20, 30, 40, 50]) + return arr * 2 -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_filter(): + """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" + # TODO: 你的程式碼 + arr = np.array([10, 20, 30, 40, 50]) + return arr[arr > 25] # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # 以下函式會接收從 products.csv 讀出的 prices, stocks 陣列 # ============================================================ - -def yellow_expensive_count(prices): - """回傳單價 > 1000 的商品數量 (int)""" - # TODO: 你的程式碼 - pass +class yellow: + def yellow_expensive_count(prices): + """回傳單價 > 1000 的商品數量 (int)""" + # TODO: 你的程式碼 + return int(np.sum(prices > 1000)) -def yellow_top3_stock_indices(stocks): - """ - 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) - 提示:np.argsort - """ - # TODO: 你的程式碼 - pass + def yellow_top3_stock_indices(stocks): + """ + 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) + 提示:np.argsort + """ + # TODO: 你的程式碼 + 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() + """ + # TODO: 你的程式碼 + mask = prices < 500 + return float(np.sum(prices[mask] * 50)) # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ - -def red_double11_prices(prices, stocks): - """ - 雙 11 定價規則(必須向量化,不能用 for-loop): - - 庫存 >= 100:打 7 折 - - 庫存 20~99:打 9 折 - - 庫存 < 20:原價 - 回傳每個商品的雙 11 售價 (ndarray) - 提示:np.where 可以巢狀使用 - """ - # TODO: 你的程式碼 - pass +class red: + def red_double11_prices(prices, stocks): + """ + 雙 11 定價規則(必須向量化,不能用 for-loop): + - 庫存 >= 100:打 7 折 + - 庫存 20~99:打 9 折 + - 庫存 < 20:原價 + 回傳每個商品的雙 11 售價 (ndarray) + 提示:np.where 可以巢狀使用 + """ + # TODO: 你的程式碼 + return np.where( + stocks >= 100, prices * 0.7, + np.where( + stocks >= 20, prices * 0.9, + prices + ) + ) diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..65a79d3 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -13,84 +13,110 @@ # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ - -def green_read_csv(): - """ - 讀取 orders_raw.csv,回傳原始 DataFrame(不做任何清理) - 提示:pd.read_csv() - """ - # TODO: 你的程式碼 - pass - - -def green_shape(df): - """ - 回傳 DataFrame 的 (列數, 欄數) tuple - 提示:df.shape - """ - # TODO: 你的程式碼 - pass - - -def green_dtypes(df): - """ - 回傳 DataFrame 的欄位型別 (Series) - 提示:df.dtypes - """ - # TODO: 你的程式碼 - pass +class Green: + def green_read_csv(): + """ + 讀取 orders_raw.csv,回傳原始 DataFrame(不做任何清理) + 提示:pd.read_csv() + """ + # TODO: 你的程式碼 + return pd.read_csv('datasets/ecommerce/orders_raw.csv') + + + def green_shape(df): + """ + 回傳 DataFrame 的 (列數, 欄數) tuple + 提示:df.shape + """ + # TODO: 你的程式碼 + return df.shape + + + def green_dtypes(df): + """ + 回傳 DataFrame 的欄位型別 (Series) + 提示:df.dtypes + """ + # TODO: 你的程式碼 + return df.dtypes # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ - -def yellow_clean_columns(df): - """ - 清理欄位名稱:去除前後空白、全部轉小寫 - 回傳清理後的 DataFrame(不要修改原始 df) - 提示:df.columns.str.strip().str.lower() - """ - # TODO: 你的程式碼 - pass - - -def yellow_clean_amount(df): - """ - 清理 amount 欄位:移除 '$' 和 ',' 符號,轉為 float - 假設欄位名稱已經是小寫的 'amount' - 回傳清理後的 DataFrame(不要修改原始 df) - 提示:.str.replace() + .astype(float) - """ - # TODO: 你的程式碼 - pass - - -def yellow_drop_duplicates(df): - """ - 移除完全重複的列,回傳去重後的 DataFrame - 提示:df.drop_duplicates() - """ - # TODO: 你的程式碼 - pass +class Yellow: + def yellow_clean_columns(df): + """ + 清理欄位名稱:去除前後空白、全部轉小寫 + 回傳清理後的 DataFrame(不要修改原始 df) + 提示:df.columns.str.strip().str.lower() + """ + # TODO: 你的程式碼 + result = df.copy() + result.columns = result.columns.str.strip().str.lower() + return result + + + def yellow_clean_amount(df): + """ + 清理 amount 欄位:移除 '$' 和 ',' 符號,轉為 float + 假設欄位名稱已經是小寫的 'amount' + 回傳清理後的 DataFrame(不要修改原始 df) + 提示:.str.replace() + .astype(float) + """ + # TODO: 你的程式碼 + result = df.copy() + result['amount'] = ( + result['amount'] + .str.replace('$', '', regex=False) + .str.replace(',', '', regex=False) + .astype(float) + ) + return result + + + def yellow_drop_duplicates(df): + """ + 移除完全重複的列,回傳去重後的 DataFrame + 提示:df.drop_duplicates() + """ + # TODO: 你的程式碼 + return df.drop_duplicates() # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ - -def red_clean_orders(path): - """ - 完整清理 pipeline:一個函式搞定所有清理步驟 - 1. 讀取 CSV - 2. 欄位名稱:去空白、轉小寫 - 3. amount:移除 '$' ',',轉 float - 4. order_date:轉為 datetime(無法轉換的設為 NaT) - 5. 刪除 amount 或 order_date 為空的列 - 6. 移除重複列 - - 回傳:清理後的 DataFrame - 提示:pd.to_datetime(errors='coerce') - """ - # TODO: 你的程式碼 - pass +class Red: + def red_clean_orders(path): + """ + 完整清理 pipeline:一個函式搞定所有清理步驟 + 1. 讀取 CSV + 2. 欄位名稱:去空白、轉小寫 + 3. amount:移除 '$' ',',轉 float + 4. order_date:轉為 datetime(無法轉換的設為 NaT) + 5. 刪除 amount 或 order_date 為空的列 + 6. 移除重複列 + + 回傳:清理後的 DataFrame + 提示:pd.to_datetime(errors='coerce') + """ + # TODO: 你的程式碼 + 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') + + df = df.dropna(subset=['amount', 'order_date']) + + df = df.drop_duplicates() + + return df diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..5f77fbc 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -15,83 +15,108 @@ # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ - -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') - """ - # TODO: 你的程式碼 - pass - - -def green_row_count(df): - """回傳 DataFrame 的列數 (int)""" - # TODO: 你的程式碼 - pass - - -def green_column_list(df): - """回傳 DataFrame 的所有欄位名稱 (list)""" - # TODO: 你的程式碼 - pass +class Green: + 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') + """ + # TODO: 你的程式碼 + orders = pd.read_csv('datasets/ecommerce/orders_clean.csv') + customers = pd.read_csv('datasets/ecommerce/customers.csv') + products = pd.read_csv('datasets/ecommerce/products.csv') + + df = pd.merge(orders, customers, on='customer_id', how='left') + df = pd.merge(df, products, on='product_id', how='left') + return df + + + def green_row_count(df): + """回傳 DataFrame 的列數 (int)""" + # TODO: 你的程式碼 + return len(df) + + + def green_column_list(df): + """回傳 DataFrame 的所有欄位名稱 (list)""" + # TODO: 你的程式碼 + return df.columns.tolist() # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ - -def yellow_top_category(df): - """ - 哪個商品類別 (category) 的總營收最高? - 回傳該類別名稱 (str) - 提示:groupby('category')['amount'].sum() - """ - # TODO: 你的程式碼 - pass - - -def yellow_gold_vip_stats(df): - """ - Gold VIP 客戶總共下了幾張訂單?總金額多少? - 回傳 tuple: (訂單數 int, 總金額 float) - 提示:df[df['vip_level'] == 'Gold'] - """ - # TODO: 你的程式碼 - pass - - -def yellow_region_avg_amount(df): - """ - 計算每個地區 (region) 的平均訂單金額 - 回傳 Series(index=region, values=平均金額) - 提示:groupby('region')['amount'].mean() - """ - # TODO: 你的程式碼 - pass +class Yellows: + def yellow_top_category(df): + """ + 哪個商品類別 (category) 的總營收最高? + 回傳該類別名稱 (str) + 提示:groupby('category')['amount'].sum() + """ + # TODO: 你的程式碼 + return df.groupby('category')['amount'].sum().idxmax() + + + def yellow_gold_vip_stats(df): + """ + Gold VIP 客戶總共下了幾張訂單?總金額多少? + 回傳 tuple: (訂單數 int, 總金額 float) + 提示:df[df['vip_level'] == 'Gold'] + """ + # TODO: 你的程式碼 + gold = df[df['vip_level'] == 'Gold'] + return (int(len(gold)), float(gold['amount'].sum())) + + + def yellow_region_avg_amount(df): + """ + 計算每個地區 (region) 的平均訂單金額 + 回傳 Series(index=region, values=平均金額) + 提示:groupby('region')['amount'].mean() + """ + # TODO: 你的程式碼 + return df.groupby('region')['amount'].mean() # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ - -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 +class Red: + 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: 你的程式碼 + df['order_date'] = pd.to_datetime(df['order_date']) + + rfm = ( + df.groupby('customer_id') + .agg( + R=('order_date', 'max'), + F=('order_date', 'count'), + M=('amount', 'sum'), + customer_name=('customer_name', 'first') + ) + .reset_index() + ) + + return ( + rfm[['customer_id', 'customer_name', 'R', 'F', 'M']] + .sort_values('M', ascending=False) + .head(5) + .reset_index(drop=True) + ) \ No newline at end of file diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..6d37edd 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -19,86 +19,110 @@ def _load_data(): # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ - -def green_avg_by_month(): - """ - 計算每個月份 (1~12) 的平均訂單金額 - 回傳 Series(index=月份 1~12, values=平均金額) - 提示:df['order_date'].dt.month - """ - # TODO: 你的程式碼 - pass - - -def green_top3_dates(): - """ - 找出訂單數最多的前 3 個日期 - 回傳 Series(index=日期, values=訂單數, 由多到少排序) - 提示:value_counts().head(3) - """ - # TODO: 你的程式碼 - pass - - -def green_date_range(): - """ - 回傳資料的日期範圍 tuple: (最早日期, 最晚日期) - 格式為 pandas Timestamp - """ - # TODO: 你的程式碼 - pass +class Green: + def green_avg_by_month(): + """ + 計算每個月份 (1~12) 的平均訂單金額 + 回傳 Series(index=月份 1~12, values=平均金額) + 提示:df['order_date'].dt.month + """ + # TODO: 你的程式碼 + df = _load_data() + return df.groupby(df['order_date'].dt.month)['amount'].mean() + + + def green_top3_dates(): + """ + 找出訂單數最多的前 3 個日期 + 回傳 Series(index=日期, values=訂單數, 由多到少排序) + 提示:value_counts().head(3) + """ + # TODO: 你的程式碼 + df = _load_data() + return df['order_date'].dt.date.value_counts().head(3) + + + + def green_date_range(): + """ + 回傳資料的日期範圍 tuple: (最早日期, 最晚日期) + 格式為 pandas Timestamp + """ + # TODO: 你的程式碼 + df = _load_data() + return (df['order_date'].min(), df['order_date'].max()) # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ - -def yellow_monthly_revenue(): - """ - 計算每月總營收 - 回傳 Series(index=月底日期 period, values=總營收) - 提示:set_index('order_date').resample('ME')['amount'].sum() - """ - # TODO: 你的程式碼 - pass - - -def yellow_rolling_avg(monthly_revenue): - """ - 計算 3 個月移動平均 - 接收 yellow_monthly_revenue() 的結果作為輸入 - 回傳 Series(同樣 index,values=移動平均,前 2 筆可為 NaN) - 提示:.rolling(window=3).mean() - """ - # TODO: 你的程式碼 - pass - - -def yellow_category_median(df): - """ - 計算每個商品類別 (category) 的訂單金額中位數,由高到低排序 - 回傳 Series(index=category, values=中位數) - 提示:groupby + median + sort_values - """ - # TODO: 你的程式碼 - pass +class Yellows: + def yellow_monthly_revenue(): + """ + 計算每月總營收 + 回傳 Series(index=月底日期 period, values=總營收) + 提示:set_index('order_date').resample('ME')['amount'].sum() + """ + # TODO: 你的程式碼 + df = _load_data() + return df.set_index('order_date').resample('ME')['amount'].sum() + + + def yellow_rolling_avg(monthly_revenue): + """ + 計算 3 個月移動平均 + 接收 yellow_monthly_revenue() 的結果作為輸入 + 回傳 Series(同樣 index,values=移動平均,前 2 筆可為 NaN) + 提示:.rolling(window=3).mean() + """ + # TODO: 你的程式碼 + return monthly_revenue.rolling(window=3).mean() + + + def yellow_category_median(df): + """ + 計算每個商品類別 (category) 的訂單金額中位數,由高到低排序 + 回傳 Series(index=category, values=中位數) + 提示:groupby + median + sort_values + """ + # TODO: 你的程式碼 + return ( + df.groupby('category')['amount'] + .median() + .sort_values(ascending=False) + ) # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ - -def red_monthly_report(): - """ - 產出月報 DataFrame,每月一列,包含: - - order_count:當月訂單數 - - revenue:當月總營收 - - active_customers:當月不重複客戶數 - - avg_order_value:客單價(revenue / order_count) - - revenue_growth:月營收成長率(相對上月的 % 變化) - - index 為月份 (period 或 datetime) - 提示:resample + agg + pct_change - """ - # TODO: 你的程式碼 - pass +class Red: + def red_monthly_report(): + """ + 產出月報 DataFrame,每月一列,包含: + - order_count:當月訂單數 + - revenue:當月總營收 + - active_customers:當月不重複客戶數 + - avg_order_value:客單價(revenue / order_count) + - revenue_growth:月營收成長率(相對上月的 % 變化) + + index 為月份 (period 或 datetime) + 提示:resample + agg + pct_change + """ + # TODO: 你的程式碼 + df = _load_data() + + monthly = ( + df.set_index('order_date') + .resample('ME') + .agg( + order_count=('amount', 'count'), + revenue=('amount', 'sum'), + active_customers=('customer_id', 'nunique') + ) + ) + + monthly['avg_order_value'] = monthly['revenue'] / monthly['order_count'] + monthly['revenue_growth'] = monthly['revenue'].pct_change() * 100 + + return monthly diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..a67cff8 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -21,90 +21,179 @@ def _load_data(): # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ - -def green_bar_category(): - """ - 畫出每個商品類別 (category) 的訂單數長條圖 - 回傳 matplotlib Figure 物件 - 提示:sns.countplot 或 value_counts().plot.bar() - """ - # TODO: 你的程式碼 - pass - - -def green_hist_amount(): - """ - 畫出訂單金額 (amount) 的分佈直方圖,分 20 個 bin - 回傳 matplotlib Figure 物件 - 提示:sns.histplot(bins=20) 或 plt.hist() - """ - # TODO: 你的程式碼 - pass - - -def green_set_labels(): - """ - 建立一個簡單的長條圖(內容不限),但必須設定: - - 圖標題 (title) - - X 軸標籤 (xlabel) - - Y 軸標籤 (ylabel) - 回傳 matplotlib Figure 物件 - """ - # TODO: 你的程式碼 - pass +class Green: + def green_bar_category(): + """ + 畫出每個商品類別 (category) 的訂單數長條圖 + 回傳 matplotlib Figure 物件 + 提示:sns.countplot 或 value_counts().plot.bar() + """ + # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 5)) + sns.countplot(data=df, x='category', order=df['category'].value_counts().index, ax=ax) + ax.set_title('Orders by Category') + ax.set_xlabel('Category') + ax.set_ylabel('Order Count') + plt.tight_layout() + return fig + + + def green_hist_amount(): + """ + 畫出訂單金額 (amount) 的分佈直方圖,分 20 個 bin + 回傳 matplotlib Figure 物件 + 提示:sns.histplot(bins=20) 或 plt.hist() + """ + # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 5)) + sns.histplot(data=df, x='amount', bins=20, ax=ax) + ax.set_title('Order Amount Distribution') + ax.set_xlabel('Amount') + ax.set_ylabel('Frequency') + plt.tight_layout() + return fig + + + def green_set_labels(): + """ + 建立一個簡單的長條圖(內容不限),但必須設定: + - 圖標題 (title) + - X 軸標籤 (xlabel) + - Y 軸標籤 (ylabel) + 回傳 matplotlib Figure 物件 + """ + # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 5)) + df['category'].value_counts().plot.bar(ax=ax) + ax.set_title('Orders by Category') + ax.set_xlabel('Category') + ax.set_ylabel('Order Count') + plt.tight_layout() + return fig # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ - -def yellow_line_region_trend(): - """ - 畫折線圖:比較 North 和 South 兩個地區的月營收趨勢 - - X 軸:月份 - - Y 軸:該月總營收 - - 兩條線,有圖例 (legend) - 回傳 matplotlib Figure 物件 - 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') - """ - # TODO: 你的程式碼 - pass - - -def yellow_box_vip(): - """ - 畫箱形圖:比較不同 VIP 等級 (vip_level) 的訂單金額分佈 - 回傳 matplotlib Figure 物件 - 提示:sns.boxplot(x='vip_level', y='amount', data=df) - """ - # TODO: 你的程式碼 - pass - - -def yellow_scatter_price_amount(): - """ - 畫散佈圖:X=商品單價 (unit_price),Y=訂單金額 (amount) - 回傳 matplotlib Figure 物件 - 提示:plt.scatter() 或 sns.scatterplot() - """ - # TODO: 你的程式碼 - pass +class Yellow: + def yellow_line_region_trend(): + """ + 畫折線圖:比較 North 和 South 兩個地區的月營收趨勢 + - X 軸:月份 + - Y 軸:該月總營收 + - 兩條線,有圖例 (legend) + 回傳 matplotlib Figure 物件 + 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') + """ + # TODO: 你的程式碼 + df = _load_data() + df['month'] = df['order_date'].dt.to_period('M') + + filtered = df[df['region'].isin(['North', 'South'])] + monthly = ( + filtered.groupby(['month', 'region'])['amount'] + .sum() + .reset_index() + ) + monthly['month'] = monthly['month'].dt.to_timestamp() + + fig, ax = plt.subplots(figsize=(10, 5)) + sns.lineplot(data=monthly, x='month', y='amount', hue='region', ax=ax) + ax.set_title('Monthly Revenue: North vs South') + ax.set_xlabel('Month') + ax.set_ylabel('Revenue') + plt.tight_layout() + return fig + + + def yellow_box_vip(): + """ + 畫箱形圖:比較不同 VIP 等級 (vip_level) 的訂單金額分佈 + 回傳 matplotlib Figure 物件 + 提示:sns.boxplot(x='vip_level', y='amount', data=df) + """ + # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 5)) + sns.boxplot(data=df, x='vip_level', y='amount', ax=ax) + ax.set_title('Order Amount by VIP Level') + ax.set_xlabel('VIP Level') + ax.set_ylabel('Amount') + plt.tight_layout() + return fig + + + def yellow_scatter_price_amount(): + """ + 畫散佈圖:X=商品單價 (unit_price),Y=訂單金額 (amount) + 回傳 matplotlib Figure 物件 + 提示:plt.scatter() 或 sns.scatterplot() + """ + # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 5)) + sns.scatterplot(data=df, x='unit_price', y='amount', alpha=0.5, ax=ax) + ax.set_title('Unit Price vs Order Amount') + ax.set_xlabel('Unit Price') + ax.set_ylabel('Amount') + plt.tight_layout() + return fig # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ - -def red_category_dashboard(category="Electronics"): - """ - 針對指定類別,畫 2×2 的 subplot dashboard: - 1. 左上:該類別月營收趨勢 (折線圖) - 2. 右上:該類別各地區營收 (長條圖) - 3. 左下:該類別 Top 5 商品營收 (水平長條圖) - 4. 右下:該類別訂單金額分佈 (直方圖) - - 回傳 matplotlib Figure 物件 - 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) - """ - # TODO: 你的程式碼 - pass +class Red: + def red_category_dashboard(category="Electronics"): + """ + 針對指定類別,畫 2×2 的 subplot dashboard: + 1. 左上:該類別月營收趨勢 (折線圖) + 2. 右上:該類別各地區營收 (長條圖) + 3. 左下:該類別 Top 5 商品營收 (水平長條圖) + 4. 右下:該類別訂單金額分佈 (直方圖) + + 回傳 matplotlib Figure 物件 + 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + """ + # TODO: 你的程式碼 + df = _load_data() + cat_df = df[df['category'] == category].copy() + cat_df['month'] = cat_df['order_date'].dt.to_period('M') + + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle(f'{category} Dashboard', fontsize=16, fontweight='bold') + + monthly_rev = cat_df.groupby('month')['amount'].sum() + monthly_rev.index = monthly_rev.index.to_timestamp() + axes[0, 0].plot(monthly_rev.index, monthly_rev.values, marker='o') + axes[0, 0].set_title('Monthly Revenue Trend') + axes[0, 0].set_xlabel('Month') + axes[0, 0].set_ylabel('Revenue') + + region_rev = cat_df.groupby('region')['amount'].sum().sort_values(ascending=False) + axes[0, 1].bar(region_rev.index, region_rev.values) + axes[0, 1].set_title('Revenue by Region') + axes[0, 1].set_xlabel('Region') + axes[0, 1].set_ylabel('Revenue') + + top5 = ( + cat_df.groupby('product_name')['amount'] + .sum() + .sort_values(ascending=False) + .head(5) + ) + axes[1, 0].barh(top5.index[::-1], top5.values[::-1]) + axes[1, 0].set_title('Top 5 Products by Revenue') + axes[1, 0].set_xlabel('Revenue') + axes[1, 0].set_ylabel('Product') + + axes[1, 1].hist(cat_df['amount'], bins=20, edgecolor='white') + axes[1, 1].set_title('Order Amount Distribution') + axes[1, 1].set_xlabel('Amount') + axes[1, 1].set_ylabel('Frequency') + + plt.tight_layout() + return fig diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..55bc15f 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -17,102 +17,247 @@ # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ +class Green: + def green_plotly_bar(): + """ + 用 Plotly Express 畫出每個商品類別 (category) 的總營收長條圖 + 資料來源:orders_enriched.csv + 回傳 plotly Figure 物件 + 提示:px.bar() + """ + # TODO: 你的程式碼 + df = _load_enriched() + category_rev = ( + df.groupby('category')['amount'] + .sum() + .reset_index() + .sort_values('amount', ascending=False) + ) + fig = px.bar( + category_rev, + x='category', y='amount', + title='Total Revenue by Category', + labels={'amount': 'Revenue', 'category': 'Category'} + ) + return fig -def green_plotly_bar(): - """ - 用 Plotly Express 畫出每個商品類別 (category) 的總營收長條圖 - 資料來源:orders_enriched.csv - 回傳 plotly Figure 物件 - 提示:px.bar() - """ - # TODO: 你的程式碼 - pass - - -def green_plotly_line(): - """ - 用 Plotly Express 畫出月營收趨勢折線圖 - 資料來源:orders_enriched.csv - 回傳 plotly Figure 物件 - 提示:先 groupby 月份算總營收,再 px.line() - """ - # TODO: 你的程式碼 - pass - - -def green_plotly_pie(): - """ - 用 Plotly Express 畫出 VIP 等級 (vip_level) 的訂單數佔比圓餅圖 - 資料來源:orders_enriched.csv - 回傳 plotly Figure 物件 - 提示:px.pie() - """ - # TODO: 你的程式碼 - pass + + def green_plotly_line(): + """ + 用 Plotly Express 畫出月營收趨勢折線圖 + 資料來源:orders_enriched.csv + 回傳 plotly Figure 物件 + 提示:先 groupby 月份算總營收,再 px.line() + """ + # TODO: 你的程式碼 + df = _load_enriched() + df['month'] = df['order_date'].dt.to_period('M').dt.to_timestamp() + monthly = df.groupby('month')['amount'].sum().reset_index() + fig = px.line( + monthly, + x='month', y='amount', + title='Monthly Revenue Trend', + labels={'month': 'Month', 'amount': 'Revenue'}, + markers=True + ) + return fig + + + def green_plotly_pie(): + """ + 用 Plotly Express 畫出 VIP 等級 (vip_level) 的訂單數佔比圓餅圖 + 資料來源:orders_enriched.csv + 回傳 plotly Figure 物件 + 提示:px.pie() + """ + # TODO: 你的程式碼 + df = _load_enriched() + vip_counts = df['vip_level'].value_counts().reset_index() + vip_counts.columns = ['vip_level', 'count'] + fig = px.pie( + vip_counts, + names='vip_level', values='count', + title='Order Share by VIP Level' + ) + return fig # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ +class Yellow: + def yellow_clean_and_merge(raw_path, customers_path, products_path): + """ + 完整 ETL:從髒資料到合併完成的 DataFrame + 1. 讀取 orders_raw.csv 並清理(欄位名稱、金額、日期、缺值、去重) + 2. 合併 customers.csv 和 products.csv + 回傳:合併後的 DataFrame + """ + # TODO: 你的程式碼 + df = pd.read_csv(raw_path) + customers = pd.read_csv(customers_path) + products = pd.read_csv(products_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') + + df = df.dropna(subset=['amount', 'order_date']) + df = df.drop_duplicates() + + + df = pd.merge(df, customers, on='customer_id', how='left') + df = pd.merge(df, products, on='product_id', how='left') + + return df + + + def yellow_kpi_summary(df): + """ + 計算 4 個核心 KPI,回傳 dict: + { + "total_revenue": float, # 總營收 + "order_count": int, # 訂單數 + "active_customers": int, # 不重複客戶數 + "avg_order_value": float, # 平均客單價 + } + """ + # TODO: 你的程式碼 + return { + "total_revenue": float(df['amount'].sum()), + "order_count": int(len(df)), + "active_customers": int(df['customer_id'].nunique()), + "avg_order_value": float(df['amount'].mean()), + } + -def yellow_clean_and_merge(raw_path, customers_path, products_path): - """ - 完整 ETL:從髒資料到合併完成的 DataFrame - 1. 讀取 orders_raw.csv 並清理(欄位名稱、金額、日期、缺值、去重) - 2. 合併 customers.csv 和 products.csv - 回傳:合併後的 DataFrame - """ - # TODO: 你的程式碼 - pass - - -def yellow_kpi_summary(df): - """ - 計算 4 個核心 KPI,回傳 dict: - { - "total_revenue": float, # 總營收 - "order_count": int, # 訂單數 - "active_customers": int, # 不重複客戶數 - "avg_order_value": float, # 平均客單價 - } - """ - # TODO: 你的程式碼 - pass - - -def yellow_plotly_scatter(df): - """ - 用 Plotly Express 畫互動散佈圖: - - X:商品單價 (unit_price) - - Y:訂單金額 (amount) - - 顏色:商品類別 (category) - - hover 顯示:商品名稱 (product_name) - 回傳 plotly Figure 物件 - 提示:px.scatter(hover_data=['product_name']) - """ - # TODO: 你的程式碼 - pass + def yellow_plotly_scatter(df): + """ + 用 Plotly Express 畫互動散佈圖: + - X:商品單價 (unit_price) + - Y:訂單金額 (amount) + - 顏色:商品類別 (category) + - hover 顯示:商品名稱 (product_name) + 回傳 plotly Figure 物件 + 提示:px.scatter(hover_data=['product_name']) + """ + # TODO: 你的程式碼 + fig = px.scatter( + df, + x='unit_price', y='amount', + color='category', + hover_data=['product_name'], + title='Unit Price vs Order Amount', + labels={'unit_price': 'Unit Price', 'amount': 'Order Amount'}, + opacity=0.6 + ) + return fig # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ +class Red: + def red_dashboard(): + """ + Capstone:完整的互動式儀表板 -def red_dashboard(): - """ - Capstone:完整的互動式儀表板 - - 流程: - 1. 清理 orders_raw.csv + 合併三張表 - 2. 建立 2×2 subplot dashboard(用 plotly make_subplots): + 流程: + 1. 清理 orders_raw.csv + 合併三張表 + 2. 建立 2×2 subplot dashboard(用 plotly make_subplots): - 左上:月營收趨勢 (line) - 右上:Top 10 商品營收 (bar) - 左下:各地區營收 (bar) - 右下:類別營收佔比 (pie/donut) - 3. 設定整體標題 + 3. 設定整體標題 + + 回傳 plotly Figure 物件 + 提示:from plotly.subplots import make_subplots + """ + # TODO: 你的程式碼 + def red_dashboard(): + + df = Yellow.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').dt.to_timestamp() + monthly = df.groupby('month')['amount'].sum().reset_index() + + top10 = ( + df.groupby('product_name')['amount'] + .sum() + .sort_values(ascending=False) + .head(10) + .reset_index() + ) + + region_rev = ( + df.groupby('region')['amount'] + .sum() + .sort_values(ascending=False) + .reset_index() + ) + + + cat_rev = df.groupby('category')['amount'].sum().reset_index() + + fig = make_subplots( + rows=2, cols=2, + subplot_titles=( + 'Monthly Revenue Trend', + 'Top 10 Products by Revenue', + 'Revenue by Region', + 'Revenue Share by Category' + ), + specs=[ + [{"type": "xy"}, {"type": "xy"}], + [{"type": "xy"}, {"type": "domain"}] + ] + ) + + fig.add_trace( + go.Scatter(x=monthly['month'], y=monthly['amount'], + mode='lines+markers', name='Monthly Revenue'), + row=1, col=1 + ) + + fig.add_trace( + go.Bar( + x=top10['amount'], + y=top10['product_name'], + orientation='h', + name='Product Revenue' + ), + row=1, col=2 + ) + + fig.add_trace( + go.Bar(x=region_rev['region'], y=region_rev['amount'], + name='Region Revenue'), + row=2, col=1 + ) + + fig.add_trace( + go.Pie(labels=cat_rev['category'], values=cat_rev['amount'], + hole=0.4, name='Category Share'), + row=2, col=2 + ) + + fig.update_layout( + title_text='E-Commerce Interactive Dashboard', + title_font_size=20, + height=800, + showlegend=False + ) - 回傳 plotly Figure 物件 - 提示:from plotly.subplots import make_subplots - """ - # TODO: 你的程式碼 - pass + return fig From 1877aaa8b989f3f3226b94518d668e2764204d8e Mon Sep 17 00:00:00 2001 From: arku02 <97019633+arku02@users.noreply.github.com> Date: Sun, 3 May 2026 18:49:05 +0800 Subject: [PATCH 2/3] delet class delet --- homework/m1_numpy.py | 105 ++++---- homework/m2_pandas_cleaning.py | 170 ++++++------- homework/m3_pandas_advanced.py | 176 ++++++------- homework/m4_timeseries.py | 186 +++++++------- homework/m5_visualization.py | 328 ++++++++++++------------ homework/m6_plotly_capstone.py | 452 ++++++++++++++++----------------- 6 files changed, 708 insertions(+), 709 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 7255d93..91e59a2 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -15,76 +15,75 @@ # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ -class green: - def green_mean(): - """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" - # TODO: 你的程式碼 - arr = np.array([10, 20, 30, 40, 50]) - return float(arr.mean()) +def green_mean(): + """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" + # TODO: 你的程式碼 + arr = np.array([10, 20, 30, 40, 50]) + return float(arr.mean()) - def green_double(): - """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" - # TODO: 你的程式碼 - arr = np.array([10, 20, 30, 40, 50]) - return arr * 2 +def green_double(): + """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" + # TODO: 你的程式碼 + arr = np.array([10, 20, 30, 40, 50]) + return arr * 2 - def green_filter(): - """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" - # TODO: 你的程式碼 - arr = np.array([10, 20, 30, 40, 50]) - return arr[arr > 25] +def green_filter(): + """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" + # TODO: 你的程式碼 + arr = np.array([10, 20, 30, 40, 50]) + return arr[arr > 25] # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # 以下函式會接收從 products.csv 讀出的 prices, stocks 陣列 # ============================================================ -class yellow: - def yellow_expensive_count(prices): - """回傳單價 > 1000 的商品數量 (int)""" - # TODO: 你的程式碼 - return int(np.sum(prices > 1000)) +def yellow_expensive_count(prices): + """回傳單價 > 1000 的商品數量 (int)""" + # TODO: 你的程式碼 + return int(np.sum(prices > 1000)) - def yellow_top3_stock_indices(stocks): - """ - 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) - 提示:np.argsort - """ - # TODO: 你的程式碼 - return np.argsort(stocks)[::-1][:3] +def yellow_top3_stock_indices(stocks): + """ + 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) + 提示:np.argsort + """ + # TODO: 你的程式碼 + return np.argsort(stocks)[::-1][:3] - def yellow_restock_cost(prices, stocks): - """ - 單價 < 500 的商品,每種各進貨 50 個,回傳總花費 (float/int) - 提示:布林遮罩 + .sum() - """ - # TODO: 你的程式碼 - mask = prices < 500 - return float(np.sum(prices[mask] * 50)) + +def yellow_restock_cost(prices, stocks): + """ + 單價 < 500 的商品,每種各進貨 50 個,回傳總花費 (float/int) + 提示:布林遮罩 + .sum() + """ + # TODO: 你的程式碼 + mask = prices < 500 + return float(np.sum(prices[mask] * 50)) # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ -class red: - def red_double11_prices(prices, stocks): - """ - 雙 11 定價規則(必須向量化,不能用 for-loop): - - 庫存 >= 100:打 7 折 - - 庫存 20~99:打 9 折 - - 庫存 < 20:原價 - 回傳每個商品的雙 11 售價 (ndarray) - 提示:np.where 可以巢狀使用 - """ - # TODO: 你的程式碼 - return np.where( - stocks >= 100, prices * 0.7, - np.where( - stocks >= 20, prices * 0.9, - prices - ) + +def red_double11_prices(prices, stocks): + """ + 雙 11 定價規則(必須向量化,不能用 for-loop): + - 庫存 >= 100:打 7 折 + - 庫存 20~99:打 9 折 + - 庫存 < 20:原價 + 回傳每個商品的雙 11 售價 (ndarray) + 提示:np.where 可以巢狀使用 + """ + # TODO: 你的程式碼 + return np.where( + stocks >= 100, prices * 0.7, + np.where( + stocks >= 20, prices * 0.9, + prices ) + ) diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index 65a79d3..a4a2a2b 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -13,110 +13,110 @@ # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ -class Green: - def green_read_csv(): - """ - 讀取 orders_raw.csv,回傳原始 DataFrame(不做任何清理) - 提示:pd.read_csv() - """ - # TODO: 你的程式碼 - return pd.read_csv('datasets/ecommerce/orders_raw.csv') +def green_read_csv(): + """ + 讀取 orders_raw.csv,回傳原始 DataFrame(不做任何清理) + 提示:pd.read_csv() + """ + # TODO: 你的程式碼 + return pd.read_csv('datasets/ecommerce/orders_raw.csv') - def green_shape(df): - """ - 回傳 DataFrame 的 (列數, 欄數) tuple - 提示:df.shape + +def green_shape(df): + """ + 回傳 DataFrame 的 (列數, 欄數) tuple + 提示:df.shape """ - # TODO: 你的程式碼 - return df.shape + # TODO: 你的程式碼 + return df.shape - def green_dtypes(df): - """ - 回傳 DataFrame 的欄位型別 (Series) - 提示:df.dtypes - """ - # TODO: 你的程式碼 - return df.dtypes +def green_dtypes(df): + """ + 回傳 DataFrame 的欄位型別 (Series) + 提示:df.dtypes + """ + # TODO: 你的程式碼 + return df.dtypes # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ -class Yellow: - def yellow_clean_columns(df): - """ - 清理欄位名稱:去除前後空白、全部轉小寫 - 回傳清理後的 DataFrame(不要修改原始 df) - 提示:df.columns.str.strip().str.lower() - """ - # TODO: 你的程式碼 - result = df.copy() - result.columns = result.columns.str.strip().str.lower() - return result - - def yellow_clean_amount(df): - """ - 清理 amount 欄位:移除 '$' 和 ',' 符號,轉為 float - 假設欄位名稱已經是小寫的 'amount' - 回傳清理後的 DataFrame(不要修改原始 df) - 提示:.str.replace() + .astype(float) - """ - # TODO: 你的程式碼 - result = df.copy() - result['amount'] = ( - result['amount'] - .str.replace('$', '', regex=False) - .str.replace(',', '', regex=False) - .astype(float) - ) - return result - - - def yellow_drop_duplicates(df): - """ - 移除完全重複的列,回傳去重後的 DataFrame - 提示:df.drop_duplicates() - """ - # TODO: 你的程式碼 - return df.drop_duplicates() +def yellow_clean_columns(df): + """ + 清理欄位名稱:去除前後空白、全部轉小寫 + 回傳清理後的 DataFrame(不要修改原始 df) + 提示:df.columns.str.strip().str.lower() + """ + # TODO: 你的程式碼 + result = df.copy() + result.columns = result.columns.str.strip().str.lower() + return result + + +def yellow_clean_amount(df): + """ + 清理 amount 欄位:移除 '$' 和 ',' 符號,轉為 float + 假設欄位名稱已經是小寫的 'amount' + 回傳清理後的 DataFrame(不要修改原始 df) + 提示:.str.replace() + .astype(float) + """ + # TODO: 你的程式碼 + result = df.copy() + result['amount'] = ( + result['amount'] + .str.replace('$', '', regex=False) + .str.replace(',', '', regex=False) + .astype(float) + ) + return result + + +def yellow_drop_duplicates(df): + """ + 移除完全重複的列,回傳去重後的 DataFrame + 提示:df.drop_duplicates() + """ + # TODO: 你的程式碼 + return df.drop_duplicates() # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ -class Red: - def red_clean_orders(path): - """ - 完整清理 pipeline:一個函式搞定所有清理步驟 - 1. 讀取 CSV - 2. 欄位名稱:去空白、轉小寫 - 3. amount:移除 '$' ',',轉 float - 4. order_date:轉為 datetime(無法轉換的設為 NaT) - 5. 刪除 amount 或 order_date 為空的列 - 6. 移除重複列 - - 回傳:清理後的 DataFrame - 提示:pd.to_datetime(errors='coerce') - """ - # TODO: 你的程式碼 - df = pd.read_csv(path) + +def red_clean_orders(path): + """ + 完整清理 pipeline:一個函式搞定所有清理步驟 + 1. 讀取 CSV + 2. 欄位名稱:去空白、轉小寫 + 3. amount:移除 '$' ',',轉 float + 4. order_date:轉為 datetime(無法轉換的設為 NaT) + 5. 刪除 amount 或 order_date 為空的列 + 6. 移除重複列 + + 回傳:清理後的 DataFrame + 提示:pd.to_datetime(errors='coerce') + """ + # TODO: 你的程式碼 + df = pd.read_csv(path) - df.columns = df.columns.str.strip().str.lower() + df.columns = df.columns.str.strip().str.lower() - df['amount'] = ( - df['amount'] - .str.replace('$', '', regex=False) - .str.replace(',', '', regex=False) - .astype(float) - ) + 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['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') - df = df.dropna(subset=['amount', 'order_date']) + df = df.dropna(subset=['amount', 'order_date']) - df = df.drop_duplicates() + df = df.drop_duplicates() - return df + return df diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 5f77fbc..81233ba 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -15,108 +15,108 @@ # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ -class Green: - 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') - """ - # TODO: 你的程式碼 - orders = pd.read_csv('datasets/ecommerce/orders_clean.csv') - customers = pd.read_csv('datasets/ecommerce/customers.csv') - products = pd.read_csv('datasets/ecommerce/products.csv') + +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') + """ + # TODO: 你的程式碼 + orders = pd.read_csv('datasets/ecommerce/orders_clean.csv') + customers = pd.read_csv('datasets/ecommerce/customers.csv') + products = pd.read_csv('datasets/ecommerce/products.csv') - df = pd.merge(orders, customers, on='customer_id', how='left') - df = pd.merge(df, products, on='product_id', how='left') - return df + df = pd.merge(orders, customers, on='customer_id', how='left') + df = pd.merge(df, products, on='product_id', how='left') + return df - def green_row_count(df): - """回傳 DataFrame 的列數 (int)""" - # TODO: 你的程式碼 - return len(df) +def green_row_count(df): + """回傳 DataFrame 的列數 (int)""" + # TODO: 你的程式碼 + return len(df) - def green_column_list(df): - """回傳 DataFrame 的所有欄位名稱 (list)""" - # TODO: 你的程式碼 - return df.columns.tolist() +def green_column_list(df): + """回傳 DataFrame 的所有欄位名稱 (list)""" + # TODO: 你的程式碼 + return df.columns.tolist() # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ -class Yellows: - def yellow_top_category(df): - """ - 哪個商品類別 (category) 的總營收最高? - 回傳該類別名稱 (str) - 提示:groupby('category')['amount'].sum() - """ - # TODO: 你的程式碼 - return df.groupby('category')['amount'].sum().idxmax() - - - def yellow_gold_vip_stats(df): - """ - Gold VIP 客戶總共下了幾張訂單?總金額多少? - 回傳 tuple: (訂單數 int, 總金額 float) - 提示:df[df['vip_level'] == 'Gold'] - """ - # TODO: 你的程式碼 - gold = df[df['vip_level'] == 'Gold'] - return (int(len(gold)), float(gold['amount'].sum())) - - - def yellow_region_avg_amount(df): - """ - 計算每個地區 (region) 的平均訂單金額 - 回傳 Series(index=region, values=平均金額) - 提示:groupby('region')['amount'].mean() - """ - # TODO: 你的程式碼 - return df.groupby('region')['amount'].mean() + +def yellow_top_category(df): + """ + 哪個商品類別 (category) 的總營收最高? + 回傳該類別名稱 (str) + 提示:groupby('category')['amount'].sum() + """ + # TODO: 你的程式碼 + return df.groupby('category')['amount'].sum().idxmax() + + +def yellow_gold_vip_stats(df): + """ + Gold VIP 客戶總共下了幾張訂單?總金額多少? + 回傳 tuple: (訂單數 int, 總金額 float) + 提示:df[df['vip_level'] == 'Gold'] + """ + # TODO: 你的程式碼 + gold = df[df['vip_level'] == 'Gold'] + return (int(len(gold)), float(gold['amount'].sum())) + + +def yellow_region_avg_amount(df): + """ + 計算每個地區 (region) 的平均訂單金額 + 回傳 Series(index=region, values=平均金額) + 提示:groupby('region')['amount'].mean() + """ + # TODO: 你的程式碼 + return df.groupby('region')['amount'].mean() # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ -class Red: - 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: 你的程式碼 - df['order_date'] = pd.to_datetime(df['order_date']) + +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: 你的程式碼 + df['order_date'] = pd.to_datetime(df['order_date']) - rfm = ( - df.groupby('customer_id') - .agg( - R=('order_date', 'max'), - F=('order_date', 'count'), - M=('amount', 'sum'), - customer_name=('customer_name', 'first') - ) - .reset_index() + rfm = ( + df.groupby('customer_id') + .agg( + R=('order_date', 'max'), + F=('order_date', 'count'), + M=('amount', 'sum'), + customer_name=('customer_name', 'first') ) - - return ( - rfm[['customer_id', 'customer_name', 'R', 'F', 'M']] - .sort_values('M', ascending=False) - .head(5) - .reset_index(drop=True) - ) \ No newline at end of file + .reset_index() + ) + + return ( + rfm[['customer_id', 'customer_name', 'R', 'F', 'M']] + .sort_values('M', ascending=False) + .head(5) + .reset_index(drop=True) + ) \ No newline at end of file diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 6d37edd..7fe52fc 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -19,110 +19,110 @@ def _load_data(): # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ -class Green: - def green_avg_by_month(): - """ - 計算每個月份 (1~12) 的平均訂單金額 - 回傳 Series(index=月份 1~12, values=平均金額) - 提示:df['order_date'].dt.month - """ - # TODO: 你的程式碼 - df = _load_data() - return df.groupby(df['order_date'].dt.month)['amount'].mean() - - - def green_top3_dates(): - """ - 找出訂單數最多的前 3 個日期 - 回傳 Series(index=日期, values=訂單數, 由多到少排序) - 提示:value_counts().head(3) - """ - # TODO: 你的程式碼 - df = _load_data() - return df['order_date'].dt.date.value_counts().head(3) - - - - def green_date_range(): - """ - 回傳資料的日期範圍 tuple: (最早日期, 最晚日期) - 格式為 pandas Timestamp - """ - # TODO: 你的程式碼 - df = _load_data() - return (df['order_date'].min(), df['order_date'].max()) + +def green_avg_by_month(): + """ + 計算每個月份 (1~12) 的平均訂單金額 + 回傳 Series(index=月份 1~12, values=平均金額) + 提示:df['order_date'].dt.month + """ + # TODO: 你的程式碼 + df = _load_data() + return df.groupby(df['order_date'].dt.month)['amount'].mean() + + +def green_top3_dates(): + """ + 找出訂單數最多的前 3 個日期 + 回傳 Series(index=日期, values=訂單數, 由多到少排序) + 提示:value_counts().head(3) + """ + # TODO: 你的程式碼 + df = _load_data() + return df['order_date'].dt.date.value_counts().head(3) + + + +def green_date_range(): + """ + 回傳資料的日期範圍 tuple: (最早日期, 最晚日期) + 格式為 pandas Timestamp + """ + # TODO: 你的程式碼 + df = _load_data() + return (df['order_date'].min(), df['order_date'].max()) # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ -class Yellows: - def yellow_monthly_revenue(): - """ - 計算每月總營收 - 回傳 Series(index=月底日期 period, values=總營收) - 提示:set_index('order_date').resample('ME')['amount'].sum() - """ - # TODO: 你的程式碼 - df = _load_data() - return df.set_index('order_date').resample('ME')['amount'].sum() - - - def yellow_rolling_avg(monthly_revenue): - """ - 計算 3 個月移動平均 - 接收 yellow_monthly_revenue() 的結果作為輸入 - 回傳 Series(同樣 index,values=移動平均,前 2 筆可為 NaN) - 提示:.rolling(window=3).mean() - """ - # TODO: 你的程式碼 - return monthly_revenue.rolling(window=3).mean() - - - def yellow_category_median(df): - """ - 計算每個商品類別 (category) 的訂單金額中位數,由高到低排序 - 回傳 Series(index=category, values=中位數) - 提示:groupby + median + sort_values - """ - # TODO: 你的程式碼 - return ( - df.groupby('category')['amount'] - .median() - .sort_values(ascending=False) - ) + +def yellow_monthly_revenue(): + """ + 計算每月總營收 + 回傳 Series(index=月底日期 period, values=總營收) + 提示:set_index('order_date').resample('ME')['amount'].sum() + """ + # TODO: 你的程式碼 + df = _load_data() + return df.set_index('order_date').resample('ME')['amount'].sum() + + +def yellow_rolling_avg(monthly_revenue): + """ + 計算 3 個月移動平均 + 接收 yellow_monthly_revenue() 的結果作為輸入 + 回傳 Series(同樣 index,values=移動平均,前 2 筆可為 NaN) + 提示:.rolling(window=3).mean() + """ + # TODO: 你的程式碼 + return monthly_revenue.rolling(window=3).mean() + + +def yellow_category_median(df): + """ + 計算每個商品類別 (category) 的訂單金額中位數,由高到低排序 + 回傳 Series(index=category, values=中位數) + 提示:groupby + median + sort_values + """ + # TODO: 你的程式碼 + return ( + df.groupby('category')['amount'] + .median() + .sort_values(ascending=False) + ) # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ -class Red: - def red_monthly_report(): - """ - 產出月報 DataFrame,每月一列,包含: - - order_count:當月訂單數 - - revenue:當月總營收 - - active_customers:當月不重複客戶數 - - avg_order_value:客單價(revenue / order_count) - - revenue_growth:月營收成長率(相對上月的 % 變化) - - index 為月份 (period 或 datetime) - 提示:resample + agg + pct_change - """ - # TODO: 你的程式碼 - df = _load_data() + +def red_monthly_report(): + """ + 產出月報 DataFrame,每月一列,包含: + - order_count:當月訂單數 + - revenue:當月總營收 + - active_customers:當月不重複客戶數 + - avg_order_value:客單價(revenue / order_count) + - revenue_growth:月營收成長率(相對上月的 % 變化) + + index 為月份 (period 或 datetime) + 提示:resample + agg + pct_change + """ + # TODO: 你的程式碼 + df = _load_data() - monthly = ( - df.set_index('order_date') - .resample('ME') - .agg( - order_count=('amount', 'count'), - revenue=('amount', 'sum'), - active_customers=('customer_id', 'nunique') - ) + monthly = ( + df.set_index('order_date') + .resample('ME') + .agg( + order_count=('amount', 'count'), + revenue=('amount', 'sum'), + active_customers=('customer_id', 'nunique') ) + ) - monthly['avg_order_value'] = monthly['revenue'] / monthly['order_count'] - monthly['revenue_growth'] = monthly['revenue'].pct_change() * 100 + monthly['avg_order_value'] = monthly['revenue'] / monthly['order_count'] + monthly['revenue_growth'] = monthly['revenue'].pct_change() * 100 - return monthly + return monthly diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index a67cff8..5c6f260 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -21,179 +21,179 @@ def _load_data(): # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ -class Green: - def green_bar_category(): - """ - 畫出每個商品類別 (category) 的訂單數長條圖 - 回傳 matplotlib Figure 物件 - 提示:sns.countplot 或 value_counts().plot.bar() - """ - # TODO: 你的程式碼 - df = _load_data() - fig, ax = plt.subplots(figsize=(8, 5)) - sns.countplot(data=df, x='category', order=df['category'].value_counts().index, ax=ax) - ax.set_title('Orders by Category') - ax.set_xlabel('Category') - ax.set_ylabel('Order Count') - plt.tight_layout() - return fig - - - def green_hist_amount(): - """ - 畫出訂單金額 (amount) 的分佈直方圖,分 20 個 bin - 回傳 matplotlib Figure 物件 - 提示:sns.histplot(bins=20) 或 plt.hist() - """ - # TODO: 你的程式碼 - df = _load_data() - fig, ax = plt.subplots(figsize=(8, 5)) - sns.histplot(data=df, x='amount', bins=20, ax=ax) - ax.set_title('Order Amount Distribution') - ax.set_xlabel('Amount') - ax.set_ylabel('Frequency') - plt.tight_layout() - return fig - - - def green_set_labels(): - """ - 建立一個簡單的長條圖(內容不限),但必須設定: - - 圖標題 (title) - - X 軸標籤 (xlabel) - - Y 軸標籤 (ylabel) - 回傳 matplotlib Figure 物件 - """ - # TODO: 你的程式碼 - df = _load_data() - fig, ax = plt.subplots(figsize=(8, 5)) - df['category'].value_counts().plot.bar(ax=ax) - ax.set_title('Orders by Category') - ax.set_xlabel('Category') - ax.set_ylabel('Order Count') - plt.tight_layout() - return fig + +def green_bar_category(): + """ + 畫出每個商品類別 (category) 的訂單數長條圖 + 回傳 matplotlib Figure 物件 + 提示:sns.countplot 或 value_counts().plot.bar() + """ + # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 5)) + sns.countplot(data=df, x='category', order=df['category'].value_counts().index, ax=ax) + ax.set_title('Orders by Category') + ax.set_xlabel('Category') + ax.set_ylabel('Order Count') + plt.tight_layout() + return fig + + +def green_hist_amount(): + """ + 畫出訂單金額 (amount) 的分佈直方圖,分 20 個 bin + 回傳 matplotlib Figure 物件 + 提示:sns.histplot(bins=20) 或 plt.hist() + """ + # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 5)) + sns.histplot(data=df, x='amount', bins=20, ax=ax) + ax.set_title('Order Amount Distribution') + ax.set_xlabel('Amount') + ax.set_ylabel('Frequency') + plt.tight_layout() + return fig + + +def green_set_labels(): + """ + 建立一個簡單的長條圖(內容不限),但必須設定: + - 圖標題 (title) + - X 軸標籤 (xlabel) + - Y 軸標籤 (ylabel) + 回傳 matplotlib Figure 物件 + """ + # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 5)) + df['category'].value_counts().plot.bar(ax=ax) + ax.set_title('Orders by Category') + ax.set_xlabel('Category') + ax.set_ylabel('Order Count') + plt.tight_layout() + return fig # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ -class Yellow: - def yellow_line_region_trend(): - """ - 畫折線圖:比較 North 和 South 兩個地區的月營收趨勢 - - X 軸:月份 - - Y 軸:該月總營收 - - 兩條線,有圖例 (legend) - 回傳 matplotlib Figure 物件 - 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') - """ - # TODO: 你的程式碼 - df = _load_data() - df['month'] = df['order_date'].dt.to_period('M') + +def yellow_line_region_trend(): + """ + 畫折線圖:比較 North 和 South 兩個地區的月營收趨勢 + - X 軸:月份 + - Y 軸:該月總營收 + - 兩條線,有圖例 (legend) + 回傳 matplotlib Figure 物件 + 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') + """ + # TODO: 你的程式碼 + df = _load_data() + df['month'] = df['order_date'].dt.to_period('M') - filtered = df[df['region'].isin(['North', 'South'])] - monthly = ( - filtered.groupby(['month', 'region'])['amount'] - .sum() - .reset_index() - ) - monthly['month'] = monthly['month'].dt.to_timestamp() + filtered = df[df['region'].isin(['North', 'South'])] + monthly = ( + filtered.groupby(['month', 'region'])['amount'] + .sum() + .reset_index() + ) + monthly['month'] = monthly['month'].dt.to_timestamp() - fig, ax = plt.subplots(figsize=(10, 5)) - sns.lineplot(data=monthly, x='month', y='amount', hue='region', ax=ax) - ax.set_title('Monthly Revenue: North vs South') - ax.set_xlabel('Month') - ax.set_ylabel('Revenue') - plt.tight_layout() - return fig - - - def yellow_box_vip(): - """ - 畫箱形圖:比較不同 VIP 等級 (vip_level) 的訂單金額分佈 - 回傳 matplotlib Figure 物件 - 提示:sns.boxplot(x='vip_level', y='amount', data=df) - """ - # TODO: 你的程式碼 - df = _load_data() - fig, ax = plt.subplots(figsize=(8, 5)) - sns.boxplot(data=df, x='vip_level', y='amount', ax=ax) - ax.set_title('Order Amount by VIP Level') - ax.set_xlabel('VIP Level') - ax.set_ylabel('Amount') - plt.tight_layout() - return fig - - - def yellow_scatter_price_amount(): - """ - 畫散佈圖:X=商品單價 (unit_price),Y=訂單金額 (amount) - 回傳 matplotlib Figure 物件 - 提示:plt.scatter() 或 sns.scatterplot() - """ - # TODO: 你的程式碼 - df = _load_data() - fig, ax = plt.subplots(figsize=(8, 5)) - sns.scatterplot(data=df, x='unit_price', y='amount', alpha=0.5, ax=ax) - ax.set_title('Unit Price vs Order Amount') - ax.set_xlabel('Unit Price') - ax.set_ylabel('Amount') - plt.tight_layout() - return fig + fig, ax = plt.subplots(figsize=(10, 5)) + sns.lineplot(data=monthly, x='month', y='amount', hue='region', ax=ax) + ax.set_title('Monthly Revenue: North vs South') + ax.set_xlabel('Month') + ax.set_ylabel('Revenue') + plt.tight_layout() + return fig + + +def yellow_box_vip(): + """ + 畫箱形圖:比較不同 VIP 等級 (vip_level) 的訂單金額分佈 + 回傳 matplotlib Figure 物件 + 提示:sns.boxplot(x='vip_level', y='amount', data=df) + """ + # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 5)) + sns.boxplot(data=df, x='vip_level', y='amount', ax=ax) + ax.set_title('Order Amount by VIP Level') + ax.set_xlabel('VIP Level') + ax.set_ylabel('Amount') + plt.tight_layout() + return fig + + +def yellow_scatter_price_amount(): + """ + 畫散佈圖:X=商品單價 (unit_price),Y=訂單金額 (amount) + 回傳 matplotlib Figure 物件 + 提示:plt.scatter() 或 sns.scatterplot() + """ + # TODO: 你的程式碼 + df = _load_data() + fig, ax = plt.subplots(figsize=(8, 5)) + sns.scatterplot(data=df, x='unit_price', y='amount', alpha=0.5, ax=ax) + ax.set_title('Unit Price vs Order Amount') + ax.set_xlabel('Unit Price') + ax.set_ylabel('Amount') + plt.tight_layout() + return fig # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ -class Red: - def red_category_dashboard(category="Electronics"): - """ - 針對指定類別,畫 2×2 的 subplot dashboard: - 1. 左上:該類別月營收趨勢 (折線圖) - 2. 右上:該類別各地區營收 (長條圖) - 3. 左下:該類別 Top 5 商品營收 (水平長條圖) - 4. 右下:該類別訂單金額分佈 (直方圖) - - 回傳 matplotlib Figure 物件 - 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) - """ - # TODO: 你的程式碼 - df = _load_data() - cat_df = df[df['category'] == category].copy() - cat_df['month'] = cat_df['order_date'].dt.to_period('M') - - fig, axes = plt.subplots(2, 2, figsize=(14, 10)) - fig.suptitle(f'{category} Dashboard', fontsize=16, fontweight='bold') - - monthly_rev = cat_df.groupby('month')['amount'].sum() - monthly_rev.index = monthly_rev.index.to_timestamp() - axes[0, 0].plot(monthly_rev.index, monthly_rev.values, marker='o') - axes[0, 0].set_title('Monthly Revenue Trend') - axes[0, 0].set_xlabel('Month') - axes[0, 0].set_ylabel('Revenue') - - region_rev = cat_df.groupby('region')['amount'].sum().sort_values(ascending=False) - axes[0, 1].bar(region_rev.index, region_rev.values) - axes[0, 1].set_title('Revenue by Region') - axes[0, 1].set_xlabel('Region') - axes[0, 1].set_ylabel('Revenue') - - top5 = ( - cat_df.groupby('product_name')['amount'] - .sum() - .sort_values(ascending=False) - .head(5) - ) - axes[1, 0].barh(top5.index[::-1], top5.values[::-1]) - axes[1, 0].set_title('Top 5 Products by Revenue') - axes[1, 0].set_xlabel('Revenue') - axes[1, 0].set_ylabel('Product') - - axes[1, 1].hist(cat_df['amount'], bins=20, edgecolor='white') - axes[1, 1].set_title('Order Amount Distribution') - axes[1, 1].set_xlabel('Amount') - axes[1, 1].set_ylabel('Frequency') - - plt.tight_layout() - return fig + +def red_category_dashboard(category="Electronics"): + """ + 針對指定類別,畫 2×2 的 subplot dashboard: + 1. 左上:該類別月營收趨勢 (折線圖) + 2. 右上:該類別各地區營收 (長條圖) + 3. 左下:該類別 Top 5 商品營收 (水平長條圖) + 4. 右下:該類別訂單金額分佈 (直方圖) + + 回傳 matplotlib Figure 物件 + 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + """ + # TODO: 你的程式碼 + df = _load_data() + cat_df = df[df['category'] == category].copy() + cat_df['month'] = cat_df['order_date'].dt.to_period('M') + + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle(f'{category} Dashboard', fontsize=16, fontweight='bold') + + monthly_rev = cat_df.groupby('month')['amount'].sum() + monthly_rev.index = monthly_rev.index.to_timestamp() + axes[0, 0].plot(monthly_rev.index, monthly_rev.values, marker='o') + axes[0, 0].set_title('Monthly Revenue Trend') + axes[0, 0].set_xlabel('Month') + axes[0, 0].set_ylabel('Revenue') + + region_rev = cat_df.groupby('region')['amount'].sum().sort_values(ascending=False) + axes[0, 1].bar(region_rev.index, region_rev.values) + axes[0, 1].set_title('Revenue by Region') + axes[0, 1].set_xlabel('Region') + axes[0, 1].set_ylabel('Revenue') + + top5 = ( + cat_df.groupby('product_name')['amount'] + .sum() + .sort_values(ascending=False) + .head(5) + ) + axes[1, 0].barh(top5.index[::-1], top5.values[::-1]) + axes[1, 0].set_title('Top 5 Products by Revenue') + axes[1, 0].set_xlabel('Revenue') + axes[1, 0].set_ylabel('Product') + + axes[1, 1].hist(cat_df['amount'], bins=20, edgecolor='white') + axes[1, 1].set_title('Order Amount Distribution') + axes[1, 1].set_xlabel('Amount') + axes[1, 1].set_ylabel('Frequency') + + plt.tight_layout() + return fig diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 55bc15f..3c201c9 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -17,247 +17,247 @@ # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ -class Green: - def green_plotly_bar(): - """ - 用 Plotly Express 畫出每個商品類別 (category) 的總營收長條圖 - 資料來源:orders_enriched.csv - 回傳 plotly Figure 物件 - 提示:px.bar() - """ - # TODO: 你的程式碼 - df = _load_enriched() - category_rev = ( - df.groupby('category')['amount'] - .sum() - .reset_index() - .sort_values('amount', ascending=False) - ) - fig = px.bar( - category_rev, - x='category', y='amount', - title='Total Revenue by Category', - labels={'amount': 'Revenue', 'category': 'Category'} - ) - return fig - - - def green_plotly_line(): - """ - 用 Plotly Express 畫出月營收趨勢折線圖 - 資料來源:orders_enriched.csv - 回傳 plotly Figure 物件 - 提示:先 groupby 月份算總營收,再 px.line() - """ - # TODO: 你的程式碼 - df = _load_enriched() - df['month'] = df['order_date'].dt.to_period('M').dt.to_timestamp() - monthly = df.groupby('month')['amount'].sum().reset_index() - fig = px.line( - monthly, - x='month', y='amount', - title='Monthly Revenue Trend', - labels={'month': 'Month', 'amount': 'Revenue'}, - markers=True - ) - return fig - - - def green_plotly_pie(): - """ - 用 Plotly Express 畫出 VIP 等級 (vip_level) 的訂單數佔比圓餅圖 - 資料來源:orders_enriched.csv - 回傳 plotly Figure 物件 - 提示:px.pie() - """ - # TODO: 你的程式碼 - df = _load_enriched() - vip_counts = df['vip_level'].value_counts().reset_index() - vip_counts.columns = ['vip_level', 'count'] - fig = px.pie( - vip_counts, - names='vip_level', values='count', - title='Order Share by VIP Level' - ) - return fig + +def green_plotly_bar(): + """ + 用 Plotly Express 畫出每個商品類別 (category) 的總營收長條圖 + 資料來源:orders_enriched.csv + 回傳 plotly Figure 物件 + 提示:px.bar() + """ + # TODO: 你的程式碼 + df = _load_enriched() + category_rev = ( + df.groupby('category')['amount'] + .sum() + .reset_index() + .sort_values('amount', ascending=False) + ) + fig = px.bar( + category_rev, + x='category', y='amount', + title='Total Revenue by Category', + labels={'amount': 'Revenue', 'category': 'Category'} + ) + return fig + + +def green_plotly_line(): + """ + 用 Plotly Express 畫出月營收趨勢折線圖 + 資料來源:orders_enriched.csv + 回傳 plotly Figure 物件 + 提示:先 groupby 月份算總營收,再 px.line() + """ + # TODO: 你的程式碼 + df = _load_enriched() + df['month'] = df['order_date'].dt.to_period('M').dt.to_timestamp() + monthly = df.groupby('month')['amount'].sum().reset_index() + fig = px.line( + monthly, + x='month', y='amount', + title='Monthly Revenue Trend', + labels={'month': 'Month', 'amount': 'Revenue'}, + markers=True + ) + return fig + + +def green_plotly_pie(): + """ + 用 Plotly Express 畫出 VIP 等級 (vip_level) 的訂單數佔比圓餅圖 + 資料來源:orders_enriched.csv + 回傳 plotly Figure 物件 + 提示:px.pie() + """ + # TODO: 你的程式碼 + df = _load_enriched() + vip_counts = df['vip_level'].value_counts().reset_index() + vip_counts.columns = ['vip_level', 'count'] + fig = px.pie( + vip_counts, + names='vip_level', values='count', + title='Order Share by VIP Level' + ) + return fig # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ -class Yellow: - def yellow_clean_and_merge(raw_path, customers_path, products_path): - """ - 完整 ETL:從髒資料到合併完成的 DataFrame - 1. 讀取 orders_raw.csv 並清理(欄位名稱、金額、日期、缺值、去重) - 2. 合併 customers.csv 和 products.csv - 回傳:合併後的 DataFrame - """ - # TODO: 你的程式碼 - df = pd.read_csv(raw_path) - customers = pd.read_csv(customers_path) - products = pd.read_csv(products_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') +def yellow_clean_and_merge(raw_path, customers_path, products_path): + """ + 完整 ETL:從髒資料到合併完成的 DataFrame + 1. 讀取 orders_raw.csv 並清理(欄位名稱、金額、日期、缺值、去重) + 2. 合併 customers.csv 和 products.csv + 回傳:合併後的 DataFrame + """ + # TODO: 你的程式碼 + df = pd.read_csv(raw_path) + customers = pd.read_csv(customers_path) + products = pd.read_csv(products_path) - df = df.dropna(subset=['amount', 'order_date']) - df = df.drop_duplicates() + 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') + + df = df.dropna(subset=['amount', 'order_date']) + df = df.drop_duplicates() - df = pd.merge(df, customers, on='customer_id', how='left') - df = pd.merge(df, products, on='product_id', how='left') - - return df - - - def yellow_kpi_summary(df): - """ - 計算 4 個核心 KPI,回傳 dict: - { - "total_revenue": float, # 總營收 - "order_count": int, # 訂單數 - "active_customers": int, # 不重複客戶數 - "avg_order_value": float, # 平均客單價 - } - """ - # TODO: 你的程式碼 - return { - "total_revenue": float(df['amount'].sum()), - "order_count": int(len(df)), - "active_customers": int(df['customer_id'].nunique()), - "avg_order_value": float(df['amount'].mean()), - } - - - def yellow_plotly_scatter(df): - """ - 用 Plotly Express 畫互動散佈圖: - - X:商品單價 (unit_price) - - Y:訂單金額 (amount) - - 顏色:商品類別 (category) - - hover 顯示:商品名稱 (product_name) - 回傳 plotly Figure 物件 - 提示:px.scatter(hover_data=['product_name']) - """ - # TODO: 你的程式碼 - fig = px.scatter( - df, - x='unit_price', y='amount', - color='category', - hover_data=['product_name'], - title='Unit Price vs Order Amount', - labels={'unit_price': 'Unit Price', 'amount': 'Order Amount'}, - opacity=0.6 - ) - return fig + df = pd.merge(df, customers, on='customer_id', how='left') + df = pd.merge(df, products, on='product_id', how='left') + + return df + + +def yellow_kpi_summary(df): + """ + 計算 4 個核心 KPI,回傳 dict: + { + "total_revenue": float, # 總營收 + "order_count": int, # 訂單數 + "active_customers": int, # 不重複客戶數 + "avg_order_value": float, # 平均客單價 + } + """ + # TODO: 你的程式碼 + return { + "total_revenue": float(df['amount'].sum()), + "order_count": int(len(df)), + "active_customers": int(df['customer_id'].nunique()), + "avg_order_value": float(df['amount'].mean()), + } + + +def yellow_plotly_scatter(df): + """ + 用 Plotly Express 畫互動散佈圖: + - X:商品單價 (unit_price) + - Y:訂單金額 (amount) + - 顏色:商品類別 (category) + - hover 顯示:商品名稱 (product_name) + 回傳 plotly Figure 物件 + 提示:px.scatter(hover_data=['product_name']) + """ + # TODO: 你的程式碼 + fig = px.scatter( + df, + x='unit_price', y='amount', + color='category', + hover_data=['product_name'], + title='Unit Price vs Order Amount', + labels={'unit_price': 'Unit Price', 'amount': 'Order Amount'}, + opacity=0.6 + ) + return fig # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ -class Red: + +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 + """ + # TODO: 你的程式碼 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 - """ - # TODO: 你的程式碼 - def red_dashboard(): - df = Yellow.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').dt.to_timestamp() - monthly = df.groupby('month')['amount'].sum().reset_index() - - top10 = ( - df.groupby('product_name')['amount'] - .sum() - .sort_values(ascending=False) - .head(10) - .reset_index() - ) - - region_rev = ( - df.groupby('region')['amount'] - .sum() - .sort_values(ascending=False) - .reset_index() - ) + df = Yellow.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').dt.to_timestamp() + monthly = df.groupby('month')['amount'].sum().reset_index() + + top10 = ( + df.groupby('product_name')['amount'] + .sum() + .sort_values(ascending=False) + .head(10) + .reset_index() + ) + + region_rev = ( + df.groupby('region')['amount'] + .sum() + .sort_values(ascending=False) + .reset_index() + ) - cat_rev = df.groupby('category')['amount'].sum().reset_index() - - fig = make_subplots( - rows=2, cols=2, - subplot_titles=( - 'Monthly Revenue Trend', - 'Top 10 Products by Revenue', - 'Revenue by Region', - 'Revenue Share by Category' - ), - specs=[ - [{"type": "xy"}, {"type": "xy"}], - [{"type": "xy"}, {"type": "domain"}] - ] - ) - - fig.add_trace( - go.Scatter(x=monthly['month'], y=monthly['amount'], - mode='lines+markers', name='Monthly Revenue'), - row=1, col=1 - ) - - fig.add_trace( - go.Bar( - x=top10['amount'], - y=top10['product_name'], - orientation='h', - name='Product Revenue' - ), - row=1, col=2 - ) - - fig.add_trace( - go.Bar(x=region_rev['region'], y=region_rev['amount'], - name='Region Revenue'), - row=2, col=1 - ) - - fig.add_trace( - go.Pie(labels=cat_rev['category'], values=cat_rev['amount'], - hole=0.4, name='Category Share'), - row=2, col=2 - ) - - fig.update_layout( - title_text='E-Commerce Interactive Dashboard', - title_font_size=20, - height=800, - showlegend=False - ) - - return fig + cat_rev = df.groupby('category')['amount'].sum().reset_index() + + fig = make_subplots( + rows=2, cols=2, + subplot_titles=( + 'Monthly Revenue Trend', + 'Top 10 Products by Revenue', + 'Revenue by Region', + 'Revenue Share by Category' + ), + specs=[ + [{"type": "xy"}, {"type": "xy"}], + [{"type": "xy"}, {"type": "domain"}] + ] + ) + + fig.add_trace( + go.Scatter(x=monthly['month'], y=monthly['amount'], + mode='lines+markers', name='Monthly Revenue'), + row=1, col=1 + ) + + fig.add_trace( + go.Bar( + x=top10['amount'], + y=top10['product_name'], + orientation='h', + name='Product Revenue' + ), + row=1, col=2 + ) + + fig.add_trace( + go.Bar(x=region_rev['region'], y=region_rev['amount'], + name='Region Revenue'), + row=2, col=1 + ) + + fig.add_trace( + go.Pie(labels=cat_rev['category'], values=cat_rev['amount'], + hole=0.4, name='Category Share'), + row=2, col=2 + ) + + fig.update_layout( + title_text='E-Commerce Interactive Dashboard', + title_font_size=20, + height=800, + showlegend=False + ) + + return fig From 84b63f32f45fd3b4139118f6a8767217617902a0 Mon Sep 17 00:00:00 2001 From: arku02 <97019633+arku02@users.noreply.github.com> Date: Sun, 3 May 2026 20:16:27 +0800 Subject: [PATCH 3/3] change hw change claude code --- homework/m5_visualization.py | 11 ++- homework/m6_plotly_capstone.py | 145 ++++++++++++++++----------------- 2 files changed, 79 insertions(+), 77 deletions(-) diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 5c6f260..af41f87 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -31,7 +31,8 @@ def green_bar_category(): # TODO: 你的程式碼 df = _load_data() fig, ax = plt.subplots(figsize=(8, 5)) - sns.countplot(data=df, x='category', order=df['category'].value_counts().index, ax=ax) + sns.countplot(data=df, x='category', + order=df['category'].value_counts().index, ax=ax) ax.set_title('Orders by Category') ax.set_xlabel('Category') ax.set_ylabel('Order Count') @@ -91,7 +92,6 @@ def yellow_line_region_trend(): # TODO: 你的程式碼 df = _load_data() df['month'] = df['order_date'].dt.to_period('M') - filtered = df[df['region'].isin(['North', 'South'])] monthly = ( filtered.groupby(['month', 'region'])['amount'] @@ -99,7 +99,6 @@ def yellow_line_region_trend(): .reset_index() ) monthly['month'] = monthly['month'].dt.to_timestamp() - fig, ax = plt.subplots(figsize=(10, 5)) sns.lineplot(data=monthly, x='month', y='amount', hue='region', ax=ax) ax.set_title('Monthly Revenue: North vs South') @@ -166,6 +165,7 @@ def red_category_dashboard(category="Electronics"): fig, axes = plt.subplots(2, 2, figsize=(14, 10)) fig.suptitle(f'{category} Dashboard', fontsize=16, fontweight='bold') + # 左上:月營收趨勢 monthly_rev = cat_df.groupby('month')['amount'].sum() monthly_rev.index = monthly_rev.index.to_timestamp() axes[0, 0].plot(monthly_rev.index, monthly_rev.values, marker='o') @@ -173,12 +173,14 @@ def red_category_dashboard(category="Electronics"): axes[0, 0].set_xlabel('Month') axes[0, 0].set_ylabel('Revenue') + # 右上:各地區營收 region_rev = cat_df.groupby('region')['amount'].sum().sort_values(ascending=False) axes[0, 1].bar(region_rev.index, region_rev.values) axes[0, 1].set_title('Revenue by Region') axes[0, 1].set_xlabel('Region') axes[0, 1].set_ylabel('Revenue') + # 左下:Top 5 商品水平長條圖 top5 = ( cat_df.groupby('product_name')['amount'] .sum() @@ -190,10 +192,11 @@ def red_category_dashboard(category="Electronics"): axes[1, 0].set_xlabel('Revenue') axes[1, 0].set_ylabel('Product') + # 右下:訂單金額分佈 axes[1, 1].hist(cat_df['amount'], bins=20, edgecolor='white') axes[1, 1].set_title('Order Amount Distribution') axes[1, 1].set_xlabel('Amount') axes[1, 1].set_ylabel('Frequency') plt.tight_layout() - return fig + return fig \ No newline at end of file diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 3c201c9..f2b25e8 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -13,6 +13,9 @@ import plotly.graph_objects as go from plotly.subplots import make_subplots +def _load_enriched(): + return pd.read_csv("datasets/ecommerce/orders_enriched.csv", + parse_dates=["order_date"]) # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) @@ -41,7 +44,6 @@ def green_plotly_bar(): ) return fig - def green_plotly_line(): """ 用 Plotly Express 畫出月營收趨勢折線圖 @@ -108,11 +110,9 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): ) df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') - df = df.dropna(subset=['amount', 'order_date']) df = df.drop_duplicates() - df = pd.merge(df, customers, on='customer_id', how='left') df = pd.merge(df, products, on='product_id', how='left') @@ -182,82 +182,81 @@ def red_dashboard(): 提示:from plotly.subplots import make_subplots """ # TODO: 你的程式碼 - def red_dashboard(): - - df = Yellow.yellow_clean_and_merge( + 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').dt.to_timestamp() - monthly = df.groupby('month')['amount'].sum().reset_index() - - top10 = ( - df.groupby('product_name')['amount'] - .sum() - .sort_values(ascending=False) - .head(10) - .reset_index() - ) - - region_rev = ( - df.groupby('region')['amount'] - .sum() - .sort_values(ascending=False) - .reset_index() - ) - - - cat_rev = df.groupby('category')['amount'].sum().reset_index() - - fig = make_subplots( - rows=2, cols=2, - subplot_titles=( + ) + + # 預計算各子圖資料 + df['month'] = df['order_date'].dt.to_period('M').dt.to_timestamp() + monthly = df.groupby('month')['amount'].sum().reset_index() + + top10 = ( + df.groupby('product_name')['amount'] + .sum() + .sort_values(ascending=False) + .head(10) + .reset_index() + ) + + region_rev = ( + df.groupby('region')['amount'] + .sum() + .sort_values(ascending=False) + .reset_index() + ) + + cat_rev = df.groupby('category')['amount'].sum().reset_index() + + # 建立 2×2 subplots + fig = make_subplots( + rows=2, cols=2, + subplot_titles=( 'Monthly Revenue Trend', 'Top 10 Products by Revenue', 'Revenue by Region', 'Revenue Share by Category' - ), - specs=[ - [{"type": "xy"}, {"type": "xy"}], - [{"type": "xy"}, {"type": "domain"}] - ] - ) - - fig.add_trace( - go.Scatter(x=monthly['month'], y=monthly['amount'], - mode='lines+markers', name='Monthly Revenue'), - row=1, col=1 - ) - - fig.add_trace( - go.Bar( - x=top10['amount'], - y=top10['product_name'], - orientation='h', - name='Product Revenue' - ), - row=1, col=2 - ) - - fig.add_trace( - go.Bar(x=region_rev['region'], y=region_rev['amount'], - name='Region Revenue'), - row=2, col=1 - ) - - fig.add_trace( - go.Pie(labels=cat_rev['category'], values=cat_rev['amount'], - hole=0.4, name='Category Share'), - row=2, col=2 - ) - - fig.update_layout( - title_text='E-Commerce Interactive Dashboard', - title_font_size=20, - height=800, - showlegend=False - ) + ), + specs=[ + [{"type": "xy"}, {"type": "xy"}], + [{"type": "xy"}, {"type": "domain"}] + ] + ) + + # 左上:折線圖 + fig.add_trace( + go.Scatter(x=monthly['month'], y=monthly['amount'], + mode='lines+markers', name='Monthly Revenue'), + row=1, col=1 + ) + + # 右上:水平長條圖 Top 10 + fig.add_trace( + go.Bar(x=top10['amount'], y=top10['product_name'], + orientation='h', name='Product Revenue'), + row=1, col=2 + ) + + # 左下:地區營收長條圖 + fig.add_trace( + go.Bar(x=region_rev['region'], y=region_rev['amount'], + name='Region Revenue'), + row=2, col=1 + ) + + # 右下:類別佔比甜甜圈圖 + fig.add_trace( + go.Pie(labels=cat_rev['category'], values=cat_rev['amount'], + hole=0.4, name='Category Share'), + row=2, col=2 + ) + + fig.update_layout( + title_text='E-Commerce Interactive Dashboard', + title_font_size=20, + height=800, + showlegend=False + ) return fig