From 7538900fce777f55c2f8979b7e2d0b953d364d3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=A6=A4=E8=BB=92?= Date: Sun, 3 May 2026 15:35:09 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BD=9C=E6=A5=AD?= =?UTF-8?q?=E7=B9=B3=E4=BA=A4-=E6=9E=97=E5=A6=A4=E8=BB=92-AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 34 ++++++++--- homework/m2_pandas_cleaning.py | 47 ++++++++++++--- homework/m3_pandas_advanced.py | 104 ++++++++++++++++++++++++++++++--- homework/m4_timeseries.py | 43 +++++++++++--- homework/m5_visualization.py | 71 ++++++++++++++++++++-- homework/m6_plotly_capstone.py | 72 ++++++++++++++++++----- 6 files changed, 324 insertions(+), 47 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..5bcb3a6 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -19,19 +19,22 @@ 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] # ============================================================ @@ -42,7 +45,9 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce/products.csv' + prices = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=3) + return len(prices[prices > 1000]) def yellow_top3_stock_indices(stocks): @@ -51,7 +56,10 @@ def yellow_top3_stock_indices(stocks): 提示:np.argsort """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce/products.csv' + stocks = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=4) + stock_max = np.argsort(stocks) + return stock_max[:3] def yellow_restock_cost(prices, stocks): @@ -60,7 +68,10 @@ def yellow_restock_cost(prices, stocks): 提示:布林遮罩 + .sum() """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce/products.csv' + prices = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=3) + total = prices[prices<500] * 50 + return total.sum() # ============================================================ @@ -77,4 +88,13 @@ def red_double11_prices(prices, stocks): 提示:np.where 可以巢狀使用 """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce/products.csv' + prices = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=3) + stocks = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=4) + prices_07 = prices[stocks >= 100] * 0.7 + prices_09 = prices[(stocks >= 20) & (stocks <= 99)] * 0.9 + prices_original = prices[stocks < 20] + + final_price = np.concatenate([prices_07, prices_09, prices_original]) + + return final_price diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..7a01ccd 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -20,7 +20,10 @@ def green_read_csv(): 提示:pd.read_csv() """ # TODO: 你的程式碼 - pass + import pandas as pd + DATA = '../datasets/ecommerce/orders_raw.csv' + row = pd.read_csv(DATA) + return row def green_shape(df): @@ -29,7 +32,9 @@ def green_shape(df): 提示:df.shape """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce/orders_raw.csv' + df = pd.read_csv(DATA) + return df.shape def green_dtypes(df): @@ -38,7 +43,9 @@ def green_dtypes(df): 提示:df.dtypes """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce/orders_raw.csv' + df = pd.read_csv(DATA) + return df.types # ============================================================ @@ -52,7 +59,10 @@ def yellow_clean_columns(df): 提示:df.columns.str.strip().str.lower() """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce/orders_raw.csv' + df = pd.read_csv(DATA) + df.columns = df.columns.str.strip().str.lower() + return df.columns def yellow_clean_amount(df): @@ -63,7 +73,10 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce/orders_raw.csv' + df = pd.read_csv(DATA) + df['amount'] = df['amount'].str.replace('$','',regex=False).str.replace(',','',regex=False).astype(float) + return df['amount'] def yellow_drop_duplicates(df): @@ -72,7 +85,11 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce/orders_raw.csv' + df = pd.read_csv(DATA) + df = df.drop_duplicates() + return df + # ============================================================ @@ -93,4 +110,20 @@ def red_clean_orders(path): 提示:pd.to_datetime(errors='coerce') """ # TODO: 你的程式碼 - pass + df = pd.read_csv(path) + + df.columns = df.columns.str.strip().str.lower() + + df['amount'] = df['amount'].astype(str) .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=['order_date']) + + df['qty'] = df['qty'].fillna(df['qty'].median()) + + df = df.drop_duplicates() + + return df + +red_clean_orders('../datasets/ecommerce/orders_raw.csv') diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..f036edc 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -11,6 +11,10 @@ """ import pandas as pd +DATA = '../datasets/ecommerce' +orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) +customers = pd.read_csv(f'{DATA}/customers.csv') +products = pd.read_csv(f'{DATA}/products.csv') # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) @@ -24,19 +28,48 @@ def green_load_and_merge(): 提示:pd.merge(how='left') """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left')) + return df def green_row_count(df): """回傳 DataFrame 的列數 (int)""" # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left')) + + return df.shape[0] def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left')) + + return df.columns # ============================================================ @@ -50,7 +83,19 @@ def yellow_top_category(df): 提示:groupby('category')['amount'].sum() """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left')) + + products_hight = df.groupby('category')['amount'].sum().sort_values(ascending=False).head(1) + + return products_hight def yellow_gold_vip_stats(df): @@ -60,7 +105,21 @@ def yellow_gold_vip_stats(df): 提示:df[df['vip_level'] == 'Gold'] """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left')) + + goldvip = df[df['vip_level'] == 'Gold'] + gold_order = len(goldvip['order_id']) + price_total = float(goldvip["amount"].sum()) + total = (gold_order,price_total) + return total def yellow_region_avg_amount(df): @@ -70,8 +129,19 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce' + orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) + customers = pd.read_csv(f'{DATA}/customers.csv') + products = pd.read_csv(f'{DATA}/products.csv') + + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left')) + + region_rev = df.groupby('region')['amount'].mean() + return region_rev # ============================================================ # 🔴 挑戰題(25 分) @@ -94,4 +164,24 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - pass + rfm = ( + orders.groupby('customer_id') + .agg( + R=('order_date', 'max'), + F=('order_id', 'count'), + M=('amount', 'sum'), + ) + .reset_index()) + + rfm_named = rfm.merge( + customers[['customer_id', 'customer_name']], + on='customer_id', + how='left',) + + top5 = ( + rfm_named + .sort_values('M', ascending=False) + .head(5) + .reset_index(drop=True) + [['customer_id', 'customer_name', 'R', 'F', 'M']]) + return top5 diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..a44615c 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -11,7 +11,7 @@ def _load_data(): """輔助函式:讀取並解析日期""" - df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", + df = pd.read_csv("../datasets/ecommerce/orders_enriched.csv", parse_dates=["order_date"]) return df @@ -27,7 +27,11 @@ def green_avg_by_month(): 提示:df['order_date'].dt.month """ # TODO: 你的程式碼 - pass + + df =_load_data() + result = df.groupby(df['order_date'].dt.month)['amount'].mean() + + return result def green_top3_dates(): @@ -37,7 +41,9 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 - pass + df =_load_data() + top3_days = df['order_date'].dt.date.value_counts().head(3) + return top3_days def green_date_range(): @@ -46,8 +52,11 @@ def green_date_range(): 格式為 pandas Timestamp """ # TODO: 你的程式碼 - pass + df =_load_data() + min_date = df['order_date'].min() + max_date = df['order_date'].max() + return (min_date, max_date) # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) @@ -60,7 +69,9 @@ def yellow_monthly_revenue(): 提示:set_index('order_date').resample('ME')['amount'].sum() """ # TODO: 你的程式碼 - pass + df = _load_data() + mount_total_price = df.set_index('order_date').resample('ME')['amount'].sum() + return mount_total_price def yellow_rolling_avg(monthly_revenue): @@ -71,7 +82,9 @@ def yellow_rolling_avg(monthly_revenue): 提示:.rolling(window=3).mean() """ # TODO: 你的程式碼 - pass + df = yellow_monthly_revenue() + q_total = df.rolling(window=3).mean() + return q_total def yellow_category_median(df): @@ -81,8 +94,9 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 - pass - + df = _load_data() + median = df.groupby('category')['amount'].median().sort_values(ascending=False).round(1) + return median # ============================================================ # 🔴 挑戰題(25 分) @@ -101,4 +115,15 @@ def red_monthly_report(): 提示:resample + agg + pct_change """ # TODO: 你的程式碼 - pass + df = _load_data() + monthly_report = ( + df.groupby(df['order_date'].dt.month).agg( + 總訂單數=('order_id', 'count'), + 總營收=('amount', 'sum'), + 不重複客戶數=('customer_id', 'nunique'),).sort_index()) + monthly_report["客單價"] = (monthly_report['總營收'] / monthly_report['總訂單數']).round(1) + monthly_report['MoM成長率(%)'] = ( + monthly_report['總營收'].pct_change() * 100).round(2) + monthly_report = monthly_report.reset_index().rename(columns={'order_date': '月份'}) + + return monthly_report diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..fff6d99 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -29,7 +29,24 @@ def green_bar_category(): 提示:sns.countplot 或 value_counts().plot.bar() """ # TODO: 你的程式碼 - pass + df = _load_data() + cat_counts = df['category'].value_counts().reset_index() + cat_counts.columns = ['category', 'order_count'] + + fig = plt.figure(figsize=(8, 4)) + + sns.barplot( + data=cat_counts, + x='category', y='order_count', + hue='category', + palette='viridis', + legend=False + ) + plt.title('Order Count by Category', fontweight='bold') + plt.xlabel('Category') + plt.ylabel('Order Count') + plt.tight_layout() + return fig def green_hist_amount(): @@ -39,7 +56,15 @@ def green_hist_amount(): 提示:sns.histplot(bins=20) 或 plt.hist() """ # TODO: 你的程式碼 - pass + df = _load_data() + fig = sns.histplot(data=df, x='amount', bins=20, kde=True, color='steelblue') + plt.title('Order Amount Distribution', fontweight='bold') + plt.xlabel('Amount (NT$)') + plt.ylabel('Frequency') + plt.tight_layout() + + return fig + def green_set_labels(): @@ -51,7 +76,24 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ # TODO: 你的程式碼 - pass + df = _load_data() + cat_counts = df['category'].value_counts().reset_index() + cat_counts.columns = ['category', 'order_count'] + + fig = plt.figure(figsize=(8, 4)) + + sns.barplot( + data=cat_counts, + x='category', y='order_count', + hue='category', + palette='viridis', + legend=False + ) + plt.title('Order Count by Category', fontweight='bold') + plt.xlabel('Category') + plt.ylabel('Order Count') + plt.tight_layout() + return fig # ============================================================ @@ -68,7 +110,28 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ # TODO: 你的程式碼 - pass + df = _load_data() + ns_df = df[df['region'].isin(['North', 'South'])] + monthly_ns = ( + ns_df.groupby(['month', 'region'])['amount'] + .sum() + .reset_index() +) + + fig = plt.figure(figsize=(10, 4)) + sns.lineplot( + data=monthly_ns, + x='month', y='amount', + hue='region', marker='o', linewidth=2, +) + plt.title('Monthly Revenue: North vs South', fontweight='bold') + plt.xlabel('Month') + plt.ylabel('Revenue (NT$)') + plt.xticks(rotation=45) + plt.legend(title='Region') + plt.tight_layout() + + return fig def yellow_box_vip(): diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..dbbd8a8 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -25,8 +25,10 @@ def green_plotly_bar(): 回傳 plotly Figure 物件 提示:px.bar() """ - # TODO: 你的程式碼 - pass + df = pd.read_csv('datasets/ecommerce/orders_enriched.csv') + category_revenue = df.groupby('category')['amount'].sum().reset_index() + fig = px.bar(category_revenue, x='category', y='amount', title='各商品類別總營收') + return fig def green_plotly_line(): @@ -36,8 +38,12 @@ def green_plotly_line(): 回傳 plotly Figure 物件 提示:先 groupby 月份算總營收,再 px.line() """ - # TODO: 你的程式碼 - pass + df = pd.read_csv('datasets/ecommerce/orders_enriched.csv') + df['order_date'] = pd.to_datetime(df['order_date']) + df['month'] = df['order_date'].dt.to_period('M').astype(str) + monthly_revenue = df.groupby('month')['amount'].sum().reset_index() + fig = px.line(monthly_revenue, x='month', y='amount', title='月營收趨勢') + return fig def green_plotly_pie(): @@ -47,8 +53,10 @@ def green_plotly_pie(): 回傳 plotly Figure 物件 提示:px.pie() """ - # TODO: 你的程式碼 - pass + df = pd.read_csv('datasets/ecommerce/orders_enriched.csv') + vip_orders = df.groupby('vip_level').size().reset_index(name='count') + fig = px.pie(vip_orders, values='count', names='vip_level', title='VIP 等級訂單數佔比') + return fig # ============================================================ @@ -62,8 +70,36 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 2. 合併 customers.csv 和 products.csv 回傳:合併後的 DataFrame """ - # TODO: 你的程式碼 - pass + # 讀取資料 + orders = pd.read_csv(raw_path) + + # 清理欄位名稱 + orders.columns = orders.columns.str.strip() + + # 清理 amount:移除 $ 和 , 轉 float + orders['amount'] = orders['amount'].astype(str).str.replace('$', '').str.replace(',', '').astype(float) + + # 填補 qty 缺值為 1 + orders['qty'] = orders['qty'].fillna(1).astype(int) + + # 清理 order_date,drop 空值 + orders = orders.dropna(subset=['order_date']) + + # 去重 + orders = orders.drop_duplicates() + + # rename 欄位以匹配 + orders = orders.rename(columns={'Order_ID': 'order_id', 'Product_ID': 'product_id'}) + + # 讀取其他表 + customers = pd.read_csv(customers_path) + products = pd.read_csv(products_path) + + # 合併 + df = orders.merge(customers, on='customer_id', how='left') + df = df.merge(products, on='product_id', how='left') + + return df def yellow_kpi_summary(df): @@ -76,8 +112,17 @@ def yellow_kpi_summary(df): "avg_order_value": float, # 平均客單價 } """ - # TODO: 你的程式碼 - pass + total_revenue = df['amount'].sum() + order_count = df.shape[0] + active_customers = df['customer_id'].nunique() + avg_order_value = total_revenue / order_count if order_count > 0 else 0 + + return { + "total_revenue": total_revenue, + "order_count": order_count, + "active_customers": active_customers, + "avg_order_value": avg_order_value, + } def yellow_plotly_scatter(df): @@ -90,8 +135,9 @@ def yellow_plotly_scatter(df): 回傳 plotly Figure 物件 提示:px.scatter(hover_data=['product_name']) """ - # TODO: 你的程式碼 - pass + fig = px.scatter(df, x='unit_price', y='amount', color='category', + hover_data=['product_name'], title='商品單價 vs 訂單金額') + return fig # ============================================================ @@ -115,4 +161,4 @@ def red_dashboard(): 提示:from plotly.subplots import make_subplots """ # TODO: 你的程式碼 - pass + pass \ No newline at end of file From c2a54e82b554ac3cb587f52a125038badb760220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=A6=A4=E8=BB=92?= Date: Sun, 3 May 2026 16:41:34 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BD=9C=E6=A5=AD?= =?UTF-8?q?=E7=B9=B3=E4=BA=A4-=E6=9E=97=E5=A6=A4=E8=BB=92-AIPE03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 27 ++--- homework/m2_pandas_cleaning.py | 62 +++++------ homework/m3_pandas_advanced.py | 144 +++++++++++-------------- homework/m4_timeseries.py | 59 ++++++----- homework/m5_visualization.py | 136 +++++++++++++++++------- homework/m6_plotly_capstone.py | 188 ++++++++++++++++++++++++--------- 6 files changed, 364 insertions(+), 252 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5bcb3a6..8066b0e 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -34,7 +34,7 @@ def green_filter(): """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" # TODO: 你的程式碼 arr = np.array([10, 20, 30, 40, 50]) - return arr[arr >25] + return arr[arr > 25] # ============================================================ @@ -45,9 +45,8 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" # TODO: 你的程式碼 - DATA = '../datasets/ecommerce/products.csv' - prices = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=3) - return len(prices[prices > 1000]) + prices_arr = np.array(prices) + return len(prices_arr[prices_arr > 1000]) def yellow_top3_stock_indices(stocks): @@ -56,10 +55,9 @@ def yellow_top3_stock_indices(stocks): 提示:np.argsort """ # TODO: 你的程式碼 - DATA = '../datasets/ecommerce/products.csv' - stocks = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=4) - stock_max = np.argsort(stocks) - return stock_max[:3] + stocks_arr = np.array(stocks) + stocks_arr = np.argsort(stocks)[::-1] + return stocks_arr[:3] def yellow_restock_cost(prices, stocks): @@ -68,9 +66,7 @@ def yellow_restock_cost(prices, stocks): 提示:布林遮罩 + .sum() """ # TODO: 你的程式碼 - DATA = '../datasets/ecommerce/products.csv' - prices = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=3) - total = prices[prices<500] * 50 + total = prices[prices < 500] *50 return total.sum() @@ -88,13 +84,8 @@ def red_double11_prices(prices, stocks): 提示:np.where 可以巢狀使用 """ # TODO: 你的程式碼 - DATA = '../datasets/ecommerce/products.csv' - prices = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=3) - stocks = np.genfromtxt(DATA, delimiter=',', skip_header=1, usecols=4) prices_07 = prices[stocks >= 100] * 0.7 - prices_09 = prices[(stocks >= 20) & (stocks <= 99)] * 0.9 + prices_09 = prices[(stocks >= 20) & (stocks < 100)] * 0.9 prices_original = prices[stocks < 20] - - final_price = np.concatenate([prices_07, prices_09, prices_original]) - + final_price = np.concatenate([prices_original, prices_07, prices_09]) return final_price diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index 7a01ccd..cc6ee97 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -20,10 +20,9 @@ def green_read_csv(): 提示:pd.read_csv() """ # TODO: 你的程式碼 - import pandas as pd - DATA = '../datasets/ecommerce/orders_raw.csv' - row = pd.read_csv(DATA) - return row + DATA = "/datasets/ecommerce/orders_raw.csv" + df = pd.read_csv(DATA) + return df def green_shape(df): @@ -32,8 +31,7 @@ def green_shape(df): 提示:df.shape """ # TODO: 你的程式碼 - DATA = '../datasets/ecommerce/orders_raw.csv' - df = pd.read_csv(DATA) + return df.shape @@ -43,9 +41,8 @@ def green_dtypes(df): 提示:df.dtypes """ # TODO: 你的程式碼 - DATA = '../datasets/ecommerce/orders_raw.csv' - df = pd.read_csv(DATA) - return df.types + + return df.dtypes # ============================================================ @@ -59,10 +56,9 @@ def yellow_clean_columns(df): 提示:df.columns.str.strip().str.lower() """ # TODO: 你的程式碼 - DATA = '../datasets/ecommerce/orders_raw.csv' - df = pd.read_csv(DATA) + df.columns = df.columns.str.strip().str.lower() - return df.columns + return df def yellow_clean_amount(df): @@ -73,10 +69,12 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 - DATA = '../datasets/ecommerce/orders_raw.csv' - df = pd.read_csv(DATA) - df['amount'] = df['amount'].str.replace('$','',regex=False).str.replace(',','',regex=False).astype(float) - return df['amount'] + arr = (df['amount'] + .astype(str) + .str.replace('$', '', regex=False) + .str.replace(',', '', regex=False) + .astype(float)) + return arr def yellow_drop_duplicates(df): @@ -85,11 +83,9 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - DATA = '../datasets/ecommerce/orders_raw.csv' - df = pd.read_csv(DATA) - df = df.drop_duplicates() - return df + arr = df.drop_duplicates() + return arr # ============================================================ @@ -110,20 +106,16 @@ def red_clean_orders(path): 提示:pd.to_datetime(errors='coerce') """ # TODO: 你的程式碼 - df = pd.read_csv(path) + df = pd.read_csv(path) df.columns = df.columns.str.strip().str.lower() - - df['amount'] = df['amount'].astype(str) .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=['order_date']) - - df['qty'] = df['qty'].fillna(df['qty'].median()) - - df = df.drop_duplicates() - - return df - -red_clean_orders('../datasets/ecommerce/orders_raw.csv') + df["amount"] = (df['amount'] + .astype(str) + .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=['order_date']) + df['qty'] = df['qty'].fillna(df['qty'].median()) + df = df.drop_duplicates() + return df \ No newline at end of file diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index f036edc..11fbed1 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -11,10 +11,6 @@ """ import pandas as pd -DATA = '../datasets/ecommerce' -orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) -customers = pd.read_csv(f'{DATA}/customers.csv') -products = pd.read_csv(f'{DATA}/products.csv') # ============================================================ # 🟢 送分題(每題 10 分,共 30 分) @@ -28,48 +24,42 @@ def green_load_and_merge(): 提示:pd.merge(how='left') """ # TODO: 你的程式碼 - DATA = '../datasets/ecommerce' - orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) - customers = pd.read_csv(f'{DATA}/customers.csv') - products = pd.read_csv(f'{DATA}/products.csv') - + 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 = ( - orders - .merge(customers, on='customer_id', how='left') - .merge(products, on='product_id', how='left')) + orders + .merge(customers, on = 'customer_id', how='left') + .merge(products, on = 'product_id', how='left') + ) return df - def green_row_count(df): """回傳 DataFrame 的列數 (int)""" # TODO: 你的程式碼 - DATA = '../datasets/ecommerce' - orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) - customers = pd.read_csv(f'{DATA}/customers.csv') - products = pd.read_csv(f'{DATA}/products.csv') - + 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 = ( - orders - .merge(customers, on='customer_id', how='left') - .merge(products, on='product_id', how='left')) - + orders + .merge(customers, on = 'customer_id', how='left') + .merge(products, on = 'product_id', how='left') + ) return df.shape[0] def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" # TODO: 你的程式碼 - DATA = '../datasets/ecommerce' - orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) - customers = pd.read_csv(f'{DATA}/customers.csv') - products = pd.read_csv(f'{DATA}/products.csv') - + 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 = ( - orders - .merge(customers, on='customer_id', how='left') - .merge(products, on='product_id', how='left')) - - return df.columns + orders + .merge(customers, on = 'customer_id', how='left') + .merge(products, on = 'product_id', how='left') + ) + return df.columns # ============================================================ @@ -83,19 +73,9 @@ def yellow_top_category(df): 提示:groupby('category')['amount'].sum() """ # TODO: 你的程式碼 - DATA = '../datasets/ecommerce' - orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) - customers = pd.read_csv(f'{DATA}/customers.csv') - products = pd.read_csv(f'{DATA}/products.csv') - - df = ( - orders - .merge(customers, on='customer_id', how='left') - .merge(products, on='product_id', how='left')) - products_hight = df.groupby('category')['amount'].sum().sort_values(ascending=False).head(1) - - return products_hight + amount = df.groupby('category')['amount'].sum().sort_values(ascending=False).head(1) + return amount def yellow_gold_vip_stats(df): @@ -105,21 +85,12 @@ def yellow_gold_vip_stats(df): 提示:df[df['vip_level'] == 'Gold'] """ # TODO: 你的程式碼 - DATA = '../datasets/ecommerce' - orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) - customers = pd.read_csv(f'{DATA}/customers.csv') - products = pd.read_csv(f'{DATA}/products.csv') - - df = ( - orders - .merge(customers, on='customer_id', how='left') - .merge(products, on='product_id', how='left')) - goldvip = df[df['vip_level'] == 'Gold'] - gold_order = len(goldvip['order_id']) - price_total = float(goldvip["amount"].sum()) - total = (gold_order,price_total) - return total + vip = df[df['vip_level'] == 'Gold'] + vip_sum = len(vip['order_id']) + vip_amount = float(vip['amount'].sum()) + Gold_Vip = (vip_sum, vip_amount) + return Gold_Vip def yellow_region_avg_amount(df): @@ -129,19 +100,10 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 - DATA = '../datasets/ecommerce' - orders = pd.read_csv(f'{DATA}/orders_clean.csv', parse_dates=['order_date']) - customers = pd.read_csv(f'{DATA}/customers.csv') - products = pd.read_csv(f'{DATA}/products.csv') - - df = ( - orders - .merge(customers, on='customer_id', how='left') - .merge(products, on='product_id', how='left')) - region_rev = df.groupby('region')['amount'].mean() + Series = df.groupby('region')['amount'].mean() + return Series - return region_rev # ============================================================ # 🔴 挑戰題(25 分) @@ -164,24 +126,38 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - rfm = ( + 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") + # R = orders.groupby('customer_id')['order_date'].max() + # F = orders.groupby('customer_id')['order_id'].count() + # M = orders.groupby('customer_id')['amount'].sum().sort_values(ascending=False).head() + # df = ( + # orders + # .merge(customers, on = 'customer_id', how='left') + # .merge(products, on = 'product_id', how='left') + # ) + RFM = ( orders.groupby('customer_id') - .agg( - R=('order_date', 'max'), - F=('order_id', 'count'), - M=('amount', 'sum'), - ) - .reset_index()) - - rfm_named = rfm.merge( + .agg( + recency = ('order_date', 'max'), + frequency = ('order_id', 'count'), + monetary = ('amount', 'sum'), + ) + .reset_index() +) + rfm_named = RFM.merge( customers[['customer_id', 'customer_name']], - on='customer_id', - how='left',) + on = 'customer_id', + how='left', +) - top5 = ( + final_5 = ( rfm_named - .sort_values('M', ascending=False) + .sort_values("M", ascending=False) .head(5) .reset_index(drop=True) - [['customer_id', 'customer_name', 'R', 'F', 'M']]) - return top5 + [['customer_id', 'customer_name', 'R', 'F', 'M']] +) + + return final_5 \ No newline at end of file diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index a44615c..9306551 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -11,7 +11,7 @@ def _load_data(): """輔助函式:讀取並解析日期""" - df = pd.read_csv("../datasets/ecommerce/orders_enriched.csv", + df = pd.read_csv("datasets/ecommerce/orders_enriched.csv", parse_dates=["order_date"]) return df @@ -27,9 +27,9 @@ def green_avg_by_month(): 提示:df['order_date'].dt.month """ # TODO: 你的程式碼 + df = _load_data() - df =_load_data() - result = df.groupby(df['order_date'].dt.month)['amount'].mean() + result = df.groupby(df["order_date"].dt.month)['amount'].mean().round(1) return result @@ -41,9 +41,9 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 - df =_load_data() - top3_days = df['order_date'].dt.date.value_counts().head(3) - return top3_days + df = _load_data() + top3 = df['order_date'].dt.date.value_counts().head(3) + return top3 def green_date_range(): @@ -52,11 +52,12 @@ def green_date_range(): 格式為 pandas Timestamp """ # TODO: 你的程式碼 - df =_load_data() - min_date = df['order_date'].min() - max_date = df['order_date'].max() + df = _load_data() + time_1 = df['order_date'].min() + time_2 = df['order_date'].max() + result = (time_1, time_2) + return result - return (min_date, max_date) # ============================================================ # 🟡 核心題(每題 15 分,共 45 分) @@ -70,8 +71,8 @@ def yellow_monthly_revenue(): """ # TODO: 你的程式碼 df = _load_data() - mount_total_price = df.set_index('order_date').resample('ME')['amount'].sum() - return mount_total_price + test = df.set_index('order_date').resample('ME')['amount'].sum() + return test def yellow_rolling_avg(monthly_revenue): @@ -82,9 +83,8 @@ def yellow_rolling_avg(monthly_revenue): 提示:.rolling(window=3).mean() """ # TODO: 你的程式碼 - df = yellow_monthly_revenue() - q_total = df.rolling(window=3).mean() - return q_total + + return monthly_revenue.rolling(window=3).mean() def yellow_category_median(df): @@ -94,9 +94,9 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 - df = _load_data() - median = df.groupby('category')['amount'].median().sort_values(ascending=False).round(1) - return median + + return df.groupby('category')['amount'].median().sort_values(ascending=False) + # ============================================================ # 🔴 挑戰題(25 分) @@ -116,14 +116,17 @@ def red_monthly_report(): """ # TODO: 你的程式碼 df = _load_data() - monthly_report = ( - df.groupby(df['order_date'].dt.month).agg( - 總訂單數=('order_id', 'count'), - 總營收=('amount', 'sum'), - 不重複客戶數=('customer_id', 'nunique'),).sort_index()) - monthly_report["客單價"] = (monthly_report['總營收'] / monthly_report['總訂單數']).round(1) - monthly_report['MoM成長率(%)'] = ( - monthly_report['總營收'].pct_change() * 100).round(2) - monthly_report = monthly_report.reset_index().rename(columns={'order_date': '月份'}) - + df['year_mon'] = df['order_date'].dt.to_period('M') + monthly_report = (df.groupby('year_mon') + .agg( + 當月訂單數 = ('order_id', 'count'), + 當月總營收 = ('amount', 'sum'), + 單月不重複客戶數 = ('customer_id', 'nunique'), + ) + .sort_index() + ) + monthly_report['客單價'] = (monthly_report['當月總營收'] / monthly_report['當月訂單數']) + monthly_report['月營收成長率'] = (monthly_report['當月總營收'].pct_change()*100) + monthly_report = monthly_report.reset_index().rename(columns={'year_mon' : '月份'}) + monthly_report['月份'] = monthly_report['月份'].astype(str) return monthly_report diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index fff6d99..38b4cee 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -30,23 +30,18 @@ def green_bar_category(): """ # TODO: 你的程式碼 df = _load_data() - cat_counts = df['category'].value_counts().reset_index() - cat_counts.columns = ['category', 'order_count'] - - fig = plt.figure(figsize=(8, 4)) - + counts = df['category'].value_counts().reset_index() + counts.columns = ['category', 'order_count'] + figure = plt.figure(figsize=(8, 4)) sns.barplot( - data=cat_counts, - x='category', y='order_count', - hue='category', - palette='viridis', - legend=False + data=counts, + x='category', y='order_count', + hue='category', palette='viridis', legend=False, ) - plt.title('Order Count by Category', fontweight='bold') - plt.xlabel('Category') - plt.ylabel('Order Count') + plt.tight_layout() - return fig + return figure + def green_hist_amount(): @@ -57,14 +52,13 @@ def green_hist_amount(): """ # TODO: 你的程式碼 df = _load_data() - fig = sns.histplot(data=df, x='amount', bins=20, kde=True, color='steelblue') + figure = plt.figure(figsize=(9, 4)) + sns.histplot(data=df, x='amount', bins=20, kde=True, color='green') plt.title('Order Amount Distribution', fontweight='bold') plt.xlabel('Amount (NT$)') plt.ylabel('Frequency') plt.tight_layout() - - return fig - + return figure def green_set_labels(): @@ -77,23 +71,22 @@ def green_set_labels(): """ # TODO: 你的程式碼 df = _load_data() - cat_counts = df['category'].value_counts().reset_index() - cat_counts.columns = ['category', 'order_count'] - - fig = plt.figure(figsize=(8, 4)) - + counts = df['category'].value_counts().reset_index() + counts.columns = ['category', 'order_count'] + figure = plt.figure(figsize=(8, 4)) sns.barplot( - data=cat_counts, - x='category', y='order_count', - hue='category', - palette='viridis', - legend=False + data=counts, + x='category', y='order_count', + hue='category', palette='viridis', legend=False, ) plt.title('Order Count by Category', fontweight='bold') plt.xlabel('Category') plt.ylabel('Order Count') + for i, v in enumerate(counts['order_count']): + plt.text(i, v, f'{v:,}', ha='center', va='bottom', fontsize=10) + plt.tight_layout() - return fig + return figure # ============================================================ @@ -116,9 +109,9 @@ def yellow_line_region_trend(): ns_df.groupby(['month', 'region'])['amount'] .sum() .reset_index() -) + ) - fig = plt.figure(figsize=(10, 4)) + figure = plt.figure(figsize=(10, 4)) sns.lineplot( data=monthly_ns, x='month', y='amount', @@ -130,8 +123,7 @@ def yellow_line_region_trend(): plt.xticks(rotation=45) plt.legend(title='Region') plt.tight_layout() - - return fig + return figure def yellow_box_vip(): @@ -141,7 +133,18 @@ def yellow_box_vip(): 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ # TODO: 你的程式碼 - pass + df = _load_data() + figure = plt.figure(figsize=(9, 5)) + sns.boxplot( + data=df, + x='vip_level', y='amount', + hue='vip_level', palette='Set3', legend=False, + ) + plt.title('Order Amount Distribution by VIP Level', fontweight='bold') + plt.xlabel('VIP Level') + plt.ylabel('Amount (NT$)') + plt.tight_layout() + return figure def yellow_scatter_price_amount(): @@ -151,7 +154,17 @@ def yellow_scatter_price_amount(): 提示:plt.scatter() 或 sns.scatterplot() """ # TODO: 你的程式碼 - pass + df = _load_data() + figure = plt.figure(figsize=(9, 5)) + sns.scatterplot( + data=df, + x='unit_price', y='amount', + ) + plt.title('Unit_price Amount Distribution by Category', fontweight='bold') + plt.xlabel('Unit Price') + plt.ylabel('Amount (NT$)') + plt.tight_layout() + return figure # ============================================================ @@ -170,4 +183,55 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ # TODO: 你的程式碼 - pass + df = _load_data() + elec = df[df['category'] == category].copy() + elec_monthly = elec.groupby('month')['amount'].sum().reset_index() + elec_region = ( + elec.groupby('region')['amount'] + .sum() + .sort_values(ascending=False) + .reset_index() + ) + elec_top5 = ( + elec.groupby('product_name')['amount'] + .sum() + .sort_values(ascending=False) + .head(5) + .reset_index() + ) + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + fig.suptitle('Electronics Category Dashboard', fontsize=16, fontweight='bold') + sns.lineplot( + data=elec_monthly, x='month', y='amount', + marker='o', linewidth=2, color='#1f77b4', ax=axes[0, 0], + ) + axes[0, 0].set_title('Monthly Revenue Trend') + axes[0, 0].set_xlabel('Month') + axes[0, 0].set_ylabel('Revenue (NT$)') + axes[0, 0].tick_params(axis='x', rotation=45) + sns.barplot( + data=elec_region, x='region', y='amount', + hue='region', palette='viridis', legend=False, ax=axes[0, 1], +) + axes[0, 1].set_title('Revenue by Region') + axes[0, 1].set_xlabel('Region') + axes[0, 1].set_ylabel('Revenue (NT$)') + for i, v in enumerate(elec_region['amount']): + axes[0, 1].text(i, v, f'{v:,.0f}', ha='center', va='bottom', fontsize=9) + sns.barplot( + data=elec_top5, y='product_name', x='amount', + hue='product_name', palette='magma', legend=False, ax=axes[1, 0], + ) + axes[1, 0].set_title('Top 5 Products') + axes[1, 0].set_xlabel('Revenue (NT$)') + axes[1, 0].set_ylabel('Product') + sns.histplot( + data=elec, x='amount', bins=25, kde=True, + color='#d62728', ax=axes[1, 1], + ) + axes[1, 1].set_title('Amount Distribution') + axes[1, 1].set_xlabel('Amount (NT$)') + 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 dbbd8a8..4b8329c 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -25,9 +25,15 @@ def green_plotly_bar(): 回傳 plotly Figure 物件 提示:px.bar() """ - df = pd.read_csv('datasets/ecommerce/orders_enriched.csv') - category_revenue = df.groupby('category')['amount'].sum().reset_index() - fig = px.bar(category_revenue, x='category', y='amount', title='各商品類別總營收') + # TODO: 你的程式碼 + df = pd.read_csv( + 'datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + region_rev = df.groupby('category', as_index=False)['amount'].sum().sort_values('amount', ascending=False) + fig = px.bar(region_rev, x='category', y='amount', text='amount', + color='category', title='Revenue by Category') + fig.update_traces(texttemplate='%{text:,.0f}', textposition='outside') + fig.update_layout(height=400, showlegend=False) return fig @@ -38,11 +44,16 @@ def green_plotly_line(): 回傳 plotly Figure 物件 提示:先 groupby 月份算總營收,再 px.line() """ - df = pd.read_csv('datasets/ecommerce/orders_enriched.csv') - df['order_date'] = pd.to_datetime(df['order_date']) + # TODO: 你的程式碼 + df = pd.read_csv( + 'datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) df['month'] = df['order_date'].dt.to_period('M').astype(str) - monthly_revenue = df.groupby('month')['amount'].sum().reset_index() - fig = px.line(monthly_revenue, x='month', y='amount', title='月營收趨勢') + monthly = df.groupby('month', as_index=False)['amount'].sum() + + fig = px.line(monthly, x='month', y='amount', markers=True, + title='Monthly Revenue Trend') + fig.update_layout(height=400) return fig @@ -53,9 +64,14 @@ def green_plotly_pie(): 回傳 plotly Figure 物件 提示:px.pie() """ - df = pd.read_csv('datasets/ecommerce/orders_enriched.csv') - vip_orders = df.groupby('vip_level').size().reset_index(name='count') - fig = px.pie(vip_orders, values='count', names='vip_level', title='VIP 等級訂單數佔比') + # TODO: 你的程式碼 + df = pd.read_csv( + 'datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + vip_rev = df.groupby('vip_level', as_index=False)['amount'].sum() + fig = px.pie(vip_rev, names='vip_level', values='amount', + title='VIP Level Share', hole=0.4) + fig.update_layout(height=400) return fig @@ -70,36 +86,30 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 2. 合併 customers.csv 和 products.csv 回傳:合併後的 DataFrame """ - # 讀取資料 - orders = pd.read_csv(raw_path) - - # 清理欄位名稱 - orders.columns = orders.columns.str.strip() - - # 清理 amount:移除 $ 和 , 轉 float - orders['amount'] = orders['amount'].astype(str).str.replace('$', '').str.replace(',', '').astype(float) - - # 填補 qty 缺值為 1 - orders['qty'] = orders['qty'].fillna(1).astype(int) - - # 清理 order_date,drop 空值 - orders = orders.dropna(subset=['order_date']) - - # 去重 - orders = orders.drop_duplicates() - - # rename 欄位以匹配 - orders = orders.rename(columns={'Order_ID': 'order_id', 'Product_ID': 'product_id'}) - - # 讀取其他表 + # TODO: 你的程式碼 + df = pd.read_csv( + raw_path, + parse_dates=['order_date'],) + df.columns = df.columns.str.strip().str.lower() + df['amount'] = ( + df['amount'].astype(str) + .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=['order_date']) + df['qty'] = df['qty'].fillna(df['qty'].median()) + df = df.drop_duplicates() customers = pd.read_csv(customers_path) - products = pd.read_csv(products_path) - - # 合併 - df = orders.merge(customers, on='customer_id', how='left') - df = df.merge(products, on='product_id', how='left') - - return df + products = pd.read_csv(products_path) + + enriched = ( + df + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') +) + return enriched def yellow_kpi_summary(df): @@ -112,18 +122,37 @@ def yellow_kpi_summary(df): "avg_order_value": float, # 平均客單價 } """ - total_revenue = df['amount'].sum() - order_count = df.shape[0] - active_customers = df['customer_id'].nunique() - avg_order_value = total_revenue / order_count if order_count > 0 else 0 + # TODO: 你的程式碼 + df = pd.read_csv('datasets/ecommerce/orders_raw.csv', + parse_dates=['order_date'],) + customers = pd.read_csv('datasets/ecommerce/customers.csv') + products = pd.read_csv('datasets/ecommerce/products.csv') + df.columns = df.columns.str.strip().str.lower() + df['amount'] = ( + df['amount'].astype(str) + .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=['order_date']) + df['qty'] = df['qty'].fillna(df['qty'].median()) + df = df.drop_duplicates() - return { - "total_revenue": total_revenue, - "order_count": order_count, - "active_customers": active_customers, - "avg_order_value": avg_order_value, + enriched = ( + df + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + kpis = { + '總營收': enriched['amount'].sum(), + '總訂單數': len(enriched), + '活躍顧客數': enriched['customer_id'].nunique(), + '客單價': enriched['amount'].sum() / len(enriched), } + return kpis + def yellow_plotly_scatter(df): """ @@ -135,8 +164,15 @@ def yellow_plotly_scatter(df): 回傳 plotly Figure 物件 提示:px.scatter(hover_data=['product_name']) """ - fig = px.scatter(df, x='unit_price', y='amount', color='category', - hover_data=['product_name'], title='商品單價 vs 訂單金額') + # TODO: 你的程式碼 + fig = px.scatter( + df, + x='unit_price', + y='amount', + color='category', + hover_data=['product_name'], + title='Unit Price vs. Amount by Category' + ) return fig @@ -161,4 +197,54 @@ def red_dashboard(): 提示:from plotly.subplots import make_subplots """ # TODO: 你的程式碼 - pass \ No newline at end of file + df = pd.read_csv('datasets/ecommerce/orders_raw.csv', + parse_dates=['order_date'],) + customers = pd.read_csv('datasets/ecommerce/customers.csv') + products = pd.read_csv('datasets/ecommerce/products.csv') + df.columns = df.columns.str.strip().str.lower() + df['amount'] = ( + df['amount'].astype(str) + .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=['order_date']) + df['qty'] = df['qty'].fillna(df['qty'].median()) + df = df.drop_duplicates() + + enriched = ( + df + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + monthly = enriched.groupby('month', as_index=False)['amount'].sum() + top_prod = (enriched.groupby('product_name', as_index=False)['amount'] + .sum().sort_values('amount', ascending=False).head(10)) + region_rev = enriched.groupby('region', as_index=False)['amount'].sum() + cat_rev = enriched.groupby('category', as_index=False)['amount'].sum() + fig = make_subplots( + rows=2, cols=2, + subplot_titles=('Monthly Revenue Trend', + 'Top 10 Products', + 'Revenue by Region', + 'Category Share'), + 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'), row=1, col=1) + fig.add_trace(go.Bar(x=top_prod['product_name'], y=top_prod['amount'], + name='Top Products'), row=1, col=2) + fig.add_trace(go.Bar(x=region_rev['region'], y=region_rev['amount'], + name='Region'), row=2, col=1) + fig.add_trace(go.Pie(labels=cat_rev['category'], values=cat_rev['amount'], + name='Category', hole=0.4), row=2, col=2) + + fig.update_layout( + title_text='E-Commerce Sales Dashboard — 2025【解答版】', + height=750, showlegend=False, + ) + fig.update_xaxes(tickangle=45, row=1, col=2) + return fig