From c97b779adba517f23afc25a47006732506cb8ef5 Mon Sep 17 00:00:00 2001 From: kellyLiu Date: Sun, 3 May 2026 21:09:42 +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=20-=20=E5=8A=89=E5=87=B1=E8=8E=89=20-=20AIPE?= =?UTF-8?q?03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 29 +++++---- homework/m2_pandas_cleaning.py | 34 +++++----- homework/m3_pandas_advanced.py | 37 ++++++----- homework/m4_timeseries.py | 36 ++++++----- homework/m5_visualization.py | 112 ++++++++++++++++++++++++++++----- homework/m6_plotly_capstone.py | 110 +++++++++++++++++++++++++++----- 6 files changed, 271 insertions(+), 87 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..07c7b96 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -18,20 +18,19 @@ 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 +40,8 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" - # TODO: 你的程式碼 - pass + mask = prices > 1000 + return mask.sum() def yellow_top3_stock_indices(stocks): @@ -50,8 +49,8 @@ def yellow_top3_stock_indices(stocks): 回傳庫存最多的前 3 個商品的索引位置 (ndarray, 由大到小排) 提示:np.argsort """ - # TODO: 你的程式碼 - pass + idx = np.argsort(-stocks) + return idx[0:3] def yellow_restock_cost(prices, stocks): @@ -59,8 +58,8 @@ def yellow_restock_cost(prices, stocks): 單價 < 500 的商品,每種各進貨 50 個,回傳總花費 (float/int) 提示:布林遮罩 + .sum() """ - # TODO: 你的程式碼 - pass + total = prices[prices < 500] *50 + return total.sum() # ============================================================ @@ -76,5 +75,5 @@ 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)) \ No newline at end of file diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index fa36bff..ee9d16d 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -19,8 +19,9 @@ def green_read_csv(): 讀取 orders_raw.csv,回傳原始 DataFrame(不做任何清理) 提示:pd.read_csv() """ - # TODO: 你的程式碼 - pass + DATA = '../datasets/ecommerce/orders_raw.csv' + df = pd.read_csv(DATA) + return df def green_shape(df): @@ -28,8 +29,7 @@ def green_shape(df): 回傳 DataFrame 的 (列數, 欄數) tuple 提示:df.shape """ - # TODO: 你的程式碼 - pass + return df.shape def green_dtypes(df): @@ -37,8 +37,7 @@ def green_dtypes(df): 回傳 DataFrame 的欄位型別 (Series) 提示:df.dtypes """ - # TODO: 你的程式碼 - pass + return df.dtypes # ============================================================ @@ -51,8 +50,9 @@ def yellow_clean_columns(df): 回傳清理後的 DataFrame(不要修改原始 df) 提示:df.columns.str.strip().str.lower() """ - # TODO: 你的程式碼 - pass + df2 = df.copy() + df2.columns = df2.columns.str.strip().str.lower() + return df2 def yellow_clean_amount(df): @@ -62,8 +62,9 @@ def yellow_clean_amount(df): 回傳清理後的 DataFrame(不要修改原始 df) 提示:.str.replace() + .astype(float) """ - # TODO: 你的程式碼 - pass + df2 = df.copy() + df2['amount'] = df2['amount'].astype(str).str.replace('$','', regex=False).str.replace(',','', regex=False).astype(float) + return df2 def yellow_drop_duplicates(df): @@ -71,8 +72,8 @@ def yellow_drop_duplicates(df): 移除完全重複的列,回傳去重後的 DataFrame 提示:df.drop_duplicates() """ - # TODO: 你的程式碼 - pass + df = df.drop_duplicates() + return df # ============================================================ @@ -92,5 +93,10 @@ def red_clean_orders(path): 回傳:清理後的 DataFrame 提示:pd.to_datetime(errors='coerce') """ - # TODO: 你的程式碼 - pass + df = pd.read_csv(path) + df.columns = df.columns.str.strip().str.lower() + df['amount'] = df['amount'].astype(str).replace('$','',regex=False).replace(',','',regex=False).astype(float) + df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') + df = df.dropna(subset=['order_date','amount']) # 只要這兩個欄位中任一個有缺失值,該列就會被剔除。 + df = df.drop_duplicates() + return df.shape[0] \ No newline at end of file diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..e0f77f1 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -23,20 +23,22 @@ def green_load_and_merge(): - 再 LEFT JOIN products.csv ON product_id 提示: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') + + return orders def green_row_count(df): """回傳 DataFrame 的列數 (int)""" - # TODO: 你的程式碼 - pass + return df.shape[0] def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" - # TODO: 你的程式碼 - pass + return list(df.columns) # ============================================================ @@ -49,9 +51,8 @@ def yellow_top_category(df): 回傳該類別名稱 (str) 提示:groupby('category')['amount'].sum() """ - # TODO: 你的程式碼 - pass - + top = df.groupby('category')['amount'].sum().sort_values(ascending=False) + return f'{top.idxmax()}' def yellow_gold_vip_stats(df): """ @@ -59,9 +60,9 @@ def yellow_gold_vip_stats(df): 回傳 tuple: (訂單數 int, 總金額 float) 提示:df[df['vip_level'] == 'Gold'] """ - # TODO: 你的程式碼 - pass - + gold_mask = df[df['vip_level'] == 'Gold'] + gold = gold_mask['amount'].agg(['count', 'sum']) + return f'訂單數: {int(gold['count'])}', f'總金額:{float(gold['sum'])}' def yellow_region_avg_amount(df): """ @@ -69,8 +70,8 @@ def yellow_region_avg_amount(df): 回傳 Series(index=region, values=平均金額) 提示:groupby('region')['amount'].mean() """ - # TODO: 你的程式碼 - pass + region_avg_amount = df.groupy('region')['amount'].mean().round(1) + return region_avg_amount # ============================================================ @@ -94,4 +95,10 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - pass + rfm = df.groupby('customer_id','customer_name').agg( + R=('order_date', 'max'), + F=('order_id', 'count'), + M=('amount', 'sum') + ).reset_index() + result = rfm.sort_values('M',ascending=False).head() + return result diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..e1d5a3f 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -26,8 +26,9 @@ def green_avg_by_month(): 回傳 Series(index=月份 1~12, values=平均金額) 提示:df['order_date'].dt.month """ - # TODO: 你的程式碼 - pass + df = _load_data() + df['month'] = df['order_date'].dt.month + return df.groupby(['month'])['amount'].mean().round(1) def green_top3_dates(): @@ -36,8 +37,8 @@ def green_top3_dates(): 回傳 Series(index=日期, values=訂單數, 由多到少排序) 提示:value_counts().head(3) """ - # TODO: 你的程式碼 - pass + df = _load_data() + return df['order_date'].value_counts().head(3) def green_date_range(): @@ -45,8 +46,8 @@ def green_date_range(): 回傳資料的日期範圍 tuple: (最早日期, 最晚日期) 格式為 pandas Timestamp """ - # TODO: 你的程式碼 - pass + df = _load_data() + return df['order_date'].min(),df['order_date'].max() # ============================================================ @@ -59,8 +60,8 @@ 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').resample('ME')['amount'].sum() def yellow_rolling_avg(monthly_revenue): @@ -70,8 +71,8 @@ def yellow_rolling_avg(monthly_revenue): 回傳 Series(同樣 index,values=移動平均,前 2 筆可為 NaN) 提示:.rolling(window=3).mean() """ - # TODO: 你的程式碼 - pass + monthly_rev = yellow_monthly_revenue() + return monthly_rev.rolling(window=3).mean() def yellow_category_median(df): @@ -80,8 +81,7 @@ def yellow_category_median(df): 回傳 Series(index=category, values=中位數) 提示:groupby + median + sort_values """ - # TODO: 你的程式碼 - pass + return df.groupby(['category'])['amount'].median().sort_values(ascending=False) # ============================================================ @@ -100,5 +100,13 @@ def red_monthly_report(): index 為月份 (period 或 datetime) 提示:resample + agg + pct_change """ - # TODO: 你的程式碼 - pass + df = _load_data() + report = df.set_index('order_date').resample('ME').agg({ + 'order_id':'count', + 'amount':'sum', + 'customer_id':'nunique' + }) + report.columns=['order_count','revenue','active_customers'] + report['avg_order_value'] = report['revenue'] / report['order_count'] + report['revenue_growth'] = report['revenue'].pct_change() * 100 + return report diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..e6c8e9a 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -28,8 +28,25 @@ def green_bar_category(): 回傳 matplotlib Figure 物件 提示: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') + for i, v in enumerate(cat_counts['order_count']): + plt.text(i, v, f'{v:,}', ha='center', va='bottom', fontsize=10) + plt.tight_layout() + + return fig def green_hist_amount(): @@ -38,8 +55,14 @@ def green_hist_amount(): 回傳 matplotlib Figure 物件 提示:sns.histplot(bins=20) 或 plt.hist() """ - # TODO: 你的程式碼 - pass + df = _load_data() + fig = plt.figure(figsize=(9,4)) + sns.histplot(data=df, x='amount', bins=20, kde=True, color='steeblue') + plt.title('Order Amount Distribution', fontweight='bold') + plt.xlabel('Amount') + plt.ylabel('Frequency') + plt.tight_layout() + return fig def green_set_labels(): @@ -50,8 +73,15 @@ def green_set_labels(): - Y 軸標籤 (ylabel) 回傳 matplotlib Figure 物件 """ - # TODO: 你的程式碼 - pass + df = _load_data() + region_rev = df.groupby('region')['amount'].sum().sort_values(ascending=False).reset_index() + fig = plt.figure(figsize=(8,4)) + sns.barplot(data=region_rev, x='region', y='amount', palette='viridis', hue='region', legend=False) + plt.title('Revenue by Region', fontweight='bold') + plt.xlabel('Region') + plt.ylabel('Revenue') + plt.tight_layout() + return fig # ============================================================ @@ -67,8 +97,19 @@ def yellow_line_region_trend(): 回傳 matplotlib Figure 物件 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ - # TODO: 你的程式碼 - pass + df = _load_data() + ns_df = df[df['region'].isin(['North','South'])] + month_sn = ( + ns_df.groupby(['month','region'])['amount'].sum().reset_index() + ) + fig = plt.figure(figsize=(8,4)) + sns.lineplot(data=month_sn, 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') + plt.legend(title='Region') + plt.tight_layout() + return fig def yellow_box_vip(): @@ -77,8 +118,14 @@ def yellow_box_vip(): 回傳 matplotlib Figure 物件 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ - # TODO: 你的程式碼 - pass + df = _load_data() + fig = 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') + plt.tight_layout() + return fig def yellow_scatter_price_amount(): @@ -87,8 +134,14 @@ def yellow_scatter_price_amount(): 回傳 matplotlib Figure 物件 提示:plt.scatter() 或 sns.scatterplot() """ - # TODO: 你的程式碼 - pass + df = _load_data() + fig = plt.figure(figsize=(8, 5)) + sns.scatterplot(data=df, x='unit_price', y='amount', alpha=0.6, color='gold') + plt.title('Relationship between Unit Price and Order Amount', fontweight='bold') + plt.xlabel('Unit Price') + plt.ylabel('Amount') + plt.tight_layout() + return fig # ============================================================ @@ -106,5 +159,36 @@ def red_category_dashboard(category="Electronics"): 回傳 matplotlib Figure 物件 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ - # TODO: 你的程式碼 - pass + df = _load_data() + target_df = df[df['category'] == category].copy() + monthly = (target_df.groupby('month')['amount'].sum().reset_index()) + region = (target_df.groupby('region')['amount'].sum().sort_values(ascending=False).reset_index()) + top5 = (target_df.groupby('product_name')['amount'].sum().sort_values(ascending=False).head().reset_index()) + + fig, axes = plt.subplots(2, 2, figsize=(14,10)) + fig.suptitle('Electronics Category Dashboard', fontsize=16, fontweight='bold') + + sns.lineplot(data=monthly, x='month', y='amount', marker='o', linewidth=2, ax=axes[0,0]) + axes[0, 0].set_title('Monthly Revenue Trend') + axes[0, 0].set_xlabel('Month') + axes[0, 0].set_ylabel('Revenue') + + sns.barplot(data=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') + for i, v in enumerate(region['amount']): + axes[0, 1].text(i, v, f'{v:,.0f}', ha='center', va='bottom', fontsize=9) + + sns.barplot(data=top5, x='amount',y='product_name',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') + axes[1, 0].set_ylabel('Product') + + sns.histplot(data=target_df,x='amount',bins=25,kde=True,color="#d6273e", ax=axes[1,1]) + axes[1, 1].set_title('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..d5fdf9a 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -25,8 +25,17 @@ def green_plotly_bar(): 回傳 plotly Figure 物件 提示:px.bar() """ - # TODO: 你的程式碼 - pass + df = pd.read_csv('../datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + + category_sum = df.groupby('category', as_index=False)['amount'].sum().sort_values('amount', ascending=False) + + fig = px.bar(category_sum, x='category', y='amount', text='amount', color='category', title='Revenue by product category') + + fig.update_traces(texttemplate='%{text:,.0f}', textposition='outside') + + fig.update_layout(height=400, showlegend=False) + return fig def green_plotly_line(): @@ -36,8 +45,15 @@ def green_plotly_line(): 回傳 plotly Figure 物件 提示:先 groupby 月份算總營收,再 px.line() """ - # TODO: 你的程式碼 - pass + df = pd.read_csv('../datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + df['month'] = df['order_date'].dt.strftime('%Y-%m') + monthly_revenue = df.groupby('month', as_index=False)['amount'].sum().sort_values('month') + + fig = px.line(monthly_revenue, x='month', y='amount', text='amount', title='Revenue by monthly') + fig.update_xaxes(type='category') + fig.update_traces(texttemplate='%{text:,.0f}', textposition='top center') + return fig def green_plotly_pie(): @@ -47,8 +63,12 @@ def green_plotly_pie(): 回傳 plotly Figure 物件 提示:px.pie() """ - # TODO: 你的程式碼 - pass + df = pd.read_csv('../datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date'],) + vip_orders = df.groupby('vip_level', as_index=False)['order_id'].count() + fig = px.pie(vip_orders,names='vip_level',values='order_id',hole=0.2,title='Percentage of orders by VIP tier') + fig.update_traces(textinfo='percent+label') + return fig # ============================================================ @@ -62,8 +82,26 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 2. 合併 customers.csv 和 products.csv 回傳:合併後的 DataFrame """ - # TODO: 你的程式碼 - pass + df = pd.read_csv(raw_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() + orders = df + customers = pd.read_csv(customers_path) + products = pd.read_csv(products_path) + df = ( + orders + .merge(customers, on='customer_id', how='left') + .merge(products, on='product_id', how='left') + ) + + return df def yellow_kpi_summary(df): @@ -76,9 +114,13 @@ def yellow_kpi_summary(df): "avg_order_value": float, # 平均客單價 } """ - # TODO: 你的程式碼 - pass - + kpi = { + "total_revenue": float(df['amount'].sum()), + "order_count": int(len(df)), + "active_customers": int(df['customer_id'].nunique()), + "avg_order_value": float(df['amount'].sum() / len(df)), + } + return kpi def yellow_plotly_scatter(df): """ @@ -90,8 +132,15 @@ 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="Price vs Amount", + ) + return fig # ============================================================ @@ -114,5 +163,36 @@ def red_dashboard(): 回傳 plotly Figure 物件 提示:from plotly.subplots import make_subplots """ - # TODO: 你的程式碼 - pass + df = yellow_clean_and_merge( + "datasets/ecommerce/orders_raw.csv", + "datasets/ecommerce/customers.csv", + "datasets/ecommerce/products.csv", + ) + df["order_date"] = pd.to_datetime(df["order_date"]) + df["month"] = df["order_date"].dt.to_period("M").astype(str) + + fig = make_subplots( + rows=2, cols=2, + subplot_titles=('月營收趨勢', 'Top 10 商品營收', '各地區營收', '類別營收佔比'), + specs=[[{}, {}], [{}, {"type": "domain"}]] + ) + + # 左上:月營收趨勢 (line) + monthly = df.groupby('month',as_index=False)['amount'].sum() + fig.add_trace(go.Scatter(x=monthly['month'], y=monthly['amount'], + mode='lines+markers', name='Monthly'), + row=1, col=1) + + # 右上:Top 10 商品營收 (bar) + top10 = df.groupby('product_name',as_index=False)['amount'].sum().sort_values('amount',ascending=False).head(10) + fig.add_trace(go.Bar(x=top10['product_name'], y=top10['amount'],name='Top'), row=1, col=2) + + # 左下:各地區營收 (bar) + region = df.groupby('region',as_index=False)['amount'].sum() + fig.add_trace(go.Bar(x=region['region'], y=region['amount'],name='Region'), row=2, col=1) + + # 右下:類別營收佔比 (pie/donut) + category = df.groupby('category',as_index=False)['amount'].sum() + fig.add_trace(go.Pie(labels=category['category'], values=category['amount'],hole=0.3, name='Category'), row=2, col=2) + + return fig \ No newline at end of file From 47b1c10b3ce98330827a0a638075b1463674f694 Mon Sep 17 00:00:00 2001 From: kellyLiu Date: Sun, 3 May 2026 22:17:37 +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=20-=20=E5=8A=89=E5=87=B1=E8=8E=89=20-=20AIPE?= =?UTF-8?q?03?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- grading_report.md | 111 +++++++++++++++++++++++++++++++++ homework/m1_numpy.py | 2 +- homework/m2_pandas_cleaning.py | 8 +-- homework/m3_pandas_advanced.py | 27 ++++---- homework/m5_visualization.py | 6 +- homework/m6_plotly_capstone.py | 11 ++-- 6 files changed, 139 insertions(+), 26 deletions(-) create mode 100644 grading_report.md diff --git a/grading_report.md b/grading_report.md new file mode 100644 index 0000000..b41e9c1 --- /dev/null +++ b/grading_report.md @@ -0,0 +1,111 @@ +# 📊 作業批改結果 + +**總分:600 / 600(100%)— 等級 A** + +--- + +## M1 NumPy(100/100 — 100%) + +| 題目 | 分數 | 狀態 | +|:-----|:----:|:----:| +| `test_green_mean` | 10/10 | ✅ | +| `test_green_double` | 10/10 | ✅ | +| `test_green_filter` | 10/10 | ✅ | +| `test_yellow_expensive_count` | 15/15 | ✅ | +| `test_yellow_top3_stock_indices` | 15/15 | ✅ | +| `test_yellow_restock_cost` | 15/15 | ✅ | +| `test_red_double11_prices` | 20/20 | ✅ | +| `test_red_no_forloop` | 5/5 | ✅ | + +> ⚠️ 解答模組未找到 + +--- + +## M2 Pandas 清理(100/100 — 100%) + +| 題目 | 分數 | 狀態 | +|:-----|:----:|:----:| +| `test_green_read_csv` | 10/10 | ✅ | +| `test_green_shape` | 10/10 | ✅ | +| `test_green_dtypes` | 10/10 | ✅ | +| `test_yellow_clean_columns` | 15/15 | ✅ | +| `test_yellow_clean_amount` | 15/15 | ✅ | +| `test_yellow_drop_duplicates` | 15/15 | ✅ | +| `test_red_clean_orders_returns_df` | 4/4 | ✅ | +| `test_red_clean_orders_columns` | 4/4 | ✅ | +| `test_red_clean_orders_amount_is_numeric` | 4/4 | ✅ | +| `test_red_clean_orders_date_is_datetime` | 5/5 | ✅ | +| `test_red_clean_orders_no_nulls` | 4/4 | ✅ | +| `test_red_clean_orders_no_duplicates` | 4/4 | ✅ | + +> ⚠️ 解答模組未找到 + +--- + +## M3 Pandas 進階(100/100 — 100%) + +| 題目 | 分數 | 狀態 | +|:-----|:----:|:----:| +| `test_green_load_and_merge` | 10/10 | ✅ | +| `test_green_row_count` | 10/10 | ✅ | +| `test_green_column_list` | 10/10 | ✅ | +| `test_yellow_top_category` | 15/15 | ✅ | +| `test_yellow_gold_vip_stats` | 15/15 | ✅ | +| `test_yellow_region_avg_amount` | 15/15 | ✅ | +| `test_red_rfm_top5_shape` | 9/9 | ✅ | +| `test_red_rfm_top5_columns` | 8/8 | ✅ | +| `test_red_rfm_top5_sorted_by_m` | 8/8 | ✅ | + +> ⚠️ 解答模組未找到 + +--- + +## M4 時間序列(100/100 — 100%) + +| 題目 | 分數 | 狀態 | +|:-----|:----:|:----:| +| `test_green_avg_by_month` | 10/10 | ✅ | +| `test_green_top3_dates` | 10/10 | ✅ | +| `test_green_date_range` | 10/10 | ✅ | +| `test_yellow_monthly_revenue` | 15/15 | ✅ | +| `test_yellow_rolling_avg` | 15/15 | ✅ | +| `test_yellow_category_median` | 15/15 | ✅ | +| `test_red_monthly_report_columns` | 9/9 | ✅ | +| `test_red_monthly_report_values` | 8/8 | ✅ | +| `test_red_monthly_report_growth` | 8/8 | ✅ | + +> ⚠️ 解答模組未找到 + +--- + +## M5 視覺化(100/100 — 100%) + +| 題目 | 分數 | 狀態 | +|:-----|:----:|:----:| +| `test_green_bar_category` | 10/10 | ✅ | +| `test_green_hist_amount` | 10/10 | ✅ | +| `test_green_set_labels` | 10/10 | ✅ | +| `test_yellow_line_region_trend` | 15/15 | ✅ | +| `test_yellow_box_vip` | 15/15 | ✅ | +| `test_yellow_scatter_price_amount` | 15/15 | ✅ | +| `test_red_category_dashboard_is_figure` | 13/13 | ✅ | +| `test_red_category_dashboard_has_4_subplots` | 12/12 | ✅ | + +> ⚠️ 解答模組未找到 + +--- + +## M6 Plotly Capstone(100/100 — 100%) + +| 題目 | 分數 | 狀態 | +|:-----|:----:|:----:| +| `test_green_plotly_bar` | 10/10 | ✅ | +| `test_green_plotly_line` | 10/10 | ✅ | +| `test_green_plotly_pie` | 10/10 | ✅ | +| `test_yellow_clean_and_merge` | 15/15 | ✅ | +| `test_yellow_kpi_summary` | 15/15 | ✅ | +| `test_yellow_plotly_scatter` | 15/15 | ✅ | +| `test_red_dashboard_is_figure` | 13/13 | ✅ | +| `test_red_dashboard_has_traces` | 12/12 | ✅ | + +> ⚠️ 解答模組未找到 diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 07c7b96..e471522 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -19,7 +19,7 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" arr = np.array([10,20,30,40,50]) - return arr.mean() + return float(arr.mean()) def green_double(): """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" diff --git a/homework/m2_pandas_cleaning.py b/homework/m2_pandas_cleaning.py index ee9d16d..9d1c153 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -19,9 +19,7 @@ def green_read_csv(): 讀取 orders_raw.csv,回傳原始 DataFrame(不做任何清理) 提示:pd.read_csv() """ - DATA = '../datasets/ecommerce/orders_raw.csv' - df = pd.read_csv(DATA) - return df + return pd.read_csv('datasets/ecommerce/orders_raw.csv') def green_shape(df): @@ -95,8 +93,8 @@ def red_clean_orders(path): """ df = pd.read_csv(path) df.columns = df.columns.str.strip().str.lower() - df['amount'] = df['amount'].astype(str).replace('$','',regex=False).replace(',','',regex=False).astype(float) + 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','amount']) # 只要這兩個欄位中任一個有缺失值,該列就會被剔除。 df = df.drop_duplicates() - return df.shape[0] \ No newline at end of file + return df \ No newline at end of file diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index e0f77f1..46836c5 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -23,12 +23,15 @@ def green_load_and_merge(): - 再 LEFT JOIN products.csv ON product_id 提示:pd.merge(how='left') """ - 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') - - return orders + 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 def green_row_count(df): @@ -62,7 +65,7 @@ def yellow_gold_vip_stats(df): """ gold_mask = df[df['vip_level'] == 'Gold'] gold = gold_mask['amount'].agg(['count', 'sum']) - return f'訂單數: {int(gold['count'])}', f'總金額:{float(gold['sum'])}' + return (int(gold['count']), float(gold['sum'])) def yellow_region_avg_amount(df): """ @@ -70,8 +73,7 @@ def yellow_region_avg_amount(df): 回傳 Series(index=region, values=平均金額) 提示:groupby('region')['amount'].mean() """ - region_avg_amount = df.groupy('region')['amount'].mean().round(1) - return region_avg_amount + return df.groupby('region')['amount'].mean() # ============================================================ @@ -95,10 +97,11 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - rfm = df.groupby('customer_id','customer_name').agg( + rfm = df.groupby('customer_id').agg( R=('order_date', 'max'), F=('order_id', 'count'), - M=('amount', 'sum') + M=('amount', 'sum'), + customer_name=("customer_name", "first") ).reset_index() result = rfm.sort_values('M',ascending=False).head() - return result + return result[["customer_id", "customer_name", "R", "F", "M"]] diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index e6c8e9a..5b7bce8 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -57,7 +57,7 @@ def green_hist_amount(): """ df = _load_data() fig = plt.figure(figsize=(9,4)) - sns.histplot(data=df, x='amount', bins=20, kde=True, color='steeblue') + sns.histplot(data=df, x='amount', bins=30, kde=True, color='steelblue') plt.title('Order Amount Distribution', fontweight='bold') plt.xlabel('Amount') plt.ylabel('Frequency') @@ -98,6 +98,7 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ df = _load_data() + df["month"] = df["order_date"].dt.to_period("M").astype(str) ns_df = df[df['region'].isin(['North','South'])] month_sn = ( ns_df.groupby(['month','region'])['amount'].sum().reset_index() @@ -136,7 +137,7 @@ def yellow_scatter_price_amount(): """ df = _load_data() fig = plt.figure(figsize=(8, 5)) - sns.scatterplot(data=df, x='unit_price', y='amount', alpha=0.6, color='gold') + sns.scatterplot(data=df, x='unit_price', y='amount', alpha=0.5) plt.title('Relationship between Unit Price and Order Amount', fontweight='bold') plt.xlabel('Unit Price') plt.ylabel('Amount') @@ -160,6 +161,7 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ df = _load_data() + df["month"] = df["order_date"].dt.to_period("M").astype(str) target_df = df[df['category'] == category].copy() monthly = (target_df.groupby('month')['amount'].sum().reset_index()) region = (target_df.groupby('region')['amount'].sum().sort_values(ascending=False).reset_index()) diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index d5fdf9a..f49ab42 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -25,8 +25,7 @@ def green_plotly_bar(): 回傳 plotly Figure 物件 提示:px.bar() """ - df = pd.read_csv('../datasets/ecommerce/orders_enriched.csv', - parse_dates=['order_date'],) + df = pd.read_csv('datasets/ecommerce/orders_enriched.csv') category_sum = df.groupby('category', as_index=False)['amount'].sum().sort_values('amount', ascending=False) @@ -45,8 +44,8 @@ def green_plotly_line(): 回傳 plotly Figure 物件 提示:先 groupby 月份算總營收,再 px.line() """ - df = pd.read_csv('../datasets/ecommerce/orders_enriched.csv', - parse_dates=['order_date'],) + df = pd.read_csv('datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date']) df['month'] = df['order_date'].dt.strftime('%Y-%m') monthly_revenue = df.groupby('month', as_index=False)['amount'].sum().sort_values('month') @@ -63,8 +62,8 @@ def green_plotly_pie(): 回傳 plotly Figure 物件 提示:px.pie() """ - df = pd.read_csv('../datasets/ecommerce/orders_enriched.csv', - parse_dates=['order_date'],) + df = pd.read_csv('datasets/ecommerce/orders_enriched.csv', + parse_dates=['order_date']) vip_orders = df.groupby('vip_level', as_index=False)['order_id'].count() fig = px.pie(vip_orders,names='vip_level',values='order_id',hole=0.2,title='Percentage of orders by VIP tier') fig.update_traces(textinfo='percent+label')