From c1b569f95803ae8a4d806a44456f868c2343bcdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B1=E6=80=9D=E5=85=83?= Date: Sun, 3 May 2026 21:55:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BD=9C=E6=A5=AD=E7=B9=B3?= =?UTF-8?q?=E4=BA=A4=20-=20=E9=82=B1=E6=80=9D=E5=85=83=20-=20AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 30 +++--- homework/m2_pandas_cleaning.py | 93 ++++++++----------- homework/m3_pandas_advanced.py | 67 +++++++------- homework/m4_timeseries.py | 88 +++++++----------- homework/m5_visualization.py | 133 +++++++++++++-------------- homework/m6_plotly_capstone.py | 163 +++++++++++++++++---------------- 6 files changed, 267 insertions(+), 307 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..433ab4f 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -18,20 +18,20 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" - # TODO: 你的程式碼 - pass + arr = np.array([10, 20, 30, 40, 50]) + return arr.mean() def green_double(): """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" - # TODO: 你的程式碼 - pass + arr = np.array([10, 20, 30, 40, 50]) + return arr * 2 def green_filter(): """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" - # TODO: 你的程式碼 - pass + arr = np.array([10, 20, 30, 40, 50]) + return arr[arr > 25] # ============================================================ @@ -41,8 +41,7 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" - # TODO: 你的程式碼 - pass + return np.sum(prices > 1000) def yellow_top3_stock_indices(stocks): @@ -50,8 +49,8 @@ def yellow_top3_stock_indices(stocks): 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) 提示:np.argsort """ - # TODO: 你的程式碼 - pass + # argsort 是從小到大,[::-1] 翻轉成大到小,再取前三個 + return np.argsort(stocks)[::-1][:3] def yellow_restock_cost(prices, stocks): @@ -59,8 +58,8 @@ def yellow_restock_cost(prices, stocks): 單價 < 500 的商品,每種各進貨 50 個,回傳總花費 (float/int) 提示:布林遮罩 + .sum() """ - # TODO: 你的程式碼 - pass + # 選出符合條件的價格加總後再乘以進貨量 + return np.sum(prices[prices < 500]) * 50 # ============================================================ @@ -76,5 +75,8 @@ def red_double11_prices(prices, stocks): 回傳每個商品的雙 11 售價 (ndarray) 提示:np.where 可以巢狀使用 """ - # TODO: 你的程式碼 - pass + 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..6e1ba37 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -9,36 +9,24 @@ """ import pandas as pd - # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ def green_read_csv(): - """ - 讀取 orders_raw.csv,回傳原始 DataFrame(不做任何清理) - 提示:pd.read_csv() - """ - # TODO: 你的程式碼 - pass + """讀取 orders_raw.csv,回傳原始 DataFrame""" + df = pd.read_csv('datasets/ecommerce/orders_raw.csv') + return df def green_shape(df): - """ - 回傳 DataFrame 的 (列數, 欄數) tuple - 提示:df.shape - """ - # TODO: 你的程式碼 - pass + """回傳 DataFrame 的 (列數, 欄數) tuple""" + return df.shape def green_dtypes(df): - """ - 回傳 DataFrame 的欄位型別 (Series) - 提示:df.dtypes - """ - # TODO: 你的程式碼 - pass + """回傳 DataFrame 的欄位型別 (Series)""" + return df.dtypes # ============================================================ @@ -46,33 +34,23 @@ def green_dtypes(df): # ============================================================ def yellow_clean_columns(df): - """ - 清理欄位名稱:去除前後空白、全部轉小寫 - 回傳清理後的 DataFrame(不要修改原始 df) - 提示:df.columns.str.strip().str.lower() - """ - # TODO: 你的程式碼 - pass + """清理欄位名稱:去除前後空白、全部轉小寫""" + new_df = df.copy() + new_df.columns = new_df.columns.str.strip().str.lower() + return new_df def yellow_clean_amount(df): - """ - 清理 amount 欄位:移除 '$' 和 ',' 符號,轉為 float - 假設欄位名稱已經是小寫的 'amount' - 回傳清理後的 DataFrame(不要修改原始 df) - 提示:.str.replace() + .astype(float) - """ - # TODO: 你的程式碼 - pass + """清理 amount 欄位:移除 '$' 和 ',' 符號,轉為 float""" + 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: 你的程式碼 - pass + """移除完全重複的列""" + return df.drop_duplicates() # ============================================================ @@ -80,17 +58,24 @@ def yellow_drop_duplicates(df): # ============================================================ 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 + """完整清理 pipeline""" + # 1. 讀取 CSV + df = pd.read_csv(path) + + # 2. 欄位名稱:去空白、轉小寫 + df.columns = df.columns.str.strip().str.lower() + + # 3. amount:移除 '$' ',',轉 float + # 使用 regex=True 的一次性取代或連續 replace + df['amount'] = df['amount'].str.replace(r'[\$,]', '', regex=True).astype(float) + + # 4. order_date:轉為 datetime(無法轉換的設為 NaT) + df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') + + # 5. 刪除 amount 或 order_date 為空的列 + df = df.dropna(subset=['amount', 'order_date']) + + # 6. 移除重複列 + df = df.drop_duplicates() + + return df diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..cfbee48 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -11,7 +11,6 @@ """ import pandas as pd - # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ @@ -19,24 +18,25 @@ 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 + df_orders = pd.read_csv('datasets/ecommerce/orders_clean.csv') + df_customers = pd.read_csv('datasets/ecommerce/customers.csv') + df_products = pd.read_csv('datasets/ecommerce/products.csv') + + # 執行連續合併 + df = pd.merge(df_orders, df_customers, on='customer_id', how='left') + df = pd.merge(df, df_products, on='product_id', how='left') + return df 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() # ============================================================ @@ -46,31 +46,26 @@ def green_column_list(df): def yellow_top_category(df): """ 哪個商品類別 (category) 的總營收最高? - 回傳該類別名稱 (str) - 提示:groupby('category')['amount'].sum() """ - # TODO: 你的程式碼 - pass + # 根據 category 分組,加總 amount 後取最大值的索引 + 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 (int(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() # ============================================================ @@ -80,18 +75,18 @@ 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 + # 確保日期格式正確,才能計算最近下單日期 + df['order_date'] = pd.to_datetime(df['order_date']) + + rfm = df.groupby(['customer_id', 'customer_name']).agg({ + 'order_date': 'max', # Recency: 最近一次日期 + 'order_id': 'count', # Frequency: 訂單總數 + 'amount': 'sum' # Monetary: 消費總額 + }).reset_index() + + # 重命名欄位 + rfm.columns = ['customer_id', 'customer_name', 'R', 'F', 'M'] + + # 按 M 排序並取前 5 + return rfm.sort_values(by='M', ascending=False).head(5) diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..cebefed 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -21,32 +21,21 @@ def _load_data(): # ============================================================ def green_avg_by_month(): - """ - 計算每個月份 (1~12) 的平均訂單金額 - 回傳 Series(index=月份 1~12, values=平均金額) - 提示:df['order_date'].dt.month - """ - # TODO: 你的程式碼 - pass + 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: 你的程式碼 - pass + df = _load_data() + # 統計日期出現次數並取前三名 + return df['order_date'].value_counts().head(3) def green_date_range(): - """ - 回傳資料的日期範圍 tuple: (最早日期, 最晚日期) - 格式為 pandas Timestamp - """ - # TODO: 你的程式碼 - pass + df = _load_data() + # 取得日期最大與最小值 + return (df['order_date'].min(), df['order_date'].max()) # ============================================================ @@ -54,34 +43,19 @@ def green_date_range(): # ============================================================ def yellow_monthly_revenue(): - """ - 計算每月總營收 - 回傳 Series(index=月底日期 period, values=總營收) - 提示:set_index('order_date').resample('ME')['amount'].sum() - """ - # TODO: 你的程式碼 - pass + df = _load_data() + # 以月底為基準重取樣並加總營收 + return df.set_index('order_date')['amount'].resample('ME').sum() def yellow_rolling_avg(monthly_revenue): - """ - 計算 3 個月移動平均 - 接收 yellow_monthly_revenue() 的結果作為輸入 - 回傳 Series(同樣 index,values=移動平均,前 2 筆可為 NaN) - 提示:.rolling(window=3).mean() - """ - # TODO: 你的程式碼 - pass + # 計算 3 個月移動平均 + return monthly_revenue.rolling(window=3).mean() def yellow_category_median(df): - """ - 計算每個商品類別 (category) 的訂單金額中位數,由高到低排序 - 回傳 Series(index=category, values=中位數) - 提示:groupby + median + sort_values - """ - # TODO: 你的程式碼 - pass + # 按類別分組計算中位數並排序 + return df.groupby('category')['amount'].median().sort_values(ascending=False) # ============================================================ @@ -89,16 +63,20 @@ def yellow_category_median(df): # ============================================================ 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 + df = _load_data() + + # 1. 基礎聚合:訂單數、總營收、不重複客戶數 + report = df.set_index('order_date').resample('ME').agg({ + 'order_id': 'count', + 'amount': 'sum', + 'customer_id': 'nunique' + }) + + # 2. 重新命名欄位 + report.columns = ['order_count', 'revenue', 'active_customers'] + + # 3. 計算衍生欄位:客單價與成長率 + report['avg_order_value'] = report['revenue'] / report['order_count'] + report['revenue_growth'] = report['revenue'].pct_change() + + return report diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..562bcb5 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -1,9 +1,5 @@ """ M5 Matplotlib & Seaborn 視覺化 — 課後作業 -========================================== -情境:把分析結果做成圖表,用視覺化說故事。 - -資料路徑:datasets/ecommerce/orders_enriched.csv """ import matplotlib matplotlib.use("Agg") # 無 GUI 環境也能跑 @@ -11,100 +7,95 @@ import pandas as pd import seaborn as sns - def _load_data(): """輔助函式:讀取資料""" return pd.read_csv("datasets/ecommerce/orders_enriched.csv", parse_dates=["order_date"]) - # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ def green_bar_category(): - """ - 畫出每個商品類別 (category) 的訂單數長條圖 - 回傳 matplotlib Figure 物件 - 提示:sns.countplot 或 value_counts().plot.bar() - """ - # TODO: 你的程式碼 - pass - + df = _load_data() + fig, ax = plt.subplots() + sns.countplot(data=df, x='category', ax=ax) + return fig def green_hist_amount(): - """ - 畫出訂單金額 (amount) 的分佈直方圖,分 20 個 bin - 回傳 matplotlib Figure 物件 - 提示:sns.histplot(bins=20) 或 plt.hist() - """ - # TODO: 你的程式碼 - pass - + df = _load_data() + fig, ax = plt.subplots() + sns.histplot(df['amount'], bins=20, ax=ax) + return fig def green_set_labels(): - """ - 建立一個簡單的長條圖(內容不限),但必須設定: - - 圖標題 (title) - - X 軸標籤 (xlabel) - - Y 軸標籤 (ylabel) - 回傳 matplotlib Figure 物件 - """ - # TODO: 你的程式碼 - pass - + df = _load_data() + fig, ax = plt.subplots() + df['category'].value_counts().plot(kind='bar', ax=ax) + ax.set_title("Order Count by Category") + ax.set_xlabel("Category Name") + ax.set_ylabel("Number of Orders") + return fig # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ def yellow_line_region_trend(): - """ - 畫折線圖:比較 North 和 South 兩個地區的月營收趨勢 - - X 軸:月份 - - Y 軸:該月總營收 - - 兩條線,有圖例 (legend) - 回傳 matplotlib Figure 物件 - 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') - """ - # TODO: 你的程式碼 - pass - + df = _load_data() + # 建立月份欄位並過濾區域 + df['month'] = df['order_date'].dt.to_period('M').astype(str) + plot_df = df[df['region'].isin(['North', 'South'])] + + # 聚合資料 + trend = plot_df.groupby(['month', 'region'])['amount'].sum().reset_index() + + fig, ax = plt.subplots(figsize=(10, 6)) + sns.lineplot(data=trend, x='month', y='amount', hue='region', marker='o', ax=ax) + plt.xticks(rotation=45) + return fig def yellow_box_vip(): - """ - 畫箱形圖:比較不同 VIP 等級 (vip_level) 的訂單金額分佈 - 回傳 matplotlib Figure 物件 - 提示:sns.boxplot(x='vip_level', y='amount', data=df) - """ - # TODO: 你的程式碼 - pass - + df = _load_data() + fig, ax = plt.subplots() + sns.boxplot(x='vip_level', y='amount', data=df, ax=ax, order=['Bronze', 'Silver', 'Gold', 'Platinum']) + return fig def yellow_scatter_price_amount(): - """ - 畫散佈圖:X=商品單價 (unit_price),Y=訂單金額 (amount) - 回傳 matplotlib Figure 物件 - 提示:plt.scatter() 或 sns.scatterplot() - """ - # TODO: 你的程式碼 - pass - + df = _load_data() + fig, ax = plt.subplots() + sns.scatterplot(x='unit_price', y='amount', data=df, ax=ax, alpha=0.5) + 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 + df = _load_data() + sub_df = df[df['category'] == category].copy() + + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle(f"Dashboard: {category}", fontsize=16) + + # 1. 左上:該類別月營收趨勢 (折線圖) + sub_df['month'] = sub_df['order_date'].dt.to_period('M').astype(str) + monthly_rev = sub_df.groupby('month')['amount'].sum() + monthly_rev.plot(kind='line', marker='o', ax=axes[0, 0]) + axes[0, 0].set_title("Monthly Revenue Trend") + + # 2. 右上:該類別各地區營收 (長條圖) + sns.barplot(x='region', y='amount', data=sub_df, estimator=sum, errorbar=None, ax=axes[0, 1]) + axes[0, 1].set_title("Revenue by Region") + + # 3. 左下:該類別 Top 5 商品營收 (水平長條圖) + top5 = sub_df.groupby('product_name')['amount'].sum().nlargest(5) + top5.plot(kind='barh', ax=axes[1, 0]) + axes[1, 0].set_title("Top 5 Products by Revenue") + + # 4. 右下:該類別訂單金額分佈 (直方圖) + sns.histplot(sub_df['amount'], bins=15, kde=True, ax=axes[1, 1]) + axes[1, 1].set_title("Order Amount Distribution") + + plt.tight_layout(rect=[0, 0.03, 1, 0.95]) + return fig diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..412b52d 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -13,106 +13,115 @@ import plotly.graph_objects as go from plotly.subplots import make_subplots - # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) # ============================================================ def green_plotly_bar(): - """ - 用 Plotly Express 畫出每個商品類別 (category) 的總營收長條圖 - 資料來源:orders_enriched.csv - 回傳 plotly Figure 物件 - 提示:px.bar() - """ - # TODO: 你的程式碼 - pass - + df = pd.read_csv('orders_enriched.csv') + # 依照 category 分組計算總營收 + df_cat = df.groupby('category')['amount'].sum().reset_index() + fig = px.bar(df_cat, x='category', y='amount', title='每個商品類別的總營收') + return fig def green_plotly_line(): - """ - 用 Plotly Express 畫出月營收趨勢折線圖 - 資料來源:orders_enriched.csv - 回傳 plotly Figure 物件 - 提示:先 groupby 月份算總營收,再 px.line() - """ - # TODO: 你的程式碼 - pass - + df = pd.read_csv('orders_enriched.csv') + # 轉換日期並取出月份 (YYYY-MM) + df['order_date'] = pd.to_datetime(df['order_date']) + df['month'] = df['order_date'].dt.to_period('M').astype(str) + df_month = df.groupby('month')['amount'].sum().reset_index() + + fig = px.line(df_month, x='month', y='amount', title='月營收趨勢圖') + return fig def green_plotly_pie(): - """ - 用 Plotly Express 畫出 VIP 等級 (vip_level) 的訂單數佔比圓餅圖 - 資料來源:orders_enriched.csv - 回傳 plotly Figure 物件 - 提示:px.pie() - """ - # TODO: 你的程式碼 - pass - + df = pd.read_csv('orders_enriched.csv') + # 計算各 VIP 等級的訂單數量 + fig = px.pie(df, names='vip_level', title='VIP 等級訂單數佔比') + return fig # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) # ============================================================ 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 - + # 1. 讀取與清理 orders_raw + df_orders = pd.read_csv(raw_path) + df_orders.columns = df_orders.columns.str.strip().str.lower() # 統一欄位名 + df_orders = df_orders.drop_duplicates().dropna() # 去重與去缺值 + + # 金額與日期轉換 + df_orders['amount'] = pd.to_numeric(df_orders['amount'], errors='coerce') + df_orders['order_date'] = pd.to_datetime(df_orders['order_date'], errors='coerce') + + # 2. 讀取維度表 + df_customers = pd.read_csv(customers_path) + df_products = pd.read_csv(products_path) + + # 3. 合併 (Merge) + df_merged = df_orders.merge(df_customers, on='customer_id', how='left') + df_merged = df_merged.merge(df_products, on='product_id', how='left') + + return df_merged def yellow_kpi_summary(df): - """ - 計算 4 個核心 KPI,回傳 dict: - { - "total_revenue": float, # 總營收 - "order_count": int, # 訂單數 - "active_customers": int, # 不重複客戶數 - "avg_order_value": float, # 平均客單價 + kpis = { + "total_revenue": float(df['amount'].sum()), + "order_count": int(df['order_id'].nunique()), + "active_customers": int(df['customer_id'].nunique()), + "avg_order_value": float(df['amount'].mean()), } - """ - # TODO: 你的程式碼 - pass - + return kpis 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 - + fig = px.scatter( + df, + x='unit_price', + y='amount', + color='category', + hover_data=['product_name'], + title='商品單價 vs 訂單金額散佈圖' + ) + return fig # ============================================================ # 🔴 挑戰題(25 分) # ============================================================ 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: 你的程式碼 - pass + # 1. 資料處理 + 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. 建立 2x2 Subplots + fig = make_subplots( + rows=2, cols=2, + subplot_titles=('月營收趨勢', 'Top 10 商品營收', '各地區營收', '類別營收佔比'), + specs=[[{"type": "xy"}, {"type": "xy"}], + [{"type": "xy"}, {"type": "domain"}]] # 右下角是圓餅圖,需設定 domain + ) + + # 左上:月營收趨勢 (Line) + df_month = df.groupby('month')['amount'].sum().reset_index() + fig.add_trace(go.Scatter(x=df_month['month'], y=df_month['amount'], name='營收'), row=1, col=1) + + # 右上:Top 10 商品 (Bar) + df_top10 = df.groupby('product_name')['amount'].sum().nlargest(10).reset_index() + fig.add_trace(go.Bar(x=df_top10['product_name'], y=df_top10['amount'], name='商品'), row=1, col=2) + + # 左下:地區營收 (Bar) + df_region = df.groupby('region')['amount'].sum().reset_index() + fig.add_trace(go.Bar(x=df_region['region'], y=df_region['amount'], name='地區'), row=2, col=1) + + # 右下:類別佔比 (Pie/Donut) + df_cat = df.groupby('category')['amount'].sum().reset_index() + fig.add_trace(go.Pie(labels=df_cat['category'], values=df_cat['amount'], hole=.3), row=2, col=2) + + # 3. 整體設定 + fig.update_layout(height=800, title_text="電商營收分析儀表板 (Capstone)", showlegend=False) + + return fig