From 35f23699e57c993540cf8109a0b53f6c67d4efa0 Mon Sep 17 00:00:00 2001 From: Bright Date: Wed, 29 Apr 2026 21:42:05 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=AD=98=E6=AA=94m1m2m3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m1_numpy.py | 22 ++++++++--------- homework/m2_pandas_cleaning.py | 28 +++++++++++++++------- homework/m3_pandas_advanced.py | 44 ++++++++++++++++++++++++++++------ 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/homework/m1_numpy.py b/homework/m1_numpy.py index 5ff41ab..39fd388 100644 --- a/homework/m1_numpy.py +++ b/homework/m1_numpy.py @@ -19,19 +19,20 @@ def green_mean(): """建立 [10, 20, 30, 40, 50],回傳所有元素的平均值 (float)""" # TODO: 你的程式碼 - pass - + nums = np.array([10, 20, 30, 40, 50]) + return float(nums.mean()) def green_double(): """建立 [10, 20, 30, 40, 50],回傳所有元素乘以 2 的 ndarray""" # TODO: 你的程式碼 - pass - + nums = np.array([10, 20, 30, 40, 50]) + return nums * 2 def green_filter(): """建立 [10, 20, 30, 40, 50],回傳大於 25 的元素 (ndarray)""" # TODO: 你的程式碼 - pass + nums = np.array([10, 20, 30, 40, 50]) + return nums[nums > 25] # ============================================================ @@ -42,8 +43,7 @@ def green_filter(): def yellow_expensive_count(prices): """回傳單價 > 1000 的商品數量 (int)""" # TODO: 你的程式碼 - pass - + return np.sum(prices > 1000) def yellow_top3_stock_indices(stocks): """ @@ -51,8 +51,7 @@ def yellow_top3_stock_indices(stocks): 提示:np.argsort """ # TODO: 你的程式碼 - pass - + return np.argsort(stocks)[::-1][:3] def yellow_restock_cost(prices, stocks): """ @@ -60,8 +59,7 @@ def yellow_restock_cost(prices, stocks): 提示:布林遮罩 + .sum() """ # TODO: 你的程式碼 - pass - + return np.sum(prices[prices < 500] * 50) # ============================================================ # 🔴 挑戰題(25 分) @@ -77,4 +75,4 @@ def red_double11_prices(prices, stocks): 提示: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..3468e6d 100644 --- a/homework/m2_pandas_cleaning.py +++ b/homework/m2_pandas_cleaning.py @@ -20,7 +20,8 @@ def green_read_csv(): 提示:pd.read_csv() """ # TODO: 你的程式碼 - pass + original_df = pd.read_csv('datasets/ecommerce/orders_raw.csv') + return original_df def green_shape(df): @@ -29,7 +30,7 @@ def green_shape(df): 提示:df.shape """ # TODO: 你的程式碼 - pass + return df.shape def green_dtypes(df): @@ -38,7 +39,7 @@ def green_dtypes(df): 提示:df.dtypes """ # TODO: 你的程式碼 - pass + return df.dtypes # ============================================================ @@ -52,7 +53,9 @@ def yellow_clean_columns(df): 提示:df.columns.str.strip().str.lower() """ # TODO: 你的程式碼 - pass + cleaned_df = df.copy() + cleaned_df.columns = cleaned_df.columns.str.strip().str.lower() + return cleaned_df def yellow_clean_amount(df): @@ -63,7 +66,9 @@ def yellow_clean_amount(df): 提示:.str.replace() + .astype(float) """ # TODO: 你的程式碼 - pass + cleaned_df = df.copy() + cleaned_df['amount'] = cleaned_df['amount'].str.replace('[\$,]', '', regex=True).astype(float) + return cleaned_df def yellow_drop_duplicates(df): @@ -72,8 +77,9 @@ def yellow_drop_duplicates(df): 提示:df.drop_duplicates() """ # TODO: 你的程式碼 - pass - + cleaned_df = df.copy() + cleaned_df = cleaned_df.drop_duplicates() + return cleaned_df # ============================================================ # 🔴 挑戰題(25 分) @@ -93,4 +99,10 @@ 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'].str.replace('[\$,]', '', regex=True).astype(float) + df['order_date'] = pd.to_datetime(df['order_date'], errors='coerce') + df = df.dropna(subset=['amount', 'order_date']) + df = df.drop_duplicates() + return df diff --git a/homework/m3_pandas_advanced.py b/homework/m3_pandas_advanced.py index 68410e8..ede393c 100644 --- a/homework/m3_pandas_advanced.py +++ b/homework/m3_pandas_advanced.py @@ -24,19 +24,25 @@ def green_load_and_merge(): 提示:pd.merge(how='left') """ # TODO: 你的程式碼 - pass + orders = pd.read_csv('datasets/ecommerce/orders_clean.csv') + customers = pd.read_csv('datasets/ecommerce/customers.csv') + products = pd.read_csv('datasets/ecommerce/products.csv') + + df = pd.merge(orders, customers, on='customer_id', how='left') + df = pd.merge(df, products, on='product_id', how='left') + return df def green_row_count(df): """回傳 DataFrame 的列數 (int)""" # TODO: 你的程式碼 - pass + return df.shape[0] def green_column_list(df): """回傳 DataFrame 的所有欄位名稱 (list)""" # TODO: 你的程式碼 - pass + return df.columns.tolist() # ============================================================ @@ -50,7 +56,9 @@ def yellow_top_category(df): 提示:groupby('category')['amount'].sum() """ # TODO: 你的程式碼 - pass + category_revenue = df.groupby('category')['amount'].sum() + top_category = category_revenue.idxmax() + return top_category def yellow_gold_vip_stats(df): @@ -60,7 +68,10 @@ def yellow_gold_vip_stats(df): 提示:df[df['vip_level'] == 'Gold'] """ # TODO: 你的程式碼 - pass + gold_vip_orders = df[df['vip_level'] == 'Gold'] + order_count = gold_vip_orders.shape[0] + total_amount = gold_vip_orders['amount'].sum() + return order_count, float(total_amount) def yellow_region_avg_amount(df): @@ -70,7 +81,8 @@ def yellow_region_avg_amount(df): 提示:groupby('region')['amount'].mean() """ # TODO: 你的程式碼 - pass + region_avg = df.groupby('region')['amount'].mean() + return region_avg # ============================================================ @@ -94,4 +106,22 @@ def red_rfm_top5(df): 提示:groupby('customer_id').agg(...) """ # TODO: 你的程式碼 - pass + 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' + ] + rfm = rfm.sort_values( + by='M', + ascending=False + ).head(5) + + return rfm From 7dd678e6fa80de0e5f9b48cc554b79eea927081b Mon Sep 17 00:00:00 2001 From: Bright Date: Tue, 5 May 2026 14:06:56 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BD=9C=E6=A5=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homework/m4_timeseries.py | 40 ++++++++++++++++---- homework/m5_visualization.py | 67 ++++++++++++++++++++++++++++++---- homework/m6_plotly_capstone.py | 53 +++++++++++++++++++++++++-- 3 files changed, 142 insertions(+), 18 deletions(-) diff --git a/homework/m4_timeseries.py b/homework/m4_timeseries.py index 047af6a..dc4c647 100644 --- a/homework/m4_timeseries.py +++ b/homework/m4_timeseries.py @@ -27,7 +27,9 @@ def green_avg_by_month(): 提示:df['order_date'].dt.month """ # TODO: 你的程式碼 - pass + df = _load_data() + df['month'] = df['order_date'].dt.month + return df.groupby('month')['amount'].mean() def green_top3_dates(): @@ -37,7 +39,8 @@ def green_top3_dates(): 提示:value_counts().head(3) """ # TODO: 你的程式碼 - pass + df = _load_data() + return df['order_date'].dt.date.value_counts().head(3) def green_date_range(): @@ -46,7 +49,10 @@ 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 # ============================================================ @@ -60,7 +66,12 @@ def yellow_monthly_revenue(): 提示: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): @@ -71,7 +82,7 @@ def yellow_rolling_avg(monthly_revenue): 提示:.rolling(window=3).mean() """ # TODO: 你的程式碼 - pass + return monthly_revenue.rolling(window=3).mean() def yellow_category_median(df): @@ -81,7 +92,8 @@ def yellow_category_median(df): 提示:groupby + median + sort_values """ # TODO: 你的程式碼 - pass + category_median = df.groupby('category')['amount'].median() + return category_median.sort_values(ascending=False) # ============================================================ @@ -101,4 +113,18 @@ def red_monthly_report(): 提示:resample + agg + pct_change """ # TODO: 你的程式碼 - pass + df = _load_data() + monthly_report = df.set_index('order_date').resample('ME').agg({ + 'order_id': 'count', + 'amount': 'sum', + 'customer_id': pd.Series.nunique + }) + monthly_report = monthly_report.rename(columns={ + 'order_id': 'order_count', + 'amount': 'revenue', + 'customer_id': 'active_customers' + }) + monthly_report['avg_order_value'] = monthly_report['amount'] / monthly_report['order_id'] + monthly_report['revenue_growth'] = monthly_report['amount'].pct_change() * 100 + monthly_report.rename(columns={ 'order_id': 'order_count', 'amount': 'revenue'}, inplace=True) + return monthly_report diff --git a/homework/m5_visualization.py b/homework/m5_visualization.py index 7e7335d..c66b4f1 100644 --- a/homework/m5_visualization.py +++ b/homework/m5_visualization.py @@ -29,7 +29,10 @@ def green_bar_category(): 提示:sns.countplot 或 value_counts().plot.bar() """ # TODO: 你的程式碼 - pass + df = _load_data() + plt.figure(figsize=(8, 5)) + sns.countplot(data=df, x='category') + return plt.gcf() def green_hist_amount(): @@ -39,7 +42,10 @@ def green_hist_amount(): 提示:sns.histplot(bins=20) 或 plt.hist() """ # TODO: 你的程式碼 - pass + df = _load_data() + plt.figure(figsize=(8, 5)) + sns.histplot(data=df, x='amount', bins=20) + return plt.gcf() def green_set_labels(): @@ -51,7 +57,13 @@ def green_set_labels(): 回傳 matplotlib Figure 物件 """ # TODO: 你的程式碼 - pass + df = _load_data() + plt.figure(figsize=(8, 5)) + sns.countplot(data=df, x='category') + plt.title("訂單數 by 商品類別") + plt.xlabel("商品類別") + plt.ylabel("訂單數") + return plt.gcf() # ============================================================ @@ -68,7 +80,15 @@ def yellow_line_region_trend(): 提示:分別 groupby 再 plot,或用 sns.lineplot(hue='region') """ # TODO: 你的程式碼 - pass + df = _load_data() + df['month'] = df['order_date'].dt.to_period('M') + monthly_revenue = df.groupby(['month', 'region'])['amount'].sum().reset_index() + plt.figure(figsize=(10, 6)) + sns.lineplot(data=monthly_revenue, x='month', y='amount', hue='region', marker='o') + plt.title("North vs South 月營收趨勢") + plt.xlabel("月份") + plt.ylabel("總營收") + return plt.gcf() def yellow_box_vip(): @@ -78,7 +98,10 @@ def yellow_box_vip(): 提示:sns.boxplot(x='vip_level', y='amount', data=df) """ # TODO: 你的程式碼 - pass + df = _load_data() + plt.figure(figsize=(8, 5)) + sns.boxplot(data=df, x='vip_level', y='amount') + return plt.gcf() def yellow_scatter_price_amount(): @@ -88,7 +111,10 @@ def yellow_scatter_price_amount(): 提示:plt.scatter() 或 sns.scatterplot() """ # TODO: 你的程式碼 - pass + df = _load_data() + plt.figure(figsize=(8, 5)) + sns.scatterplot(data=df, x='unit_price', y='amount') + return plt.gcf() # ============================================================ @@ -107,4 +133,31 @@ def red_category_dashboard(category="Electronics"): 提示:fig, axes = plt.subplots(2, 2, figsize=(14, 10)) """ # TODO: 你的程式碼 - pass + df = _load_data() + category_df = df[df['category'] == category] + fig, axes = plt.subplots(2, 2, figsize=(14, 10)) + # 1. 月營收趨勢 + category_df['month'] = category_df['order_date'].dt.to_period('M') + monthly_revenue = category_df.groupby('month')['amount'].sum().reset_index() + sns.lineplot(data=monthly_revenue, x='month', y='amount', ax=axes[0, 0], marker='o') + axes[0, 0].set_title(f"{category} 月營收趨勢") + axes[0, 0].set_xlabel("月份") + axes[0, 0].set_ylabel("總營收") + # 2. 各地區營收 + region_revenue = category_df.groupby('region')['amount'].sum().reset_index() + sns.barplot(data=region_revenue, x='region', y='amount', ax=axes[0, 1]) + axes[0, 1].set_title(f"{category} 各地區營收") + axes[0, 1].set_xlabel("地區") + axes[0, 1].set_ylabel("總營收") + # 3. Top 5 商品營收 + top_products = category_df.groupby('product_name')['amount'].sum().nlargest(5).reset_index() + sns.barplot(data=top_products, x='amount', y='product_name', ax=axes[1, 0]) + axes[1, 0].set_title(f"{category} Top 5 商品營收") + axes[1, 0].set_xlabel("總營收") + axes[1, 0].set_ylabel("商品名稱") + # 4. 訂單金額分佈 + sns.histplot(data=category_df, x='amount', bins=20, ax=axes[1, 1]) + axes[1, 1].set_title(f"{category} 訂單金額分佈") + axes[1, 1].set_xlabel("訂單金額") + axes[1, 1].set_ylabel("頻率") + return fig diff --git a/homework/m6_plotly_capstone.py b/homework/m6_plotly_capstone.py index 0e2c32a..216f66d 100644 --- a/homework/m6_plotly_capstone.py +++ b/homework/m6_plotly_capstone.py @@ -26,7 +26,24 @@ def green_plotly_bar(): 提示:px.bar() """ # TODO: 你的程式碼 - pass + df = pd.read_csv('orders_enriched.csv') + + # 計算每個 category 的總營收 + 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(): @@ -37,7 +54,16 @@ def green_plotly_line(): 提示:先 groupby 月份算總營收,再 px.line() """ # TODO: 你的程式碼 - pass + df = pd.read_csv('orders_enriched.csv') + df['month'] = df['order_date'].str.slice(0, 7) + 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(): @@ -48,7 +74,14 @@ def green_plotly_pie(): 提示:px.pie() """ # TODO: 你的程式碼 - pass + df = pd.read_csv('orders_enriched.csv') + fig = px.pie( + df, + values='order_count', + names='vip_level', + title='VIP 等級訂單數佔比' + ) + return fig # ============================================================ @@ -63,7 +96,19 @@ def yellow_clean_and_merge(raw_path, customers_path, products_path): 回傳:合併後的 DataFrame """ # TODO: 你的程式碼 - pass + orders = pd.read_csv(raw_path) + customers = pd.read_csv(customers_path) + products = pd.read_csv(products_path) + + # 清理 orders DataFrame + orders = orders.dropna() + orders = orders.drop_duplicates() + + # 合併 DataFrame + df = pd.merge(orders, customers, on='customer_id') + df = pd.merge(df, products, on='product_id') + + return df def yellow_kpi_summary(df):